summaryrefslogtreecommitdiff
path: root/drivers/char
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/char
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/char')
-rw-r--r--drivers/char/ChangeLog775
-rw-r--r--drivers/char/Kconfig988
-rw-r--r--drivers/char/Makefile118
-rw-r--r--drivers/char/agp/Kconfig171
-rw-r--r--drivers/char/agp/Makefile18
-rw-r--r--drivers/char/agp/agp.h331
-rw-r--r--drivers/char/agp/ali-agp.c414
-rw-r--r--drivers/char/agp/alpha-agp.c216
-rw-r--r--drivers/char/agp/amd-k7-agp.c542
-rw-r--r--drivers/char/agp/amd64-agp.c761
-rw-r--r--drivers/char/agp/ati-agp.c548
-rw-r--r--drivers/char/agp/backend.c348
-rw-r--r--drivers/char/agp/efficeon-agp.c463
-rw-r--r--drivers/char/agp/frontend.c1103
-rw-r--r--drivers/char/agp/generic.c1222
-rw-r--r--drivers/char/agp/hp-agp.c552
-rw-r--r--drivers/char/agp/i460-agp.c642
-rw-r--r--drivers/char/agp/intel-agp.c1833
-rw-r--r--drivers/char/agp/isoch.c470
-rw-r--r--drivers/char/agp/nvidia-agp.c424
-rw-r--r--drivers/char/agp/sgi-agp.c331
-rw-r--r--drivers/char/agp/sis-agp.c360
-rw-r--r--drivers/char/agp/sworks-agp.c556
-rw-r--r--drivers/char/agp/uninorth-agp.c647
-rw-r--r--drivers/char/agp/via-agp.c548
-rw-r--r--drivers/char/amiserial.c2179
-rw-r--r--drivers/char/applicom.c862
-rw-r--r--drivers/char/applicom.h85
-rw-r--r--drivers/char/cd1865.h263
-rw-r--r--drivers/char/consolemap.c672
-rw-r--r--drivers/char/cp437.uni291
-rw-r--r--drivers/char/cyclades.c5540
-rw-r--r--drivers/char/decserial.c100
-rw-r--r--drivers/char/defkeymap.c_shipped262
-rw-r--r--drivers/char/defkeymap.map357
-rw-r--r--drivers/char/digi.h71
-rw-r--r--drivers/char/digi1.h100
-rw-r--r--drivers/char/digiFep1.h136
-rw-r--r--drivers/char/digiPCI.h42
-rw-r--r--drivers/char/drm/Kconfig98
-rw-r--r--drivers/char/drm/Makefile33
-rw-r--r--drivers/char/drm/README.drm46
-rw-r--r--drivers/char/drm/ati_pcigart.c208
-rw-r--r--drivers/char/drm/drm.h675
-rw-r--r--drivers/char/drm/drmP.h1073
-rw-r--r--drivers/char/drm/drm_agpsupport.c448
-rw-r--r--drivers/char/drm/drm_auth.c230
-rw-r--r--drivers/char/drm/drm_bufs.c1270
-rw-r--r--drivers/char/drm/drm_context.c578
-rw-r--r--drivers/char/drm/drm_core.h34
-rw-r--r--drivers/char/drm/drm_dma.c180
-rw-r--r--drivers/char/drm/drm_drawable.c56
-rw-r--r--drivers/char/drm/drm_drv.c531
-rw-r--r--drivers/char/drm/drm_fops.c451
-rw-r--r--drivers/char/drm/drm_init.c52
-rw-r--r--drivers/char/drm/drm_ioctl.c370
-rw-r--r--drivers/char/drm/drm_irq.c370
-rw-r--r--drivers/char/drm/drm_lock.c303
-rw-r--r--drivers/char/drm/drm_memory.c181
-rw-r--r--drivers/char/drm/drm_memory.h197
-rw-r--r--drivers/char/drm/drm_memory_debug.h459
-rw-r--r--drivers/char/drm/drm_os_linux.h149
-rw-r--r--drivers/char/drm/drm_pci.c140
-rw-r--r--drivers/char/drm/drm_pciids.h224
-rw-r--r--drivers/char/drm/drm_proc.c539
-rw-r--r--drivers/char/drm/drm_sarea.h78
-rw-r--r--drivers/char/drm/drm_scatter.c231
-rw-r--r--drivers/char/drm/drm_stub.c319
-rw-r--r--drivers/char/drm/drm_sysfs.c208
-rw-r--r--drivers/char/drm/drm_vm.c678
-rw-r--r--drivers/char/drm/ffb_context.c551
-rw-r--r--drivers/char/drm/ffb_drv.c365
-rw-r--r--drivers/char/drm/ffb_drv.h286
-rw-r--r--drivers/char/drm/gamma_context.h492
-rw-r--r--drivers/char/drm/gamma_dma.c946
-rw-r--r--drivers/char/drm/gamma_drm.h90
-rw-r--r--drivers/char/drm/gamma_drv.c59
-rw-r--r--drivers/char/drm/gamma_drv.h147
-rw-r--r--drivers/char/drm/gamma_lists.h215
-rw-r--r--drivers/char/drm/gamma_lock.h140
-rw-r--r--drivers/char/drm/gamma_old_dma.h313
-rw-r--r--drivers/char/drm/i810_dma.c1385
-rw-r--r--drivers/char/drm/i810_drm.h289
-rw-r--r--drivers/char/drm/i810_drv.c126
-rw-r--r--drivers/char/drm/i810_drv.h236
-rw-r--r--drivers/char/drm/i830_dma.c1588
-rw-r--r--drivers/char/drm/i830_drm.h350
-rw-r--r--drivers/char/drm/i830_drv.c137
-rw-r--r--drivers/char/drm/i830_drv.h301
-rw-r--r--drivers/char/drm/i830_irq.c204
-rw-r--r--drivers/char/drm/i915_dma.c725
-rw-r--r--drivers/char/drm/i915_drm.h167
-rw-r--r--drivers/char/drm/i915_drv.c104
-rw-r--r--drivers/char/drm/i915_drv.h243
-rw-r--r--drivers/char/drm/i915_irq.c161
-rw-r--r--drivers/char/drm/i915_mem.c346
-rw-r--r--drivers/char/drm/mga_dma.c754
-rw-r--r--drivers/char/drm/mga_drm.h349
-rw-r--r--drivers/char/drm/mga_drv.c127
-rw-r--r--drivers/char/drm/mga_drv.h638
-rw-r--r--drivers/char/drm/mga_irq.c102
-rw-r--r--drivers/char/drm/mga_state.c1123
-rw-r--r--drivers/char/drm/mga_ucode.h11645
-rw-r--r--drivers/char/drm/mga_warp.c210
-rw-r--r--drivers/char/drm/r128_cce.c943
-rw-r--r--drivers/char/drm/r128_drm.h345
-rw-r--r--drivers/char/drm/r128_drv.c122
-rw-r--r--drivers/char/drm/r128_drv.h521
-rw-r--r--drivers/char/drm/r128_irq.c102
-rw-r--r--drivers/char/drm/r128_state.c1732
-rw-r--r--drivers/char/drm/radeon_cp.c2061
-rw-r--r--drivers/char/drm/radeon_drm.h659
-rw-r--r--drivers/char/drm/radeon_drv.c127
-rw-r--r--drivers/char/drm/radeon_drv.h1044
-rw-r--r--drivers/char/drm/radeon_irq.c251
-rw-r--r--drivers/char/drm/radeon_mem.c322
-rw-r--r--drivers/char/drm/radeon_state.c3102
-rw-r--r--drivers/char/drm/sis_drm.h42
-rw-r--r--drivers/char/drm/sis_drv.c110
-rw-r--r--drivers/char/drm/sis_drv.h52
-rw-r--r--drivers/char/drm/sis_ds.c301
-rw-r--r--drivers/char/drm/sis_ds.h145
-rw-r--r--drivers/char/drm/sis_mm.c417
-rw-r--r--drivers/char/drm/tdfx_drv.c107
-rw-r--r--drivers/char/drm/tdfx_drv.h50
-rw-r--r--drivers/char/ds1286.c578
-rw-r--r--drivers/char/ds1302.c354
-rw-r--r--drivers/char/ds1620.c416
-rw-r--r--drivers/char/dsp56k.c547
-rw-r--r--drivers/char/dtlk.c659
-rw-r--r--drivers/char/ec3104_keyb.c459
-rw-r--r--drivers/char/efirtc.c417
-rw-r--r--drivers/char/epca.c3789
-rw-r--r--drivers/char/epca.h165
-rw-r--r--drivers/char/epcaconfig.h7
-rw-r--r--drivers/char/esp.c2630
-rw-r--r--drivers/char/ftape/Kconfig340
-rw-r--r--drivers/char/ftape/Makefile28
-rw-r--r--drivers/char/ftape/README.PCI81
-rw-r--r--drivers/char/ftape/RELEASE-NOTES966
-rw-r--r--drivers/char/ftape/compressor/Makefile31
-rw-r--r--drivers/char/ftape/compressor/lzrw3.c743
-rw-r--r--drivers/char/ftape/compressor/lzrw3.h253
-rw-r--r--drivers/char/ftape/compressor/zftape-compress.c1203
-rw-r--r--drivers/char/ftape/compressor/zftape-compress.h83
-rw-r--r--drivers/char/ftape/lowlevel/Makefile43
-rw-r--r--drivers/char/ftape/lowlevel/fc-10.c175
-rw-r--r--drivers/char/ftape/lowlevel/fc-10.h39
-rw-r--r--drivers/char/ftape/lowlevel/fdc-io.c1352
-rw-r--r--drivers/char/ftape/lowlevel/fdc-io.h252
-rw-r--r--drivers/char/ftape/lowlevel/fdc-isr.c1170
-rw-r--r--drivers/char/ftape/lowlevel/fdc-isr.h55
-rw-r--r--drivers/char/ftape/lowlevel/ftape-bsm.c491
-rw-r--r--drivers/char/ftape/lowlevel/ftape-bsm.h66
-rw-r--r--drivers/char/ftape/lowlevel/ftape-buffer.c129
-rw-r--r--drivers/char/ftape/lowlevel/ftape-buffer.h32
-rw-r--r--drivers/char/ftape/lowlevel/ftape-calibr.c276
-rw-r--r--drivers/char/ftape/lowlevel/ftape-calibr.h37
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ctl.c897
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ctl.h162
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ecc.c853
-rw-r--r--drivers/char/ftape/lowlevel/ftape-ecc.h84
-rw-r--r--drivers/char/ftape/lowlevel/ftape-format.c344
-rw-r--r--drivers/char/ftape/lowlevel/ftape-format.h37
-rw-r--r--drivers/char/ftape/lowlevel/ftape-init.c161
-rw-r--r--drivers/char/ftape/lowlevel/ftape-init.h43
-rw-r--r--drivers/char/ftape/lowlevel/ftape-io.c992
-rw-r--r--drivers/char/ftape/lowlevel/ftape-io.h90
-rw-r--r--drivers/char/ftape/lowlevel/ftape-proc.c215
-rw-r--r--drivers/char/ftape/lowlevel/ftape-proc.h35
-rw-r--r--drivers/char/ftape/lowlevel/ftape-read.c621
-rw-r--r--drivers/char/ftape/lowlevel/ftape-read.h51
-rw-r--r--drivers/char/ftape/lowlevel/ftape-rw.c1092
-rw-r--r--drivers/char/ftape/lowlevel/ftape-rw.h111
-rw-r--r--drivers/char/ftape/lowlevel/ftape-setup.c105
-rw-r--r--drivers/char/ftape/lowlevel/ftape-tracing.c118
-rw-r--r--drivers/char/ftape/lowlevel/ftape-tracing.h180
-rw-r--r--drivers/char/ftape/lowlevel/ftape-write.c336
-rw-r--r--drivers/char/ftape/lowlevel/ftape-write.h53
-rw-r--r--drivers/char/ftape/lowlevel/ftape_syms.c88
-rw-r--r--drivers/char/ftape/zftape/Makefile36
-rw-r--r--drivers/char/ftape/zftape/zftape-buffers.c149
-rw-r--r--drivers/char/ftape/zftape/zftape-buffers.h55
-rw-r--r--drivers/char/ftape/zftape/zftape-ctl.c1418
-rw-r--r--drivers/char/ftape/zftape/zftape-ctl.h59
-rw-r--r--drivers/char/ftape/zftape/zftape-eof.c199
-rw-r--r--drivers/char/ftape/zftape/zftape-eof.h52
-rw-r--r--drivers/char/ftape/zftape/zftape-init.c403
-rw-r--r--drivers/char/ftape/zftape/zftape-init.h77
-rw-r--r--drivers/char/ftape/zftape/zftape-read.c377
-rw-r--r--drivers/char/ftape/zftape/zftape-read.h53
-rw-r--r--drivers/char/ftape/zftape/zftape-rw.c376
-rw-r--r--drivers/char/ftape/zftape/zftape-rw.h102
-rw-r--r--drivers/char/ftape/zftape/zftape-vtbl.c757
-rw-r--r--drivers/char/ftape/zftape/zftape-vtbl.h227
-rw-r--r--drivers/char/ftape/zftape/zftape-write.c483
-rw-r--r--drivers/char/ftape/zftape/zftape-write.h38
-rw-r--r--drivers/char/ftape/zftape/zftape_syms.c43
-rw-r--r--drivers/char/generic_nvram.c145
-rw-r--r--drivers/char/generic_serial.c1001
-rw-r--r--drivers/char/genrtc.c535
-rw-r--r--drivers/char/hangcheck-timer.c129
-rw-r--r--drivers/char/hpet.c994
-rw-r--r--drivers/char/hvc_console.c831
-rw-r--r--drivers/char/hvcs.c1649
-rw-r--r--drivers/char/hvsi.c1320
-rw-r--r--drivers/char/hw_random.c630
-rw-r--r--drivers/char/i8k.c788
-rw-r--r--drivers/char/ip2.c110
-rw-r--r--drivers/char/ip2/fip_firm.h2149
-rw-r--r--drivers/char/ip2/i2cmd.c209
-rw-r--r--drivers/char/ip2/i2cmd.h643
-rw-r--r--drivers/char/ip2/i2ellis.c1487
-rw-r--r--drivers/char/ip2/i2ellis.h615
-rw-r--r--drivers/char/ip2/i2hw.h648
-rw-r--r--drivers/char/ip2/i2lib.c2219
-rw-r--r--drivers/char/ip2/i2lib.h351
-rw-r--r--drivers/char/ip2/i2os.h127
-rw-r--r--drivers/char/ip2/i2pack.h364
-rw-r--r--drivers/char/ip2/ip2.h107
-rw-r--r--drivers/char/ip2/ip2ioctl.h35
-rw-r--r--drivers/char/ip2/ip2trace.h42
-rw-r--r--drivers/char/ip2/ip2types.h57
-rw-r--r--drivers/char/ip27-rtc.c327
-rw-r--r--drivers/char/ip2main.c3265
-rw-r--r--drivers/char/ipmi/Kconfig67
-rw-r--r--drivers/char/ipmi/Makefile15
-rw-r--r--drivers/char/ipmi/ipmi_bt_sm.c513
-rw-r--r--drivers/char/ipmi/ipmi_devintf.c582
-rw-r--r--drivers/char/ipmi/ipmi_kcs_sm.c500
-rw-r--r--drivers/char/ipmi/ipmi_msghandler.c3174
-rw-r--r--drivers/char/ipmi/ipmi_poweroff.c549
-rw-r--r--drivers/char/ipmi/ipmi_si_intf.c2359
-rw-r--r--drivers/char/ipmi/ipmi_si_sm.h120
-rw-r--r--drivers/char/ipmi/ipmi_smic_sm.c599
-rw-r--r--drivers/char/ipmi/ipmi_watchdog.c1068
-rw-r--r--drivers/char/isicom.c2079
-rw-r--r--drivers/char/istallion.c5276
-rw-r--r--drivers/char/ite_gpio.c419
-rw-r--r--drivers/char/keyboard.c1254
-rw-r--r--drivers/char/lcd.c683
-rw-r--r--drivers/char/lcd.h186
-rw-r--r--drivers/char/lp.c995
-rw-r--r--drivers/char/mem.c880
-rw-r--r--drivers/char/misc.c331
-rw-r--r--drivers/char/mmtimer.c725
-rw-r--r--drivers/char/moxa.c3243
-rw-r--r--drivers/char/mwave/3780i.c727
-rw-r--r--drivers/char/mwave/3780i.h362
-rw-r--r--drivers/char/mwave/Makefile15
-rw-r--r--drivers/char/mwave/README50
-rw-r--r--drivers/char/mwave/mwavedd.c674
-rw-r--r--drivers/char/mwave/mwavedd.h150
-rw-r--r--drivers/char/mwave/mwavepub.h89
-rw-r--r--drivers/char/mwave/smapi.c570
-rw-r--r--drivers/char/mwave/smapi.h80
-rw-r--r--drivers/char/mwave/tp3780i.c592
-rw-r--r--drivers/char/mwave/tp3780i.h103
-rw-r--r--drivers/char/mxser.c3170
-rw-r--r--drivers/char/mxser.h450
-rw-r--r--drivers/char/n_hdlc.c978
-rw-r--r--drivers/char/n_r3964.c1416
-rw-r--r--drivers/char/n_tty.c1562
-rw-r--r--drivers/char/nvram.c926
-rw-r--r--drivers/char/nwbutton.c248
-rw-r--r--drivers/char/nwbutton.h40
-rw-r--r--drivers/char/nwflash.c702
-rw-r--r--drivers/char/pcmcia/Kconfig22
-rw-r--r--drivers/char/pcmcia/Makefile7
-rw-r--r--drivers/char/pcmcia/synclink_cs.c4611
-rw-r--r--drivers/char/ppdev.c824
-rw-r--r--drivers/char/pty.c412
-rw-r--r--drivers/char/qtronix.c601
-rw-r--r--drivers/char/qtronixmap.c_shipped265
-rw-r--r--drivers/char/qtronixmap.map287
-rw-r--r--drivers/char/random.c1629
-rw-r--r--drivers/char/raw.c342
-rw-r--r--drivers/char/rio/Makefile12
-rw-r--r--drivers/char/rio/board.h143
-rw-r--r--drivers/char/rio/bootpkt.h62
-rw-r--r--drivers/char/rio/brates.h107
-rw-r--r--drivers/char/rio/chan.h33
-rw-r--r--drivers/char/rio/cirrus.h463
-rw-r--r--drivers/char/rio/cmd.h84
-rw-r--r--drivers/char/rio/cmdblk.h60
-rw-r--r--drivers/char/rio/cmdpkt.h206
-rw-r--r--drivers/char/rio/control.h62
-rw-r--r--drivers/char/rio/daemon.h334
-rw-r--r--drivers/char/rio/data.h40
-rw-r--r--drivers/char/rio/debug.h39
-rw-r--r--drivers/char/rio/defaults.h59
-rw-r--r--drivers/char/rio/eisa.h104
-rw-r--r--drivers/char/rio/enable.h50
-rw-r--r--drivers/char/rio/error.h85
-rw-r--r--drivers/char/rio/errors.h104
-rw-r--r--drivers/char/rio/formpkt.h154
-rw-r--r--drivers/char/rio/func.h154
-rw-r--r--drivers/char/rio/host.h134
-rw-r--r--drivers/char/rio/hosthw.h57
-rw-r--r--drivers/char/rio/link.h188
-rw-r--r--drivers/char/rio/linux_compat.h122
-rw-r--r--drivers/char/rio/list.h196
-rw-r--r--drivers/char/rio/lrt.h55
-rw-r--r--drivers/char/rio/ltt.h55
-rw-r--r--drivers/char/rio/lttwake.h53
-rw-r--r--drivers/char/rio/map.h103
-rw-r--r--drivers/char/rio/mca.h73
-rw-r--r--drivers/char/rio/mesg.h41
-rw-r--r--drivers/char/rio/param.h61
-rw-r--r--drivers/char/rio/parmmap.h96
-rw-r--r--drivers/char/rio/pci.h76
-rw-r--r--drivers/char/rio/phb.h293
-rw-r--r--drivers/char/rio/pkt.h120
-rw-r--r--drivers/char/rio/poll.h76
-rw-r--r--drivers/char/rio/port.h245
-rw-r--r--drivers/char/rio/proto.h244
-rw-r--r--drivers/char/rio/protsts.h119
-rw-r--r--drivers/char/rio/qbuf.h67
-rw-r--r--drivers/char/rio/rio.h294
-rw-r--r--drivers/char/rio/rio_linux.c1380
-rw-r--r--drivers/char/rio/rio_linux.h187
-rw-r--r--drivers/char/rio/rioboard.h281
-rw-r--r--drivers/char/rio/rioboot.c1360
-rw-r--r--drivers/char/rio/riocmd.c1041
-rw-r--r--drivers/char/rio/rioctrl.c1869
-rw-r--r--drivers/char/rio/riodrvr.h144
-rw-r--r--drivers/char/rio/rioinfo.h96
-rw-r--r--drivers/char/rio/rioinit.c1617
-rw-r--r--drivers/char/rio/riointr.c951
-rw-r--r--drivers/char/rio/rioioctl.h103
-rw-r--r--drivers/char/rio/riolocks.h43
-rw-r--r--drivers/char/rio/rioparam.c744
-rw-r--r--drivers/char/rio/riopcicopy.c8
-rw-r--r--drivers/char/rio/rioroute.c1238
-rw-r--r--drivers/char/rio/riospace.h161
-rw-r--r--drivers/char/rio/riotable.c1044
-rw-r--r--drivers/char/rio/riotime.h63
-rw-r--r--drivers/char/rio/riotty.c1376
-rw-r--r--drivers/char/rio/riotypes.h135
-rw-r--r--drivers/char/rio/riowinif.h1335
-rw-r--r--drivers/char/rio/riscos.h63
-rw-r--r--drivers/char/rio/rom.h64
-rw-r--r--drivers/char/rio/route.h108
-rw-r--r--drivers/char/rio/rtahw.h75
-rw-r--r--drivers/char/rio/rup.h82
-rw-r--r--drivers/char/rio/rupstat.h51
-rw-r--r--drivers/char/rio/sam.h74
-rw-r--r--drivers/char/rio/selftest.h73
-rw-r--r--drivers/char/rio/space.h45
-rw-r--r--drivers/char/rio/sysmap.h63
-rw-r--r--drivers/char/rio/timeouts.h51
-rw-r--r--drivers/char/rio/top.h49
-rw-r--r--drivers/char/rio/typdef.h82
-rw-r--r--drivers/char/rio/unixrup.h56
-rw-r--r--drivers/char/riscom8.c1809
-rw-r--r--drivers/char/riscom8.h102
-rw-r--r--drivers/char/riscom8_reg.h254
-rw-r--r--drivers/char/rocket.c3299
-rw-r--r--drivers/char/rocket.h111
-rw-r--r--drivers/char/rocket_int.h1296
-rw-r--r--drivers/char/rtc.c1354
-rw-r--r--drivers/char/s3c2410-rtc.c588
-rw-r--r--drivers/char/scan_keyb.c149
-rw-r--r--drivers/char/scan_keyb.h15
-rw-r--r--drivers/char/scc.h613
-rw-r--r--drivers/char/scx200_gpio.c149
-rw-r--r--drivers/char/selection.c306
-rw-r--r--drivers/char/ser_a2232.c825
-rw-r--r--drivers/char/ser_a2232.h202
-rw-r--r--drivers/char/ser_a2232fw.ax529
-rw-r--r--drivers/char/ser_a2232fw.h306
-rw-r--r--drivers/char/serial167.c2858
-rw-r--r--drivers/char/snsc.c448
-rw-r--r--drivers/char/snsc.h50
-rw-r--r--drivers/char/sonypi.c1403
-rw-r--r--drivers/char/specialix.c2610
-rw-r--r--drivers/char/specialix_io8.h149
-rw-r--r--drivers/char/stallion.c5197
-rw-r--r--drivers/char/sx.c2621
-rw-r--r--drivers/char/sx.h202
-rw-r--r--drivers/char/sxboards.h206
-rw-r--r--drivers/char/sxwindow.h393
-rw-r--r--drivers/char/synclink.c8214
-rw-r--r--drivers/char/synclinkmp.c5671
-rw-r--r--drivers/char/sysrq.c432
-rw-r--r--drivers/char/tb0219.c347
-rw-r--r--drivers/char/tipar.c564
-rw-r--r--drivers/char/toshiba.c532
-rw-r--r--drivers/char/tpm/Kconfig39
-rw-r--r--drivers/char/tpm/Makefile7
-rw-r--r--drivers/char/tpm/tpm.c697
-rw-r--r--drivers/char/tpm/tpm.h93
-rw-r--r--drivers/char/tpm/tpm_atmel.c216
-rw-r--r--drivers/char/tpm/tpm_nsc.c373
-rw-r--r--drivers/char/tty_io.c2980
-rw-r--r--drivers/char/tty_ioctl.c551
-rw-r--r--drivers/char/vc_screen.c509
-rw-r--r--drivers/char/viocons.c1195
-rw-r--r--drivers/char/viotape.c1129
-rw-r--r--drivers/char/vme_scc.c1056
-rw-r--r--drivers/char/vr41xx_rtc.c709
-rw-r--r--drivers/char/vt.c3242
-rw-r--r--drivers/char/vt_ioctl.c1201
-rw-r--r--drivers/char/watchdog/Kconfig549
-rw-r--r--drivers/char/watchdog/Makefile42
-rw-r--r--drivers/char/watchdog/acquirewdt.c332
-rw-r--r--drivers/char/watchdog/advantechwdt.c333
-rw-r--r--drivers/char/watchdog/alim1535_wdt.c463
-rw-r--r--drivers/char/watchdog/alim7101_wdt.c421
-rw-r--r--drivers/char/watchdog/cpu5wdt.c303
-rw-r--r--drivers/char/watchdog/eurotechwdt.c474
-rw-r--r--drivers/char/watchdog/i8xx_tco.c535
-rw-r--r--drivers/char/watchdog/i8xx_tco.h42
-rw-r--r--drivers/char/watchdog/ib700wdt.c352
-rw-r--r--drivers/char/watchdog/indydog.c221
-rw-r--r--drivers/char/watchdog/ixp2000_wdt.c219
-rw-r--r--drivers/char/watchdog/ixp4xx_wdt.c230
-rw-r--r--drivers/char/watchdog/machzwd.c501
-rw-r--r--drivers/char/watchdog/mixcomwd.c306
-rw-r--r--drivers/char/watchdog/mpc8xx_wdt.c164
-rw-r--r--drivers/char/watchdog/pcwd.c926
-rw-r--r--drivers/char/watchdog/pcwd_pci.c677
-rw-r--r--drivers/char/watchdog/pcwd_usb.c796
-rw-r--r--drivers/char/watchdog/s3c2410_wdt.c516
-rw-r--r--drivers/char/watchdog/sa1100_wdt.c223
-rw-r--r--drivers/char/watchdog/sbc60xxwdt.c413
-rw-r--r--drivers/char/watchdog/sc1200wdt.c467
-rw-r--r--drivers/char/watchdog/sc520_wdt.c447
-rw-r--r--drivers/char/watchdog/scx200_wdt.c274
-rw-r--r--drivers/char/watchdog/shwdt.c452
-rw-r--r--drivers/char/watchdog/softdog.c309
-rw-r--r--drivers/char/watchdog/w83627hf_wdt.c362
-rw-r--r--drivers/char/watchdog/w83877f_wdt.c426
-rw-r--r--drivers/char/watchdog/wafer5823wdt.c330
-rw-r--r--drivers/char/watchdog/wd501p.h52
-rw-r--r--drivers/char/watchdog/wdt.c647
-rw-r--r--drivers/char/watchdog/wdt285.c229
-rw-r--r--drivers/char/watchdog/wdt977.c459
-rw-r--r--drivers/char/watchdog/wdt_pci.c763
439 files changed, 264246 insertions, 0 deletions
diff --git a/drivers/char/ChangeLog b/drivers/char/ChangeLog
new file mode 100644
index 000000000000..56b8a2e76ab1
--- /dev/null
+++ b/drivers/char/ChangeLog
@@ -0,0 +1,775 @@
+2001-08-11 Tim Waugh <twaugh@redhat.com>
+
+ * serial.c (get_pci_port): Deal with awkward Titan cards.
+
+1998-08-26 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (rs_open): Correctly decrement the module in-use count
+ on errors.
+
+Thu Feb 19 14:24:08 1998 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (tty_name): Remove the non-reentrant (and non-SMP safe)
+ version of tty_name, and rename the reentrant _tty_name
+ function to be tty_name.
+ (tty_open): Add a warning message stating callout devices
+ are deprecated.
+
+Mon Dec 1 08:24:15 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (tty_get_baud_rate): Print a warning syslog if the
+ tty->alt_speed kludge is used; this means the system is
+ using the deprecated SPD_HI ioctls.
+
+Mon Nov 24 10:37:49 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c, esp.c, rocket.c: Change drivers to take advantage of
+ tty_get_baud_rate().
+
+ * tty_io.c (tty_get_baud_rate): New function which computes the
+ correct baud rate for the tty. More factoring out of
+ common code out of the serial driver to the high-level tty
+ functions....
+
+Sat Nov 22 07:53:36 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c, esp.c, rocket.c: Add tty->driver.break() routine, and
+ allow high-level tty code to handle the break and soft
+ carrier ioctls.
+
+ * tty_ioctl.c (n_tty_ioctl): Support TIOCGSOFTCAR and
+ TIOCSSOFTCAR, so that device drivers don't have to support
+ it.
+
+ * serial.c (autoconfig): Change 16750 test to hopefully eliminate
+ false results by people with strange 16550As being
+ detected as 16750s. Hopefully 16750s will still be
+ detected as 16750, and other weird UARTs won't get poorly
+ autodetected. If this doesn't work, I'll have to disable
+ the auto identification for the 16750.
+
+ * tty_io.c (tty_hangup): Now actually do the tty hangup
+ processing during the timer processing, and disable
+ interrupts while doing the hangup processing. This avoids
+ several nasty race conditions which happened when the
+ hangup processing was done asynchronously.
+ (tty_ioctl): Do break handling in the tty driver if
+ driver's break function is supported.
+ (tty_flip_buffer_push): New exported function which should
+ be used by drivers to push characters in the flip buffer
+ to the tty handler. This may either be done using a task
+ queue function for better CPU efficiency, or directly for
+ low latency operation.
+
+ * serial.c (rs_set_termios): Fix bug rs_set_termios when
+ transitioning away from B0, submitted by Stanislav
+ Voronyi.
+
+Thu Jun 19 20:05:58 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (begin_break, end_break, rs_ioctl): Applied patch
+ to support BSD ioctls to set and clear the break
+ condition explicitly.
+
+ * console.c (scrup, scrdown, insert_line, delete_line): Applied
+ fix suggested by Aaron Tiensivu to speed up block scrolls
+ up and down.
+
+ * n_tty.c (opost_block, write_chan): Added a modified "fast
+ console" patch which processes a block of text via
+ "cooking" efficiently.
+
+Wed Jun 18 15:25:50 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (init_dev, release_dev): Applied fix suggested by Bill
+ Hawes to prevent race conditions in the tty code.
+
+ * n_tty.c (n_tty_chars_in_buffer): Applied fix suggested by Bill
+ Hawes so that n_tty_chars_in_buffer returns the correct
+ value in the case when the tty is in cannonical mode. (To
+ avoid a pty deadlock with telnetd.)
+
+Thu Feb 27 01:53:08 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (change_speed): Add support for the termios flag
+ CMSPAR, which allows the user to select stick parity.
+ (i.e, if PARODD is set, the parity bit is always 1; if
+ PARRODD is not set, then the parity bit is always 0).
+
+Wed Feb 26 19:03:10 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (cleanup_module): Fix memory leak when using the serial
+ driver as a module; make sure tmp_buf gets freed!
+
+Tue Feb 25 11:01:59 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (set_modem_info): Add support for setting and clearing
+ the OUT1 and OUT2 bits. (For special case UART's, usually
+ for half-duplex.)
+ (autoconfig, change_speed): Fix TI 16750 support.
+
+Sun Feb 16 00:14:43 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (release_dev): Add sanity check to make sure there are
+ no waiters on tty->read_wait or tty->write_wait.
+
+ * serial.c (rs_init): Don't autoconfig a device if the I/O region
+ is already reserved.
+
+ * serial.c (serial_proc_info): Add support for /proc/serial.
+
+Thu Feb 13 00:49:10 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (receive_chars): When the UART repotrs an overrun
+ condition, it does so with a valid character. Changed to
+ not throw away the valid character, but instead report the
+ overrun after the valid character.
+
+ * serial.c: Added new #ifdef's for some of the advanced serial
+ driver features. A minimal driver that only supports COM
+ 1/2/3/4 without sharing serial interrupts only takes 17k;
+ the full driver takes 32k.
+
+Wed Feb 12 14:50:44 1997 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * vt.c:
+ * pty.c:
+ * tty_ioctl.c:
+ * serial.c: Update routines to use the new 2.1 memory access
+ routines.
+
+Wed Dec 4 07:51:52 1996 Theodore Ts'o <tytso@localhost.mit.edu>
+
+ * serial.c (change_speed): Use save_flags(); cli() and
+ restore_flags() in order to ensure we don't accidentally
+ turn on interrupts when starting up the port.
+ (startup): Move the insertion of serial structure into the
+ IRQ chain earlier into the startup processing. Interrupts
+ should be off this whole time, but we eventually will want
+ to reduce this window.
+
+Thu Nov 21 10:05:22 1996 Theodore Ts'o <tytso@localhost.mit.edu>
+
+ * tty_ioctl.c (tty_wait_until_sent): Always check the driver
+ wait_until_ready routine, even if there are no characters
+ in the xmit buffer. (There may be charactes in the device
+ FIFO.)
+ (n_tty_ioctl): Add new flag tty->flow_stopped which
+ indicates whether the tty is stopped due to a request by
+ the TCXONC ioctl (used by tcflow). If so, don't let an
+ incoming XOFF character restart the tty. The tty can only
+ be restarted by another TCXONC request.
+
+ * tty_io.c (start_tty): Don't allow the tty to be restarted if
+ tty->flow_stopped is true.
+
+ * n_tty.c (n_tty_receive_char): If tty->flow_stopped is true, and
+ IXANY is set, don't eat a character trying to restart the
+ tty.
+
+ * serial.c (startup): Remove need for MCR_noint from the
+ async_struct structure. Only turn on DTR and RTS if the
+ baud rate is not zero.
+ (change_speed): More accurately calculate the timeout
+ value based on the word size. Move responsibility of
+ hangup when speed becomes B0 to rs_set_termios()
+ (set_serial_info): When changing the UART type set the
+ current xmit_fifo_size as well as the permanent
+ xmit_fifo_size.
+ (rs_ioctl): Fix TCSBRK (used by tcdrain) and TCSBRKP
+ ioctls to return EINTR if interrupted by a signal.
+ (rs_set_termios): If the baud rate changes to or from B0,
+ this function is now responsible for setting or clearing
+ DTR and RTS. DTR and RTS are only be changed on the
+ transition to or from the B0 state.
+ (rs_close): Wait for the characters to drain based on
+ info->timeout. At low baud rates (50 bps), it may take a
+ long time for the FIFO to completely drain out!
+ (rs_wait_until_sent): Fixed timeout handling. Now
+ releases control to the scheduler, but checks frequently
+ enough so that the function is sensitive enough to pass
+ the timing requirements of the NIST-PCTS.
+ (block_til_ready): When opening the device, don't turn on
+ DTR and RTS if the baud rate is B0.
+
+Thu Nov 14 00:06:09 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c (autoconfig): Fix autoconfiguration problems;
+ info->flags wasn't getting initialized from the state
+ structure. Put in more paranoid test for the 16750.
+
+Fri Nov 8 20:19:50 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * n_tty.c (n_tty_flush_buffer): Only call driver->unthrottle() if
+ the tty was previous throttled.
+ (n_tty_set_termios, write_chan): Add changes suggested by
+ Simon P. Allen to allow hardware cooking.
+
+ * tty_ioctl.c (set_termios): If we get a signal while waiting for
+ the tty to drain, return -EINTR.
+
+ * serial.c (change_speed): Add support for CREAD, as required by
+ POSIX.
+
+Sat Nov 2 20:43:10 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * serial.c: Wholesale changes. Added support for the Startech
+ 16650 and 16650V2 chips. (WARNING: the new startech
+ 16650A may or may not work!) Added support for the
+ TI16750 (not yet tested). Split async_struct into a
+ transient part (async_struct) and a permanent part
+ (serial_state) which contains the configuration
+ information for the ports. Added new driver routines
+ wait_until_sent() and send_xchar() to help with POSIX
+ compliance. Added support for radio clocks which waggle
+ the carrier detect line (CONFIG_HARD_PPS).
+
+ * tty_ioctl.c (tty_wait_until_sent): Added call to new driver
+ function tty->driver.wait_until_sent(), which returns when
+ the tty's device xmit buffers are drained. Needed for
+ full POSIX compliance.
+
+ (send_prio_char): New function, called by the ioctl's
+ TCIOFF and TCION; uses the new driver call send_xchar(),
+ which will send the XON or XOFF character at high priority
+ (and even if tty output is stopped).
+
+Wed Jun 5 18:52:04 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * pty.c (pty_close): When closing a pty, make sure packet mode is
+ cleared.
+
+Sun May 26 09:33:52 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * vesa_blank.c (set_vesa_blanking): Add missing verify_area() call.
+
+ * selection.c (set_selection): Add missing verify_area() call.
+
+ * tty_io.c (tty_ioctl): Add missing verify_area() calls.
+
+ * serial.c (rs_ioctl): Add missing verify_area() calls.
+ (rs_init): Allow initialization of serial driver
+ configuration from a module.
+
+ * random.c (extract_entropy): Add missing verify_area call.
+ Don't limit number of characters returned to
+ 32,768. Extract entropy is now no longer a inlined
+ function.
+
+ (random_read): Check return value in case extract_entropy
+ returns an error.
+
+ (secure_tcp_sequence_number): New function which returns a
+ secure TCP sequence number. This is needed to prevent some
+ nasty TCP hijacking attacks.
+
+ (init_std_data): Initialize using gettimeofday() instead of
+ struct timeval xtime.
+
+ (fast_add_entropy_word, add_entropy_word): Rename the
+ inline function add_entropy_word() to
+ fast_add_entropy_word(). Make add_entropy_word() be the
+ non-inlined function which is used in non-timing critical
+ places, in order to save space.
+
+ (initialize_benchmark, begin_benchmark, end_benchmark): New
+ functions defined when RANDOM_BENCHMARK is defined. They
+ allow us to benchmark the speed of the
+ add_timer_randomness() call.
+
+ (int_ln, rotate_left): Add two new inline functions with
+ i386 optimized asm instructions. This speeds up the
+ critical add_entropy_word() and add_timer_randomness()
+ functions, which are called from interrupt handlers.
+
+Tue May 7 22:51:11 1996 <tytso@rsts-11.mit.edu>
+
+ * random.c (add_timer_randomness): Limit the amount randomness
+ that we estimate to 12 bits. (An arbitrary amount).
+
+ (extract_entropy): To make it harder to analyze the hash
+ function, fold the hash function in half using XOR, and
+ use the folded result as the value to emit to the user.
+ Also, add timer randomness each pass through the
+ exact_entropy call, to increase the amount of unknown
+ values during the extraction process.
+
+ (random_ioctl): Use IOR/IOW definitions to define the
+ ioctl values used by the /dev/random driver. Allow the
+ old ioctl values to be used for backwards compatibility
+ (for a limited amount of time).
+
+Wed Apr 24 14:02:04 1996 Theodore Ts'o <tytso@rsts-11.mit.edu>
+
+ * random.c (add_timer_randomness): Use 2nd derivative as well to
+ better estimate entropy.
+
+ (rand_initialize): Explicitly initialize all the pointers
+ to NULL. (Clearing pointers using memset isn't portable.)
+ Initialize the random pool with OS-dependent data.
+
+ (random_write): Add sanity checking to the arguments to
+ random_write(), so that bad arguments won't cause a kernel
+ SEGV.
+
+ (random_read): Update the access time of the device inode
+ when you return data to the user.
+
+ (random_ioctl): Wake up the random_wait channel when there
+ are only WAIT_INPUT_BITS available. Add more paranoia
+ checks to make sure entropy_count doesn't go beyond the
+ bounds of (0, POOLSIZE). Add a few missing verify_area
+ checks. Add support for the RNDCLEARPOOL ioctl, which
+ zaps the random pool.
+
+ (add_timer_randomness): Wake up the random_wait
+ channel only when there are WAIT_INPUT_BITS available.
+
+ (random_select): Allow a random refresh daemon process to
+ select on /dev/random for writing; wake up the daemon when
+ there are less than WAIT_OUTPUT_BITS bits of randomness
+ available.
+
+Tue Apr 23 22:56:07 1996 <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (init_dev): Change return code when user attempts to
+ open master pty which is already open from EAGAIN to EIO,
+ to match with BSD expectations. EIO is more correct
+ anyway, since EAGAIN implies that retrying will be
+ successful --- which it might be.... Eventually!!
+
+ * pty.c (pty_open, pty_close): Fix wait loop so that we don't
+ busy loop while waiting for the master side to open.
+ Fix tty opening/closing logic. TTY_SLAVE_CLOSED was
+ renamed to TTY_OTHER_CLOSED, so that the name is more
+ descriptive. Also fixed code so that the tty flag
+ actually works correctly now....
+
+Mon Apr 1 10:22:01 1996 <tytso@rsts-11.mit.edu>
+
+ * serial.c (rs_close): Cleaned up modularization changes.
+ Remove code which forced line discipline back to N_TTY
+ this is done in the tty upper layers, and there's no
+ reason to do it here. (Making this change also
+ removed the requirement that the serial module access
+ the internal kernel symbol "ldiscs".)
+
+ * tty_io.c (tty_init): Formally register a tty_driver entry for
+ /dev/tty (device 4, 0) and /dev/console (device 5, 0).
+ This guarantees that major device numbers 4 and 5 will be
+ reserved for the tty subsystem (as they have to be because
+ of /dev/tty and /dev/console). Removed tty_regdev, as
+ this interface is no longer necessary.
+
+Sun Mar 17 20:42:47 GMT 1996 <ah@doc.ic.ac.uk>
+
+ * serial.c : modularisation (changes in linux/fs/device.c allow
+ kerneld to automatically load the serial module).
+
+ * Makefile, Config.in : serial modularisation adds.
+
+ * tty_io.c : tty_init_ctty used by to register "cua" driver just
+ for the /dev/tty device (5,0). Added tty_regdev.
+
+ * serial.c (shutdown, rs_ioctl) : when port shuts down wakeup processes
+ waiting on delta_msr_wait. The TIOCMIWAIT ioctl returns EIO
+ if no change was done since the time of call.
+
+Sat Mar 16 14:33:13 1996 <aeb@cwi.nl>
+
+ * tty_io.c (disassociate_ctty): If disassociate_ctty is called by
+ exit, do not perform an implicit vhangup on a pty.
+
+Fri Feb 9 14:15:47 1996 <tytso@rsts-11.mit.edu>
+
+ * serial.c (block_til_ready): Fixed another race condition which
+ happens if a hangup happens during the open.
+
+Wed Jan 10 10:08:00 1996 <tytso@rsts-11.mit.edu>
+
+ * serial.c (block_til_ready): Remove race condition which happened
+ if a hangup condition happened during the setup of the
+ UART, before rs_open() called block_til_ready(). This
+ caused the info->count counter to be erroneously
+ decremented.
+
+ * serial.c (startup, rs_open): Remove race condition that could
+ cause a memory leak of one page. (Fortunately, both race
+ conditions were relatively rare in practice.)
+
+Tue Dec 5 13:21:27 1995 <tytso@rsts-11.mit.edu>
+
+ * serial.c (check_modem_status, rs_ioctl): Support the new
+ ioctl()'s TIOCGICOUNT, TIOCMIWAIT. These allow an
+ application program to wait on a modem serial register
+ status bit change, and to find out how many changes have
+ taken place for the MSR bits.
+
+ (rs_write): Eliminate a race condition which is introduced
+ if it is necessary to wait for the semaphore.
+
+Sat Nov 4 17:14:45 1995 <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (tty_init): Move registration of TTY_MAJOR and
+ TTY_AUX_MAJOR to the end, so that /proc/devices looks
+ prettier.
+
+ * pty.c (pty_init): Use new major numbers for PTY master and slave
+ devices. This allow us to have more than 64 pty's. We
+ register the old pty devices for backwards compatibility.
+ Note that a system should either be using the old pty
+ devices or the new pty devices --- in general, it should
+ try to use both, since they map into the same pty table.
+ The old pty devices are strictly for backwards compatibility.
+
+Wed Oct 11 12:45:24 1995 <tytso@rsts-11.mit.edu>
+
+ * tty_io.c (disassociate_ctty): If disassociate_ctty is called by
+ exit, perform an implicit vhangup on the tty.
+
+ * pty.c (pty_close): When the master pty is closed, send a hangup
+ to the slave pty.
+ (pty_open): Use the flag TTY_SLAVE_CLOSED to test to see
+ if there are any open slave ptys, instead of using
+ tty->link->count. The old method got confused if there
+ were processes that had hung-up file descriptors on the
+ slave tty.
+
+Tue May 2 00:53:25 1995 <tytso@rsx-11.mit.edu>
+
+ * tty_io.c (tty_set_ldisc): Wait until the output buffer is
+ drained before closing the old line discipline --- needed
+ in only one case: XON/XOFF processing.
+
+ * n_tty.c (n_tty_close): Don't bother waiting until the output
+ driver is closed; in general, the line discipline
+ shouldn't care if the hardware is finished
+ transmitting before the line discipline terminates.
+
+ * tty_io.c (release_dev): Shutdown the line discipline after
+ decrementing the tty count variable; but set the
+ TTY_CLOSING flag so that we know that this tty structure
+ isn't long for this world.
+
+ * tty_io.c (init_dev): Add sanity code to check to see if
+ TTY_CLOSING is set on a tty structure; if so, something
+ bad has happened (probably a line discipline close blocked
+ when it shouldn't have; so do a kernel printk and then
+ return an error).
+
+Wed Apr 26 10:23:44 1995 Theodore Y. Ts'o <tytso@localhost>
+
+ * tty_io.c (release_dev): Try to shutdown the line discipline
+ *before* decrementing the tty count variable; this removes
+ a potential race condition which occurs when the line
+ discipline close blocks, and another process then tries
+ open the same serial port.
+
+ * serial.c (rs_hangup): When hanging up, flush the output buffer
+ before shutting down the UART. Otherwise the line
+ discipline close blocks waiting for the characters to get
+ flushed, which never happens until the serial port gets reused.
+
+Wed Apr 12 08:06:16 1995 Theodore Y. Ts'o <tytso@localhost>
+
+ * serial.c (do_serial_hangup, do_softint, check_modem_status,
+ rs_init): Hangups are now scheduled via a separate tqueue
+ structure in the async_struct structure, tqueue_hangup.
+ This task is pushed on to the tq_schedule queue, so that
+ it is processed synchronously by the scheduler.
+
+Sat Feb 18 12:13:51 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (disassociate_ctty, tty_open, tty_ioctl): Clear
+ current->tty_old_pgrp field when a session leader
+ acquires a controlling tty, and after a session leader
+ has disassociated from a controlling tty.
+
+Fri Feb 17 09:34:09 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_interrupt_single, rs_interrupt, rs_interrupt_multi):
+ Change the number of passes made from 64 to be 256,
+ configurable with the #define RS_ISR_PASS_LIMIT.
+
+ * serial.c (rs_init, set_serial_info, get_serial_info, rs_close):
+ Remove support for closing_wait2. Instead, set
+ tty->closing and rely on the line discipline to prevent
+ echo wars.
+
+ * n_tty.c (n_tty_receive_char): IEXTEN does not need to be
+ enabled in order for IXANY to be active.
+
+ If tty->closing is set, then only process XON and XOFF
+ characters.
+
+Sun Feb 12 23:57:48 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_timer): Change the interrupt poll time from 60
+ seconds to 10 seconds, configurable with the #define
+ RS_STROBE_TIME.
+
+ * serial.c (rs_interrupt_multi, startup, shutdown, rs_ioctl,
+ set_multiport_struct, get_multiport_struct): Add
+ provisions for a new type of interrupt service routine,
+ which better supports multiple serial ports on a single
+ IRQ.
+
+Sun Feb 5 19:35:11 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_ioctl.c (n_tty_ioctl, set_termios, tty_wait_until_sent):
+ * serial.c (rs_ioctl, rs_close):
+ * cyclades.c (cy_ioctl, cy_close):
+ * n_tty.c (n_tty_close): Rename wait_until_sent to
+ tty_wait_until_sent, so that it's a better name to export
+ in ksyms.c.
+
+Sat Feb 4 23:36:20 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_close): Added missing check for closing_wait2 being
+ ASYNC_CLOSING_WAIT_NONE.
+
+Thu Jan 26 09:02:49 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_init, set_serial_info, get_serial_info,
+ rs_close): Support close_wait in the serial driver.
+ This is helpful for slow devices (like serial
+ plotters) so that their outputs don't get flushed upon
+ device close. This has to be configurable because
+ normally we don't want ports to be hung up for long
+ periods of time during a close when they are not
+ connected to a device, or the device is powered off.
+
+ The default is to wait 30 seconds; in the case of a
+ very slow device, the close_wait timeout should be
+ lengthened. If it is set to 0, the kernel will wait
+ forever for all of the data to be transmitted.
+
+Thu Jan 17 01:17:20 1995 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (startup, change_speed, rs_init): Add support to detect
+ the StarTech 16650 chip. Treat it as a 16450 for now,
+ because of its FIFO bugs.
+
+Thu Jan 5 21:21:57 1995 <dahinds@users.sourceforge.net>
+
+ * serial.c: (receive_char): Added counter to prevent infinite loop
+ when a PCMCIA serial device is ejected.
+
+Thu Dec 29 17:53:48 1994 <tytso@rsx-11.mit.edu>
+
+ * tty_io.c (check_tty_count): New procedure which checks
+ tty->count to make sure that it matches with the number of
+ open file descriptors which point at the structure. If
+ the number doesn't match, it prints a warning message.
+
+Wed Dec 28 15:41:51 1994 <tytso@rsx-11.mit.edu>
+
+ * tty_io.c (do_tty_hangup, disassociate_ctty): At hangup time,
+ save the tty's current foreground process group in the
+ session leader's task structure. When the session leader
+ terminates, send a SIGHUP, SIGCONT to that process group.
+ This is not required by POSIX, but it's not prohibited
+ either, and it appears to be the least intrusive way
+ to fix a problem that dialup servers have with
+ orphaned process groups caused by modem hangups.
+
+Thu Dec 8 14:52:11 1994 <tytso@rsx-11.mit.edu>
+
+ * serial.c (rs_ioctl): Don't allow most ioctl's if the serial port
+ isn't initialized.
+
+ * serial.c (rs_close): Don't clear the IER if the serial port
+ isn't initialized.
+
+ * serial.c (block_til_ready): Don't try to block on the dialin
+ port if the serial port isn't initialized.
+
+Wed Dec 7 10:48:30 1994 Si Park (si@wimpol.demon.co.uk)
+ * tty_io.c (tty_register_driver): Fix bug when linking onto
+ the tty_drivers list. We now test that there are elements
+ already on the list before setting the back link from the
+ first element to the new driver.
+
+ * tty_io.c (tty_unregister_driver): Fix bug in unlinking the
+ specified driver from the tty_drivers list. We were not
+ setting the back link correctly. This used to result in
+ a dangling back link pointer and cause panics on the next
+ call to get_tty_driver().
+
+Tue Nov 29 10:21:09 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (tty_unregister_driver): Fix bug in
+ tty_unregister_driver where the pointer to the refcount is
+ tested, instead of the refcount itself. This caused
+ tty_unregister_driver to always return EBUSY.
+
+Sat Nov 26 11:59:24 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (tty_ioctl): Add support for the new ioctl
+ TIOCTTYGSTRUCT, which allow a kernel debugging program
+ direct read access to the tty and tty_driver structures.
+
+Fri Nov 25 17:26:22 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_set_termios): Don't wake up processes blocked in
+ open when the CLOCAL flag changes, since a blocking
+ open only samples the CLOCAL flag once when it blocks,
+ and doesn't check it again. (n.b. FreeBSD has a
+ different behavior for blocking opens; it's not clear
+ whether Linux or FreeBSD's interpretation is correct.
+ POSIX doesn't give clear guidance on this issue, so
+ this may change in the future....)
+
+ * serial.c (block_til_ready): Use the correct termios structure to
+ check the CLOCAL flag. If the cuaXX device is active,
+ then check the saved termios for the ttySXX device.
+ Otherwise, use the currently active termios structure.
+
+Sun Nov 6 21:05:44 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (change_speed): Add support for direct access of
+ 57,600 and 115,200 bps.
+
+Wed Nov 2 10:32:36 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * n_tty.c (n_tty_receive_room): Only allow excess characters
+ through if we are in ICANON mode *and* there are other no
+ pending lines in the buffer. Otherwise cut and paste over
+ 4k breaks.
+
+Sat Oct 29 18:17:34 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_ioctl, get_lsr_info): Added patch suggested by Arne
+ Riiber so that user mode programs can tell when the
+ transmitter shift register is empty.
+
+Thu Oct 27 23:14:29 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_ioctl.c (wait_until_sent): Added debugging printk statements
+ (under the #ifdef TTY_DEBUG_WAIT_UNTIL_SENT)
+
+ * serial.c (rs_interrupt, rs_interrupt_single, receive_chars,
+ change_speed, rs_close): rs_close now disables receiver
+ interrupts when closing the serial port. This allows the
+ serial port to close quickly when Linux and a modem (or a
+ mouse) are engaged in an echo war; when closing the serial
+ port, we now first stop listening to incoming characters,
+ and *then* wait for the transmit buffer to drain.
+
+ In order to make this change, the info->read_status_mask
+ is now used to control what bits of the line status
+ register are looked at in the interrupt routine in all
+ cases; previously it was only used in receive_chars to
+ select a few of the status bits.
+
+Mon Oct 24 23:36:21 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_close): Add a timeout to the transmitter flush
+ loop; this is just a sanity check in case we have flaky
+ (or non-existent-but-configured-by-the-user) hardware.
+
+Fri Oct 21 09:37:23 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (tty_fasync): When asynchronous I/O is enabled, if the
+ process or process group has not be specified yet, set it
+ to be the tty's process group, or if that is not yet set,
+ to the current process's pid.
+
+Thu Oct 20 23:17:28 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * n_tty.c (n_tty_receive_room): If we are doing input
+ canonicalization, let as many characters through as
+ possible, so that the excess characters can be "beeped".
+
+Tue Oct 18 10:02:43 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_start): Removed an incorrect '!' that was
+ preventing transmit interrupts from being re-enabled in
+ rs_start(). Fortunately in most cases it would be
+ re-enabled elsewhere, but this still should be fixed
+ correctly.
+
+Sun Oct 9 23:46:03 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (do_tty_hangup): If the tty driver flags
+ TTY_DRIVER_RESET_TERMIOS is set, then reset the termios
+ settings back to the driver's initial configuration. This
+ allows the termios settings to be reset even if a process
+ has hung up file descriptors keeping a pty's termios from
+ being freed and reset.
+
+ * tty_io.c (release_dev): Fix memory leak. The pty's other
+ termios structure should also be freed.
+
+ * serial.c (rs_close, shutdown): Change how we wait for the
+ transmitter to completely drain before shutting down the
+ serial port. We now do it by scheduling in another
+ process instead of busy looping with the interrupts turned
+ on. This may eliminate some race condition problems that
+ some people seem to be reporting.
+
+Sun Sep 25 14:18:14 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (release_dev): When freeing a tty make sure that both
+ the tty and the o_tty (if present) aren't a process's
+ controlling tty. (Previously, we only checked the tty.)
+
+ * serial.c (change_speed): Only enable the Modem Status
+ Interrupt for a port if CLOCAL is not set or CRTSCTS
+ is set. If we're not checking the carrier detect and
+ CTS line, there's no point in enabling the modem
+ status interrupt. This will save spurious interrupts
+ from slowing down systems who have terminals that
+ don't support either line. (Of course, if you want
+ only one of CD and CTS support, you will need a
+ properly wired serial cable.)
+
+Thu Sep 22 08:32:48 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (do_SAK): Return if tty is null.
+
+ * tty_io.c (_tty_name): Return "NULL tty" if the passed in tty is
+ NULL.
+
+Sat Sep 17 13:19:25 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_ioctl.c (n_tty_ioctl): Fix TIOCGLCKTRMIOS and
+ TIOCSLCKTRMIOS, which were totally broken. Remove
+ extra indirection from argument; it should be a struct
+ termios *, not a struct termios **.
+ &real_tty->termios_locked should have been
+ real_tty->termios_locked. This caused us to be
+ reading and writing the termios_locked structure to
+ random places in kernel memory.
+
+ * tty_io.c (release_dev): Oops! Forgot to delete a critical kfree
+ of the locked_termios. This leaves the locked_termios
+ structure pointed at a freed object.
+
+Fri Sep 16 08:13:25 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * tty_io.c (tty_open): Don't check for an exclusive open until
+ after the device specific open routine has been called.
+ Otherwise, the serial device ref counting will be screwed
+ up.
+
+ * serial.c (rs_open, block_til_ready): Don't set termios structure
+ until after block_til_ready has returned successfully.
+ Modify block_til_ready to check the normal_termios
+ structure directly, so it doesn't rely on termios being
+ set before it's called.
+
+Thu Sep 15 23:34:01 1994 Theodore Y. Ts'o (tytso@rt-11)
+
+ * serial.c (rs_close): Turn off interrupts during rs_close() to
+ prevent a race condition with the hangup code (which
+ runs during a software interrupt).
+
+ * tty_io.c (release_dev): Don't free the locked_termios structure;
+ its state must be retained across device opens.
+
+
+ * tty_io.c (tty_unregister_driver): Added function to unregister a
+ tty driver. (For loadable device drivers.)
+
+
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
new file mode 100644
index 000000000000..096a1202ea07
--- /dev/null
+++ b/drivers/char/Kconfig
@@ -0,0 +1,988 @@
+#
+# Character device configuration
+#
+
+menu "Character devices"
+
+config VT
+ bool "Virtual terminal" if EMBEDDED
+ select INPUT
+ default y if !VIOCONS
+ ---help---
+ If you say Y here, you will get support for terminal devices with
+ display and keyboard devices. These are called "virtual" because you
+ can run several virtual terminals (also called virtual consoles) on
+ one physical terminal. This is rather useful, for example one
+ virtual terminal can collect system messages and warnings, another
+ one can be used for a text-mode user session, and a third could run
+ an X session, all in parallel. Switching between virtual terminals
+ is done with certain key combinations, usually Alt-<function key>.
+
+ The setterm command ("man setterm") can be used to change the
+ properties (such as colors or beeping) of a virtual terminal. The
+ man page console_codes(4) ("man console_codes") contains the special
+ character sequences that can be used to change those properties
+ directly. The fonts used on virtual terminals can be changed with
+ the setfont ("man setfont") command and the key bindings are defined
+ with the loadkeys ("man loadkeys") command.
+
+ You need at least one virtual terminal device in order to make use
+ of your keyboard and monitor. Therefore, only people configuring an
+ embedded system would want to say N here in order to save some
+ memory; the only way to log into such a system is then via a serial
+ or network connection.
+
+ If unsure, say Y, or else you won't be able to do much with your new
+ shiny Linux system :-)
+
+config VT_CONSOLE
+ bool "Support for console on virtual terminal" if EMBEDDED
+ depends on VT
+ default y
+ ---help---
+ The system console is the device which receives all kernel messages
+ and warnings and which allows logins in single user mode. If you
+ answer Y here, a virtual terminal (the device used to interact with
+ a physical terminal) can be used as system console. This is the most
+ common mode of operations, so you should say Y here unless you want
+ the kernel messages be output only to a serial port (in which case
+ you should say Y to "Console on serial port", below).
+
+ If you do say Y here, by default the currently visible virtual
+ terminal (/dev/tty0) will be used as system console. You can change
+ that with a kernel command line option such as "console=tty3" which
+ would use the third virtual terminal as system console. (Try "man
+ bootparam" or see the documentation of your boot loader (lilo or
+ loadlin) about how to pass options to the kernel at boot time.)
+
+ If unsure, say Y.
+
+config HW_CONSOLE
+ bool
+ depends on VT && !S390 && !UML
+ default y
+
+config SERIAL_NONSTANDARD
+ bool "Non-standard serial port support"
+ ---help---
+ Say Y here if you have any non-standard serial boards -- boards
+ which aren't supported using the standard "dumb" serial driver.
+ This includes intelligent serial boards such as Cyclades,
+ Digiboards, etc. These are usually used for systems that need many
+ serial ports because they serve many terminals or dial-in
+ connections.
+
+ Note that the answer to this question won't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about non-standard serial boards.
+
+ Most people can say N here.
+
+config COMPUTONE
+ tristate "Computone IntelliPort Plus serial support"
+ depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+ ---help---
+ This driver supports the entire family of Intelliport II/Plus
+ controllers with the exception of the MicroChannel controllers and
+ products previous to the Intelliport II. These are multiport cards,
+ which give you many serial ports. You would need something like this
+ to connect more than two modems to your Linux box, for instance in
+ order to become a dial-in server. If you have a card like that, say
+ Y here and read <file:Documentation/computone.txt>.
+
+ To compile this driver as modules, choose M here: the
+ modules will be called ip2 and ip2main.
+
+config ROCKETPORT
+ tristate "Comtrol RocketPort support"
+ depends on SERIAL_NONSTANDARD
+ help
+ This driver supports Comtrol RocketPort and RocketModem PCI boards.
+ These boards provide 2, 4, 8, 16, or 32 high-speed serial ports or
+ modems. For information about the RocketPort/RocketModem boards
+ and this driver read <file:Documentation/rocket.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rocket.
+
+ If you want to compile this driver into the kernel, say Y here. If
+ you don't have a Comtrol RocketPort/RocketModem card installed, say N.
+
+config CYCLADES
+ tristate "Cyclades async mux support"
+ depends on SERIAL_NONSTANDARD
+ ---help---
+ This driver supports Cyclades Z and Y multiserial boards.
+ You would need something like this to connect more than two modems to
+ your Linux box, for instance in order to become a dial-in server.
+
+ For information about the Cyclades-Z card, read
+ <file:drivers/char/README.cycladesZ>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cyclades.
+
+ If you haven't heard about it, it's safe to say N.
+
+config CYZ_INTR
+ bool "Cyclades-Z interrupt mode operation (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && CYCLADES
+ help
+ The Cyclades-Z family of multiport cards allows 2 (two) driver op
+ modes: polling and interrupt. In polling mode, the driver will check
+ the status of the Cyclades-Z ports every certain amount of time
+ (which is called polling cycle and is configurable). In interrupt
+ mode, it will use an interrupt line (IRQ) in order to check the
+ status of the Cyclades-Z ports. The default op mode is polling. If
+ unsure, say N.
+
+config DIGIEPCA
+ tristate "Digiboard Intelligent Async Support"
+ depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+ ---help---
+ This is a driver for Digi International's Xx, Xeve, and Xem series
+ of cards which provide multiple serial ports. You would need
+ something like this to connect more than two modems to your Linux
+ box, for instance in order to become a dial-in server. This driver
+ supports the original PC (ISA) boards as well as PCI, and EISA. If
+ you have a card like this, say Y here and read the file
+ <file:Documentation/digiepca.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called epca.
+
+config ESPSERIAL
+ tristate "Hayes ESP serial port support"
+ depends on SERIAL_NONSTANDARD && ISA && BROKEN_ON_SMP
+ help
+ This is a driver which supports Hayes ESP serial ports. Both single
+ port cards and multiport cards are supported. Make sure to read
+ <file:Documentation/hayes-esp.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called esp.
+
+ If unsure, say N.
+
+config MOXA_INTELLIO
+ tristate "Moxa Intellio support"
+ depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+ help
+ Say Y here if you have a Moxa Intellio multiport serial card.
+
+ To compile this driver as a module, choose M here: the
+ module will be called moxa.
+
+config MOXA_SMARTIO
+ tristate "Moxa SmartIO support"
+ depends on SERIAL_NONSTANDARD
+ help
+ Say Y here if you have a Moxa SmartIO multiport serial card.
+
+ This driver can also be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called mxser. If you want to do that, say M
+ here.
+
+config ISI
+ tristate "Multi-Tech multiport card support (EXPERIMENTAL)"
+ depends on SERIAL_NONSTANDARD
+ help
+ This is a driver for the Multi-Tech cards which provide several
+ serial ports. The driver is experimental and can currently only be
+ built as a module. The module will be called isicom.
+ If you want to do that, choose M here.
+
+config SYNCLINK
+ tristate "Microgate SyncLink card support"
+ depends on SERIAL_NONSTANDARD && PCI
+ help
+ Provides support for the SyncLink ISA and PCI multiprotocol serial
+ adapters. These adapters support asynchronous and HDLC bit
+ synchronous communication up to 10Mbps (PCI adapter).
+
+ This driver can only be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called synclink. If you want to do that, say M
+ here.
+
+config SYNCLINKMP
+ tristate "SyncLink Multiport support"
+ depends on SERIAL_NONSTANDARD
+ help
+ Enable support for the SyncLink Multiport (2 or 4 ports)
+ serial adapter, running asynchronous and HDLC communications up
+ to 2.048Mbps. Each ports is independently selectable for
+ RS-232, V.35, RS-449, RS-530, and X.21
+
+ This driver may be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called synclinkmp. If you want to do that, say M
+ here.
+
+config N_HDLC
+ tristate "HDLC line discipline support"
+ depends on SERIAL_NONSTANDARD
+ help
+ Allows synchronous HDLC communications with tty device drivers that
+ support synchronous HDLC such as the Microgate SyncLink adapter.
+
+ This driver can only be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called n_hdlc. If you want to do that, say M
+ here.
+
+config RISCOM8
+ tristate "SDL RISCom/8 card support"
+ depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+ help
+ This is a driver for the SDL Communications RISCom/8 multiport card,
+ which gives you many serial ports. You would need something like
+ this to connect more than two modems to your Linux box, for instance
+ in order to become a dial-in server. If you have a card like that,
+ say Y here and read the file <file:Documentation/riscom8.txt>.
+
+ Also it's possible to say M here and compile this driver as kernel
+ loadable module; the module will be called riscom8.
+
+config SPECIALIX
+ tristate "Specialix IO8+ card support"
+ depends on SERIAL_NONSTANDARD
+ help
+ This is a driver for the Specialix IO8+ multiport card (both the
+ ISA and the PCI version) which gives you many serial ports. You
+ would need something like this to connect more than two modems to
+ your Linux box, for instance in order to become a dial-in server.
+
+ If you have a card like that, say Y here and read the file
+ <file:Documentation/specialix.txt>. Also it's possible to say M here
+ and compile this driver as kernel loadable module which will be
+ called specialix.
+
+config SPECIALIX_RTSCTS
+ bool "Specialix DTR/RTS pin is RTS"
+ depends on SPECIALIX
+ help
+ The Specialix IO8+ card can only support either RTS or DTR. If you
+ say N here, the driver will use the pin as "DTR" when the tty is in
+ software handshake mode. If you say Y here or hardware handshake is
+ on, it will always be RTS. Read the file
+ <file:Documentation/specialix.txt> for more information.
+
+config SX
+ tristate "Specialix SX (and SI) card support"
+ depends on SERIAL_NONSTANDARD
+ help
+ This is a driver for the SX and SI multiport serial cards.
+ Please read the file <file:Documentation/sx.txt> for details.
+
+ This driver can only be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called sx. If you want to do that, say M here.
+
+config RIO
+ tristate "Specialix RIO system support"
+ depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+ help
+ This is a driver for the Specialix RIO, a smart serial card which
+ drives an outboard box that can support up to 128 ports. Product
+ information is at <http://www.perle.com/support/documentation.html#multiport>.
+ There are both ISA and PCI versions.
+
+config RIO_OLDPCI
+ bool "Support really old RIO/PCI cards"
+ depends on RIO
+ help
+ Older RIO PCI cards need some initialization-time configuration to
+ determine the IRQ and some control addresses. If you have a RIO and
+ this doesn't seem to work, try setting this to Y.
+
+config STALDRV
+ bool "Stallion multiport serial support"
+ depends on SERIAL_NONSTANDARD
+ help
+ Stallion cards give you many serial ports. You would need something
+ like this to connect more than two modems to your Linux box, for
+ instance in order to become a dial-in server. If you say Y here,
+ you will be asked for your specific card model in the next
+ questions. Make sure to read <file:Documentation/stallion.txt> in
+ this case. If you have never heard about all this, it's safe to
+ say N.
+
+config STALLION
+ tristate "Stallion EasyIO or EC8/32 support"
+ depends on STALDRV && BROKEN_ON_SMP
+ help
+ If you have an EasyIO or EasyConnection 8/32 multiport Stallion
+ card, then this is for you; say Y. Make sure to read
+ <file:Documentation/stallion.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stallion.
+
+config ISTALLION
+ tristate "Stallion EC8/64, ONboard, Brumby support"
+ depends on STALDRV && BROKEN_ON_SMP
+ help
+ If you have an EasyConnection 8/64, ONboard, Brumby or Stallion
+ serial multiport card, say Y here. Make sure to read
+ <file:Documentation/stallion.txt>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called istallion.
+
+config AU1000_UART
+ bool "Enable Au1000 UART Support"
+ depends on SERIAL_NONSTANDARD && MIPS
+ help
+ If you have an Alchemy AU1000 processor (MIPS based) and you want
+ to use serial ports, say Y. Otherwise, say N.
+
+config AU1000_SERIAL_CONSOLE
+ bool "Enable Au1000 serial console"
+ depends on AU1000_UART
+ help
+ If you have an Alchemy AU1000 processor (MIPS based) and you want
+ to use a console on a serial port, say Y. Otherwise, say N.
+
+config QTRONIX_KEYBOARD
+ bool "Enable Qtronix 990P Keyboard Support"
+ depends on IT8712
+ help
+ Images of Qtronix keyboards are at
+ <http://www.qtronix.com/keyboard.html>.
+
+config IT8172_CIR
+ bool
+ depends on QTRONIX_KEYBOARD
+ default y
+
+config IT8172_SCR0
+ bool "Enable Smart Card Reader 0 Support "
+ depends on IT8712
+ help
+ Say Y here to support smart-card reader 0 (SCR0) on the Integrated
+ Technology Express, Inc. ITE8172 SBC. Vendor page at
+ <http://www.ite.com.tw/ia/brief_it8172bsp.htm>; picture of the
+ board at <http://www.mvista.com/partners/semiconductor/ite.html>.
+
+config IT8172_SCR1
+ bool "Enable Smart Card Reader 1 Support "
+ depends on IT8712
+ help
+ Say Y here to support smart-card reader 1 (SCR1) on the Integrated
+ Technology Express, Inc. ITE8172 SBC. Vendor page at
+ <http://www.ite.com.tw/ia/brief_it8172bsp.htm>; picture of the
+ board at <http://www.mvista.com/partners/semiconductor/ite.html>.
+
+config A2232
+ tristate "Commodore A2232 serial support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && ZORRO && BROKEN_ON_SMP
+ ---help---
+ This option supports the 2232 7-port serial card shipped with the
+ Amiga 2000 and other Zorro-bus machines, dating from 1989. At
+ a max of 19,200 bps, the ports are served by a 6551 ACIA UART chip
+ each, plus a 8520 CIA, and a master 6502 CPU and buffer as well. The
+ ports were connected with 8 pin DIN connectors on the card bracket,
+ for which 8 pin to DB25 adapters were supplied. The card also had
+ jumpers internally to toggle various pinning configurations.
+
+ This driver can be built as a module; but then "generic_serial"
+ will also be built as a module. This has to be loaded before
+ "ser_a2232". If you want to do this, answer M here.
+
+config SGI_SNSC
+ bool "SGI Altix system controller communication support"
+ depends on (IA64_SGI_SN2 || IA64_GENERIC)
+ help
+ If you have an SGI Altix and you want to enable system
+ controller communication from user space (you want this!),
+ say Y. Otherwise, say N.
+
+source "drivers/serial/Kconfig"
+
+config UNIX98_PTYS
+ bool "Unix98 PTY support" if EMBEDDED
+ default y
+ ---help---
+ A pseudo terminal (PTY) is a software device consisting of two
+ halves: a master and a slave. The slave device behaves identical to
+ a physical terminal; the master device is used by a process to
+ read data from and write data to the slave, thereby emulating a
+ terminal. Typical programs for the master side are telnet servers
+ and xterms.
+
+ Linux has traditionally used the BSD-like names /dev/ptyxx for
+ masters and /dev/ttyxx for slaves of pseudo terminals. This scheme
+ has a number of problems. The GNU C library glibc 2.1 and later,
+ however, supports the Unix98 naming standard: in order to acquire a
+ pseudo terminal, a process opens /dev/ptmx; the number of the pseudo
+ terminal is then made available to the process and the pseudo
+ terminal slave can be accessed as /dev/pts/<number>. What was
+ traditionally /dev/ttyp2 will then be /dev/pts/2, for example.
+
+ All modern Linux systems use the Unix98 ptys. Say Y unless
+ you're on an embedded system and want to conserve memory.
+
+config LEGACY_PTYS
+ bool "Legacy (BSD) PTY support"
+ default y
+ ---help---
+ A pseudo terminal (PTY) is a software device consisting of two
+ halves: a master and a slave. The slave device behaves identical to
+ a physical terminal; the master device is used by a process to
+ read data from and write data to the slave, thereby emulating a
+ terminal. Typical programs for the master side are telnet servers
+ and xterms.
+
+ Linux has traditionally used the BSD-like names /dev/ptyxx
+ for masters and /dev/ttyxx for slaves of pseudo
+ terminals. This scheme has a number of problems, including
+ security. This option enables these legacy devices; on most
+ systems, it is safe to say N.
+
+
+config LEGACY_PTY_COUNT
+ int "Maximum number of legacy PTY in use"
+ depends on LEGACY_PTYS
+ range 1 256
+ default "256"
+ ---help---
+ The maximum number of legacy PTYs that can be used at any one time.
+ The default is 256, and should be more than enough. Embedded
+ systems may want to reduce this to save memory.
+
+ When not in use, each legacy PTY occupies 12 bytes on 32-bit
+ architectures and 24 bytes on 64-bit architectures.
+
+config PRINTER
+ tristate "Parallel printer support"
+ depends on PARPORT
+ ---help---
+ If you intend to attach a printer to the parallel port of your Linux
+ box (as opposed to using a serial printer; if the connector at the
+ printer has 9 or 25 holes ["female"], then it's serial), say Y.
+ Also read the Printing-HOWTO, available from
+ <http://www.tldp.org/docs.html#howto>.
+
+ It is possible to share one parallel port among several devices
+ (e.g. printer and ZIP drive) and it is safe to compile the
+ corresponding drivers into the kernel.
+
+ To compile this driver as a module, choose M here and read
+ <file:Documentation/parport.txt>. The module will be called lp.
+
+ If you have several parallel ports, you can specify which ports to
+ use with the "lp" kernel command line option. (Try "man bootparam"
+ or see the documentation of your boot loader (lilo or loadlin) about
+ how to pass options to the kernel at boot time.) The syntax of the
+ "lp" command line option can be found in <file:drivers/char/lp.c>.
+
+ If you have more than 8 printers, you need to increase the LP_NO
+ macro in lp.c and the PARPORT_MAX macro in parport.h.
+
+config LP_CONSOLE
+ bool "Support for console on line printer"
+ depends on PRINTER
+ ---help---
+ If you want kernel messages to be printed out as they occur, you
+ can have a console on the printer. This option adds support for
+ doing that; to actually get it to happen you need to pass the
+ option "console=lp0" to the kernel at boot time.
+
+ If the printer is out of paper (or off, or unplugged, or too
+ busy..) the kernel will stall until the printer is ready again.
+ By defining CONSOLE_LP_STRICT to 0 (at your own risk) you
+ can make the kernel continue when this happens,
+ but it'll lose the kernel messages.
+
+ If unsure, say N.
+
+config PPDEV
+ tristate "Support for user-space parallel port device drivers"
+ depends on PARPORT
+ ---help---
+ Saying Y to this adds support for /dev/parport device nodes. This
+ is needed for programs that want portable access to the parallel
+ port, for instance deviceid (which displays Plug-and-Play device
+ IDs).
+
+ This is the parallel port equivalent of SCSI generic support (sg).
+ It is safe to say N to this -- it is not needed for normal printing
+ or parallel port CD-ROM/disk support.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ppdev.
+
+ If unsure, say N.
+
+config TIPAR
+ tristate "Texas Instruments parallel link cable support"
+ depends on PARPORT
+ ---help---
+ If you own a Texas Instruments graphing calculator and use a
+ parallel link cable, then you might be interested in this driver.
+
+ If you enable this driver, you will be able to communicate with
+ your calculator through a set of device nodes under /dev. The
+ main advantage of this driver is that you don't have to be root
+ to use this precise link cable (depending on the permissions on
+ the device nodes, though).
+
+ To compile this driver as a module, choose M here: the
+ module will be called tipar.
+
+ If you don't know what a parallel link cable is or what a Texas
+ Instruments graphing calculator is, then you probably don't need this
+ driver.
+
+ If unsure, say N.
+
+config HVC_CONSOLE
+ bool "pSeries Hypervisor Virtual Console support"
+ depends on PPC_PSERIES
+ help
+ pSeries machines when partitioned support a hypervisor virtual
+ console. This driver allows each pSeries partition to have a console
+ which is accessed via the HMC.
+
+config HVCS
+ tristate "IBM Hypervisor Virtual Console Server support"
+ depends on PPC_PSERIES
+ help
+ Partitionable IBM Power5 ppc64 machines allow hosting of
+ firmware virtual consoles from one Linux partition by
+ another Linux partition. This driver allows console data
+ from Linux partitions to be accessed through TTY device
+ interfaces in the device tree of a Linux partition running
+ this driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hvcs.ko. Additionally, this module
+ will depend on arch specific APIs exported from hvcserver.ko
+ which will also be compiled when this driver is built as a
+ module.
+
+source "drivers/char/ipmi/Kconfig"
+
+source "drivers/char/watchdog/Kconfig"
+
+config DS1620
+ tristate "NetWinder thermometer support"
+ depends on ARCH_NETWINDER
+ help
+ Say Y here to include support for the thermal management hardware
+ found in the NetWinder. This driver allows the user to control the
+ temperature set points and to read the current temperature.
+
+ It is also possible to say M here to build it as a module (ds1620)
+ It is recommended to be used on a NetWinder, but it is not a
+ necessity.
+
+config NWBUTTON
+ tristate "NetWinder Button"
+ depends on ARCH_NETWINDER
+ ---help---
+ If you say Y here and create a character device node /dev/nwbutton
+ with major and minor numbers 10 and 158 ("man mknod"), then every
+ time the orange button is pressed a number of times, the number of
+ times the button was pressed will be written to that device.
+
+ This is most useful for applications, as yet unwritten, which
+ perform actions based on how many times the button is pressed in a
+ row.
+
+ Do not hold the button down for too long, as the driver does not
+ alter the behaviour of the hardware reset circuitry attached to the
+ button; it will still execute a hard reset if the button is held
+ down for longer than approximately five seconds.
+
+ To compile this driver as a module, choose M here: the
+ module will be called nwbutton.
+
+ Most people will answer Y to this question and "Reboot Using Button"
+ below to be able to initiate a system shutdown from the button.
+
+config NWBUTTON_REBOOT
+ bool "Reboot Using Button"
+ depends on NWBUTTON
+ help
+ If you say Y here, then you will be able to initiate a system
+ shutdown and reboot by pressing the orange button a number of times.
+ The number of presses to initiate the shutdown is two by default,
+ but this can be altered by modifying the value of NUM_PRESSES_REBOOT
+ in nwbutton.h and recompiling the driver or, if you compile the
+ driver as a module, you can specify the number of presses at load
+ time with "insmod button reboot_count=<something>".
+
+config NWFLASH
+ tristate "NetWinder flash support"
+ depends on ARCH_NETWINDER
+ ---help---
+ If you say Y here and create a character device /dev/flash with
+ major 10 and minor 160 you can manipulate the flash ROM containing
+ the NetWinder firmware. Be careful as accidentally overwriting the
+ flash contents can render your computer unbootable. On no account
+ allow random users access to this device. :-)
+
+ To compile this driver as a module, choose M here: the
+ module will be called nwflash.
+
+ If you're not sure, say N.
+
+config HW_RANDOM
+ tristate "Intel/AMD/VIA HW Random Number Generator support"
+ depends on (X86 || IA64) && PCI
+ ---help---
+ This driver provides kernel-side support for the Random Number
+ Generator hardware found on Intel i8xx-based motherboards,
+ AMD 76x-based motherboards, and Via Nehemiah CPUs.
+
+ Provides a character driver, used to read() entropy data.
+
+ To compile this driver as a module, choose M here: the
+ module will be called hw_random.
+
+ If unsure, say N.
+
+config NVRAM
+ tristate "/dev/nvram support"
+ depends on ATARI || X86 || X86_64 || ARM || GENERIC_NVRAM
+ ---help---
+ If you say Y here and create a character special file /dev/nvram
+ with major number 10 and minor number 144 using mknod ("man mknod"),
+ you get read and write access to the extra bytes of non-volatile
+ memory in the real time clock (RTC), which is contained in every PC
+ and most Ataris. The actual number of bytes varies, depending on the
+ nvram in the system, but is usually 114 (128-14 for the RTC).
+
+ This memory is conventionally called "CMOS RAM" on PCs and "NVRAM"
+ on Ataris. /dev/nvram may be used to view settings there, or to
+ change them (with some utility). It could also be used to frequently
+ save a few bits of very important data that may not be lost over
+ power-off and for which writing to disk is too insecure. Note
+ however that most NVRAM space in a PC belongs to the BIOS and you
+ should NEVER idly tamper with it. See Ralf Brown's interrupt list
+ for a guide to the use of CMOS bytes by your BIOS.
+
+ On Atari machines, /dev/nvram is always configured and does not need
+ to be selected.
+
+ To compile this driver as a module, choose M here: the
+ module will be called nvram.
+
+config RTC
+ tristate "Enhanced Real Time Clock Support"
+ depends on !PPC32 && !PARISC && !IA64 && !M68K
+ ---help---
+ If you say Y here and create a character special file /dev/rtc with
+ major number 10 and minor number 135 using mknod ("man mknod"), you
+ will get access to the real time clock (or hardware clock) built
+ into your computer.
+
+ Every PC has such a clock built in. It can be used to generate
+ signals from as low as 1Hz up to 8192Hz, and can also be used
+ as a 24 hour alarm. It reports status information via the file
+ /proc/driver/rtc and its behaviour is set by various ioctls on
+ /dev/rtc.
+
+ If you run Linux on a multiprocessor machine and said Y to
+ "Symmetric Multi Processing" above, you should say Y here to read
+ and set the RTC in an SMP compatible fashion.
+
+ If you think you have a use for such a device (such as periodic data
+ sampling), then say Y here, and read <file:Documentation/rtc.txt>
+ for details.
+
+ To compile this driver as a module, choose M here: the
+ module will be called rtc.
+
+config SGI_DS1286
+ tristate "SGI DS1286 RTC support"
+ depends on SGI_IP22
+ help
+ If you say Y here and create a character special file /dev/rtc with
+ major number 10 and minor number 135 using mknod ("man mknod"), you
+ will get access to the real time clock built into your computer.
+ Every SGI has such a clock built in. It reports status information
+ via the file /proc/rtc and its behaviour is set by various ioctls on
+ /dev/rtc.
+
+config SGI_IP27_RTC
+ bool "SGI M48T35 RTC support"
+ depends on SGI_IP27
+ help
+ If you say Y here and create a character special file /dev/rtc with
+ major number 10 and minor number 135 using mknod ("man mknod"), you
+ will get access to the real time clock built into your computer.
+ Every SGI has such a clock built in. It reports status information
+ via the file /proc/rtc and its behaviour is set by various ioctls on
+ /dev/rtc.
+
+config GEN_RTC
+ tristate "Generic /dev/rtc emulation"
+ depends on RTC!=y && !IA64 && !ARM
+ ---help---
+ If you say Y here and create a character special file /dev/rtc with
+ major number 10 and minor number 135 using mknod ("man mknod"), you
+ will get access to the real time clock (or hardware clock) built
+ into your computer.
+
+ It reports status information via the file /proc/driver/rtc and its
+ behaviour is set by various ioctls on /dev/rtc. If you enable the
+ "extended RTC operation" below it will also provide an emulation
+ for RTC_UIE which is required by some programs and may improve
+ precision in some cases.
+
+ To compile this driver as a module, choose M here: the
+ module will be called genrtc.
+
+config GEN_RTC_X
+ bool "Extended RTC operation"
+ depends on GEN_RTC
+ help
+ Provides an emulation for RTC_UIE which is required by some programs
+ and may improve precision of the generic RTC support in some cases.
+
+config EFI_RTC
+ bool "EFI Real Time Clock Services"
+ depends on IA64
+
+config DS1302
+ tristate "DS1302 RTC support"
+ depends on M32R && (PLAT_M32700UT || PLAT_OPSPUT)
+ help
+ If you say Y here and create a character special file /dev/rtc with
+ major number 121 and minor number 0 using mknod ("man mknod"), you
+ will get access to the real time clock (or hardware clock) built
+ into your computer.
+
+config S3C2410_RTC
+ bool "S3C2410 RTC Driver"
+ depends on ARCH_S3C2410
+ help
+ RTC (Realtime Clock) driver for the clock inbuilt into the
+ Samsung S3C2410. This can provide periodic interrupt rates
+ from 1Hz to 64Hz for user programs, and wakeup from Alarm.
+
+config RTC_VR41XX
+ tristate "NEC VR4100 series Real Time Clock Support"
+ depends on CPU_VR41XX
+
+config COBALT_LCD
+ bool "Support for Cobalt LCD"
+ depends on MIPS_COBALT
+ help
+ This option enables support for the LCD display and buttons found
+ on Cobalt systems through a misc device.
+
+config DTLK
+ tristate "Double Talk PC internal speech card support"
+ help
+ This driver is for the DoubleTalk PC, a speech synthesizer
+ manufactured by RC Systems (<http://www.rcsys.com/>). It is also
+ called the `internal DoubleTalk'.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dtlk.
+
+config R3964
+ tristate "Siemens R3964 line discipline"
+ ---help---
+ This driver allows synchronous communication with devices using the
+ Siemens R3964 packet protocol. Unless you are dealing with special
+ hardware like PLCs, you are unlikely to need this.
+
+ To compile this driver as a module, choose M here: the
+ module will be called n_r3964.
+
+ If unsure, say N.
+
+config APPLICOM
+ tristate "Applicom intelligent fieldbus card support"
+ depends on PCI
+ ---help---
+ This driver provides the kernel-side support for the intelligent
+ fieldbus cards made by Applicom International. More information
+ about these cards can be found on the WWW at the address
+ <http://www.applicom-int.com/>, or by email from David Woodhouse
+ <dwmw2@infradead.org>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called applicom.
+
+ If unsure, say N.
+
+config SONYPI
+ tristate "Sony Vaio Programmable I/O Control Device support (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && X86 && PCI && INPUT && !64BIT
+ ---help---
+ This driver enables access to the Sony Programmable I/O Control
+ Device which can be found in many (all ?) Sony Vaio laptops.
+
+ If you have one of those laptops, read
+ <file:Documentation/sonypi.txt>, and say Y or M here.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sonypi.
+
+config TANBAC_TB0219
+ tristate "TANBAC TB0219 base board support"
+ depends TANBAC_TB0229
+
+
+menu "Ftape, the floppy tape device driver"
+
+config FTAPE
+ tristate "Ftape (QIC-80/Travan) support"
+ depends on BROKEN_ON_SMP && (ALPHA || X86)
+ ---help---
+ If you have a tape drive that is connected to your floppy
+ controller, say Y here.
+
+ Some tape drives (like the Seagate "Tape Store 3200" or the Iomega
+ "Ditto 3200" or the Exabyte "Eagle TR-3") come with a "high speed"
+ controller of their own. These drives (and their companion
+ controllers) are also supported if you say Y here.
+
+ If you have a special controller (such as the CMS FC-10, FC-20,
+ Mountain Mach-II, or any controller that is based on the Intel 82078
+ FDC like the high speed controllers by Seagate and Exabyte and
+ Iomega's "Ditto Dash") you must configure it by selecting the
+ appropriate entries from the "Floppy tape controllers" sub-menu
+ below and possibly modify the default values for the IRQ and DMA
+ channel and the IO base in ftape's configuration menu.
+
+ If you want to use your floppy tape drive on a PCI-bus based system,
+ please read the file <file:drivers/char/ftape/README.PCI>.
+
+ The ftape kernel driver is also available as a runtime loadable
+ module. To compile this driver as a module, choose M here: the
+ module will be called ftape.
+
+ Note that the Ftape-HOWTO is out of date (sorry) and documents the
+ older version 2.08 of this software but still contains useful
+ information. There is a web page with more recent documentation at
+ <http://www.instmath.rwth-aachen.de/~heine/ftape/>. This page
+ always contains the latest release of the ftape driver and useful
+ information (backup software, ftape related patches and
+ documentation, FAQ). Note that the file system interface has
+ changed quite a bit compared to previous versions of ftape. Please
+ read <file:Documentation/ftape.txt>.
+
+source "drivers/char/ftape/Kconfig"
+
+endmenu
+
+source "drivers/char/agp/Kconfig"
+
+source "drivers/char/drm/Kconfig"
+
+source "drivers/char/pcmcia/Kconfig"
+
+config MWAVE
+ tristate "ACP Modem (Mwave) support"
+ depends on X86
+ select SERIAL_8250
+ ---help---
+ The ACP modem (Mwave) for Linux is a WinModem. It is composed of a
+ kernel driver and a user level application. Together these components
+ support direct attachment to public switched telephone networks (PSTNs)
+ and support selected world wide countries.
+
+ This version of the ACP Modem driver supports the IBM Thinkpad 600E,
+ 600, and 770 that include on board ACP modem hardware.
+
+ The modem also supports the standard communications port interface
+ (ttySx) and is compatible with the Hayes AT Command Set.
+
+ The user level application needed to use this driver can be found at
+ the IBM Linux Technology Center (LTC) web site:
+ <http://www.ibm.com/linux/ltc/>.
+
+ If you own one of the above IBM Thinkpads which has the Mwave chipset
+ in it, say Y.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mwave.
+
+config SCx200_GPIO
+ tristate "NatSemi SCx200 GPIO Support"
+ depends on SCx200
+ help
+ Give userspace access to the GPIO pins on the National
+ Semiconductor SCx200 processors.
+
+ If compiled as a module, it will be called scx200_gpio.
+
+config RAW_DRIVER
+ tristate "RAW driver (/dev/raw/rawN) (OBSOLETE)"
+ help
+ The raw driver permits block devices to be bound to /dev/raw/rawN.
+ Once bound, I/O against /dev/raw/rawN uses efficient zero-copy I/O.
+ See the raw(8) manpage for more details.
+
+ The raw driver is deprecated and may be removed from 2.7
+ kernels. Applications should simply open the device (eg /dev/hda1)
+ with the O_DIRECT flag.
+
+config HPET
+ bool "HPET - High Precision Event Timer" if (X86 || IA64)
+ default n
+ depends on ACPI
+ help
+ If you say Y here, you will have a miscdevice named "/dev/hpet/". Each
+ open selects one of the timers supported by the HPET. The timers are
+ non-periodioc and/or periodic.
+
+config HPET_RTC_IRQ
+ bool "HPET Control RTC IRQ" if !HPET_EMULATE_RTC
+ default n
+ depends on HPET
+ help
+ If you say Y here, you will disable RTC_IRQ in drivers/char/rtc.c. It
+ is assumed the platform called hpet_alloc with the RTC IRQ values for
+ the HPET timers.
+
+config HPET_MMAP
+ bool "Allow mmap of HPET"
+ default y
+ depends on HPET
+ help
+ If you say Y here, user applications will be able to mmap
+ the HPET registers.
+
+ In some hardware implementations, the page containing HPET
+ registers may also contain other things that shouldn't be
+ exposed to the user. If this applies to your hardware,
+ say N here.
+
+config MAX_RAW_DEVS
+ int "Maximum number of RAW devices to support (1-8192)"
+ depends on RAW_DRIVER
+ default "256"
+ help
+ The maximum number of RAW devices that are supported.
+ Default is 256. Increase this number in case you need lots of
+ raw devices.
+
+config HANGCHECK_TIMER
+ tristate "Hangcheck timer"
+ depends on X86_64 || X86
+ help
+ The hangcheck-timer module detects when the system has gone
+ out to lunch past a certain margin. It can reboot the system
+ or merely print a warning.
+
+config MMTIMER
+ tristate "MMTIMER Memory mapped RTC for SGI Altix"
+ depends on IA64_GENERIC || IA64_SGI_SN2
+ default y
+ help
+ The mmtimer device allows direct userspace access to the
+ Altix system timer.
+
+source "drivers/char/tpm/Kconfig"
+
+endmenu
+
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
new file mode 100644
index 000000000000..54ed76af1a47
--- /dev/null
+++ b/drivers/char/Makefile
@@ -0,0 +1,118 @@
+#
+# Makefile for the kernel character device drivers.
+#
+
+#
+# This file contains the font map for the default (hardware) font
+#
+FONTMAPFILE = cp437.uni
+
+obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o
+
+obj-$(CONFIG_LEGACY_PTYS) += pty.o
+obj-$(CONFIG_UNIX98_PTYS) += pty.o
+obj-y += misc.o
+obj-$(CONFIG_VT) += vt_ioctl.o vc_screen.o consolemap.o \
+ consolemap_deftbl.o selection.o keyboard.o
+obj-$(CONFIG_HW_CONSOLE) += vt.o defkeymap.o
+obj-$(CONFIG_MAGIC_SYSRQ) += sysrq.o
+obj-$(CONFIG_ESPSERIAL) += esp.o
+obj-$(CONFIG_MVME147_SCC) += generic_serial.o vme_scc.o
+obj-$(CONFIG_MVME162_SCC) += generic_serial.o vme_scc.o
+obj-$(CONFIG_BVME6000_SCC) += generic_serial.o vme_scc.o
+obj-$(CONFIG_ROCKETPORT) += rocket.o
+obj-$(CONFIG_SERIAL167) += serial167.o
+obj-$(CONFIG_CYCLADES) += cyclades.o
+obj-$(CONFIG_STALLION) += stallion.o
+obj-$(CONFIG_ISTALLION) += istallion.o
+obj-$(CONFIG_DIGIEPCA) += epca.o
+obj-$(CONFIG_SPECIALIX) += specialix.o
+obj-$(CONFIG_MOXA_INTELLIO) += moxa.o
+obj-$(CONFIG_A2232) += ser_a2232.o generic_serial.o
+obj-$(CONFIG_ATARI_DSP56K) += dsp56k.o
+obj-$(CONFIG_MOXA_SMARTIO) += mxser.o
+obj-$(CONFIG_COMPUTONE) += ip2.o ip2main.o
+obj-$(CONFIG_RISCOM8) += riscom8.o
+obj-$(CONFIG_ISI) += isicom.o
+obj-$(CONFIG_SYNCLINK) += synclink.o
+obj-$(CONFIG_SYNCLINKMP) += synclinkmp.o
+obj-$(CONFIG_N_HDLC) += n_hdlc.o
+obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o
+obj-$(CONFIG_SX) += sx.o generic_serial.o
+obj-$(CONFIG_RIO) += rio/ generic_serial.o
+obj-$(CONFIG_HVC_CONSOLE) += hvc_console.o hvsi.o
+obj-$(CONFIG_RAW_DRIVER) += raw.o
+obj-$(CONFIG_SGI_SNSC) += snsc.o
+obj-$(CONFIG_MMTIMER) += mmtimer.o
+obj-$(CONFIG_VIOCONS) += viocons.o
+obj-$(CONFIG_VIOTAPE) += viotape.o
+obj-$(CONFIG_HVCS) += hvcs.o
+
+obj-$(CONFIG_PRINTER) += lp.o
+obj-$(CONFIG_TIPAR) += tipar.o
+
+obj-$(CONFIG_DTLK) += dtlk.o
+obj-$(CONFIG_R3964) += n_r3964.o
+obj-$(CONFIG_APPLICOM) += applicom.o
+obj-$(CONFIG_SONYPI) += sonypi.o
+obj-$(CONFIG_RTC) += rtc.o
+obj-$(CONFIG_HPET) += hpet.o
+obj-$(CONFIG_GEN_RTC) += genrtc.o
+obj-$(CONFIG_EFI_RTC) += efirtc.o
+obj-$(CONFIG_SGI_DS1286) += ds1286.o
+obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o
+obj-$(CONFIG_DS1302) += ds1302.o
+obj-$(CONFIG_S3C2410_RTC) += s3c2410-rtc.o
+obj-$(CONFIG_RTC_VR41XX) += vr41xx_rtc.o
+ifeq ($(CONFIG_GENERIC_NVRAM),y)
+ obj-$(CONFIG_NVRAM) += generic_nvram.o
+else
+ obj-$(CONFIG_NVRAM) += nvram.o
+endif
+obj-$(CONFIG_TOSHIBA) += toshiba.o
+obj-$(CONFIG_I8K) += i8k.o
+obj-$(CONFIG_DS1620) += ds1620.o
+obj-$(CONFIG_HW_RANDOM) += hw_random.o
+obj-$(CONFIG_FTAPE) += ftape/
+obj-$(CONFIG_COBALT_LCD) += lcd.o
+obj-$(CONFIG_PPDEV) += ppdev.o
+obj-$(CONFIG_NWBUTTON) += nwbutton.o
+obj-$(CONFIG_NWFLASH) += nwflash.o
+obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
+obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+
+obj-$(CONFIG_WATCHDOG) += watchdog/
+obj-$(CONFIG_MWAVE) += mwave/
+obj-$(CONFIG_AGP) += agp/
+obj-$(CONFIG_DRM) += drm/
+obj-$(CONFIG_PCMCIA) += pcmcia/
+obj-$(CONFIG_IPMI_HANDLER) += ipmi/
+
+obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o
+obj-$(CONFIG_TCG_TPM) += tpm/
+# Files generated that shall be removed upon make clean
+clean-files := consolemap_deftbl.c defkeymap.c qtronixmap.c
+
+quiet_cmd_conmk = CONMK $@
+ cmd_conmk = scripts/conmakehash $< > $@
+
+$(obj)/consolemap_deftbl.c: $(src)/$(FONTMAPFILE)
+ $(call cmd,conmk)
+
+$(obj)/defkeymap.o: $(obj)/defkeymap.c
+
+$(obj)/qtronixmap.o: $(obj)/qtronixmap.c
+
+# Uncomment if you're changing the keymap and have an appropriate
+# loadkeys version for the map. By default, we'll use the shipped
+# versions.
+# GENERATE_KEYMAP := 1
+
+ifdef GENERATE_KEYMAP
+
+$(obj)/defkeymap.c $(obj)/qtronixmap.c: $(obj)/%.c: $(src)/%.map
+ loadkeys --mktable $< > $@.tmp
+ sed -e 's/^static *//' $@.tmp > $@
+ rm $@.tmp
+
+endif
diff --git a/drivers/char/agp/Kconfig b/drivers/char/agp/Kconfig
new file mode 100644
index 000000000000..7f8c1b53b754
--- /dev/null
+++ b/drivers/char/agp/Kconfig
@@ -0,0 +1,171 @@
+config AGP
+ tristate "/dev/agpgart (AGP Support)" if !GART_IOMMU
+ depends on ALPHA || IA64 || PPC || X86
+ default y if GART_IOMMU
+ ---help---
+ AGP (Accelerated Graphics Port) is a bus system mainly used to
+ connect graphics cards to the rest of the system.
+
+ If you have an AGP system and you say Y here, it will be possible to
+ use the AGP features of your 3D rendering video card. This code acts
+ as a sort of "AGP driver" for the motherboard's chipset.
+
+ If you need more texture memory than you can get with the AGP GART
+ (theoretically up to 256 MB, but in practice usually 64 or 128 MB
+ due to kernel allocation issues), you could use PCI accesses
+ and have up to a couple gigs of texture space.
+
+ Note that this is the only means to have XFree4/GLX use
+ write-combining with MTRR support on the AGP bus. Without it, OpenGL
+ direct rendering will be a lot slower but still faster than PIO.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called agpgart.
+
+config AGP_ALI
+ tristate "ALI chipset support"
+ depends on AGP && X86 && !X86_64
+ ---help---
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on the following ALi chipsets. The supported chipsets
+ include M1541, M1621, M1631, M1632, M1641,M1647,and M1651.
+ For the ALi-chipset question, ALi suggests you refer to
+ <http://www.ali.com.tw/eng/support/index.shtml>.
+
+ The M1541 chipset can do AGP 1x and 2x, but note that there is an
+ acknowledged incompatibility with Matrox G200 cards. Due to
+ timing issues, this chipset cannot do AGP 2x with the G200.
+ This is a hardware limitation. AGP 1x seems to be fine, though.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+config AGP_ATI
+ tristate "ATI chipset support"
+ depends on AGP && X86 && !X86_64
+ ---help---
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on the ATI RadeonIGP family of chipsets.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+config AGP_AMD
+ tristate "AMD Irongate, 761, and 762 chipset support"
+ depends on AGP && X86 && !X86_64
+ help
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on AMD Irongate, 761, and 762 chipsets.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+config AGP_AMD64
+ tristate "AMD Opteron/Athlon64 on-CPU GART support" if !GART_IOMMU
+ depends on AGP && X86
+ default y if GART_IOMMU
+ help
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x using the on-CPU northbridge of the AMD Athlon64/Opteron CPUs.
+ You still need an external AGP bridge like the AMD 8151, VIA
+ K8T400M, SiS755. It may also support other AGP bridges when loaded
+ with agp_try_unsupported=1.
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say Y
+
+config AGP_INTEL
+ tristate "Intel 440LX/BX/GX, I8xx and E7x05 chipset support"
+ depends on AGP && X86
+ help
+ This option gives you AGP support for the GLX component of XFree86 4.x
+ on Intel 440LX/BX/GX, 815, 820, 830, 840, 845, 850, 860, 875,
+ E7205 and E7505 chipsets and full support for the 810, 815, 830M, 845G,
+ 852GM, 855GM, 865G and I915 integrated graphics chipsets.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI, or if you have any Intel integrated graphics
+ chipsets. If unsure, say Y.
+
+config AGP_NVIDIA
+ tristate "NVIDIA nForce/nForce2 chipset support"
+ depends on AGP && X86 && !X86_64
+ help
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on the following NVIDIA chipsets. The supported chipsets
+ include nForce and nForce2
+
+config AGP_SIS
+ tristate "SiS chipset support"
+ depends on AGP && X86 && !X86_64
+ help
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on Silicon Integrated Systems [SiS] chipsets.
+
+ Note that 5591/5592 AGP chipsets are NOT supported.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+config AGP_SWORKS
+ tristate "Serverworks LE/HE chipset support"
+ depends on AGP && X86 && !X86_64
+ help
+ Say Y here to support the Serverworks AGP card. See
+ <http://www.serverworks.com/> for product descriptions and images.
+
+config AGP_VIA
+ tristate "VIA chipset support"
+ depends on AGP && X86 && !X86_64
+ help
+ This option gives you AGP support for the GLX component of
+ XFree86 4.x on VIA MVP3/Apollo Pro chipsets.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say N.
+
+config AGP_I460
+ tristate "Intel 460GX chipset support"
+ depends on AGP && (IA64_DIG || IA64_GENERIC)
+ help
+ This option gives you AGP GART support for the Intel 460GX chipset
+ for IA64 processors.
+
+config AGP_HP_ZX1
+ tristate "HP ZX1 chipset AGP support"
+ depends on AGP && (IA64_HP_ZX1 || IA64_HP_ZX1_SWIOTLB || IA64_GENERIC)
+ help
+ This option gives you AGP GART support for the HP ZX1 chipset
+ for IA64 processors.
+
+config AGP_ALPHA_CORE
+ tristate "Alpha AGP support"
+ depends on AGP && (ALPHA_GENERIC || ALPHA_TITAN || ALPHA_MARVEL)
+ default AGP
+
+config AGP_UNINORTH
+ tristate "Apple UniNorth & U3 AGP support"
+ depends on AGP && PPC_PMAC
+ help
+ This option gives you AGP support for Apple machines with a
+ UniNorth or U3 (Apple G5) bridge.
+
+config AGP_EFFICEON
+ tristate "Transmeta Efficeon support"
+ depends on AGP && X86 && !X86_64
+ help
+ This option gives you AGP support for the Transmeta Efficeon
+ series processors with integrated northbridges.
+
+ You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+ use GLX or DRI. If unsure, say Y.
+
+config AGP_SGI_TIOCA
+ tristate "SGI TIO chipset AGP support"
+ depends on AGP && (IA64_SGI_SN2 || IA64_GENERIC)
+ help
+ This option gives you AGP GART support for the SGI TIO chipset
+ for IA64 processors.
+
diff --git a/drivers/char/agp/Makefile b/drivers/char/agp/Makefile
new file mode 100644
index 000000000000..d33a22f2fa0b
--- /dev/null
+++ b/drivers/char/agp/Makefile
@@ -0,0 +1,18 @@
+agpgart-y := backend.o frontend.o generic.o isoch.o
+
+obj-$(CONFIG_AGP) += agpgart.o
+obj-$(CONFIG_AGP_ALI) += ali-agp.o
+obj-$(CONFIG_AGP_ATI) += ati-agp.o
+obj-$(CONFIG_AGP_AMD) += amd-k7-agp.o
+obj-$(CONFIG_AGP_AMD64) += amd64-agp.o
+obj-$(CONFIG_AGP_ALPHA_CORE) += alpha-agp.o
+obj-$(CONFIG_AGP_EFFICEON) += efficeon-agp.o
+obj-$(CONFIG_AGP_HP_ZX1) += hp-agp.o
+obj-$(CONFIG_AGP_I460) += i460-agp.o
+obj-$(CONFIG_AGP_INTEL) += intel-agp.o
+obj-$(CONFIG_AGP_NVIDIA) += nvidia-agp.o
+obj-$(CONFIG_AGP_SGI_TIOCA) += sgi-agp.o
+obj-$(CONFIG_AGP_SIS) += sis-agp.o
+obj-$(CONFIG_AGP_SWORKS) += sworks-agp.o
+obj-$(CONFIG_AGP_UNINORTH) += uninorth-agp.o
+obj-$(CONFIG_AGP_VIA) += via-agp.o
diff --git a/drivers/char/agp/agp.h b/drivers/char/agp/agp.h
new file mode 100644
index 000000000000..ad9c11391d81
--- /dev/null
+++ b/drivers/char/agp/agp.h
@@ -0,0 +1,331 @@
+/*
+ * AGPGART
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2004 Dave Jones
+ * Copyright (C) 1999 Jeff Hartmann
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef _AGP_BACKEND_PRIV_H
+#define _AGP_BACKEND_PRIV_H 1
+
+#include <asm/agp.h> /* for flush_agp_cache() */
+
+#define PFX "agpgart: "
+
+//#define AGP_DEBUG 1
+#ifdef AGP_DEBUG
+#define DBG(x,y...) printk (KERN_DEBUG PFX "%s: " x "\n", __FUNCTION__ , ## y)
+#else
+#define DBG(x,y...) do { } while (0)
+#endif
+
+extern struct agp_bridge_data *agp_bridge;
+
+enum aper_size_type {
+ U8_APER_SIZE,
+ U16_APER_SIZE,
+ U32_APER_SIZE,
+ LVL2_APER_SIZE,
+ FIXED_APER_SIZE
+};
+
+struct gatt_mask {
+ unsigned long mask;
+ u32 type;
+ /* totally device specific, for integrated chipsets that
+ * might have different types of memory masks. For other
+ * devices this will probably be ignored */
+};
+
+struct aper_size_info_8 {
+ int size;
+ int num_entries;
+ int page_order;
+ u8 size_value;
+};
+
+struct aper_size_info_16 {
+ int size;
+ int num_entries;
+ int page_order;
+ u16 size_value;
+};
+
+struct aper_size_info_32 {
+ int size;
+ int num_entries;
+ int page_order;
+ u32 size_value;
+};
+
+struct aper_size_info_lvl2 {
+ int size;
+ int num_entries;
+ u32 size_value;
+};
+
+struct aper_size_info_fixed {
+ int size;
+ int num_entries;
+ int page_order;
+};
+
+struct agp_bridge_driver {
+ struct module *owner;
+ void *aperture_sizes;
+ int num_aperture_sizes;
+ enum aper_size_type size_type;
+ int cant_use_aperture;
+ int needs_scratch_page;
+ struct gatt_mask *masks;
+ int (*fetch_size)(void);
+ int (*configure)(void);
+ void (*agp_enable)(struct agp_bridge_data *, u32);
+ void (*cleanup)(void);
+ void (*tlb_flush)(struct agp_memory *);
+ unsigned long (*mask_memory)(struct agp_bridge_data *,
+ unsigned long, int);
+ void (*cache_flush)(void);
+ int (*create_gatt_table)(struct agp_bridge_data *);
+ int (*free_gatt_table)(struct agp_bridge_data *);
+ int (*insert_memory)(struct agp_memory *, off_t, int);
+ int (*remove_memory)(struct agp_memory *, off_t, int);
+ struct agp_memory *(*alloc_by_type) (size_t, int);
+ void (*free_by_type)(struct agp_memory *);
+ void *(*agp_alloc_page)(struct agp_bridge_data *);
+ void (*agp_destroy_page)(void *);
+};
+
+struct agp_bridge_data {
+ struct agp_version *version;
+ struct agp_bridge_driver *driver;
+ struct vm_operations_struct *vm_ops;
+ void *previous_size;
+ void *current_size;
+ void *dev_private_data;
+ struct pci_dev *dev;
+ u32 __iomem *gatt_table;
+ u32 *gatt_table_real;
+ unsigned long scratch_page;
+ unsigned long scratch_page_real;
+ unsigned long gart_bus_addr;
+ unsigned long gatt_bus_addr;
+ u32 mode;
+ enum chipset_type type;
+ unsigned long *key_list;
+ atomic_t current_memory_agp;
+ atomic_t agp_in_use;
+ int max_memory_agp; /* in number of pages */
+ int aperture_size_idx;
+ int capndx;
+ int flags;
+ char major_version;
+ char minor_version;
+ struct list_head list;
+};
+
+#define KB(x) ((x) * 1024)
+#define MB(x) (KB (KB (x)))
+#define GB(x) (MB (KB (x)))
+
+#define A_SIZE_8(x) ((struct aper_size_info_8 *) x)
+#define A_SIZE_16(x) ((struct aper_size_info_16 *) x)
+#define A_SIZE_32(x) ((struct aper_size_info_32 *) x)
+#define A_SIZE_LVL2(x) ((struct aper_size_info_lvl2 *) x)
+#define A_SIZE_FIX(x) ((struct aper_size_info_fixed *) x)
+#define A_IDX8(bridge) (A_SIZE_8((bridge)->driver->aperture_sizes) + i)
+#define A_IDX16(bridge) (A_SIZE_16((bridge)->driver->aperture_sizes) + i)
+#define A_IDX32(bridge) (A_SIZE_32((bridge)->driver->aperture_sizes) + i)
+#define MAXKEY (4096 * 32)
+
+#define PGE_EMPTY(b, p) (!(p) || (p) == (unsigned long) (b)->scratch_page)
+
+
+/* Intel registers */
+#define INTEL_APSIZE 0xb4
+#define INTEL_ATTBASE 0xb8
+#define INTEL_AGPCTRL 0xb0
+#define INTEL_NBXCFG 0x50
+#define INTEL_ERRSTS 0x91
+
+/* Intel i830 registers */
+#define I830_GMCH_CTRL 0x52
+#define I830_GMCH_ENABLED 0x4
+#define I830_GMCH_MEM_MASK 0x1
+#define I830_GMCH_MEM_64M 0x1
+#define I830_GMCH_MEM_128M 0
+#define I830_GMCH_GMS_MASK 0x70
+#define I830_GMCH_GMS_DISABLED 0x00
+#define I830_GMCH_GMS_LOCAL 0x10
+#define I830_GMCH_GMS_STOLEN_512 0x20
+#define I830_GMCH_GMS_STOLEN_1024 0x30
+#define I830_GMCH_GMS_STOLEN_8192 0x40
+#define I830_RDRAM_CHANNEL_TYPE 0x03010
+#define I830_RDRAM_ND(x) (((x) & 0x20) >> 5)
+#define I830_RDRAM_DDT(x) (((x) & 0x18) >> 3)
+
+/* This one is for I830MP w. an external graphic card */
+#define INTEL_I830_ERRSTS 0x92
+
+/* Intel 855GM/852GM registers */
+#define I855_GMCH_GMS_STOLEN_0M 0x0
+#define I855_GMCH_GMS_STOLEN_1M (0x1 << 4)
+#define I855_GMCH_GMS_STOLEN_4M (0x2 << 4)
+#define I855_GMCH_GMS_STOLEN_8M (0x3 << 4)
+#define I855_GMCH_GMS_STOLEN_16M (0x4 << 4)
+#define I855_GMCH_GMS_STOLEN_32M (0x5 << 4)
+#define I85X_CAPID 0x44
+#define I85X_VARIANT_MASK 0x7
+#define I85X_VARIANT_SHIFT 5
+#define I855_GME 0x0
+#define I855_GM 0x4
+#define I852_GME 0x2
+#define I852_GM 0x5
+
+/* Intel i845 registers */
+#define INTEL_I845_AGPM 0x51
+#define INTEL_I845_ERRSTS 0xc8
+
+/* Intel i860 registers */
+#define INTEL_I860_MCHCFG 0x50
+#define INTEL_I860_ERRSTS 0xc8
+
+/* Intel i810 registers */
+#define I810_GMADDR 0x10
+#define I810_MMADDR 0x14
+#define I810_PTE_BASE 0x10000
+#define I810_PTE_MAIN_UNCACHED 0x00000000
+#define I810_PTE_LOCAL 0x00000002
+#define I810_PTE_VALID 0x00000001
+#define I810_SMRAM_MISCC 0x70
+#define I810_GFX_MEM_WIN_SIZE 0x00010000
+#define I810_GFX_MEM_WIN_32M 0x00010000
+#define I810_GMS 0x000000c0
+#define I810_GMS_DISABLE 0x00000000
+#define I810_PGETBL_CTL 0x2020
+#define I810_PGETBL_ENABLED 0x00000001
+#define I810_DRAM_CTL 0x3000
+#define I810_DRAM_ROW_0 0x00000001
+#define I810_DRAM_ROW_0_SDRAM 0x00000001
+
+struct agp_device_ids {
+ unsigned short device_id; /* first, to make table easier to read */
+ enum chipset_type chipset;
+ const char *chipset_name;
+ int (*chipset_setup) (struct pci_dev *pdev); /* used to override generic */
+};
+
+/* Driver registration */
+struct agp_bridge_data *agp_alloc_bridge(void);
+void agp_put_bridge(struct agp_bridge_data *bridge);
+int agp_add_bridge(struct agp_bridge_data *bridge);
+void agp_remove_bridge(struct agp_bridge_data *bridge);
+
+/* Frontend routines. */
+int agp_frontend_initialize(void);
+void agp_frontend_cleanup(void);
+
+/* Generic routines. */
+void agp_generic_enable(struct agp_bridge_data *bridge, u32 mode);
+int agp_generic_create_gatt_table(struct agp_bridge_data *bridge);
+int agp_generic_free_gatt_table(struct agp_bridge_data *bridge);
+struct agp_memory *agp_create_memory(int scratch_pages);
+int agp_generic_insert_memory(struct agp_memory *mem, off_t pg_start, int type);
+int agp_generic_remove_memory(struct agp_memory *mem, off_t pg_start, int type);
+struct agp_memory *agp_generic_alloc_by_type(size_t page_count, int type);
+void agp_generic_free_by_type(struct agp_memory *curr);
+void *agp_generic_alloc_page(struct agp_bridge_data *bridge);
+void agp_generic_destroy_page(void *addr);
+void agp_free_key(int key);
+int agp_num_entries(void);
+u32 agp_collect_device_status(struct agp_bridge_data *bridge, u32 mode, u32 command);
+void agp_device_command(u32 command, int agp_v3);
+int agp_3_5_enable(struct agp_bridge_data *bridge);
+void global_cache_flush(void);
+void get_agp_version(struct agp_bridge_data *bridge);
+unsigned long agp_generic_mask_memory(struct agp_bridge_data *bridge,
+ unsigned long addr, int type);
+struct agp_bridge_data *agp_generic_find_bridge(struct pci_dev *pdev);
+
+/* generic routines for agp>=3 */
+int agp3_generic_fetch_size(void);
+void agp3_generic_tlbflush(struct agp_memory *mem);
+int agp3_generic_configure(void);
+void agp3_generic_cleanup(void);
+
+/* aperture sizes have been standardised since v3 */
+#define AGP_GENERIC_SIZES_ENTRIES 11
+extern struct aper_size_info_16 agp3_generic_sizes[];
+
+
+extern int agp_off;
+extern int agp_try_unsupported_boot;
+
+/* Chipset independant registers (from AGP Spec) */
+#define AGP_APBASE 0x10
+
+#define AGPSTAT 0x4
+#define AGPCMD 0x8
+#define AGPNISTAT 0xc
+#define AGPCTRL 0x10
+#define AGPAPSIZE 0x14
+#define AGPNEPG 0x16
+#define AGPGARTLO 0x18
+#define AGPGARTHI 0x1c
+#define AGPNICMD 0x20
+
+#define AGP_MAJOR_VERSION_SHIFT (20)
+#define AGP_MINOR_VERSION_SHIFT (16)
+
+#define AGPSTAT_RQ_DEPTH (0xff000000)
+#define AGPSTAT_RQ_DEPTH_SHIFT 24
+
+#define AGPSTAT_CAL_MASK (1<<12|1<<11|1<<10)
+#define AGPSTAT_ARQSZ (1<<15|1<<14|1<<13)
+#define AGPSTAT_ARQSZ_SHIFT 13
+
+#define AGPSTAT_SBA (1<<9)
+#define AGPSTAT_AGP_ENABLE (1<<8)
+#define AGPSTAT_FW (1<<4)
+#define AGPSTAT_MODE_3_0 (1<<3)
+
+#define AGPSTAT2_1X (1<<0)
+#define AGPSTAT2_2X (1<<1)
+#define AGPSTAT2_4X (1<<2)
+
+#define AGPSTAT3_RSVD (1<<2)
+#define AGPSTAT3_8X (1<<1)
+#define AGPSTAT3_4X (1)
+
+#define AGPCTRL_APERENB (1<<8)
+#define AGPCTRL_GTLBEN (1<<7)
+
+#define AGP2_RESERVED_MASK 0x00fffcc8
+#define AGP3_RESERVED_MASK 0x00ff00c4
+
+#define AGP_ERRATA_FASTWRITES 1<<0
+#define AGP_ERRATA_SBA 1<<1
+#define AGP_ERRATA_1X 1<<2
+
+#endif /* _AGP_BACKEND_PRIV_H */
diff --git a/drivers/char/agp/ali-agp.c b/drivers/char/agp/ali-agp.c
new file mode 100644
index 000000000000..c86a22c5499b
--- /dev/null
+++ b/drivers/char/agp/ali-agp.c
@@ -0,0 +1,414 @@
+/*
+ * ALi AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+#define ALI_AGPCTRL 0xb8
+#define ALI_ATTBASE 0xbc
+#define ALI_TLBCTRL 0xc0
+#define ALI_TAGCTRL 0xc4
+#define ALI_CACHE_FLUSH_CTRL 0xD0
+#define ALI_CACHE_FLUSH_ADDR_MASK 0xFFFFF000
+#define ALI_CACHE_FLUSH_EN 0x100
+
+static int ali_fetch_size(void)
+{
+ int i;
+ u32 temp;
+ struct aper_size_info_32 *values;
+
+ pci_read_config_dword(agp_bridge->dev, ALI_ATTBASE, &temp);
+ temp &= ~(0xfffffff0);
+ values = A_SIZE_32(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static void ali_tlbflush(struct agp_memory *mem)
+{
+ u32 temp;
+
+ pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+ temp &= 0xfffffff0;
+ temp |= (1<<0 | 1<<1);
+ pci_write_config_dword(agp_bridge->dev, ALI_TAGCTRL, temp);
+}
+
+static void ali_cleanup(void)
+{
+ struct aper_size_info_32 *previous_size;
+ u32 temp;
+
+ previous_size = A_SIZE_32(agp_bridge->previous_size);
+
+ pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+// clear tag
+ pci_write_config_dword(agp_bridge->dev, ALI_TAGCTRL,
+ ((temp & 0xffffff00) | 0x00000001|0x00000002));
+
+ pci_read_config_dword(agp_bridge->dev, ALI_ATTBASE, &temp);
+ pci_write_config_dword(agp_bridge->dev, ALI_ATTBASE,
+ ((temp & 0x00000ff0) | previous_size->size_value));
+}
+
+static int ali_configure(void)
+{
+ u32 temp;
+ struct aper_size_info_32 *current_size;
+
+ current_size = A_SIZE_32(agp_bridge->current_size);
+
+ /* aperture size and gatt addr */
+ pci_read_config_dword(agp_bridge->dev, ALI_ATTBASE, &temp);
+ temp = (((temp & 0x00000ff0) | (agp_bridge->gatt_bus_addr & 0xfffff000))
+ | (current_size->size_value & 0xf));
+ pci_write_config_dword(agp_bridge->dev, ALI_ATTBASE, temp);
+
+ /* tlb control */
+ pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, ALI_TLBCTRL, ((temp & 0xffffff00) | 0x00000010));
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+#if 0
+ if (agp_bridge->type == ALI_M1541) {
+ u32 nlvm_addr = 0;
+
+ switch (current_size->size_value) {
+ case 0: break;
+ case 1: nlvm_addr = 0x100000;break;
+ case 2: nlvm_addr = 0x200000;break;
+ case 3: nlvm_addr = 0x400000;break;
+ case 4: nlvm_addr = 0x800000;break;
+ case 6: nlvm_addr = 0x1000000;break;
+ case 7: nlvm_addr = 0x2000000;break;
+ case 8: nlvm_addr = 0x4000000;break;
+ case 9: nlvm_addr = 0x8000000;break;
+ case 10: nlvm_addr = 0x10000000;break;
+ default: break;
+ }
+ nlvm_addr--;
+ nlvm_addr&=0xfff00000;
+
+ nlvm_addr+= agp_bridge->gart_bus_addr;
+ nlvm_addr|=(agp_bridge->gart_bus_addr>>12);
+ printk(KERN_INFO PFX "nlvm top &base = %8x\n",nlvm_addr);
+ }
+#endif
+
+ pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+ temp &= 0xffffff7f; //enable TLB
+ pci_write_config_dword(agp_bridge->dev, ALI_TLBCTRL, temp);
+
+ return 0;
+}
+
+
+static void m1541_cache_flush(void)
+{
+ int i, page_count;
+ u32 temp;
+
+ global_cache_flush();
+
+ page_count = 1 << A_SIZE_32(agp_bridge->current_size)->page_order;
+ for (i = 0; i < PAGE_SIZE * page_count; i += PAGE_SIZE) {
+ pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+ &temp);
+ pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+ (((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+ (agp_bridge->gatt_bus_addr + i)) |
+ ALI_CACHE_FLUSH_EN));
+ }
+}
+
+static void *m1541_alloc_page(struct agp_bridge_data *bridge)
+{
+ void *addr = agp_generic_alloc_page(agp_bridge);
+ u32 temp;
+
+ if (!addr)
+ return NULL;
+
+ pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+ (((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+ virt_to_phys(addr)) | ALI_CACHE_FLUSH_EN ));
+ return addr;
+}
+
+static void ali_destroy_page(void * addr)
+{
+ if (addr) {
+ global_cache_flush(); /* is this really needed? --hch */
+ agp_generic_destroy_page(addr);
+ }
+}
+
+static void m1541_destroy_page(void * addr)
+{
+ u32 temp;
+
+ if (addr == NULL)
+ return;
+
+ global_cache_flush();
+
+ pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+ (((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+ virt_to_phys(addr)) | ALI_CACHE_FLUSH_EN));
+ agp_generic_destroy_page(addr);
+}
+
+
+/* Setup function */
+
+static struct aper_size_info_32 ali_generic_sizes[7] =
+{
+ {256, 65536, 6, 10},
+ {128, 32768, 5, 9},
+ {64, 16384, 4, 8},
+ {32, 8192, 3, 7},
+ {16, 4096, 2, 6},
+ {8, 2048, 1, 4},
+ {4, 1024, 0, 3}
+};
+
+struct agp_bridge_driver ali_generic_bridge = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = ali_generic_sizes,
+ .size_type = U32_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = ali_configure,
+ .fetch_size = ali_fetch_size,
+ .cleanup = ali_cleanup,
+ .tlb_flush = ali_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = ali_destroy_page,
+};
+
+struct agp_bridge_driver ali_m1541_bridge = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = ali_generic_sizes,
+ .size_type = U32_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = ali_configure,
+ .fetch_size = ali_fetch_size,
+ .cleanup = ali_cleanup,
+ .tlb_flush = ali_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = m1541_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = m1541_alloc_page,
+ .agp_destroy_page = m1541_destroy_page,
+};
+
+
+static struct agp_device_ids ali_agp_device_ids[] __devinitdata =
+{
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1541,
+ .chipset_name = "M1541",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1621,
+ .chipset_name = "M1621",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1631,
+ .chipset_name = "M1631",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1632,
+ .chipset_name = "M1632",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1641,
+ .chipset_name = "M1641",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1644,
+ .chipset_name = "M1644",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1647,
+ .chipset_name = "M1647",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1651,
+ .chipset_name = "M1651",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1671,
+ .chipset_name = "M1671",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1681,
+ .chipset_name = "M1681",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AL_M1683,
+ .chipset_name = "M1683",
+ },
+
+ { }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_ali_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_device_ids *devs = ali_agp_device_ids;
+ struct agp_bridge_data *bridge;
+ u8 hidden_1621_id, cap_ptr;
+ int j;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ /* probe for known chipsets */
+ for (j = 0; devs[j].chipset_name; j++) {
+ if (pdev->device == devs[j].device_id)
+ goto found;
+ }
+
+ printk(KERN_ERR PFX "Unsupported ALi chipset (device id: %04x)\n",
+ pdev->device);
+ return -ENODEV;
+
+
+found:
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_AL_M1541:
+ bridge->driver = &ali_m1541_bridge;
+ break;
+ case PCI_DEVICE_ID_AL_M1621:
+ pci_read_config_byte(pdev, 0xFB, &hidden_1621_id);
+ switch (hidden_1621_id) {
+ case 0x31:
+ devs[j].chipset_name = "M1631";
+ break;
+ case 0x32:
+ devs[j].chipset_name = "M1632";
+ break;
+ case 0x41:
+ devs[j].chipset_name = "M1641";
+ break;
+ case 0x43:
+ devs[j].chipset_name = "M????";
+ break;
+ case 0x47:
+ devs[j].chipset_name = "M1647";
+ break;
+ case 0x51:
+ devs[j].chipset_name = "M1651";
+ break;
+ default:
+ break;
+ }
+ /*FALLTHROUGH*/
+ default:
+ bridge->driver = &ali_generic_bridge;
+ }
+
+ printk(KERN_INFO PFX "Detected ALi %s chipset\n",
+ devs[j].chipset_name);
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_ali_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_ali_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AL,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_ali_pci_table);
+
+static struct pci_driver agp_ali_pci_driver = {
+ .name = "agpgart-ali",
+ .id_table = agp_ali_pci_table,
+ .probe = agp_ali_probe,
+ .remove = agp_ali_remove,
+};
+
+static int __init agp_ali_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_ali_pci_driver);
+}
+
+static void __exit agp_ali_cleanup(void)
+{
+ pci_unregister_driver(&agp_ali_pci_driver);
+}
+
+module_init(agp_ali_init);
+module_exit(agp_ali_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/alpha-agp.c b/drivers/char/agp/alpha-agp.c
new file mode 100644
index 000000000000..a072d32005a4
--- /dev/null
+++ b/drivers/char/agp/alpha-agp.c
@@ -0,0 +1,216 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <asm/machvec.h>
+#include <asm/agp_backend.h>
+#include "../../../arch/alpha/kernel/pci_impl.h"
+
+#include "agp.h"
+
+static struct page *alpha_core_agp_vm_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int *type)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+ dma_addr_t dma_addr;
+ unsigned long pa;
+ struct page *page;
+
+ dma_addr = address - vma->vm_start + agp->aperture.bus_base;
+ pa = agp->ops->translate(agp, dma_addr);
+
+ if (pa == (unsigned long)-EINVAL) return NULL; /* no translation */
+
+ /*
+ * Get the page, inc the use count, and return it
+ */
+ page = virt_to_page(__va(pa));
+ get_page(page);
+ if (type)
+ *type = VM_FAULT_MINOR;
+ return page;
+}
+
+static struct aper_size_info_fixed alpha_core_agp_sizes[] =
+{
+ { 0, 0, 0 }, /* filled in by alpha_core_agp_setup */
+};
+
+struct vm_operations_struct alpha_core_agp_vm_ops = {
+ .nopage = alpha_core_agp_vm_nopage,
+};
+
+
+static int alpha_core_agp_nop(void)
+{
+ /* just return success */
+ return 0;
+}
+
+static int alpha_core_agp_fetch_size(void)
+{
+ return alpha_core_agp_sizes[0].size;
+}
+
+static int alpha_core_agp_configure(void)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+ agp_bridge->gart_bus_addr = agp->aperture.bus_base;
+ return 0;
+}
+
+static void alpha_core_agp_cleanup(void)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+
+ agp->ops->cleanup(agp);
+}
+
+static void alpha_core_agp_tlbflush(struct agp_memory *mem)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+ alpha_mv.mv_pci_tbi(agp->hose, 0, -1);
+}
+
+static void alpha_core_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ alpha_agp_info *agp = bridge->dev_private_data;
+
+ agp->mode.lw = agp_collect_device_status(bridge, mode,
+ agp->capability.lw);
+
+ agp->mode.bits.enable = 1;
+ agp->ops->configure(agp);
+
+ agp_device_command(agp->mode.lw, 0);
+}
+
+static int alpha_core_agp_insert_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+ int num_entries, status;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+ if ((pg_start + mem->page_count) > num_entries) return -EINVAL;
+
+ status = agp->ops->bind(agp, pg_start, mem);
+ mb();
+ alpha_core_agp_tlbflush(mem);
+
+ return status;
+}
+
+static int alpha_core_agp_remove_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ alpha_agp_info *agp = agp_bridge->dev_private_data;
+ int status;
+
+ status = agp->ops->unbind(agp, pg_start, mem);
+ alpha_core_agp_tlbflush(mem);
+ return status;
+}
+
+struct agp_bridge_driver alpha_core_agp_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = alpha_core_agp_sizes,
+ .num_aperture_sizes = 1,
+ .size_type = FIXED_APER_SIZE,
+ .cant_use_aperture = 1,
+ .masks = NULL,
+
+ .fetch_size = alpha_core_agp_fetch_size,
+ .configure = alpha_core_agp_configure,
+ .agp_enable = alpha_core_agp_enable,
+ .cleanup = alpha_core_agp_cleanup,
+ .tlb_flush = alpha_core_agp_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = alpha_core_agp_nop,
+ .free_gatt_table = alpha_core_agp_nop,
+ .insert_memory = alpha_core_agp_insert_memory,
+ .remove_memory = alpha_core_agp_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+struct agp_bridge_data *alpha_bridge;
+
+int __init
+alpha_core_agp_setup(void)
+{
+ alpha_agp_info *agp = alpha_mv.agp_info();
+ struct pci_dev *pdev; /* faked */
+ struct aper_size_info_fixed *aper_size;
+
+ if (!agp)
+ return -ENODEV;
+ if (agp->ops->setup(agp))
+ return -ENODEV;
+
+ /*
+ * Build the aperture size descriptor
+ */
+ aper_size = alpha_core_agp_sizes;
+ aper_size->size = agp->aperture.size / (1024 * 1024);
+ aper_size->num_entries = agp->aperture.size / PAGE_SIZE;
+ aper_size->page_order = __ffs(aper_size->num_entries / 1024);
+
+ /*
+ * Build a fake pci_dev struct
+ */
+ pdev = kmalloc(sizeof(struct pci_dev), GFP_KERNEL);
+ if (!pdev)
+ return -ENOMEM;
+ pdev->vendor = 0xffff;
+ pdev->device = 0xffff;
+ pdev->sysdata = agp->hose;
+
+ alpha_bridge = agp_alloc_bridge();
+ if (!alpha_bridge)
+ goto fail;
+
+ alpha_bridge->driver = &alpha_core_agp_driver;
+ alpha_bridge->vm_ops = &alpha_core_agp_vm_ops;
+ alpha_bridge->current_size = aper_size; /* only 1 size */
+ alpha_bridge->dev_private_data = agp;
+ alpha_bridge->dev = pdev;
+ alpha_bridge->mode = agp->capability.lw;
+
+ printk(KERN_INFO PFX "Detected AGP on hose %d\n", agp->hose->index);
+ return agp_add_bridge(alpha_bridge);
+
+ fail:
+ kfree(pdev);
+ return -ENOMEM;
+}
+
+static int __init agp_alpha_core_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ if (alpha_mv.agp_info)
+ return alpha_core_agp_setup();
+ return -ENODEV;
+}
+
+static void __exit agp_alpha_core_cleanup(void)
+{
+ agp_remove_bridge(alpha_bridge);
+ agp_put_bridge(alpha_bridge);
+}
+
+module_init(agp_alpha_core_init);
+module_exit(agp_alpha_core_cleanup);
+
+MODULE_AUTHOR("Jeff Wiedemeier <Jeff.Wiedemeier@hp.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/amd-k7-agp.c b/drivers/char/agp/amd-k7-agp.c
new file mode 100644
index 000000000000..f1ea87ea6b65
--- /dev/null
+++ b/drivers/char/agp/amd-k7-agp.c
@@ -0,0 +1,542 @@
+/*
+ * AMD K7 AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+#define AMD_MMBASE 0x14
+#define AMD_APSIZE 0xac
+#define AMD_MODECNTL 0xb0
+#define AMD_MODECNTL2 0xb2
+#define AMD_GARTENABLE 0x02 /* In mmio region (16-bit register) */
+#define AMD_ATTBASE 0x04 /* In mmio region (32-bit register) */
+#define AMD_TLBFLUSH 0x0c /* In mmio region (32-bit register) */
+#define AMD_CACHEENTRY 0x10 /* In mmio region (32-bit register) */
+
+static struct pci_device_id agp_amdk7_pci_table[];
+
+struct amd_page_map {
+ unsigned long *real;
+ unsigned long __iomem *remapped;
+};
+
+static struct _amd_irongate_private {
+ volatile u8 __iomem *registers;
+ struct amd_page_map **gatt_pages;
+ int num_tables;
+} amd_irongate_private;
+
+static int amd_create_page_map(struct amd_page_map *page_map)
+{
+ int i;
+
+ page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+ if (page_map->real == NULL)
+ return -ENOMEM;
+
+ SetPageReserved(virt_to_page(page_map->real));
+ global_cache_flush();
+ page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real),
+ PAGE_SIZE);
+ if (page_map->remapped == NULL) {
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+ page_map->real = NULL;
+ return -ENOMEM;
+ }
+ global_cache_flush();
+
+ for (i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++) {
+ writel(agp_bridge->scratch_page, page_map->remapped+i);
+ readl(page_map->remapped+i); /* PCI Posting. */
+ }
+
+ return 0;
+}
+
+static void amd_free_page_map(struct amd_page_map *page_map)
+{
+ iounmap(page_map->remapped);
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+}
+
+static void amd_free_gatt_pages(void)
+{
+ int i;
+ struct amd_page_map **tables;
+ struct amd_page_map *entry;
+
+ tables = amd_irongate_private.gatt_pages;
+ for (i = 0; i < amd_irongate_private.num_tables; i++) {
+ entry = tables[i];
+ if (entry != NULL) {
+ if (entry->real != NULL)
+ amd_free_page_map(entry);
+ kfree(entry);
+ }
+ }
+ kfree(tables);
+ amd_irongate_private.gatt_pages = NULL;
+}
+
+static int amd_create_gatt_pages(int nr_tables)
+{
+ struct amd_page_map **tables;
+ struct amd_page_map *entry;
+ int retval = 0;
+ int i;
+
+ tables = kmalloc((nr_tables + 1) * sizeof(struct amd_page_map *),
+ GFP_KERNEL);
+ if (tables == NULL)
+ return -ENOMEM;
+
+ memset (tables, 0, sizeof(struct amd_page_map *) * (nr_tables + 1));
+ for (i = 0; i < nr_tables; i++) {
+ entry = kmalloc(sizeof(struct amd_page_map), GFP_KERNEL);
+ if (entry == NULL) {
+ retval = -ENOMEM;
+ break;
+ }
+ memset (entry, 0, sizeof(struct amd_page_map));
+ tables[i] = entry;
+ retval = amd_create_page_map(entry);
+ if (retval != 0)
+ break;
+ }
+ amd_irongate_private.num_tables = nr_tables;
+ amd_irongate_private.gatt_pages = tables;
+
+ if (retval != 0)
+ amd_free_gatt_pages();
+
+ return retval;
+}
+
+/* Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+ GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#define GET_GATT(addr) (amd_irongate_private.gatt_pages[\
+ GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int amd_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct aper_size_info_lvl2 *value;
+ struct amd_page_map page_dir;
+ unsigned long addr;
+ int retval;
+ u32 temp;
+ int i;
+
+ value = A_SIZE_LVL2(agp_bridge->current_size);
+ retval = amd_create_page_map(&page_dir);
+ if (retval != 0)
+ return retval;
+
+ retval = amd_create_gatt_pages(value->num_entries / 1024);
+ if (retval != 0) {
+ amd_free_page_map(&page_dir);
+ return retval;
+ }
+
+ agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+ agp_bridge->gatt_table = (u32 __iomem *)page_dir.remapped;
+ agp_bridge->gatt_bus_addr = virt_to_phys(page_dir.real);
+
+ /* Get the address for the gart region.
+ * This is a bus address even on the alpha, b/c its
+ * used to program the agp master not the cpu
+ */
+
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ agp_bridge->gart_bus_addr = addr;
+
+ /* Calculate the agp offset */
+ for (i = 0; i < value->num_entries / 1024; i++, addr += 0x00400000) {
+ writel(virt_to_phys(amd_irongate_private.gatt_pages[i]->real) | 1,
+ page_dir.remapped+GET_PAGE_DIR_OFF(addr));
+ readl(page_dir.remapped+GET_PAGE_DIR_OFF(addr)); /* PCI Posting. */
+ }
+
+ return 0;
+}
+
+static int amd_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct amd_page_map page_dir;
+
+ page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+ page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+ amd_free_gatt_pages();
+ amd_free_page_map(&page_dir);
+ return 0;
+}
+
+static int amd_irongate_fetch_size(void)
+{
+ int i;
+ u32 temp;
+ struct aper_size_info_lvl2 *values;
+
+ pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+ temp = (temp & 0x0000000e);
+ values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static int amd_irongate_configure(void)
+{
+ struct aper_size_info_lvl2 *current_size;
+ u32 temp;
+ u16 enable_reg;
+
+ current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+ /* Get the memory mapped registers */
+ pci_read_config_dword(agp_bridge->dev, AMD_MMBASE, &temp);
+ temp = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ amd_irongate_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+
+ /* Write out the address of the gatt table */
+ writel(agp_bridge->gatt_bus_addr, amd_irongate_private.registers+AMD_ATTBASE);
+ readl(amd_irongate_private.registers+AMD_ATTBASE); /* PCI Posting. */
+
+ /* Write the Sync register */
+ pci_write_config_byte(agp_bridge->dev, AMD_MODECNTL, 0x80);
+
+ /* Set indexing mode */
+ pci_write_config_byte(agp_bridge->dev, AMD_MODECNTL2, 0x00);
+
+ /* Write the enable register */
+ enable_reg = readw(amd_irongate_private.registers+AMD_GARTENABLE);
+ enable_reg = (enable_reg | 0x0004);
+ writew(enable_reg, amd_irongate_private.registers+AMD_GARTENABLE);
+ readw(amd_irongate_private.registers+AMD_GARTENABLE); /* PCI Posting. */
+
+ /* Write out the size register */
+ pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+ temp = (((temp & ~(0x0000000e)) | current_size->size_value) | 1);
+ pci_write_config_dword(agp_bridge->dev, AMD_APSIZE, temp);
+
+ /* Flush the tlb */
+ writel(1, amd_irongate_private.registers+AMD_TLBFLUSH);
+ readl(amd_irongate_private.registers+AMD_TLBFLUSH); /* PCI Posting.*/
+ return 0;
+}
+
+static void amd_irongate_cleanup(void)
+{
+ struct aper_size_info_lvl2 *previous_size;
+ u32 temp;
+ u16 enable_reg;
+
+ previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+
+ enable_reg = readw(amd_irongate_private.registers+AMD_GARTENABLE);
+ enable_reg = (enable_reg & ~(0x0004));
+ writew(enable_reg, amd_irongate_private.registers+AMD_GARTENABLE);
+ readw(amd_irongate_private.registers+AMD_GARTENABLE); /* PCI Posting. */
+
+ /* Write back the previous size and disable gart translation */
+ pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+ temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+ pci_write_config_dword(agp_bridge->dev, AMD_APSIZE, temp);
+ iounmap((void __iomem *) amd_irongate_private.registers);
+}
+
+/*
+ * This routine could be implemented by taking the addresses
+ * written to the GATT, and flushing them individually. However
+ * currently it just flushes the whole table. Which is probably
+ * more efficent, since agp_memory blocks can be a large number of
+ * entries.
+ */
+
+static void amd_irongate_tlbflush(struct agp_memory *temp)
+{
+ writel(1, amd_irongate_private.registers+AMD_TLBFLUSH);
+ readl(amd_irongate_private.registers+AMD_TLBFLUSH); /* PCI Posting. */
+}
+
+static int amd_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i, j, num_entries;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+ while (j < (pg_start + mem->page_count)) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ if (!PGE_EMPTY(agp_bridge, readl(cur_gatt+GET_GATT_OFF(addr))))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ writel(agp_generic_mask_memory(agp_bridge,
+ mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+ readl(cur_gatt+GET_GATT_OFF(addr)); /* PCI Posting. */
+ }
+ amd_irongate_tlbflush(mem);
+ return 0;
+}
+
+static int amd_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+ readl(cur_gatt+GET_GATT_OFF(addr)); /* PCI Posting. */
+ }
+
+ amd_irongate_tlbflush(mem);
+ return 0;
+}
+
+static struct aper_size_info_lvl2 amd_irongate_sizes[7] =
+{
+ {2048, 524288, 0x0000000c},
+ {1024, 262144, 0x0000000a},
+ {512, 131072, 0x00000008},
+ {256, 65536, 0x00000006},
+ {128, 32768, 0x00000004},
+ {64, 16384, 0x00000002},
+ {32, 8192, 0x00000000}
+};
+
+static struct gatt_mask amd_irongate_masks[] =
+{
+ {.mask = 1, .type = 0}
+};
+
+struct agp_bridge_driver amd_irongate_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = amd_irongate_sizes,
+ .size_type = LVL2_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = amd_irongate_configure,
+ .fetch_size = amd_irongate_fetch_size,
+ .cleanup = amd_irongate_cleanup,
+ .tlb_flush = amd_irongate_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = amd_irongate_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = amd_create_gatt_table,
+ .free_gatt_table = amd_free_gatt_table,
+ .insert_memory = amd_insert_memory,
+ .remove_memory = amd_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_device_ids amd_agp_device_ids[] __devinitdata =
+{
+ {
+ .device_id = PCI_DEVICE_ID_AMD_FE_GATE_7006,
+ .chipset_name = "Irongate",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AMD_FE_GATE_700E,
+ .chipset_name = "761",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_AMD_FE_GATE_700C,
+ .chipset_name = "760MP",
+ },
+ { }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_amdk7_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+ int j;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ j = ent - agp_amdk7_pci_table;
+ printk(KERN_INFO PFX "Detected AMD %s chipset\n",
+ amd_agp_device_ids[j].chipset_name);
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &amd_irongate_driver;
+ bridge->dev_private_data = &amd_irongate_private,
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ /* 751 Errata (22564_B-1.PDF)
+ erratum 20: strobe glitch with Nvidia NV10 GeForce cards.
+ system controller may experience noise due to strong drive strengths
+ */
+ if (agp_bridge->dev->device == PCI_DEVICE_ID_AMD_FE_GATE_7006) {
+ u8 cap_ptr=0;
+ struct pci_dev *gfxcard=NULL;
+ while (!cap_ptr) {
+ gfxcard = pci_get_class(PCI_CLASS_DISPLAY_VGA<<8, gfxcard);
+ if (!gfxcard) {
+ printk (KERN_INFO PFX "Couldn't find an AGP VGA controller.\n");
+ return -ENODEV;
+ }
+ cap_ptr = pci_find_capability(gfxcard, PCI_CAP_ID_AGP);
+ if (!cap_ptr) {
+ pci_dev_put(gfxcard);
+ continue;
+ }
+ }
+
+ /* With so many variants of NVidia cards, it's simpler just
+ to blacklist them all, and then whitelist them as needed
+ (if necessary at all). */
+ if (gfxcard->vendor == PCI_VENDOR_ID_NVIDIA) {
+ agp_bridge->flags |= AGP_ERRATA_1X;
+ printk (KERN_INFO PFX "AMD 751 chipset with NVidia GeForce detected. Forcing to 1X due to errata.\n");
+ }
+ pci_dev_put(gfxcard);
+ }
+
+ /* 761 Errata (23613_F.pdf)
+ * Revisions B0/B1 were a disaster.
+ * erratum 44: SYSCLK/AGPCLK skew causes 2X failures -- Force mode to 1X
+ * erratum 45: Timing problem prevents fast writes -- Disable fast write.
+ * erratum 46: Setup violation on AGP SBA pins - Disable side band addressing.
+ * With this lot disabled, we should prevent lockups. */
+ if (agp_bridge->dev->device == PCI_DEVICE_ID_AMD_FE_GATE_700E) {
+ u8 revision=0;
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &revision);
+ if (revision == 0x10 || revision == 0x11) {
+ agp_bridge->flags = AGP_ERRATA_FASTWRITES;
+ agp_bridge->flags |= AGP_ERRATA_SBA;
+ agp_bridge->flags |= AGP_ERRATA_1X;
+ printk (KERN_INFO PFX "AMD 761 chipset with errata detected - disabling AGP fast writes & SBA and forcing to 1X.\n");
+ }
+ }
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_amdk7_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+/* must be the same order as name table above */
+static struct pci_device_id agp_amdk7_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AMD,
+ .device = PCI_DEVICE_ID_AMD_FE_GATE_7006,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AMD,
+ .device = PCI_DEVICE_ID_AMD_FE_GATE_700E,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AMD,
+ .device = PCI_DEVICE_ID_AMD_FE_GATE_700C,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_amdk7_pci_table);
+
+static struct pci_driver agp_amdk7_pci_driver = {
+ .name = "agpgart-amdk7",
+ .id_table = agp_amdk7_pci_table,
+ .probe = agp_amdk7_probe,
+ .remove = agp_amdk7_remove,
+};
+
+static int __init agp_amdk7_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_amdk7_pci_driver);
+}
+
+static void __exit agp_amdk7_cleanup(void)
+{
+ pci_unregister_driver(&agp_amdk7_pci_driver);
+}
+
+module_init(agp_amdk7_init);
+module_exit(agp_amdk7_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/amd64-agp.c b/drivers/char/agp/amd64-agp.c
new file mode 100644
index 000000000000..905f0629c44f
--- /dev/null
+++ b/drivers/char/agp/amd64-agp.c
@@ -0,0 +1,761 @@
+/*
+ * Copyright 2001-2003 SuSE Labs.
+ * Distributed under the GNU public license, v2.
+ *
+ * This is a GART driver for the AMD Opteron/Athlon64 on-CPU northbridge.
+ * It also includes support for the AMD 8151 AGP bridge,
+ * although it doesn't actually do much, as all the real
+ * work is done in the northbridge(s).
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+/* Will need to be increased if AMD64 ever goes >8-way. */
+#define MAX_HAMMER_GARTS 8
+
+/* PTE bits. */
+#define GPTE_VALID 1
+#define GPTE_COHERENT 2
+
+/* Aperture control register bits. */
+#define GARTEN (1<<0)
+#define DISGARTCPU (1<<4)
+#define DISGARTIO (1<<5)
+
+/* GART cache control register bits. */
+#define INVGART (1<<0)
+#define GARTPTEERR (1<<1)
+
+/* K8 On-cpu GART registers */
+#define AMD64_GARTAPERTURECTL 0x90
+#define AMD64_GARTAPERTUREBASE 0x94
+#define AMD64_GARTTABLEBASE 0x98
+#define AMD64_GARTCACHECTL 0x9c
+#define AMD64_GARTEN (1<<0)
+
+/* NVIDIA K8 registers */
+#define NVIDIA_X86_64_0_APBASE 0x10
+#define NVIDIA_X86_64_1_APBASE1 0x50
+#define NVIDIA_X86_64_1_APLIMIT1 0x54
+#define NVIDIA_X86_64_1_APSIZE 0xa8
+#define NVIDIA_X86_64_1_APBASE2 0xd8
+#define NVIDIA_X86_64_1_APLIMIT2 0xdc
+
+/* ULi K8 registers */
+#define ULI_X86_64_BASE_ADDR 0x10
+#define ULI_X86_64_HTT_FEA_REG 0x50
+#define ULI_X86_64_ENU_SCR_REG 0x54
+
+static int nr_garts;
+static struct pci_dev * hammers[MAX_HAMMER_GARTS];
+
+static struct resource *aperture_resource;
+static int __initdata agp_try_unsupported;
+
+static int gart_iterator;
+#define for_each_nb() for(gart_iterator=0;gart_iterator<nr_garts;gart_iterator++)
+
+static void flush_amd64_tlb(struct pci_dev *dev)
+{
+ u32 tmp;
+
+ pci_read_config_dword (dev, AMD64_GARTCACHECTL, &tmp);
+ tmp |= INVGART;
+ pci_write_config_dword (dev, AMD64_GARTCACHECTL, tmp);
+}
+
+static void amd64_tlbflush(struct agp_memory *temp)
+{
+ for_each_nb()
+ flush_amd64_tlb(hammers[gart_iterator]);
+}
+
+static int amd64_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i, j, num_entries;
+ long long tmp;
+ u32 pte;
+
+ num_entries = agp_num_entries();
+
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ /* Make sure we can fit the range in the gatt table. */
+ /* FIXME: could wrap */
+ if (((unsigned long)pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+
+ /* gatt table should be empty. */
+ while (j < (pg_start + mem->page_count)) {
+ if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+j)))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ tmp = agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type);
+
+ BUG_ON(tmp & 0xffffff0000000ffcULL);
+ pte = (tmp & 0x000000ff00000000ULL) >> 28;
+ pte |=(tmp & 0x00000000fffff000ULL);
+ pte |= GPTE_VALID | GPTE_COHERENT;
+
+ writel(pte, agp_bridge->gatt_table+j);
+ readl(agp_bridge->gatt_table+j); /* PCI Posting. */
+ }
+ amd64_tlbflush(mem);
+ return 0;
+}
+
+/*
+ * This hack alters the order element according
+ * to the size of a long. It sucks. I totally disown this, even
+ * though it does appear to work for the most part.
+ */
+static struct aper_size_info_32 amd64_aperture_sizes[7] =
+{
+ {32, 8192, 3+(sizeof(long)/8), 0 },
+ {64, 16384, 4+(sizeof(long)/8), 1<<1 },
+ {128, 32768, 5+(sizeof(long)/8), 1<<2 },
+ {256, 65536, 6+(sizeof(long)/8), 1<<1 | 1<<2 },
+ {512, 131072, 7+(sizeof(long)/8), 1<<3 },
+ {1024, 262144, 8+(sizeof(long)/8), 1<<1 | 1<<3},
+ {2048, 524288, 9+(sizeof(long)/8), 1<<2 | 1<<3}
+};
+
+
+/*
+ * Get the current Aperture size from the x86-64.
+ * Note, that there may be multiple x86-64's, but we just return
+ * the value from the first one we find. The set_size functions
+ * keep the rest coherent anyway. Or at least should do.
+ */
+static int amd64_fetch_size(void)
+{
+ struct pci_dev *dev;
+ int i;
+ u32 temp;
+ struct aper_size_info_32 *values;
+
+ dev = hammers[0];
+ if (dev==NULL)
+ return 0;
+
+ pci_read_config_dword(dev, AMD64_GARTAPERTURECTL, &temp);
+ temp = (temp & 0xe);
+ values = A_SIZE_32(amd64_aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+ return 0;
+}
+
+/*
+ * In a multiprocessor x86-64 system, this function gets
+ * called once for each CPU.
+ */
+static u64 amd64_configure (struct pci_dev *hammer, u64 gatt_table)
+{
+ u64 aperturebase;
+ u32 tmp;
+ u64 addr, aper_base;
+
+ /* Address to map to */
+ pci_read_config_dword (hammer, AMD64_GARTAPERTUREBASE, &tmp);
+ aperturebase = tmp << 25;
+ aper_base = (aperturebase & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* address of the mappings table */
+ addr = (u64) gatt_table;
+ addr >>= 12;
+ tmp = (u32) addr<<4;
+ tmp &= ~0xf;
+ pci_write_config_dword (hammer, AMD64_GARTTABLEBASE, tmp);
+
+ /* Enable GART translation for this hammer. */
+ pci_read_config_dword(hammer, AMD64_GARTAPERTURECTL, &tmp);
+ tmp |= GARTEN;
+ tmp &= ~(DISGARTCPU | DISGARTIO);
+ pci_write_config_dword(hammer, AMD64_GARTAPERTURECTL, tmp);
+
+ /* keep CPU's coherent. */
+ flush_amd64_tlb (hammer);
+
+ return aper_base;
+}
+
+
+static struct aper_size_info_32 amd_8151_sizes[7] =
+{
+ {2048, 524288, 9, 0x00000000 }, /* 0 0 0 0 0 0 */
+ {1024, 262144, 8, 0x00000400 }, /* 1 0 0 0 0 0 */
+ {512, 131072, 7, 0x00000600 }, /* 1 1 0 0 0 0 */
+ {256, 65536, 6, 0x00000700 }, /* 1 1 1 0 0 0 */
+ {128, 32768, 5, 0x00000720 }, /* 1 1 1 1 0 0 */
+ {64, 16384, 4, 0x00000730 }, /* 1 1 1 1 1 0 */
+ {32, 8192, 3, 0x00000738 } /* 1 1 1 1 1 1 */
+};
+
+static int amd_8151_configure(void)
+{
+ unsigned long gatt_bus = virt_to_phys(agp_bridge->gatt_table_real);
+
+ /* Configure AGP regs in each x86-64 host bridge. */
+ for_each_nb() {
+ agp_bridge->gart_bus_addr =
+ amd64_configure(hammers[gart_iterator],gatt_bus);
+ }
+ return 0;
+}
+
+
+static void amd64_cleanup(void)
+{
+ u32 tmp;
+
+ for_each_nb() {
+ /* disable gart translation */
+ pci_read_config_dword (hammers[gart_iterator], AMD64_GARTAPERTURECTL, &tmp);
+ tmp &= ~AMD64_GARTEN;
+ pci_write_config_dword (hammers[gart_iterator], AMD64_GARTAPERTURECTL, tmp);
+ }
+}
+
+
+struct agp_bridge_driver amd_8151_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = amd_8151_sizes,
+ .size_type = U32_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = amd_8151_configure,
+ .fetch_size = amd64_fetch_size,
+ .cleanup = amd64_cleanup,
+ .tlb_flush = amd64_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = amd64_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+/* Some basic sanity checks for the aperture. */
+static int __devinit aperture_valid(u64 aper, u32 size)
+{
+ u32 pfn, c;
+ if (aper == 0) {
+ printk(KERN_ERR PFX "No aperture\n");
+ return 0;
+ }
+ if (size < 32*1024*1024) {
+ printk(KERN_ERR PFX "Aperture too small (%d MB)\n", size>>20);
+ return 0;
+ }
+ if (aper + size > 0xffffffff) {
+ printk(KERN_ERR PFX "Aperture out of bounds\n");
+ return 0;
+ }
+ pfn = aper >> PAGE_SHIFT;
+ for (c = 0; c < size/PAGE_SIZE; c++) {
+ if (!pfn_valid(pfn + c))
+ break;
+ if (!PageReserved(pfn_to_page(pfn + c))) {
+ printk(KERN_ERR PFX "Aperture pointing to RAM\n");
+ return 0;
+ }
+ }
+
+ /* Request the Aperture. This catches cases when someone else
+ already put a mapping in there - happens with some very broken BIOS
+
+ Maybe better to use pci_assign_resource/pci_enable_device instead
+ trusting the bridges? */
+ if (!aperture_resource &&
+ !(aperture_resource = request_mem_region(aper, size, "aperture"))) {
+ printk(KERN_ERR PFX "Aperture conflicts with PCI mapping.\n");
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * W*s centric BIOS sometimes only set up the aperture in the AGP
+ * bridge, not the northbridge. On AMD64 this is handled early
+ * in aperture.c, but when GART_IOMMU is not enabled or we run
+ * on a 32bit kernel this needs to be redone.
+ * Unfortunately it is impossible to fix the aperture here because it's too late
+ * to allocate that much memory. But at least error out cleanly instead of
+ * crashing.
+ */
+static __devinit int fix_northbridge(struct pci_dev *nb, struct pci_dev *agp,
+ u16 cap)
+{
+ u32 aper_low, aper_hi;
+ u64 aper, nb_aper;
+ int order = 0;
+ u32 nb_order, nb_base;
+ u16 apsize;
+
+ pci_read_config_dword(nb, 0x90, &nb_order);
+ nb_order = (nb_order >> 1) & 7;
+ pci_read_config_dword(nb, 0x94, &nb_base);
+ nb_aper = nb_base << 25;
+ if (aperture_valid(nb_aper, (32*1024*1024)<<nb_order)) {
+ return 0;
+ }
+
+ /* Northbridge seems to contain crap. Try the AGP bridge. */
+
+ pci_read_config_word(agp, cap+0x14, &apsize);
+ if (apsize == 0xffff)
+ return -1;
+
+ apsize &= 0xfff;
+ /* Some BIOS use weird encodings not in the AGPv3 table. */
+ if (apsize & 0xff)
+ apsize |= 0xf00;
+ order = 7 - hweight16(apsize);
+
+ pci_read_config_dword(agp, 0x10, &aper_low);
+ pci_read_config_dword(agp, 0x14, &aper_hi);
+ aper = (aper_low & ~((1<<22)-1)) | ((u64)aper_hi << 32);
+ printk(KERN_INFO PFX "Aperture from AGP @ %Lx size %u MB\n", aper, 32 << order);
+ if (order < 0 || !aperture_valid(aper, (32*1024*1024)<<order))
+ return -1;
+
+ pci_write_config_dword(nb, 0x90, order << 1);
+ pci_write_config_dword(nb, 0x94, aper >> 25);
+
+ return 0;
+}
+
+static __devinit int cache_nbs (struct pci_dev *pdev, u32 cap_ptr)
+{
+ struct pci_dev *loop_dev = NULL;
+ int i = 0;
+
+ /* cache pci_devs of northbridges. */
+ while ((loop_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x1103, loop_dev))
+ != NULL) {
+ if (i == MAX_HAMMER_GARTS) {
+ printk(KERN_ERR PFX "Too many northbridges for AGP\n");
+ return -1;
+ }
+ if (fix_northbridge(loop_dev, pdev, cap_ptr) < 0) {
+ printk(KERN_ERR PFX "No usable aperture found.\n");
+#ifdef __x86_64__
+ /* should port this to i386 */
+ printk(KERN_ERR PFX "Consider rebooting with iommu=memaper=2 to get a good aperture.\n");
+#endif
+ return -1;
+ }
+ hammers[i++] = loop_dev;
+ }
+ nr_garts = i;
+ return i == 0 ? -1 : 0;
+}
+
+/* Handle AMD 8151 quirks */
+static void __devinit amd8151_init(struct pci_dev *pdev, struct agp_bridge_data *bridge)
+{
+ char *revstring;
+ u8 rev_id;
+
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+ switch (rev_id) {
+ case 0x01: revstring="A0"; break;
+ case 0x02: revstring="A1"; break;
+ case 0x11: revstring="B0"; break;
+ case 0x12: revstring="B1"; break;
+ case 0x13: revstring="B2"; break;
+ case 0x14: revstring="B3"; break;
+ default: revstring="??"; break;
+ }
+
+ printk (KERN_INFO PFX "Detected AMD 8151 AGP Bridge rev %s\n", revstring);
+
+ /*
+ * Work around errata.
+ * Chips before B2 stepping incorrectly reporting v3.5
+ */
+ if (rev_id < 0x13) {
+ printk (KERN_INFO PFX "Correcting AGP revision (reports 3.5, is really 3.0)\n");
+ bridge->major_version = 3;
+ bridge->minor_version = 0;
+ }
+}
+
+
+static struct aper_size_info_32 uli_sizes[7] =
+{
+ {256, 65536, 6, 10},
+ {128, 32768, 5, 9},
+ {64, 16384, 4, 8},
+ {32, 8192, 3, 7},
+ {16, 4096, 2, 6},
+ {8, 2048, 1, 4},
+ {4, 1024, 0, 3}
+};
+static int __devinit uli_agp_init(struct pci_dev *pdev)
+{
+ u32 httfea,baseaddr,enuscr;
+ struct pci_dev *dev1;
+ int i;
+ unsigned size = amd64_fetch_size();
+ printk(KERN_INFO "Setting up ULi AGP. \n");
+ dev1 = pci_find_slot ((unsigned int)pdev->bus->number,PCI_DEVFN(0,0));
+ if (dev1 == NULL) {
+ printk(KERN_INFO PFX "Detected a ULi chipset, "
+ "but could not fine the secondary device.\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(uli_sizes); i++)
+ if (uli_sizes[i].size == size)
+ break;
+
+ if (i == ARRAY_SIZE(uli_sizes)) {
+ printk(KERN_INFO PFX "No ULi size found for %d\n", size);
+ return -ENODEV;
+ }
+
+ /* shadow x86-64 registers into ULi registers */
+ pci_read_config_dword (hammers[0], AMD64_GARTAPERTUREBASE, &httfea);
+
+ /* if x86-64 aperture base is beyond 4G, exit here */
+ if ((httfea & 0x7fff) >> (32 - 25))
+ return -ENODEV;
+
+ httfea = (httfea& 0x7fff) << 25;
+
+ pci_read_config_dword(pdev, ULI_X86_64_BASE_ADDR, &baseaddr);
+ baseaddr&= ~PCI_BASE_ADDRESS_MEM_MASK;
+ baseaddr|= httfea;
+ pci_write_config_dword(pdev, ULI_X86_64_BASE_ADDR, baseaddr);
+
+ enuscr= httfea+ (size * 1024 * 1024) - 1;
+ pci_write_config_dword(dev1, ULI_X86_64_HTT_FEA_REG, httfea);
+ pci_write_config_dword(dev1, ULI_X86_64_ENU_SCR_REG, enuscr);
+ return 0;
+}
+
+
+static struct aper_size_info_32 nforce3_sizes[5] =
+{
+ {512, 131072, 7, 0x00000000 },
+ {256, 65536, 6, 0x00000008 },
+ {128, 32768, 5, 0x0000000C },
+ {64, 16384, 4, 0x0000000E },
+ {32, 8192, 3, 0x0000000F }
+};
+
+/* Handle shadow device of the Nvidia NForce3 */
+/* CHECK-ME original 2.4 version set up some IORRs. Check if that is needed. */
+static int __devinit nforce3_agp_init(struct pci_dev *pdev)
+{
+ u32 tmp, apbase, apbar, aplimit;
+ struct pci_dev *dev1;
+ int i;
+ unsigned size = amd64_fetch_size();
+
+ printk(KERN_INFO PFX "Setting up Nforce3 AGP.\n");
+
+ dev1 = pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(11, 0));
+ if (dev1 == NULL) {
+ printk(KERN_INFO PFX "agpgart: Detected an NVIDIA "
+ "nForce3 chipset, but could not find "
+ "the secondary device.\n");
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(nforce3_sizes); i++)
+ if (nforce3_sizes[i].size == size)
+ break;
+
+ if (i == ARRAY_SIZE(nforce3_sizes)) {
+ printk(KERN_INFO PFX "No NForce3 size found for %d\n", size);
+ return -ENODEV;
+ }
+
+ pci_read_config_dword(dev1, NVIDIA_X86_64_1_APSIZE, &tmp);
+ tmp &= ~(0xf);
+ tmp |= nforce3_sizes[i].size_value;
+ pci_write_config_dword(dev1, NVIDIA_X86_64_1_APSIZE, tmp);
+
+ /* shadow x86-64 registers into NVIDIA registers */
+ pci_read_config_dword (hammers[0], AMD64_GARTAPERTUREBASE, &apbase);
+
+ /* if x86-64 aperture base is beyond 4G, exit here */
+ if ( (apbase & 0x7fff) >> (32 - 25) )
+ return -ENODEV;
+
+ apbase = (apbase & 0x7fff) << 25;
+
+ pci_read_config_dword(pdev, NVIDIA_X86_64_0_APBASE, &apbar);
+ apbar &= ~PCI_BASE_ADDRESS_MEM_MASK;
+ apbar |= apbase;
+ pci_write_config_dword(pdev, NVIDIA_X86_64_0_APBASE, apbar);
+
+ aplimit = apbase + (size * 1024 * 1024) - 1;
+ pci_write_config_dword(dev1, NVIDIA_X86_64_1_APBASE1, apbase);
+ pci_write_config_dword(dev1, NVIDIA_X86_64_1_APLIMIT1, aplimit);
+ pci_write_config_dword(dev1, NVIDIA_X86_64_1_APBASE2, apbase);
+ pci_write_config_dword(dev1, NVIDIA_X86_64_1_APLIMIT2, aplimit);
+
+ return 0;
+}
+
+static int __devinit agp_amd64_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ /* Could check for AGPv3 here */
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ if (pdev->vendor == PCI_VENDOR_ID_AMD &&
+ pdev->device == PCI_DEVICE_ID_AMD_8151_0) {
+ amd8151_init(pdev, bridge);
+ } else {
+ printk(KERN_INFO PFX "Detected AGP bridge %x\n", pdev->devfn);
+ }
+
+ bridge->driver = &amd_8151_driver;
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+
+ if (cache_nbs(pdev, cap_ptr) == -1) {
+ agp_put_bridge(bridge);
+ return -ENODEV;
+ }
+
+ if (pdev->vendor == PCI_VENDOR_ID_NVIDIA) {
+ int ret = nforce3_agp_init(pdev);
+ if (ret) {
+ agp_put_bridge(bridge);
+ return ret;
+ }
+ }
+
+ if (pdev->vendor == PCI_VENDOR_ID_AL) {
+ int ret = uli_agp_init(pdev);
+ if (ret) {
+ agp_put_bridge(bridge);
+ return ret;
+ }
+ }
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_amd64_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ release_mem_region(virt_to_phys(bridge->gatt_table_real),
+ amd64_aperture_sizes[bridge->aperture_size_idx].size);
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_amd64_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AMD,
+ .device = PCI_DEVICE_ID_AMD_8151_0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* ULi M1689 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_AL,
+ .device = PCI_DEVICE_ID_AL_M1689,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* VIA K8T800Pro */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = PCI_DEVICE_ID_VIA_K8T800PRO_0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* VIA K8T800 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = PCI_DEVICE_ID_VIA_8385_0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* VIA K8M800 / K8N800 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = PCI_DEVICE_ID_VIA_8380_0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* VIA K8T890 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = PCI_DEVICE_ID_VIA_3238_0,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* VIA K8T800/K8M800/K8N800 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_VIA,
+ .device = PCI_DEVICE_ID_VIA_838X_1,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* NForce3 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_NVIDIA,
+ .device = PCI_DEVICE_ID_NVIDIA_NFORCE3,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_NVIDIA,
+ .device = PCI_DEVICE_ID_NVIDIA_NFORCE3S,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ /* SIS 755 */
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_SI,
+ .device = PCI_DEVICE_ID_SI_755,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_amd64_pci_table);
+
+static struct pci_driver agp_amd64_pci_driver = {
+ .name = "agpgart-amd64",
+ .id_table = agp_amd64_pci_table,
+ .probe = agp_amd64_probe,
+ .remove = agp_amd64_remove,
+};
+
+
+/* Not static due to IOMMU code calling it early. */
+int __init agp_amd64_init(void)
+{
+ int err = 0;
+ static struct pci_device_id amd64nb[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x1103) },
+ { },
+ };
+
+ if (agp_off)
+ return -EINVAL;
+ if (pci_register_driver(&agp_amd64_pci_driver) > 0) {
+ struct pci_dev *dev;
+ if (!agp_try_unsupported && !agp_try_unsupported_boot) {
+ printk(KERN_INFO PFX "No supported AGP bridge found.\n");
+#ifdef MODULE
+ printk(KERN_INFO PFX "You can try agp_try_unsupported=1\n");
+#else
+ printk(KERN_INFO PFX "You can boot with agp=try_unsupported\n");
+#endif
+ return -ENODEV;
+ }
+
+ /* First check that we have at least one AMD64 NB */
+ if (!pci_dev_present(amd64nb))
+ return -ENODEV;
+
+ /* Look for any AGP bridge */
+ dev = NULL;
+ err = -ENODEV;
+ for_each_pci_dev(dev) {
+ if (!pci_find_capability(dev, PCI_CAP_ID_AGP))
+ continue;
+ /* Only one bridge supported right now */
+ if (agp_amd64_probe(dev, NULL) == 0) {
+ err = 0;
+ break;
+ }
+ }
+ }
+ return err;
+}
+
+static void __exit agp_amd64_cleanup(void)
+{
+ if (aperture_resource)
+ release_resource(aperture_resource);
+ pci_unregister_driver(&agp_amd64_pci_driver);
+}
+
+/* On AMD64 the PCI driver needs to initialize this driver early
+ for the IOMMU, so it has to be called via a backdoor. */
+#ifndef CONFIG_GART_IOMMU
+module_init(agp_amd64_init);
+module_exit(agp_amd64_cleanup);
+#endif
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>, Andi Kleen");
+module_param(agp_try_unsupported, bool, 0);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/agp/ati-agp.c b/drivers/char/agp/ati-agp.c
new file mode 100644
index 000000000000..757dde006fc9
--- /dev/null
+++ b/drivers/char/agp/ati-agp.c
@@ -0,0 +1,548 @@
+/*
+ * ATi AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <asm/agp.h>
+#include "agp.h"
+
+#define ATI_GART_MMBASE_ADDR 0x14
+#define ATI_RS100_APSIZE 0xac
+#define ATI_RS100_IG_AGPMODE 0xb0
+#define ATI_RS300_APSIZE 0xf8
+#define ATI_RS300_IG_AGPMODE 0xfc
+#define ATI_GART_FEATURE_ID 0x00
+#define ATI_GART_BASE 0x04
+#define ATI_GART_CACHE_SZBASE 0x08
+#define ATI_GART_CACHE_CNTRL 0x0c
+#define ATI_GART_CACHE_ENTRY_CNTRL 0x10
+
+
+static struct aper_size_info_lvl2 ati_generic_sizes[7] =
+{
+ {2048, 524288, 0x0000000c},
+ {1024, 262144, 0x0000000a},
+ {512, 131072, 0x00000008},
+ {256, 65536, 0x00000006},
+ {128, 32768, 0x00000004},
+ {64, 16384, 0x00000002},
+ {32, 8192, 0x00000000}
+};
+
+static struct gatt_mask ati_generic_masks[] =
+{
+ { .mask = 1, .type = 0}
+};
+
+
+
+typedef struct _ati_page_map {
+ unsigned long *real;
+ unsigned long __iomem *remapped;
+} ati_page_map;
+
+static struct _ati_generic_private {
+ volatile u8 __iomem *registers;
+ ati_page_map **gatt_pages;
+ int num_tables;
+} ati_generic_private;
+
+static int ati_create_page_map(ati_page_map *page_map)
+{
+ int i, err = 0;
+
+ page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+ if (page_map->real == NULL)
+ return -ENOMEM;
+
+ SetPageReserved(virt_to_page(page_map->real));
+ err = map_page_into_agp(virt_to_page(page_map->real));
+ page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real),
+ PAGE_SIZE);
+ if (page_map->remapped == NULL || err) {
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+ page_map->real = NULL;
+ return -ENOMEM;
+ }
+ /*CACHE_FLUSH();*/
+ global_cache_flush();
+
+ for(i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++) {
+ writel(agp_bridge->scratch_page, page_map->remapped+i);
+ readl(page_map->remapped+i); /* PCI Posting. */
+ }
+
+ return 0;
+}
+
+
+static void ati_free_page_map(ati_page_map *page_map)
+{
+ unmap_page_from_agp(virt_to_page(page_map->real));
+ iounmap(page_map->remapped);
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+}
+
+
+static void ati_free_gatt_pages(void)
+{
+ int i;
+ ati_page_map **tables;
+ ati_page_map *entry;
+
+ tables = ati_generic_private.gatt_pages;
+ for(i = 0; i < ati_generic_private.num_tables; i++) {
+ entry = tables[i];
+ if (entry != NULL) {
+ if (entry->real != NULL)
+ ati_free_page_map(entry);
+ kfree(entry);
+ }
+ }
+ kfree(tables);
+}
+
+
+static int ati_create_gatt_pages(int nr_tables)
+{
+ ati_page_map **tables;
+ ati_page_map *entry;
+ int retval = 0;
+ int i;
+
+ tables = kmalloc((nr_tables + 1) * sizeof(ati_page_map *),
+ GFP_KERNEL);
+ if (tables == NULL)
+ return -ENOMEM;
+
+ memset(tables, 0, sizeof(ati_page_map *) * (nr_tables + 1));
+ for (i = 0; i < nr_tables; i++) {
+ entry = kmalloc(sizeof(ati_page_map), GFP_KERNEL);
+ if (entry == NULL) {
+ while (i>0) {
+ kfree (tables[i-1]);
+ i--;
+ }
+ kfree (tables);
+ tables = NULL;
+ retval = -ENOMEM;
+ break;
+ }
+ memset(entry, 0, sizeof(ati_page_map));
+ tables[i] = entry;
+ retval = ati_create_page_map(entry);
+ if (retval != 0) break;
+ }
+ ati_generic_private.num_tables = nr_tables;
+ ati_generic_private.gatt_pages = tables;
+
+ if (retval != 0) ati_free_gatt_pages();
+
+ return retval;
+}
+
+static int is_r200(void)
+{
+ if ((agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS100) ||
+ (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS200) ||
+ (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS200_B) ||
+ (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS250))
+ return 1;
+ return 0;
+}
+
+static int ati_fetch_size(void)
+{
+ int i;
+ u32 temp;
+ struct aper_size_info_lvl2 *values;
+
+ if (is_r200())
+ pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+ else
+ pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+
+ temp = (temp & 0x0000000e);
+ values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static void ati_tlbflush(struct agp_memory * mem)
+{
+ writel(1, ati_generic_private.registers+ATI_GART_CACHE_CNTRL);
+ readl(ati_generic_private.registers+ATI_GART_CACHE_CNTRL); /* PCI Posting. */
+}
+
+static void ati_cleanup(void)
+{
+ struct aper_size_info_lvl2 *previous_size;
+ u32 temp;
+
+ previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+
+ /* Write back the previous size and disable gart translation */
+ if (is_r200()) {
+ pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+ temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+ pci_write_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, temp);
+ } else {
+ pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+ temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+ pci_write_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, temp);
+ }
+ iounmap((volatile u8 __iomem *)ati_generic_private.registers);
+}
+
+
+static int ati_configure(void)
+{
+ u32 temp;
+
+ /* Get the memory mapped registers */
+ pci_read_config_dword(agp_bridge->dev, ATI_GART_MMBASE_ADDR, &temp);
+ temp = (temp & 0xfffff000);
+ ati_generic_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+
+ if (is_r200())
+ pci_write_config_dword(agp_bridge->dev, ATI_RS100_IG_AGPMODE, 0x20000);
+ else
+ pci_write_config_dword(agp_bridge->dev, ATI_RS300_IG_AGPMODE, 0x20000);
+
+ /* address to map too */
+ /*
+ pci_read_config_dword(agp_bridge.dev, AGP_APBASE, &temp);
+ agp_bridge.gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ printk(KERN_INFO PFX "IGP320 gart_bus_addr: %x\n", agp_bridge.gart_bus_addr);
+ */
+ writel(0x60000, ati_generic_private.registers+ATI_GART_FEATURE_ID);
+ readl(ati_generic_private.registers+ATI_GART_FEATURE_ID); /* PCI Posting.*/
+
+ /* SIGNALED_SYSTEM_ERROR @ NB_STATUS */
+ pci_read_config_dword(agp_bridge->dev, 4, &temp);
+ pci_write_config_dword(agp_bridge->dev, 4, temp | (1<<14));
+
+ /* Write out the address of the gatt table */
+ writel(agp_bridge->gatt_bus_addr, ati_generic_private.registers+ATI_GART_BASE);
+ readl(ati_generic_private.registers+ATI_GART_BASE); /* PCI Posting. */
+
+ return 0;
+}
+
+
+/*
+ *Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+ GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#undef GET_GATT
+#define GET_GATT(addr) (ati_generic_private.gatt_pages[\
+ GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int ati_insert_memory(struct agp_memory * mem,
+ off_t pg_start, int type)
+{
+ int i, j, num_entries;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+ while (j < (pg_start + mem->page_count)) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ if (!PGE_EMPTY(agp_bridge,readl(cur_gatt+GET_GATT_OFF(addr))))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ /*CACHE_FLUSH(); */
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ writel(agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+ readl(cur_gatt+GET_GATT_OFF(addr)); /* PCI Posting. */
+ }
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int ati_remove_memory(struct agp_memory * mem, off_t pg_start,
+ int type)
+{
+ int i;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = GET_GATT(addr);
+ writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+ readl(cur_gatt+GET_GATT_OFF(addr)); /* PCI Posting. */
+ }
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int ati_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct aper_size_info_lvl2 *value;
+ ati_page_map page_dir;
+ unsigned long addr;
+ int retval;
+ u32 temp;
+ int i;
+ struct aper_size_info_lvl2 *current_size;
+
+ value = A_SIZE_LVL2(agp_bridge->current_size);
+ retval = ati_create_page_map(&page_dir);
+ if (retval != 0)
+ return retval;
+
+ retval = ati_create_gatt_pages(value->num_entries / 1024);
+ if (retval != 0) {
+ ati_free_page_map(&page_dir);
+ return retval;
+ }
+
+ agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+ agp_bridge->gatt_table = (u32 __iomem *) page_dir.remapped;
+ agp_bridge->gatt_bus_addr = virt_to_bus(page_dir.real);
+
+ /* Write out the size register */
+ current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+ if (is_r200()) {
+ pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+ temp = (((temp & ~(0x0000000e)) | current_size->size_value)
+ | 0x00000001);
+ pci_write_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, temp);
+ pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+ } else {
+ pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+ temp = (((temp & ~(0x0000000e)) | current_size->size_value)
+ | 0x00000001);
+ pci_write_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, temp);
+ pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+ }
+
+ /*
+ * Get the address for the gart region.
+ * This is a bus address even on the alpha, b/c its
+ * used to program the agp master not the cpu
+ */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ agp_bridge->gart_bus_addr = addr;
+
+ /* Calculate the agp offset */
+ for(i = 0; i < value->num_entries / 1024; i++, addr += 0x00400000) {
+ writel(virt_to_bus(ati_generic_private.gatt_pages[i]->real) | 1,
+ page_dir.remapped+GET_PAGE_DIR_OFF(addr));
+ readl(page_dir.remapped+GET_PAGE_DIR_OFF(addr)); /* PCI Posting. */
+ }
+
+ return 0;
+}
+
+static int ati_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ ati_page_map page_dir;
+
+ page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+ page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+ ati_free_gatt_pages();
+ ati_free_page_map(&page_dir);
+ return 0;
+}
+
+struct agp_bridge_driver ati_generic_bridge = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = ati_generic_sizes,
+ .size_type = LVL2_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = ati_configure,
+ .fetch_size = ati_fetch_size,
+ .cleanup = ati_cleanup,
+ .tlb_flush = ati_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = ati_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = ati_create_gatt_table,
+ .free_gatt_table = ati_free_gatt_table,
+ .insert_memory = ati_insert_memory,
+ .remove_memory = ati_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+
+static struct agp_device_ids ati_agp_device_ids[] __devinitdata =
+{
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS100,
+ .chipset_name = "IGP320/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS200,
+ .chipset_name = "IGP330/340/345/350/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS200_B,
+ .chipset_name = "IGP345M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS250,
+ .chipset_name = "IGP7000/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS300_100,
+ .chipset_name = "IGP9100/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS300_133,
+ .chipset_name = "IGP9100/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS300_166,
+ .chipset_name = "IGP9100/M",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_ATI_RS300_200,
+ .chipset_name = "IGP9100/M",
+ },
+ { }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_ati_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_device_ids *devs = ati_agp_device_ids;
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+ int j;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ /* probe for known chipsets */
+ for (j = 0; devs[j].chipset_name; j++) {
+ if (pdev->device == devs[j].device_id)
+ goto found;
+ }
+
+ printk(KERN_ERR PFX
+ "Unsupported Ati chipset (device id: %04x)\n", pdev->device);
+ return -ENODEV;
+
+found:
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ bridge->driver = &ati_generic_bridge;
+
+
+ printk(KERN_INFO PFX "Detected Ati %s chipset\n",
+ devs[j].chipset_name);
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_ati_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_ati_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_ATI,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_ati_pci_table);
+
+static struct pci_driver agp_ati_pci_driver = {
+ .name = "agpgart-ati",
+ .id_table = agp_ati_pci_table,
+ .probe = agp_ati_probe,
+ .remove = agp_ati_remove,
+};
+
+static int __init agp_ati_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_ati_pci_driver);
+}
+
+static void __exit agp_ati_cleanup(void)
+{
+ pci_unregister_driver(&agp_ati_pci_driver);
+}
+
+module_init(agp_ati_init);
+module_exit(agp_ati_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/backend.c b/drivers/char/agp/backend.c
new file mode 100644
index 000000000000..c3442f3c6480
--- /dev/null
+++ b/drivers/char/agp/backend.c
@@ -0,0 +1,348 @@
+/*
+ * AGPGART driver backend routines.
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2003 Dave Jones.
+ * Copyright (C) 1999 Jeff Hartmann.
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * JEFF HARTMANN, DAVE JONES, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * TODO:
+ * - Allocate more than order 0 pages to avoid too much linear map splitting.
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/miscdevice.h>
+#include <linux/pm.h>
+#include <linux/agp_backend.h>
+#include <linux/agpgart.h>
+#include <linux/vmalloc.h>
+#include <asm/io.h>
+#include "agp.h"
+
+/* Due to XFree86 brain-damage, we can't go to 1.0 until they
+ * fix some real stupidity. It's only by chance we can bump
+ * past 0.99 at all due to some boolean logic error. */
+#define AGPGART_VERSION_MAJOR 0
+#define AGPGART_VERSION_MINOR 101
+static struct agp_version agp_current_version =
+{
+ .major = AGPGART_VERSION_MAJOR,
+ .minor = AGPGART_VERSION_MINOR,
+};
+
+struct agp_bridge_data *(*agp_find_bridge)(struct pci_dev *) =
+ &agp_generic_find_bridge;
+
+struct agp_bridge_data *agp_bridge;
+LIST_HEAD(agp_bridges);
+EXPORT_SYMBOL(agp_bridge);
+EXPORT_SYMBOL(agp_bridges);
+EXPORT_SYMBOL(agp_find_bridge);
+
+/**
+ * agp_backend_acquire - attempt to acquire an agp backend.
+ *
+ */
+struct agp_bridge_data *agp_backend_acquire(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge;
+
+ bridge = agp_find_bridge(pdev);
+
+ if (!bridge)
+ return NULL;
+
+ if (atomic_read(&bridge->agp_in_use))
+ return NULL;
+ atomic_inc(&bridge->agp_in_use);
+ return bridge;
+}
+EXPORT_SYMBOL(agp_backend_acquire);
+
+
+/**
+ * agp_backend_release - release the lock on the agp backend.
+ *
+ * The caller must insure that the graphics aperture translation table
+ * is read for use by another entity.
+ *
+ * (Ensure that all memory it bound is unbound.)
+ */
+void agp_backend_release(struct agp_bridge_data *bridge)
+{
+
+ if (bridge)
+ atomic_dec(&bridge->agp_in_use);
+}
+EXPORT_SYMBOL(agp_backend_release);
+
+
+struct { int mem, agp; } maxes_table[] = {
+ {0, 0},
+ {32, 4},
+ {64, 28},
+ {128, 96},
+ {256, 204},
+ {512, 440},
+ {1024, 942},
+ {2048, 1920},
+ {4096, 3932}
+};
+
+static int agp_find_max(void)
+{
+ long memory, index, result;
+
+#if PAGE_SHIFT < 20
+ memory = num_physpages >> (20 - PAGE_SHIFT);
+#else
+ memory = num_physpages << (PAGE_SHIFT - 20);
+#endif
+ index = 1;
+
+ while ((memory > maxes_table[index].mem) && (index < 8))
+ index++;
+
+ result = maxes_table[index - 1].agp +
+ ( (memory - maxes_table[index - 1].mem) *
+ (maxes_table[index].agp - maxes_table[index - 1].agp)) /
+ (maxes_table[index].mem - maxes_table[index - 1].mem);
+
+ result = result << (20 - PAGE_SHIFT);
+ return result;
+}
+
+
+static int agp_backend_initialize(struct agp_bridge_data *bridge)
+{
+ int size_value, rc, got_gatt=0, got_keylist=0;
+
+ bridge->max_memory_agp = agp_find_max();
+ bridge->version = &agp_current_version;
+
+ if (bridge->driver->needs_scratch_page) {
+ void *addr = bridge->driver->agp_alloc_page(bridge);
+
+ if (!addr) {
+ printk(KERN_ERR PFX "unable to get memory for scratch page.\n");
+ return -ENOMEM;
+ }
+
+ bridge->scratch_page_real = virt_to_phys(addr);
+ bridge->scratch_page =
+ bridge->driver->mask_memory(bridge, bridge->scratch_page_real, 0);
+ }
+
+ size_value = bridge->driver->fetch_size();
+ if (size_value == 0) {
+ printk(KERN_ERR PFX "unable to determine aperture size.\n");
+ rc = -EINVAL;
+ goto err_out;
+ }
+ if (bridge->driver->create_gatt_table(bridge)) {
+ printk(KERN_ERR PFX
+ "unable to get memory for graphics translation table.\n");
+ rc = -ENOMEM;
+ goto err_out;
+ }
+ got_gatt = 1;
+
+ bridge->key_list = vmalloc(PAGE_SIZE * 4);
+ if (bridge->key_list == NULL) {
+ printk(KERN_ERR PFX "error allocating memory for key lists.\n");
+ rc = -ENOMEM;
+ goto err_out;
+ }
+ got_keylist = 1;
+
+ /* FIXME vmalloc'd memory not guaranteed contiguous */
+ memset(bridge->key_list, 0, PAGE_SIZE * 4);
+
+ if (bridge->driver->configure()) {
+ printk(KERN_ERR PFX "error configuring host chipset.\n");
+ rc = -EINVAL;
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ if (bridge->driver->needs_scratch_page)
+ bridge->driver->agp_destroy_page(
+ phys_to_virt(bridge->scratch_page_real));
+ if (got_gatt)
+ bridge->driver->free_gatt_table(bridge);
+ if (got_keylist) {
+ vfree(bridge->key_list);
+ bridge->key_list = NULL;
+ }
+ return rc;
+}
+
+/* cannot be __exit b/c as it could be called from __init code */
+static void agp_backend_cleanup(struct agp_bridge_data *bridge)
+{
+ if (bridge->driver->cleanup)
+ bridge->driver->cleanup();
+ if (bridge->driver->free_gatt_table)
+ bridge->driver->free_gatt_table(bridge);
+ if (bridge->key_list) {
+ vfree(bridge->key_list);
+ bridge->key_list = NULL;
+ }
+
+ if (bridge->driver->agp_destroy_page &&
+ bridge->driver->needs_scratch_page)
+ bridge->driver->agp_destroy_page(
+ phys_to_virt(bridge->scratch_page_real));
+}
+
+/* When we remove the global variable agp_bridge from all drivers
+ * then agp_alloc_bridge and agp_generic_find_bridge need to be updated
+ */
+
+struct agp_bridge_data *agp_alloc_bridge(void)
+{
+ struct agp_bridge_data *bridge = kmalloc(sizeof(*bridge), GFP_KERNEL);
+
+ if (!bridge)
+ return NULL;
+
+ memset(bridge, 0, sizeof(*bridge));
+ atomic_set(&bridge->agp_in_use, 0);
+ atomic_set(&bridge->current_memory_agp, 0);
+
+ if (list_empty(&agp_bridges))
+ agp_bridge = bridge;
+
+ return bridge;
+}
+EXPORT_SYMBOL(agp_alloc_bridge);
+
+
+void agp_put_bridge(struct agp_bridge_data *bridge)
+{
+ kfree(bridge);
+
+ if (list_empty(&agp_bridges))
+ agp_bridge = NULL;
+}
+EXPORT_SYMBOL(agp_put_bridge);
+
+
+int agp_add_bridge(struct agp_bridge_data *bridge)
+{
+ int error;
+
+ if (agp_off)
+ return -ENODEV;
+
+ if (!bridge->dev) {
+ printk (KERN_DEBUG PFX "Erk, registering with no pci_dev!\n");
+ return -EINVAL;
+ }
+
+ /* Grab reference on the chipset driver. */
+ if (!try_module_get(bridge->driver->owner)) {
+ printk (KERN_INFO PFX "Couldn't lock chipset driver.\n");
+ return -EINVAL;
+ }
+
+ error = agp_backend_initialize(bridge);
+ if (error) {
+ printk (KERN_INFO PFX "agp_backend_initialize() failed.\n");
+ goto err_out;
+ }
+
+ if (list_empty(&agp_bridges)) {
+ error = agp_frontend_initialize();
+ if (error) {
+ printk (KERN_INFO PFX "agp_frontend_initialize() failed.\n");
+ goto frontend_err;
+ }
+
+ printk(KERN_INFO PFX "AGP aperture is %dM @ 0x%lx\n",
+ bridge->driver->fetch_size(), bridge->gart_bus_addr);
+
+ }
+
+ list_add(&bridge->list, &agp_bridges);
+ return 0;
+
+frontend_err:
+ agp_backend_cleanup(bridge);
+err_out:
+ module_put(bridge->driver->owner);
+ agp_put_bridge(bridge);
+ return error;
+}
+EXPORT_SYMBOL_GPL(agp_add_bridge);
+
+
+void agp_remove_bridge(struct agp_bridge_data *bridge)
+{
+ agp_backend_cleanup(bridge);
+ list_del(&bridge->list);
+ if (list_empty(&agp_bridges))
+ agp_frontend_cleanup();
+ module_put(bridge->driver->owner);
+}
+EXPORT_SYMBOL_GPL(agp_remove_bridge);
+
+int agp_off;
+int agp_try_unsupported_boot;
+EXPORT_SYMBOL(agp_off);
+EXPORT_SYMBOL(agp_try_unsupported_boot);
+
+static int __init agp_init(void)
+{
+ if (!agp_off)
+ printk(KERN_INFO "Linux agpgart interface v%d.%d (c) Dave Jones\n",
+ AGPGART_VERSION_MAJOR, AGPGART_VERSION_MINOR);
+ return 0;
+}
+
+void __exit agp_exit(void)
+{
+}
+
+#ifndef MODULE
+static __init int agp_setup(char *s)
+{
+ if (!strcmp(s,"off"))
+ agp_off = 1;
+ if (!strcmp(s,"try_unsupported"))
+ agp_try_unsupported_boot = 1;
+ return 1;
+}
+__setup("agp=", agp_setup);
+#endif
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_DESCRIPTION("AGP GART driver");
+MODULE_LICENSE("GPL and additional rights");
+MODULE_ALIAS_MISCDEV(AGPGART_MINOR);
+
+module_init(agp_init);
+module_exit(agp_exit);
+
diff --git a/drivers/char/agp/efficeon-agp.c b/drivers/char/agp/efficeon-agp.c
new file mode 100644
index 000000000000..52c0a097118c
--- /dev/null
+++ b/drivers/char/agp/efficeon-agp.c
@@ -0,0 +1,463 @@
+/*
+ * Transmeta's Efficeon AGPGART driver.
+ *
+ * Based upon a diff by Linus around November '02.
+ *
+ * Ported to the 2.6 kernel by Carlos Puchol <cpglinux@puchol.com>
+ * and H. Peter Anvin <hpa@transmeta.com>.
+ */
+
+/*
+ * NOTE-cpg-040217:
+ *
+ * - when compiled as a module, after loading the module,
+ * it will refuse to unload, indicating it is in use,
+ * when it is not.
+ * - no s3 (suspend to ram) testing.
+ * - tested on the efficeon integrated nothbridge for tens
+ * of iterations of starting x and glxgears.
+ * - tested with radeon 9000 and radeon mobility m9 cards
+ * - tested with c3/c4 enabled (with the mobility m9 card)
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+/*
+ * The real differences to the generic AGP code is
+ * in the GART mappings - a two-level setup with the
+ * first level being an on-chip 64-entry table.
+ *
+ * The page array is filled through the ATTPAGE register
+ * (Aperture Translation Table Page Register) at 0xB8. Bits:
+ * 31:20: physical page address
+ * 11:9: Page Attribute Table Index (PATI)
+ * must match the PAT index for the
+ * mapped pages (the 2nd level page table pages
+ * themselves should be just regular WB-cacheable,
+ * so this is normally zero.)
+ * 8: Present
+ * 7:6: reserved, write as zero
+ * 5:0: GATT directory index: which 1st-level entry
+ *
+ * The Efficeon AGP spec requires pages to be WB-cacheable
+ * but to be explicitly CLFLUSH'd after any changes.
+ */
+#define EFFICEON_ATTPAGE 0xb8
+#define EFFICEON_L1_SIZE 64 /* Number of PDE pages */
+
+#define EFFICEON_PATI (0 << 9)
+#define EFFICEON_PRESENT (1 << 8)
+
+static struct _efficeon_private {
+ unsigned long l1_table[EFFICEON_L1_SIZE];
+} efficeon_private;
+
+static struct gatt_mask efficeon_generic_masks[] =
+{
+ {.mask = 0x00000001, .type = 0}
+};
+
+static struct aper_size_info_lvl2 efficeon_generic_sizes[4] =
+{
+ {256, 65536, 0},
+ {128, 32768, 32},
+ {64, 16384, 48},
+ {32, 8192, 56}
+};
+
+/*
+ * Control interfaces are largely identical to
+ * the legacy Intel 440BX..
+ */
+
+static int efficeon_fetch_size(void)
+{
+ int i;
+ u16 temp;
+ struct aper_size_info_lvl2 *values;
+
+ pci_read_config_word(agp_bridge->dev, INTEL_APSIZE, &temp);
+ values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static void efficeon_tlbflush(struct agp_memory * mem)
+{
+ printk(KERN_DEBUG PFX "efficeon_tlbflush()\n");
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2200);
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+}
+
+static void efficeon_cleanup(void)
+{
+ u16 temp;
+ struct aper_size_info_lvl2 *previous_size;
+
+ printk(KERN_DEBUG PFX "efficeon_cleanup()\n");
+ previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+ pci_write_config_word(agp_bridge->dev, INTEL_APSIZE,
+ previous_size->size_value);
+}
+
+static int efficeon_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_lvl2 *current_size;
+
+ printk(KERN_DEBUG PFX "efficeon_configure()\n");
+
+ current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_word(agp_bridge->dev, INTEL_APSIZE,
+ current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+
+ /* paccfg/nbxcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG,
+ (temp2 & ~(1 << 10)) | (1 << 9) | (1 << 11));
+ /* clear any possible error conditions */
+ pci_write_config_byte(agp_bridge->dev, INTEL_ERRSTS + 1, 7);
+ return 0;
+}
+
+static int efficeon_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ int index, freed = 0;
+
+ for (index = 0; index < EFFICEON_L1_SIZE; index++) {
+ unsigned long page = efficeon_private.l1_table[index];
+ if (page) {
+ efficeon_private.l1_table[index] = 0;
+ ClearPageReserved(virt_to_page((char *)page));
+ free_page(page);
+ freed++;
+ }
+ printk(KERN_DEBUG PFX "efficeon_free_gatt_table(%p, %02x, %08x)\n",
+ agp_bridge->dev, EFFICEON_ATTPAGE, index);
+ pci_write_config_dword(agp_bridge->dev,
+ EFFICEON_ATTPAGE, index);
+ }
+ printk(KERN_DEBUG PFX "efficeon_free_gatt_table() freed %d pages\n", freed);
+ return 0;
+}
+
+
+/*
+ * Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+ GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#undef GET_GATT
+#define GET_GATT(addr) (efficeon_private.gatt_pages[\
+ GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int efficeon_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ int index;
+ const int pati = EFFICEON_PATI;
+ const int present = EFFICEON_PRESENT;
+ const int clflush_chunk = ((cpuid_ebx(1) >> 8) & 0xff) << 3;
+ int num_entries, l1_pages;
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+ printk(KERN_DEBUG PFX "efficeon_create_gatt_table(%d)\n", num_entries);
+
+ /* There are 2^10 PTE pages per PDE page */
+ BUG_ON(num_entries & 0x3ff);
+ l1_pages = num_entries >> 10;
+
+ for (index = 0 ; index < l1_pages ; index++) {
+ int offset;
+ unsigned long page;
+ unsigned long value;
+
+ page = efficeon_private.l1_table[index];
+ BUG_ON(page);
+
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+ efficeon_free_gatt_table(agp_bridge);
+ return -ENOMEM;
+ }
+ SetPageReserved(virt_to_page((char *)page));
+
+ for (offset = 0; offset < PAGE_SIZE; offset += clflush_chunk)
+ asm volatile("clflush %0" : : "m" (*(char *)(page+offset)));
+
+ efficeon_private.l1_table[index] = page;
+
+ value = __pa(page) | pati | present | index;
+
+ pci_write_config_dword(agp_bridge->dev,
+ EFFICEON_ATTPAGE, value);
+ }
+
+ return 0;
+}
+
+static int efficeon_insert_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+ int i, count = mem->page_count, num_entries;
+ unsigned int *page, *last_page;
+ const int clflush_chunk = ((cpuid_ebx(1) >> 8) & 0xff) << 3;
+ const unsigned long clflush_mask = ~(clflush_chunk-1);
+
+ printk(KERN_DEBUG PFX "efficeon_insert_memory(%lx, %d)\n", pg_start, count);
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ last_page = NULL;
+ for (i = 0; i < count; i++) {
+ int index = pg_start + i;
+ unsigned long insert = mem->memory[i];
+
+ page = (unsigned int *) efficeon_private.l1_table[index >> 10];
+
+ if (!page)
+ continue;
+
+ page += (index & 0x3ff);
+ *page = insert;
+
+ /* clflush is slow, so don't clflush until we have to */
+ if ( last_page &&
+ ((unsigned long)page^(unsigned long)last_page) & clflush_mask )
+ asm volatile("clflush %0" : : "m" (*last_page));
+
+ last_page = page;
+ }
+
+ if ( last_page )
+ asm volatile("clflush %0" : : "m" (*last_page));
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int efficeon_remove_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+ int i, count = mem->page_count, num_entries;
+
+ printk(KERN_DEBUG PFX "efficeon_remove_memory(%lx, %d)\n", pg_start, count);
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+ if (type != 0 || mem->type != 0)
+ return -EINVAL;
+
+ for (i = 0; i < count; i++) {
+ int index = pg_start + i;
+ unsigned int *page = (unsigned int *) efficeon_private.l1_table[index >> 10];
+
+ if (!page)
+ continue;
+ page += (index & 0x3ff);
+ *page = 0;
+ }
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+
+struct agp_bridge_driver efficeon_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = efficeon_generic_sizes,
+ .size_type = LVL2_APER_SIZE,
+ .num_aperture_sizes = 4,
+ .configure = efficeon_configure,
+ .fetch_size = efficeon_fetch_size,
+ .cleanup = efficeon_cleanup,
+ .tlb_flush = efficeon_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = efficeon_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+
+ // Efficeon-specific GATT table setup / populate / teardown
+ .create_gatt_table = efficeon_create_gatt_table,
+ .free_gatt_table = efficeon_free_gatt_table,
+ .insert_memory = efficeon_insert_memory,
+ .remove_memory = efficeon_remove_memory,
+ .cant_use_aperture = 0, // 1 might be faster?
+
+ // Generic
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+
+static int agp_efficeon_resume(struct pci_dev *pdev)
+{
+ printk(KERN_DEBUG PFX "agp_efficeon_resume()\n");
+ return efficeon_configure();
+}
+
+static int __devinit agp_efficeon_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+ struct resource *r;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ /* Probe for Efficeon controller */
+ if (pdev->device != PCI_DEVICE_ID_EFFICEON) {
+ printk(KERN_ERR PFX "Unsupported Efficeon chipset (device id: %04x)\n",
+ pdev->device);
+ return -ENODEV;
+ }
+
+ printk(KERN_INFO PFX "Detected Transmeta Efficeon TM8000 series chipset\n");
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &efficeon_driver;
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ /*
+ * The following fixes the case where the BIOS has "forgotten" to
+ * provide an address range for the GART.
+ * 20030610 - hamish@zot.org
+ */
+ r = &pdev->resource[0];
+ if (!r->start && r->end) {
+ if(pci_assign_resource(pdev, 0)) {
+ printk(KERN_ERR PFX "could not assign resource 0\n");
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * If the device has not been properly setup, the following will catch
+ * the problem and should stop the system from crashing.
+ * 20030610 - hamish@zot.org
+ */
+ if (pci_enable_device(pdev)) {
+ printk(KERN_ERR PFX "Unable to Enable PCI device\n");
+ return -ENODEV;
+ }
+
+ /* Fill in the mode register */
+ if (cap_ptr) {
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+ }
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_efficeon_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static int agp_efficeon_suspend(struct pci_dev *dev, u32 state)
+{
+ return 0;
+}
+
+
+static struct pci_device_id agp_efficeon_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_TRANSMETA,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_efficeon_pci_table);
+
+static struct pci_driver agp_efficeon_pci_driver = {
+ .name = "agpgart-efficeon",
+ .id_table = agp_efficeon_pci_table,
+ .probe = agp_efficeon_probe,
+ .remove = agp_efficeon_remove,
+ .suspend = agp_efficeon_suspend,
+ .resume = agp_efficeon_resume,
+};
+
+static int __init agp_efficeon_init(void)
+{
+ static int agp_initialised=0;
+
+ if (agp_off)
+ return -EINVAL;
+
+ if (agp_initialised == 1)
+ return 0;
+ agp_initialised=1;
+
+ return pci_register_driver(&agp_efficeon_pci_driver);
+}
+
+static void __exit agp_efficeon_cleanup(void)
+{
+ pci_unregister_driver(&agp_efficeon_pci_driver);
+}
+
+module_init(agp_efficeon_init);
+module_exit(agp_efficeon_cleanup);
+
+MODULE_AUTHOR("Carlos Puchol <cpglinux@puchol.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/frontend.c b/drivers/char/agp/frontend.c
new file mode 100644
index 000000000000..f633623ac802
--- /dev/null
+++ b/drivers/char/agp/frontend.c
@@ -0,0 +1,1103 @@
+/*
+ * AGPGART driver frontend
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2003 Dave Jones
+ * Copyright (C) 1999 Jeff Hartmann
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mman.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/agp_backend.h>
+#include <linux/agpgart.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include "agp.h"
+
+static struct agp_front_data agp_fe;
+
+static struct agp_memory *agp_find_mem_by_key(int key)
+{
+ struct agp_memory *curr;
+
+ if (agp_fe.current_controller == NULL)
+ return NULL;
+
+ curr = agp_fe.current_controller->pool;
+
+ while (curr != NULL) {
+ if (curr->key == key)
+ break;
+ curr = curr->next;
+ }
+
+ DBG("key=%d -> mem=%p", key, curr);
+ return curr;
+}
+
+static void agp_remove_from_pool(struct agp_memory *temp)
+{
+ struct agp_memory *prev;
+ struct agp_memory *next;
+
+ /* Check to see if this is even in the memory pool */
+
+ DBG("mem=%p", temp);
+ if (agp_find_mem_by_key(temp->key) != NULL) {
+ next = temp->next;
+ prev = temp->prev;
+
+ if (prev != NULL) {
+ prev->next = next;
+ if (next != NULL)
+ next->prev = prev;
+
+ } else {
+ /* This is the first item on the list */
+ if (next != NULL)
+ next->prev = NULL;
+
+ agp_fe.current_controller->pool = next;
+ }
+ }
+}
+
+/*
+ * Routines for managing each client's segment list -
+ * These routines handle adding and removing segments
+ * to each auth'ed client.
+ */
+
+static struct
+agp_segment_priv *agp_find_seg_in_client(const struct agp_client *client,
+ unsigned long offset,
+ int size, pgprot_t page_prot)
+{
+ struct agp_segment_priv *seg;
+ int num_segments, i;
+ off_t pg_start;
+ size_t pg_count;
+
+ pg_start = offset / 4096;
+ pg_count = size / 4096;
+ seg = *(client->segments);
+ num_segments = client->num_segments;
+
+ for (i = 0; i < client->num_segments; i++) {
+ if ((seg[i].pg_start == pg_start) &&
+ (seg[i].pg_count == pg_count) &&
+ (pgprot_val(seg[i].prot) == pgprot_val(page_prot))) {
+ return seg + i;
+ }
+ }
+
+ return NULL;
+}
+
+static void agp_remove_seg_from_client(struct agp_client *client)
+{
+ DBG("client=%p", client);
+
+ if (client->segments != NULL) {
+ if (*(client->segments) != NULL) {
+ DBG("Freeing %p from client %p", *(client->segments), client);
+ kfree(*(client->segments));
+ }
+ DBG("Freeing %p from client %p", client->segments, client);
+ kfree(client->segments);
+ client->segments = NULL;
+ }
+}
+
+static void agp_add_seg_to_client(struct agp_client *client,
+ struct agp_segment_priv ** seg, int num_segments)
+{
+ struct agp_segment_priv **prev_seg;
+
+ prev_seg = client->segments;
+
+ if (prev_seg != NULL)
+ agp_remove_seg_from_client(client);
+
+ DBG("Adding seg %p (%d segments) to client %p", seg, num_segments, client);
+ client->num_segments = num_segments;
+ client->segments = seg;
+}
+
+/* Originally taken from linux/mm/mmap.c from the array
+ * protection_map.
+ * The original really should be exported to modules, or
+ * some routine which does the conversion for you
+ */
+
+static const pgprot_t my_protect_map[16] =
+{
+ __P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111,
+ __S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111
+};
+
+static pgprot_t agp_convert_mmap_flags(int prot)
+{
+#define _trans(x,bit1,bit2) \
+((bit1==bit2)?(x&bit1):(x&bit1)?bit2:0)
+
+ unsigned long prot_bits;
+ pgprot_t temp;
+
+ prot_bits = _trans(prot, PROT_READ, VM_READ) |
+ _trans(prot, PROT_WRITE, VM_WRITE) |
+ _trans(prot, PROT_EXEC, VM_EXEC);
+
+ prot_bits |= VM_SHARED;
+
+ temp = my_protect_map[prot_bits & 0x0000000f];
+
+ return temp;
+}
+
+static int agp_create_segment(struct agp_client *client, struct agp_region *region)
+{
+ struct agp_segment_priv **ret_seg;
+ struct agp_segment_priv *seg;
+ struct agp_segment *user_seg;
+ size_t i;
+
+ seg = kmalloc((sizeof(struct agp_segment_priv) * region->seg_count), GFP_KERNEL);
+ if (seg == NULL) {
+ kfree(region->seg_list);
+ region->seg_list = NULL;
+ return -ENOMEM;
+ }
+ memset(seg, 0, (sizeof(struct agp_segment_priv) * region->seg_count));
+ user_seg = region->seg_list;
+
+ for (i = 0; i < region->seg_count; i++) {
+ seg[i].pg_start = user_seg[i].pg_start;
+ seg[i].pg_count = user_seg[i].pg_count;
+ seg[i].prot = agp_convert_mmap_flags(user_seg[i].prot);
+ }
+ kfree(region->seg_list);
+ region->seg_list = NULL;
+
+ ret_seg = kmalloc(sizeof(void *), GFP_KERNEL);
+ if (ret_seg == NULL) {
+ kfree(seg);
+ return -ENOMEM;
+ }
+ *ret_seg = seg;
+ agp_add_seg_to_client(client, ret_seg, region->seg_count);
+ return 0;
+}
+
+/* End - Routines for managing each client's segment list */
+
+/* This function must only be called when current_controller != NULL */
+static void agp_insert_into_pool(struct agp_memory * temp)
+{
+ struct agp_memory *prev;
+
+ prev = agp_fe.current_controller->pool;
+
+ if (prev != NULL) {
+ prev->prev = temp;
+ temp->next = prev;
+ }
+ agp_fe.current_controller->pool = temp;
+}
+
+
+/* File private list routines */
+
+struct agp_file_private *agp_find_private(pid_t pid)
+{
+ struct agp_file_private *curr;
+
+ curr = agp_fe.file_priv_list;
+
+ while (curr != NULL) {
+ if (curr->my_pid == pid)
+ return curr;
+ curr = curr->next;
+ }
+
+ return NULL;
+}
+
+void agp_insert_file_private(struct agp_file_private * priv)
+{
+ struct agp_file_private *prev;
+
+ prev = agp_fe.file_priv_list;
+
+ if (prev != NULL)
+ prev->prev = priv;
+ priv->next = prev;
+ agp_fe.file_priv_list = priv;
+}
+
+void agp_remove_file_private(struct agp_file_private * priv)
+{
+ struct agp_file_private *next;
+ struct agp_file_private *prev;
+
+ next = priv->next;
+ prev = priv->prev;
+
+ if (prev != NULL) {
+ prev->next = next;
+
+ if (next != NULL)
+ next->prev = prev;
+
+ } else {
+ if (next != NULL)
+ next->prev = NULL;
+
+ agp_fe.file_priv_list = next;
+ }
+}
+
+/* End - File flag list routines */
+
+/*
+ * Wrappers for agp_free_memory & agp_allocate_memory
+ * These make sure that internal lists are kept updated.
+ */
+static void agp_free_memory_wrap(struct agp_memory *memory)
+{
+ agp_remove_from_pool(memory);
+ agp_free_memory(memory);
+}
+
+static struct agp_memory *agp_allocate_memory_wrap(size_t pg_count, u32 type)
+{
+ struct agp_memory *memory;
+
+ memory = agp_allocate_memory(agp_bridge, pg_count, type);
+ if (memory == NULL)
+ return NULL;
+
+ agp_insert_into_pool(memory);
+ return memory;
+}
+
+/* Routines for managing the list of controllers -
+ * These routines manage the current controller, and the list of
+ * controllers
+ */
+
+static struct agp_controller *agp_find_controller_by_pid(pid_t id)
+{
+ struct agp_controller *controller;
+
+ controller = agp_fe.controllers;
+
+ while (controller != NULL) {
+ if (controller->pid == id)
+ return controller;
+ controller = controller->next;
+ }
+
+ return NULL;
+}
+
+static struct agp_controller *agp_create_controller(pid_t id)
+{
+ struct agp_controller *controller;
+
+ controller = kmalloc(sizeof(struct agp_controller), GFP_KERNEL);
+
+ if (controller == NULL)
+ return NULL;
+
+ memset(controller, 0, sizeof(struct agp_controller));
+ controller->pid = id;
+
+ return controller;
+}
+
+static int agp_insert_controller(struct agp_controller *controller)
+{
+ struct agp_controller *prev_controller;
+
+ prev_controller = agp_fe.controllers;
+ controller->next = prev_controller;
+
+ if (prev_controller != NULL)
+ prev_controller->prev = controller;
+
+ agp_fe.controllers = controller;
+
+ return 0;
+}
+
+static void agp_remove_all_clients(struct agp_controller *controller)
+{
+ struct agp_client *client;
+ struct agp_client *temp;
+
+ client = controller->clients;
+
+ while (client) {
+ struct agp_file_private *priv;
+
+ temp = client;
+ agp_remove_seg_from_client(temp);
+ priv = agp_find_private(temp->pid);
+
+ if (priv != NULL) {
+ clear_bit(AGP_FF_IS_VALID, &priv->access_flags);
+ clear_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+ }
+ client = client->next;
+ kfree(temp);
+ }
+}
+
+static void agp_remove_all_memory(struct agp_controller *controller)
+{
+ struct agp_memory *memory;
+ struct agp_memory *temp;
+
+ memory = controller->pool;
+
+ while (memory) {
+ temp = memory;
+ memory = memory->next;
+ agp_free_memory_wrap(temp);
+ }
+}
+
+static int agp_remove_controller(struct agp_controller *controller)
+{
+ struct agp_controller *prev_controller;
+ struct agp_controller *next_controller;
+
+ prev_controller = controller->prev;
+ next_controller = controller->next;
+
+ if (prev_controller != NULL) {
+ prev_controller->next = next_controller;
+ if (next_controller != NULL)
+ next_controller->prev = prev_controller;
+
+ } else {
+ if (next_controller != NULL)
+ next_controller->prev = NULL;
+
+ agp_fe.controllers = next_controller;
+ }
+
+ agp_remove_all_memory(controller);
+ agp_remove_all_clients(controller);
+
+ if (agp_fe.current_controller == controller) {
+ agp_fe.current_controller = NULL;
+ agp_fe.backend_acquired = FALSE;
+ agp_backend_release(agp_bridge);
+ }
+ kfree(controller);
+ return 0;
+}
+
+static void agp_controller_make_current(struct agp_controller *controller)
+{
+ struct agp_client *clients;
+
+ clients = controller->clients;
+
+ while (clients != NULL) {
+ struct agp_file_private *priv;
+
+ priv = agp_find_private(clients->pid);
+
+ if (priv != NULL) {
+ set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+ set_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+ }
+ clients = clients->next;
+ }
+
+ agp_fe.current_controller = controller;
+}
+
+static void agp_controller_release_current(struct agp_controller *controller,
+ struct agp_file_private *controller_priv)
+{
+ struct agp_client *clients;
+
+ clear_bit(AGP_FF_IS_VALID, &controller_priv->access_flags);
+ clients = controller->clients;
+
+ while (clients != NULL) {
+ struct agp_file_private *priv;
+
+ priv = agp_find_private(clients->pid);
+
+ if (priv != NULL)
+ clear_bit(AGP_FF_IS_VALID, &priv->access_flags);
+
+ clients = clients->next;
+ }
+
+ agp_fe.current_controller = NULL;
+ agp_fe.used_by_controller = FALSE;
+ agp_backend_release(agp_bridge);
+}
+
+/*
+ * Routines for managing client lists -
+ * These routines are for managing the list of auth'ed clients.
+ */
+
+static struct agp_client
+*agp_find_client_in_controller(struct agp_controller *controller, pid_t id)
+{
+ struct agp_client *client;
+
+ if (controller == NULL)
+ return NULL;
+
+ client = controller->clients;
+
+ while (client != NULL) {
+ if (client->pid == id)
+ return client;
+ client = client->next;
+ }
+
+ return NULL;
+}
+
+static struct agp_controller *agp_find_controller_for_client(pid_t id)
+{
+ struct agp_controller *controller;
+
+ controller = agp_fe.controllers;
+
+ while (controller != NULL) {
+ if ((agp_find_client_in_controller(controller, id)) != NULL)
+ return controller;
+ controller = controller->next;
+ }
+
+ return NULL;
+}
+
+static struct agp_client *agp_find_client_by_pid(pid_t id)
+{
+ struct agp_client *temp;
+
+ if (agp_fe.current_controller == NULL)
+ return NULL;
+
+ temp = agp_find_client_in_controller(agp_fe.current_controller, id);
+ return temp;
+}
+
+static void agp_insert_client(struct agp_client *client)
+{
+ struct agp_client *prev_client;
+
+ prev_client = agp_fe.current_controller->clients;
+ client->next = prev_client;
+
+ if (prev_client != NULL)
+ prev_client->prev = client;
+
+ agp_fe.current_controller->clients = client;
+ agp_fe.current_controller->num_clients++;
+}
+
+static struct agp_client *agp_create_client(pid_t id)
+{
+ struct agp_client *new_client;
+
+ new_client = kmalloc(sizeof(struct agp_client), GFP_KERNEL);
+
+ if (new_client == NULL)
+ return NULL;
+
+ memset(new_client, 0, sizeof(struct agp_client));
+ new_client->pid = id;
+ agp_insert_client(new_client);
+ return new_client;
+}
+
+static int agp_remove_client(pid_t id)
+{
+ struct agp_client *client;
+ struct agp_client *prev_client;
+ struct agp_client *next_client;
+ struct agp_controller *controller;
+
+ controller = agp_find_controller_for_client(id);
+ if (controller == NULL)
+ return -EINVAL;
+
+ client = agp_find_client_in_controller(controller, id);
+ if (client == NULL)
+ return -EINVAL;
+
+ prev_client = client->prev;
+ next_client = client->next;
+
+ if (prev_client != NULL) {
+ prev_client->next = next_client;
+ if (next_client != NULL)
+ next_client->prev = prev_client;
+
+ } else {
+ if (next_client != NULL)
+ next_client->prev = NULL;
+ controller->clients = next_client;
+ }
+
+ controller->num_clients--;
+ agp_remove_seg_from_client(client);
+ kfree(client);
+ return 0;
+}
+
+/* End - Routines for managing client lists */
+
+/* File Operations */
+
+static int agp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned int size, current_size;
+ unsigned long offset;
+ struct agp_client *client;
+ struct agp_file_private *priv = file->private_data;
+ struct agp_kern_info kerninfo;
+
+ down(&(agp_fe.agp_mutex));
+
+ if (agp_fe.backend_acquired != TRUE)
+ goto out_eperm;
+
+ if (!(test_bit(AGP_FF_IS_VALID, &priv->access_flags)))
+ goto out_eperm;
+
+ agp_copy_info(agp_bridge, &kerninfo);
+ size = vma->vm_end - vma->vm_start;
+ current_size = kerninfo.aper_size;
+ current_size = current_size * 0x100000;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+ DBG("%lx:%lx", offset, offset+size);
+
+ if (test_bit(AGP_FF_IS_CLIENT, &priv->access_flags)) {
+ if ((size + offset) > current_size)
+ goto out_inval;
+
+ client = agp_find_client_by_pid(current->pid);
+
+ if (client == NULL)
+ goto out_eperm;
+
+ if (!agp_find_seg_in_client(client, offset, size, vma->vm_page_prot))
+ goto out_inval;
+
+ DBG("client vm_ops=%p", kerninfo.vm_ops);
+ if (kerninfo.vm_ops) {
+ vma->vm_ops = kerninfo.vm_ops;
+ } else if (io_remap_pfn_range(vma, vma->vm_start,
+ (kerninfo.aper_base + offset) >> PAGE_SHIFT,
+ size, vma->vm_page_prot)) {
+ goto out_again;
+ }
+ up(&(agp_fe.agp_mutex));
+ return 0;
+ }
+
+ if (test_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags)) {
+ if (size != current_size)
+ goto out_inval;
+
+ DBG("controller vm_ops=%p", kerninfo.vm_ops);
+ if (kerninfo.vm_ops) {
+ vma->vm_ops = kerninfo.vm_ops;
+ } else if (io_remap_pfn_range(vma, vma->vm_start,
+ kerninfo.aper_base >> PAGE_SHIFT,
+ size, vma->vm_page_prot)) {
+ goto out_again;
+ }
+ up(&(agp_fe.agp_mutex));
+ return 0;
+ }
+
+out_eperm:
+ up(&(agp_fe.agp_mutex));
+ return -EPERM;
+
+out_inval:
+ up(&(agp_fe.agp_mutex));
+ return -EINVAL;
+
+out_again:
+ up(&(agp_fe.agp_mutex));
+ return -EAGAIN;
+}
+
+static int agp_release(struct inode *inode, struct file *file)
+{
+ struct agp_file_private *priv = file->private_data;
+
+ down(&(agp_fe.agp_mutex));
+
+ DBG("priv=%p", priv);
+
+ if (test_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags)) {
+ struct agp_controller *controller;
+
+ controller = agp_find_controller_by_pid(priv->my_pid);
+
+ if (controller != NULL) {
+ if (controller == agp_fe.current_controller)
+ agp_controller_release_current(controller, priv);
+ agp_remove_controller(controller);
+ controller = NULL;
+ }
+ }
+
+ if (test_bit(AGP_FF_IS_CLIENT, &priv->access_flags))
+ agp_remove_client(priv->my_pid);
+
+ agp_remove_file_private(priv);
+ kfree(priv);
+ file->private_data = NULL;
+ up(&(agp_fe.agp_mutex));
+ return 0;
+}
+
+static int agp_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct agp_file_private *priv;
+ struct agp_client *client;
+ int rc = -ENXIO;
+
+ down(&(agp_fe.agp_mutex));
+
+ if (minor != AGPGART_MINOR)
+ goto err_out;
+
+ priv = kmalloc(sizeof(struct agp_file_private), GFP_KERNEL);
+ if (priv == NULL)
+ goto err_out_nomem;
+
+ memset(priv, 0, sizeof(struct agp_file_private));
+ set_bit(AGP_FF_ALLOW_CLIENT, &priv->access_flags);
+ priv->my_pid = current->pid;
+
+ if ((current->uid == 0) || (current->suid == 0)) {
+ /* Root priv, can be controller */
+ set_bit(AGP_FF_ALLOW_CONTROLLER, &priv->access_flags);
+ }
+ client = agp_find_client_by_pid(current->pid);
+
+ if (client != NULL) {
+ set_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+ set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+ }
+ file->private_data = (void *) priv;
+ agp_insert_file_private(priv);
+ DBG("private=%p, client=%p", priv, client);
+ up(&(agp_fe.agp_mutex));
+ return 0;
+
+err_out_nomem:
+ rc = -ENOMEM;
+err_out:
+ up(&(agp_fe.agp_mutex));
+ return rc;
+}
+
+
+static ssize_t agp_read(struct file *file, char __user *buf,
+ size_t count, loff_t * ppos)
+{
+ return -EINVAL;
+}
+
+static ssize_t agp_write(struct file *file, const char __user *buf,
+ size_t count, loff_t * ppos)
+{
+ return -EINVAL;
+}
+
+static int agpioc_info_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_info userinfo;
+ struct agp_kern_info kerninfo;
+
+ agp_copy_info(agp_bridge, &kerninfo);
+
+ userinfo.version.major = kerninfo.version.major;
+ userinfo.version.minor = kerninfo.version.minor;
+ userinfo.bridge_id = kerninfo.device->vendor |
+ (kerninfo.device->device << 16);
+ userinfo.agp_mode = kerninfo.mode;
+ userinfo.aper_base = kerninfo.aper_base;
+ userinfo.aper_size = kerninfo.aper_size;
+ userinfo.pg_total = userinfo.pg_system = kerninfo.max_memory;
+ userinfo.pg_used = kerninfo.current_memory;
+
+ if (copy_to_user(arg, &userinfo, sizeof(struct agp_info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int agpioc_acquire_wrap(struct agp_file_private *priv)
+{
+ struct agp_controller *controller;
+
+ DBG("");
+
+ if (!(test_bit(AGP_FF_ALLOW_CONTROLLER, &priv->access_flags)))
+ return -EPERM;
+
+ if (agp_fe.current_controller != NULL)
+ return -EBUSY;
+
+ if(!agp_bridge)
+ return -ENODEV;
+
+ if (atomic_read(&agp_bridge->agp_in_use))
+ return -EBUSY;
+
+ atomic_inc(&agp_bridge->agp_in_use);
+
+ agp_fe.backend_acquired = TRUE;
+
+ controller = agp_find_controller_by_pid(priv->my_pid);
+
+ if (controller != NULL) {
+ agp_controller_make_current(controller);
+ } else {
+ controller = agp_create_controller(priv->my_pid);
+
+ if (controller == NULL) {
+ agp_fe.backend_acquired = FALSE;
+ agp_backend_release(agp_bridge);
+ return -ENOMEM;
+ }
+ agp_insert_controller(controller);
+ agp_controller_make_current(controller);
+ }
+
+ set_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags);
+ set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+ return 0;
+}
+
+static int agpioc_release_wrap(struct agp_file_private *priv)
+{
+ DBG("");
+ agp_controller_release_current(agp_fe.current_controller, priv);
+ return 0;
+}
+
+static int agpioc_setup_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_setup mode;
+
+ DBG("");
+ if (copy_from_user(&mode, arg, sizeof(struct agp_setup)))
+ return -EFAULT;
+
+ agp_enable(agp_bridge, mode.agp_mode);
+ return 0;
+}
+
+static int agpioc_reserve_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_region reserve;
+ struct agp_client *client;
+ struct agp_file_private *client_priv;
+
+ DBG("");
+ if (copy_from_user(&reserve, arg, sizeof(struct agp_region)))
+ return -EFAULT;
+
+ if ((unsigned) reserve.seg_count >= ~0U/sizeof(struct agp_segment))
+ return -EFAULT;
+
+ client = agp_find_client_by_pid(reserve.pid);
+
+ if (reserve.seg_count == 0) {
+ /* remove a client */
+ client_priv = agp_find_private(reserve.pid);
+
+ if (client_priv != NULL) {
+ set_bit(AGP_FF_IS_CLIENT, &client_priv->access_flags);
+ set_bit(AGP_FF_IS_VALID, &client_priv->access_flags);
+ }
+ if (client == NULL) {
+ /* client is already removed */
+ return 0;
+ }
+ return agp_remove_client(reserve.pid);
+ } else {
+ struct agp_segment *segment;
+
+ if (reserve.seg_count >= 16384)
+ return -EINVAL;
+
+ segment = kmalloc((sizeof(struct agp_segment) * reserve.seg_count),
+ GFP_KERNEL);
+
+ if (segment == NULL)
+ return -ENOMEM;
+
+ if (copy_from_user(segment, (void __user *) reserve.seg_list,
+ sizeof(struct agp_segment) * reserve.seg_count)) {
+ kfree(segment);
+ return -EFAULT;
+ }
+ reserve.seg_list = segment;
+
+ if (client == NULL) {
+ /* Create the client and add the segment */
+ client = agp_create_client(reserve.pid);
+
+ if (client == NULL) {
+ kfree(segment);
+ return -ENOMEM;
+ }
+ client_priv = agp_find_private(reserve.pid);
+
+ if (client_priv != NULL) {
+ set_bit(AGP_FF_IS_CLIENT, &client_priv->access_flags);
+ set_bit(AGP_FF_IS_VALID, &client_priv->access_flags);
+ }
+ }
+ return agp_create_segment(client, &reserve);
+ }
+ /* Will never really happen */
+ return -EINVAL;
+}
+
+static int agpioc_protect_wrap(struct agp_file_private *priv)
+{
+ DBG("");
+ /* This function is not currently implemented */
+ return -EINVAL;
+}
+
+static int agpioc_allocate_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_memory *memory;
+ struct agp_allocate alloc;
+
+ DBG("");
+ if (copy_from_user(&alloc, arg, sizeof(struct agp_allocate)))
+ return -EFAULT;
+
+ memory = agp_allocate_memory_wrap(alloc.pg_count, alloc.type);
+
+ if (memory == NULL)
+ return -ENOMEM;
+
+ alloc.key = memory->key;
+ alloc.physical = memory->physical;
+
+ if (copy_to_user(arg, &alloc, sizeof(struct agp_allocate))) {
+ agp_free_memory_wrap(memory);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+static int agpioc_deallocate_wrap(struct agp_file_private *priv, int arg)
+{
+ struct agp_memory *memory;
+
+ DBG("");
+ memory = agp_find_mem_by_key(arg);
+
+ if (memory == NULL)
+ return -EINVAL;
+
+ agp_free_memory_wrap(memory);
+ return 0;
+}
+
+static int agpioc_bind_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_bind bind_info;
+ struct agp_memory *memory;
+
+ DBG("");
+ if (copy_from_user(&bind_info, arg, sizeof(struct agp_bind)))
+ return -EFAULT;
+
+ memory = agp_find_mem_by_key(bind_info.key);
+
+ if (memory == NULL)
+ return -EINVAL;
+
+ return agp_bind_memory(memory, bind_info.pg_start);
+}
+
+static int agpioc_unbind_wrap(struct agp_file_private *priv, void __user *arg)
+{
+ struct agp_memory *memory;
+ struct agp_unbind unbind;
+
+ DBG("");
+ if (copy_from_user(&unbind, arg, sizeof(struct agp_unbind)))
+ return -EFAULT;
+
+ memory = agp_find_mem_by_key(unbind.key);
+
+ if (memory == NULL)
+ return -EINVAL;
+
+ return agp_unbind_memory(memory);
+}
+
+static int agp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct agp_file_private *curr_priv = file->private_data;
+ int ret_val = -ENOTTY;
+
+ DBG("priv=%p, cmd=%x", curr_priv, cmd);
+ down(&(agp_fe.agp_mutex));
+
+ if ((agp_fe.current_controller == NULL) &&
+ (cmd != AGPIOC_ACQUIRE)) {
+ ret_val = -EINVAL;
+ goto ioctl_out;
+ }
+ if ((agp_fe.backend_acquired != TRUE) &&
+ (cmd != AGPIOC_ACQUIRE)) {
+ ret_val = -EBUSY;
+ goto ioctl_out;
+ }
+ if (cmd != AGPIOC_ACQUIRE) {
+ if (!(test_bit(AGP_FF_IS_CONTROLLER, &curr_priv->access_flags))) {
+ ret_val = -EPERM;
+ goto ioctl_out;
+ }
+ /* Use the original pid of the controller,
+ * in case it's threaded */
+
+ if (agp_fe.current_controller->pid != curr_priv->my_pid) {
+ ret_val = -EBUSY;
+ goto ioctl_out;
+ }
+ }
+
+ switch (cmd) {
+ case AGPIOC_INFO:
+ ret_val = agpioc_info_wrap(curr_priv, (void __user *) arg);
+ break;
+
+ case AGPIOC_ACQUIRE:
+ ret_val = agpioc_acquire_wrap(curr_priv);
+ break;
+
+ case AGPIOC_RELEASE:
+ ret_val = agpioc_release_wrap(curr_priv);
+ break;
+
+ case AGPIOC_SETUP:
+ ret_val = agpioc_setup_wrap(curr_priv, (void __user *) arg);
+ break;
+
+ case AGPIOC_RESERVE:
+ ret_val = agpioc_reserve_wrap(curr_priv, (void __user *) arg);
+ break;
+
+ case AGPIOC_PROTECT:
+ ret_val = agpioc_protect_wrap(curr_priv);
+ break;
+
+ case AGPIOC_ALLOCATE:
+ ret_val = agpioc_allocate_wrap(curr_priv, (void __user *) arg);
+ break;
+
+ case AGPIOC_DEALLOCATE:
+ ret_val = agpioc_deallocate_wrap(curr_priv, (int) arg);
+ break;
+
+ case AGPIOC_BIND:
+ ret_val = agpioc_bind_wrap(curr_priv, (void __user *) arg);
+ break;
+
+ case AGPIOC_UNBIND:
+ ret_val = agpioc_unbind_wrap(curr_priv, (void __user *) arg);
+ break;
+ }
+
+ioctl_out:
+ DBG("ioctl returns %d\n", ret_val);
+ up(&(agp_fe.agp_mutex));
+ return ret_val;
+}
+
+static struct file_operations agp_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = agp_read,
+ .write = agp_write,
+ .ioctl = agp_ioctl,
+ .mmap = agp_mmap,
+ .open = agp_open,
+ .release = agp_release,
+};
+
+static struct miscdevice agp_miscdev =
+{
+ .minor = AGPGART_MINOR,
+ .name = "agpgart",
+ .fops = &agp_fops
+};
+
+int agp_frontend_initialize(void)
+{
+ memset(&agp_fe, 0, sizeof(struct agp_front_data));
+ sema_init(&(agp_fe.agp_mutex), 1);
+
+ if (misc_register(&agp_miscdev)) {
+ printk(KERN_ERR PFX "unable to get minor: %d\n", AGPGART_MINOR);
+ return -EIO;
+ }
+ return 0;
+}
+
+void agp_frontend_cleanup(void)
+{
+ misc_deregister(&agp_miscdev);
+}
diff --git a/drivers/char/agp/generic.c b/drivers/char/agp/generic.c
new file mode 100644
index 000000000000..c321a924e38a
--- /dev/null
+++ b/drivers/char/agp/generic.c
@@ -0,0 +1,1222 @@
+/*
+ * AGPGART driver.
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2005 Dave Jones.
+ * Copyright (C) 1999 Jeff Hartmann.
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * TODO:
+ * - Allocate more than order 0 pages to avoid too much linear map splitting.
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/miscdevice.h>
+#include <linux/pm.h>
+#include <linux/agp_backend.h>
+#include <linux/vmalloc.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <asm/io.h>
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+#include "agp.h"
+
+__u32 *agp_gatt_table;
+int agp_memory_reserved;
+
+/*
+ * Needed by the Nforce GART driver for the time being. Would be
+ * nice to do this some other way instead of needing this export.
+ */
+EXPORT_SYMBOL_GPL(agp_memory_reserved);
+
+#if defined(CONFIG_X86)
+int map_page_into_agp(struct page *page)
+{
+ int i;
+ i = change_page_attr(page, 1, PAGE_KERNEL_NOCACHE);
+ global_flush_tlb();
+ return i;
+}
+EXPORT_SYMBOL_GPL(map_page_into_agp);
+
+int unmap_page_from_agp(struct page *page)
+{
+ int i;
+ i = change_page_attr(page, 1, PAGE_KERNEL);
+ global_flush_tlb();
+ return i;
+}
+EXPORT_SYMBOL_GPL(unmap_page_from_agp);
+#endif
+
+/*
+ * Generic routines for handling agp_memory structures -
+ * They use the basic page allocation routines to do the brunt of the work.
+ */
+
+void agp_free_key(int key)
+{
+ if (key < 0)
+ return;
+
+ if (key < MAXKEY)
+ clear_bit(key, agp_bridge->key_list);
+}
+EXPORT_SYMBOL(agp_free_key);
+
+
+static int agp_get_key(void)
+{
+ int bit;
+
+ bit = find_first_zero_bit(agp_bridge->key_list, MAXKEY);
+ if (bit < MAXKEY) {
+ set_bit(bit, agp_bridge->key_list);
+ return bit;
+ }
+ return -1;
+}
+
+
+struct agp_memory *agp_create_memory(int scratch_pages)
+{
+ struct agp_memory *new;
+
+ new = kmalloc(sizeof(struct agp_memory), GFP_KERNEL);
+
+ if (new == NULL)
+ return NULL;
+
+ memset(new, 0, sizeof(struct agp_memory));
+ new->key = agp_get_key();
+
+ if (new->key < 0) {
+ kfree(new);
+ return NULL;
+ }
+ new->memory = vmalloc(PAGE_SIZE * scratch_pages);
+
+ if (new->memory == NULL) {
+ agp_free_key(new->key);
+ kfree(new);
+ return NULL;
+ }
+ new->num_scratch_pages = scratch_pages;
+ return new;
+}
+EXPORT_SYMBOL(agp_create_memory);
+
+/**
+ * agp_free_memory - free memory associated with an agp_memory pointer.
+ *
+ * @curr: agp_memory pointer to be freed.
+ *
+ * It is the only function that can be called when the backend is not owned
+ * by the caller. (So it can free memory on client death.)
+ */
+void agp_free_memory(struct agp_memory *curr)
+{
+ size_t i;
+
+ if (curr == NULL)
+ return;
+
+ if (curr->is_bound == TRUE)
+ agp_unbind_memory(curr);
+
+ if (curr->type != 0) {
+ curr->bridge->driver->free_by_type(curr);
+ return;
+ }
+ if (curr->page_count != 0) {
+ for (i = 0; i < curr->page_count; i++) {
+ curr->bridge->driver->agp_destroy_page(phys_to_virt(curr->memory[i]));
+ }
+ }
+ agp_free_key(curr->key);
+ vfree(curr->memory);
+ kfree(curr);
+}
+EXPORT_SYMBOL(agp_free_memory);
+
+#define ENTRIES_PER_PAGE (PAGE_SIZE / sizeof(unsigned long))
+
+/**
+ * agp_allocate_memory - allocate a group of pages of a certain type.
+ *
+ * @page_count: size_t argument of the number of pages
+ * @type: u32 argument of the type of memory to be allocated.
+ *
+ * Every agp bridge device will allow you to allocate AGP_NORMAL_MEMORY which
+ * maps to physical ram. Any other type is device dependent.
+ *
+ * It returns NULL whenever memory is unavailable.
+ */
+struct agp_memory *agp_allocate_memory(struct agp_bridge_data *bridge,
+ size_t page_count, u32 type)
+{
+ int scratch_pages;
+ struct agp_memory *new;
+ size_t i;
+
+ if (!bridge)
+ return NULL;
+
+ if ((atomic_read(&bridge->current_memory_agp) + page_count) > bridge->max_memory_agp)
+ return NULL;
+
+ if (type != 0) {
+ new = bridge->driver->alloc_by_type(page_count, type);
+ if (new)
+ new->bridge = bridge;
+ return new;
+ }
+
+ scratch_pages = (page_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
+
+ new = agp_create_memory(scratch_pages);
+
+ if (new == NULL)
+ return NULL;
+
+ for (i = 0; i < page_count; i++) {
+ void *addr = bridge->driver->agp_alloc_page(bridge);
+
+ if (addr == NULL) {
+ agp_free_memory(new);
+ return NULL;
+ }
+ new->memory[i] = virt_to_phys(addr);
+ new->page_count++;
+ }
+ new->bridge = bridge;
+
+ flush_agp_mappings();
+
+ return new;
+}
+EXPORT_SYMBOL(agp_allocate_memory);
+
+
+/* End - Generic routines for handling agp_memory structures */
+
+
+static int agp_return_size(void)
+{
+ int current_size;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+
+ switch (agp_bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ current_size = A_SIZE_8(temp)->size;
+ break;
+ case U16_APER_SIZE:
+ current_size = A_SIZE_16(temp)->size;
+ break;
+ case U32_APER_SIZE:
+ current_size = A_SIZE_32(temp)->size;
+ break;
+ case LVL2_APER_SIZE:
+ current_size = A_SIZE_LVL2(temp)->size;
+ break;
+ case FIXED_APER_SIZE:
+ current_size = A_SIZE_FIX(temp)->size;
+ break;
+ default:
+ current_size = 0;
+ break;
+ }
+
+ current_size -= (agp_memory_reserved / (1024*1024));
+ if (current_size <0)
+ current_size = 0;
+ return current_size;
+}
+
+
+int agp_num_entries(void)
+{
+ int num_entries;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+
+ switch (agp_bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ num_entries = A_SIZE_8(temp)->num_entries;
+ break;
+ case U16_APER_SIZE:
+ num_entries = A_SIZE_16(temp)->num_entries;
+ break;
+ case U32_APER_SIZE:
+ num_entries = A_SIZE_32(temp)->num_entries;
+ break;
+ case LVL2_APER_SIZE:
+ num_entries = A_SIZE_LVL2(temp)->num_entries;
+ break;
+ case FIXED_APER_SIZE:
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+ break;
+ default:
+ num_entries = 0;
+ break;
+ }
+
+ num_entries -= agp_memory_reserved>>PAGE_SHIFT;
+ if (num_entries<0)
+ num_entries = 0;
+ return num_entries;
+}
+EXPORT_SYMBOL_GPL(agp_num_entries);
+
+
+static int check_bridge_mode(struct pci_dev *dev)
+{
+ u32 agp3;
+ u8 cap_ptr;
+
+ cap_ptr = pci_find_capability(dev, PCI_CAP_ID_AGP);
+ pci_read_config_dword(dev, cap_ptr+AGPSTAT, &agp3);
+ if (agp3 & AGPSTAT_MODE_3_0)
+ return 1;
+ return 0;
+}
+
+
+/**
+ * agp_copy_info - copy bridge state information
+ *
+ * @info: agp_kern_info pointer. The caller should insure that this pointer is valid.
+ *
+ * This function copies information about the agp bridge device and the state of
+ * the agp backend into an agp_kern_info pointer.
+ */
+int agp_copy_info(struct agp_bridge_data *bridge, struct agp_kern_info *info)
+{
+ memset(info, 0, sizeof(struct agp_kern_info));
+ if (!bridge) {
+ info->chipset = NOT_SUPPORTED;
+ return -EIO;
+ }
+
+ info->version.major = bridge->version->major;
+ info->version.minor = bridge->version->minor;
+ info->chipset = SUPPORTED;
+ info->device = bridge->dev;
+ if (check_bridge_mode(bridge->dev))
+ info->mode = bridge->mode & ~AGP3_RESERVED_MASK;
+ else
+ info->mode = bridge->mode & ~AGP2_RESERVED_MASK;
+ info->mode = bridge->mode;
+ info->aper_base = bridge->gart_bus_addr;
+ info->aper_size = agp_return_size();
+ info->max_memory = bridge->max_memory_agp;
+ info->current_memory = atomic_read(&bridge->current_memory_agp);
+ info->cant_use_aperture = bridge->driver->cant_use_aperture;
+ info->vm_ops = bridge->vm_ops;
+ info->page_mask = ~0UL;
+ return 0;
+}
+EXPORT_SYMBOL(agp_copy_info);
+
+/* End - Routine to copy over information structure */
+
+/*
+ * Routines for handling swapping of agp_memory into the GATT -
+ * These routines take agp_memory and insert them into the GATT.
+ * They call device specific routines to actually write to the GATT.
+ */
+
+/**
+ * agp_bind_memory - Bind an agp_memory structure into the GATT.
+ *
+ * @curr: agp_memory pointer
+ * @pg_start: an offset into the graphics aperture translation table
+ *
+ * It returns -EINVAL if the pointer == NULL.
+ * It returns -EBUSY if the area of the table requested is already in use.
+ */
+int agp_bind_memory(struct agp_memory *curr, off_t pg_start)
+{
+ int ret_val;
+
+ if (curr == NULL)
+ return -EINVAL;
+
+ if (curr->is_bound == TRUE) {
+ printk (KERN_INFO PFX "memory %p is already bound!\n", curr);
+ return -EINVAL;
+ }
+ if (curr->is_flushed == FALSE) {
+ curr->bridge->driver->cache_flush();
+ curr->is_flushed = TRUE;
+ }
+ ret_val = curr->bridge->driver->insert_memory(curr, pg_start, curr->type);
+
+ if (ret_val != 0)
+ return ret_val;
+
+ curr->is_bound = TRUE;
+ curr->pg_start = pg_start;
+ return 0;
+}
+EXPORT_SYMBOL(agp_bind_memory);
+
+
+/**
+ * agp_unbind_memory - Removes an agp_memory structure from the GATT
+ *
+ * @curr: agp_memory pointer to be removed from the GATT.
+ *
+ * It returns -EINVAL if this piece of agp_memory is not currently bound to
+ * the graphics aperture translation table or if the agp_memory pointer == NULL
+ */
+int agp_unbind_memory(struct agp_memory *curr)
+{
+ int ret_val;
+
+ if (curr == NULL)
+ return -EINVAL;
+
+ if (curr->is_bound != TRUE) {
+ printk (KERN_INFO PFX "memory %p was not bound!\n", curr);
+ return -EINVAL;
+ }
+
+ ret_val = curr->bridge->driver->remove_memory(curr, curr->pg_start, curr->type);
+
+ if (ret_val != 0)
+ return ret_val;
+
+ curr->is_bound = FALSE;
+ curr->pg_start = 0;
+ return 0;
+}
+EXPORT_SYMBOL(agp_unbind_memory);
+
+/* End - Routines for handling swapping of agp_memory into the GATT */
+
+
+/* Generic Agp routines - Start */
+static void agp_v2_parse_one(u32 *requested_mode, u32 *bridge_agpstat, u32 *vga_agpstat)
+{
+ u32 tmp;
+
+ if (*requested_mode & AGP2_RESERVED_MASK) {
+ printk (KERN_INFO PFX "reserved bits set in mode 0x%x. Fixed.\n", *requested_mode);
+ *requested_mode &= ~AGP2_RESERVED_MASK;
+ }
+
+ /* Check the speed bits make sense. Only one should be set. */
+ tmp = *requested_mode & 7;
+ switch (tmp) {
+ case 0:
+ printk (KERN_INFO PFX "%s tried to set rate=x0. Setting to x1 mode.\n", current->comm);
+ *requested_mode |= AGPSTAT2_1X;
+ break;
+ case 1:
+ case 2:
+ break;
+ case 3:
+ *requested_mode &= ~(AGPSTAT2_1X); /* rate=2 */
+ break;
+ case 4:
+ break;
+ case 5:
+ case 6:
+ case 7:
+ *requested_mode &= ~(AGPSTAT2_1X|AGPSTAT2_2X); /* rate=4*/
+ break;
+ }
+
+ /* disable SBA if it's not supported */
+ if (!((*bridge_agpstat & AGPSTAT_SBA) && (*vga_agpstat & AGPSTAT_SBA) && (*requested_mode & AGPSTAT_SBA)))
+ *bridge_agpstat &= ~AGPSTAT_SBA;
+
+ /* Set rate */
+ if (!((*bridge_agpstat & AGPSTAT2_4X) && (*vga_agpstat & AGPSTAT2_4X) && (*requested_mode & AGPSTAT2_4X)))
+ *bridge_agpstat &= ~AGPSTAT2_4X;
+
+ if (!((*bridge_agpstat & AGPSTAT2_2X) && (*vga_agpstat & AGPSTAT2_2X) && (*requested_mode & AGPSTAT2_2X)))
+ *bridge_agpstat &= ~AGPSTAT2_2X;
+
+ if (!((*bridge_agpstat & AGPSTAT2_1X) && (*vga_agpstat & AGPSTAT2_1X) && (*requested_mode & AGPSTAT2_1X)))
+ *bridge_agpstat &= ~AGPSTAT2_1X;
+
+ /* Now we know what mode it should be, clear out the unwanted bits. */
+ if (*bridge_agpstat & AGPSTAT2_4X)
+ *bridge_agpstat &= ~(AGPSTAT2_1X | AGPSTAT2_2X); /* 4X */
+
+ if (*bridge_agpstat & AGPSTAT2_2X)
+ *bridge_agpstat &= ~(AGPSTAT2_1X | AGPSTAT2_4X); /* 2X */
+
+ if (*bridge_agpstat & AGPSTAT2_1X)
+ *bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X); /* 1X */
+
+ /* Apply any errata. */
+ if (agp_bridge->flags & AGP_ERRATA_FASTWRITES)
+ *bridge_agpstat &= ~AGPSTAT_FW;
+
+ if (agp_bridge->flags & AGP_ERRATA_SBA)
+ *bridge_agpstat &= ~AGPSTAT_SBA;
+
+ if (agp_bridge->flags & AGP_ERRATA_1X) {
+ *bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X);
+ *bridge_agpstat |= AGPSTAT2_1X;
+ }
+
+ /* If we've dropped down to 1X, disable fast writes. */
+ if (*bridge_agpstat & AGPSTAT2_1X)
+ *bridge_agpstat &= ~AGPSTAT_FW;
+}
+
+/*
+ * requested_mode = Mode requested by (typically) X.
+ * bridge_agpstat = PCI_AGP_STATUS from agp bridge.
+ * vga_agpstat = PCI_AGP_STATUS from graphic card.
+ */
+static void agp_v3_parse_one(u32 *requested_mode, u32 *bridge_agpstat, u32 *vga_agpstat)
+{
+ u32 origbridge=*bridge_agpstat, origvga=*vga_agpstat;
+ u32 tmp;
+
+ if (*requested_mode & AGP3_RESERVED_MASK) {
+ printk (KERN_INFO PFX "reserved bits set in mode 0x%x. Fixed.\n", *requested_mode);
+ *requested_mode &= ~AGP3_RESERVED_MASK;
+ }
+
+ /* Check the speed bits make sense. */
+ tmp = *requested_mode & 7;
+ if (tmp == 0) {
+ printk (KERN_INFO PFX "%s tried to set rate=x0. Setting to AGP3 x4 mode.\n", current->comm);
+ *requested_mode |= AGPSTAT3_4X;
+ }
+ if (tmp >= 3) {
+ printk (KERN_INFO PFX "%s tried to set rate=x%d. Setting to AGP3 x8 mode.\n", current->comm, tmp * 4);
+ *requested_mode = (*requested_mode & ~7) | AGPSTAT3_8X;
+ }
+
+ /* ARQSZ - Set the value to the maximum one.
+ * Don't allow the mode register to override values. */
+ *bridge_agpstat = ((*bridge_agpstat & ~AGPSTAT_ARQSZ) |
+ max_t(u32,(*bridge_agpstat & AGPSTAT_ARQSZ),(*vga_agpstat & AGPSTAT_ARQSZ)));
+
+ /* Calibration cycle.
+ * Don't allow the mode register to override values. */
+ *bridge_agpstat = ((*bridge_agpstat & ~AGPSTAT_CAL_MASK) |
+ min_t(u32,(*bridge_agpstat & AGPSTAT_CAL_MASK),(*vga_agpstat & AGPSTAT_CAL_MASK)));
+
+ /* SBA *must* be supported for AGP v3 */
+ *bridge_agpstat |= AGPSTAT_SBA;
+
+ /*
+ * Set speed.
+ * Check for invalid speeds. This can happen when applications
+ * written before the AGP 3.0 standard pass AGP2.x modes to AGP3 hardware
+ */
+ if (*requested_mode & AGPSTAT_MODE_3_0) {
+ /*
+ * Caller hasn't a clue what it is doing. Bridge is in 3.0 mode,
+ * have been passed a 3.0 mode, but with 2.x speed bits set.
+ * AGP2.x 4x -> AGP3.0 4x.
+ */
+ if (*requested_mode & AGPSTAT2_4X) {
+ printk (KERN_INFO PFX "%s passes broken AGP3 flags (%x). Fixed.\n",
+ current->comm, *requested_mode);
+ *requested_mode &= ~AGPSTAT2_4X;
+ *requested_mode |= AGPSTAT3_4X;
+ }
+ } else {
+ /*
+ * The caller doesn't know what they are doing. We are in 3.0 mode,
+ * but have been passed an AGP 2.x mode.
+ * Convert AGP 1x,2x,4x -> AGP 3.0 4x.
+ */
+ printk (KERN_INFO PFX "%s passes broken AGP2 flags (%x) in AGP3 mode. Fixed.\n",
+ current->comm, *requested_mode);
+ *requested_mode &= ~(AGPSTAT2_4X | AGPSTAT2_2X | AGPSTAT2_1X);
+ *requested_mode |= AGPSTAT3_4X;
+ }
+
+ if (*requested_mode & AGPSTAT3_8X) {
+ if (!(*bridge_agpstat & AGPSTAT3_8X)) {
+ *bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+ *bridge_agpstat |= AGPSTAT3_4X;
+ printk ("%s requested AGPx8 but bridge not capable.\n", current->comm);
+ return;
+ }
+ if (!(*vga_agpstat & AGPSTAT3_8X)) {
+ *bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+ *bridge_agpstat |= AGPSTAT3_4X;
+ printk ("%s requested AGPx8 but graphic card not capable.\n", current->comm);
+ return;
+ }
+ /* All set, bridge & device can do AGP x8*/
+ *bridge_agpstat &= ~(AGPSTAT3_4X | AGPSTAT3_RSVD);
+ goto done;
+
+ } else {
+
+ /*
+ * If we didn't specify AGPx8, we can only do x4.
+ * If the hardware can't do x4, we're up shit creek, and never
+ * should have got this far.
+ */
+ *bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+ if ((*bridge_agpstat & AGPSTAT3_4X) && (*vga_agpstat & AGPSTAT3_4X))
+ *bridge_agpstat |= AGPSTAT3_4X;
+ else {
+ printk (KERN_INFO PFX "Badness. Don't know which AGP mode to set. "
+ "[bridge_agpstat:%x vga_agpstat:%x fell back to:- bridge_agpstat:%x vga_agpstat:%x]\n",
+ origbridge, origvga, *bridge_agpstat, *vga_agpstat);
+ if (!(*bridge_agpstat & AGPSTAT3_4X))
+ printk (KERN_INFO PFX "Bridge couldn't do AGP x4.\n");
+ if (!(*vga_agpstat & AGPSTAT3_4X))
+ printk (KERN_INFO PFX "Graphic card couldn't do AGP x4.\n");
+ return;
+ }
+ }
+
+done:
+ /* Apply any errata. */
+ if (agp_bridge->flags & AGP_ERRATA_FASTWRITES)
+ *bridge_agpstat &= ~AGPSTAT_FW;
+
+ if (agp_bridge->flags & AGP_ERRATA_SBA)
+ *bridge_agpstat &= ~AGPSTAT_SBA;
+
+ if (agp_bridge->flags & AGP_ERRATA_1X) {
+ *bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X);
+ *bridge_agpstat |= AGPSTAT2_1X;
+ }
+}
+
+
+/**
+ * agp_collect_device_status - determine correct agp_cmd from various agp_stat's
+ * @bridge: an agp_bridge_data struct allocated for the AGP host bridge.
+ * @requested_mode: requested agp_stat from userspace (Typically from X)
+ * @bridge_agpstat: current agp_stat from AGP bridge.
+ *
+ * This function will hunt for an AGP graphics card, and try to match
+ * the requested mode to the capabilities of both the bridge and the card.
+ */
+u32 agp_collect_device_status(struct agp_bridge_data *bridge, u32 requested_mode, u32 bridge_agpstat)
+{
+ struct pci_dev *device = NULL;
+ u32 vga_agpstat;
+ u8 cap_ptr;
+
+ for (;;) {
+ device = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, device);
+ if (!device) {
+ printk (KERN_INFO PFX "Couldn't find an AGP VGA controller.\n");
+ return 0;
+ }
+ cap_ptr = pci_find_capability(device, PCI_CAP_ID_AGP);
+ if (cap_ptr)
+ break;
+ }
+
+ /*
+ * Ok, here we have a AGP device. Disable impossible
+ * settings, and adjust the readqueue to the minimum.
+ */
+ pci_read_config_dword(device, cap_ptr+PCI_AGP_STATUS, &vga_agpstat);
+
+ /* adjust RQ depth */
+ bridge_agpstat = ((bridge_agpstat & ~AGPSTAT_RQ_DEPTH) |
+ min_t(u32, (requested_mode & AGPSTAT_RQ_DEPTH),
+ min_t(u32, (bridge_agpstat & AGPSTAT_RQ_DEPTH), (vga_agpstat & AGPSTAT_RQ_DEPTH))));
+
+ /* disable FW if it's not supported */
+ if (!((bridge_agpstat & AGPSTAT_FW) &&
+ (vga_agpstat & AGPSTAT_FW) &&
+ (requested_mode & AGPSTAT_FW)))
+ bridge_agpstat &= ~AGPSTAT_FW;
+
+ /* Check to see if we are operating in 3.0 mode */
+ if (check_bridge_mode(agp_bridge->dev))
+ agp_v3_parse_one(&requested_mode, &bridge_agpstat, &vga_agpstat);
+ else
+ agp_v2_parse_one(&requested_mode, &bridge_agpstat, &vga_agpstat);
+
+ pci_dev_put(device);
+ return bridge_agpstat;
+}
+EXPORT_SYMBOL(agp_collect_device_status);
+
+
+void agp_device_command(u32 bridge_agpstat, int agp_v3)
+{
+ struct pci_dev *device = NULL;
+ int mode;
+
+ mode = bridge_agpstat & 0x7;
+ if (agp_v3)
+ mode *= 4;
+
+ for_each_pci_dev(device) {
+ u8 agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+ if (!agp)
+ continue;
+
+ printk(KERN_INFO PFX "Putting AGP V%d device at %s into %dx mode\n",
+ agp_v3 ? 3 : 2, pci_name(device), mode);
+ pci_write_config_dword(device, agp + PCI_AGP_COMMAND, bridge_agpstat);
+ }
+}
+EXPORT_SYMBOL(agp_device_command);
+
+
+void get_agp_version(struct agp_bridge_data *bridge)
+{
+ u32 ncapid;
+
+ /* Exit early if already set by errata workarounds. */
+ if (bridge->major_version != 0)
+ return;
+
+ pci_read_config_dword(bridge->dev, bridge->capndx, &ncapid);
+ bridge->major_version = (ncapid >> AGP_MAJOR_VERSION_SHIFT) & 0xf;
+ bridge->minor_version = (ncapid >> AGP_MINOR_VERSION_SHIFT) & 0xf;
+}
+EXPORT_SYMBOL(get_agp_version);
+
+
+void agp_generic_enable(struct agp_bridge_data *bridge, u32 requested_mode)
+{
+ u32 bridge_agpstat, temp;
+
+ get_agp_version(agp_bridge);
+
+ printk(KERN_INFO PFX "Found an AGP %d.%d compliant device at %s.\n",
+ agp_bridge->major_version,
+ agp_bridge->minor_version,
+ pci_name(agp_bridge->dev));
+
+ pci_read_config_dword(agp_bridge->dev,
+ agp_bridge->capndx + PCI_AGP_STATUS, &bridge_agpstat);
+
+ bridge_agpstat = agp_collect_device_status(agp_bridge, requested_mode, bridge_agpstat);
+ if (bridge_agpstat == 0)
+ /* Something bad happened. FIXME: Return error code? */
+ return;
+
+ bridge_agpstat |= AGPSTAT_AGP_ENABLE;
+
+ /* Do AGP version specific frobbing. */
+ if (bridge->major_version >= 3) {
+ if (check_bridge_mode(bridge->dev)) {
+ /* If we have 3.5, we can do the isoch stuff. */
+ if (bridge->minor_version >= 5)
+ agp_3_5_enable(bridge);
+ agp_device_command(bridge_agpstat, TRUE);
+ return;
+ } else {
+ /* Disable calibration cycle in RX91<1> when not in AGP3.0 mode of operation.*/
+ bridge_agpstat &= ~(7<<10) ;
+ pci_read_config_dword(bridge->dev,
+ bridge->capndx+AGPCTRL, &temp);
+ temp |= (1<<9);
+ pci_write_config_dword(bridge->dev,
+ bridge->capndx+AGPCTRL, temp);
+
+ printk (KERN_INFO PFX "Device is in legacy mode,"
+ " falling back to 2.x\n");
+ }
+ }
+
+ /* AGP v<3 */
+ agp_device_command(bridge_agpstat, FALSE);
+}
+EXPORT_SYMBOL(agp_generic_enable);
+
+
+int agp_generic_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ char *table;
+ char *table_end;
+ int size;
+ int page_order;
+ int num_entries;
+ int i;
+ void *temp;
+ struct page *page;
+
+ /* The generic routines can't handle 2 level gatt's */
+ if (bridge->driver->size_type == LVL2_APER_SIZE)
+ return -EINVAL;
+
+ table = NULL;
+ i = bridge->aperture_size_idx;
+ temp = bridge->current_size;
+ size = page_order = num_entries = 0;
+
+ if (bridge->driver->size_type != FIXED_APER_SIZE) {
+ do {
+ switch (bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ size = A_SIZE_8(temp)->size;
+ page_order =
+ A_SIZE_8(temp)->page_order;
+ num_entries =
+ A_SIZE_8(temp)->num_entries;
+ break;
+ case U16_APER_SIZE:
+ size = A_SIZE_16(temp)->size;
+ page_order = A_SIZE_16(temp)->page_order;
+ num_entries = A_SIZE_16(temp)->num_entries;
+ break;
+ case U32_APER_SIZE:
+ size = A_SIZE_32(temp)->size;
+ page_order = A_SIZE_32(temp)->page_order;
+ num_entries = A_SIZE_32(temp)->num_entries;
+ break;
+ /* This case will never really happen. */
+ case FIXED_APER_SIZE:
+ case LVL2_APER_SIZE:
+ default:
+ size = page_order = num_entries = 0;
+ break;
+ }
+
+ table = (char *) __get_free_pages(GFP_KERNEL,
+ page_order);
+
+ if (table == NULL) {
+ i++;
+ switch (bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ bridge->current_size = A_IDX8(bridge);
+ break;
+ case U16_APER_SIZE:
+ bridge->current_size = A_IDX16(bridge);
+ break;
+ case U32_APER_SIZE:
+ bridge->current_size = A_IDX32(bridge);
+ break;
+ /* This case will never really happen. */
+ case FIXED_APER_SIZE:
+ case LVL2_APER_SIZE:
+ default:
+ bridge->current_size =
+ bridge->current_size;
+ break;
+ }
+ temp = bridge->current_size;
+ } else {
+ bridge->aperture_size_idx = i;
+ }
+ } while (!table && (i < bridge->driver->num_aperture_sizes));
+ } else {
+ size = ((struct aper_size_info_fixed *) temp)->size;
+ page_order = ((struct aper_size_info_fixed *) temp)->page_order;
+ num_entries = ((struct aper_size_info_fixed *) temp)->num_entries;
+ table = (char *) __get_free_pages(GFP_KERNEL, page_order);
+ }
+
+ if (table == NULL)
+ return -ENOMEM;
+
+ table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+ for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+ SetPageReserved(page);
+
+ bridge->gatt_table_real = (u32 *) table;
+ agp_gatt_table = (void *)table;
+
+ bridge->driver->cache_flush();
+ bridge->gatt_table = ioremap_nocache(virt_to_phys(table),
+ (PAGE_SIZE * (1 << page_order)));
+ bridge->driver->cache_flush();
+
+ if (bridge->gatt_table == NULL) {
+ for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+ ClearPageReserved(page);
+
+ free_pages((unsigned long) table, page_order);
+
+ return -ENOMEM;
+ }
+ bridge->gatt_bus_addr = virt_to_phys(bridge->gatt_table_real);
+
+ /* AK: bogus, should encode addresses > 4GB */
+ for (i = 0; i < num_entries; i++) {
+ writel(bridge->scratch_page, bridge->gatt_table+i);
+ readl(bridge->gatt_table+i); /* PCI Posting. */
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(agp_generic_create_gatt_table);
+
+int agp_generic_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ int page_order;
+ char *table, *table_end;
+ void *temp;
+ struct page *page;
+
+ temp = bridge->current_size;
+
+ switch (bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ page_order = A_SIZE_8(temp)->page_order;
+ break;
+ case U16_APER_SIZE:
+ page_order = A_SIZE_16(temp)->page_order;
+ break;
+ case U32_APER_SIZE:
+ page_order = A_SIZE_32(temp)->page_order;
+ break;
+ case FIXED_APER_SIZE:
+ page_order = A_SIZE_FIX(temp)->page_order;
+ break;
+ case LVL2_APER_SIZE:
+ /* The generic routines can't deal with 2 level gatt's */
+ return -EINVAL;
+ break;
+ default:
+ page_order = 0;
+ break;
+ }
+
+ /* Do not worry about freeing memory, because if this is
+ * called, then all agp memory is deallocated and removed
+ * from the table. */
+
+ iounmap(bridge->gatt_table);
+ table = (char *) bridge->gatt_table_real;
+ table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+ for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+ ClearPageReserved(page);
+
+ free_pages((unsigned long) bridge->gatt_table_real, page_order);
+
+ agp_gatt_table = NULL;
+ bridge->gatt_table = NULL;
+ bridge->gatt_table_real = NULL;
+ bridge->gatt_bus_addr = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(agp_generic_free_gatt_table);
+
+
+int agp_generic_insert_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+ int num_entries;
+ size_t i;
+ off_t j;
+ void *temp;
+ struct agp_bridge_data *bridge;
+
+ bridge = mem->bridge;
+ if (!bridge)
+ return -EINVAL;
+
+ temp = bridge->current_size;
+
+ switch (bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ num_entries = A_SIZE_8(temp)->num_entries;
+ break;
+ case U16_APER_SIZE:
+ num_entries = A_SIZE_16(temp)->num_entries;
+ break;
+ case U32_APER_SIZE:
+ num_entries = A_SIZE_32(temp)->num_entries;
+ break;
+ case FIXED_APER_SIZE:
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+ break;
+ case LVL2_APER_SIZE:
+ /* The generic routines can't deal with 2 level gatt's */
+ return -EINVAL;
+ break;
+ default:
+ num_entries = 0;
+ break;
+ }
+
+ num_entries -= agp_memory_reserved/PAGE_SIZE;
+ if (num_entries < 0) num_entries = 0;
+
+ if (type != 0 || mem->type != 0) {
+ /* The generic routines know nothing of memory types */
+ return -EINVAL;
+ }
+
+ /* AK: could wrap */
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+
+ while (j < (pg_start + mem->page_count)) {
+ if (!PGE_EMPTY(bridge, readl(bridge->gatt_table+j)))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ bridge->driver->cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ writel(bridge->driver->mask_memory(bridge, mem->memory[i], mem->type), bridge->gatt_table+j);
+ readl(bridge->gatt_table+j); /* PCI Posting. */
+ }
+
+ bridge->driver->tlb_flush(mem);
+ return 0;
+}
+EXPORT_SYMBOL(agp_generic_insert_memory);
+
+
+int agp_generic_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ size_t i;
+ struct agp_bridge_data *bridge;
+
+ bridge = mem->bridge;
+ if (!bridge)
+ return -EINVAL;
+
+ if (type != 0 || mem->type != 0) {
+ /* The generic routines know nothing of memory types */
+ return -EINVAL;
+ }
+
+ /* AK: bogus, should encode addresses > 4GB */
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ writel(bridge->scratch_page, bridge->gatt_table+i);
+ readl(bridge->gatt_table+i); /* PCI Posting. */
+ }
+
+ global_cache_flush();
+ bridge->driver->tlb_flush(mem);
+ return 0;
+}
+EXPORT_SYMBOL(agp_generic_remove_memory);
+
+
+struct agp_memory *agp_generic_alloc_by_type(size_t page_count, int type)
+{
+ return NULL;
+}
+EXPORT_SYMBOL(agp_generic_alloc_by_type);
+
+
+void agp_generic_free_by_type(struct agp_memory *curr)
+{
+ vfree(curr->memory);
+ agp_free_key(curr->key);
+ kfree(curr);
+}
+EXPORT_SYMBOL(agp_generic_free_by_type);
+
+
+/*
+ * Basic Page Allocation Routines -
+ * These routines handle page allocation and by default they reserve the allocated
+ * memory. They also handle incrementing the current_memory_agp value, Which is checked
+ * against a maximum value.
+ */
+
+void *agp_generic_alloc_page(struct agp_bridge_data *bridge)
+{
+ struct page * page;
+
+ page = alloc_page(GFP_KERNEL);
+ if (page == NULL)
+ return NULL;
+
+ map_page_into_agp(page);
+
+ get_page(page);
+ SetPageLocked(page);
+ atomic_inc(&agp_bridge->current_memory_agp);
+ return page_address(page);
+}
+EXPORT_SYMBOL(agp_generic_alloc_page);
+
+
+void agp_generic_destroy_page(void *addr)
+{
+ struct page *page;
+
+ if (addr == NULL)
+ return;
+
+ page = virt_to_page(addr);
+ unmap_page_from_agp(page);
+ put_page(page);
+ unlock_page(page);
+ free_page((unsigned long)addr);
+ atomic_dec(&agp_bridge->current_memory_agp);
+}
+EXPORT_SYMBOL(agp_generic_destroy_page);
+
+/* End Basic Page Allocation Routines */
+
+
+/**
+ * agp_enable - initialise the agp point-to-point connection.
+ *
+ * @mode: agp mode register value to configure with.
+ */
+void agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ if (!bridge)
+ return;
+ bridge->driver->agp_enable(bridge, mode);
+}
+EXPORT_SYMBOL(agp_enable);
+
+/* When we remove the global variable agp_bridge from all drivers
+ * then agp_alloc_bridge and agp_generic_find_bridge need to be updated
+ */
+
+struct agp_bridge_data *agp_generic_find_bridge(struct pci_dev *pdev)
+{
+ if (list_empty(&agp_bridges))
+ return NULL;
+
+ return agp_bridge;
+}
+
+static void ipi_handler(void *null)
+{
+ flush_agp_cache();
+}
+
+void global_cache_flush(void)
+{
+ if (on_each_cpu(ipi_handler, NULL, 1, 1) != 0)
+ panic(PFX "timed out waiting for the other CPUs!\n");
+}
+EXPORT_SYMBOL(global_cache_flush);
+
+unsigned long agp_generic_mask_memory(struct agp_bridge_data *bridge,
+ unsigned long addr, int type)
+{
+ /* memory type is ignored in the generic routine */
+ if (bridge->driver->masks)
+ return addr | bridge->driver->masks[0].mask;
+ else
+ return addr;
+}
+EXPORT_SYMBOL(agp_generic_mask_memory);
+
+/*
+ * These functions are implemented according to the AGPv3 spec,
+ * which covers implementation details that had previously been
+ * left open.
+ */
+
+int agp3_generic_fetch_size(void)
+{
+ u16 temp_size;
+ int i;
+ struct aper_size_info_16 *values;
+
+ pci_read_config_word(agp_bridge->dev, agp_bridge->capndx+AGPAPSIZE, &temp_size);
+ values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp_size == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(agp3_generic_fetch_size);
+
+void agp3_generic_tlbflush(struct agp_memory *mem)
+{
+ u32 ctrl;
+ pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &ctrl);
+ pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl & ~AGPCTRL_GTLBEN);
+ pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl);
+}
+EXPORT_SYMBOL(agp3_generic_tlbflush);
+
+int agp3_generic_configure(void)
+{
+ u32 temp;
+ struct aper_size_info_16 *current_size;
+
+ current_size = A_SIZE_16(agp_bridge->current_size);
+
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* set aperture size */
+ pci_write_config_word(agp_bridge->dev, agp_bridge->capndx+AGPAPSIZE, current_size->size_value);
+ /* set gart pointer */
+ pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPGARTLO, agp_bridge->gatt_bus_addr);
+ /* enable aperture and GTLB */
+ pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, temp | AGPCTRL_APERENB | AGPCTRL_GTLBEN);
+ return 0;
+}
+EXPORT_SYMBOL(agp3_generic_configure);
+
+void agp3_generic_cleanup(void)
+{
+ u32 ctrl;
+ pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &ctrl);
+ pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl & ~AGPCTRL_APERENB);
+}
+EXPORT_SYMBOL(agp3_generic_cleanup);
+
+struct aper_size_info_16 agp3_generic_sizes[AGP_GENERIC_SIZES_ENTRIES] =
+{
+ {4096, 1048576, 10,0x000},
+ {2048, 524288, 9, 0x800},
+ {1024, 262144, 8, 0xc00},
+ { 512, 131072, 7, 0xe00},
+ { 256, 65536, 6, 0xf00},
+ { 128, 32768, 5, 0xf20},
+ { 64, 16384, 4, 0xf30},
+ { 32, 8192, 3, 0xf38},
+ { 16, 4096, 2, 0xf3c},
+ { 8, 2048, 1, 0xf3e},
+ { 4, 1024, 0, 0xf3f}
+};
+EXPORT_SYMBOL(agp3_generic_sizes);
+
diff --git a/drivers/char/agp/hp-agp.c b/drivers/char/agp/hp-agp.c
new file mode 100644
index 000000000000..6052bfa04c72
--- /dev/null
+++ b/drivers/char/agp/hp-agp.c
@@ -0,0 +1,552 @@
+/*
+ * HP zx1 AGPGART routines.
+ *
+ * (c) Copyright 2002, 2003 Hewlett-Packard Development Company, L.P.
+ * Bjorn Helgaas <bjorn.helgaas@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+
+#include <asm/acpi-ext.h>
+
+#include "agp.h"
+
+#ifndef log2
+#define log2(x) ffz(~(x))
+#endif
+
+#define HP_ZX1_IOC_OFFSET 0x1000 /* ACPI reports SBA, we want IOC */
+
+/* HP ZX1 IOC registers */
+#define HP_ZX1_IBASE 0x300
+#define HP_ZX1_IMASK 0x308
+#define HP_ZX1_PCOM 0x310
+#define HP_ZX1_TCNFG 0x318
+#define HP_ZX1_PDIR_BASE 0x320
+
+#define HP_ZX1_IOVA_BASE GB(1UL)
+#define HP_ZX1_IOVA_SIZE GB(1UL)
+#define HP_ZX1_GART_SIZE (HP_ZX1_IOVA_SIZE / 2)
+#define HP_ZX1_SBA_IOMMU_COOKIE 0x0000badbadc0ffeeUL
+
+#define HP_ZX1_PDIR_VALID_BIT 0x8000000000000000UL
+#define HP_ZX1_IOVA_TO_PDIR(va) ((va - hp_private.iova_base) >> hp_private.io_tlb_shift)
+
+#define AGP8X_MODE_BIT 3
+#define AGP8X_MODE (1 << AGP8X_MODE_BIT)
+
+/* AGP bridge need not be PCI device, but DRM thinks it is. */
+static struct pci_dev fake_bridge_dev;
+
+static int hp_zx1_gart_found;
+
+static struct aper_size_info_fixed hp_zx1_sizes[] =
+{
+ {0, 0, 0}, /* filled in by hp_zx1_fetch_size() */
+};
+
+static struct gatt_mask hp_zx1_masks[] =
+{
+ {.mask = HP_ZX1_PDIR_VALID_BIT, .type = 0}
+};
+
+static struct _hp_private {
+ volatile u8 __iomem *ioc_regs;
+ volatile u8 __iomem *lba_regs;
+ int lba_cap_offset;
+ u64 *io_pdir; // PDIR for entire IOVA
+ u64 *gatt; // PDIR just for GART (subset of above)
+ u64 gatt_entries;
+ u64 iova_base;
+ u64 gart_base;
+ u64 gart_size;
+ u64 io_pdir_size;
+ int io_pdir_owner; // do we own it, or share it with sba_iommu?
+ int io_page_size;
+ int io_tlb_shift;
+ int io_tlb_ps; // IOC ps config
+ int io_pages_per_kpage;
+} hp_private;
+
+static int __init hp_zx1_ioc_shared(void)
+{
+ struct _hp_private *hp = &hp_private;
+
+ printk(KERN_INFO PFX "HP ZX1 IOC: IOPDIR shared with sba_iommu\n");
+
+ /*
+ * IOC already configured by sba_iommu module; just use
+ * its setup. We assume:
+ * - IOVA space is 1Gb in size
+ * - first 512Mb is IOMMU, second 512Mb is GART
+ */
+ hp->io_tlb_ps = readq(hp->ioc_regs+HP_ZX1_TCNFG);
+ switch (hp->io_tlb_ps) {
+ case 0: hp->io_tlb_shift = 12; break;
+ case 1: hp->io_tlb_shift = 13; break;
+ case 2: hp->io_tlb_shift = 14; break;
+ case 3: hp->io_tlb_shift = 16; break;
+ default:
+ printk(KERN_ERR PFX "Invalid IOTLB page size "
+ "configuration 0x%x\n", hp->io_tlb_ps);
+ hp->gatt = NULL;
+ hp->gatt_entries = 0;
+ return -ENODEV;
+ }
+ hp->io_page_size = 1 << hp->io_tlb_shift;
+ hp->io_pages_per_kpage = PAGE_SIZE / hp->io_page_size;
+
+ hp->iova_base = readq(hp->ioc_regs+HP_ZX1_IBASE) & ~0x1;
+ hp->gart_base = hp->iova_base + HP_ZX1_IOVA_SIZE - HP_ZX1_GART_SIZE;
+
+ hp->gart_size = HP_ZX1_GART_SIZE;
+ hp->gatt_entries = hp->gart_size / hp->io_page_size;
+
+ hp->io_pdir = phys_to_virt(readq(hp->ioc_regs+HP_ZX1_PDIR_BASE));
+ hp->gatt = &hp->io_pdir[HP_ZX1_IOVA_TO_PDIR(hp->gart_base)];
+
+ if (hp->gatt[0] != HP_ZX1_SBA_IOMMU_COOKIE) {
+ /* Normal case when no AGP device in system */
+ hp->gatt = NULL;
+ hp->gatt_entries = 0;
+ printk(KERN_ERR PFX "No reserved IO PDIR entry found; "
+ "GART disabled\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int __init
+hp_zx1_ioc_owner (void)
+{
+ struct _hp_private *hp = &hp_private;
+
+ printk(KERN_INFO PFX "HP ZX1 IOC: IOPDIR dedicated to GART\n");
+
+ /*
+ * Select an IOV page size no larger than system page size.
+ */
+ if (PAGE_SIZE >= KB(64)) {
+ hp->io_tlb_shift = 16;
+ hp->io_tlb_ps = 3;
+ } else if (PAGE_SIZE >= KB(16)) {
+ hp->io_tlb_shift = 14;
+ hp->io_tlb_ps = 2;
+ } else if (PAGE_SIZE >= KB(8)) {
+ hp->io_tlb_shift = 13;
+ hp->io_tlb_ps = 1;
+ } else {
+ hp->io_tlb_shift = 12;
+ hp->io_tlb_ps = 0;
+ }
+ hp->io_page_size = 1 << hp->io_tlb_shift;
+ hp->io_pages_per_kpage = PAGE_SIZE / hp->io_page_size;
+
+ hp->iova_base = HP_ZX1_IOVA_BASE;
+ hp->gart_size = HP_ZX1_GART_SIZE;
+ hp->gart_base = hp->iova_base + HP_ZX1_IOVA_SIZE - hp->gart_size;
+
+ hp->gatt_entries = hp->gart_size / hp->io_page_size;
+ hp->io_pdir_size = (HP_ZX1_IOVA_SIZE / hp->io_page_size) * sizeof(u64);
+
+ return 0;
+}
+
+static int __init
+hp_zx1_ioc_init (u64 hpa)
+{
+ struct _hp_private *hp = &hp_private;
+
+ hp->ioc_regs = ioremap(hpa, 1024);
+ if (!hp->ioc_regs)
+ return -ENOMEM;
+
+ /*
+ * If the IOTLB is currently disabled, we can take it over.
+ * Otherwise, we have to share with sba_iommu.
+ */
+ hp->io_pdir_owner = (readq(hp->ioc_regs+HP_ZX1_IBASE) & 0x1) == 0;
+
+ if (hp->io_pdir_owner)
+ return hp_zx1_ioc_owner();
+
+ return hp_zx1_ioc_shared();
+}
+
+static int
+hp_zx1_lba_find_capability (volatile u8 __iomem *hpa, int cap)
+{
+ u16 status;
+ u8 pos, id;
+ int ttl = 48;
+
+ status = readw(hpa+PCI_STATUS);
+ if (!(status & PCI_STATUS_CAP_LIST))
+ return 0;
+ pos = readb(hpa+PCI_CAPABILITY_LIST);
+ while (ttl-- && pos >= 0x40) {
+ pos &= ~3;
+ id = readb(hpa+pos+PCI_CAP_LIST_ID);
+ if (id == 0xff)
+ break;
+ if (id == cap)
+ return pos;
+ pos = readb(hpa+pos+PCI_CAP_LIST_NEXT);
+ }
+ return 0;
+}
+
+static int __init
+hp_zx1_lba_init (u64 hpa)
+{
+ struct _hp_private *hp = &hp_private;
+ int cap;
+
+ hp->lba_regs = ioremap(hpa, 256);
+ if (!hp->lba_regs)
+ return -ENOMEM;
+
+ hp->lba_cap_offset = hp_zx1_lba_find_capability(hp->lba_regs, PCI_CAP_ID_AGP);
+
+ cap = readl(hp->lba_regs+hp->lba_cap_offset) & 0xff;
+ if (cap != PCI_CAP_ID_AGP) {
+ printk(KERN_ERR PFX "Invalid capability ID 0x%02x at 0x%x\n",
+ cap, hp->lba_cap_offset);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int
+hp_zx1_fetch_size(void)
+{
+ int size;
+
+ size = hp_private.gart_size / MB(1);
+ hp_zx1_sizes[0].size = size;
+ agp_bridge->current_size = (void *) &hp_zx1_sizes[0];
+ return size;
+}
+
+static int
+hp_zx1_configure (void)
+{
+ struct _hp_private *hp = &hp_private;
+
+ agp_bridge->gart_bus_addr = hp->gart_base;
+ agp_bridge->capndx = hp->lba_cap_offset;
+ agp_bridge->mode = readl(hp->lba_regs+hp->lba_cap_offset+PCI_AGP_STATUS);
+
+ if (hp->io_pdir_owner) {
+ writel(virt_to_phys(hp->io_pdir), hp->ioc_regs+HP_ZX1_PDIR_BASE);
+ readl(hp->ioc_regs+HP_ZX1_PDIR_BASE);
+ writel(hp->io_tlb_ps, hp->ioc_regs+HP_ZX1_TCNFG);
+ readl(hp->ioc_regs+HP_ZX1_TCNFG);
+ writel(~(HP_ZX1_IOVA_SIZE-1), hp->ioc_regs+HP_ZX1_IMASK);
+ readl(hp->ioc_regs+HP_ZX1_IMASK);
+ writel(hp->iova_base|1, hp->ioc_regs+HP_ZX1_IBASE);
+ readl(hp->ioc_regs+HP_ZX1_IBASE);
+ writel(hp->iova_base|log2(HP_ZX1_IOVA_SIZE), hp->ioc_regs+HP_ZX1_PCOM);
+ readl(hp->ioc_regs+HP_ZX1_PCOM);
+ }
+
+ return 0;
+}
+
+static void
+hp_zx1_cleanup (void)
+{
+ struct _hp_private *hp = &hp_private;
+
+ if (hp->ioc_regs) {
+ if (hp->io_pdir_owner) {
+ writeq(0, hp->ioc_regs+HP_ZX1_IBASE);
+ readq(hp->ioc_regs+HP_ZX1_IBASE);
+ }
+ iounmap(hp->ioc_regs);
+ }
+ if (hp->lba_regs)
+ iounmap(hp->lba_regs);
+}
+
+static void
+hp_zx1_tlbflush (struct agp_memory *mem)
+{
+ struct _hp_private *hp = &hp_private;
+
+ writeq(hp->gart_base | log2(hp->gart_size), hp->ioc_regs+HP_ZX1_PCOM);
+ readq(hp->ioc_regs+HP_ZX1_PCOM);
+}
+
+static int
+hp_zx1_create_gatt_table (struct agp_bridge_data *bridge)
+{
+ struct _hp_private *hp = &hp_private;
+ int i;
+
+ if (hp->io_pdir_owner) {
+ hp->io_pdir = (u64 *) __get_free_pages(GFP_KERNEL,
+ get_order(hp->io_pdir_size));
+ if (!hp->io_pdir) {
+ printk(KERN_ERR PFX "Couldn't allocate contiguous "
+ "memory for I/O PDIR\n");
+ hp->gatt = NULL;
+ hp->gatt_entries = 0;
+ return -ENOMEM;
+ }
+ memset(hp->io_pdir, 0, hp->io_pdir_size);
+
+ hp->gatt = &hp->io_pdir[HP_ZX1_IOVA_TO_PDIR(hp->gart_base)];
+ }
+
+ for (i = 0; i < hp->gatt_entries; i++) {
+ hp->gatt[i] = (unsigned long) agp_bridge->scratch_page;
+ }
+
+ return 0;
+}
+
+static int
+hp_zx1_free_gatt_table (struct agp_bridge_data *bridge)
+{
+ struct _hp_private *hp = &hp_private;
+
+ if (hp->io_pdir_owner)
+ free_pages((unsigned long) hp->io_pdir,
+ get_order(hp->io_pdir_size));
+ else
+ hp->gatt[0] = HP_ZX1_SBA_IOMMU_COOKIE;
+ return 0;
+}
+
+static int
+hp_zx1_insert_memory (struct agp_memory *mem, off_t pg_start, int type)
+{
+ struct _hp_private *hp = &hp_private;
+ int i, k;
+ off_t j, io_pg_start;
+ int io_pg_count;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+
+ io_pg_start = hp->io_pages_per_kpage * pg_start;
+ io_pg_count = hp->io_pages_per_kpage * mem->page_count;
+ if ((io_pg_start + io_pg_count) > hp->gatt_entries) {
+ return -EINVAL;
+ }
+
+ j = io_pg_start;
+ while (j < (io_pg_start + io_pg_count)) {
+ if (hp->gatt[j]) {
+ return -EBUSY;
+ }
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = io_pg_start; i < mem->page_count; i++) {
+ unsigned long paddr;
+
+ paddr = mem->memory[i];
+ for (k = 0;
+ k < hp->io_pages_per_kpage;
+ k++, j++, paddr += hp->io_page_size) {
+ hp->gatt[j] =
+ agp_bridge->driver->mask_memory(agp_bridge,
+ paddr, type);
+ }
+ }
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int
+hp_zx1_remove_memory (struct agp_memory *mem, off_t pg_start, int type)
+{
+ struct _hp_private *hp = &hp_private;
+ int i, io_pg_start, io_pg_count;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+
+ io_pg_start = hp->io_pages_per_kpage * pg_start;
+ io_pg_count = hp->io_pages_per_kpage * mem->page_count;
+ for (i = io_pg_start; i < io_pg_count + io_pg_start; i++) {
+ hp->gatt[i] = agp_bridge->scratch_page;
+ }
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static unsigned long
+hp_zx1_mask_memory (struct agp_bridge_data *bridge,
+ unsigned long addr, int type)
+{
+ return HP_ZX1_PDIR_VALID_BIT | addr;
+}
+
+static void
+hp_zx1_enable (struct agp_bridge_data *bridge, u32 mode)
+{
+ struct _hp_private *hp = &hp_private;
+ u32 command;
+
+ command = readl(hp->lba_regs+hp->lba_cap_offset+PCI_AGP_STATUS);
+ command = agp_collect_device_status(bridge, mode, command);
+ command |= 0x00000100;
+
+ writel(command, hp->lba_regs+hp->lba_cap_offset+PCI_AGP_COMMAND);
+
+ agp_device_command(command, (mode & AGP8X_MODE) != 0);
+}
+
+struct agp_bridge_driver hp_zx1_driver = {
+ .owner = THIS_MODULE,
+ .size_type = FIXED_APER_SIZE,
+ .configure = hp_zx1_configure,
+ .fetch_size = hp_zx1_fetch_size,
+ .cleanup = hp_zx1_cleanup,
+ .tlb_flush = hp_zx1_tlbflush,
+ .mask_memory = hp_zx1_mask_memory,
+ .masks = hp_zx1_masks,
+ .agp_enable = hp_zx1_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = hp_zx1_create_gatt_table,
+ .free_gatt_table = hp_zx1_free_gatt_table,
+ .insert_memory = hp_zx1_insert_memory,
+ .remove_memory = hp_zx1_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+ .cant_use_aperture = 1,
+};
+
+static int __init
+hp_zx1_setup (u64 ioc_hpa, u64 lba_hpa)
+{
+ struct agp_bridge_data *bridge;
+ int error = 0;
+
+ error = hp_zx1_ioc_init(ioc_hpa);
+ if (error)
+ goto fail;
+
+ error = hp_zx1_lba_init(lba_hpa);
+ if (error)
+ goto fail;
+
+ bridge = agp_alloc_bridge();
+ if (!bridge) {
+ error = -ENOMEM;
+ goto fail;
+ }
+ bridge->driver = &hp_zx1_driver;
+
+ fake_bridge_dev.vendor = PCI_VENDOR_ID_HP;
+ fake_bridge_dev.device = PCI_DEVICE_ID_HP_PCIX_LBA;
+ bridge->dev = &fake_bridge_dev;
+
+ error = agp_add_bridge(bridge);
+ fail:
+ if (error)
+ hp_zx1_cleanup();
+ return error;
+}
+
+static acpi_status __init
+zx1_gart_probe (acpi_handle obj, u32 depth, void *context, void **ret)
+{
+ acpi_handle handle, parent;
+ acpi_status status;
+ struct acpi_buffer buffer;
+ struct acpi_device_info *info;
+ u64 lba_hpa, sba_hpa, length;
+ int match;
+
+ status = hp_acpi_csr_space(obj, &lba_hpa, &length);
+ if (ACPI_FAILURE(status))
+ return AE_OK; /* keep looking for another bridge */
+
+ /* Look for an enclosing IOC scope and find its CSR space */
+ handle = obj;
+ do {
+ buffer.length = ACPI_ALLOCATE_LOCAL_BUFFER;
+ status = acpi_get_object_info(handle, &buffer);
+ if (ACPI_SUCCESS(status)) {
+ /* TBD check _CID also */
+ info = buffer.pointer;
+ info->hardware_id.value[sizeof(info->hardware_id)-1] = '\0';
+ match = (strcmp(info->hardware_id.value, "HWP0001") == 0);
+ ACPI_MEM_FREE(info);
+ if (match) {
+ status = hp_acpi_csr_space(handle, &sba_hpa, &length);
+ if (ACPI_SUCCESS(status))
+ break;
+ else {
+ printk(KERN_ERR PFX "Detected HP ZX1 "
+ "AGP LBA but no IOC.\n");
+ return AE_OK;
+ }
+ }
+ }
+
+ status = acpi_get_parent(handle, &parent);
+ handle = parent;
+ } while (ACPI_SUCCESS(status));
+
+ if (hp_zx1_setup(sba_hpa + HP_ZX1_IOC_OFFSET, lba_hpa))
+ return AE_OK;
+
+ printk(KERN_INFO PFX "Detected HP ZX1 %s AGP chipset (ioc=%lx, lba=%lx)\n",
+ (char *) context, sba_hpa + HP_ZX1_IOC_OFFSET, lba_hpa);
+
+ hp_zx1_gart_found = 1;
+ return AE_CTRL_TERMINATE; /* we only support one bridge; quit looking */
+}
+
+static int __init
+agp_hp_init (void)
+{
+ if (agp_off)
+ return -EINVAL;
+
+ acpi_get_devices("HWP0003", zx1_gart_probe, "HWP0003", NULL);
+ if (hp_zx1_gart_found)
+ return 0;
+
+ acpi_get_devices("HWP0007", zx1_gart_probe, "HWP0007", NULL);
+ if (hp_zx1_gart_found)
+ return 0;
+
+ return -ENODEV;
+}
+
+static void __exit
+agp_hp_cleanup (void)
+{
+}
+
+module_init(agp_hp_init);
+module_exit(agp_hp_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/i460-agp.c b/drivers/char/agp/i460-agp.c
new file mode 100644
index 000000000000..adbea896c0d2
--- /dev/null
+++ b/drivers/char/agp/i460-agp.c
@@ -0,0 +1,642 @@
+/*
+ * For documentation on the i460 AGP interface, see Chapter 7 (AGP Subsystem) of
+ * the "Intel 460GTX Chipset Software Developer's Manual":
+ * http://developer.intel.com/design/itanium/downloads/24870401s.htm
+ */
+/*
+ * 460GX support by Chris Ahna <christopher.j.ahna@intel.com>
+ * Clean up & simplification by David Mosberger-Tang <davidm@hpl.hp.com>
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+
+#include "agp.h"
+
+#define INTEL_I460_BAPBASE 0x98
+#define INTEL_I460_GXBCTL 0xa0
+#define INTEL_I460_AGPSIZ 0xa2
+#define INTEL_I460_ATTBASE 0xfe200000
+#define INTEL_I460_GATT_VALID (1UL << 24)
+#define INTEL_I460_GATT_COHERENT (1UL << 25)
+
+/*
+ * The i460 can operate with large (4MB) pages, but there is no sane way to support this
+ * within the current kernel/DRM environment, so we disable the relevant code for now.
+ * See also comments in ia64_alloc_page()...
+ */
+#define I460_LARGE_IO_PAGES 0
+
+#if I460_LARGE_IO_PAGES
+# define I460_IO_PAGE_SHIFT i460.io_page_shift
+#else
+# define I460_IO_PAGE_SHIFT 12
+#endif
+
+#define I460_IOPAGES_PER_KPAGE (PAGE_SIZE >> I460_IO_PAGE_SHIFT)
+#define I460_KPAGES_PER_IOPAGE (1 << (I460_IO_PAGE_SHIFT - PAGE_SHIFT))
+#define I460_SRAM_IO_DISABLE (1 << 4)
+#define I460_BAPBASE_ENABLE (1 << 3)
+#define I460_AGPSIZ_MASK 0x7
+#define I460_4M_PS (1 << 1)
+
+/* Control bits for Out-Of-GART coherency and Burst Write Combining */
+#define I460_GXBCTL_OOG (1UL << 0)
+#define I460_GXBCTL_BWC (1UL << 2)
+
+/*
+ * gatt_table entries are 32-bits wide on the i460; the generic code ought to declare the
+ * gatt_table and gatt_table_real pointers a "void *"...
+ */
+#define RD_GATT(index) readl((u32 *) i460.gatt + (index))
+#define WR_GATT(index, val) writel((val), (u32 *) i460.gatt + (index))
+/*
+ * The 460 spec says we have to read the last location written to make sure that all
+ * writes have taken effect
+ */
+#define WR_FLUSH_GATT(index) RD_GATT(index)
+
+#define log2(x) ffz(~(x))
+
+static struct {
+ void *gatt; /* ioremap'd GATT area */
+
+ /* i460 supports multiple GART page sizes, so GART pageshift is dynamic: */
+ u8 io_page_shift;
+
+ /* BIOS configures chipset to one of 2 possible apbase values: */
+ u8 dynamic_apbase;
+
+ /* structure for tracking partial use of 4MB GART pages: */
+ struct lp_desc {
+ unsigned long *alloced_map; /* bitmap of kernel-pages in use */
+ int refcount; /* number of kernel pages using the large page */
+ u64 paddr; /* physical address of large page */
+ } *lp_desc;
+} i460;
+
+static struct aper_size_info_8 i460_sizes[3] =
+{
+ /*
+ * The 32GB aperture is only available with a 4M GART page size. Due to the
+ * dynamic GART page size, we can't figure out page_order or num_entries until
+ * runtime.
+ */
+ {32768, 0, 0, 4},
+ {1024, 0, 0, 2},
+ {256, 0, 0, 1}
+};
+
+static struct gatt_mask i460_masks[] =
+{
+ {
+ .mask = INTEL_I460_GATT_VALID | INTEL_I460_GATT_COHERENT,
+ .type = 0
+ }
+};
+
+static int i460_fetch_size (void)
+{
+ int i;
+ u8 temp;
+ struct aper_size_info_8 *values;
+
+ /* Determine the GART page size */
+ pci_read_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL, &temp);
+ i460.io_page_shift = (temp & I460_4M_PS) ? 22 : 12;
+ pr_debug("i460_fetch_size: io_page_shift=%d\n", i460.io_page_shift);
+
+ if (i460.io_page_shift != I460_IO_PAGE_SHIFT) {
+ printk(KERN_ERR PFX
+ "I/O (GART) page-size %ZuKB doesn't match expected size %ZuKB\n",
+ 1UL << (i460.io_page_shift - 10), 1UL << (I460_IO_PAGE_SHIFT));
+ return 0;
+ }
+
+ values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+ pci_read_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ, &temp);
+
+ /* Exit now if the IO drivers for the GART SRAMS are turned off */
+ if (temp & I460_SRAM_IO_DISABLE) {
+ printk(KERN_ERR PFX "GART SRAMS disabled on 460GX chipset\n");
+ printk(KERN_ERR PFX "AGPGART operation not possible\n");
+ return 0;
+ }
+
+ /* Make sure we don't try to create an 2 ^ 23 entry GATT */
+ if ((i460.io_page_shift == 0) && ((temp & I460_AGPSIZ_MASK) == 4)) {
+ printk(KERN_ERR PFX "We can't have a 32GB aperture with 4KB GART pages\n");
+ return 0;
+ }
+
+ /* Determine the proper APBASE register */
+ if (temp & I460_BAPBASE_ENABLE)
+ i460.dynamic_apbase = INTEL_I460_BAPBASE;
+ else
+ i460.dynamic_apbase = AGP_APBASE;
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ /*
+ * Dynamically calculate the proper num_entries and page_order values for
+ * the define aperture sizes. Take care not to shift off the end of
+ * values[i].size.
+ */
+ values[i].num_entries = (values[i].size << 8) >> (I460_IO_PAGE_SHIFT - 12);
+ values[i].page_order = log2((sizeof(u32)*values[i].num_entries) >> PAGE_SHIFT);
+ }
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ /* Neglect control bits when matching up size_value */
+ if ((temp & I460_AGPSIZ_MASK) == values[i].size_value) {
+ agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+/* There isn't anything to do here since 460 has no GART TLB. */
+static void i460_tlb_flush (struct agp_memory *mem)
+{
+ return;
+}
+
+/*
+ * This utility function is needed to prevent corruption of the control bits
+ * which are stored along with the aperture size in 460's AGPSIZ register
+ */
+static void i460_write_agpsiz (u8 size_value)
+{
+ u8 temp;
+
+ pci_read_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ, &temp);
+ pci_write_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ,
+ ((temp & ~I460_AGPSIZ_MASK) | size_value));
+}
+
+static void i460_cleanup (void)
+{
+ struct aper_size_info_8 *previous_size;
+
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ i460_write_agpsiz(previous_size->size_value);
+
+ if (I460_IO_PAGE_SHIFT > PAGE_SHIFT)
+ kfree(i460.lp_desc);
+}
+
+static int i460_configure (void)
+{
+ union {
+ u32 small[2];
+ u64 large;
+ } temp;
+ size_t size;
+ u8 scratch;
+ struct aper_size_info_8 *current_size;
+
+ temp.large = 0;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+ i460_write_agpsiz(current_size->size_value);
+
+ /*
+ * Do the necessary rigmarole to read all eight bytes of APBASE.
+ * This has to be done since the AGP aperture can be above 4GB on
+ * 460 based systems.
+ */
+ pci_read_config_dword(agp_bridge->dev, i460.dynamic_apbase, &(temp.small[0]));
+ pci_read_config_dword(agp_bridge->dev, i460.dynamic_apbase + 4, &(temp.small[1]));
+
+ /* Clear BAR control bits */
+ agp_bridge->gart_bus_addr = temp.large & ~((1UL << 3) - 1);
+
+ pci_read_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL, &scratch);
+ pci_write_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL,
+ (scratch & 0x02) | I460_GXBCTL_OOG | I460_GXBCTL_BWC);
+
+ /*
+ * Initialize partial allocation trackers if a GART page is bigger than a kernel
+ * page.
+ */
+ if (I460_IO_PAGE_SHIFT > PAGE_SHIFT) {
+ size = current_size->num_entries * sizeof(i460.lp_desc[0]);
+ i460.lp_desc = kmalloc(size, GFP_KERNEL);
+ if (!i460.lp_desc)
+ return -ENOMEM;
+ memset(i460.lp_desc, 0, size);
+ }
+ return 0;
+}
+
+static int i460_create_gatt_table (struct agp_bridge_data *bridge)
+{
+ int page_order, num_entries, i;
+ void *temp;
+
+ /*
+ * Load up the fixed address of the GART SRAMS which hold our GATT table.
+ */
+ temp = agp_bridge->current_size;
+ page_order = A_SIZE_8(temp)->page_order;
+ num_entries = A_SIZE_8(temp)->num_entries;
+
+ i460.gatt = ioremap(INTEL_I460_ATTBASE, PAGE_SIZE << page_order);
+
+ /* These are no good, the should be removed from the agp_bridge strucure... */
+ agp_bridge->gatt_table_real = NULL;
+ agp_bridge->gatt_table = NULL;
+ agp_bridge->gatt_bus_addr = 0;
+
+ for (i = 0; i < num_entries; ++i)
+ WR_GATT(i, 0);
+ WR_FLUSH_GATT(i - 1);
+ return 0;
+}
+
+static int i460_free_gatt_table (struct agp_bridge_data *bridge)
+{
+ int num_entries, i;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+
+ num_entries = A_SIZE_8(temp)->num_entries;
+
+ for (i = 0; i < num_entries; ++i)
+ WR_GATT(i, 0);
+ WR_FLUSH_GATT(num_entries - 1);
+
+ iounmap(i460.gatt);
+ return 0;
+}
+
+/*
+ * The following functions are called when the I/O (GART) page size is smaller than
+ * PAGE_SIZE.
+ */
+
+static int i460_insert_memory_small_io_page (struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ unsigned long paddr, io_pg_start, io_page_size;
+ int i, j, k, num_entries;
+ void *temp;
+
+ pr_debug("i460_insert_memory_small_io_page(mem=%p, pg_start=%ld, type=%d, paddr0=0x%lx)\n",
+ mem, pg_start, type, mem->memory[0]);
+
+ io_pg_start = I460_IOPAGES_PER_KPAGE * pg_start;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_8(temp)->num_entries;
+
+ if ((io_pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count) > num_entries) {
+ printk(KERN_ERR PFX "Looks like we're out of AGP memory\n");
+ return -EINVAL;
+ }
+
+ j = io_pg_start;
+ while (j < (io_pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count)) {
+ if (!PGE_EMPTY(agp_bridge, RD_GATT(j))) {
+ pr_debug("i460_insert_memory_small_io_page: GATT[%d]=0x%x is busy\n",
+ j, RD_GATT(j));
+ return -EBUSY;
+ }
+ j++;
+ }
+
+ io_page_size = 1UL << I460_IO_PAGE_SHIFT;
+ for (i = 0, j = io_pg_start; i < mem->page_count; i++) {
+ paddr = mem->memory[i];
+ for (k = 0; k < I460_IOPAGES_PER_KPAGE; k++, j++, paddr += io_page_size)
+ WR_GATT(j, agp_bridge->driver->mask_memory(agp_bridge,
+ paddr, mem->type));
+ }
+ WR_FLUSH_GATT(j - 1);
+ return 0;
+}
+
+static int i460_remove_memory_small_io_page(struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ int i;
+
+ pr_debug("i460_remove_memory_small_io_page(mem=%p, pg_start=%ld, type=%d)\n",
+ mem, pg_start, type);
+
+ pg_start = I460_IOPAGES_PER_KPAGE * pg_start;
+
+ for (i = pg_start; i < (pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count); i++)
+ WR_GATT(i, 0);
+ WR_FLUSH_GATT(i - 1);
+ return 0;
+}
+
+#if I460_LARGE_IO_PAGES
+
+/*
+ * These functions are called when the I/O (GART) page size exceeds PAGE_SIZE.
+ *
+ * This situation is interesting since AGP memory allocations that are smaller than a
+ * single GART page are possible. The i460.lp_desc array tracks partial allocation of the
+ * large GART pages to work around this issue.
+ *
+ * i460.lp_desc[pg_num].refcount tracks the number of kernel pages in use within GART page
+ * pg_num. i460.lp_desc[pg_num].paddr is the physical address of the large page and
+ * i460.lp_desc[pg_num].alloced_map is a bitmap of kernel pages that are in use (allocated).
+ */
+
+static int i460_alloc_large_page (struct lp_desc *lp)
+{
+ unsigned long order = I460_IO_PAGE_SHIFT - PAGE_SHIFT;
+ size_t map_size;
+ void *lpage;
+
+ lpage = (void *) __get_free_pages(GFP_KERNEL, order);
+ if (!lpage) {
+ printk(KERN_ERR PFX "Couldn't alloc 4M GART page...\n");
+ return -ENOMEM;
+ }
+
+ map_size = ((I460_KPAGES_PER_IOPAGE + BITS_PER_LONG - 1) & -BITS_PER_LONG)/8;
+ lp->alloced_map = kmalloc(map_size, GFP_KERNEL);
+ if (!lp->alloced_map) {
+ free_pages((unsigned long) lpage, order);
+ printk(KERN_ERR PFX "Out of memory, we're in trouble...\n");
+ return -ENOMEM;
+ }
+ memset(lp->alloced_map, 0, map_size);
+
+ lp->paddr = virt_to_phys(lpage);
+ lp->refcount = 0;
+ atomic_add(I460_KPAGES_PER_IOPAGE, &agp_bridge->current_memory_agp);
+ return 0;
+}
+
+static void i460_free_large_page (struct lp_desc *lp)
+{
+ kfree(lp->alloced_map);
+ lp->alloced_map = NULL;
+
+ free_pages((unsigned long) phys_to_virt(lp->paddr), I460_IO_PAGE_SHIFT - PAGE_SHIFT);
+ atomic_sub(I460_KPAGES_PER_IOPAGE, &agp_bridge->current_memory_agp);
+}
+
+static int i460_insert_memory_large_io_page (struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ int i, start_offset, end_offset, idx, pg, num_entries;
+ struct lp_desc *start, *end, *lp;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_8(temp)->num_entries;
+
+ /* Figure out what pg_start means in terms of our large GART pages */
+ start = &i460.lp_desc[pg_start / I460_KPAGES_PER_IOPAGE];
+ end = &i460.lp_desc[(pg_start + mem->page_count - 1) / I460_KPAGES_PER_IOPAGE];
+ start_offset = pg_start % I460_KPAGES_PER_IOPAGE;
+ end_offset = (pg_start + mem->page_count - 1) % I460_KPAGES_PER_IOPAGE;
+
+ if (end > i460.lp_desc + num_entries) {
+ printk(KERN_ERR PFX "Looks like we're out of AGP memory\n");
+ return -EINVAL;
+ }
+
+ /* Check if the requested region of the aperture is free */
+ for (lp = start; lp <= end; ++lp) {
+ if (!lp->alloced_map)
+ continue; /* OK, the entire large page is available... */
+
+ for (idx = ((lp == start) ? start_offset : 0);
+ idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+ idx++)
+ {
+ if (test_bit(idx, lp->alloced_map))
+ return -EBUSY;
+ }
+ }
+
+ for (lp = start, i = 0; lp <= end; ++lp) {
+ if (!lp->alloced_map) {
+ /* Allocate new GART pages... */
+ if (i460_alloc_large_page(lp) < 0)
+ return -ENOMEM;
+ pg = lp - i460.lp_desc;
+ WR_GATT(pg, agp_bridge->driver->mask_memory(agp_bridge,
+ lp->paddr, 0));
+ WR_FLUSH_GATT(pg);
+ }
+
+ for (idx = ((lp == start) ? start_offset : 0);
+ idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+ idx++, i++)
+ {
+ mem->memory[i] = lp->paddr + idx*PAGE_SIZE;
+ __set_bit(idx, lp->alloced_map);
+ ++lp->refcount;
+ }
+ }
+ return 0;
+}
+
+static int i460_remove_memory_large_io_page (struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ int i, pg, start_offset, end_offset, idx, num_entries;
+ struct lp_desc *start, *end, *lp;
+ void *temp;
+
+ temp = agp_bridge->driver->current_size;
+ num_entries = A_SIZE_8(temp)->num_entries;
+
+ /* Figure out what pg_start means in terms of our large GART pages */
+ start = &i460.lp_desc[pg_start / I460_KPAGES_PER_IOPAGE];
+ end = &i460.lp_desc[(pg_start + mem->page_count - 1) / I460_KPAGES_PER_IOPAGE];
+ start_offset = pg_start % I460_KPAGES_PER_IOPAGE;
+ end_offset = (pg_start + mem->page_count - 1) % I460_KPAGES_PER_IOPAGE;
+
+ for (i = 0, lp = start; lp <= end; ++lp) {
+ for (idx = ((lp == start) ? start_offset : 0);
+ idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+ idx++, i++)
+ {
+ mem->memory[i] = 0;
+ __clear_bit(idx, lp->alloced_map);
+ --lp->refcount;
+ }
+
+ /* Free GART pages if they are unused */
+ if (lp->refcount == 0) {
+ pg = lp - i460.lp_desc;
+ WR_GATT(pg, 0);
+ WR_FLUSH_GATT(pg);
+ i460_free_large_page(lp);
+ }
+ }
+ return 0;
+}
+
+/* Wrapper routines to call the approriate {small_io_page,large_io_page} function */
+
+static int i460_insert_memory (struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+ return i460_insert_memory_small_io_page(mem, pg_start, type);
+ else
+ return i460_insert_memory_large_io_page(mem, pg_start, type);
+}
+
+static int i460_remove_memory (struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+ return i460_remove_memory_small_io_page(mem, pg_start, type);
+ else
+ return i460_remove_memory_large_io_page(mem, pg_start, type);
+}
+
+/*
+ * If the I/O (GART) page size is bigger than the kernel page size, we don't want to
+ * allocate memory until we know where it is to be bound in the aperture (a
+ * multi-kernel-page alloc might fit inside of an already allocated GART page).
+ *
+ * Let's just hope nobody counts on the allocated AGP memory being there before bind time
+ * (I don't think current drivers do)...
+ */
+static void *i460_alloc_page (struct agp_bridge_data *bridge)
+{
+ void *page;
+
+ if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+ page = agp_generic_alloc_page(agp_bridge);
+ else
+ /* Returning NULL would cause problems */
+ /* AK: really dubious code. */
+ page = (void *)~0UL;
+ return page;
+}
+
+static void i460_destroy_page (void *page)
+{
+ if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+ agp_generic_destroy_page(page);
+}
+
+#endif /* I460_LARGE_IO_PAGES */
+
+static unsigned long i460_mask_memory (struct agp_bridge_data *bridge,
+ unsigned long addr, int type)
+{
+ /* Make sure the returned address is a valid GATT entry */
+ return bridge->driver->masks[0].mask
+ | (((addr & ~((1 << I460_IO_PAGE_SHIFT) - 1)) & 0xffffff000) >> 12);
+}
+
+struct agp_bridge_driver intel_i460_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = i460_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 3,
+ .configure = i460_configure,
+ .fetch_size = i460_fetch_size,
+ .cleanup = i460_cleanup,
+ .tlb_flush = i460_tlb_flush,
+ .mask_memory = i460_mask_memory,
+ .masks = i460_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = i460_create_gatt_table,
+ .free_gatt_table = i460_free_gatt_table,
+#if I460_LARGE_IO_PAGES
+ .insert_memory = i460_insert_memory,
+ .remove_memory = i460_remove_memory,
+ .agp_alloc_page = i460_alloc_page,
+ .agp_destroy_page = i460_destroy_page,
+#else
+ .insert_memory = i460_insert_memory_small_io_page,
+ .remove_memory = i460_remove_memory_small_io_page,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+#endif
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .cant_use_aperture = 1,
+};
+
+static int __devinit agp_intel_i460_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &intel_i460_driver;
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ printk(KERN_INFO PFX "Detected Intel 460GX chipset\n");
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_intel_i460_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_intel_i460_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = PCI_DEVICE_ID_INTEL_84460GX,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_intel_i460_pci_table);
+
+static struct pci_driver agp_intel_i460_pci_driver = {
+ .name = "agpgart-intel-i460",
+ .id_table = agp_intel_i460_pci_table,
+ .probe = agp_intel_i460_probe,
+ .remove = __devexit_p(agp_intel_i460_remove),
+};
+
+static int __init agp_intel_i460_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_intel_i460_pci_driver);
+}
+
+static void __exit agp_intel_i460_cleanup(void)
+{
+ pci_unregister_driver(&agp_intel_i460_pci_driver);
+}
+
+module_init(agp_intel_i460_init);
+module_exit(agp_intel_i460_cleanup);
+
+MODULE_AUTHOR("Chris Ahna <Christopher.J.Ahna@intel.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/intel-agp.c b/drivers/char/agp/intel-agp.c
new file mode 100644
index 000000000000..8c7d727432bb
--- /dev/null
+++ b/drivers/char/agp/intel-agp.c
@@ -0,0 +1,1833 @@
+/*
+ * Intel AGPGART routines.
+ */
+
+/*
+ * Intel(R) 855GM/852GM and 865G support added by David Dawes
+ * <dawes@tungstengraphics.com>.
+ *
+ * Intel(R) 915G/915GM support added by Alan Hourihane
+ * <alanh@tungstengraphics.com>.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+/* Intel 815 register */
+#define INTEL_815_APCONT 0x51
+#define INTEL_815_ATTBASE_MASK ~0x1FFFFFFF
+
+/* Intel i820 registers */
+#define INTEL_I820_RDCR 0x51
+#define INTEL_I820_ERRSTS 0xc8
+
+/* Intel i840 registers */
+#define INTEL_I840_MCHCFG 0x50
+#define INTEL_I840_ERRSTS 0xc8
+
+/* Intel i850 registers */
+#define INTEL_I850_MCHCFG 0x50
+#define INTEL_I850_ERRSTS 0xc8
+
+/* intel 915G registers */
+#define I915_GMADDR 0x18
+#define I915_MMADDR 0x10
+#define I915_PTEADDR 0x1C
+#define I915_GMCH_GMS_STOLEN_48M (0x6 << 4)
+#define I915_GMCH_GMS_STOLEN_64M (0x7 << 4)
+
+
+/* Intel 7505 registers */
+#define INTEL_I7505_APSIZE 0x74
+#define INTEL_I7505_NCAPID 0x60
+#define INTEL_I7505_NISTAT 0x6c
+#define INTEL_I7505_ATTBASE 0x78
+#define INTEL_I7505_ERRSTS 0x42
+#define INTEL_I7505_AGPCTRL 0x70
+#define INTEL_I7505_MCHCFG 0x50
+
+static struct aper_size_info_fixed intel_i810_sizes[] =
+{
+ {64, 16384, 4},
+ /* The 32M mode still requires a 64k gatt */
+ {32, 8192, 4}
+};
+
+#define AGP_DCACHE_MEMORY 1
+#define AGP_PHYS_MEMORY 2
+
+static struct gatt_mask intel_i810_masks[] =
+{
+ {.mask = I810_PTE_VALID, .type = 0},
+ {.mask = (I810_PTE_VALID | I810_PTE_LOCAL), .type = AGP_DCACHE_MEMORY},
+ {.mask = I810_PTE_VALID, .type = 0}
+};
+
+static struct _intel_i810_private {
+ struct pci_dev *i810_dev; /* device one */
+ volatile u8 __iomem *registers;
+ int num_dcache_entries;
+} intel_i810_private;
+
+static int intel_i810_fetch_size(void)
+{
+ u32 smram_miscc;
+ struct aper_size_info_fixed *values;
+
+ pci_read_config_dword(agp_bridge->dev, I810_SMRAM_MISCC, &smram_miscc);
+ values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+ if ((smram_miscc & I810_GMS) == I810_GMS_DISABLE) {
+ printk(KERN_WARNING PFX "i810 is disabled\n");
+ return 0;
+ }
+ if ((smram_miscc & I810_GFX_MEM_WIN_SIZE) == I810_GFX_MEM_WIN_32M) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + 1);
+ agp_bridge->aperture_size_idx = 1;
+ return values[1].size;
+ } else {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values);
+ agp_bridge->aperture_size_idx = 0;
+ return values[0].size;
+ }
+
+ return 0;
+}
+
+static int intel_i810_configure(void)
+{
+ struct aper_size_info_fixed *current_size;
+ u32 temp;
+ int i;
+
+ current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+ pci_read_config_dword(intel_i810_private.i810_dev, I810_MMADDR, &temp);
+ temp &= 0xfff80000;
+
+ intel_i810_private.registers = ioremap(temp, 128 * 4096);
+ if (!intel_i810_private.registers) {
+ printk(KERN_ERR PFX "Unable to remap memory.\n");
+ return -ENOMEM;
+ }
+
+ if ((readl(intel_i810_private.registers+I810_DRAM_CTL)
+ & I810_DRAM_ROW_0) == I810_DRAM_ROW_0_SDRAM) {
+ /* This will need to be dynamically assigned */
+ printk(KERN_INFO PFX "detected 4MB dedicated video ram.\n");
+ intel_i810_private.num_dcache_entries = 1024;
+ }
+ pci_read_config_dword(intel_i810_private.i810_dev, I810_GMADDR, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ writel(agp_bridge->gatt_bus_addr | I810_PGETBL_ENABLED, intel_i810_private.registers+I810_PGETBL_CTL);
+ readl(intel_i810_private.registers+I810_PGETBL_CTL); /* PCI Posting. */
+
+ if (agp_bridge->driver->needs_scratch_page) {
+ for (i = 0; i < current_size->num_entries; i++) {
+ writel(agp_bridge->scratch_page, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+ readl(intel_i810_private.registers+I810_PTE_BASE+(i*4)); /* PCI posting. */
+ }
+ }
+ global_cache_flush();
+ return 0;
+}
+
+static void intel_i810_cleanup(void)
+{
+ writel(0, intel_i810_private.registers+I810_PGETBL_CTL);
+ readl(intel_i810_private.registers); /* PCI Posting. */
+ iounmap(intel_i810_private.registers);
+}
+
+static void intel_i810_tlbflush(struct agp_memory *mem)
+{
+ return;
+}
+
+static void intel_i810_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ return;
+}
+
+/* Exists to support ARGB cursors */
+static void *i8xx_alloc_pages(void)
+{
+ struct page * page;
+
+ page = alloc_pages(GFP_KERNEL, 2);
+ if (page == NULL)
+ return NULL;
+
+ if (change_page_attr(page, 4, PAGE_KERNEL_NOCACHE) < 0) {
+ global_flush_tlb();
+ __free_page(page);
+ return NULL;
+ }
+ global_flush_tlb();
+ get_page(page);
+ SetPageLocked(page);
+ atomic_inc(&agp_bridge->current_memory_agp);
+ return page_address(page);
+}
+
+static void i8xx_destroy_pages(void *addr)
+{
+ struct page *page;
+
+ if (addr == NULL)
+ return;
+
+ page = virt_to_page(addr);
+ change_page_attr(page, 4, PAGE_KERNEL);
+ global_flush_tlb();
+ put_page(page);
+ unlock_page(page);
+ free_pages((unsigned long)addr, 2);
+ atomic_dec(&agp_bridge->current_memory_agp);
+}
+
+static int intel_i810_insert_entries(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ int i, j, num_entries;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+
+ if ((pg_start + mem->page_count) > num_entries) {
+ return -EINVAL;
+ }
+ for (j = pg_start; j < (pg_start + mem->page_count); j++) {
+ if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+j)))
+ return -EBUSY;
+ }
+
+ if (type != 0 || mem->type != 0) {
+ if ((type == AGP_DCACHE_MEMORY) && (mem->type == AGP_DCACHE_MEMORY)) {
+ /* special insert */
+ global_cache_flush();
+ for (i = pg_start; i < (pg_start + mem->page_count); i++) {
+ writel((i*4096)|I810_PTE_LOCAL|I810_PTE_VALID, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+ readl(intel_i810_private.registers+I810_PTE_BASE+(i*4)); /* PCI Posting. */
+ }
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+ }
+ if((type == AGP_PHYS_MEMORY) && (mem->type == AGP_PHYS_MEMORY))
+ goto insert;
+ return -EINVAL;
+ }
+
+insert:
+ global_cache_flush();
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ writel(agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type),
+ intel_i810_private.registers+I810_PTE_BASE+(j*4));
+ readl(intel_i810_private.registers+I810_PTE_BASE+(j*4)); /* PCI Posting. */
+ }
+ global_cache_flush();
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int intel_i810_remove_entries(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ int i;
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ writel(agp_bridge->scratch_page, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+ readl(intel_i810_private.registers+I810_PTE_BASE+(i*4)); /* PCI Posting. */
+ }
+
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+/*
+ * The i810/i830 requires a physical address to program its mouse
+ * pointer into hardware.
+ * However the Xserver still writes to it through the agp aperture.
+ */
+static struct agp_memory *alloc_agpphysmem_i8xx(size_t pg_count, int type)
+{
+ struct agp_memory *new;
+ void *addr;
+
+ if (pg_count != 1 && pg_count != 4)
+ return NULL;
+
+ switch (pg_count) {
+ case 1: addr = agp_bridge->driver->agp_alloc_page(agp_bridge);
+ break;
+ case 4:
+ /* kludge to get 4 physical pages for ARGB cursor */
+ addr = i8xx_alloc_pages();
+ break;
+ default:
+ return NULL;
+ }
+
+ if (addr == NULL)
+ return NULL;
+
+ new = agp_create_memory(pg_count);
+ if (new == NULL)
+ return NULL;
+
+ new->memory[0] = virt_to_phys(addr);
+ if (pg_count == 4) {
+ /* kludge to get 4 physical pages for ARGB cursor */
+ new->memory[1] = new->memory[0] + PAGE_SIZE;
+ new->memory[2] = new->memory[1] + PAGE_SIZE;
+ new->memory[3] = new->memory[2] + PAGE_SIZE;
+ }
+ new->page_count = pg_count;
+ new->num_scratch_pages = pg_count;
+ new->type = AGP_PHYS_MEMORY;
+ new->physical = new->memory[0];
+ return new;
+}
+
+static struct agp_memory *intel_i810_alloc_by_type(size_t pg_count, int type)
+{
+ struct agp_memory *new;
+
+ if (type == AGP_DCACHE_MEMORY) {
+ if (pg_count != intel_i810_private.num_dcache_entries)
+ return NULL;
+
+ new = agp_create_memory(1);
+ if (new == NULL)
+ return NULL;
+
+ new->type = AGP_DCACHE_MEMORY;
+ new->page_count = pg_count;
+ new->num_scratch_pages = 0;
+ vfree(new->memory);
+ return new;
+ }
+ if (type == AGP_PHYS_MEMORY)
+ return alloc_agpphysmem_i8xx(pg_count, type);
+
+ return NULL;
+}
+
+static void intel_i810_free_by_type(struct agp_memory *curr)
+{
+ agp_free_key(curr->key);
+ if(curr->type == AGP_PHYS_MEMORY) {
+ if (curr->page_count == 4)
+ i8xx_destroy_pages(phys_to_virt(curr->memory[0]));
+ else
+ agp_bridge->driver->agp_destroy_page(
+ phys_to_virt(curr->memory[0]));
+ vfree(curr->memory);
+ }
+ kfree(curr);
+}
+
+static unsigned long intel_i810_mask_memory(struct agp_bridge_data *bridge,
+ unsigned long addr, int type)
+{
+ /* Type checking must be done elsewhere */
+ return addr | bridge->driver->masks[type].mask;
+}
+
+static struct aper_size_info_fixed intel_i830_sizes[] =
+{
+ {128, 32768, 5},
+ /* The 64M mode still requires a 128k gatt */
+ {64, 16384, 5},
+ {256, 65536, 6},
+};
+
+static struct _intel_i830_private {
+ struct pci_dev *i830_dev; /* device one */
+ volatile u8 __iomem *registers;
+ volatile u32 __iomem *gtt; /* I915G */
+ int gtt_entries;
+} intel_i830_private;
+
+static void intel_i830_init_gtt_entries(void)
+{
+ u16 gmch_ctrl;
+ int gtt_entries;
+ u8 rdct;
+ int local = 0;
+ static const int ddt[4] = { 0, 16, 32, 64 };
+ int size;
+
+ pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+
+ /* We obtain the size of the GTT, which is also stored (for some
+ * reason) at the top of stolen memory. Then we add 4KB to that
+ * for the video BIOS popup, which is also stored in there. */
+ size = agp_bridge->driver->fetch_size() + 4;
+
+ if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82830_HB ||
+ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82845G_HB) {
+ switch (gmch_ctrl & I830_GMCH_GMS_MASK) {
+ case I830_GMCH_GMS_STOLEN_512:
+ gtt_entries = KB(512) - KB(size);
+ break;
+ case I830_GMCH_GMS_STOLEN_1024:
+ gtt_entries = MB(1) - KB(size);
+ break;
+ case I830_GMCH_GMS_STOLEN_8192:
+ gtt_entries = MB(8) - KB(size);
+ break;
+ case I830_GMCH_GMS_LOCAL:
+ rdct = readb(intel_i830_private.registers+I830_RDRAM_CHANNEL_TYPE);
+ gtt_entries = (I830_RDRAM_ND(rdct) + 1) *
+ MB(ddt[I830_RDRAM_DDT(rdct)]);
+ local = 1;
+ break;
+ default:
+ gtt_entries = 0;
+ break;
+ }
+ } else {
+ switch (gmch_ctrl & I830_GMCH_GMS_MASK) {
+ case I855_GMCH_GMS_STOLEN_1M:
+ gtt_entries = MB(1) - KB(size);
+ break;
+ case I855_GMCH_GMS_STOLEN_4M:
+ gtt_entries = MB(4) - KB(size);
+ break;
+ case I855_GMCH_GMS_STOLEN_8M:
+ gtt_entries = MB(8) - KB(size);
+ break;
+ case I855_GMCH_GMS_STOLEN_16M:
+ gtt_entries = MB(16) - KB(size);
+ break;
+ case I855_GMCH_GMS_STOLEN_32M:
+ gtt_entries = MB(32) - KB(size);
+ break;
+ case I915_GMCH_GMS_STOLEN_48M:
+ /* Check it's really I915G */
+ if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915G_HB ||
+ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915GM_HB)
+ gtt_entries = MB(48) - KB(size);
+ else
+ gtt_entries = 0;
+ break;
+ case I915_GMCH_GMS_STOLEN_64M:
+ /* Check it's really I915G */
+ if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915G_HB ||
+ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915GM_HB)
+ gtt_entries = MB(64) - KB(size);
+ else
+ gtt_entries = 0;
+ default:
+ gtt_entries = 0;
+ break;
+ }
+ }
+ if (gtt_entries > 0)
+ printk(KERN_INFO PFX "Detected %dK %s memory.\n",
+ gtt_entries / KB(1), local ? "local" : "stolen");
+ else
+ printk(KERN_INFO PFX
+ "No pre-allocated video memory detected.\n");
+ gtt_entries /= KB(4);
+
+ intel_i830_private.gtt_entries = gtt_entries;
+}
+
+/* The intel i830 automatically initializes the agp aperture during POST.
+ * Use the memory already set aside for in the GTT.
+ */
+static int intel_i830_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ int page_order;
+ struct aper_size_info_fixed *size;
+ int num_entries;
+ u32 temp;
+
+ size = agp_bridge->current_size;
+ page_order = size->page_order;
+ num_entries = size->num_entries;
+ agp_bridge->gatt_table_real = NULL;
+
+ pci_read_config_dword(intel_i830_private.i830_dev,I810_MMADDR,&temp);
+ temp &= 0xfff80000;
+
+ intel_i830_private.registers = ioremap(temp,128 * 4096);
+ if (!intel_i830_private.registers)
+ return -ENOMEM;
+
+ temp = readl(intel_i830_private.registers+I810_PGETBL_CTL) & 0xfffff000;
+ global_cache_flush(); /* FIXME: ?? */
+
+ /* we have to call this as early as possible after the MMIO base address is known */
+ intel_i830_init_gtt_entries();
+
+ agp_bridge->gatt_table = NULL;
+
+ agp_bridge->gatt_bus_addr = temp;
+
+ return 0;
+}
+
+/* Return the gatt table to a sane state. Use the top of stolen
+ * memory for the GTT.
+ */
+static int intel_i830_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ return 0;
+}
+
+static int intel_i830_fetch_size(void)
+{
+ u16 gmch_ctrl;
+ struct aper_size_info_fixed *values;
+
+ values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+ if (agp_bridge->dev->device != PCI_DEVICE_ID_INTEL_82830_HB &&
+ agp_bridge->dev->device != PCI_DEVICE_ID_INTEL_82845G_HB) {
+ /* 855GM/852GM/865G has 128MB aperture size */
+ agp_bridge->previous_size = agp_bridge->current_size = (void *) values;
+ agp_bridge->aperture_size_idx = 0;
+ return values[0].size;
+ }
+
+ pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+
+ if ((gmch_ctrl & I830_GMCH_MEM_MASK) == I830_GMCH_MEM_128M) {
+ agp_bridge->previous_size = agp_bridge->current_size = (void *) values;
+ agp_bridge->aperture_size_idx = 0;
+ return values[0].size;
+ } else {
+ agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + 1);
+ agp_bridge->aperture_size_idx = 1;
+ return values[1].size;
+ }
+
+ return 0;
+}
+
+static int intel_i830_configure(void)
+{
+ struct aper_size_info_fixed *current_size;
+ u32 temp;
+ u16 gmch_ctrl;
+ int i;
+
+ current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+ pci_read_config_dword(intel_i830_private.i830_dev,I810_GMADDR,&temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+ gmch_ctrl |= I830_GMCH_ENABLED;
+ pci_write_config_word(agp_bridge->dev,I830_GMCH_CTRL,gmch_ctrl);
+
+ writel(agp_bridge->gatt_bus_addr|I810_PGETBL_ENABLED, intel_i830_private.registers+I810_PGETBL_CTL);
+ readl(intel_i830_private.registers+I810_PGETBL_CTL); /* PCI Posting. */
+
+ if (agp_bridge->driver->needs_scratch_page) {
+ for (i = intel_i830_private.gtt_entries; i < current_size->num_entries; i++) {
+ writel(agp_bridge->scratch_page, intel_i830_private.registers+I810_PTE_BASE+(i*4));
+ readl(intel_i830_private.registers+I810_PTE_BASE+(i*4)); /* PCI Posting. */
+ }
+ }
+
+ global_cache_flush();
+ return 0;
+}
+
+static void intel_i830_cleanup(void)
+{
+ iounmap(intel_i830_private.registers);
+}
+
+static int intel_i830_insert_entries(struct agp_memory *mem,off_t pg_start, int type)
+{
+ int i,j,num_entries;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+
+ if (pg_start < intel_i830_private.gtt_entries) {
+ printk (KERN_DEBUG PFX "pg_start == 0x%.8lx,intel_i830_private.gtt_entries == 0x%.8x\n",
+ pg_start,intel_i830_private.gtt_entries);
+
+ printk (KERN_INFO PFX "Trying to insert into local/stolen memory\n");
+ return -EINVAL;
+ }
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ /* The i830 can't check the GTT for entries since its read only,
+ * depend on the caller to make the correct offset decisions.
+ */
+
+ if ((type != 0 && type != AGP_PHYS_MEMORY) ||
+ (mem->type != 0 && mem->type != AGP_PHYS_MEMORY))
+ return -EINVAL;
+
+ global_cache_flush(); /* FIXME: Necessary ?*/
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ writel(agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type),
+ intel_i830_private.registers+I810_PTE_BASE+(j*4));
+ readl(intel_i830_private.registers+I810_PTE_BASE+(j*4)); /* PCI Posting. */
+ }
+
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int intel_i830_remove_entries(struct agp_memory *mem,off_t pg_start,
+ int type)
+{
+ int i;
+
+ global_cache_flush();
+
+ if (pg_start < intel_i830_private.gtt_entries) {
+ printk (KERN_INFO PFX "Trying to disable local/stolen memory\n");
+ return -EINVAL;
+ }
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ writel(agp_bridge->scratch_page, intel_i830_private.registers+I810_PTE_BASE+(i*4));
+ readl(intel_i830_private.registers+I810_PTE_BASE+(i*4)); /* PCI Posting. */
+ }
+
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static struct agp_memory *intel_i830_alloc_by_type(size_t pg_count,int type)
+{
+ if (type == AGP_PHYS_MEMORY)
+ return alloc_agpphysmem_i8xx(pg_count, type);
+
+ /* always return NULL for other allocation types for now */
+ return NULL;
+}
+
+static int intel_i915_configure(void)
+{
+ struct aper_size_info_fixed *current_size;
+ u32 temp;
+ u16 gmch_ctrl;
+ int i;
+
+ current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+ pci_read_config_dword(intel_i830_private.i830_dev, I915_GMADDR, &temp);
+
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+ gmch_ctrl |= I830_GMCH_ENABLED;
+ pci_write_config_word(agp_bridge->dev,I830_GMCH_CTRL,gmch_ctrl);
+
+ writel(agp_bridge->gatt_bus_addr|I810_PGETBL_ENABLED, intel_i830_private.registers+I810_PGETBL_CTL);
+ readl(intel_i830_private.registers+I810_PGETBL_CTL); /* PCI Posting. */
+
+ if (agp_bridge->driver->needs_scratch_page) {
+ for (i = intel_i830_private.gtt_entries; i < current_size->num_entries; i++) {
+ writel(agp_bridge->scratch_page, intel_i830_private.gtt+i);
+ readl(intel_i830_private.gtt+i); /* PCI Posting. */
+ }
+ }
+
+ global_cache_flush();
+ return 0;
+}
+
+static void intel_i915_cleanup(void)
+{
+ iounmap(intel_i830_private.gtt);
+ iounmap(intel_i830_private.registers);
+}
+
+static int intel_i915_insert_entries(struct agp_memory *mem,off_t pg_start,
+ int type)
+{
+ int i,j,num_entries;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+
+ if (pg_start < intel_i830_private.gtt_entries) {
+ printk (KERN_DEBUG PFX "pg_start == 0x%.8lx,intel_i830_private.gtt_entries == 0x%.8x\n",
+ pg_start,intel_i830_private.gtt_entries);
+
+ printk (KERN_INFO PFX "Trying to insert into local/stolen memory\n");
+ return -EINVAL;
+ }
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ /* The i830 can't check the GTT for entries since its read only,
+ * depend on the caller to make the correct offset decisions.
+ */
+
+ if ((type != 0 && type != AGP_PHYS_MEMORY) ||
+ (mem->type != 0 && mem->type != AGP_PHYS_MEMORY))
+ return -EINVAL;
+
+ global_cache_flush();
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ writel(agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type), intel_i830_private.gtt+j);
+ readl(intel_i830_private.gtt+j); /* PCI Posting. */
+ }
+
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int intel_i915_remove_entries(struct agp_memory *mem,off_t pg_start,
+ int type)
+{
+ int i;
+
+ global_cache_flush();
+
+ if (pg_start < intel_i830_private.gtt_entries) {
+ printk (KERN_INFO PFX "Trying to disable local/stolen memory\n");
+ return -EINVAL;
+ }
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ writel(agp_bridge->scratch_page, intel_i830_private.gtt+i);
+ readl(intel_i830_private.gtt+i);
+ }
+
+ global_cache_flush();
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int intel_i915_fetch_size(void)
+{
+ struct aper_size_info_fixed *values;
+ u32 temp, offset = 0;
+
+#define I915_256MB_ADDRESS_MASK (1<<27)
+
+ values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+ pci_read_config_dword(intel_i830_private.i830_dev, I915_GMADDR, &temp);
+ if (temp & I915_256MB_ADDRESS_MASK)
+ offset = 0; /* 128MB aperture */
+ else
+ offset = 2; /* 256MB aperture */
+ agp_bridge->previous_size = agp_bridge->current_size = (void *)(values + offset);
+ return values[offset].size;
+}
+
+/* The intel i915 automatically initializes the agp aperture during POST.
+ * Use the memory already set aside for in the GTT.
+ */
+static int intel_i915_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ int page_order;
+ struct aper_size_info_fixed *size;
+ int num_entries;
+ u32 temp, temp2;
+
+ size = agp_bridge->current_size;
+ page_order = size->page_order;
+ num_entries = size->num_entries;
+ agp_bridge->gatt_table_real = NULL;
+
+ pci_read_config_dword(intel_i830_private.i830_dev, I915_MMADDR, &temp);
+ pci_read_config_dword(intel_i830_private.i830_dev, I915_PTEADDR,&temp2);
+
+ intel_i830_private.gtt = ioremap(temp2, 256 * 1024);
+ if (!intel_i830_private.gtt)
+ return -ENOMEM;
+
+ temp &= 0xfff80000;
+
+ intel_i830_private.registers = ioremap(temp,128 * 4096);
+ if (!intel_i830_private.registers)
+ return -ENOMEM;
+
+ temp = readl(intel_i830_private.registers+I810_PGETBL_CTL) & 0xfffff000;
+ global_cache_flush(); /* FIXME: ? */
+
+ /* we have to call this as early as possible after the MMIO base address is known */
+ intel_i830_init_gtt_entries();
+
+ agp_bridge->gatt_table = NULL;
+
+ agp_bridge->gatt_bus_addr = temp;
+
+ return 0;
+}
+
+static int intel_fetch_size(void)
+{
+ int i;
+ u16 temp;
+ struct aper_size_info_16 *values;
+
+ pci_read_config_word(agp_bridge->dev, INTEL_APSIZE, &temp);
+ values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static int __intel_8xx_fetch_size(u8 temp)
+{
+ int i;
+ struct aper_size_info_8 *values;
+
+ values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+ return 0;
+}
+
+static int intel_8xx_fetch_size(void)
+{
+ u8 temp;
+
+ pci_read_config_byte(agp_bridge->dev, INTEL_APSIZE, &temp);
+ return __intel_8xx_fetch_size(temp);
+}
+
+static int intel_815_fetch_size(void)
+{
+ u8 temp;
+
+ /* Intel 815 chipsets have a _weird_ APSIZE register with only
+ * one non-reserved bit, so mask the others out ... */
+ pci_read_config_byte(agp_bridge->dev, INTEL_APSIZE, &temp);
+ temp &= (1 << 3);
+
+ return __intel_8xx_fetch_size(temp);
+}
+
+static void intel_tlbflush(struct agp_memory *mem)
+{
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2200);
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+}
+
+
+static void intel_8xx_tlbflush(struct agp_memory *mem)
+{
+ u32 temp;
+ pci_read_config_dword(agp_bridge->dev, INTEL_AGPCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, temp & ~(1 << 7));
+ pci_read_config_dword(agp_bridge->dev, INTEL_AGPCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, temp | (1 << 7));
+}
+
+
+static void intel_cleanup(void)
+{
+ u16 temp;
+ struct aper_size_info_16 *previous_size;
+
+ previous_size = A_SIZE_16(agp_bridge->previous_size);
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+ pci_write_config_word(agp_bridge->dev, INTEL_APSIZE, previous_size->size_value);
+}
+
+
+static void intel_8xx_cleanup(void)
+{
+ u16 temp;
+ struct aper_size_info_8 *previous_size;
+
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, previous_size->size_value);
+}
+
+
+static int intel_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_16 *current_size;
+
+ current_size = A_SIZE_16(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_word(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+
+ /* paccfg/nbxcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG,
+ (temp2 & ~(1 << 10)) | (1 << 9));
+ /* clear any possible error conditions */
+ pci_write_config_byte(agp_bridge->dev, INTEL_ERRSTS + 1, 7);
+ return 0;
+}
+
+static int intel_815_configure(void)
+{
+ u32 temp, addr;
+ u8 temp2;
+ struct aper_size_info_8 *current_size;
+
+ /* attbase - aperture base */
+ /* the Intel 815 chipset spec. says that bits 29-31 in the
+ * ATTBASE register are reserved -> try not to write them */
+ if (agp_bridge->gatt_bus_addr & INTEL_815_ATTBASE_MASK) {
+ printk (KERN_EMERG PFX "gatt bus addr too high");
+ return -EINVAL;
+ }
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE,
+ current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ pci_read_config_dword(agp_bridge->dev, INTEL_ATTBASE, &addr);
+ addr &= INTEL_815_ATTBASE_MASK;
+ addr |= agp_bridge->gatt_bus_addr;
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* apcont */
+ pci_read_config_byte(agp_bridge->dev, INTEL_815_APCONT, &temp2);
+ pci_write_config_byte(agp_bridge->dev, INTEL_815_APCONT, temp2 | (1 << 1));
+
+ /* clear any possible error conditions */
+ /* Oddness : this chipset seems to have no ERRSTS register ! */
+ return 0;
+}
+
+static void intel_820_tlbflush(struct agp_memory *mem)
+{
+ return;
+}
+
+static void intel_820_cleanup(void)
+{
+ u8 temp;
+ struct aper_size_info_8 *previous_size;
+
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ pci_read_config_byte(agp_bridge->dev, INTEL_I820_RDCR, &temp);
+ pci_write_config_byte(agp_bridge->dev, INTEL_I820_RDCR,
+ temp & ~(1 << 1));
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE,
+ previous_size->size_value);
+}
+
+
+static int intel_820_configure(void)
+{
+ u32 temp;
+ u8 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* global enable aperture access */
+ /* This flag is not accessed through MCHCFG register as in */
+ /* i850 chipset. */
+ pci_read_config_byte(agp_bridge->dev, INTEL_I820_RDCR, &temp2);
+ pci_write_config_byte(agp_bridge->dev, INTEL_I820_RDCR, temp2 | (1 << 1));
+ /* clear any possible AGP-related error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I820_ERRSTS, 0x001c);
+ return 0;
+}
+
+static int intel_840_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* mcgcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_I840_MCHCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_I840_MCHCFG, temp2 | (1 << 9));
+ /* clear any possible error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I840_ERRSTS, 0xc000);
+ return 0;
+}
+
+static int intel_845_configure(void)
+{
+ u32 temp;
+ u8 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* agpm */
+ pci_read_config_byte(agp_bridge->dev, INTEL_I845_AGPM, &temp2);
+ pci_write_config_byte(agp_bridge->dev, INTEL_I845_AGPM, temp2 | (1 << 1));
+ /* clear any possible error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I845_ERRSTS, 0x001c);
+ return 0;
+}
+
+static int intel_850_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* mcgcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_I850_MCHCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_I850_MCHCFG, temp2 | (1 << 9));
+ /* clear any possible AGP-related error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I850_ERRSTS, 0x001c);
+ return 0;
+}
+
+static int intel_860_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* mcgcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_I860_MCHCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_I860_MCHCFG, temp2 | (1 << 9));
+ /* clear any possible AGP-related error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I860_ERRSTS, 0xf700);
+ return 0;
+}
+
+static int intel_830mp_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* gmch */
+ pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp2 | (1 << 9));
+ /* clear any possible AGP-related error conditions */
+ pci_write_config_word(agp_bridge->dev, INTEL_I830_ERRSTS, 0x1c);
+ return 0;
+}
+
+static int intel_7505_configure(void)
+{
+ u32 temp;
+ u16 temp2;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture base */
+ pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+ /* agpctrl */
+ pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+ /* mchcfg */
+ pci_read_config_word(agp_bridge->dev, INTEL_I7505_MCHCFG, &temp2);
+ pci_write_config_word(agp_bridge->dev, INTEL_I7505_MCHCFG, temp2 | (1 << 9));
+
+ return 0;
+}
+
+/* Setup function */
+static struct gatt_mask intel_generic_masks[] =
+{
+ {.mask = 0x00000017, .type = 0}
+};
+
+static struct aper_size_info_8 intel_815_sizes[2] =
+{
+ {64, 16384, 4, 0},
+ {32, 8192, 3, 8},
+};
+
+static struct aper_size_info_8 intel_8xx_sizes[7] =
+{
+ {256, 65536, 6, 0},
+ {128, 32768, 5, 32},
+ {64, 16384, 4, 48},
+ {32, 8192, 3, 56},
+ {16, 4096, 2, 60},
+ {8, 2048, 1, 62},
+ {4, 1024, 0, 63}
+};
+
+static struct aper_size_info_16 intel_generic_sizes[7] =
+{
+ {256, 65536, 6, 0},
+ {128, 32768, 5, 32},
+ {64, 16384, 4, 48},
+ {32, 8192, 3, 56},
+ {16, 4096, 2, 60},
+ {8, 2048, 1, 62},
+ {4, 1024, 0, 63}
+};
+
+static struct aper_size_info_8 intel_830mp_sizes[4] =
+{
+ {256, 65536, 6, 0},
+ {128, 32768, 5, 32},
+ {64, 16384, 4, 48},
+ {32, 8192, 3, 56}
+};
+
+static struct agp_bridge_driver intel_generic_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_generic_sizes,
+ .size_type = U16_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_configure,
+ .fetch_size = intel_fetch_size,
+ .cleanup = intel_cleanup,
+ .tlb_flush = intel_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_810_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_i810_sizes,
+ .size_type = FIXED_APER_SIZE,
+ .num_aperture_sizes = 2,
+ .needs_scratch_page = TRUE,
+ .configure = intel_i810_configure,
+ .fetch_size = intel_i810_fetch_size,
+ .cleanup = intel_i810_cleanup,
+ .tlb_flush = intel_i810_tlbflush,
+ .mask_memory = intel_i810_mask_memory,
+ .masks = intel_i810_masks,
+ .agp_enable = intel_i810_agp_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = intel_i810_insert_entries,
+ .remove_memory = intel_i810_remove_entries,
+ .alloc_by_type = intel_i810_alloc_by_type,
+ .free_by_type = intel_i810_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_815_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_815_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 2,
+ .configure = intel_815_configure,
+ .fetch_size = intel_815_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_830_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_i830_sizes,
+ .size_type = FIXED_APER_SIZE,
+ .num_aperture_sizes = 3,
+ .needs_scratch_page = TRUE,
+ .configure = intel_i830_configure,
+ .fetch_size = intel_i830_fetch_size,
+ .cleanup = intel_i830_cleanup,
+ .tlb_flush = intel_i810_tlbflush,
+ .mask_memory = intel_i810_mask_memory,
+ .masks = intel_i810_masks,
+ .agp_enable = intel_i810_agp_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = intel_i830_create_gatt_table,
+ .free_gatt_table = intel_i830_free_gatt_table,
+ .insert_memory = intel_i830_insert_entries,
+ .remove_memory = intel_i830_remove_entries,
+ .alloc_by_type = intel_i830_alloc_by_type,
+ .free_by_type = intel_i810_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_820_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_820_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_820_cleanup,
+ .tlb_flush = intel_820_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_830mp_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_830mp_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 4,
+ .configure = intel_830mp_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_840_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_840_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_845_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_845_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_850_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_850_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_860_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_860_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_915_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_i830_sizes,
+ .size_type = FIXED_APER_SIZE,
+ .num_aperture_sizes = 3,
+ .needs_scratch_page = TRUE,
+ .configure = intel_i915_configure,
+ .fetch_size = intel_i915_fetch_size,
+ .cleanup = intel_i915_cleanup,
+ .tlb_flush = intel_i810_tlbflush,
+ .mask_memory = intel_i810_mask_memory,
+ .masks = intel_i810_masks,
+ .agp_enable = intel_i810_agp_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = intel_i915_create_gatt_table,
+ .free_gatt_table = intel_i830_free_gatt_table,
+ .insert_memory = intel_i915_insert_entries,
+ .remove_memory = intel_i915_remove_entries,
+ .alloc_by_type = intel_i830_alloc_by_type,
+ .free_by_type = intel_i810_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+
+static struct agp_bridge_driver intel_7505_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = intel_8xx_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = intel_7505_configure,
+ .fetch_size = intel_8xx_fetch_size,
+ .cleanup = intel_8xx_cleanup,
+ .tlb_flush = intel_8xx_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = intel_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static int find_i810(u16 device)
+{
+ struct pci_dev *i810_dev;
+
+ i810_dev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
+ if (!i810_dev)
+ return 0;
+ intel_i810_private.i810_dev = i810_dev;
+ return 1;
+}
+
+static int find_i830(u16 device)
+{
+ struct pci_dev *i830_dev;
+
+ i830_dev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
+ if (i830_dev && PCI_FUNC(i830_dev->devfn) != 0) {
+ i830_dev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ device, i830_dev);
+ }
+
+ if (!i830_dev)
+ return 0;
+
+ intel_i830_private.i830_dev = i830_dev;
+ return 1;
+}
+
+static int __devinit agp_intel_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ char *name = "(unknown)";
+ u8 cap_ptr = 0;
+ struct resource *r;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_INTEL_82443LX_0:
+ bridge->driver = &intel_generic_driver;
+ name = "440LX";
+ break;
+ case PCI_DEVICE_ID_INTEL_82443BX_0:
+ bridge->driver = &intel_generic_driver;
+ name = "440BX";
+ break;
+ case PCI_DEVICE_ID_INTEL_82443GX_0:
+ bridge->driver = &intel_generic_driver;
+ name = "440GX";
+ break;
+ case PCI_DEVICE_ID_INTEL_82810_MC1:
+ name = "i810";
+ if (!find_i810(PCI_DEVICE_ID_INTEL_82810_IG1))
+ goto fail;
+ bridge->driver = &intel_810_driver;
+ break;
+ case PCI_DEVICE_ID_INTEL_82810_MC3:
+ name = "i810 DC100";
+ if (!find_i810(PCI_DEVICE_ID_INTEL_82810_IG3))
+ goto fail;
+ bridge->driver = &intel_810_driver;
+ break;
+ case PCI_DEVICE_ID_INTEL_82810E_MC:
+ name = "i810 E";
+ if (!find_i810(PCI_DEVICE_ID_INTEL_82810E_IG))
+ goto fail;
+ bridge->driver = &intel_810_driver;
+ break;
+ case PCI_DEVICE_ID_INTEL_82815_MC:
+ /*
+ * The i815 can operate either as an i810 style
+ * integrated device, or as an AGP4X motherboard.
+ */
+ if (find_i810(PCI_DEVICE_ID_INTEL_82815_CGC))
+ bridge->driver = &intel_810_driver;
+ else
+ bridge->driver = &intel_815_driver;
+ name = "i815";
+ break;
+ case PCI_DEVICE_ID_INTEL_82820_HB:
+ case PCI_DEVICE_ID_INTEL_82820_UP_HB:
+ bridge->driver = &intel_820_driver;
+ name = "i820";
+ break;
+ case PCI_DEVICE_ID_INTEL_82830_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82830_CGC)) {
+ bridge->driver = &intel_830_driver;
+ } else {
+ bridge->driver = &intel_830mp_driver;
+ }
+ name = "830M";
+ break;
+ case PCI_DEVICE_ID_INTEL_82840_HB:
+ bridge->driver = &intel_840_driver;
+ name = "i840";
+ break;
+ case PCI_DEVICE_ID_INTEL_82845_HB:
+ bridge->driver = &intel_845_driver;
+ name = "i845";
+ break;
+ case PCI_DEVICE_ID_INTEL_82845G_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82845G_IG)) {
+ bridge->driver = &intel_830_driver;
+ } else {
+ bridge->driver = &intel_845_driver;
+ }
+ name = "845G";
+ break;
+ case PCI_DEVICE_ID_INTEL_82850_HB:
+ bridge->driver = &intel_850_driver;
+ name = "i850";
+ break;
+ case PCI_DEVICE_ID_INTEL_82855PM_HB:
+ bridge->driver = &intel_845_driver;
+ name = "855PM";
+ break;
+ case PCI_DEVICE_ID_INTEL_82855GM_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82855GM_IG)) {
+ bridge->driver = &intel_830_driver;
+ name = "855";
+ } else {
+ bridge->driver = &intel_845_driver;
+ name = "855GM";
+ }
+ break;
+ case PCI_DEVICE_ID_INTEL_82860_HB:
+ bridge->driver = &intel_860_driver;
+ name = "i860";
+ break;
+ case PCI_DEVICE_ID_INTEL_82865_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82865_IG)) {
+ bridge->driver = &intel_830_driver;
+ } else {
+ bridge->driver = &intel_845_driver;
+ }
+ name = "865";
+ break;
+ case PCI_DEVICE_ID_INTEL_82875_HB:
+ bridge->driver = &intel_845_driver;
+ name = "i875";
+ break;
+ case PCI_DEVICE_ID_INTEL_82915G_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82915G_IG)) {
+ bridge->driver = &intel_915_driver;
+ } else {
+ bridge->driver = &intel_845_driver;
+ }
+ name = "915G";
+ break;
+ case PCI_DEVICE_ID_INTEL_82915GM_HB:
+ if (find_i830(PCI_DEVICE_ID_INTEL_82915GM_IG)) {
+ bridge->driver = &intel_915_driver;
+ } else {
+ bridge->driver = &intel_845_driver;
+ }
+ name = "915GM";
+ break;
+ case PCI_DEVICE_ID_INTEL_7505_0:
+ bridge->driver = &intel_7505_driver;
+ name = "E7505";
+ break;
+ case PCI_DEVICE_ID_INTEL_7205_0:
+ bridge->driver = &intel_7505_driver;
+ name = "E7205";
+ break;
+ default:
+ if (cap_ptr)
+ printk(KERN_WARNING PFX "Unsupported Intel chipset (device id: %04x)\n",
+ pdev->device);
+ agp_put_bridge(bridge);
+ return -ENODEV;
+ };
+
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ if (bridge->driver == &intel_810_driver)
+ bridge->dev_private_data = &intel_i810_private;
+ else if (bridge->driver == &intel_830_driver)
+ bridge->dev_private_data = &intel_i830_private;
+
+ printk(KERN_INFO PFX "Detected an Intel %s Chipset.\n", name);
+
+ /*
+ * The following fixes the case where the BIOS has "forgotten" to
+ * provide an address range for the GART.
+ * 20030610 - hamish@zot.org
+ */
+ r = &pdev->resource[0];
+ if (!r->start && r->end) {
+ if(pci_assign_resource(pdev, 0)) {
+ printk(KERN_ERR PFX "could not assign resource 0\n");
+ agp_put_bridge(bridge);
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * If the device has not been properly setup, the following will catch
+ * the problem and should stop the system from crashing.
+ * 20030610 - hamish@zot.org
+ */
+ if (pci_enable_device(pdev)) {
+ printk(KERN_ERR PFX "Unable to Enable PCI device\n");
+ agp_put_bridge(bridge);
+ return -ENODEV;
+ }
+
+ /* Fill in the mode register */
+ if (cap_ptr) {
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+ }
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+
+fail:
+ printk(KERN_ERR PFX "Detected an Intel %s chipset, "
+ "but could not find the secondary device.\n", name);
+ agp_put_bridge(bridge);
+ return -ENODEV;
+}
+
+static void __devexit agp_intel_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+
+ if (intel_i810_private.i810_dev)
+ pci_dev_put(intel_i810_private.i810_dev);
+ if (intel_i830_private.i830_dev)
+ pci_dev_put(intel_i830_private.i830_dev);
+
+ agp_put_bridge(bridge);
+}
+
+static int agp_intel_resume(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ pci_restore_state(pdev);
+
+ if (bridge->driver == &intel_generic_driver)
+ intel_configure();
+ else if (bridge->driver == &intel_850_driver)
+ intel_850_configure();
+ else if (bridge->driver == &intel_845_driver)
+ intel_845_configure();
+ else if (bridge->driver == &intel_830mp_driver)
+ intel_830mp_configure();
+ else if (bridge->driver == &intel_915_driver)
+ intel_i915_configure();
+ else if (bridge->driver == &intel_830_driver)
+ intel_i830_configure();
+ else if (bridge->driver == &intel_810_driver)
+ intel_i810_configure();
+
+ return 0;
+}
+
+static struct pci_device_id agp_intel_pci_table[] = {
+#define ID(x) \
+ { \
+ .class = (PCI_CLASS_BRIDGE_HOST << 8), \
+ .class_mask = ~0, \
+ .vendor = PCI_VENDOR_ID_INTEL, \
+ .device = x, \
+ .subvendor = PCI_ANY_ID, \
+ .subdevice = PCI_ANY_ID, \
+ }
+ ID(PCI_DEVICE_ID_INTEL_82443LX_0),
+ ID(PCI_DEVICE_ID_INTEL_82443BX_0),
+ ID(PCI_DEVICE_ID_INTEL_82443GX_0),
+ ID(PCI_DEVICE_ID_INTEL_82810_MC1),
+ ID(PCI_DEVICE_ID_INTEL_82810_MC3),
+ ID(PCI_DEVICE_ID_INTEL_82810E_MC),
+ ID(PCI_DEVICE_ID_INTEL_82815_MC),
+ ID(PCI_DEVICE_ID_INTEL_82820_HB),
+ ID(PCI_DEVICE_ID_INTEL_82820_UP_HB),
+ ID(PCI_DEVICE_ID_INTEL_82830_HB),
+ ID(PCI_DEVICE_ID_INTEL_82840_HB),
+ ID(PCI_DEVICE_ID_INTEL_82845_HB),
+ ID(PCI_DEVICE_ID_INTEL_82845G_HB),
+ ID(PCI_DEVICE_ID_INTEL_82850_HB),
+ ID(PCI_DEVICE_ID_INTEL_82855PM_HB),
+ ID(PCI_DEVICE_ID_INTEL_82855GM_HB),
+ ID(PCI_DEVICE_ID_INTEL_82860_HB),
+ ID(PCI_DEVICE_ID_INTEL_82865_HB),
+ ID(PCI_DEVICE_ID_INTEL_82875_HB),
+ ID(PCI_DEVICE_ID_INTEL_7505_0),
+ ID(PCI_DEVICE_ID_INTEL_7205_0),
+ ID(PCI_DEVICE_ID_INTEL_82915G_HB),
+ ID(PCI_DEVICE_ID_INTEL_82915GM_HB),
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_intel_pci_table);
+
+static struct pci_driver agp_intel_pci_driver = {
+ .name = "agpgart-intel",
+ .id_table = agp_intel_pci_table,
+ .probe = agp_intel_probe,
+ .remove = __devexit_p(agp_intel_remove),
+ .resume = agp_intel_resume,
+};
+
+static int __init agp_intel_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_intel_pci_driver);
+}
+
+static void __exit agp_intel_cleanup(void)
+{
+ pci_unregister_driver(&agp_intel_pci_driver);
+}
+
+module_init(agp_intel_init);
+module_exit(agp_intel_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/isoch.c b/drivers/char/agp/isoch.c
new file mode 100644
index 000000000000..c9ac731504f2
--- /dev/null
+++ b/drivers/char/agp/isoch.c
@@ -0,0 +1,470 @@
+/*
+ * Setup routines for AGP 3.5 compliant bridges.
+ */
+
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/agp_backend.h>
+#include <linux/module.h>
+
+#include "agp.h"
+
+/* Generic AGP 3.5 enabling routines */
+
+struct agp_3_5_dev {
+ struct list_head list;
+ u8 capndx;
+ u32 maxbw;
+ struct pci_dev *dev;
+};
+
+static void agp_3_5_dev_list_insert(struct list_head *head, struct list_head *new)
+{
+ struct agp_3_5_dev *cur, *n = list_entry(new, struct agp_3_5_dev, list);
+ struct list_head *pos;
+
+ list_for_each(pos, head) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+ if(cur->maxbw > n->maxbw)
+ break;
+ }
+ list_add_tail(new, pos);
+}
+
+static void agp_3_5_dev_list_sort(struct agp_3_5_dev *list, unsigned int ndevs)
+{
+ struct agp_3_5_dev *cur;
+ struct pci_dev *dev;
+ struct list_head *pos, *tmp, *head = &list->list, *start = head->next;
+ u32 nistat;
+
+ INIT_LIST_HEAD(head);
+
+ for (pos=start; pos!=head; ) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+ dev = cur->dev;
+
+ pci_read_config_dword(dev, cur->capndx+AGPNISTAT, &nistat);
+ cur->maxbw = (nistat >> 16) & 0xff;
+
+ tmp = pos;
+ pos = pos->next;
+ agp_3_5_dev_list_insert(head, tmp);
+ }
+}
+
+/*
+ * Initialize all isochronous transfer parameters for an AGP 3.0
+ * node (i.e. a host bridge in combination with the adapters
+ * lying behind it...)
+ */
+
+static int agp_3_5_isochronous_node_enable(struct agp_bridge_data *bridge,
+ struct agp_3_5_dev *dev_list, unsigned int ndevs)
+{
+ /*
+ * Convenience structure to make the calculations clearer
+ * here. The field names come straight from the AGP 3.0 spec.
+ */
+ struct isoch_data {
+ u32 maxbw;
+ u32 n;
+ u32 y;
+ u32 l;
+ u32 rq;
+ struct agp_3_5_dev *dev;
+ };
+
+ struct pci_dev *td = bridge->dev, *dev;
+ struct list_head *head = &dev_list->list, *pos;
+ struct agp_3_5_dev *cur;
+ struct isoch_data *master, target;
+ unsigned int cdev = 0;
+ u32 mnistat, tnistat, tstatus, mcmd;
+ u16 tnicmd, mnicmd;
+ u8 mcapndx;
+ u32 tot_bw = 0, tot_n = 0, tot_rq = 0, y_max, rq_isoch, rq_async;
+ u32 step, rem, rem_isoch, rem_async;
+ int ret = 0;
+
+ /*
+ * We'll work with an array of isoch_data's (one for each
+ * device in dev_list) throughout this function.
+ */
+ if ((master = kmalloc(ndevs * sizeof(*master), GFP_KERNEL)) == NULL) {
+ ret = -ENOMEM;
+ goto get_out;
+ }
+
+ /*
+ * Sort the device list by maxbw. We need to do this because the
+ * spec suggests that the devices with the smallest requirements
+ * have their resources allocated first, with all remaining resources
+ * falling to the device with the largest requirement.
+ *
+ * We don't exactly do this, we divide target resources by ndevs
+ * and split them amongst the AGP 3.0 devices. The remainder of such
+ * division operations are dropped on the last device, sort of like
+ * the spec mentions it should be done.
+ *
+ * We can't do this sort when we initially construct the dev_list
+ * because we don't know until this function whether isochronous
+ * transfers are enabled and consequently whether maxbw will mean
+ * anything.
+ */
+ agp_3_5_dev_list_sort(dev_list, ndevs);
+
+ pci_read_config_dword(td, bridge->capndx+AGPNISTAT, &tnistat);
+ pci_read_config_dword(td, bridge->capndx+AGPSTAT, &tstatus);
+
+ /* Extract power-on defaults from the target */
+ target.maxbw = (tnistat >> 16) & 0xff;
+ target.n = (tnistat >> 8) & 0xff;
+ target.y = (tnistat >> 6) & 0x3;
+ target.l = (tnistat >> 3) & 0x7;
+ target.rq = (tstatus >> 24) & 0xff;
+
+ y_max = target.y;
+
+ /*
+ * Extract power-on defaults for each device in dev_list. Along
+ * the way, calculate the total isochronous bandwidth required
+ * by these devices and the largest requested payload size.
+ */
+ list_for_each(pos, head) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+ dev = cur->dev;
+
+ mcapndx = cur->capndx;
+
+ pci_read_config_dword(dev, cur->capndx+AGPNISTAT, &mnistat);
+
+ master[cdev].maxbw = (mnistat >> 16) & 0xff;
+ master[cdev].n = (mnistat >> 8) & 0xff;
+ master[cdev].y = (mnistat >> 6) & 0x3;
+ master[cdev].dev = cur;
+
+ tot_bw += master[cdev].maxbw;
+ y_max = max(y_max, master[cdev].y);
+
+ cdev++;
+ }
+
+ /* Check if this configuration has any chance of working */
+ if (tot_bw > target.maxbw) {
+ printk(KERN_ERR PFX "isochronous bandwidth required "
+ "by AGP 3.0 devices exceeds that which is supported by "
+ "the AGP 3.0 bridge!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+
+ target.y = y_max;
+
+ /*
+ * Write the calculated payload size into the target's NICMD
+ * register. Doing this directly effects the ISOCH_N value
+ * in the target's NISTAT register, so we need to do this now
+ * to get an accurate value for ISOCH_N later.
+ */
+ pci_read_config_word(td, bridge->capndx+AGPNICMD, &tnicmd);
+ tnicmd &= ~(0x3 << 6);
+ tnicmd |= target.y << 6;
+ pci_write_config_word(td, bridge->capndx+AGPNICMD, tnicmd);
+
+ /* Reread the target's ISOCH_N */
+ pci_read_config_dword(td, bridge->capndx+AGPNISTAT, &tnistat);
+ target.n = (tnistat >> 8) & 0xff;
+
+ /* Calculate the minimum ISOCH_N needed by each master */
+ for (cdev=0; cdev<ndevs; cdev++) {
+ master[cdev].y = target.y;
+ master[cdev].n = master[cdev].maxbw / (master[cdev].y + 1);
+
+ tot_n += master[cdev].n;
+ }
+
+ /* Exit if the minimal ISOCH_N allocation among the masters is more
+ * than the target can handle. */
+ if (tot_n > target.n) {
+ printk(KERN_ERR PFX "number of isochronous "
+ "transactions per period required by AGP 3.0 devices "
+ "exceeds that which is supported by the AGP 3.0 "
+ "bridge!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+
+ /* Calculate left over ISOCH_N capability in the target. We'll give
+ * this to the hungriest device (as per the spec) */
+ rem = target.n - tot_n;
+
+ /*
+ * Calculate the minimum isochronous RQ depth needed by each master.
+ * Along the way, distribute the extra ISOCH_N capability calculated
+ * above.
+ */
+ for (cdev=0; cdev<ndevs; cdev++) {
+ /*
+ * This is a little subtle. If ISOCH_Y > 64B, then ISOCH_Y
+ * byte isochronous writes will be broken into 64B pieces.
+ * This means we need to budget more RQ depth to account for
+ * these kind of writes (each isochronous write is actually
+ * many writes on the AGP bus).
+ */
+ master[cdev].rq = master[cdev].n;
+ if(master[cdev].y > 0x1)
+ master[cdev].rq *= (1 << (master[cdev].y - 1));
+
+ tot_rq += master[cdev].rq;
+
+ if (cdev == ndevs-1)
+ master[cdev].n += rem;
+ }
+
+ /* Figure the number of isochronous and asynchronous RQ slots the
+ * target is providing. */
+ rq_isoch = (target.y > 0x1) ? target.n * (1 << (target.y - 1)) : target.n;
+ rq_async = target.rq - rq_isoch;
+
+ /* Exit if the minimal RQ needs of the masters exceeds what the target
+ * can provide. */
+ if (tot_rq > rq_isoch) {
+ printk(KERN_ERR PFX "number of request queue slots "
+ "required by the isochronous bandwidth requested by "
+ "AGP 3.0 devices exceeds the number provided by the "
+ "AGP 3.0 bridge!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+
+ /* Calculate asynchronous RQ capability in the target (per master) as
+ * well as the total number of leftover isochronous RQ slots. */
+ step = rq_async / ndevs;
+ rem_async = step + (rq_async % ndevs);
+ rem_isoch = rq_isoch - tot_rq;
+
+ /* Distribute the extra RQ slots calculated above and write our
+ * isochronous settings out to the actual devices. */
+ for (cdev=0; cdev<ndevs; cdev++) {
+ cur = master[cdev].dev;
+ dev = cur->dev;
+
+ mcapndx = cur->capndx;
+
+ master[cdev].rq += (cdev == ndevs - 1)
+ ? (rem_async + rem_isoch) : step;
+
+ pci_read_config_word(dev, cur->capndx+AGPNICMD, &mnicmd);
+ pci_read_config_dword(dev, cur->capndx+AGPCMD, &mcmd);
+
+ mnicmd &= ~(0xff << 8);
+ mnicmd &= ~(0x3 << 6);
+ mcmd &= ~(0xff << 24);
+
+ mnicmd |= master[cdev].n << 8;
+ mnicmd |= master[cdev].y << 6;
+ mcmd |= master[cdev].rq << 24;
+
+ pci_write_config_dword(dev, cur->capndx+AGPCMD, mcmd);
+ pci_write_config_word(dev, cur->capndx+AGPNICMD, mnicmd);
+ }
+
+free_and_exit:
+ kfree(master);
+
+get_out:
+ return ret;
+}
+
+/*
+ * This function basically allocates request queue slots among the
+ * AGP 3.0 systems in nonisochronous nodes. The algorithm is
+ * pretty stupid, divide the total number of RQ slots provided by the
+ * target by ndevs. Distribute this many slots to each AGP 3.0 device,
+ * giving any left over slots to the last device in dev_list.
+ */
+static void agp_3_5_nonisochronous_node_enable(struct agp_bridge_data *bridge,
+ struct agp_3_5_dev *dev_list, unsigned int ndevs)
+{
+ struct agp_3_5_dev *cur;
+ struct list_head *head = &dev_list->list, *pos;
+ u32 tstatus, mcmd;
+ u32 trq, mrq, rem;
+ unsigned int cdev = 0;
+
+ pci_read_config_dword(bridge->dev, bridge->capndx+AGPSTAT, &tstatus);
+
+ trq = (tstatus >> 24) & 0xff;
+ mrq = trq / ndevs;
+
+ rem = mrq + (trq % ndevs);
+
+ for (pos=head->next; cdev<ndevs; cdev++, pos=pos->next) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+
+ pci_read_config_dword(cur->dev, cur->capndx+AGPCMD, &mcmd);
+ mcmd &= ~(0xff << 24);
+ mcmd |= ((cdev == ndevs - 1) ? rem : mrq) << 24;
+ pci_write_config_dword(cur->dev, cur->capndx+AGPCMD, mcmd);
+ }
+}
+
+/*
+ * Fully configure and enable an AGP 3.0 host bridge and all the devices
+ * lying behind it.
+ */
+int agp_3_5_enable(struct agp_bridge_data *bridge)
+{
+ struct pci_dev *td = bridge->dev, *dev = NULL;
+ u8 mcapndx;
+ u32 isoch, arqsz;
+ u32 tstatus, mstatus, ncapid;
+ u32 mmajor;
+ u16 mpstat;
+ struct agp_3_5_dev *dev_list, *cur;
+ struct list_head *head, *pos;
+ unsigned int ndevs = 0;
+ int ret = 0;
+
+ /* Extract some power-on defaults from the target */
+ pci_read_config_dword(td, bridge->capndx+AGPSTAT, &tstatus);
+ isoch = (tstatus >> 17) & 0x1;
+ if (isoch == 0) /* isoch xfers not available, bail out. */
+ return -ENODEV;
+
+ arqsz = (tstatus >> 13) & 0x7;
+
+ /*
+ * Allocate a head for our AGP 3.5 device list
+ * (multiple AGP v3 devices are allowed behind a single bridge).
+ */
+ if ((dev_list = kmalloc(sizeof(*dev_list), GFP_KERNEL)) == NULL) {
+ ret = -ENOMEM;
+ goto get_out;
+ }
+ head = &dev_list->list;
+ INIT_LIST_HEAD(head);
+
+ /* Find all AGP devices, and add them to dev_list. */
+ for_each_pci_dev(dev) {
+ mcapndx = pci_find_capability(dev, PCI_CAP_ID_AGP);
+ if (mcapndx == 0)
+ continue;
+
+ switch ((dev->class >>8) & 0xff00) {
+ case 0x0600: /* Bridge */
+ /* Skip bridges. We should call this function for each one. */
+ continue;
+
+ case 0x0001: /* Unclassified device */
+ /* Don't know what this is, but log it for investigation. */
+ if (mcapndx != 0) {
+ printk (KERN_INFO PFX "Wacky, found unclassified AGP device. %x:%x\n",
+ dev->vendor, dev->device);
+ }
+ continue;
+
+ case 0x0300: /* Display controller */
+ case 0x0400: /* Multimedia controller */
+ if((cur = kmalloc(sizeof(*cur), GFP_KERNEL)) == NULL) {
+ ret = -ENOMEM;
+ goto free_and_exit;
+ }
+ cur->dev = dev;
+
+ pos = &cur->list;
+ list_add(pos, head);
+ ndevs++;
+ continue;
+
+ default:
+ continue;
+ }
+ }
+
+ /*
+ * Take an initial pass through the devices lying behind our host
+ * bridge. Make sure each one is actually an AGP 3.0 device, otherwise
+ * exit with an error message. Along the way store the AGP 3.0
+ * cap_ptr for each device
+ */
+ list_for_each(pos, head) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+ dev = cur->dev;
+
+ pci_read_config_word(dev, PCI_STATUS, &mpstat);
+ if ((mpstat & PCI_STATUS_CAP_LIST) == 0)
+ continue;
+
+ pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &mcapndx);
+ if (mcapndx != 0) {
+ do {
+ pci_read_config_dword(dev, mcapndx, &ncapid);
+ if ((ncapid & 0xff) != 2)
+ mcapndx = (ncapid >> 8) & 0xff;
+ }
+ while (((ncapid & 0xff) != 2) && (mcapndx != 0));
+ }
+
+ if (mcapndx == 0) {
+ printk(KERN_ERR PFX "woah! Non-AGP device "
+ "found on the secondary bus of an AGP 3.5 bridge!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+
+ mmajor = (ncapid >> AGP_MAJOR_VERSION_SHIFT) & 0xf;
+ if (mmajor < 3) {
+ printk(KERN_ERR PFX "woah! AGP 2.0 device "
+ "found on the secondary bus of an AGP 3.5 "
+ "bridge operating with AGP 3.0 electricals!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+
+ cur->capndx = mcapndx;
+
+ pci_read_config_dword(dev, cur->capndx+AGPSTAT, &mstatus);
+
+ if (((mstatus >> 3) & 0x1) == 0) {
+ printk(KERN_ERR PFX "woah! AGP 3.x device "
+ "not operating in AGP 3.x mode found on the "
+ "secondary bus of an AGP 3.5 bridge operating "
+ "with AGP 3.0 electricals!\n");
+ ret = -ENODEV;
+ goto free_and_exit;
+ }
+ }
+
+ /*
+ * Call functions to divide target resources amongst the AGP 3.0
+ * masters. This process is dramatically different depending on
+ * whether isochronous transfers are supported.
+ */
+ if (isoch) {
+ ret = agp_3_5_isochronous_node_enable(bridge, dev_list, ndevs);
+ if (ret) {
+ printk(KERN_INFO PFX "Something bad happened setting "
+ "up isochronous xfers. Falling back to "
+ "non-isochronous xfer mode.\n");
+ } else {
+ goto free_and_exit;
+ }
+ }
+ agp_3_5_nonisochronous_node_enable(bridge, dev_list, ndevs);
+
+free_and_exit:
+ /* Be sure to free the dev_list */
+ for (pos=head->next; pos!=head; ) {
+ cur = list_entry(pos, struct agp_3_5_dev, list);
+
+ pos = pos->next;
+ kfree(cur);
+ }
+ kfree(dev_list);
+
+get_out:
+ return ret;
+}
+
diff --git a/drivers/char/agp/nvidia-agp.c b/drivers/char/agp/nvidia-agp.c
new file mode 100644
index 000000000000..4f7a3e8bc919
--- /dev/null
+++ b/drivers/char/agp/nvidia-agp.c
@@ -0,0 +1,424 @@
+/*
+ * Nvidia AGPGART routines.
+ * Based upon a 2.4 agpgart diff by the folks from NVIDIA, and hacked up
+ * to work in 2.5 by Dave Jones <davej@codemonkey.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+/* NVIDIA registers */
+#define NVIDIA_0_APSIZE 0x80
+#define NVIDIA_1_WBC 0xf0
+#define NVIDIA_2_GARTCTRL 0xd0
+#define NVIDIA_2_APBASE 0xd8
+#define NVIDIA_2_APLIMIT 0xdc
+#define NVIDIA_2_ATTBASE(i) (0xe0 + (i) * 4)
+#define NVIDIA_3_APBASE 0x50
+#define NVIDIA_3_APLIMIT 0x54
+
+
+static struct _nvidia_private {
+ struct pci_dev *dev_1;
+ struct pci_dev *dev_2;
+ struct pci_dev *dev_3;
+ volatile u32 __iomem *aperture;
+ int num_active_entries;
+ off_t pg_offset;
+ u32 wbc_mask;
+} nvidia_private;
+
+
+static int nvidia_fetch_size(void)
+{
+ int i;
+ u8 size_value;
+ struct aper_size_info_8 *values;
+
+ pci_read_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE, &size_value);
+ size_value &= 0x0f;
+ values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (size_value == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+#define SYSCFG 0xC0010010
+#define IORR_BASE0 0xC0010016
+#define IORR_MASK0 0xC0010017
+#define AMD_K7_NUM_IORR 2
+
+static int nvidia_init_iorr(u32 base, u32 size)
+{
+ u32 base_hi, base_lo;
+ u32 mask_hi, mask_lo;
+ u32 sys_hi, sys_lo;
+ u32 iorr_addr, free_iorr_addr;
+
+ /* Find the iorr that is already used for the base */
+ /* If not found, determine the uppermost available iorr */
+ free_iorr_addr = AMD_K7_NUM_IORR;
+ for(iorr_addr = 0; iorr_addr < AMD_K7_NUM_IORR; iorr_addr++) {
+ rdmsr(IORR_BASE0 + 2 * iorr_addr, base_lo, base_hi);
+ rdmsr(IORR_MASK0 + 2 * iorr_addr, mask_lo, mask_hi);
+
+ if ((base_lo & 0xfffff000) == (base & 0xfffff000))
+ break;
+
+ if ((mask_lo & 0x00000800) == 0)
+ free_iorr_addr = iorr_addr;
+ }
+
+ if (iorr_addr >= AMD_K7_NUM_IORR) {
+ iorr_addr = free_iorr_addr;
+ if (iorr_addr >= AMD_K7_NUM_IORR)
+ return -EINVAL;
+ }
+ base_hi = 0x0;
+ base_lo = (base & ~0xfff) | 0x18;
+ mask_hi = 0xf;
+ mask_lo = ((~(size - 1)) & 0xfffff000) | 0x800;
+ wrmsr(IORR_BASE0 + 2 * iorr_addr, base_lo, base_hi);
+ wrmsr(IORR_MASK0 + 2 * iorr_addr, mask_lo, mask_hi);
+
+ rdmsr(SYSCFG, sys_lo, sys_hi);
+ sys_lo |= 0x00100000;
+ wrmsr(SYSCFG, sys_lo, sys_hi);
+
+ return 0;
+}
+
+static int nvidia_configure(void)
+{
+ int i, rc, num_dirs;
+ u32 apbase, aplimit;
+ struct aper_size_info_8 *current_size;
+ u32 temp;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE,
+ current_size->size_value);
+
+ /* address to map to */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &apbase);
+ apbase &= PCI_BASE_ADDRESS_MEM_MASK;
+ agp_bridge->gart_bus_addr = apbase;
+ aplimit = apbase + (current_size->size * 1024 * 1024) - 1;
+ pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_APBASE, apbase);
+ pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_APLIMIT, aplimit);
+ pci_write_config_dword(nvidia_private.dev_3, NVIDIA_3_APBASE, apbase);
+ pci_write_config_dword(nvidia_private.dev_3, NVIDIA_3_APLIMIT, aplimit);
+ if (0 != (rc = nvidia_init_iorr(apbase, current_size->size * 1024 * 1024)))
+ return rc;
+
+ /* directory size is 64k */
+ num_dirs = current_size->size / 64;
+ nvidia_private.num_active_entries = current_size->num_entries;
+ nvidia_private.pg_offset = 0;
+ if (num_dirs == 0) {
+ num_dirs = 1;
+ nvidia_private.num_active_entries /= (64 / current_size->size);
+ nvidia_private.pg_offset = (apbase & (64 * 1024 * 1024 - 1) &
+ ~(current_size->size * 1024 * 1024 - 1)) / PAGE_SIZE;
+ }
+
+ /* attbase */
+ for(i = 0; i < 8; i++) {
+ pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_ATTBASE(i),
+ (agp_bridge->gatt_bus_addr + (i % num_dirs) * 64 * 1024) | 1);
+ }
+
+ /* gtlb control */
+ pci_read_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, &temp);
+ pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, temp | 0x11);
+
+ /* gart control */
+ pci_read_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, &temp);
+ pci_write_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, temp | 0x100);
+
+ /* map aperture */
+ nvidia_private.aperture =
+ (volatile u32 __iomem *) ioremap(apbase, 33 * PAGE_SIZE);
+
+ return 0;
+}
+
+static void nvidia_cleanup(void)
+{
+ struct aper_size_info_8 *previous_size;
+ u32 temp;
+
+ /* gart control */
+ pci_read_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, &temp);
+ pci_write_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, temp & ~(0x100));
+
+ /* gtlb control */
+ pci_read_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, &temp);
+ pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, temp & ~(0x11));
+
+ /* unmap aperture */
+ iounmap((void __iomem *) nvidia_private.aperture);
+
+ /* restore previous aperture size */
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ pci_write_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE,
+ previous_size->size_value);
+
+ /* restore iorr for previous aperture size */
+ nvidia_init_iorr(agp_bridge->gart_bus_addr,
+ previous_size->size * 1024 * 1024);
+}
+
+
+/*
+ * Note we can't use the generic routines, even though they are 99% the same.
+ * Aperture sizes <64M still requires a full 64k GART directory, but
+ * only use the portion of the TLB entries that correspond to the apertures
+ * alignment inside the surrounding 64M block.
+ */
+extern int agp_memory_reserved;
+
+static int nvidia_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i, j;
+
+ if ((type != 0) || (mem->type != 0))
+ return -EINVAL;
+
+ if ((pg_start + mem->page_count) >
+ (nvidia_private.num_active_entries - agp_memory_reserved/PAGE_SIZE))
+ return -EINVAL;
+
+ for(j = pg_start; j < (pg_start + mem->page_count); j++) {
+ if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+nvidia_private.pg_offset+j)))
+ return -EBUSY;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ writel(agp_bridge->driver->mask_memory(agp_bridge,
+ mem->memory[i], mem->type),
+ agp_bridge->gatt_table+nvidia_private.pg_offset+j);
+ readl(agp_bridge->gatt_table+nvidia_private.pg_offset+j); /* PCI Posting. */
+ }
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+
+static int nvidia_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i;
+
+ if ((type != 0) || (mem->type != 0))
+ return -EINVAL;
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++)
+ writel(agp_bridge->scratch_page, agp_bridge->gatt_table+nvidia_private.pg_offset+i);
+
+ agp_bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+
+static void nvidia_tlbflush(struct agp_memory *mem)
+{
+ unsigned long end;
+ u32 wbc_reg, temp;
+ int i;
+
+ /* flush chipset */
+ if (nvidia_private.wbc_mask) {
+ pci_read_config_dword(nvidia_private.dev_1, NVIDIA_1_WBC, &wbc_reg);
+ wbc_reg |= nvidia_private.wbc_mask;
+ pci_write_config_dword(nvidia_private.dev_1, NVIDIA_1_WBC, wbc_reg);
+
+ end = jiffies + 3*HZ;
+ do {
+ pci_read_config_dword(nvidia_private.dev_1,
+ NVIDIA_1_WBC, &wbc_reg);
+ if ((signed)(end - jiffies) <= 0) {
+ printk(KERN_ERR PFX
+ "TLB flush took more than 3 seconds.\n");
+ }
+ } while (wbc_reg & nvidia_private.wbc_mask);
+ }
+
+ /* flush TLB entries */
+ for(i = 0; i < 32 + 1; i++)
+ temp = readl(nvidia_private.aperture+(i * PAGE_SIZE / sizeof(u32)));
+ for(i = 0; i < 32 + 1; i++)
+ temp = readl(nvidia_private.aperture+(i * PAGE_SIZE / sizeof(u32)));
+}
+
+
+static struct aper_size_info_8 nvidia_generic_sizes[5] =
+{
+ {512, 131072, 7, 0},
+ {256, 65536, 6, 8},
+ {128, 32768, 5, 12},
+ {64, 16384, 4, 14},
+ /* The 32M mode still requires a 64k gatt */
+ {32, 16384, 4, 15}
+};
+
+
+static struct gatt_mask nvidia_generic_masks[] =
+{
+ { .mask = 1, .type = 0}
+};
+
+
+struct agp_bridge_driver nvidia_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = nvidia_generic_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 5,
+ .configure = nvidia_configure,
+ .fetch_size = nvidia_fetch_size,
+ .cleanup = nvidia_cleanup,
+ .tlb_flush = nvidia_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = nvidia_generic_masks,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = nvidia_insert_memory,
+ .remove_memory = nvidia_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static int __devinit agp_nvidia_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+
+ nvidia_private.dev_1 =
+ pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(0, 1));
+ nvidia_private.dev_2 =
+ pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(0, 2));
+ nvidia_private.dev_3 =
+ pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(30, 0));
+
+ if (!nvidia_private.dev_1 || !nvidia_private.dev_2 || !nvidia_private.dev_3) {
+ printk(KERN_INFO PFX "Detected an NVIDIA nForce/nForce2 "
+ "chipset, but could not find the secondary devices.\n");
+ return -ENODEV;
+ }
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ switch (pdev->device) {
+ case PCI_DEVICE_ID_NVIDIA_NFORCE:
+ printk(KERN_INFO PFX "Detected NVIDIA nForce chipset\n");
+ nvidia_private.wbc_mask = 0x00010000;
+ break;
+ case PCI_DEVICE_ID_NVIDIA_NFORCE2:
+ printk(KERN_INFO PFX "Detected NVIDIA nForce2 chipset\n");
+ nvidia_private.wbc_mask = 0x80000000;
+ break;
+ default:
+ printk(KERN_ERR PFX "Unsupported NVIDIA chipset (device id: %04x)\n",
+ pdev->device);
+ return -ENODEV;
+ }
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &nvidia_driver;
+ bridge->dev_private_data = &nvidia_private,
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS,
+ &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_nvidia_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_nvidia_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_NVIDIA,
+ .device = PCI_DEVICE_ID_NVIDIA_NFORCE,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_NVIDIA,
+ .device = PCI_DEVICE_ID_NVIDIA_NFORCE2,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_nvidia_pci_table);
+
+static struct pci_driver agp_nvidia_pci_driver = {
+ .name = "agpgart-nvidia",
+ .id_table = agp_nvidia_pci_table,
+ .probe = agp_nvidia_probe,
+ .remove = agp_nvidia_remove,
+};
+
+static int __init agp_nvidia_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_nvidia_pci_driver);
+}
+
+static void __exit agp_nvidia_cleanup(void)
+{
+ pci_unregister_driver(&agp_nvidia_pci_driver);
+}
+
+module_init(agp_nvidia_init);
+module_exit(agp_nvidia_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
+MODULE_AUTHOR("NVIDIA Corporation");
+
diff --git a/drivers/char/agp/sgi-agp.c b/drivers/char/agp/sgi-agp.c
new file mode 100644
index 000000000000..4b3eda267976
--- /dev/null
+++ b/drivers/char/agp/sgi-agp.c
@@ -0,0 +1,331 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2003-2005 Silicon Graphics, Inc. All Rights Reserved.
+ */
+
+/*
+ * SGI TIOCA AGPGART routines.
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/pcidev.h>
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/tioca_provider.h>
+#include "agp.h"
+
+extern int agp_memory_reserved;
+extern uint32_t tioca_gart_found;
+extern struct list_head tioca_list;
+static struct agp_bridge_data **sgi_tioca_agp_bridges;
+
+/*
+ * The aperature size and related information is set up at TIOCA init time.
+ * Values for this table will be extracted and filled in at
+ * sgi_tioca_fetch_size() time.
+ */
+
+static struct aper_size_info_fixed sgi_tioca_sizes[] = {
+ {0, 0, 0},
+};
+
+static void *sgi_tioca_alloc_page(struct agp_bridge_data *bridge)
+{
+ struct page *page;
+ int nid;
+ struct tioca_kernel *info =
+ (struct tioca_kernel *)bridge->dev_private_data;
+
+ nid = info->ca_closest_node;
+ page = alloc_pages_node(nid, GFP_KERNEL, 0);
+ if (page == NULL) {
+ return 0;
+ }
+
+ get_page(page);
+ SetPageLocked(page);
+ atomic_inc(&agp_bridge->current_memory_agp);
+ return page_address(page);
+}
+
+/*
+ * Flush GART tlb's. Cannot selectively flush based on memory so the mem
+ * arg is ignored.
+ */
+
+static void sgi_tioca_tlbflush(struct agp_memory *mem)
+{
+ tioca_tlbflush(mem->bridge->dev_private_data);
+}
+
+/*
+ * Given an address of a host physical page, turn it into a valid gart
+ * entry.
+ */
+static unsigned long
+sgi_tioca_mask_memory(struct agp_bridge_data *bridge,
+ unsigned long addr, int type)
+{
+ return tioca_physpage_to_gart(addr);
+}
+
+static void sgi_tioca_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ tioca_fastwrite_enable(bridge->dev_private_data);
+}
+
+/*
+ * sgi_tioca_configure() doesn't have anything to do since the base CA driver
+ * has alreay set up the GART.
+ */
+
+static int sgi_tioca_configure(void)
+{
+ return 0;
+}
+
+/*
+ * Determine gfx aperature size. This has already been determined by the
+ * CA driver init, so just need to set agp_bridge values accordingly.
+ */
+
+static int sgi_tioca_fetch_size(void)
+{
+ struct tioca_kernel *info =
+ (struct tioca_kernel *)agp_bridge->dev_private_data;
+
+ sgi_tioca_sizes[0].size = info->ca_gfxap_size / MB(1);
+ sgi_tioca_sizes[0].num_entries = info->ca_gfxgart_entries;
+
+ return sgi_tioca_sizes[0].size;
+}
+
+static int sgi_tioca_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct tioca_kernel *info =
+ (struct tioca_kernel *)bridge->dev_private_data;
+
+ bridge->gatt_table_real = (u32 *) info->ca_gfxgart;
+ bridge->gatt_table = bridge->gatt_table_real;
+ bridge->gatt_bus_addr = info->ca_gfxgart_base;
+
+ return 0;
+}
+
+static int sgi_tioca_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ return 0;
+}
+
+static int sgi_tioca_insert_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ int num_entries;
+ size_t i;
+ off_t j;
+ void *temp;
+ struct agp_bridge_data *bridge;
+
+ bridge = mem->bridge;
+ if (!bridge)
+ return -EINVAL;
+
+ temp = bridge->current_size;
+
+ switch (bridge->driver->size_type) {
+ case U8_APER_SIZE:
+ num_entries = A_SIZE_8(temp)->num_entries;
+ break;
+ case U16_APER_SIZE:
+ num_entries = A_SIZE_16(temp)->num_entries;
+ break;
+ case U32_APER_SIZE:
+ num_entries = A_SIZE_32(temp)->num_entries;
+ break;
+ case FIXED_APER_SIZE:
+ num_entries = A_SIZE_FIX(temp)->num_entries;
+ break;
+ case LVL2_APER_SIZE:
+ return -EINVAL;
+ break;
+ default:
+ num_entries = 0;
+ break;
+ }
+
+ num_entries -= agp_memory_reserved / PAGE_SIZE;
+ if (num_entries < 0)
+ num_entries = 0;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+
+ while (j < (pg_start + mem->page_count)) {
+ if (*(bridge->gatt_table + j))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ bridge->driver->cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ *(bridge->gatt_table + j) =
+ bridge->driver->mask_memory(bridge, mem->memory[i],
+ mem->type);
+ }
+
+ bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static int sgi_tioca_remove_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ size_t i;
+ struct agp_bridge_data *bridge;
+
+ bridge = mem->bridge;
+ if (!bridge)
+ return -EINVAL;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ *(bridge->gatt_table + i) = 0;
+ }
+
+ bridge->driver->tlb_flush(mem);
+ return 0;
+}
+
+static void sgi_tioca_cache_flush(void)
+{
+}
+
+/*
+ * Cleanup. Nothing to do as the CA driver owns the GART.
+ */
+
+static void sgi_tioca_cleanup(void)
+{
+}
+
+static struct agp_bridge_data *sgi_tioca_find_bridge(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge;
+
+ list_for_each_entry(bridge, &agp_bridges, list) {
+ if (bridge->dev->bus == pdev->bus)
+ break;
+ }
+ return bridge;
+}
+
+struct agp_bridge_driver sgi_tioca_driver = {
+ .owner = THIS_MODULE,
+ .size_type = U16_APER_SIZE,
+ .configure = sgi_tioca_configure,
+ .fetch_size = sgi_tioca_fetch_size,
+ .cleanup = sgi_tioca_cleanup,
+ .tlb_flush = sgi_tioca_tlbflush,
+ .mask_memory = sgi_tioca_mask_memory,
+ .agp_enable = sgi_tioca_agp_enable,
+ .cache_flush = sgi_tioca_cache_flush,
+ .create_gatt_table = sgi_tioca_create_gatt_table,
+ .free_gatt_table = sgi_tioca_free_gatt_table,
+ .insert_memory = sgi_tioca_insert_memory,
+ .remove_memory = sgi_tioca_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = sgi_tioca_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+ .cant_use_aperture = 1,
+ .needs_scratch_page = 0,
+ .num_aperture_sizes = 1,
+};
+
+static int __devinit agp_sgi_init(void)
+{
+ unsigned int j;
+ struct tioca_kernel *info;
+ struct pci_dev *pdev = NULL;
+
+ if (tioca_gart_found)
+ printk(KERN_INFO PFX "SGI TIO CA GART driver initialized.\n");
+ else
+ return 0;
+
+ sgi_tioca_agp_bridges =
+ (struct agp_bridge_data **)kmalloc(tioca_gart_found *
+ sizeof(struct agp_bridge_data *),
+ GFP_KERNEL);
+
+ j = 0;
+ list_for_each_entry(info, &tioca_list, ca_list) {
+ struct list_head *tmp;
+ list_for_each(tmp, info->ca_devices) {
+ u8 cap_ptr;
+ pdev = pci_dev_b(tmp);
+ if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8))
+ continue;
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ continue;
+ }
+ sgi_tioca_agp_bridges[j] = agp_alloc_bridge();
+ printk(KERN_INFO PFX "bridge %d = 0x%p\n", j,
+ sgi_tioca_agp_bridges[j]);
+ if (sgi_tioca_agp_bridges[j]) {
+ sgi_tioca_agp_bridges[j]->dev = pdev;
+ sgi_tioca_agp_bridges[j]->dev_private_data = info;
+ sgi_tioca_agp_bridges[j]->driver = &sgi_tioca_driver;
+ sgi_tioca_agp_bridges[j]->gart_bus_addr =
+ info->ca_gfxap_base;
+ sgi_tioca_agp_bridges[j]->mode = (0x7D << 24) | /* 126 requests */
+ (0x1 << 9) | /* SBA supported */
+ (0x1 << 5) | /* 64-bit addresses supported */
+ (0x1 << 4) | /* FW supported */
+ (0x1 << 3) | /* AGP 3.0 mode */
+ 0x2; /* 8x transfer only */
+ sgi_tioca_agp_bridges[j]->current_size =
+ sgi_tioca_agp_bridges[j]->previous_size =
+ (void *)&sgi_tioca_sizes[0];
+ agp_add_bridge(sgi_tioca_agp_bridges[j]);
+ }
+ j++;
+ }
+
+ agp_find_bridge = &sgi_tioca_find_bridge;
+ return 0;
+}
+
+static void __devexit agp_sgi_cleanup(void)
+{
+ if(sgi_tioca_agp_bridges)
+ kfree(sgi_tioca_agp_bridges);
+ sgi_tioca_agp_bridges=NULL;
+}
+
+module_init(agp_sgi_init);
+module_exit(agp_sgi_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/sis-agp.c b/drivers/char/agp/sis-agp.c
new file mode 100644
index 000000000000..cfccacb2a647
--- /dev/null
+++ b/drivers/char/agp/sis-agp.c
@@ -0,0 +1,360 @@
+/*
+ * SiS AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/delay.h>
+#include "agp.h"
+
+#define SIS_ATTBASE 0x90
+#define SIS_APSIZE 0x94
+#define SIS_TLBCNTRL 0x97
+#define SIS_TLBFLUSH 0x98
+
+static int __devinitdata agp_sis_force_delay = 0;
+static int __devinitdata agp_sis_agp_spec = -1;
+
+static int sis_fetch_size(void)
+{
+ u8 temp_size;
+ int i;
+ struct aper_size_info_8 *values;
+
+ pci_read_config_byte(agp_bridge->dev, SIS_APSIZE, &temp_size);
+ values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if ((temp_size == values[i].size_value) ||
+ ((temp_size & ~(0x03)) ==
+ (values[i].size_value & ~(0x03)))) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+static void sis_tlbflush(struct agp_memory *mem)
+{
+ pci_write_config_byte(agp_bridge->dev, SIS_TLBFLUSH, 0x02);
+}
+
+static int sis_configure(void)
+{
+ u32 temp;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+ pci_write_config_byte(agp_bridge->dev, SIS_TLBCNTRL, 0x05);
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ pci_write_config_dword(agp_bridge->dev, SIS_ATTBASE,
+ agp_bridge->gatt_bus_addr);
+ pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
+ current_size->size_value);
+ return 0;
+}
+
+static void sis_cleanup(void)
+{
+ struct aper_size_info_8 *previous_size;
+
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
+ (previous_size->size_value & ~(0x03)));
+}
+
+static void sis_delayed_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ struct pci_dev *device = NULL;
+ u32 command;
+ int rate;
+
+ printk(KERN_INFO PFX "Found an AGP %d.%d compliant device at %s.\n",
+ agp_bridge->major_version,
+ agp_bridge->minor_version,
+ pci_name(agp_bridge->dev));
+
+ pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx + PCI_AGP_STATUS, &command);
+ command = agp_collect_device_status(bridge, mode, command);
+ command |= AGPSTAT_AGP_ENABLE;
+ rate = (command & 0x7) << 2;
+
+ for_each_pci_dev(device) {
+ u8 agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+ if (!agp)
+ continue;
+
+ printk(KERN_INFO PFX "Putting AGP V3 device at %s into %dx mode\n",
+ pci_name(device), rate);
+
+ pci_write_config_dword(device, agp + PCI_AGP_COMMAND, command);
+
+ /*
+ * Weird: on some sis chipsets any rate change in the target
+ * command register triggers a 5ms screwup during which the master
+ * cannot be configured
+ */
+ if (device->device == bridge->dev->device) {
+ printk(KERN_INFO PFX "SiS delay workaround: giving bridge time to recover.\n");
+ msleep(10);
+ }
+ }
+}
+
+static struct aper_size_info_8 sis_generic_sizes[7] =
+{
+ {256, 65536, 6, 99},
+ {128, 32768, 5, 83},
+ {64, 16384, 4, 67},
+ {32, 8192, 3, 51},
+ {16, 4096, 2, 35},
+ {8, 2048, 1, 19},
+ {4, 1024, 0, 3}
+};
+
+struct agp_bridge_driver sis_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = sis_generic_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = sis_configure,
+ .fetch_size = sis_fetch_size,
+ .cleanup = sis_cleanup,
+ .tlb_flush = sis_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_device_ids sis_agp_device_ids[] __devinitdata =
+{
+ {
+ .device_id = PCI_DEVICE_ID_SI_5591_AGP,
+ .chipset_name = "5591",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_530,
+ .chipset_name = "530",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_540,
+ .chipset_name = "540",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_550,
+ .chipset_name = "550",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_620,
+ .chipset_name = "620",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_630,
+ .chipset_name = "630",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_635,
+ .chipset_name = "635",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_645,
+ .chipset_name = "645",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_646,
+ .chipset_name = "646",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_648,
+ .chipset_name = "648",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_650,
+ .chipset_name = "650",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_651,
+ .chipset_name = "651",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_655,
+ .chipset_name = "655",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_661,
+ .chipset_name = "661",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_730,
+ .chipset_name = "730",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_735,
+ .chipset_name = "735",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_740,
+ .chipset_name = "740",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_741,
+ .chipset_name = "741",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_745,
+ .chipset_name = "745",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_746,
+ .chipset_name = "746",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_SI_760,
+ .chipset_name = "760",
+ },
+ { }, /* dummy final entry, always present */
+};
+
+
+// chipsets that require the 'delay hack'
+static int sis_broken_chipsets[] __devinitdata = {
+ PCI_DEVICE_ID_SI_648,
+ PCI_DEVICE_ID_SI_746,
+ 0 // terminator
+};
+
+static void __devinit sis_get_driver(struct agp_bridge_data *bridge)
+{
+ int i;
+
+ for(i=0; sis_broken_chipsets[i]!=0; ++i)
+ if(bridge->dev->device==sis_broken_chipsets[i])
+ break;
+
+ if(sis_broken_chipsets[i] || agp_sis_force_delay)
+ sis_driver.agp_enable=sis_delayed_enable;
+
+ // sis chipsets that indicate less than agp3.5
+ // are not actually fully agp3 compliant
+ if ((agp_bridge->major_version == 3 && agp_bridge->minor_version >= 5
+ && agp_sis_agp_spec!=0) || agp_sis_agp_spec==1) {
+ sis_driver.aperture_sizes = agp3_generic_sizes;
+ sis_driver.size_type = U16_APER_SIZE;
+ sis_driver.num_aperture_sizes = AGP_GENERIC_SIZES_ENTRIES;
+ sis_driver.configure = agp3_generic_configure;
+ sis_driver.fetch_size = agp3_generic_fetch_size;
+ sis_driver.cleanup = agp3_generic_cleanup;
+ sis_driver.tlb_flush = agp3_generic_tlbflush;
+ }
+}
+
+
+static int __devinit agp_sis_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_device_ids *devs = sis_agp_device_ids;
+ struct agp_bridge_data *bridge;
+ u8 cap_ptr;
+ int j;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ /* probe for known chipsets */
+ for (j = 0; devs[j].chipset_name; j++) {
+ if (pdev->device == devs[j].device_id) {
+ printk(KERN_INFO PFX "Detected SiS %s chipset\n",
+ devs[j].chipset_name);
+ goto found;
+ }
+ }
+
+ printk(KERN_ERR PFX "Unsupported SiS chipset (device id: %04x)\n",
+ pdev->device);
+ return -ENODEV;
+
+found:
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &sis_driver;
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+
+ get_agp_version(bridge);
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+ sis_get_driver(bridge);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_sis_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_sis_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_SI,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_sis_pci_table);
+
+static struct pci_driver agp_sis_pci_driver = {
+ .name = "agpgart-sis",
+ .id_table = agp_sis_pci_table,
+ .probe = agp_sis_probe,
+ .remove = agp_sis_remove,
+};
+
+static int __init agp_sis_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_sis_pci_driver);
+}
+
+static void __exit agp_sis_cleanup(void)
+{
+ pci_unregister_driver(&agp_sis_pci_driver);
+}
+
+module_init(agp_sis_init);
+module_exit(agp_sis_cleanup);
+
+module_param(agp_sis_force_delay, bool, 0);
+MODULE_PARM_DESC(agp_sis_force_delay,"forces sis delay hack");
+module_param(agp_sis_agp_spec, int, 0);
+MODULE_PARM_DESC(agp_sis_agp_spec,"0=force sis init, 1=force generic agp3 init, default: autodetect");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/sworks-agp.c b/drivers/char/agp/sworks-agp.c
new file mode 100644
index 000000000000..bb338d9134e0
--- /dev/null
+++ b/drivers/char/agp/sworks-agp.c
@@ -0,0 +1,556 @@
+/*
+ * Serverworks AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+#define SVWRKS_COMMAND 0x04
+#define SVWRKS_APSIZE 0x10
+#define SVWRKS_MMBASE 0x14
+#define SVWRKS_CACHING 0x4b
+#define SVWRKS_AGP_ENABLE 0x60
+#define SVWRKS_FEATURE 0x68
+
+#define SVWRKS_SIZE_MASK 0xfe000000
+
+/* Memory mapped registers */
+#define SVWRKS_GART_CACHE 0x02
+#define SVWRKS_GATTBASE 0x04
+#define SVWRKS_TLBFLUSH 0x10
+#define SVWRKS_POSTFLUSH 0x14
+#define SVWRKS_DIRFLUSH 0x0c
+
+
+struct serverworks_page_map {
+ unsigned long *real;
+ unsigned long __iomem *remapped;
+};
+
+static struct _serverworks_private {
+ struct pci_dev *svrwrks_dev; /* device one */
+ volatile u8 __iomem *registers;
+ struct serverworks_page_map **gatt_pages;
+ int num_tables;
+ struct serverworks_page_map scratch_dir;
+
+ int gart_addr_ofs;
+ int mm_addr_ofs;
+} serverworks_private;
+
+static int serverworks_create_page_map(struct serverworks_page_map *page_map)
+{
+ int i;
+
+ page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+ if (page_map->real == NULL) {
+ return -ENOMEM;
+ }
+ SetPageReserved(virt_to_page(page_map->real));
+ global_cache_flush();
+ page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real),
+ PAGE_SIZE);
+ if (page_map->remapped == NULL) {
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+ page_map->real = NULL;
+ return -ENOMEM;
+ }
+ global_cache_flush();
+
+ for(i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++)
+ writel(agp_bridge->scratch_page, page_map->remapped+i);
+
+ return 0;
+}
+
+static void serverworks_free_page_map(struct serverworks_page_map *page_map)
+{
+ iounmap(page_map->remapped);
+ ClearPageReserved(virt_to_page(page_map->real));
+ free_page((unsigned long) page_map->real);
+}
+
+static void serverworks_free_gatt_pages(void)
+{
+ int i;
+ struct serverworks_page_map **tables;
+ struct serverworks_page_map *entry;
+
+ tables = serverworks_private.gatt_pages;
+ for(i = 0; i < serverworks_private.num_tables; i++) {
+ entry = tables[i];
+ if (entry != NULL) {
+ if (entry->real != NULL) {
+ serverworks_free_page_map(entry);
+ }
+ kfree(entry);
+ }
+ }
+ kfree(tables);
+}
+
+static int serverworks_create_gatt_pages(int nr_tables)
+{
+ struct serverworks_page_map **tables;
+ struct serverworks_page_map *entry;
+ int retval = 0;
+ int i;
+
+ tables = kmalloc((nr_tables + 1) * sizeof(struct serverworks_page_map *),
+ GFP_KERNEL);
+ if (tables == NULL) {
+ return -ENOMEM;
+ }
+ memset(tables, 0, sizeof(struct serverworks_page_map *) * (nr_tables + 1));
+ for (i = 0; i < nr_tables; i++) {
+ entry = kmalloc(sizeof(struct serverworks_page_map), GFP_KERNEL);
+ if (entry == NULL) {
+ retval = -ENOMEM;
+ break;
+ }
+ memset(entry, 0, sizeof(struct serverworks_page_map));
+ tables[i] = entry;
+ retval = serverworks_create_page_map(entry);
+ if (retval != 0) break;
+ }
+ serverworks_private.num_tables = nr_tables;
+ serverworks_private.gatt_pages = tables;
+
+ if (retval != 0) serverworks_free_gatt_pages();
+
+ return retval;
+}
+
+#define SVRWRKS_GET_GATT(addr) (serverworks_private.gatt_pages[\
+ GET_PAGE_DIR_IDX(addr)]->remapped)
+
+#ifndef GET_PAGE_DIR_OFF
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#endif
+
+#ifndef GET_PAGE_DIR_IDX
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+ GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#endif
+
+#ifndef GET_GATT_OFF
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#endif
+
+static int serverworks_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct aper_size_info_lvl2 *value;
+ struct serverworks_page_map page_dir;
+ int retval;
+ u32 temp;
+ int i;
+
+ value = A_SIZE_LVL2(agp_bridge->current_size);
+ retval = serverworks_create_page_map(&page_dir);
+ if (retval != 0) {
+ return retval;
+ }
+ retval = serverworks_create_page_map(&serverworks_private.scratch_dir);
+ if (retval != 0) {
+ serverworks_free_page_map(&page_dir);
+ return retval;
+ }
+ /* Create a fake scratch directory */
+ for(i = 0; i < 1024; i++) {
+ writel(agp_bridge->scratch_page, serverworks_private.scratch_dir.remapped+i);
+ writel(virt_to_phys(serverworks_private.scratch_dir.real) | 1, page_dir.remapped+i);
+ }
+
+ retval = serverworks_create_gatt_pages(value->num_entries / 1024);
+ if (retval != 0) {
+ serverworks_free_page_map(&page_dir);
+ serverworks_free_page_map(&serverworks_private.scratch_dir);
+ return retval;
+ }
+
+ agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+ agp_bridge->gatt_table = (u32 __iomem *)page_dir.remapped;
+ agp_bridge->gatt_bus_addr = virt_to_phys(page_dir.real);
+
+ /* Get the address for the gart region.
+ * This is a bus address even on the alpha, b/c its
+ * used to program the agp master not the cpu
+ */
+
+ pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* Calculate the agp offset */
+
+ for(i = 0; i < value->num_entries / 1024; i++)
+ writel(virt_to_phys(serverworks_private.gatt_pages[i]->real)|1, page_dir.remapped+i);
+
+ return 0;
+}
+
+static int serverworks_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ struct serverworks_page_map page_dir;
+
+ page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+ page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+ serverworks_free_gatt_pages();
+ serverworks_free_page_map(&page_dir);
+ serverworks_free_page_map(&serverworks_private.scratch_dir);
+ return 0;
+}
+
+static int serverworks_fetch_size(void)
+{
+ int i;
+ u32 temp;
+ u32 temp2;
+ struct aper_size_info_lvl2 *values;
+
+ values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+ pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp);
+ pci_write_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,
+ SVWRKS_SIZE_MASK);
+ pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp2);
+ pci_write_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,temp);
+ temp2 &= SVWRKS_SIZE_MASK;
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp2 == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This routine could be implemented by taking the addresses
+ * written to the GATT, and flushing them individually. However
+ * currently it just flushes the whole table. Which is probably
+ * more efficent, since agp_memory blocks can be a large number of
+ * entries.
+ */
+static void serverworks_tlbflush(struct agp_memory *temp)
+{
+ writeb(1, serverworks_private.registers+SVWRKS_POSTFLUSH);
+ while (readb(serverworks_private.registers+SVWRKS_POSTFLUSH) == 1)
+ cpu_relax();
+
+ writel(1, serverworks_private.registers+SVWRKS_DIRFLUSH);
+ while(readl(serverworks_private.registers+SVWRKS_DIRFLUSH) == 1)
+ cpu_relax();
+}
+
+static int serverworks_configure(void)
+{
+ struct aper_size_info_lvl2 *current_size;
+ u32 temp;
+ u8 enable_reg;
+ u16 cap_reg;
+
+ current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+ /* Get the memory mapped registers */
+ pci_read_config_dword(agp_bridge->dev, serverworks_private.mm_addr_ofs, &temp);
+ temp = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+ serverworks_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+ if (!serverworks_private.registers) {
+ printk (KERN_ERR PFX "Unable to ioremap() memory.\n");
+ return -ENOMEM;
+ }
+
+ writeb(0xA, serverworks_private.registers+SVWRKS_GART_CACHE);
+ readb(serverworks_private.registers+SVWRKS_GART_CACHE); /* PCI Posting. */
+
+ writel(agp_bridge->gatt_bus_addr, serverworks_private.registers+SVWRKS_GATTBASE);
+ readl(serverworks_private.registers+SVWRKS_GATTBASE); /* PCI Posting. */
+
+ cap_reg = readw(serverworks_private.registers+SVWRKS_COMMAND);
+ cap_reg &= ~0x0007;
+ cap_reg |= 0x4;
+ writew(cap_reg, serverworks_private.registers+SVWRKS_COMMAND);
+ readw(serverworks_private.registers+SVWRKS_COMMAND);
+
+ pci_read_config_byte(serverworks_private.svrwrks_dev,SVWRKS_AGP_ENABLE, &enable_reg);
+ enable_reg |= 0x1; /* Agp Enable bit */
+ pci_write_config_byte(serverworks_private.svrwrks_dev,SVWRKS_AGP_ENABLE, enable_reg);
+ serverworks_tlbflush(NULL);
+
+ agp_bridge->capndx = pci_find_capability(serverworks_private.svrwrks_dev, PCI_CAP_ID_AGP);
+
+ /* Fill in the mode register */
+ pci_read_config_dword(serverworks_private.svrwrks_dev,
+ agp_bridge->capndx+PCI_AGP_STATUS, &agp_bridge->mode);
+
+ pci_read_config_byte(agp_bridge->dev, SVWRKS_CACHING, &enable_reg);
+ enable_reg &= ~0x3;
+ pci_write_config_byte(agp_bridge->dev, SVWRKS_CACHING, enable_reg);
+
+ pci_read_config_byte(agp_bridge->dev, SVWRKS_FEATURE, &enable_reg);
+ enable_reg |= (1<<6);
+ pci_write_config_byte(agp_bridge->dev,SVWRKS_FEATURE, enable_reg);
+
+ return 0;
+}
+
+static void serverworks_cleanup(void)
+{
+ iounmap((void __iomem *) serverworks_private.registers);
+}
+
+static int serverworks_insert_memory(struct agp_memory *mem,
+ off_t pg_start, int type)
+{
+ int i, j, num_entries;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+ if ((pg_start + mem->page_count) > num_entries) {
+ return -EINVAL;
+ }
+
+ j = pg_start;
+ while (j < (pg_start + mem->page_count)) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = SVRWRKS_GET_GATT(addr);
+ if (!PGE_EMPTY(agp_bridge, readl(cur_gatt+GET_GATT_OFF(addr))))
+ return -EBUSY;
+ j++;
+ }
+
+ if (mem->is_flushed == FALSE) {
+ global_cache_flush();
+ mem->is_flushed = TRUE;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = SVRWRKS_GET_GATT(addr);
+ writel(agp_bridge->driver->mask_memory(agp_bridge, mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+ }
+ serverworks_tlbflush(mem);
+ return 0;
+}
+
+static int serverworks_remove_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ int i;
+ unsigned long __iomem *cur_gatt;
+ unsigned long addr;
+
+ if (type != 0 || mem->type != 0) {
+ return -EINVAL;
+ }
+
+ global_cache_flush();
+ serverworks_tlbflush(mem);
+
+ for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+ addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+ cur_gatt = SVRWRKS_GET_GATT(addr);
+ writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+ }
+
+ serverworks_tlbflush(mem);
+ return 0;
+}
+
+static struct gatt_mask serverworks_masks[] =
+{
+ {.mask = 1, .type = 0}
+};
+
+static struct aper_size_info_lvl2 serverworks_sizes[7] =
+{
+ {2048, 524288, 0x80000000},
+ {1024, 262144, 0xc0000000},
+ {512, 131072, 0xe0000000},
+ {256, 65536, 0xf0000000},
+ {128, 32768, 0xf8000000},
+ {64, 16384, 0xfc000000},
+ {32, 8192, 0xfe000000}
+};
+
+static void serverworks_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ u32 command;
+
+ pci_read_config_dword(serverworks_private.svrwrks_dev,
+ bridge->capndx + PCI_AGP_STATUS,
+ &command);
+
+ command = agp_collect_device_status(bridge, mode, command);
+
+ command &= ~0x10; /* disable FW */
+ command &= ~0x08;
+
+ command |= 0x100;
+
+ pci_write_config_dword(serverworks_private.svrwrks_dev,
+ bridge->capndx + PCI_AGP_COMMAND,
+ command);
+
+ agp_device_command(command, 0);
+}
+
+struct agp_bridge_driver sworks_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = serverworks_sizes,
+ .size_type = LVL2_APER_SIZE,
+ .num_aperture_sizes = 7,
+ .configure = serverworks_configure,
+ .fetch_size = serverworks_fetch_size,
+ .cleanup = serverworks_cleanup,
+ .tlb_flush = serverworks_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = serverworks_masks,
+ .agp_enable = serverworks_agp_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = serverworks_create_gatt_table,
+ .free_gatt_table = serverworks_free_gatt_table,
+ .insert_memory = serverworks_insert_memory,
+ .remove_memory = serverworks_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static int __devinit agp_serverworks_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_bridge_data *bridge;
+ struct pci_dev *bridge_dev;
+ u32 temp, temp2;
+ u8 cap_ptr = 0;
+
+ /* Everything is on func 1 here so we are hardcoding function one */
+ bridge_dev = pci_find_slot((unsigned int)pdev->bus->number,
+ PCI_DEVFN(0, 1));
+ if (!bridge_dev) {
+ printk(KERN_INFO PFX "Detected a Serverworks chipset "
+ "but could not find the secondary device.\n");
+ return -ENODEV;
+ }
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+
+ switch (pdev->device) {
+ case 0x0006:
+ /* ServerWorks CNB20HE
+ Fail silently.*/
+ printk (KERN_ERR PFX "Detected ServerWorks CNB20HE chipset: No AGP present.\n");
+ return -ENODEV;
+
+ case PCI_DEVICE_ID_SERVERWORKS_HE:
+ case PCI_DEVICE_ID_SERVERWORKS_LE:
+ case 0x0007:
+ break;
+
+ default:
+ if (cap_ptr)
+ printk(KERN_ERR PFX "Unsupported Serverworks chipset "
+ "(device id: %04x)\n", pdev->device);
+ return -ENODEV;
+ }
+
+ serverworks_private.svrwrks_dev = bridge_dev;
+ serverworks_private.gart_addr_ofs = 0x10;
+
+ pci_read_config_dword(pdev, SVWRKS_APSIZE, &temp);
+ if (temp & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ pci_read_config_dword(pdev, SVWRKS_APSIZE + 4, &temp2);
+ if (temp2 != 0) {
+ printk(KERN_INFO PFX "Detected 64 bit aperture address, "
+ "but top bits are not zero. Disabling agp\n");
+ return -ENODEV;
+ }
+ serverworks_private.mm_addr_ofs = 0x18;
+ } else
+ serverworks_private.mm_addr_ofs = 0x14;
+
+ pci_read_config_dword(pdev, serverworks_private.mm_addr_ofs, &temp);
+ if (temp & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ pci_read_config_dword(pdev,
+ serverworks_private.mm_addr_ofs + 4, &temp2);
+ if (temp2 != 0) {
+ printk(KERN_INFO PFX "Detected 64 bit MMIO address, "
+ "but top bits are not zero. Disabling agp\n");
+ return -ENODEV;
+ }
+ }
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->driver = &sworks_driver;
+ bridge->dev_private_data = &serverworks_private,
+ bridge->dev = pdev;
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_serverworks_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_serverworks_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_SERVERWORKS,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_serverworks_pci_table);
+
+static struct pci_driver agp_serverworks_pci_driver = {
+ .name = "agpgart-serverworks",
+ .id_table = agp_serverworks_pci_table,
+ .probe = agp_serverworks_probe,
+ .remove = agp_serverworks_remove,
+};
+
+static int __init agp_serverworks_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_serverworks_pci_driver);
+}
+
+static void __exit agp_serverworks_cleanup(void)
+{
+ pci_unregister_driver(&agp_serverworks_pci_driver);
+}
+
+module_init(agp_serverworks_init);
+module_exit(agp_serverworks_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/uninorth-agp.c b/drivers/char/agp/uninorth-agp.c
new file mode 100644
index 000000000000..0f248239b4ba
--- /dev/null
+++ b/drivers/char/agp/uninorth-agp.c
@@ -0,0 +1,647 @@
+/*
+ * UniNorth AGPGART routines.
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/agp_backend.h>
+#include <linux/delay.h>
+#include <asm/uninorth.h>
+#include <asm/pci-bridge.h>
+#include <asm/prom.h>
+#include "agp.h"
+
+/*
+ * NOTES for uninorth3 (G5 AGP) supports :
+ *
+ * There maybe also possibility to have bigger cache line size for
+ * agp (see pmac_pci.c and look for cache line). Need to be investigated
+ * by someone.
+ *
+ * PAGE size are hardcoded but this may change, see asm/page.h.
+ *
+ * Jerome Glisse <j.glisse@gmail.com>
+ */
+static int uninorth_rev;
+static int is_u3;
+
+static int uninorth_fetch_size(void)
+{
+ int i;
+ u32 temp;
+ struct aper_size_info_32 *values;
+
+ pci_read_config_dword(agp_bridge->dev, UNI_N_CFG_GART_BASE, &temp);
+ temp &= ~(0xfffff000);
+ values = A_SIZE_32(agp_bridge->driver->aperture_sizes);
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + 1);
+ agp_bridge->aperture_size_idx = 1;
+ return values[1].size;
+
+ return 0;
+}
+
+static void uninorth_tlbflush(struct agp_memory *mem)
+{
+ u32 ctrl = UNI_N_CFG_GART_ENABLE;
+
+ if (is_u3)
+ ctrl |= U3_N_CFG_GART_PERFRD;
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+ ctrl | UNI_N_CFG_GART_INVAL);
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, ctrl);
+
+ if (uninorth_rev <= 0x30) {
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+ ctrl | UNI_N_CFG_GART_2xRESET);
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+ ctrl);
+ }
+}
+
+static void uninorth_cleanup(void)
+{
+ u32 tmp;
+
+ pci_read_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, &tmp);
+ if (!(tmp & UNI_N_CFG_GART_ENABLE))
+ return;
+ tmp |= UNI_N_CFG_GART_INVAL;
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, tmp);
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, 0);
+
+ if (uninorth_rev <= 0x30) {
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+ UNI_N_CFG_GART_2xRESET);
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+ 0);
+ }
+}
+
+static int uninorth_configure(void)
+{
+ struct aper_size_info_32 *current_size;
+
+ current_size = A_SIZE_32(agp_bridge->current_size);
+
+ printk(KERN_INFO PFX "configuring for size idx: %d\n",
+ current_size->size_value);
+
+ /* aperture size and gatt addr */
+ pci_write_config_dword(agp_bridge->dev,
+ UNI_N_CFG_GART_BASE,
+ (agp_bridge->gatt_bus_addr & 0xfffff000)
+ | current_size->size_value);
+
+ /* HACK ALERT
+ * UniNorth seem to be buggy enough not to handle properly when
+ * the AGP aperture isn't mapped at bus physical address 0
+ */
+ agp_bridge->gart_bus_addr = 0;
+#ifdef CONFIG_PPC64
+ /* Assume U3 or later on PPC64 systems */
+ /* high 4 bits of GART physical address go in UNI_N_CFG_AGP_BASE */
+ pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_AGP_BASE,
+ (agp_bridge->gatt_bus_addr >> 32) & 0xf);
+#else
+ pci_write_config_dword(agp_bridge->dev,
+ UNI_N_CFG_AGP_BASE, agp_bridge->gart_bus_addr);
+#endif
+
+ if (is_u3) {
+ pci_write_config_dword(agp_bridge->dev,
+ UNI_N_CFG_GART_DUMMY_PAGE,
+ agp_bridge->scratch_page_real >> 12);
+ }
+
+ return 0;
+}
+
+static int uninorth_insert_memory(struct agp_memory *mem, off_t pg_start,
+ int type)
+{
+ int i, j, num_entries;
+ void *temp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_32(temp)->num_entries;
+
+ if (type != 0 || mem->type != 0)
+ /* We know nothing of memory types */
+ return -EINVAL;
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ j = pg_start;
+
+ while (j < (pg_start + mem->page_count)) {
+ if (agp_bridge->gatt_table[j])
+ return -EBUSY;
+ j++;
+ }
+
+ for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+ agp_bridge->gatt_table[j] =
+ cpu_to_le32((mem->memory[i] & 0xFFFFF000UL) | 0x1UL);
+ flush_dcache_range((unsigned long)__va(mem->memory[i]),
+ (unsigned long)__va(mem->memory[i])+0x1000);
+ }
+ (void)in_le32((volatile u32*)&agp_bridge->gatt_table[pg_start]);
+ mb();
+ flush_dcache_range((unsigned long)&agp_bridge->gatt_table[pg_start],
+ (unsigned long)&agp_bridge->gatt_table[pg_start + mem->page_count]);
+
+ uninorth_tlbflush(mem);
+ return 0;
+}
+
+static int u3_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ int i, num_entries;
+ void *temp;
+ u32 *gp;
+
+ temp = agp_bridge->current_size;
+ num_entries = A_SIZE_32(temp)->num_entries;
+
+ if (type != 0 || mem->type != 0)
+ /* We know nothing of memory types */
+ return -EINVAL;
+ if ((pg_start + mem->page_count) > num_entries)
+ return -EINVAL;
+
+ gp = (u32 *) &agp_bridge->gatt_table[pg_start];
+ for (i = 0; i < mem->page_count; ++i) {
+ if (gp[i]) {
+ printk("u3_insert_memory: entry 0x%x occupied (%x)\n",
+ i, gp[i]);
+ return -EBUSY;
+ }
+ }
+
+ for (i = 0; i < mem->page_count; i++) {
+ gp[i] = (mem->memory[i] >> PAGE_SHIFT) | 0x80000000UL;
+ flush_dcache_range((unsigned long)__va(mem->memory[i]),
+ (unsigned long)__va(mem->memory[i])+0x1000);
+ }
+ mb();
+ flush_dcache_range((unsigned long)gp, (unsigned long) &gp[i]);
+ uninorth_tlbflush(mem);
+
+ return 0;
+}
+
+int u3_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+ size_t i;
+ u32 *gp;
+
+ if (type != 0 || mem->type != 0)
+ /* We know nothing of memory types */
+ return -EINVAL;
+
+ gp = (u32 *) &agp_bridge->gatt_table[pg_start];
+ for (i = 0; i < mem->page_count; ++i)
+ gp[i] = 0;
+ mb();
+ flush_dcache_range((unsigned long)gp, (unsigned long) &gp[i]);
+ uninorth_tlbflush(mem);
+
+ return 0;
+}
+
+static void uninorth_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+ u32 command, scratch, status;
+ int timeout;
+
+ pci_read_config_dword(bridge->dev,
+ bridge->capndx + PCI_AGP_STATUS,
+ &status);
+
+ command = agp_collect_device_status(bridge, mode, status);
+ command |= PCI_AGP_COMMAND_AGP;
+
+ if (uninorth_rev == 0x21) {
+ /*
+ * Darwin disable AGP 4x on this revision, thus we
+ * may assume it's broken. This is an AGP2 controller.
+ */
+ command &= ~AGPSTAT2_4X;
+ }
+
+ if ((uninorth_rev >= 0x30) && (uninorth_rev <= 0x33)) {
+ /*
+ * We need to to set REQ_DEPTH to 7 for U3 versions 1.0, 2.1,
+ * 2.2 and 2.3, Darwin do so.
+ */
+ if ((command >> AGPSTAT_RQ_DEPTH_SHIFT) > 7)
+ command = (command & ~AGPSTAT_RQ_DEPTH)
+ | (7 << AGPSTAT_RQ_DEPTH_SHIFT);
+ }
+
+ uninorth_tlbflush(NULL);
+
+ timeout = 0;
+ do {
+ pci_write_config_dword(bridge->dev,
+ bridge->capndx + PCI_AGP_COMMAND,
+ command);
+ pci_read_config_dword(bridge->dev,
+ bridge->capndx + PCI_AGP_COMMAND,
+ &scratch);
+ } while ((scratch & PCI_AGP_COMMAND_AGP) == 0 && ++timeout < 1000);
+ if ((scratch & PCI_AGP_COMMAND_AGP) == 0)
+ printk(KERN_ERR PFX "failed to write UniNorth AGP command reg\n");
+
+ if (uninorth_rev >= 0x30) {
+ /* This is an AGP V3 */
+ agp_device_command(command, (status & AGPSTAT_MODE_3_0));
+ } else {
+ /* AGP V2 */
+ agp_device_command(command, 0);
+ }
+
+ uninorth_tlbflush(NULL);
+}
+
+#ifdef CONFIG_PM
+static int agp_uninorth_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+ u32 cmd;
+ u8 agp;
+ struct pci_dev *device = NULL;
+
+ if (state != PMSG_SUSPEND)
+ return 0;
+
+ /* turn off AGP on the video chip, if it was enabled */
+ for_each_pci_dev(device) {
+ /* Don't touch the bridge yet, device first */
+ if (device == pdev)
+ continue;
+ /* Only deal with devices on the same bus here, no Mac has a P2P
+ * bridge on the AGP port, and mucking around the entire PCI
+ * tree is source of problems on some machines because of a bug
+ * in some versions of pci_find_capability() when hitting a dead
+ * device
+ */
+ if (device->bus != pdev->bus)
+ continue;
+ agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+ if (!agp)
+ continue;
+ pci_read_config_dword(device, agp + PCI_AGP_COMMAND, &cmd);
+ if (!(cmd & PCI_AGP_COMMAND_AGP))
+ continue;
+ printk("uninorth-agp: disabling AGP on device %s\n",
+ pci_name(device));
+ cmd &= ~PCI_AGP_COMMAND_AGP;
+ pci_write_config_dword(device, agp + PCI_AGP_COMMAND, cmd);
+ }
+
+ /* turn off AGP on the bridge */
+ agp = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ pci_read_config_dword(pdev, agp + PCI_AGP_COMMAND, &cmd);
+ if (cmd & PCI_AGP_COMMAND_AGP) {
+ printk("uninorth-agp: disabling AGP on bridge %s\n",
+ pci_name(pdev));
+ cmd &= ~PCI_AGP_COMMAND_AGP;
+ pci_write_config_dword(pdev, agp + PCI_AGP_COMMAND, cmd);
+ }
+ /* turn off the GART */
+ uninorth_cleanup();
+
+ return 0;
+}
+
+static int agp_uninorth_resume(struct pci_dev *pdev)
+{
+ return 0;
+}
+#endif
+
+static int uninorth_create_gatt_table(struct agp_bridge_data *bridge)
+{
+ char *table;
+ char *table_end;
+ int size;
+ int page_order;
+ int num_entries;
+ int i;
+ void *temp;
+ struct page *page;
+
+ /* We can't handle 2 level gatt's */
+ if (bridge->driver->size_type == LVL2_APER_SIZE)
+ return -EINVAL;
+
+ table = NULL;
+ i = bridge->aperture_size_idx;
+ temp = bridge->current_size;
+ size = page_order = num_entries = 0;
+
+ do {
+ size = A_SIZE_32(temp)->size;
+ page_order = A_SIZE_32(temp)->page_order;
+ num_entries = A_SIZE_32(temp)->num_entries;
+
+ table = (char *) __get_free_pages(GFP_KERNEL, page_order);
+
+ if (table == NULL) {
+ i++;
+ bridge->current_size = A_IDX32(bridge);
+ } else {
+ bridge->aperture_size_idx = i;
+ }
+ } while (!table && (i < bridge->driver->num_aperture_sizes));
+
+ if (table == NULL)
+ return -ENOMEM;
+
+ table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+ for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+ SetPageReserved(page);
+
+ bridge->gatt_table_real = (u32 *) table;
+ bridge->gatt_table = (u32 *)table;
+ bridge->gatt_bus_addr = virt_to_phys(table);
+
+ for (i = 0; i < num_entries; i++)
+ bridge->gatt_table[i] = 0;
+
+ flush_dcache_range((unsigned long)table, (unsigned long)table_end);
+
+ return 0;
+}
+
+static int uninorth_free_gatt_table(struct agp_bridge_data *bridge)
+{
+ int page_order;
+ char *table, *table_end;
+ void *temp;
+ struct page *page;
+
+ temp = bridge->current_size;
+ page_order = A_SIZE_32(temp)->page_order;
+
+ /* Do not worry about freeing memory, because if this is
+ * called, then all agp memory is deallocated and removed
+ * from the table.
+ */
+
+ table = (char *) bridge->gatt_table_real;
+ table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+ for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+ ClearPageReserved(page);
+
+ free_pages((unsigned long) bridge->gatt_table_real, page_order);
+
+ return 0;
+}
+
+void null_cache_flush(void)
+{
+ mb();
+}
+
+/* Setup function */
+
+static struct aper_size_info_32 uninorth_sizes[7] =
+{
+#if 0 /* Not sure uninorth supports that high aperture sizes */
+ {256, 65536, 6, 64},
+ {128, 32768, 5, 32},
+ {64, 16384, 4, 16},
+#endif
+ {32, 8192, 3, 8},
+ {16, 4096, 2, 4},
+ {8, 2048, 1, 2},
+ {4, 1024, 0, 1}
+};
+
+/*
+ * Not sure that u3 supports that high aperture sizes but it
+ * would strange if it did not :)
+ */
+static struct aper_size_info_32 u3_sizes[8] =
+{
+ {512, 131072, 7, 128},
+ {256, 65536, 6, 64},
+ {128, 32768, 5, 32},
+ {64, 16384, 4, 16},
+ {32, 8192, 3, 8},
+ {16, 4096, 2, 4},
+ {8, 2048, 1, 2},
+ {4, 1024, 0, 1}
+};
+
+struct agp_bridge_driver uninorth_agp_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = (void *)uninorth_sizes,
+ .size_type = U32_APER_SIZE,
+ .num_aperture_sizes = 4,
+ .configure = uninorth_configure,
+ .fetch_size = uninorth_fetch_size,
+ .cleanup = uninorth_cleanup,
+ .tlb_flush = uninorth_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .cache_flush = null_cache_flush,
+ .agp_enable = uninorth_agp_enable,
+ .create_gatt_table = uninorth_create_gatt_table,
+ .free_gatt_table = uninorth_free_gatt_table,
+ .insert_memory = uninorth_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+ .cant_use_aperture = 1,
+};
+
+struct agp_bridge_driver u3_agp_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = (void *)u3_sizes,
+ .size_type = U32_APER_SIZE,
+ .num_aperture_sizes = 8,
+ .configure = uninorth_configure,
+ .fetch_size = uninorth_fetch_size,
+ .cleanup = uninorth_cleanup,
+ .tlb_flush = uninorth_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .cache_flush = null_cache_flush,
+ .agp_enable = uninorth_agp_enable,
+ .create_gatt_table = uninorth_create_gatt_table,
+ .free_gatt_table = uninorth_free_gatt_table,
+ .insert_memory = u3_insert_memory,
+ .remove_memory = u3_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+ .cant_use_aperture = 1,
+ .needs_scratch_page = 1,
+};
+
+static struct agp_device_ids uninorth_agp_device_ids[] __devinitdata = {
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_UNI_N_AGP,
+ .chipset_name = "UniNorth",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_UNI_N_AGP_P,
+ .chipset_name = "UniNorth/Pangea",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_UNI_N_AGP15,
+ .chipset_name = "UniNorth 1.5",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_UNI_N_AGP2,
+ .chipset_name = "UniNorth 2",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_U3_AGP,
+ .chipset_name = "U3",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_U3L_AGP,
+ .chipset_name = "U3L",
+ },
+ {
+ .device_id = PCI_DEVICE_ID_APPLE_U3H_AGP,
+ .chipset_name = "U3H",
+ },
+};
+
+static int __devinit agp_uninorth_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_device_ids *devs = uninorth_agp_device_ids;
+ struct agp_bridge_data *bridge;
+ struct device_node *uninorth_node;
+ u8 cap_ptr;
+ int j;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (cap_ptr == 0)
+ return -ENODEV;
+
+ /* probe for known chipsets */
+ for (j = 0; devs[j].chipset_name != NULL; ++j) {
+ if (pdev->device == devs[j].device_id) {
+ printk(KERN_INFO PFX "Detected Apple %s chipset\n",
+ devs[j].chipset_name);
+ goto found;
+ }
+ }
+
+ printk(KERN_ERR PFX "Unsupported Apple chipset (device id: %04x).\n",
+ pdev->device);
+ return -ENODEV;
+
+ found:
+ /* Set revision to 0 if we could not read it. */
+ uninorth_rev = 0;
+ is_u3 = 0;
+ /* Locate core99 Uni-N */
+ uninorth_node = of_find_node_by_name(NULL, "uni-n");
+ /* Locate G5 u3 */
+ if (uninorth_node == NULL) {
+ is_u3 = 1;
+ uninorth_node = of_find_node_by_name(NULL, "u3");
+ }
+ if (uninorth_node) {
+ int *revprop = (int *)
+ get_property(uninorth_node, "device-rev", NULL);
+ if (revprop != NULL)
+ uninorth_rev = *revprop & 0x3f;
+ of_node_put(uninorth_node);
+ }
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ if (is_u3)
+ bridge->driver = &u3_agp_driver;
+ else
+ bridge->driver = &uninorth_agp_driver;
+
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+ bridge->flags = AGP_ERRATA_FASTWRITES;
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev, cap_ptr+PCI_AGP_STATUS, &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_uninorth_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_uninorth_pci_table[] = {
+ {
+ .class = (PCI_CLASS_BRIDGE_HOST << 8),
+ .class_mask = ~0,
+ .vendor = PCI_VENDOR_ID_APPLE,
+ .device = PCI_ANY_ID,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_uninorth_pci_table);
+
+static struct pci_driver agp_uninorth_pci_driver = {
+ .name = "agpgart-uninorth",
+ .id_table = agp_uninorth_pci_table,
+ .probe = agp_uninorth_probe,
+ .remove = agp_uninorth_remove,
+#ifdef CONFIG_PM
+ .suspend = agp_uninorth_suspend,
+ .resume = agp_uninorth_resume,
+#endif
+};
+
+static int __init agp_uninorth_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_uninorth_pci_driver);
+}
+
+static void __exit agp_uninorth_cleanup(void)
+{
+ pci_unregister_driver(&agp_uninorth_pci_driver);
+}
+
+module_init(agp_uninorth_init);
+module_exit(agp_uninorth_cleanup);
+
+MODULE_AUTHOR("Ben Herrenschmidt & Paul Mackerras");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/agp/via-agp.c b/drivers/char/agp/via-agp.c
new file mode 100644
index 000000000000..e1451dd9b6a7
--- /dev/null
+++ b/drivers/char/agp/via-agp.c
@@ -0,0 +1,548 @@
+/*
+ * VIA AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+static struct pci_device_id agp_via_pci_table[];
+
+#define VIA_GARTCTRL 0x80
+#define VIA_APSIZE 0x84
+#define VIA_ATTBASE 0x88
+
+#define VIA_AGP3_GARTCTRL 0x90
+#define VIA_AGP3_APSIZE 0x94
+#define VIA_AGP3_ATTBASE 0x98
+#define VIA_AGPSEL 0xfd
+
+static int via_fetch_size(void)
+{
+ int i;
+ u8 temp;
+ struct aper_size_info_8 *values;
+
+ values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+ pci_read_config_byte(agp_bridge->dev, VIA_APSIZE, &temp);
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+ printk(KERN_ERR PFX "Unknown aperture size from AGP bridge (0x%x)\n", temp);
+ return 0;
+}
+
+
+static int via_configure(void)
+{
+ u32 temp;
+ struct aper_size_info_8 *current_size;
+
+ current_size = A_SIZE_8(agp_bridge->current_size);
+ /* aperture size */
+ pci_write_config_byte(agp_bridge->dev, VIA_APSIZE,
+ current_size->size_value);
+ /* address to map too */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* GART control register */
+ pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, 0x0000000f);
+
+ /* attbase - aperture GATT base */
+ pci_write_config_dword(agp_bridge->dev, VIA_ATTBASE,
+ (agp_bridge->gatt_bus_addr & 0xfffff000) | 3);
+ return 0;
+}
+
+
+static void via_cleanup(void)
+{
+ struct aper_size_info_8 *previous_size;
+
+ previous_size = A_SIZE_8(agp_bridge->previous_size);
+ pci_write_config_byte(agp_bridge->dev, VIA_APSIZE,
+ previous_size->size_value);
+ /* Do not disable by writing 0 to VIA_ATTBASE, it screws things up
+ * during reinitialization.
+ */
+}
+
+
+static void via_tlbflush(struct agp_memory *mem)
+{
+ u32 temp;
+
+ pci_read_config_dword(agp_bridge->dev, VIA_GARTCTRL, &temp);
+ temp |= (1<<7);
+ pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, temp);
+ temp &= ~(1<<7);
+ pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, temp);
+}
+
+
+static struct aper_size_info_8 via_generic_sizes[9] =
+{
+ {256, 65536, 6, 0},
+ {128, 32768, 5, 128},
+ {64, 16384, 4, 192},
+ {32, 8192, 3, 224},
+ {16, 4096, 2, 240},
+ {8, 2048, 1, 248},
+ {4, 1024, 0, 252},
+ {2, 512, 0, 254},
+ {1, 256, 0, 255}
+};
+
+
+static int via_fetch_size_agp3(void)
+{
+ int i;
+ u16 temp;
+ struct aper_size_info_16 *values;
+
+ values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+ pci_read_config_word(agp_bridge->dev, VIA_AGP3_APSIZE, &temp);
+ temp &= 0xfff;
+
+ for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+ if (temp == values[i].size_value) {
+ agp_bridge->previous_size =
+ agp_bridge->current_size = (void *) (values + i);
+ agp_bridge->aperture_size_idx = i;
+ return values[i].size;
+ }
+ }
+ return 0;
+}
+
+
+static int via_configure_agp3(void)
+{
+ u32 temp;
+ struct aper_size_info_16 *current_size;
+
+ current_size = A_SIZE_16(agp_bridge->current_size);
+
+ /* address to map too */
+ pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+ agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+ /* attbase - aperture GATT base */
+ pci_write_config_dword(agp_bridge->dev, VIA_AGP3_ATTBASE,
+ agp_bridge->gatt_bus_addr & 0xfffff000);
+
+ /* 1. Enable GTLB in RX90<7>, all AGP aperture access needs to fetch
+ * translation table first.
+ * 2. Enable AGP aperture in RX91<0>. This bit controls the enabling of the
+ * graphics AGP aperture for the AGP3.0 port.
+ */
+ pci_read_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp | (3<<7));
+ return 0;
+}
+
+
+static void via_cleanup_agp3(void)
+{
+ struct aper_size_info_16 *previous_size;
+
+ previous_size = A_SIZE_16(agp_bridge->previous_size);
+ pci_write_config_byte(agp_bridge->dev, VIA_APSIZE, previous_size->size_value);
+}
+
+
+static void via_tlbflush_agp3(struct agp_memory *mem)
+{
+ u32 temp;
+
+ pci_read_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, &temp);
+ pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp & ~(1<<7));
+ pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp);
+}
+
+
+struct agp_bridge_driver via_agp3_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = agp3_generic_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 10,
+ .configure = via_configure_agp3,
+ .fetch_size = via_fetch_size_agp3,
+ .cleanup = via_cleanup_agp3,
+ .tlb_flush = via_tlbflush_agp3,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+struct agp_bridge_driver via_driver = {
+ .owner = THIS_MODULE,
+ .aperture_sizes = via_generic_sizes,
+ .size_type = U8_APER_SIZE,
+ .num_aperture_sizes = 9,
+ .configure = via_configure,
+ .fetch_size = via_fetch_size,
+ .cleanup = via_cleanup,
+ .tlb_flush = via_tlbflush,
+ .mask_memory = agp_generic_mask_memory,
+ .masks = NULL,
+ .agp_enable = agp_generic_enable,
+ .cache_flush = global_cache_flush,
+ .create_gatt_table = agp_generic_create_gatt_table,
+ .free_gatt_table = agp_generic_free_gatt_table,
+ .insert_memory = agp_generic_insert_memory,
+ .remove_memory = agp_generic_remove_memory,
+ .alloc_by_type = agp_generic_alloc_by_type,
+ .free_by_type = agp_generic_free_by_type,
+ .agp_alloc_page = agp_generic_alloc_page,
+ .agp_destroy_page = agp_generic_destroy_page,
+};
+
+static struct agp_device_ids via_agp_device_ids[] __devinitdata =
+{
+ {
+ .device_id = PCI_DEVICE_ID_VIA_82C597_0,
+ .chipset_name = "Apollo VP3",
+ },
+
+ {
+ .device_id = PCI_DEVICE_ID_VIA_82C598_0,
+ .chipset_name = "Apollo MVP3",
+ },
+
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8501_0,
+ .chipset_name = "Apollo MVP4",
+ },
+
+ /* VT8601 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8601_0,
+ .chipset_name = "Apollo ProMedia/PLE133Ta",
+ },
+
+ /* VT82C693A / VT28C694T */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_82C691_0,
+ .chipset_name = "Apollo Pro 133",
+ },
+
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8371_0,
+ .chipset_name = "KX133",
+ },
+
+ /* VT8633 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8633_0,
+ .chipset_name = "Pro 266",
+ },
+
+ {
+ .device_id = PCI_DEVICE_ID_VIA_XN266,
+ .chipset_name = "Apollo Pro266",
+ },
+
+ /* VT8361 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8361,
+ .chipset_name = "KLE133",
+ },
+
+ /* VT8365 / VT8362 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8363_0,
+ .chipset_name = "Twister-K/KT133x/KM133",
+ },
+
+ /* VT8753A */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8753_0,
+ .chipset_name = "P4X266",
+ },
+
+ /* VT8366 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8367_0,
+ .chipset_name = "KT266/KY266x/KT333",
+ },
+
+ /* VT8633 (for CuMine/ Celeron) */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8653_0,
+ .chipset_name = "Pro266T",
+ },
+
+ /* KM266 / PM266 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_XM266,
+ .chipset_name = "PM266/KM266",
+ },
+
+ /* CLE266 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_862X_0,
+ .chipset_name = "CLE266",
+ },
+
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8377_0,
+ .chipset_name = "KT400/KT400A/KT600",
+ },
+
+ /* VT8604 / VT8605 / VT8603
+ * (Apollo Pro133A chipset with S3 Savage4) */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8605_0,
+ .chipset_name = "ProSavage PM133/PL133/PN133"
+ },
+
+ /* P4M266x/P4N266 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8703_51_0,
+ .chipset_name = "P4M266x/P4N266",
+ },
+
+ /* VT8754 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8754C_0,
+ .chipset_name = "PT800",
+ },
+
+ /* P4X600 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8763_0,
+ .chipset_name = "P4X600"
+ },
+
+ /* KM400 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8378_0,
+ .chipset_name = "KM400/KM400A",
+ },
+
+ /* PT880 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_PT880,
+ .chipset_name = "PT880",
+ },
+
+ /* PT890 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_8783_0,
+ .chipset_name = "PT890",
+ },
+
+ /* PM800/PN800/PM880/PN880 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_PX8X0_0,
+ .chipset_name = "PM800/PN800/PM880/PN880",
+ },
+ /* KT880 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_3269_0,
+ .chipset_name = "KT880",
+ },
+ /* KTxxx/Px8xx */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_83_87XX_1,
+ .chipset_name = "VT83xx/VT87xx/KTxxx/Px8xx",
+ },
+ /* P4M800 */
+ {
+ .device_id = PCI_DEVICE_ID_VIA_3296_0,
+ .chipset_name = "P4M800",
+ },
+
+ { }, /* dummy final entry, always present */
+};
+
+
+/*
+ * VIA's AGP3 chipsets do magick to put the AGP bridge compliant
+ * with the same standards version as the graphics card.
+ */
+static void check_via_agp3 (struct agp_bridge_data *bridge)
+{
+ u8 reg;
+
+ pci_read_config_byte(bridge->dev, VIA_AGPSEL, &reg);
+ /* Check AGP 2.0 compatibility mode. */
+ if ((reg & (1<<1))==0)
+ bridge->driver = &via_agp3_driver;
+}
+
+
+static int __devinit agp_via_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct agp_device_ids *devs = via_agp_device_ids;
+ struct agp_bridge_data *bridge;
+ int j = 0;
+ u8 cap_ptr;
+
+ cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+ if (!cap_ptr)
+ return -ENODEV;
+
+ j = ent - agp_via_pci_table;
+ printk (KERN_INFO PFX "Detected VIA %s chipset\n", devs[j].chipset_name);
+
+ bridge = agp_alloc_bridge();
+ if (!bridge)
+ return -ENOMEM;
+
+ bridge->dev = pdev;
+ bridge->capndx = cap_ptr;
+ bridge->driver = &via_driver;
+
+ /*
+ * Garg, there are KT400s with KT266 IDs.
+ */
+ if (pdev->device == PCI_DEVICE_ID_VIA_8367_0) {
+ /* Is there a KT400 subsystem ? */
+ if (pdev->subsystem_device == PCI_DEVICE_ID_VIA_8377_0) {
+ printk(KERN_INFO PFX "Found KT400 in disguise as a KT266.\n");
+ check_via_agp3(bridge);
+ }
+ }
+
+ /* If this is an AGP3 bridge, check which mode its in and adjust. */
+ get_agp_version(bridge);
+ if (bridge->major_version >= 3)
+ check_via_agp3(bridge);
+
+ /* Fill in the mode register */
+ pci_read_config_dword(pdev,
+ bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+
+ pci_set_drvdata(pdev, bridge);
+ return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_via_remove(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ agp_remove_bridge(bridge);
+ agp_put_bridge(bridge);
+}
+
+#ifdef CONFIG_PM
+
+static int agp_via_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+ pci_save_state (pdev);
+ pci_set_power_state (pdev, PCI_D3hot);
+
+ return 0;
+}
+
+static int agp_via_resume(struct pci_dev *pdev)
+{
+ struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+ pci_set_power_state (pdev, PCI_D0);
+ pci_restore_state(pdev);
+
+ if (bridge->driver == &via_agp3_driver)
+ return via_configure_agp3();
+ else if (bridge->driver == &via_driver)
+ return via_configure();
+
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+/* must be the same order as name table above */
+static struct pci_device_id agp_via_pci_table[] = {
+#define ID(x) \
+ { \
+ .class = (PCI_CLASS_BRIDGE_HOST << 8), \
+ .class_mask = ~0, \
+ .vendor = PCI_VENDOR_ID_VIA, \
+ .device = x, \
+ .subvendor = PCI_ANY_ID, \
+ .subdevice = PCI_ANY_ID, \
+ }
+ ID(PCI_DEVICE_ID_VIA_82C597_0),
+ ID(PCI_DEVICE_ID_VIA_82C598_0),
+ ID(PCI_DEVICE_ID_VIA_8501_0),
+ ID(PCI_DEVICE_ID_VIA_8601_0),
+ ID(PCI_DEVICE_ID_VIA_82C691_0),
+ ID(PCI_DEVICE_ID_VIA_8371_0),
+ ID(PCI_DEVICE_ID_VIA_8633_0),
+ ID(PCI_DEVICE_ID_VIA_XN266),
+ ID(PCI_DEVICE_ID_VIA_8361),
+ ID(PCI_DEVICE_ID_VIA_8363_0),
+ ID(PCI_DEVICE_ID_VIA_8753_0),
+ ID(PCI_DEVICE_ID_VIA_8367_0),
+ ID(PCI_DEVICE_ID_VIA_8653_0),
+ ID(PCI_DEVICE_ID_VIA_XM266),
+ ID(PCI_DEVICE_ID_VIA_862X_0),
+ ID(PCI_DEVICE_ID_VIA_8377_0),
+ ID(PCI_DEVICE_ID_VIA_8605_0),
+ ID(PCI_DEVICE_ID_VIA_8703_51_0),
+ ID(PCI_DEVICE_ID_VIA_8754C_0),
+ ID(PCI_DEVICE_ID_VIA_8763_0),
+ ID(PCI_DEVICE_ID_VIA_8378_0),
+ ID(PCI_DEVICE_ID_VIA_PT880),
+ ID(PCI_DEVICE_ID_VIA_8783_0),
+ ID(PCI_DEVICE_ID_VIA_PX8X0_0),
+ ID(PCI_DEVICE_ID_VIA_3269_0),
+ ID(PCI_DEVICE_ID_VIA_83_87XX_1),
+ ID(PCI_DEVICE_ID_VIA_3296_0),
+ { }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_via_pci_table);
+
+
+static struct pci_driver agp_via_pci_driver = {
+ .name = "agpgart-via",
+ .id_table = agp_via_pci_table,
+ .probe = agp_via_probe,
+ .remove = agp_via_remove,
+#ifdef CONFIG_PM
+ .suspend = agp_via_suspend,
+ .resume = agp_via_resume,
+#endif
+};
+
+
+static int __init agp_via_init(void)
+{
+ if (agp_off)
+ return -EINVAL;
+ return pci_register_driver(&agp_via_pci_driver);
+}
+
+static void __exit agp_via_cleanup(void)
+{
+ pci_unregister_driver(&agp_via_pci_driver);
+}
+
+module_init(agp_via_init);
+module_exit(agp_via_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
diff --git a/drivers/char/amiserial.c b/drivers/char/amiserial.c
new file mode 100644
index 000000000000..1dc4259213a6
--- /dev/null
+++ b/drivers/char/amiserial.c
@@ -0,0 +1,2179 @@
+/*
+ * linux/drivers/char/amiserial.c
+ *
+ * Serial driver for the amiga builtin port.
+ *
+ * This code was created by taking serial.c version 4.30 from kernel
+ * release 2.3.22, replacing all hardware related stuff with the
+ * corresponding amiga hardware actions, and removing all irrelevant
+ * code. As a consequence, it uses many of the constants and names
+ * associated with the registers and bits of 16550 compatible UARTS -
+ * but only to keep track of status, etc in the state variables. It
+ * was done this was to make it easier to keep the code in line with
+ * (non hardware specific) changes to serial.c.
+ *
+ * The port is registered with the tty driver as minor device 64, and
+ * therefore other ports should should only use 65 upwards.
+ *
+ * Richard Lucock 28/12/99
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ * Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997,
+ * 1998, 1999 Theodore Ts'o
+ *
+ */
+
+/*
+ * Serial driver configuration section. Here are the various options:
+ *
+ * SERIAL_PARANOIA_CHECK
+ * Check the magic number for the async_structure where
+ * ever possible.
+ */
+
+#include <linux/config.h>
+#include <linux/delay.h>
+
+#undef SERIAL_PARANOIA_CHECK
+#define SERIAL_DO_RESTART
+
+/* Set of debugging defines */
+
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_FLOW
+#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+
+/* Sanity checks */
+
+#define SERIAL_INLINE
+
+#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)
+#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \
+ tty->name, (info->flags), serial_driver->refcount,info->count,tty->count,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+/*
+ * End of serial driver configuration section.
+ */
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/serial_reg.h>
+static char *serial_version = "4.30";
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/console.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/setup.h>
+
+#include <asm/system.h>
+
+#include <asm/irq.h>
+
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+
+#ifdef SERIAL_INLINE
+#define _INLINE_ inline
+#endif
+
+static char *serial_name = "Amiga-builtin serial driver";
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+static struct async_struct *IRQ_ports;
+
+static unsigned char current_ctl_bits;
+
+static void change_speed(struct async_struct *info, struct termios *old);
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout);
+
+
+static struct serial_state rs_table[1];
+
+#define NR_PORTS (sizeof(rs_table)/sizeof(struct serial_state))
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write. We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+#include <asm/uaccess.h>
+
+#define serial_isroot() (capable(CAP_SYS_ADMIN))
+
+
+static inline int serial_paranoia_check(struct async_struct *info,
+ char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for serial struct (%s) in %s\n";
+ static const char *badinfo =
+ "Warning: null async_struct for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (info->magic != SERIAL_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/* some serial hardware definitions */
+#define SDR_OVRUN (1<<15)
+#define SDR_RBF (1<<14)
+#define SDR_TBE (1<<13)
+#define SDR_TSRE (1<<12)
+
+#define SERPER_PARENB (1<<15)
+
+#define AC_SETCLR (1<<15)
+#define AC_UARTBRK (1<<11)
+
+#define SER_DTR (1<<7)
+#define SER_RTS (1<<6)
+#define SER_DCD (1<<5)
+#define SER_CTS (1<<4)
+#define SER_DSR (1<<3)
+
+static __inline__ void rtsdtr_ctrl(int bits)
+{
+ ciab.pra = ((bits & (SER_RTS | SER_DTR)) ^ (SER_RTS | SER_DTR)) | (ciab.pra & ~(SER_RTS | SER_DTR));
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_stop() and rs_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rs_stop(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_stop"))
+ return;
+
+ local_irq_save(flags);
+ if (info->IER & UART_IER_THRI) {
+ info->IER &= ~UART_IER_THRI;
+ /* disable Tx interrupt and remove any pending interrupts */
+ custom.intena = IF_TBE;
+ mb();
+ custom.intreq = IF_TBE;
+ mb();
+ }
+ local_irq_restore(flags);
+}
+
+static void rs_start(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_start"))
+ return;
+
+ local_irq_save(flags);
+ if (info->xmit.head != info->xmit.tail
+ && info->xmit.buf
+ && !(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ }
+ local_irq_restore(flags);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Here starts the interrupt handling routines. All of the following
+ * subroutines are declared as inline and are folded into
+ * rs_interrupt(). They were separated out for readability's sake.
+ *
+ * Note: rs_interrupt() is a "fast" interrupt, which means that it
+ * runs with interrupts turned off. People who may want to modify
+ * rs_interrupt() should try to keep the interrupt handler as fast as
+ * possible. After you are done making modifications, it is not a bad
+ * idea to do:
+ *
+ * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
+ *
+ * and look at the resulting assemble code in serial.s.
+ *
+ * - Ted Ts'o (tytso@mit.edu), 7-Mar-93
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver.
+ */
+static _INLINE_ void rs_sched_event(struct async_struct *info,
+ int event)
+{
+ info->event |= 1 << event;
+ tasklet_schedule(&info->tlet);
+}
+
+static _INLINE_ void receive_chars(struct async_struct *info)
+{
+ int status;
+ int serdatr;
+ struct tty_struct *tty = info->tty;
+ unsigned char ch;
+ struct async_icount *icount;
+
+ icount = &info->state->icount;
+
+ status = UART_LSR_DR; /* We obviously have a character! */
+ serdatr = custom.serdatr;
+ mb();
+ custom.intreq = IF_RBF;
+ mb();
+
+ if((serdatr & 0x1ff) == 0)
+ status |= UART_LSR_BI;
+ if(serdatr & SDR_OVRUN)
+ status |= UART_LSR_OE;
+
+ ch = serdatr & 0xff;
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ goto ignore_char;
+ *tty->flip.char_buf_ptr = ch;
+ icount->rx++;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("DR%02x:%02x...", ch, status);
+#endif
+ *tty->flip.flag_buf_ptr = 0;
+
+ /*
+ * We don't handle parity or frame errors - but I have left
+ * the code in, since I'm not sure that the errors can't be
+ * detected.
+ */
+
+ if (status & (UART_LSR_BI | UART_LSR_PE |
+ UART_LSR_FE | UART_LSR_OE)) {
+ /*
+ * For statistics only
+ */
+ if (status & UART_LSR_BI) {
+ status &= ~(UART_LSR_FE | UART_LSR_PE);
+ icount->brk++;
+ } else if (status & UART_LSR_PE)
+ icount->parity++;
+ else if (status & UART_LSR_FE)
+ icount->frame++;
+ if (status & UART_LSR_OE)
+ icount->overrun++;
+
+ /*
+ * Now check to see if character should be
+ * ignored, and mask off conditions which
+ * should be ignored.
+ */
+ if (status & info->ignore_status_mask)
+ goto ignore_char;
+
+ status &= info->read_status_mask;
+
+ if (status & (UART_LSR_BI)) {
+#ifdef SERIAL_DEBUG_INTR
+ printk("handling break....");
+#endif
+ *tty->flip.flag_buf_ptr = TTY_BREAK;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (status & UART_LSR_PE)
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ else if (status & UART_LSR_FE)
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ if (status & UART_LSR_OE) {
+ /*
+ * Overrun is special, since it's
+ * reported immediately, and doesn't
+ * affect the current character
+ */
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ tty->flip.count++;
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ *tty->flip.flag_buf_ptr = TTY_OVERRUN;
+ }
+ }
+ }
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ ignore_char:
+
+ tty_flip_buffer_push(tty);
+}
+
+static _INLINE_ void transmit_chars(struct async_struct *info)
+{
+ custom.intreq = IF_TBE;
+ mb();
+ if (info->x_char) {
+ custom.serdat = info->x_char | 0x100;
+ mb();
+ info->state->icount.tx++;
+ info->x_char = 0;
+ return;
+ }
+ if (info->xmit.head == info->xmit.tail
+ || info->tty->stopped
+ || info->tty->hw_stopped) {
+ info->IER &= ~UART_IER_THRI;
+ custom.intena = IF_TBE;
+ mb();
+ return;
+ }
+
+ custom.serdat = info->xmit.buf[info->xmit.tail++] | 0x100;
+ mb();
+ info->xmit.tail = info->xmit.tail & (SERIAL_XMIT_SIZE-1);
+ info->state->icount.tx++;
+
+ if (CIRC_CNT(info->xmit.head,
+ info->xmit.tail,
+ SERIAL_XMIT_SIZE) < WAKEUP_CHARS)
+ rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("THRE...");
+#endif
+ if (info->xmit.head == info->xmit.tail) {
+ custom.intena = IF_TBE;
+ mb();
+ info->IER &= ~UART_IER_THRI;
+ }
+}
+
+static _INLINE_ void check_modem_status(struct async_struct *info)
+{
+ unsigned char status = ciab.pra & (SER_DCD | SER_CTS | SER_DSR);
+ unsigned char dstatus;
+ struct async_icount *icount;
+
+ /* Determine bits that have changed */
+ dstatus = status ^ current_ctl_bits;
+ current_ctl_bits = status;
+
+ if (dstatus) {
+ icount = &info->state->icount;
+ /* update input line counters */
+ if (dstatus & SER_DSR)
+ icount->dsr++;
+ if (dstatus & SER_DCD) {
+ icount->dcd++;
+#ifdef CONFIG_HARD_PPS
+ if ((info->flags & ASYNC_HARDPPS_CD) &&
+ !(status & SER_DCD))
+ hardpps();
+#endif
+ }
+ if (dstatus & SER_CTS)
+ icount->cts++;
+ wake_up_interruptible(&info->delta_msr_wait);
+ }
+
+ if ((info->flags & ASYNC_CHECK_CD) && (dstatus & SER_DCD)) {
+#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
+ printk("ttyS%d CD now %s...", info->line,
+ (!(status & SER_DCD)) ? "on" : "off");
+#endif
+ if (!(status & SER_DCD))
+ wake_up_interruptible(&info->open_wait);
+ else {
+#ifdef SERIAL_DEBUG_OPEN
+ printk("doing serial hangup...");
+#endif
+ if (info->tty)
+ tty_hangup(info->tty);
+ }
+ }
+ if (info->flags & ASYNC_CTS_FLOW) {
+ if (info->tty->hw_stopped) {
+ if (!(status & SER_CTS)) {
+#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
+ printk("CTS tx start...");
+#endif
+ info->tty->hw_stopped = 0;
+ info->IER |= UART_IER_THRI;
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+ return;
+ }
+ } else {
+ if ((status & SER_CTS)) {
+#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
+ printk("CTS tx stop...");
+#endif
+ info->tty->hw_stopped = 1;
+ info->IER &= ~UART_IER_THRI;
+ /* disable Tx interrupt and remove any pending interrupts */
+ custom.intena = IF_TBE;
+ mb();
+ custom.intreq = IF_TBE;
+ mb();
+ }
+ }
+ }
+}
+
+static irqreturn_t ser_vbl_int( int irq, void *data, struct pt_regs *regs)
+{
+ /* vbl is just a periodic interrupt we tie into to update modem status */
+ struct async_struct * info = IRQ_ports;
+ /*
+ * TBD - is it better to unregister from this interrupt or to
+ * ignore it if MSI is clear ?
+ */
+ if(info->IER & UART_IER_MSI)
+ check_modem_status(info);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ser_rx_int(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct async_struct * info;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("ser_rx_int...");
+#endif
+
+ info = IRQ_ports;
+ if (!info || !info->tty)
+ return IRQ_NONE;
+
+ receive_chars(info);
+ info->last_active = jiffies;
+#ifdef SERIAL_DEBUG_INTR
+ printk("end.\n");
+#endif
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ser_tx_int(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct async_struct * info;
+
+ if (custom.serdatr & SDR_TBE) {
+#ifdef SERIAL_DEBUG_INTR
+ printk("ser_tx_int...");
+#endif
+
+ info = IRQ_ports;
+ if (!info || !info->tty)
+ return IRQ_NONE;
+
+ transmit_chars(info);
+ info->last_active = jiffies;
+#ifdef SERIAL_DEBUG_INTR
+ printk("end.\n");
+#endif
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * -------------------------------------------------------------------
+ * Here ends the serial interrupt routines.
+ * -------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using rs_sched_event(), and they get done here.
+ */
+
+static void do_softint(unsigned long private_)
+{
+ struct async_struct *info = (struct async_struct *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+ if (!tty)
+ return;
+
+ if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+}
+
+/*
+ * ---------------------------------------------------------------
+ * Low level utility subroutines for the serial driver: routines to
+ * figure out the appropriate timeout for an interrupt chain, routines
+ * to initialize and startup a serial port, and routines to shutdown a
+ * serial port. Useful stuff like that.
+ * ---------------------------------------------------------------
+ */
+
+static int startup(struct async_struct * info)
+{
+ unsigned long flags;
+ int retval=0;
+ unsigned long page;
+
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+
+ local_irq_save(flags);
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ free_page(page);
+ goto errout;
+ }
+
+ if (info->xmit.buf)
+ free_page(page);
+ else
+ info->xmit.buf = (unsigned char *) page;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("starting up ttys%d ...", info->line);
+#endif
+
+ /* Clear anything in the input buffer */
+
+ custom.intreq = IF_RBF;
+ mb();
+
+ retval = request_irq(IRQ_AMIGA_VERTB, ser_vbl_int, 0, "serial status", info);
+ if (retval) {
+ if (serial_isroot()) {
+ if (info->tty)
+ set_bit(TTY_IO_ERROR,
+ &info->tty->flags);
+ retval = 0;
+ }
+ goto errout;
+ }
+
+ /* enable both Rx and Tx interrupts */
+ custom.intena = IF_SETCLR | IF_RBF | IF_TBE;
+ mb();
+ info->IER = UART_IER_MSI;
+
+ /* remember current state of the DCD and CTS bits */
+ current_ctl_bits = ciab.pra & (SER_DCD | SER_CTS | SER_DSR);
+
+ IRQ_ports = info;
+
+ info->MCR = 0;
+ if (info->tty->termios->c_cflag & CBAUD)
+ info->MCR = SER_DTR | SER_RTS;
+ rtsdtr_ctrl(info->MCR);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ info->xmit.head = info->xmit.tail = 0;
+
+ /*
+ * Set up the tty->alt_speed kludge
+ */
+ if (info->tty) {
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ info->tty->alt_speed = 460800;
+ }
+
+ /*
+ * and set the speed of the serial port
+ */
+ change_speed(info, NULL);
+
+ info->flags |= ASYNC_INITIALIZED;
+ local_irq_restore(flags);
+ return 0;
+
+errout:
+ local_irq_restore(flags);
+ return retval;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void shutdown(struct async_struct * info)
+{
+ unsigned long flags;
+ struct serial_state *state;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ state = info->state;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("Shutting down serial port %d ....\n", info->line);
+#endif
+
+ local_irq_save(flags); /* Disable interrupts */
+
+ /*
+ * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
+ * here so the queue might never be waken up
+ */
+ wake_up_interruptible(&info->delta_msr_wait);
+
+ IRQ_ports = NULL;
+
+ /*
+ * Free the IRQ, if necessary
+ */
+ free_irq(IRQ_AMIGA_VERTB, info);
+
+ if (info->xmit.buf) {
+ free_page((unsigned long) info->xmit.buf);
+ info->xmit.buf = NULL;
+ }
+
+ info->IER = 0;
+ custom.intena = IF_RBF | IF_TBE;
+ mb();
+
+ /* disable break condition */
+ custom.adkcon = AC_UARTBRK;
+ mb();
+
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+ info->MCR &= ~(SER_DTR|SER_RTS);
+ rtsdtr_ctrl(info->MCR);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+ local_irq_restore(flags);
+}
+
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void change_speed(struct async_struct *info,
+ struct termios *old_termios)
+{
+ int quot = 0, baud_base, baud;
+ unsigned cflag, cval = 0;
+ int bits;
+ unsigned long flags;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+ cflag = info->tty->termios->c_cflag;
+
+ /* Byte size is always 8 bits plus parity bit if requested */
+
+ cval = 3; bits = 10;
+ if (cflag & CSTOPB) {
+ cval |= 0x04;
+ bits++;
+ }
+ if (cflag & PARENB) {
+ cval |= UART_LCR_PARITY;
+ bits++;
+ }
+ if (!(cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+ if (cflag & CMSPAR)
+ cval |= UART_LCR_SPAR;
+#endif
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(info->tty);
+ if (!baud)
+ baud = 9600; /* B0 transition handled in rs_set_termios */
+ baud_base = info->state->baud_base;
+ if (baud == 38400 &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+ quot = info->state->custom_divisor;
+ else {
+ if (baud == 134)
+ /* Special case since 134 is really 134.5 */
+ quot = (2*baud_base / 269);
+ else if (baud)
+ quot = baud_base / baud;
+ }
+ /* If the quotient is zero refuse the change */
+ if (!quot && old_termios) {
+ info->tty->termios->c_cflag &= ~CBAUD;
+ info->tty->termios->c_cflag |= (old_termios->c_cflag & CBAUD);
+ baud = tty_get_baud_rate(info->tty);
+ if (!baud)
+ baud = 9600;
+ if (baud == 38400 &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+ quot = info->state->custom_divisor;
+ else {
+ if (baud == 134)
+ /* Special case since 134 is really 134.5 */
+ quot = (2*baud_base / 269);
+ else if (baud)
+ quot = baud_base / baud;
+ }
+ }
+ /* As a last resort, if the quotient is zero, default to 9600 bps */
+ if (!quot)
+ quot = baud_base / 9600;
+ info->quot = quot;
+ info->timeout = ((info->xmit_fifo_size*HZ*bits*quot) / baud_base);
+ info->timeout += HZ/50; /* Add .02 seconds of slop */
+
+ /* CTS flow control flag and modem status interrupts */
+ info->IER &= ~UART_IER_MSI;
+ if (info->flags & ASYNC_HARDPPS_CD)
+ info->IER |= UART_IER_MSI;
+ if (cflag & CRTSCTS) {
+ info->flags |= ASYNC_CTS_FLOW;
+ info->IER |= UART_IER_MSI;
+ } else
+ info->flags &= ~ASYNC_CTS_FLOW;
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else {
+ info->flags |= ASYNC_CHECK_CD;
+ info->IER |= UART_IER_MSI;
+ }
+ /* TBD:
+ * Does clearing IER_MSI imply that we should disbale the VBL interrupt ?
+ */
+
+ /*
+ * Set up parity check flag
+ */
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+ info->read_status_mask = UART_LSR_OE | UART_LSR_DR;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask |= UART_LSR_BI;
+
+ /*
+ * Characters to ignore
+ */
+ info->ignore_status_mask = 0;
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_BI;
+ /*
+ * If we're ignore parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= UART_LSR_OE;
+ }
+ /*
+ * !!! ignore all characters if CREAD is not set
+ */
+ if ((cflag & CREAD) == 0)
+ info->ignore_status_mask |= UART_LSR_DR;
+ local_irq_save(flags);
+
+ {
+ short serper;
+
+ /* Set up the baud rate */
+ serper = quot - 1;
+
+ /* Enable or disable parity bit */
+
+ if(cval & UART_LCR_PARITY)
+ serper |= (SERPER_PARENB);
+
+ custom.serper = serper;
+ mb();
+ }
+
+ info->LCR = cval; /* Save LCR */
+ local_irq_restore(flags);
+}
+
+static void rs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_put_char"))
+ return;
+
+ if (!tty || !info->xmit.buf)
+ return;
+
+ local_irq_save(flags);
+ if (CIRC_SPACE(info->xmit.head,
+ info->xmit.tail,
+ SERIAL_XMIT_SIZE) == 0) {
+ local_irq_restore(flags);
+ return;
+ }
+
+ info->xmit.buf[info->xmit.head++] = ch;
+ info->xmit.head &= SERIAL_XMIT_SIZE-1;
+ local_irq_restore(flags);
+}
+
+static void rs_flush_chars(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_flush_chars"))
+ return;
+
+ if (info->xmit.head == info->xmit.tail
+ || tty->stopped
+ || tty->hw_stopped
+ || !info->xmit.buf)
+ return;
+
+ local_irq_save(flags);
+ info->IER |= UART_IER_THRI;
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ local_irq_restore(flags);
+}
+
+static int rs_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+ int c, ret = 0;
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_write"))
+ return 0;
+
+ if (!tty || !info->xmit.buf || !tmp_buf)
+ return 0;
+
+ local_save_flags(flags);
+ local_irq_disable();
+ while (1) {
+ c = CIRC_SPACE_TO_END(info->xmit.head,
+ info->xmit.tail,
+ SERIAL_XMIT_SIZE);
+ if (count < c)
+ c = count;
+ if (c <= 0) {
+ break;
+ }
+ memcpy(info->xmit.buf + info->xmit.head, buf, c);
+ info->xmit.head = ((info->xmit.head + c) &
+ (SERIAL_XMIT_SIZE-1));
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+ local_irq_restore(flags);
+
+ if (info->xmit.head != info->xmit.tail
+ && !tty->stopped
+ && !tty->hw_stopped
+ && !(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ local_irq_disable();
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ local_irq_restore(flags);
+ }
+ return ret;
+}
+
+static int rs_write_room(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "rs_write_room"))
+ return 0;
+ return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);
+}
+
+static int rs_chars_in_buffer(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer"))
+ return 0;
+ return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);
+}
+
+static void rs_flush_buffer(struct tty_struct *tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_flush_buffer"))
+ return;
+ local_irq_save(flags);
+ info->xmit.head = info->xmit.tail = 0;
+ local_irq_restore(flags);
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+/*
+ * This function is used to send a high-priority XON/XOFF character to
+ * the device
+ */
+static void rs_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_send_char"))
+ return;
+
+ info->x_char = ch;
+ if (ch) {
+ /* Make sure transmit interrupts are on */
+
+ /* Check this ! */
+ local_irq_save(flags);
+ if(!(custom.intenar & IF_TBE)) {
+ custom.intena = IF_SETCLR | IF_TBE;
+ mb();
+ /* set a pending Tx Interrupt, transmitter should restart now */
+ custom.intreq = IF_SETCLR | IF_TBE;
+ mb();
+ }
+ local_irq_restore(flags);
+
+ info->IER |= UART_IER_THRI;
+ }
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_throttle()
+ *
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void rs_throttle(struct tty_struct * tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("throttle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "rs_throttle"))
+ return;
+
+ if (I_IXOFF(tty))
+ rs_send_xchar(tty, STOP_CHAR(tty));
+
+ if (tty->termios->c_cflag & CRTSCTS)
+ info->MCR &= ~SER_RTS;
+
+ local_irq_save(flags);
+ rtsdtr_ctrl(info->MCR);
+ local_irq_restore(flags);
+}
+
+static void rs_unthrottle(struct tty_struct * tty)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("unthrottle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "rs_unthrottle"))
+ return;
+
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else
+ rs_send_xchar(tty, START_CHAR(tty));
+ }
+ if (tty->termios->c_cflag & CRTSCTS)
+ info->MCR |= SER_RTS;
+ local_irq_save(flags);
+ rtsdtr_ctrl(info->MCR);
+ local_irq_restore(flags);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static int get_serial_info(struct async_struct * info,
+ struct serial_struct * retinfo)
+{
+ struct serial_struct tmp;
+ struct serial_state *state = info->state;
+
+ if (!retinfo)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = state->type;
+ tmp.line = state->line;
+ tmp.port = state->port;
+ tmp.irq = state->irq;
+ tmp.flags = state->flags;
+ tmp.xmit_fifo_size = state->xmit_fifo_size;
+ tmp.baud_base = state->baud_base;
+ tmp.close_delay = state->close_delay;
+ tmp.closing_wait = state->closing_wait;
+ tmp.custom_divisor = state->custom_divisor;
+ if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
+ return -EFAULT;
+ return 0;
+}
+
+static int set_serial_info(struct async_struct * info,
+ struct serial_struct * new_info)
+{
+ struct serial_struct new_serial;
+ struct serial_state old_state, *state;
+ unsigned int change_irq,change_port;
+ int retval = 0;
+
+ if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+ return -EFAULT;
+ state = info->state;
+ old_state = *state;
+
+ change_irq = new_serial.irq != state->irq;
+ change_port = (new_serial.port != state->port);
+ if(change_irq || change_port || (new_serial.xmit_fifo_size != state->xmit_fifo_size))
+ return -EINVAL;
+
+ if (!serial_isroot()) {
+ if ((new_serial.baud_base != state->baud_base) ||
+ (new_serial.close_delay != state->close_delay) ||
+ (new_serial.xmit_fifo_size != state->xmit_fifo_size) ||
+ ((new_serial.flags & ~ASYNC_USR_MASK) !=
+ (state->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ state->flags = ((state->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ state->custom_divisor = new_serial.custom_divisor;
+ goto check_and_exit;
+ }
+
+ if (new_serial.baud_base < 9600)
+ return -EINVAL;
+
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+
+ state->baud_base = new_serial.baud_base;
+ state->flags = ((state->flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ info->flags = ((state->flags & ~ASYNC_INTERNAL_FLAGS) |
+ (info->flags & ASYNC_INTERNAL_FLAGS));
+ state->custom_divisor = new_serial.custom_divisor;
+ state->close_delay = new_serial.close_delay * HZ/100;
+ state->closing_wait = new_serial.closing_wait * HZ/100;
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+check_and_exit:
+ if (info->flags & ASYNC_INITIALIZED) {
+ if (((old_state.flags & ASYNC_SPD_MASK) !=
+ (state->flags & ASYNC_SPD_MASK)) ||
+ (old_state.custom_divisor != state->custom_divisor)) {
+ if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ info->tty->alt_speed = 460800;
+ change_speed(info, NULL);
+ }
+ } else
+ retval = startup(info);
+ return retval;
+}
+
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int get_lsr_info(struct async_struct * info, unsigned int *value)
+{
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ status = custom.serdatr;
+ mb();
+ local_irq_restore(flags);
+ result = ((status & SDR_TSRE) ? TIOCSER_TEMT : 0);
+ if (copy_to_user(value, &result, sizeof(int)))
+ return -EFAULT;
+ return 0;
+}
+
+
+static int rs_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ unsigned char control, status;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ control = info->MCR;
+ local_irq_save(flags);
+ status = ciab.pra;
+ local_irq_restore(flags);
+ return ((control & SER_RTS) ? TIOCM_RTS : 0)
+ | ((control & SER_DTR) ? TIOCM_DTR : 0)
+ | (!(status & SER_DCD) ? TIOCM_CAR : 0)
+ | (!(status & SER_DSR) ? TIOCM_DSR : 0)
+ | (!(status & SER_CTS) ? TIOCM_CTS : 0);
+}
+
+static int rs_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ local_irq_save(flags);
+ if (set & TIOCM_RTS)
+ info->MCR |= SER_RTS;
+ if (set & TIOCM_DTR)
+ info->MCR |= SER_DTR;
+ if (clear & TIOCM_RTS)
+ info->MCR &= ~SER_RTS;
+ if (clear & TIOCM_DTR)
+ info->MCR &= ~SER_DTR;
+ rtsdtr_ctrl(info->MCR);
+ local_irq_restore(flags);
+ return 0;
+}
+
+/*
+ * rs_break() --- routine which turns the break handling on or off
+ */
+static void rs_break(struct tty_struct *tty, int break_state)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_break"))
+ return;
+
+ local_irq_save(flags);
+ if (break_state == -1)
+ custom.adkcon = AC_SETCLR | AC_UARTBRK;
+ else
+ custom.adkcon = AC_UARTBRK;
+ mb();
+ local_irq_restore(flags);
+}
+
+
+static int rs_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ struct async_icount cprev, cnow; /* kernel counter temps */
+ struct serial_icounter_struct icount;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case TIOCGSERIAL:
+ return get_serial_info(info,
+ (struct serial_struct *) arg);
+ case TIOCSSERIAL:
+ return set_serial_info(info,
+ (struct serial_struct *) arg);
+ case TIOCSERCONFIG:
+ return 0;
+
+ case TIOCSERGETLSR: /* Get line status register */
+ return get_lsr_info(info, (unsigned int *) arg);
+
+ case TIOCSERGSTRUCT:
+ if (copy_to_user((struct async_struct *) arg,
+ info, sizeof(struct async_struct)))
+ return -EFAULT;
+ return 0;
+
+ /*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+ case TIOCMIWAIT:
+ local_irq_save(flags);
+ /* note the counters on entry */
+ cprev = info->state->icount;
+ local_irq_restore(flags);
+ while (1) {
+ interruptible_sleep_on(&info->delta_msr_wait);
+ /* see if a signal did it */
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ local_irq_save(flags);
+ cnow = info->state->icount; /* atomic copy */
+ local_irq_restore(flags);
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
+ return -EIO; /* no change => error */
+ if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
+ ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+ return 0;
+ }
+ cprev = cnow;
+ }
+ /* NOTREACHED */
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ local_irq_save(flags);
+ cnow = info->state->icount;
+ local_irq_restore(flags);
+ icount.cts = cnow.cts;
+ icount.dsr = cnow.dsr;
+ icount.rng = cnow.rng;
+ icount.dcd = cnow.dcd;
+ icount.rx = cnow.rx;
+ icount.tx = cnow.tx;
+ icount.frame = cnow.frame;
+ icount.overrun = cnow.overrun;
+ icount.parity = cnow.parity;
+ icount.brk = cnow.brk;
+ icount.buf_overrun = cnow.buf_overrun;
+
+ if (copy_to_user((void *)arg, &icount, sizeof(icount)))
+ return -EFAULT;
+ return 0;
+ case TIOCSERGWILD:
+ case TIOCSERSWILD:
+ /* "setserial -W" is called in Debian boot */
+ printk ("TIOCSER?WILD ioctl obsolete, ignored.\n");
+ return 0;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ struct async_struct *info = (struct async_struct *)tty->driver_data;
+ unsigned long flags;
+ unsigned int cflag = tty->termios->c_cflag;
+
+ if ( (cflag == old_termios->c_cflag)
+ && ( RELEVANT_IFLAG(tty->termios->c_iflag)
+ == RELEVANT_IFLAG(old_termios->c_iflag)))
+ return;
+
+ change_speed(info, old_termios);
+
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) &&
+ !(cflag & CBAUD)) {
+ info->MCR &= ~(SER_DTR|SER_RTS);
+ local_irq_save(flags);
+ rtsdtr_ctrl(info->MCR);
+ local_irq_restore(flags);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ (cflag & CBAUD)) {
+ info->MCR |= SER_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ info->MCR |= SER_RTS;
+ }
+ local_irq_save(flags);
+ rtsdtr_ctrl(info->MCR);
+ local_irq_restore(flags);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ rs_start(tty);
+ }
+
+#if 0
+ /*
+ * No need to wake up processes in open wait, since they
+ * sample the CLOCAL flag once, and don't recheck it.
+ * XXX It's not clear whether the current behavior is correct
+ * or not. Hence, this may change.....
+ */
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&info->open_wait);
+#endif
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_close()
+ *
+ * This routine is called when the serial port gets closed. First, we
+ * wait for the last remaining data to be sent. Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ * ------------------------------------------------------------
+ */
+static void rs_close(struct tty_struct *tty, struct file * filp)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ struct serial_state *state;
+ unsigned long flags;
+
+ if (!info || serial_paranoia_check(info, tty->name, "rs_close"))
+ return;
+
+ state = info->state;
+
+ local_irq_save(flags);
+
+ if (tty_hung_up_p(filp)) {
+ DBG_CNT("before DEC-hung");
+ local_irq_restore(flags);
+ return;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("rs_close ttys%d, count = %d\n", info->line, state->count);
+#endif
+ if ((tty->count == 1) && (state->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. state->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk("rs_close: bad serial port count; tty->count is 1, "
+ "state->count is %d\n", state->count);
+ state->count = 1;
+ }
+ if (--state->count < 0) {
+ printk("rs_close: bad serial port count for ttys%d: %d\n",
+ info->line, state->count);
+ state->count = 0;
+ }
+ if (state->count) {
+ DBG_CNT("before DEC-2");
+ local_irq_restore(flags);
+ return;
+ }
+ info->flags |= ASYNC_CLOSING;
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, info->closing_wait);
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ info->read_status_mask &= ~UART_LSR_DR;
+ if (info->flags & ASYNC_INITIALIZED) {
+ /* disable receive interrupts */
+ custom.intena = IF_RBF;
+ mb();
+ /* clear any pending receive interrupt */
+ custom.intreq = IF_RBF;
+ mb();
+
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ rs_wait_until_sent(tty, info->timeout);
+ }
+ shutdown(info);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+ tty->closing = 0;
+ info->event = 0;
+ info->tty = NULL;
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+ local_irq_restore(flags);
+}
+
+/*
+ * rs_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+ int lsr;
+
+ if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent"))
+ return;
+
+ if (info->xmit_fifo_size == 0)
+ return; /* Just in case.... */
+
+ orig_jiffies = jiffies;
+ /*
+ * Set the check interval to be 1/5 of the estimated time to
+ * send a single character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ *
+ * Note: we have to use pretty tight timings here to satisfy
+ * the NIST-PCTS.
+ */
+ char_time = (info->timeout - HZ/50) / info->xmit_fifo_size;
+ char_time = char_time / 5;
+ if (char_time == 0)
+ char_time = 1;
+ if (timeout)
+ char_time = min_t(unsigned long, char_time, timeout);
+ /*
+ * If the transmitter hasn't cleared in twice the approximate
+ * amount of time to send the entire FIFO, it probably won't
+ * ever clear. This assumes the UART isn't doing flow
+ * control, which is currently the case. Hence, if it ever
+ * takes longer than info->timeout, this is probably due to a
+ * UART bug of some kind. So, we clamp the timeout parameter at
+ * 2*info->timeout.
+ */
+ if (!timeout || timeout > 2*info->timeout)
+ timeout = 2*info->timeout;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
+ printk("jiff=%lu...", jiffies);
+#endif
+ while(!((lsr = custom.serdatr) & SDR_TSRE)) {
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("serdatr = %d (jiff=%lu)...", lsr, jiffies);
+#endif
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ current->state = TASK_RUNNING;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
+#endif
+}
+
+/*
+ * rs_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void rs_hangup(struct tty_struct *tty)
+{
+ struct async_struct * info = (struct async_struct *)tty->driver_data;
+ struct serial_state *state = info->state;
+
+ if (serial_paranoia_check(info, tty->name, "rs_hangup"))
+ return;
+
+ state = info->state;
+
+ rs_flush_buffer(tty);
+ shutdown(info);
+ info->event = 0;
+ state->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+ wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_open() and friends
+ * ------------------------------------------------------------
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct async_struct *info)
+{
+#ifdef DECLARE_WAITQUEUE
+ DECLARE_WAITQUEUE(wait, current);
+#else
+ struct wait_queue wait = { current, NULL };
+#endif
+ struct serial_state *state = info->state;
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) ||
+ (info->flags & ASYNC_CLOSING)) {
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+ return ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+#else
+ return -EAGAIN;
+#endif
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, state->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready before block: ttys%d, count = %d\n",
+ state->line, state->count);
+#endif
+ local_irq_save(flags);
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ state->count--;
+ }
+ local_irq_restore(flags);
+ info->blocked_open++;
+ while (1) {
+ local_irq_save(flags);
+ if (tty->termios->c_cflag & CBAUD)
+ rtsdtr_ctrl(SER_DTR|SER_RTS);
+ local_irq_restore(flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(info->flags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+ if (info->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+#else
+ retval = -EAGAIN;
+#endif
+ break;
+ }
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal || (!(ciab.pra & SER_DCD)) ))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready blocking: ttys%d, count = %d\n",
+ info->line, state->count);
+#endif
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+ if (extra_count)
+ state->count++;
+ info->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready after blocking: ttys%d, count = %d\n",
+ info->line, state->count);
+#endif
+ if (retval)
+ return retval;
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+}
+
+static int get_async_struct(int line, struct async_struct **ret_info)
+{
+ struct async_struct *info;
+ struct serial_state *sstate;
+
+ sstate = rs_table + line;
+ sstate->count++;
+ if (sstate->info) {
+ *ret_info = sstate->info;
+ return 0;
+ }
+ info = kmalloc(sizeof(struct async_struct), GFP_KERNEL);
+ if (!info) {
+ sstate->count--;
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(struct async_struct));
+#ifdef DECLARE_WAITQUEUE
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->delta_msr_wait);
+#endif
+ info->magic = SERIAL_MAGIC;
+ info->port = sstate->port;
+ info->flags = sstate->flags;
+ info->xmit_fifo_size = sstate->xmit_fifo_size;
+ info->line = line;
+ tasklet_init(&info->tlet, do_softint, (unsigned long)info);
+ info->state = sstate;
+ if (sstate->info) {
+ kfree(info);
+ *ret_info = sstate->info;
+ return 0;
+ }
+ *ret_info = sstate->info = info;
+ return 0;
+}
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain. It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int rs_open(struct tty_struct *tty, struct file * filp)
+{
+ struct async_struct *info;
+ int retval, line;
+ unsigned long page;
+
+ line = tty->index;
+ if ((line < 0) || (line >= NR_PORTS)) {
+ return -ENODEV;
+ }
+ retval = get_async_struct(line, &info);
+ if (retval) {
+ return retval;
+ }
+ tty->driver_data = info;
+ info->tty = tty;
+ if (serial_paranoia_check(info, tty->name, "rs_open"))
+ return -ENODEV;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("rs_open %s, count = %d\n", tty->name, info->state->count);
+#endif
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ if (!tmp_buf) {
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+ return -ENOMEM;
+ }
+ if (tmp_buf)
+ free_page(page);
+ else
+ tmp_buf = (unsigned char *) page;
+ }
+
+ /*
+ * If the port is the middle of closing, bail out now
+ */
+ if (tty_hung_up_p(filp) ||
+ (info->flags & ASYNC_CLOSING)) {
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+ return ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+#else
+ return -EAGAIN;
+#endif
+ }
+
+ /*
+ * Start up serial port
+ */
+ retval = startup(info);
+ if (retval) {
+ return retval;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+ printk("rs_open returning after block_til_ready with %d\n",
+ retval);
+#endif
+ return retval;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("rs_open %s successful...", tty->name);
+#endif
+ return 0;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, struct serial_state *state)
+{
+ struct async_struct *info = state->info, scr_info;
+ char stat_buf[30], control, status;
+ int ret;
+ unsigned long flags;
+
+ ret = sprintf(buf, "%d: uart:amiga_builtin",state->line);
+
+ /*
+ * Figure out the current RS-232 lines
+ */
+ if (!info) {
+ info = &scr_info; /* This is just for serial_{in,out} */
+
+ info->magic = SERIAL_MAGIC;
+ info->flags = state->flags;
+ info->quot = 0;
+ info->tty = NULL;
+ }
+ local_irq_save(flags);
+ status = ciab.pra;
+ control = info ? info->MCR : status;
+ local_irq_restore(flags);
+
+ stat_buf[0] = 0;
+ stat_buf[1] = 0;
+ if(!(control & SER_RTS))
+ strcat(stat_buf, "|RTS");
+ if(!(status & SER_CTS))
+ strcat(stat_buf, "|CTS");
+ if(!(control & SER_DTR))
+ strcat(stat_buf, "|DTR");
+ if(!(status & SER_DSR))
+ strcat(stat_buf, "|DSR");
+ if(!(status & SER_DCD))
+ strcat(stat_buf, "|CD");
+
+ if (info->quot) {
+ ret += sprintf(buf+ret, " baud:%d",
+ state->baud_base / info->quot);
+ }
+
+ ret += sprintf(buf+ret, " tx:%d rx:%d",
+ state->icount.tx, state->icount.rx);
+
+ if (state->icount.frame)
+ ret += sprintf(buf+ret, " fe:%d", state->icount.frame);
+
+ if (state->icount.parity)
+ ret += sprintf(buf+ret, " pe:%d", state->icount.parity);
+
+ if (state->icount.brk)
+ ret += sprintf(buf+ret, " brk:%d", state->icount.brk);
+
+ if (state->icount.overrun)
+ ret += sprintf(buf+ret, " oe:%d", state->icount.overrun);
+
+ /*
+ * Last thing is the RS-232 status lines
+ */
+ ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+ return ret;
+}
+
+static int rs_read_proc(char *page, char **start, off_t off, int count,
+ int *eof, void *data)
+{
+ int len = 0, l;
+ off_t begin = 0;
+
+ len += sprintf(page, "serinfo:1.0 driver:%s\n", serial_version);
+ l = line_info(page + len, &rs_table[0]);
+ len += l;
+ if (len+begin > off+count)
+ goto done;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ *eof = 1;
+done:
+ if (off >= len+begin)
+ return 0;
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * rs_init() and friends
+ *
+ * rs_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+static _INLINE_ void show_serial_version(void)
+{
+ printk(KERN_INFO "%s version %s\n", serial_name, serial_version);
+}
+
+
+int register_serial(struct serial_struct *req);
+void unregister_serial(int line);
+
+
+static struct tty_operations serial_ops = {
+ .open = rs_open,
+ .close = rs_close,
+ .write = rs_write,
+ .put_char = rs_put_char,
+ .flush_chars = rs_flush_chars,
+ .write_room = rs_write_room,
+ .chars_in_buffer = rs_chars_in_buffer,
+ .flush_buffer = rs_flush_buffer,
+ .ioctl = rs_ioctl,
+ .throttle = rs_throttle,
+ .unthrottle = rs_unthrottle,
+ .set_termios = rs_set_termios,
+ .stop = rs_stop,
+ .start = rs_start,
+ .hangup = rs_hangup,
+ .break_ctl = rs_break,
+ .send_xchar = rs_send_xchar,
+ .wait_until_sent = rs_wait_until_sent,
+ .read_proc = rs_read_proc,
+ .tiocmget = rs_tiocmget,
+ .tiocmset = rs_tiocmset,
+};
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+static int __init rs_init(void)
+{
+ unsigned long flags;
+ struct serial_state * state;
+
+ if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_SERIAL))
+ return -ENODEV;
+
+ serial_driver = alloc_tty_driver(1);
+ if (!serial_driver)
+ return -ENOMEM;
+
+ /*
+ * We request SERDAT and SERPER only, because the serial registers are
+ * too spreaded over the custom register space
+ */
+ if (!request_mem_region(CUSTOM_PHYSADDR+0x30, 4, "amiserial [Paula]"))
+ return -EBUSY;
+
+ IRQ_ports = NULL;
+
+ show_serial_version();
+
+ /* Initialize the tty_driver structure */
+
+ serial_driver->owner = THIS_MODULE;
+ serial_driver->driver_name = "amiserial";
+ serial_driver->name = "ttyS";
+ serial_driver->major = TTY_MAJOR;
+ serial_driver->minor_start = 64;
+ serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ serial_driver->init_termios = tty_std_termios;
+ serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(serial_driver, &serial_ops);
+
+ if (tty_register_driver(serial_driver))
+ panic("Couldn't register serial driver\n");
+
+ state = rs_table;
+ state->magic = SSTATE_MAGIC;
+ state->port = (int)&custom.serdatr; /* Just to give it a value */
+ state->line = 0;
+ state->custom_divisor = 0;
+ state->close_delay = 5*HZ/10;
+ state->closing_wait = 30*HZ;
+ state->icount.cts = state->icount.dsr =
+ state->icount.rng = state->icount.dcd = 0;
+ state->icount.rx = state->icount.tx = 0;
+ state->icount.frame = state->icount.parity = 0;
+ state->icount.overrun = state->icount.brk = 0;
+ /*
+ if(state->port && check_region(state->port,REGION_LENGTH(state)))
+ continue;
+ */
+
+ printk(KERN_INFO "ttyS%d is the amiga builtin serial port\n",
+ state->line);
+
+ /* Hardware set up */
+
+ state->baud_base = amiga_colorclock;
+ state->xmit_fifo_size = 1;
+
+ local_irq_save(flags);
+
+ /* set ISRs, and then disable the rx interrupts */
+ request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", state);
+ request_irq(IRQ_AMIGA_RBF, ser_rx_int, SA_INTERRUPT, "serial RX", state);
+
+ /* turn off Rx and Tx interrupts */
+ custom.intena = IF_RBF | IF_TBE;
+ mb();
+
+ /* clear any pending interrupt */
+ custom.intreq = IF_RBF | IF_TBE;
+ mb();
+
+ local_irq_restore(flags);
+
+ /*
+ * set the appropriate directions for the modem control flags,
+ * and clear RTS and DTR
+ */
+ ciab.ddra |= (SER_DTR | SER_RTS); /* outputs */
+ ciab.ddra &= ~(SER_DCD | SER_CTS | SER_DSR); /* inputs */
+
+ return 0;
+}
+
+static __exit void rs_exit(void)
+{
+ int error;
+ struct async_struct *info = rs_table[0].info;
+
+ /* printk("Unloading %s: version %s\n", serial_name, serial_version); */
+ tasklet_kill(&info->tlet);
+ if ((error = tty_unregister_driver(serial_driver)))
+ printk("SERIAL: failed to unregister serial driver (%d)\n",
+ error);
+ put_tty_driver(serial_driver);
+
+ if (info) {
+ rs_table[0].info = NULL;
+ kfree(info);
+ }
+
+ if (tmp_buf) {
+ free_page((unsigned long) tmp_buf);
+ tmp_buf = NULL;
+ }
+
+ release_mem_region(CUSTOM_PHYSADDR+0x30, 4);
+}
+
+module_init(rs_init)
+module_exit(rs_exit)
+
+
+/*
+ * ------------------------------------------------------------
+ * Serial console driver
+ * ------------------------------------------------------------
+ */
+#ifdef CONFIG_SERIAL_CONSOLE
+
+static void amiga_serial_putc(char c)
+{
+ custom.serdat = (unsigned char)c | 0x100;
+ while (!(custom.serdatr & 0x2000))
+ barrier();
+}
+
+/*
+ * Print a string to the serial port trying not to disturb
+ * any possible real use of the port...
+ *
+ * The console must be locked when we get here.
+ */
+static void serial_console_write(struct console *co, const char *s,
+ unsigned count)
+{
+ unsigned short intena = custom.intenar;
+
+ custom.intena = IF_TBE;
+
+ while (count--) {
+ if (*s == '\n')
+ amiga_serial_putc('\r');
+ amiga_serial_putc(*s++);
+ }
+
+ custom.intena = IF_SETCLR | (intena & IF_TBE);
+}
+
+static struct tty_driver *serial_console_device(struct console *c, int *index)
+{
+ *index = 0;
+ return serial_driver;
+}
+
+static struct console sercons = {
+ .name = "ttyS",
+ .write = serial_console_write,
+ .device = serial_console_device,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+/*
+ * Register console.
+ */
+static int __init amiserial_console_init(void)
+{
+ register_console(&sercons);
+ return 0;
+}
+console_initcall(amiserial_console_init);
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c
new file mode 100644
index 000000000000..6bf2e27dc23a
--- /dev/null
+++ b/drivers/char/applicom.c
@@ -0,0 +1,862 @@
+/* Derived from Applicom driver ac.c for SCO Unix */
+/* Ported by David Woodhouse, Axiom (Cambridge) Ltd. */
+/* dwmw2@infradead.org 30/8/98 */
+/* $Id: ac.c,v 1.30 2000/03/22 16:03:57 dwmw2 Exp $ */
+/* This module is for Linux 2.1 and 2.2 series kernels. */
+/*****************************************************************************/
+/* J PAGET 18/02/94 passage V2.4.2 ioctl avec code 2 reset to les interrupt */
+/* ceci pour reseter correctement apres une sortie sauvage */
+/* J PAGET 02/05/94 passage V2.4.3 dans le traitement de d'interruption, */
+/* LoopCount n'etait pas initialise a 0. */
+/* F LAFORSE 04/07/95 version V2.6.0 lecture bidon apres acces a une carte */
+/* pour liberer le bus */
+/* J.PAGET 19/11/95 version V2.6.1 Nombre, addresse,irq n'est plus configure */
+/* et passe en argument a acinit, mais est scrute sur le bus pour s'adapter */
+/* au nombre de cartes presentes sur le bus. IOCL code 6 affichait V2.4.3 */
+/* F.LAFORSE 28/11/95 creation de fichiers acXX.o avec les differentes */
+/* adresses de base des cartes, IOCTL 6 plus complet */
+/* J.PAGET le 19/08/96 copie de la version V2.6 en V2.8.0 sans modification */
+/* de code autre que le texte V2.6.1 en V2.8.0 */
+/*****************************************************************************/
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/wait.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "applicom.h"
+
+
+/* NOTE: We use for loops with {write,read}b() instead of
+ memcpy_{from,to}io throughout this driver. This is because
+ the board doesn't correctly handle word accesses - only
+ bytes.
+*/
+
+
+#undef DEBUG
+
+#define MAX_BOARD 8 /* maximum of pc board possible */
+#define MAX_ISA_BOARD 4
+#define LEN_RAM_IO 0x800
+#define AC_MINOR 157
+
+#ifndef PCI_VENDOR_ID_APPLICOM
+#define PCI_VENDOR_ID_APPLICOM 0x1389
+#define PCI_DEVICE_ID_APPLICOM_PCIGENERIC 0x0001
+#define PCI_DEVICE_ID_APPLICOM_PCI2000IBS_CAN 0x0002
+#define PCI_DEVICE_ID_APPLICOM_PCI2000PFB 0x0003
+#endif
+#define MAX_PCI_DEVICE_NUM 3
+
+static char *applicom_pci_devnames[] = {
+ "PCI board",
+ "PCI2000IBS / PCI2000CAN",
+ "PCI2000PFB"
+};
+
+static struct pci_device_id applicom_pci_tbl[] = {
+ { PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCIGENERIC,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCI2000IBS_CAN,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCI2000PFB,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, applicom_pci_tbl);
+
+MODULE_AUTHOR("David Woodhouse & Applicom International");
+MODULE_DESCRIPTION("Driver for Applicom Profibus card");
+MODULE_LICENSE("GPL");
+
+MODULE_SUPPORTED_DEVICE("ac");
+
+
+static struct applicom_board {
+ unsigned long PhysIO;
+ void __iomem *RamIO;
+ wait_queue_head_t FlagSleepSend;
+ long irq;
+ spinlock_t mutex;
+} apbs[MAX_BOARD];
+
+static unsigned int irq = 0; /* interrupt number IRQ */
+static unsigned long mem = 0; /* physical segment of board */
+
+module_param(irq, uint, 0);
+MODULE_PARM_DESC(irq, "IRQ of the Applicom board");
+module_param(mem, ulong, 0);
+MODULE_PARM_DESC(mem, "Shared Memory Address of Applicom board");
+
+static unsigned int numboards; /* number of installed boards */
+static volatile unsigned char Dummy;
+static DECLARE_WAIT_QUEUE_HEAD(FlagSleepRec);
+static unsigned int WriteErrorCount; /* number of write error */
+static unsigned int ReadErrorCount; /* number of read error */
+static unsigned int DeviceErrorCount; /* number of device error */
+
+static ssize_t ac_read (struct file *, char __user *, size_t, loff_t *);
+static ssize_t ac_write (struct file *, const char __user *, size_t, loff_t *);
+static int ac_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+static irqreturn_t ac_interrupt(int, void *, struct pt_regs *);
+
+static struct file_operations ac_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = ac_read,
+ .write = ac_write,
+ .ioctl = ac_ioctl,
+};
+
+static struct miscdevice ac_miscdev = {
+ AC_MINOR,
+ "ac",
+ &ac_fops
+};
+
+static int dummy; /* dev_id for request_irq() */
+
+static int ac_register_board(unsigned long physloc, void __iomem *loc,
+ unsigned char boardno)
+{
+ volatile unsigned char byte_reset_it;
+
+ if((readb(loc + CONF_END_TEST) != 0x00) ||
+ (readb(loc + CONF_END_TEST + 1) != 0x55) ||
+ (readb(loc + CONF_END_TEST + 2) != 0xAA) ||
+ (readb(loc + CONF_END_TEST + 3) != 0xFF))
+ return 0;
+
+ if (!boardno)
+ boardno = readb(loc + NUMCARD_OWNER_TO_PC);
+
+ if (!boardno && boardno > MAX_BOARD) {
+ printk(KERN_WARNING "Board #%d (at 0x%lx) is out of range (1 <= x <= %d).\n",
+ boardno, physloc, MAX_BOARD);
+ return 0;
+ }
+
+ if (apbs[boardno - 1].RamIO) {
+ printk(KERN_WARNING "Board #%d (at 0x%lx) conflicts with previous board #%d (at 0x%lx)\n",
+ boardno, physloc, boardno, apbs[boardno-1].PhysIO);
+ return 0;
+ }
+
+ boardno--;
+
+ apbs[boardno].PhysIO = physloc;
+ apbs[boardno].RamIO = loc;
+ init_waitqueue_head(&apbs[boardno].FlagSleepSend);
+ spin_lock_init(&apbs[boardno].mutex);
+ byte_reset_it = readb(loc + RAM_IT_TO_PC);
+
+ numboards++;
+ return boardno + 1;
+}
+
+#ifdef MODULE
+
+#define applicom_init init_module
+
+void cleanup_module(void)
+{
+ int i;
+
+ misc_deregister(&ac_miscdev);
+
+ for (i = 0; i < MAX_BOARD; i++) {
+
+ if (!apbs[i].RamIO)
+ continue;
+
+ if (apbs[i].irq)
+ free_irq(apbs[i].irq, &dummy);
+
+ iounmap(apbs[i].RamIO);
+ }
+}
+
+#endif /* MODULE */
+
+int __init applicom_init(void)
+{
+ int i, numisa = 0;
+ struct pci_dev *dev = NULL;
+ void __iomem *RamIO;
+ int boardno;
+
+ printk(KERN_INFO "Applicom driver: $Id: ac.c,v 1.30 2000/03/22 16:03:57 dwmw2 Exp $\n");
+
+ /* No mem and irq given - check for a PCI card */
+
+ while ( (dev = pci_get_class(PCI_CLASS_OTHERS << 16, dev))) {
+
+ if (dev->vendor != PCI_VENDOR_ID_APPLICOM)
+ continue;
+
+ if (dev->device > MAX_PCI_DEVICE_NUM || dev->device == 0)
+ continue;
+
+ if (pci_enable_device(dev))
+ return -EIO;
+
+ RamIO = ioremap(dev->resource[0].start, LEN_RAM_IO);
+
+ if (!RamIO) {
+ printk(KERN_INFO "ac.o: Failed to ioremap PCI memory space at 0x%lx\n", dev->resource[0].start);
+ pci_disable_device(dev);
+ return -EIO;
+ }
+
+ printk(KERN_INFO "Applicom %s found at mem 0x%lx, irq %d\n",
+ applicom_pci_devnames[dev->device-1], dev->resource[0].start,
+ dev->irq);
+
+ boardno = ac_register_board(dev->resource[0].start, RamIO,0);
+ if (!boardno) {
+ printk(KERN_INFO "ac.o: PCI Applicom device doesn't have correct signature.\n");
+ iounmap(RamIO);
+ pci_disable_device(dev);
+ continue;
+ }
+
+ if (request_irq(dev->irq, &ac_interrupt, SA_SHIRQ, "Applicom PCI", &dummy)) {
+ printk(KERN_INFO "Could not allocate IRQ %d for PCI Applicom device.\n", dev->irq);
+ iounmap(RamIO);
+ pci_disable_device(dev);
+ apbs[boardno - 1].RamIO = NULL;
+ continue;
+ }
+
+ /* Enable interrupts. */
+
+ writeb(0x40, apbs[boardno - 1].RamIO + RAM_IT_FROM_PC);
+
+ apbs[boardno - 1].irq = dev->irq;
+ }
+
+ /* Finished with PCI cards. If none registered,
+ * and there was no mem/irq specified, exit */
+
+ if (!mem || !irq) {
+ if (numboards)
+ goto fin;
+ else {
+ printk(KERN_INFO "ac.o: No PCI boards found.\n");
+ printk(KERN_INFO "ac.o: For an ISA board you must supply memory and irq parameters.\n");
+ return -ENXIO;
+ }
+ }
+
+ /* Now try the specified ISA cards */
+
+ for (i = 0; i < MAX_ISA_BOARD; i++) {
+ RamIO = ioremap(mem + (LEN_RAM_IO * i), LEN_RAM_IO);
+
+ if (!RamIO) {
+ printk(KERN_INFO "ac.o: Failed to ioremap the ISA card's memory space (slot #%d)\n", i + 1);
+ continue;
+ }
+
+ if (!(boardno = ac_register_board((unsigned long)mem+ (LEN_RAM_IO*i),
+ RamIO,i+1))) {
+ iounmap(RamIO);
+ continue;
+ }
+
+ printk(KERN_NOTICE "Applicom ISA card found at mem 0x%lx, irq %d\n", mem + (LEN_RAM_IO*i), irq);
+
+ if (!numisa) {
+ if (request_irq(irq, &ac_interrupt, SA_SHIRQ, "Applicom ISA", &dummy)) {
+ printk(KERN_WARNING "Could not allocate IRQ %d for ISA Applicom device.\n", irq);
+ iounmap(RamIO);
+ apbs[boardno - 1].RamIO = NULL;
+ }
+ else
+ apbs[boardno - 1].irq = irq;
+ }
+ else
+ apbs[boardno - 1].irq = 0;
+
+ numisa++;
+ }
+
+ if (!numisa)
+ printk(KERN_WARNING"ac.o: No valid ISA Applicom boards found at mem 0x%lx\n",mem);
+
+ fin:
+ init_waitqueue_head(&FlagSleepRec);
+
+ WriteErrorCount = 0;
+ ReadErrorCount = 0;
+ DeviceErrorCount = 0;
+
+ if (numboards) {
+ misc_register(&ac_miscdev);
+ for (i = 0; i < MAX_BOARD; i++) {
+ int serial;
+ char boardname[(SERIAL_NUMBER - TYPE_CARD) + 1];
+
+ if (!apbs[i].RamIO)
+ continue;
+
+ for (serial = 0; serial < SERIAL_NUMBER - TYPE_CARD; serial++)
+ boardname[serial] = readb(apbs[i].RamIO + TYPE_CARD + serial);
+
+ boardname[serial] = 0;
+
+
+ printk(KERN_INFO "Applicom board %d: %s, PROM V%d.%d",
+ i+1, boardname,
+ (int)(readb(apbs[i].RamIO + VERS) >> 4),
+ (int)(readb(apbs[i].RamIO + VERS) & 0xF));
+
+ serial = (readb(apbs[i].RamIO + SERIAL_NUMBER) << 16) +
+ (readb(apbs[i].RamIO + SERIAL_NUMBER + 1) << 8) +
+ (readb(apbs[i].RamIO + SERIAL_NUMBER + 2) );
+
+ if (serial != 0)
+ printk(" S/N %d\n", serial);
+ else
+ printk("\n");
+ }
+ return 0;
+ }
+
+ else
+ return -ENXIO;
+}
+
+
+#ifndef MODULE
+__initcall(applicom_init);
+#endif
+
+static ssize_t ac_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
+{
+ unsigned int NumCard; /* Board number 1 -> 8 */
+ unsigned int IndexCard; /* Index board number 0 -> 7 */
+ unsigned char TicCard; /* Board TIC to send */
+ unsigned long flags; /* Current priority */
+ struct st_ram_io st_loc;
+ struct mailbox tmpmailbox;
+#ifdef DEBUG
+ int c;
+#endif
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (count != sizeof(struct st_ram_io) + sizeof(struct mailbox)) {
+ static int warncount = 5;
+ if (warncount) {
+ printk(KERN_INFO "Hmmm. write() of Applicom card, length %zd != expected %zd\n",
+ count, sizeof(struct st_ram_io) + sizeof(struct mailbox));
+ warncount--;
+ }
+ return -EINVAL;
+ }
+
+ if(copy_from_user(&st_loc, buf, sizeof(struct st_ram_io)))
+ return -EFAULT;
+
+ if(copy_from_user(&tmpmailbox, &buf[sizeof(struct st_ram_io)],
+ sizeof(struct mailbox)))
+ return -EFAULT;
+
+ NumCard = st_loc.num_card; /* board number to send */
+ TicCard = st_loc.tic_des_from_pc; /* tic number to send */
+ IndexCard = NumCard - 1;
+
+ if((NumCard < 1) || (NumCard > MAX_BOARD) || !apbs[IndexCard].RamIO)
+ return -EINVAL;
+
+#ifdef DEBUG
+ printk("Write to applicom card #%d. struct st_ram_io follows:",
+ IndexCard+1);
+
+ for (c = 0; c < sizeof(struct st_ram_io);) {
+
+ printk("\n%5.5X: %2.2X", c, ((unsigned char *) &st_loc)[c]);
+
+ for (c++; c % 8 && c < sizeof(struct st_ram_io); c++) {
+ printk(" %2.2X", ((unsigned char *) &st_loc)[c]);
+ }
+ }
+
+ printk("\nstruct mailbox follows:");
+
+ for (c = 0; c < sizeof(struct mailbox);) {
+ printk("\n%5.5X: %2.2X", c, ((unsigned char *) &tmpmailbox)[c]);
+
+ for (c++; c % 8 && c < sizeof(struct mailbox); c++) {
+ printk(" %2.2X", ((unsigned char *) &tmpmailbox)[c]);
+ }
+ }
+
+ printk("\n");
+#endif
+
+ spin_lock_irqsave(&apbs[IndexCard].mutex, flags);
+
+ /* Test octet ready correct */
+ if(readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY) > 2) {
+ Dummy = readb(apbs[IndexCard].RamIO + VERS);
+ spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+ printk(KERN_WARNING "APPLICOM driver write error board %d, DataFromPcReady = %d\n",
+ IndexCard,(int)readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY));
+ DeviceErrorCount++;
+ return -EIO;
+ }
+
+ /* Place ourselves on the wait queue */
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&apbs[IndexCard].FlagSleepSend, &wait);
+
+ /* Check whether the card is ready for us */
+ while (readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY) != 0) {
+ Dummy = readb(apbs[IndexCard].RamIO + VERS);
+ /* It's busy. Sleep. */
+
+ spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+ schedule();
+ if (signal_pending(current)) {
+ remove_wait_queue(&apbs[IndexCard].FlagSleepSend,
+ &wait);
+ return -EINTR;
+ }
+ spin_lock_irqsave(&apbs[IndexCard].mutex, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+
+ /* We may not have actually slept */
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&apbs[IndexCard].FlagSleepSend, &wait);
+
+ writeb(1, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+
+ /* Which is best - lock down the pages with rawio and then
+ copy directly, or use bounce buffers? For now we do the latter
+ because it works with 2.2 still */
+ {
+ unsigned char *from = (unsigned char *) &tmpmailbox;
+ void __iomem *to = apbs[IndexCard].RamIO + RAM_FROM_PC;
+ int c;
+
+ for (c = 0; c < sizeof(struct mailbox); c++)
+ writeb(*(from++), to++);
+ }
+
+ writeb(0x20, apbs[IndexCard].RamIO + TIC_OWNER_FROM_PC);
+ writeb(0xff, apbs[IndexCard].RamIO + NUMCARD_OWNER_FROM_PC);
+ writeb(TicCard, apbs[IndexCard].RamIO + TIC_DES_FROM_PC);
+ writeb(NumCard, apbs[IndexCard].RamIO + NUMCARD_DES_FROM_PC);
+ writeb(2, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+ writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+ Dummy = readb(apbs[IndexCard].RamIO + VERS);
+ spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+ return 0;
+}
+
+static int do_ac_read(int IndexCard, char __user *buf,
+ struct st_ram_io *st_loc, struct mailbox *mailbox)
+{
+ void __iomem *from = apbs[IndexCard].RamIO + RAM_TO_PC;
+ unsigned char *to = (unsigned char *)&mailbox;
+#ifdef DEBUG
+ int c;
+#endif
+
+ st_loc->tic_owner_to_pc = readb(apbs[IndexCard].RamIO + TIC_OWNER_TO_PC);
+ st_loc->numcard_owner_to_pc = readb(apbs[IndexCard].RamIO + NUMCARD_OWNER_TO_PC);
+
+
+ {
+ int c;
+
+ for (c = 0; c < sizeof(struct mailbox); c++)
+ *(to++) = readb(from++);
+ }
+ writeb(1, apbs[IndexCard].RamIO + ACK_FROM_PC_READY);
+ writeb(1, apbs[IndexCard].RamIO + TYP_ACK_FROM_PC);
+ writeb(IndexCard+1, apbs[IndexCard].RamIO + NUMCARD_ACK_FROM_PC);
+ writeb(readb(apbs[IndexCard].RamIO + TIC_OWNER_TO_PC),
+ apbs[IndexCard].RamIO + TIC_ACK_FROM_PC);
+ writeb(2, apbs[IndexCard].RamIO + ACK_FROM_PC_READY);
+ writeb(0, apbs[IndexCard].RamIO + DATA_TO_PC_READY);
+ writeb(2, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+ Dummy = readb(apbs[IndexCard].RamIO + VERS);
+
+#ifdef DEBUG
+ printk("Read from applicom card #%d. struct st_ram_io follows:", NumCard);
+
+ for (c = 0; c < sizeof(struct st_ram_io);) {
+ printk("\n%5.5X: %2.2X", c, ((unsigned char *)st_loc)[c]);
+
+ for (c++; c % 8 && c < sizeof(struct st_ram_io); c++) {
+ printk(" %2.2X", ((unsigned char *)st_loc)[c]);
+ }
+ }
+
+ printk("\nstruct mailbox follows:");
+
+ for (c = 0; c < sizeof(struct mailbox);) {
+ printk("\n%5.5X: %2.2X", c, ((unsigned char *)mailbox)[c]);
+
+ for (c++; c % 8 && c < sizeof(struct mailbox); c++) {
+ printk(" %2.2X", ((unsigned char *)mailbox)[c]);
+ }
+ }
+ printk("\n");
+#endif
+ return (sizeof(struct st_ram_io) + sizeof(struct mailbox));
+}
+
+static ssize_t ac_read (struct file *filp, char __user *buf, size_t count, loff_t *ptr)
+{
+ unsigned long flags;
+ unsigned int i;
+ unsigned char tmp;
+ int ret = 0;
+ DECLARE_WAITQUEUE(wait, current);
+#ifdef DEBUG
+ int loopcount=0;
+#endif
+ /* No need to ratelimit this. Only root can trigger it anyway */
+ if (count != sizeof(struct st_ram_io) + sizeof(struct mailbox)) {
+ printk( KERN_WARNING "Hmmm. read() of Applicom card, length %zd != expected %zd\n",
+ count,sizeof(struct st_ram_io) + sizeof(struct mailbox));
+ return -EINVAL;
+ }
+
+ while(1) {
+ /* Stick ourself on the wait queue */
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&FlagSleepRec, &wait);
+
+ /* Scan each board, looking for one which has a packet for us */
+ for (i=0; i < MAX_BOARD; i++) {
+ if (!apbs[i].RamIO)
+ continue;
+ spin_lock_irqsave(&apbs[i].mutex, flags);
+
+ tmp = readb(apbs[i].RamIO + DATA_TO_PC_READY);
+
+ if (tmp == 2) {
+ struct st_ram_io st_loc;
+ struct mailbox mailbox;
+
+ /* Got a packet for us */
+ ret = do_ac_read(i, buf, &st_loc, &mailbox);
+ spin_unlock_irqrestore(&apbs[i].mutex, flags);
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&FlagSleepRec, &wait);
+
+ if (copy_to_user(buf, &st_loc, sizeof(st_loc)))
+ return -EFAULT;
+ if (copy_to_user(buf + sizeof(st_loc), &mailbox, sizeof(mailbox)))
+ return -EFAULT;
+ return tmp;
+ }
+
+ if (tmp > 2) {
+ /* Got an error */
+ Dummy = readb(apbs[i].RamIO + VERS);
+
+ spin_unlock_irqrestore(&apbs[i].mutex, flags);
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&FlagSleepRec, &wait);
+
+ printk(KERN_WARNING "APPLICOM driver read error board %d, DataToPcReady = %d\n",
+ i,(int)readb(apbs[i].RamIO + DATA_TO_PC_READY));
+ DeviceErrorCount++;
+ return -EIO;
+ }
+
+ /* Nothing for us. Try the next board */
+ Dummy = readb(apbs[i].RamIO + VERS);
+ spin_unlock_irqrestore(&apbs[i].mutex, flags);
+
+ } /* per board */
+
+ /* OK - No boards had data for us. Sleep now */
+
+ schedule();
+ remove_wait_queue(&FlagSleepRec, &wait);
+
+ if (signal_pending(current))
+ return -EINTR;
+
+#ifdef DEBUG
+ if (loopcount++ > 2) {
+ printk("Looping in ac_read. loopcount %d\n", loopcount);
+ }
+#endif
+ }
+}
+
+static irqreturn_t ac_interrupt(int vec, void *dev_instance, struct pt_regs *regs)
+{
+ unsigned int i;
+ unsigned int FlagInt;
+ unsigned int LoopCount;
+ int handled = 0;
+
+ // printk("Applicom interrupt on IRQ %d occurred\n", vec);
+
+ LoopCount = 0;
+
+ do {
+ FlagInt = 0;
+ for (i = 0; i < MAX_BOARD; i++) {
+
+ /* Skip if this board doesn't exist */
+ if (!apbs[i].RamIO)
+ continue;
+
+ spin_lock(&apbs[i].mutex);
+
+ /* Skip if this board doesn't want attention */
+ if(readb(apbs[i].RamIO + RAM_IT_TO_PC) == 0) {
+ spin_unlock(&apbs[i].mutex);
+ continue;
+ }
+
+ handled = 1;
+ FlagInt = 1;
+ writeb(0, apbs[i].RamIO + RAM_IT_TO_PC);
+
+ if (readb(apbs[i].RamIO + DATA_TO_PC_READY) > 2) {
+ printk(KERN_WARNING "APPLICOM driver interrupt err board %d, DataToPcReady = %d\n",
+ i+1,(int)readb(apbs[i].RamIO + DATA_TO_PC_READY));
+ DeviceErrorCount++;
+ }
+
+ if((readb(apbs[i].RamIO + DATA_FROM_PC_READY) > 2) &&
+ (readb(apbs[i].RamIO + DATA_FROM_PC_READY) != 6)) {
+
+ printk(KERN_WARNING "APPLICOM driver interrupt err board %d, DataFromPcReady = %d\n",
+ i+1,(int)readb(apbs[i].RamIO + DATA_FROM_PC_READY));
+ DeviceErrorCount++;
+ }
+
+ if (readb(apbs[i].RamIO + DATA_TO_PC_READY) == 2) { /* mailbox sent by the card ? */
+ if (waitqueue_active(&FlagSleepRec)) {
+ wake_up_interruptible(&FlagSleepRec);
+ }
+ }
+
+ if (readb(apbs[i].RamIO + DATA_FROM_PC_READY) == 0) { /* ram i/o free for write by pc ? */
+ if (waitqueue_active(&apbs[i].FlagSleepSend)) { /* process sleep during read ? */
+ wake_up_interruptible(&apbs[i].FlagSleepSend);
+ }
+ }
+ Dummy = readb(apbs[i].RamIO + VERS);
+
+ if(readb(apbs[i].RamIO + RAM_IT_TO_PC)) {
+ /* There's another int waiting on this card */
+ spin_unlock(&apbs[i].mutex);
+ i--;
+ } else {
+ spin_unlock(&apbs[i].mutex);
+ }
+ }
+ if (FlagInt)
+ LoopCount = 0;
+ else
+ LoopCount++;
+ } while(LoopCount < 2);
+ return IRQ_RETVAL(handled);
+}
+
+
+
+static int ac_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+
+{ /* @ ADG ou ATO selon le cas */
+ int i;
+ unsigned char IndexCard;
+ void __iomem *pmem;
+ int ret = 0;
+ volatile unsigned char byte_reset_it;
+ struct st_ram_io *adgl;
+ void __user *argp = (void __user *)arg;
+
+ /* In general, the device is only openable by root anyway, so we're not
+ particularly concerned that bogus ioctls can flood the console. */
+
+ adgl = kmalloc(sizeof(struct st_ram_io), GFP_KERNEL);
+ if (!adgl)
+ return -ENOMEM;
+
+ if (copy_from_user(adgl, argp, sizeof(struct st_ram_io))) {
+ kfree(adgl);
+ return -EFAULT;
+ }
+
+ IndexCard = adgl->num_card-1;
+
+ if(cmd != 0 && cmd != 6 &&
+ ((IndexCard >= MAX_BOARD) || !apbs[IndexCard].RamIO)) {
+ static int warncount = 10;
+ if (warncount) {
+ printk( KERN_WARNING "APPLICOM driver IOCTL, bad board number %d\n",(int)IndexCard+1);
+ warncount--;
+ }
+ kfree(adgl);
+ return -EINVAL;
+ }
+
+ switch (cmd) {
+
+ case 0:
+ pmem = apbs[IndexCard].RamIO;
+ for (i = 0; i < sizeof(struct st_ram_io); i++)
+ ((unsigned char *)adgl)[i]=readb(pmem++);
+ if (copy_to_user(argp, adgl, sizeof(struct st_ram_io)))
+ ret = -EFAULT;
+ break;
+ case 1:
+ pmem = apbs[IndexCard].RamIO + CONF_END_TEST;
+ for (i = 0; i < 4; i++)
+ adgl->conf_end_test[i] = readb(pmem++);
+ for (i = 0; i < 2; i++)
+ adgl->error_code[i] = readb(pmem++);
+ for (i = 0; i < 4; i++)
+ adgl->parameter_error[i] = readb(pmem++);
+ pmem = apbs[IndexCard].RamIO + VERS;
+ adgl->vers = readb(pmem);
+ pmem = apbs[IndexCard].RamIO + TYPE_CARD;
+ for (i = 0; i < 20; i++)
+ adgl->reserv1[i] = readb(pmem++);
+ *(int *)&adgl->reserv1[20] =
+ (readb(apbs[IndexCard].RamIO + SERIAL_NUMBER) << 16) +
+ (readb(apbs[IndexCard].RamIO + SERIAL_NUMBER + 1) << 8) +
+ (readb(apbs[IndexCard].RamIO + SERIAL_NUMBER + 2) );
+
+ if (copy_to_user(argp, adgl, sizeof(struct st_ram_io)))
+ ret = -EFAULT;
+ break;
+ case 2:
+ pmem = apbs[IndexCard].RamIO + CONF_END_TEST;
+ for (i = 0; i < 10; i++)
+ writeb(0xff, pmem++);
+ writeb(adgl->data_from_pc_ready,
+ apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+
+ writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+
+ for (i = 0; i < MAX_BOARD; i++) {
+ if (apbs[i].RamIO) {
+ byte_reset_it = readb(apbs[i].RamIO + RAM_IT_TO_PC);
+ }
+ }
+ break;
+ case 3:
+ pmem = apbs[IndexCard].RamIO + TIC_DES_FROM_PC;
+ writeb(adgl->tic_des_from_pc, pmem);
+ break;
+ case 4:
+ pmem = apbs[IndexCard].RamIO + TIC_OWNER_TO_PC;
+ adgl->tic_owner_to_pc = readb(pmem++);
+ adgl->numcard_owner_to_pc = readb(pmem);
+ if (copy_to_user(argp, adgl,sizeof(struct st_ram_io)))
+ ret = -EFAULT;
+ break;
+ case 5:
+ writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_OWNER_TO_PC);
+ writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_DES_FROM_PC);
+ writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_ACK_FROM_PC);
+ writeb(4, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+ writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+ break;
+ case 6:
+ printk(KERN_INFO "APPLICOM driver release .... V2.8.0 ($Revision: 1.30 $)\n");
+ printk(KERN_INFO "Number of installed boards . %d\n", (int) numboards);
+ printk(KERN_INFO "Segment of board ........... %X\n", (int) mem);
+ printk(KERN_INFO "Interrupt IRQ number ....... %d\n", (int) irq);
+ for (i = 0; i < MAX_BOARD; i++) {
+ int serial;
+ char boardname[(SERIAL_NUMBER - TYPE_CARD) + 1];
+
+ if (!apbs[i].RamIO)
+ continue;
+
+ for (serial = 0; serial < SERIAL_NUMBER - TYPE_CARD; serial++)
+ boardname[serial] = readb(apbs[i].RamIO + TYPE_CARD + serial);
+ boardname[serial] = 0;
+
+ printk(KERN_INFO "Prom version board %d ....... V%d.%d %s",
+ i+1,
+ (int)(readb(apbs[IndexCard].RamIO + VERS) >> 4),
+ (int)(readb(apbs[IndexCard].RamIO + VERS) & 0xF),
+ boardname);
+
+
+ serial = (readb(apbs[i].RamIO + SERIAL_NUMBER) << 16) +
+ (readb(apbs[i].RamIO + SERIAL_NUMBER + 1) << 8) +
+ (readb(apbs[i].RamIO + SERIAL_NUMBER + 2) );
+
+ if (serial != 0)
+ printk(" S/N %d\n", serial);
+ else
+ printk("\n");
+ }
+ if (DeviceErrorCount != 0)
+ printk(KERN_INFO "DeviceErrorCount ........... %d\n", DeviceErrorCount);
+ if (ReadErrorCount != 0)
+ printk(KERN_INFO "ReadErrorCount ............. %d\n", ReadErrorCount);
+ if (WriteErrorCount != 0)
+ printk(KERN_INFO "WriteErrorCount ............ %d\n", WriteErrorCount);
+ if (waitqueue_active(&FlagSleepRec))
+ printk(KERN_INFO "Process in read pending\n");
+ for (i = 0; i < MAX_BOARD; i++) {
+ if (apbs[i].RamIO && waitqueue_active(&apbs[i].FlagSleepSend))
+ printk(KERN_INFO "Process in write pending board %d\n",i+1);
+ }
+ break;
+ default:
+ printk(KERN_INFO "APPLICOM driver ioctl, unknown function code %d\n",cmd) ;
+ ret = -EINVAL;
+ break;
+ }
+ Dummy = readb(apbs[IndexCard].RamIO + VERS);
+ kfree(adgl);
+ return 0;
+}
+
+#ifndef MODULE
+static int __init applicom_setup(char *str)
+{
+ int ints[4];
+
+ (void) get_options(str, 4, ints);
+
+ if (ints[0] > 2) {
+ printk(KERN_WARNING "Too many arguments to 'applicom=', expected mem,irq only.\n");
+ }
+
+ if (ints[0] < 2) {
+ printk(KERN_INFO"applicom numargs: %d\n", ints[0]);
+ return 0;
+ }
+
+ mem = ints[1];
+ irq = ints[2];
+ return 1;
+}
+
+__setup("applicom=", applicom_setup);
+
+#endif /* MODULE */
+
diff --git a/drivers/char/applicom.h b/drivers/char/applicom.h
new file mode 100644
index 000000000000..35530b3d9bd6
--- /dev/null
+++ b/drivers/char/applicom.h
@@ -0,0 +1,85 @@
+/* $Id: applicom.h,v 1.2 1999/08/28 15:09:49 dwmw2 Exp $ */
+
+
+#ifndef __LINUX_APPLICOM_H__
+#define __LINUX_APPLICOM_H__
+
+
+#define DATA_TO_PC_READY 0x00
+#define TIC_OWNER_TO_PC 0x01
+#define NUMCARD_OWNER_TO_PC 0x02
+#define TIC_DES_TO_PC 0x03
+#define NUMCARD_DES_TO_PC 0x04
+#define DATA_FROM_PC_READY 0x05
+#define TIC_OWNER_FROM_PC 0x06
+#define NUMCARD_OWNER_FROM_PC 0x07
+#define TIC_DES_FROM_PC 0x08
+#define NUMCARD_DES_FROM_PC 0x09
+#define ACK_FROM_PC_READY 0x0E
+#define TIC_ACK_FROM_PC 0x0F
+#define NUMCARD_ACK_FROM_PC 0x010
+#define TYP_ACK_FROM_PC 0x011
+#define CONF_END_TEST 0x012
+#define ERROR_CODE 0x016
+#define PARAMETER_ERROR 0x018
+#define VERS 0x01E
+#define RAM_TO_PC 0x040
+#define RAM_FROM_PC 0x0170
+#define TYPE_CARD 0x03C0
+#define SERIAL_NUMBER 0x03DA
+#define RAM_IT_FROM_PC 0x03FE
+#define RAM_IT_TO_PC 0x03FF
+
+struct mailbox{
+ u16 stjb_codef; /* offset 00 */
+ s16 stjb_status; /* offset 02 */
+ u16 stjb_ticuser_root; /* offset 04 */
+ u8 stjb_piduser[4]; /* offset 06 */
+ u16 stjb_mode; /* offset 0A */
+ u16 stjb_time; /* offset 0C */
+ u16 stjb_stop; /* offset 0E */
+ u16 stjb_nfonc; /* offset 10 */
+ u16 stjb_ncard; /* offset 12 */
+ u16 stjb_nchan; /* offset 14 */
+ u16 stjb_nes; /* offset 16 */
+ u16 stjb_nb; /* offset 18 */
+ u16 stjb_typvar; /* offset 1A */
+ u32 stjb_adr; /* offset 1C */
+ u16 stjb_ticuser_dispcyc; /* offset 20 */
+ u16 stjb_ticuser_protocol; /* offset 22 */
+ u8 stjb_filler[12]; /* offset 24 */
+ u8 stjb_data[256]; /* offset 30 */
+ };
+
+struct st_ram_io
+{
+ unsigned char data_to_pc_ready;
+ unsigned char tic_owner_to_pc;
+ unsigned char numcard_owner_to_pc;
+ unsigned char tic_des_to_pc;
+ unsigned char numcard_des_to_pc;
+ unsigned char data_from_pc_ready;
+ unsigned char tic_owner_from_pc;
+ unsigned char numcard_owner_from_pc;
+ unsigned char tic_des_from_pc;
+ unsigned char numcard_des_from_pc;
+ unsigned char ack_to_pc_ready;
+ unsigned char tic_ack_to_pc;
+ unsigned char numcard_ack_to_pc;
+ unsigned char typ_ack_to_pc;
+ unsigned char ack_from_pc_ready;
+ unsigned char tic_ack_from_pc;
+ unsigned char numcard_ack_from_pc;
+ unsigned char typ_ack_from_pc;
+ unsigned char conf_end_test[4];
+ unsigned char error_code[2];
+ unsigned char parameter_error[4];
+ unsigned char time_base;
+ unsigned char nul_inc;
+ unsigned char vers;
+ unsigned char num_card;
+ unsigned char reserv1[32];
+};
+
+
+#endif /* __LINUX_APPLICOM_H__ */
diff --git a/drivers/char/cd1865.h b/drivers/char/cd1865.h
new file mode 100644
index 000000000000..9940966e7a1d
--- /dev/null
+++ b/drivers/char/cd1865.h
@@ -0,0 +1,263 @@
+/*
+ * linux/drivers/char/cd1865.h -- Definitions relating to the CD1865
+ * for the Specialix IO8+ multiport serial driver.
+ *
+ * Copyright (C) 1997 Roger Wolff (R.E.Wolff@BitWizard.nl)
+ * Copyright (C) 1994-1996 Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact io8-linux@specialix.co.uk if you require
+ * support.
+ *
+ * This driver was developped in the BitWizard linux device
+ * driver service. If you require a linux device driver for your
+ * product, please contact devices@BitWizard.nl for a quote.
+ *
+ */
+
+/*
+ * Definitions for Driving CD180/CD1864/CD1865 based eightport serial cards.
+ */
+
+
+/* Values of choice for Interrupt ACKs */
+/* These values are "obligatory" if you use the register based
+ * interrupt acknowledgements. See page 99-101 of V2.0 of the CD1865
+ * databook */
+#define SX_ACK_MINT 0x75 /* goes to PILR1 */
+#define SX_ACK_TINT 0x76 /* goes to PILR2 */
+#define SX_ACK_RINT 0x77 /* goes to PILR3 */
+
+/* Chip ID (is used when chips ar daisy chained.) */
+#define SX_ID 0x10
+
+/* Definitions for Cirrus Logic CL-CD186x 8-port async mux chip */
+
+#define CD186x_NCH 8 /* Total number of channels */
+#define CD186x_TPC 16 /* Ticks per character */
+#define CD186x_NFIFO 8 /* TX FIFO size */
+
+
+/* Global registers */
+
+#define CD186x_GIVR 0x40 /* Global Interrupt Vector Register */
+#define CD186x_GICR 0x41 /* Global Interrupting Channel Register */
+#define CD186x_PILR1 0x61 /* Priority Interrupt Level Register 1 */
+#define CD186x_PILR2 0x62 /* Priority Interrupt Level Register 2 */
+#define CD186x_PILR3 0x63 /* Priority Interrupt Level Register 3 */
+#define CD186x_CAR 0x64 /* Channel Access Register */
+#define CD186x_SRSR 0x65 /* Channel Access Register */
+#define CD186x_GFRCR 0x6b /* Global Firmware Revision Code Register */
+#define CD186x_PPRH 0x70 /* Prescaler Period Register High */
+#define CD186x_PPRL 0x71 /* Prescaler Period Register Low */
+#define CD186x_RDR 0x78 /* Receiver Data Register */
+#define CD186x_RCSR 0x7a /* Receiver Character Status Register */
+#define CD186x_TDR 0x7b /* Transmit Data Register */
+#define CD186x_EOIR 0x7f /* End of Interrupt Register */
+#define CD186x_MRAR 0x75 /* Modem Request Acknowledge register */
+#define CD186x_TRAR 0x76 /* Transmit Request Acknowledge register */
+#define CD186x_RRAR 0x77 /* Receive Request Acknowledge register */
+#define CD186x_SRCR 0x66 /* Service Request Configuration register */
+
+/* Channel Registers */
+
+#define CD186x_CCR 0x01 /* Channel Command Register */
+#define CD186x_IER 0x02 /* Interrupt Enable Register */
+#define CD186x_COR1 0x03 /* Channel Option Register 1 */
+#define CD186x_COR2 0x04 /* Channel Option Register 2 */
+#define CD186x_COR3 0x05 /* Channel Option Register 3 */
+#define CD186x_CCSR 0x06 /* Channel Control Status Register */
+#define CD186x_RDCR 0x07 /* Receive Data Count Register */
+#define CD186x_SCHR1 0x09 /* Special Character Register 1 */
+#define CD186x_SCHR2 0x0a /* Special Character Register 2 */
+#define CD186x_SCHR3 0x0b /* Special Character Register 3 */
+#define CD186x_SCHR4 0x0c /* Special Character Register 4 */
+#define CD186x_MCOR1 0x10 /* Modem Change Option 1 Register */
+#define CD186x_MCOR2 0x11 /* Modem Change Option 2 Register */
+#define CD186x_MCR 0x12 /* Modem Change Register */
+#define CD186x_RTPR 0x18 /* Receive Timeout Period Register */
+#define CD186x_MSVR 0x28 /* Modem Signal Value Register */
+#define CD186x_MSVRTS 0x29 /* Modem Signal Value Register */
+#define CD186x_MSVDTR 0x2a /* Modem Signal Value Register */
+#define CD186x_RBPRH 0x31 /* Receive Baud Rate Period Register High */
+#define CD186x_RBPRL 0x32 /* Receive Baud Rate Period Register Low */
+#define CD186x_TBPRH 0x39 /* Transmit Baud Rate Period Register High */
+#define CD186x_TBPRL 0x3a /* Transmit Baud Rate Period Register Low */
+
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GIVR_ITMASK 0x07 /* Interrupt type mask */
+#define GIVR_IT_MODEM 0x01 /* Modem Signal Change Interrupt */
+#define GIVR_IT_TX 0x02 /* Transmit Data Interrupt */
+#define GIVR_IT_RCV 0x03 /* Receive Good Data Interrupt */
+#define GIVR_IT_REXC 0x07 /* Receive Exception Interrupt */
+
+
+/* Global Interrupt Channel Register (R/W) */
+
+#define GICR_CHAN 0x1c /* Channel Number Mask */
+#define GICR_CHAN_OFF 2 /* Channel Number shift */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN 0x07 /* Channel Number Mask */
+#define CAR_A7 0x08 /* A7 Address Extension (unused) */
+
+
+/* Receive Character Status Register (R/O) */
+
+#define RCSR_TOUT 0x80 /* Rx Timeout */
+#define RCSR_SCDET 0x70 /* Special Character Detected Mask */
+#define RCSR_NO_SC 0x00 /* No Special Characters Detected */
+#define RCSR_SC_1 0x10 /* Special Char 1 (or 1 & 3) Detected */
+#define RCSR_SC_2 0x20 /* Special Char 2 (or 2 & 4) Detected */
+#define RCSR_SC_3 0x30 /* Special Char 3 Detected */
+#define RCSR_SC_4 0x40 /* Special Char 4 Detected */
+#define RCSR_BREAK 0x08 /* Break has been detected */
+#define RCSR_PE 0x04 /* Parity Error */
+#define RCSR_FE 0x02 /* Frame Error */
+#define RCSR_OE 0x01 /* Overrun Error */
+
+
+/* Channel Command Register (R/W) (commands in groups can be OR-ed) */
+
+#define CCR_HARDRESET 0x81 /* Reset the chip */
+
+#define CCR_SOFTRESET 0x80 /* Soft Channel Reset */
+
+#define CCR_CORCHG1 0x42 /* Channel Option Register 1 Changed */
+#define CCR_CORCHG2 0x44 /* Channel Option Register 2 Changed */
+#define CCR_CORCHG3 0x48 /* Channel Option Register 3 Changed */
+
+#define CCR_SSCH1 0x21 /* Send Special Character 1 */
+
+#define CCR_SSCH2 0x22 /* Send Special Character 2 */
+
+#define CCR_SSCH3 0x23 /* Send Special Character 3 */
+
+#define CCR_SSCH4 0x24 /* Send Special Character 4 */
+
+#define CCR_TXEN 0x18 /* Enable Transmitter */
+#define CCR_RXEN 0x12 /* Enable Receiver */
+
+#define CCR_TXDIS 0x14 /* Disable Transmitter */
+#define CCR_RXDIS 0x11 /* Disable Receiver */
+
+
+/* Interrupt Enable Register (R/W) */
+
+#define IER_DSR 0x80 /* Enable interrupt on DSR change */
+#define IER_CD 0x40 /* Enable interrupt on CD change */
+#define IER_CTS 0x20 /* Enable interrupt on CTS change */
+#define IER_RXD 0x10 /* Enable interrupt on Receive Data */
+#define IER_RXSC 0x08 /* Enable interrupt on Receive Spec. Char */
+#define IER_TXRDY 0x04 /* Enable interrupt on TX FIFO empty */
+#define IER_TXEMPTY 0x02 /* Enable interrupt on TX completely empty */
+#define IER_RET 0x01 /* Enable interrupt on RX Exc. Timeout */
+
+
+/* Channel Option Register 1 (R/W) */
+
+#define COR1_ODDP 0x80 /* Odd Parity */
+#define COR1_PARMODE 0x60 /* Parity Mode mask */
+#define COR1_NOPAR 0x00 /* No Parity */
+#define COR1_FORCEPAR 0x20 /* Force Parity */
+#define COR1_NORMPAR 0x40 /* Normal Parity */
+#define COR1_IGNORE 0x10 /* Ignore Parity on RX */
+#define COR1_STOPBITS 0x0c /* Number of Stop Bits */
+#define COR1_1SB 0x00 /* 1 Stop Bit */
+#define COR1_15SB 0x04 /* 1.5 Stop Bits */
+#define COR1_2SB 0x08 /* 2 Stop Bits */
+#define COR1_CHARLEN 0x03 /* Character Length */
+#define COR1_5BITS 0x00 /* 5 bits */
+#define COR1_6BITS 0x01 /* 6 bits */
+#define COR1_7BITS 0x02 /* 7 bits */
+#define COR1_8BITS 0x03 /* 8 bits */
+
+
+/* Channel Option Register 2 (R/W) */
+
+#define COR2_IXM 0x80 /* Implied XON mode */
+#define COR2_TXIBE 0x40 /* Enable In-Band (XON/XOFF) Flow Control */
+#define COR2_ETC 0x20 /* Embedded Tx Commands Enable */
+#define COR2_LLM 0x10 /* Local Loopback Mode */
+#define COR2_RLM 0x08 /* Remote Loopback Mode */
+#define COR2_RTSAO 0x04 /* RTS Automatic Output Enable */
+#define COR2_CTSAE 0x02 /* CTS Automatic Enable */
+#define COR2_DSRAE 0x01 /* DSR Automatic Enable */
+
+
+/* Channel Option Register 3 (R/W) */
+
+#define COR3_XONCH 0x80 /* XON is a pair of characters (1 & 3) */
+#define COR3_XOFFCH 0x40 /* XOFF is a pair of characters (2 & 4) */
+#define COR3_FCT 0x20 /* Flow-Control Transparency Mode */
+#define COR3_SCDE 0x10 /* Special Character Detection Enable */
+#define COR3_RXTH 0x0f /* RX FIFO Threshold value (1-8) */
+
+
+/* Channel Control Status Register (R/O) */
+
+#define CCSR_RXEN 0x80 /* Receiver Enabled */
+#define CCSR_RXFLOFF 0x40 /* Receive Flow Off (XOFF was sent) */
+#define CCSR_RXFLON 0x20 /* Receive Flow On (XON was sent) */
+#define CCSR_TXEN 0x08 /* Transmitter Enabled */
+#define CCSR_TXFLOFF 0x04 /* Transmit Flow Off (got XOFF) */
+#define CCSR_TXFLON 0x02 /* Transmit Flow On (got XON) */
+
+
+/* Modem Change Option Register 1 (R/W) */
+
+#define MCOR1_DSRZD 0x80 /* Detect 0->1 transition of DSR */
+#define MCOR1_CDZD 0x40 /* Detect 0->1 transition of CD */
+#define MCOR1_CTSZD 0x20 /* Detect 0->1 transition of CTS */
+#define MCOR1_DTRTH 0x0f /* Auto DTR flow control Threshold (1-8) */
+#define MCOR1_NODTRFC 0x0 /* Automatic DTR flow control disabled */
+
+
+/* Modem Change Option Register 2 (R/W) */
+
+#define MCOR2_DSROD 0x80 /* Detect 1->0 transition of DSR */
+#define MCOR2_CDOD 0x40 /* Detect 1->0 transition of CD */
+#define MCOR2_CTSOD 0x20 /* Detect 1->0 transition of CTS */
+
+/* Modem Change Register (R/W) */
+
+#define MCR_DSRCHG 0x80 /* DSR Changed */
+#define MCR_CDCHG 0x40 /* CD Changed */
+#define MCR_CTSCHG 0x20 /* CTS Changed */
+
+
+/* Modem Signal Value Register (R/W) */
+
+#define MSVR_DSR 0x80 /* Current state of DSR input */
+#define MSVR_CD 0x40 /* Current state of CD input */
+#define MSVR_CTS 0x20 /* Current state of CTS input */
+#define MSVR_DTR 0x02 /* Current state of DTR output */
+#define MSVR_RTS 0x01 /* Current state of RTS output */
+
+
+/* Escape characters */
+
+#define CD186x_C_ESC 0x00 /* Escape character */
+#define CD186x_C_SBRK 0x81 /* Start sending BREAK */
+#define CD186x_C_DELAY 0x82 /* Delay output */
+#define CD186x_C_EBRK 0x83 /* Stop sending BREAK */
+
+#define SRSR_RREQint 0x10 /* This chip wants "rec" serviced */
+#define SRSR_TREQint 0x04 /* This chip wants "transmit" serviced */
+#define SRSR_MREQint 0x01 /* This chip wants "mdm change" serviced */
+
+
+
+#define SRCR_PKGTYPE 0x80
+#define SRCR_REGACKEN 0x40
+#define SRCR_DAISYEN 0x20
+#define SRCR_GLOBPRI 0x10
+#define SRCR_UNFAIR 0x08
+#define SRCR_AUTOPRI 0x02
+#define SRCR_PRISEL 0x01
+
+
diff --git a/drivers/char/consolemap.c b/drivers/char/consolemap.c
new file mode 100644
index 000000000000..406dea914635
--- /dev/null
+++ b/drivers/char/consolemap.c
@@ -0,0 +1,672 @@
+/*
+ * consolemap.c
+ *
+ * Mapping from internal code (such as Latin-1 or Unicode or IBM PC code)
+ * to font positions.
+ *
+ * aeb, 950210
+ *
+ * Support for multiple unimaps by Jakub Jelinek <jj@ultra.linux.cz>, July 1998
+ *
+ * Fix bug in inverse translation. Stanislav Voronyi <stas@cnti.uanet.kharkov.ua>, Dec 1998
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kd.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <asm/uaccess.h>
+#include <linux/consolemap.h>
+#include <linux/vt_kern.h>
+
+static unsigned short translations[][256] = {
+ /* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
+ {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+ },
+ /* VT100 graphics mapped to Unicode */
+ {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
+ 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+ 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+ },
+ /* IBM Codepage 437 mapped to Unicode */
+ {
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+ 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
+ 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
+ 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
+ 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
+ 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
+ 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
+ 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
+ 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+ },
+ /* User mapping -- default to codes for direct font mapping */
+ {
+ 0xf000, 0xf001, 0xf002, 0xf003, 0xf004, 0xf005, 0xf006, 0xf007,
+ 0xf008, 0xf009, 0xf00a, 0xf00b, 0xf00c, 0xf00d, 0xf00e, 0xf00f,
+ 0xf010, 0xf011, 0xf012, 0xf013, 0xf014, 0xf015, 0xf016, 0xf017,
+ 0xf018, 0xf019, 0xf01a, 0xf01b, 0xf01c, 0xf01d, 0xf01e, 0xf01f,
+ 0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf025, 0xf026, 0xf027,
+ 0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02d, 0xf02e, 0xf02f,
+ 0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
+ 0xf038, 0xf039, 0xf03a, 0xf03b, 0xf03c, 0xf03d, 0xf03e, 0xf03f,
+ 0xf040, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
+ 0xf048, 0xf049, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f,
+ 0xf050, 0xf051, 0xf052, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057,
+ 0xf058, 0xf059, 0xf05a, 0xf05b, 0xf05c, 0xf05d, 0xf05e, 0xf05f,
+ 0xf060, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
+ 0xf068, 0xf069, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f,
+ 0xf070, 0xf071, 0xf072, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077,
+ 0xf078, 0xf079, 0xf07a, 0xf07b, 0xf07c, 0xf07d, 0xf07e, 0xf07f,
+ 0xf080, 0xf081, 0xf082, 0xf083, 0xf084, 0xf085, 0xf086, 0xf087,
+ 0xf088, 0xf089, 0xf08a, 0xf08b, 0xf08c, 0xf08d, 0xf08e, 0xf08f,
+ 0xf090, 0xf091, 0xf092, 0xf093, 0xf094, 0xf095, 0xf096, 0xf097,
+ 0xf098, 0xf099, 0xf09a, 0xf09b, 0xf09c, 0xf09d, 0xf09e, 0xf09f,
+ 0xf0a0, 0xf0a1, 0xf0a2, 0xf0a3, 0xf0a4, 0xf0a5, 0xf0a6, 0xf0a7,
+ 0xf0a8, 0xf0a9, 0xf0aa, 0xf0ab, 0xf0ac, 0xf0ad, 0xf0ae, 0xf0af,
+ 0xf0b0, 0xf0b1, 0xf0b2, 0xf0b3, 0xf0b4, 0xf0b5, 0xf0b6, 0xf0b7,
+ 0xf0b8, 0xf0b9, 0xf0ba, 0xf0bb, 0xf0bc, 0xf0bd, 0xf0be, 0xf0bf,
+ 0xf0c0, 0xf0c1, 0xf0c2, 0xf0c3, 0xf0c4, 0xf0c5, 0xf0c6, 0xf0c7,
+ 0xf0c8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0cc, 0xf0cd, 0xf0ce, 0xf0cf,
+ 0xf0d0, 0xf0d1, 0xf0d2, 0xf0d3, 0xf0d4, 0xf0d5, 0xf0d6, 0xf0d7,
+ 0xf0d8, 0xf0d9, 0xf0da, 0xf0db, 0xf0dc, 0xf0dd, 0xf0de, 0xf0df,
+ 0xf0e0, 0xf0e1, 0xf0e2, 0xf0e3, 0xf0e4, 0xf0e5, 0xf0e6, 0xf0e7,
+ 0xf0e8, 0xf0e9, 0xf0ea, 0xf0eb, 0xf0ec, 0xf0ed, 0xf0ee, 0xf0ef,
+ 0xf0f0, 0xf0f1, 0xf0f2, 0xf0f3, 0xf0f4, 0xf0f5, 0xf0f6, 0xf0f7,
+ 0xf0f8, 0xf0f9, 0xf0fa, 0xf0fb, 0xf0fc, 0xf0fd, 0xf0fe, 0xf0ff
+ }
+};
+
+/* The standard kernel character-to-font mappings are not invertible
+ -- this is just a best effort. */
+
+#define MAX_GLYPH 512 /* Max possible glyph value */
+
+static int inv_translate[MAX_NR_CONSOLES];
+
+struct uni_pagedir {
+ u16 **uni_pgdir[32];
+ unsigned long refcount;
+ unsigned long sum;
+ unsigned char *inverse_translations[4];
+ int readonly;
+};
+
+static struct uni_pagedir *dflt;
+
+static void set_inverse_transl(struct vc_data *conp, struct uni_pagedir *p, int i)
+{
+ int j, glyph;
+ unsigned short *t = translations[i];
+ unsigned char *q;
+
+ if (!p) return;
+ q = p->inverse_translations[i];
+
+ if (!q) {
+ q = p->inverse_translations[i] = (unsigned char *)
+ kmalloc(MAX_GLYPH, GFP_KERNEL);
+ if (!q) return;
+ }
+ memset(q, 0, MAX_GLYPH);
+
+ for (j = 0; j < E_TABSZ; j++) {
+ glyph = conv_uni_to_pc(conp, t[j]);
+ if (glyph >= 0 && glyph < MAX_GLYPH && q[glyph] < 32) {
+ /* prefer '-' above SHY etc. */
+ q[glyph] = j;
+ }
+ }
+}
+
+unsigned short *set_translate(int m, struct vc_data *vc)
+{
+ inv_translate[vc->vc_num] = m;
+ return translations[m];
+}
+
+/*
+ * Inverse translation is impossible for several reasons:
+ * 1. The font<->character maps are not 1-1.
+ * 2. The text may have been written while a different translation map
+ * was active, or using Unicode.
+ * Still, it is now possible to a certain extent to cut and paste non-ASCII.
+ */
+unsigned char inverse_translate(struct vc_data *conp, int glyph)
+{
+ struct uni_pagedir *p;
+ if (glyph < 0 || glyph >= MAX_GLYPH)
+ return 0;
+ else if (!(p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc) ||
+ !p->inverse_translations[inv_translate[conp->vc_num]])
+ return glyph;
+ else
+ return p->inverse_translations[inv_translate[conp->vc_num]][glyph];
+}
+
+static void update_user_maps(void)
+{
+ int i;
+ struct uni_pagedir *p, *q = NULL;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ if (!vc_cons_allocated(i))
+ continue;
+ p = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
+ if (p && p != q) {
+ set_inverse_transl(vc_cons[i].d, p, USER_MAP);
+ q = p;
+ }
+ }
+}
+
+/*
+ * Load customizable translation table
+ * arg points to a 256 byte translation table.
+ *
+ * The "old" variants are for translation directly to font (using the
+ * 0xf000-0xf0ff "transparent" Unicodes) whereas the "new" variants set
+ * Unicodes explicitly.
+ */
+int con_set_trans_old(unsigned char __user * arg)
+{
+ int i;
+ unsigned short *p = translations[USER_MAP];
+
+ if (!access_ok(VERIFY_READ, arg, E_TABSZ))
+ return -EFAULT;
+
+ for (i=0; i<E_TABSZ ; i++) {
+ unsigned char uc;
+ __get_user(uc, arg+i);
+ p[i] = UNI_DIRECT_BASE | uc;
+ }
+
+ update_user_maps();
+ return 0;
+}
+
+int con_get_trans_old(unsigned char __user * arg)
+{
+ int i, ch;
+ unsigned short *p = translations[USER_MAP];
+
+ if (!access_ok(VERIFY_WRITE, arg, E_TABSZ))
+ return -EFAULT;
+
+ for (i=0; i<E_TABSZ ; i++)
+ {
+ ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
+ __put_user((ch & ~0xff) ? 0 : ch, arg+i);
+ }
+ return 0;
+}
+
+int con_set_trans_new(ushort __user * arg)
+{
+ int i;
+ unsigned short *p = translations[USER_MAP];
+
+ if (!access_ok(VERIFY_READ, arg, E_TABSZ*sizeof(unsigned short)))
+ return -EFAULT;
+
+ for (i=0; i<E_TABSZ ; i++) {
+ unsigned short us;
+ __get_user(us, arg+i);
+ p[i] = us;
+ }
+
+ update_user_maps();
+ return 0;
+}
+
+int con_get_trans_new(ushort __user * arg)
+{
+ int i;
+ unsigned short *p = translations[USER_MAP];
+
+ if (!access_ok(VERIFY_WRITE, arg, E_TABSZ*sizeof(unsigned short)))
+ return -EFAULT;
+
+ for (i=0; i<E_TABSZ ; i++)
+ __put_user(p[i], arg+i);
+
+ return 0;
+}
+
+/*
+ * Unicode -> current font conversion
+ *
+ * A font has at most 512 chars, usually 256.
+ * But one font position may represent several Unicode chars.
+ * A hashtable is somewhat of a pain to deal with, so use a
+ * "paged table" instead. Simulation has shown the memory cost of
+ * this 3-level paged table scheme to be comparable to a hash table.
+ */
+
+extern u8 dfont_unicount[]; /* Defined in console_defmap.c */
+extern u16 dfont_unitable[];
+
+static void con_release_unimap(struct uni_pagedir *p)
+{
+ u16 **p1;
+ int i, j;
+
+ if (p == dflt) dflt = NULL;
+ for (i = 0; i < 32; i++) {
+ if ((p1 = p->uni_pgdir[i]) != NULL) {
+ for (j = 0; j < 32; j++)
+ if (p1[j])
+ kfree(p1[j]);
+ kfree(p1);
+ }
+ p->uni_pgdir[i] = NULL;
+ }
+ for (i = 0; i < 4; i++)
+ if (p->inverse_translations[i]) {
+ kfree(p->inverse_translations[i]);
+ p->inverse_translations[i] = NULL;
+ }
+}
+
+void con_free_unimap(struct vc_data *vc)
+{
+ struct uni_pagedir *p;
+
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ if (!p)
+ return;
+ *vc->vc_uni_pagedir_loc = 0;
+ if (--p->refcount)
+ return;
+ con_release_unimap(p);
+ kfree(p);
+}
+
+static int con_unify_unimap(struct vc_data *conp, struct uni_pagedir *p)
+{
+ int i, j, k;
+ struct uni_pagedir *q;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ if (!vc_cons_allocated(i))
+ continue;
+ q = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
+ if (!q || q == p || q->sum != p->sum)
+ continue;
+ for (j = 0; j < 32; j++) {
+ u16 **p1, **q1;
+ p1 = p->uni_pgdir[j]; q1 = q->uni_pgdir[j];
+ if (!p1 && !q1)
+ continue;
+ if (!p1 || !q1)
+ break;
+ for (k = 0; k < 32; k++) {
+ if (!p1[k] && !q1[k])
+ continue;
+ if (!p1[k] || !q1[k])
+ break;
+ if (memcmp(p1[k], q1[k], 64*sizeof(u16)))
+ break;
+ }
+ if (k < 32)
+ break;
+ }
+ if (j == 32) {
+ q->refcount++;
+ *conp->vc_uni_pagedir_loc = (unsigned long)q;
+ con_release_unimap(p);
+ kfree(p);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+con_insert_unipair(struct uni_pagedir *p, u_short unicode, u_short fontpos)
+{
+ int i, n;
+ u16 **p1, *p2;
+
+ if (!(p1 = p->uni_pgdir[n = unicode >> 11])) {
+ p1 = p->uni_pgdir[n] = kmalloc(32*sizeof(u16 *), GFP_KERNEL);
+ if (!p1) return -ENOMEM;
+ for (i = 0; i < 32; i++)
+ p1[i] = NULL;
+ }
+
+ if (!(p2 = p1[n = (unicode >> 6) & 0x1f])) {
+ p2 = p1[n] = kmalloc(64*sizeof(u16), GFP_KERNEL);
+ if (!p2) return -ENOMEM;
+ memset(p2, 0xff, 64*sizeof(u16)); /* No glyphs for the characters (yet) */
+ }
+
+ p2[unicode & 0x3f] = fontpos;
+
+ p->sum += (fontpos << 20) + unicode;
+
+ return 0;
+}
+
+/* ui is a leftover from using a hashtable, but might be used again */
+int con_clear_unimap(struct vc_data *vc, struct unimapinit *ui)
+{
+ struct uni_pagedir *p, *q;
+
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ if (p && p->readonly) return -EIO;
+ if (!p || --p->refcount) {
+ q = (struct uni_pagedir *)kmalloc(sizeof(*p), GFP_KERNEL);
+ if (!q) {
+ if (p) p->refcount++;
+ return -ENOMEM;
+ }
+ memset(q, 0, sizeof(*q));
+ q->refcount=1;
+ *vc->vc_uni_pagedir_loc = (unsigned long)q;
+ } else {
+ if (p == dflt) dflt = NULL;
+ p->refcount++;
+ p->sum = 0;
+ con_release_unimap(p);
+ }
+ return 0;
+}
+
+int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
+{
+ int err = 0, err1, i;
+ struct uni_pagedir *p, *q;
+
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ if (p->readonly) return -EIO;
+
+ if (!ct) return 0;
+
+ if (p->refcount > 1) {
+ int j, k;
+ u16 **p1, *p2, l;
+
+ err1 = con_clear_unimap(vc, NULL);
+ if (err1) return err1;
+
+ q = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ for (i = 0, l = 0; i < 32; i++)
+ if ((p1 = p->uni_pgdir[i]))
+ for (j = 0; j < 32; j++)
+ if ((p2 = p1[j]))
+ for (k = 0; k < 64; k++, l++)
+ if (p2[k] != 0xffff) {
+ err1 = con_insert_unipair(q, l, p2[k]);
+ if (err1) {
+ p->refcount++;
+ *vc->vc_uni_pagedir_loc = (unsigned long)p;
+ con_release_unimap(q);
+ kfree(q);
+ return err1;
+ }
+ }
+ p = q;
+ } else if (p == dflt)
+ dflt = NULL;
+
+ while (ct--) {
+ unsigned short unicode, fontpos;
+ __get_user(unicode, &list->unicode);
+ __get_user(fontpos, &list->fontpos);
+ if ((err1 = con_insert_unipair(p, unicode,fontpos)) != 0)
+ err = err1;
+ list++;
+ }
+
+ if (con_unify_unimap(vc, p))
+ return err;
+
+ for (i = 0; i <= 3; i++)
+ set_inverse_transl(vc, p, i); /* Update all inverse translations */
+
+ return err;
+}
+
+/* Loads the unimap for the hardware font, as defined in uni_hash.tbl.
+ The representation used was the most compact I could come up
+ with. This routine is executed at sys_setup time, and when the
+ PIO_FONTRESET ioctl is called. */
+
+int con_set_default_unimap(struct vc_data *vc)
+{
+ int i, j, err = 0, err1;
+ u16 *q;
+ struct uni_pagedir *p;
+
+ if (dflt) {
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ if (p == dflt)
+ return 0;
+ dflt->refcount++;
+ *vc->vc_uni_pagedir_loc = (unsigned long)dflt;
+ if (p && --p->refcount) {
+ con_release_unimap(p);
+ kfree(p);
+ }
+ return 0;
+ }
+
+ /* The default font is always 256 characters */
+
+ err = con_clear_unimap(vc, NULL);
+ if (err) return err;
+
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ q = dfont_unitable;
+
+ for (i = 0; i < 256; i++)
+ for (j = dfont_unicount[i]; j; j--) {
+ err1 = con_insert_unipair(p, *(q++), i);
+ if (err1)
+ err = err1;
+ }
+
+ if (con_unify_unimap(vc, p)) {
+ dflt = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ return err;
+ }
+
+ for (i = 0; i <= 3; i++)
+ set_inverse_transl(vc, p, i); /* Update all inverse translations */
+ dflt = p;
+ return err;
+}
+EXPORT_SYMBOL(con_set_default_unimap);
+
+int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
+{
+ struct uni_pagedir *q;
+
+ if (!*src_vc->vc_uni_pagedir_loc)
+ return -EINVAL;
+ if (*dst_vc->vc_uni_pagedir_loc == *src_vc->vc_uni_pagedir_loc)
+ return 0;
+ con_free_unimap(dst_vc);
+ q = (struct uni_pagedir *)*src_vc->vc_uni_pagedir_loc;
+ q->refcount++;
+ *dst_vc->vc_uni_pagedir_loc = (long)q;
+ return 0;
+}
+
+int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct unipair __user *list)
+{
+ int i, j, k, ect;
+ u16 **p1, *p2;
+ struct uni_pagedir *p;
+
+ ect = 0;
+ if (*vc->vc_uni_pagedir_loc) {
+ p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+ for (i = 0; i < 32; i++)
+ if ((p1 = p->uni_pgdir[i]))
+ for (j = 0; j < 32; j++)
+ if ((p2 = *(p1++)))
+ for (k = 0; k < 64; k++) {
+ if (*p2 < MAX_GLYPH && ect++ < ct) {
+ __put_user((u_short)((i<<11)+(j<<6)+k),
+ &list->unicode);
+ __put_user((u_short) *p2,
+ &list->fontpos);
+ list++;
+ }
+ p2++;
+ }
+ }
+ __put_user(ect, uct);
+ return ((ect <= ct) ? 0 : -ENOMEM);
+}
+
+void con_protect_unimap(struct vc_data *vc, int rdonly)
+{
+ struct uni_pagedir *p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+
+ if (p)
+ p->readonly = rdonly;
+}
+
+int
+conv_uni_to_pc(struct vc_data *conp, long ucs)
+{
+ int h;
+ u16 **p1, *p2;
+ struct uni_pagedir *p;
+
+ /* Only 16-bit codes supported at this time */
+ if (ucs > 0xffff)
+ ucs = 0xfffd; /* U+FFFD: REPLACEMENT CHARACTER */
+ else if (ucs < 0x20 || ucs >= 0xfffe)
+ return -1; /* Not a printable character */
+ else if (ucs == 0xfeff || (ucs >= 0x200a && ucs <= 0x200f))
+ return -2; /* Zero-width space */
+ /*
+ * UNI_DIRECT_BASE indicates the start of the region in the User Zone
+ * which always has a 1:1 mapping to the currently loaded font. The
+ * UNI_DIRECT_MASK indicates the bit span of the region.
+ */
+ else if ((ucs & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE)
+ return ucs & UNI_DIRECT_MASK;
+
+ if (!*conp->vc_uni_pagedir_loc)
+ return -3;
+
+ p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc;
+ if ((p1 = p->uni_pgdir[ucs >> 11]) &&
+ (p2 = p1[(ucs >> 6) & 0x1f]) &&
+ (h = p2[ucs & 0x3f]) < MAX_GLYPH)
+ return h;
+
+ return -4; /* not found */
+}
+
+/*
+ * This is called at sys_setup time, after memory and the console are
+ * initialized. It must be possible to call kmalloc(..., GFP_KERNEL)
+ * from this function, hence the call from sys_setup.
+ */
+void __init
+console_map_init(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ if (vc_cons_allocated(i) && !*vc_cons[i].d->vc_uni_pagedir_loc)
+ con_set_default_unimap(vc_cons[i].d);
+}
+
+EXPORT_SYMBOL(con_copy_unimap);
diff --git a/drivers/char/cp437.uni b/drivers/char/cp437.uni
new file mode 100644
index 000000000000..1f06889a96b9
--- /dev/null
+++ b/drivers/char/cp437.uni
@@ -0,0 +1,291 @@
+#
+# Unicode table for IBM Codepage 437. Note that there are many more
+# substitutions that could be conceived (for example, thick-line
+# graphs probably should be replaced with double-line ones, accented
+# Latin characters should replaced with their nonaccented versions,
+# and some upper case Greek characters could be replaced by Latin), however,
+# I have limited myself to the Unicodes used by the kernel ISO 8859-1,
+# DEC VT, and IBM CP 437 tables.
+#
+# --------------------------------
+#
+# Basic IBM dingbats, some of which will never have a purpose clear
+# to mankind
+#
+0x00 U+0000
+0x01 U+263a
+0x02 U+263b
+0x03 U+2665
+0x04 U+2666 U+25c6
+0x05 U+2663
+0x06 U+2660
+0x07 U+2022
+0x08 U+25d8
+0x09 U+25cb
+0x0a U+25d9
+0x0b U+2642
+0x0c U+2640
+0x0d U+266a
+0x0e U+266b
+0x0f U+263c
+0x10 U+25b6 U+25ba
+0x11 U+25c0 U+25c4
+0x12 U+2195
+0x13 U+203c
+0x14 U+00b6
+0x15 U+00a7
+0x16 U+25ac
+0x17 U+21a8
+0x18 U+2191
+0x19 U+2193
+0x1a U+2192
+0x1b U+2190
+0x1c U+221f
+0x1d U+2194
+0x1e U+25b2
+0x1f U+25bc
+#
+# The ASCII range is identity-mapped, but some of the characters also
+# have to act as substitutes, especially the upper-case characters.
+#
+0x20 U+0020
+0x21 U+0021
+0x22 U+0022 U+00a8
+0x23 U+0023
+0x24 U+0024
+0x25 U+0025
+0x26 U+0026
+0x27 U+0027
+0x28 U+0028
+0x29 U+0029
+0x2a U+002a
+0x2b U+002b
+0x2c U+002c U+00b8
+0x2d U+002d U+00ad
+0x2e U+002e
+0x2f U+002f
+0x30 U+0030
+0x31 U+0031
+0x32 U+0032
+0x33 U+0033
+0x34 U+0034
+0x35 U+0035
+0x36 U+0036
+0x37 U+0037
+0x38 U+0038
+0x39 U+0039
+0x3a U+003a
+0x3b U+003b
+0x3c U+003c
+0x3d U+003d
+0x3e U+003e
+0x3f U+003f
+0x40 U+0040
+0x41 U+0041 U+00c0 U+00c1 U+00c2 U+00c3
+0x42 U+0042
+0x43 U+0043 U+00a9
+0x44 U+0044
+0x45 U+0045 U+00c8 U+00ca U+00cb
+0x46 U+0046
+0x47 U+0047
+0x48 U+0048
+0x49 U+0049 U+00cc U+00cd U+00ce U+00cf
+0x4a U+004a
+0x4b U+004b U+212a
+0x4c U+004c
+0x4d U+004d
+0x4e U+004e
+0x4f U+004f U+00d2 U+00d3 U+00d4 U+00d5
+0x50 U+0050
+0x51 U+0051
+0x52 U+0052 U+00ae
+0x53 U+0053
+0x54 U+0054
+0x55 U+0055 U+00d9 U+00da U+00db
+0x56 U+0056
+0x57 U+0057
+0x58 U+0058
+0x59 U+0059 U+00dd
+0x5a U+005a
+0x5b U+005b
+0x5c U+005c
+0x5d U+005d
+0x5e U+005e
+0x5f U+005f U+23bd U+f804
+0x60 U+0060
+0x61 U+0061 U+00e3
+0x62 U+0062
+0x63 U+0063
+0x64 U+0064
+0x65 U+0065
+0x66 U+0066
+0x67 U+0067
+0x68 U+0068
+0x69 U+0069
+0x6a U+006a
+0x6b U+006b
+0x6c U+006c
+0x6d U+006d
+0x6e U+006e
+0x6f U+006f U+00f5
+0x70 U+0070
+0x71 U+0071
+0x72 U+0072
+0x73 U+0073
+0x74 U+0074
+0x75 U+0075
+0x76 U+0076
+0x77 U+0077
+0x78 U+0078 U+00d7
+0x79 U+0079 U+00fd
+0x7a U+007a
+0x7b U+007b
+0x7c U+007c U+00a5
+0x7d U+007d
+0x7e U+007e
+#
+# Okay, what on Earth is this one supposed to be used for?
+#
+0x7f U+2302
+#
+# Non-English characters, mostly lower case letters...
+#
+0x80 U+00c7
+0x81 U+00fc
+0x82 U+00e9
+0x83 U+00e2
+0x84 U+00e4
+0x85 U+00e0
+0x86 U+00e5
+0x87 U+00e7
+0x88 U+00ea
+0x89 U+00eb
+0x8a U+00e8
+0x8b U+00ef
+0x8c U+00ee
+0x8d U+00ec
+0x8e U+00c4
+0x8f U+00c5 U+212b
+0x90 U+00c9
+0x91 U+00e6
+0x92 U+00c6
+0x93 U+00f4
+0x94 U+00f6
+0x95 U+00f2
+0x96 U+00fb
+0x97 U+00f9
+0x98 U+00ff
+0x99 U+00d6
+0x9a U+00dc
+0x9b U+00a2
+0x9c U+00a3
+0x9d U+00a5
+0x9e U+20a7
+0x9f U+0192
+0xa0 U+00e1
+0xa1 U+00ed
+0xa2 U+00f3
+0xa3 U+00fa
+0xa4 U+00f1
+0xa5 U+00d1
+0xa6 U+00aa
+0xa7 U+00ba
+0xa8 U+00bf
+0xa9 U+2310
+0xaa U+00ac
+0xab U+00bd
+0xac U+00bc
+0xad U+00a1
+0xae U+00ab
+0xaf U+00bb
+#
+# Block graphics
+#
+0xb0 U+2591
+0xb1 U+2592
+0xb2 U+2593
+0xb3 U+2502
+0xb4 U+2524
+0xb5 U+2561
+0xb6 U+2562
+0xb7 U+2556
+0xb8 U+2555
+0xb9 U+2563
+0xba U+2551
+0xbb U+2557
+0xbc U+255d
+0xbd U+255c
+0xbe U+255b
+0xbf U+2510
+0xc0 U+2514
+0xc1 U+2534
+0xc2 U+252c
+0xc3 U+251c
+0xc4 U+2500
+0xc5 U+253c
+0xc6 U+255e
+0xc7 U+255f
+0xc8 U+255a
+0xc9 U+2554
+0xca U+2569
+0xcb U+2566
+0xcc U+2560
+0xcd U+2550
+0xce U+256c
+0xcf U+2567
+0xd0 U+2568
+0xd1 U+2564
+0xd2 U+2565
+0xd3 U+2559
+0xd4 U+2558
+0xd5 U+2552
+0xd6 U+2553
+0xd7 U+256b
+0xd8 U+256a
+0xd9 U+2518
+0xda U+250c
+0xdb U+2588
+0xdc U+2584
+0xdd U+258c
+0xde U+2590
+0xdf U+2580
+#
+# Greek letters and mathematical symbols
+#
+0xe0 U+03b1
+0xe1 U+03b2 U+00df
+0xe2 U+0393
+0xe3 U+03c0
+0xe4 U+03a3
+0xe5 U+03c3
+0xe6 U+00b5 U+03bc
+0xe7 U+03c4
+0xe8 U+03a6 U+00d8
+0xe9 U+0398
+0xea U+03a9 U+2126
+0xeb U+03b4
+0xec U+221e
+0xed U+03c6 U+00f8
+0xee U+03b5
+0xef U+2229
+0xf0 U+2261
+0xf1 U+00b1
+0xf2 U+2265
+0xf3 U+2264
+0xf4 U+2320
+0xf5 U+2321
+0xf6 U+00f7
+0xf7 U+2248
+0xf8 U+00b0
+0xf9 U+2219
+0xfa U+00b7
+0xfb U+221a
+0xfc U+207f
+0xfd U+00b2
+#
+# Square bullet, non-spacing blank
+# Mapping U+fffd to the square bullet means it is the substitution
+# character
+#
+0xfe U+25a0 U+fffd
+0xff U+00a0
diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c
new file mode 100644
index 000000000000..6a5337bf0936
--- /dev/null
+++ b/drivers/char/cyclades.c
@@ -0,0 +1,5540 @@
+#undef BLOCKMOVE
+#define Z_WAKE
+#undef Z_EXT_CHARS_IN_BUFFER
+static char rcsid[] =
+"$Revision: 2.3.2.20 $$Date: 2004/02/25 18:14:16 $";
+
+/*
+ * linux/drivers/char/cyclades.c
+ *
+ * This file contains the driver for the Cyclades async multiport
+ * serial boards.
+ *
+ * Initially written by Randolph Bentson <bentson@grieg.seaslug.org>.
+ * Modified and maintained by Marcio Saito <marcio@cyclades.com>.
+ * Currently maintained by Cyclades team <async@cyclades.com>.
+ *
+ * For Technical support and installation problems, please send e-mail
+ * to support@cyclades.com.
+ *
+ * Much of the design and some of the code came from serial.c
+ * which was copyright (C) 1991, 1992 Linus Torvalds. It was
+ * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92,
+ * and then fixed as suggested by Michael K. Johnson 12/12/92.
+ *
+ * This version supports shared IRQ's (only for PCI boards).
+ *
+ * $Log: cyclades.c,v $
+ * Prevent users from opening non-existing Z ports.
+ *
+ * Revision 2.3.2.8 2000/07/06 18:14:16 ivan
+ * Fixed the PCI detection function to work properly on Alpha systems.
+ * Implemented support for TIOCSERGETLSR ioctl.
+ * Implemented full support for non-standard baud rates.
+ *
+ * Revision 2.3.2.7 2000/06/01 18:26:34 ivan
+ * Request PLX I/O region, although driver doesn't use it, to avoid
+ * problems with other drivers accessing it.
+ * Removed count for on-board buffer characters in cy_chars_in_buffer
+ * (Cyclades-Z only).
+ *
+ * Revision 2.3.2.6 2000/05/05 13:56:05 ivan
+ * Driver now reports physical instead of virtual memory addresses.
+ * Masks were added to some Cyclades-Z read accesses.
+ * Implemented workaround for PLX9050 bug that would cause a system lockup
+ * in certain systems, depending on the MMIO addresses allocated to the
+ * board.
+ * Changed the Tx interrupt programming in the CD1400 chips to boost up
+ * performance (Cyclom-Y only).
+ * Code is now compliant with the new module interface (module_[init|exit]).
+ * Make use of the PCI helper functions to access PCI resources.
+ * Did some code "housekeeping".
+ *
+ * Revision 2.3.2.5 2000/01/19 14:35:33 ivan
+ * Fixed bug in cy_set_termios on CRTSCTS flag turnoff.
+ *
+ * Revision 2.3.2.4 2000/01/17 09:19:40 ivan
+ * Fixed SMP locking in Cyclom-Y interrupt handler.
+ *
+ * Revision 2.3.2.3 1999/12/28 12:11:39 ivan
+ * Added a new cyclades_card field called nports to allow the driver to
+ * know the exact number of ports found by the Z firmware after its load;
+ * RX buffer contention prevention logic on interrupt op mode revisited
+ * (Cyclades-Z only);
+ * Revisited printk's for Z debug;
+ * Driver now makes sure that the constant SERIAL_XMIT_SIZE is defined;
+ *
+ * Revision 2.3.2.2 1999/10/01 11:27:43 ivan
+ * Fixed bug in cyz_poll that would make all ports but port 0
+ * unable to transmit/receive data (Cyclades-Z only);
+ * Implemented logic to prevent the RX buffer from being stuck with data
+ * due to a driver / firmware race condition in interrupt op mode
+ * (Cyclades-Z only);
+ * Fixed bug in block_til_ready logic that would lead to a system crash;
+ * Revisited cy_close spinlock usage;
+ *
+ * Revision 2.3.2.1 1999/09/28 11:01:22 ivan
+ * Revisited CONFIG_PCI conditional compilation for PCI board support;
+ * Implemented TIOCGICOUNT and TIOCMIWAIT ioctl support;
+ * _Major_ cleanup on the Cyclades-Z interrupt support code / logic;
+ * Removed CTS handling from the driver -- this is now completely handled
+ * by the firmware (Cyclades-Z only);
+ * Flush RX on-board buffers on a port open (Cyclades-Z only);
+ * Fixed handling of ASYNC_SPD_* TTY flags;
+ * Module unload now unmaps all memory area allocated by ioremap;
+ *
+ * Revision 2.3.1.1 1999/07/15 16:45:53 ivan
+ * Removed CY_PROC conditional compilation;
+ * Implemented SMP-awareness for the driver;
+ * Implemented a new ISA IRQ autoprobe that uses the irq_probe_[on|off]
+ * functions;
+ * The driver now accepts memory addresses (maddr=0xMMMMM) and IRQs
+ * (irq=NN) as parameters (only for ISA boards);
+ * Fixed bug in set_line_char that would prevent the Cyclades-Z
+ * ports from being configured at speeds above 115.2Kbps;
+ * Fixed bug in cy_set_termios that would prevent XON/XOFF flow control
+ * switching from working properly;
+ * The driver now only prints IRQ info for the Cyclades-Z if it's
+ * configured to work in interrupt mode;
+ *
+ * Revision 2.2.2.3 1999/06/28 11:13:29 ivan
+ * Added support for interrupt mode operation for the Z cards;
+ * Removed the driver inactivity control for the Z;
+ * Added a missing MOD_DEC_USE_COUNT in the cy_open function for when
+ * the Z firmware is not loaded yet;
+ * Replaced the "manual" Z Tx flush buffer by a call to a FW command of
+ * same functionality;
+ * Implemented workaround for IRQ setting loss on the PCI configuration
+ * registers after a PCI bridge EEPROM reload (affects PLX9060 only);
+ *
+ * Revision 2.2.2.2 1999/05/14 17:18:15 ivan
+ * /proc entry location changed to /proc/tty/driver/cyclades;
+ * Added support to shared IRQ's (only for PCI boards);
+ * Added support for Cobalt Qube2 systems;
+ * IRQ [de]allocation scheme revisited;
+ * BREAK implementation changed in order to make use of the 'break_ctl'
+ * TTY facility;
+ * Fixed typo in TTY structure field 'driver_name';
+ * Included a PCI bridge reset and EEPROM reload in the board
+ * initialization code (for both Y and Z series).
+ *
+ * Revision 2.2.2.1 1999/04/08 16:17:43 ivan
+ * Fixed a bug in cy_wait_until_sent that was preventing the port to be
+ * closed properly after a SIGINT;
+ * Module usage counter scheme revisited;
+ * Added support to the upcoming Y PCI boards (i.e., support to additional
+ * PCI Device ID's).
+ *
+ * Revision 2.2.1.10 1999/01/20 16:14:29 ivan
+ * Removed all unnecessary page-alignement operations in ioremap calls
+ * (ioremap is currently safe for these operations).
+ *
+ * Revision 2.2.1.9 1998/12/30 18:18:30 ivan
+ * Changed access to PLX PCI bridge registers from I/O to MMIO, in
+ * order to make PLX9050-based boards work with certain motherboards.
+ *
+ * Revision 2.2.1.8 1998/11/13 12:46:20 ivan
+ * cy_close function now resets (correctly) the tty->closing flag;
+ * JIFFIES_DIFF macro fixed.
+ *
+ * Revision 2.2.1.7 1998/09/03 12:07:28 ivan
+ * Fixed bug in cy_close function, which was not informing HW of
+ * which port should have the reception disabled before doing so;
+ * fixed Cyclom-8YoP hardware detection bug.
+ *
+ * Revision 2.2.1.6 1998/08/20 17:15:39 ivan
+ * Fixed bug in cy_close function, which causes malfunction
+ * of one of the first 4 ports when a higher port is closed
+ * (Cyclom-Y only).
+ *
+ * Revision 2.2.1.5 1998/08/10 18:10:28 ivan
+ * Fixed Cyclom-4Yo hardware detection bug.
+ *
+ * Revision 2.2.1.4 1998/08/04 11:02:50 ivan
+ * /proc/cyclades implementation with great collaboration of
+ * Marc Lewis <marc@blarg.net>;
+ * cyy_interrupt was changed to avoid occurrence of kernel oopses
+ * during PPP operation.
+ *
+ * Revision 2.2.1.3 1998/06/01 12:09:10 ivan
+ * General code review in order to comply with 2.1 kernel standards;
+ * data loss prevention for slow devices revisited (cy_wait_until_sent
+ * was created);
+ * removed conditional compilation for new/old PCI structure support
+ * (now the driver only supports the new PCI structure).
+ *
+ * Revision 2.2.1.1 1998/03/19 16:43:12 ivan
+ * added conditional compilation for new/old PCI structure support;
+ * removed kernel series (2.0.x / 2.1.x) conditional compilation.
+ *
+ * Revision 2.1.1.3 1998/03/16 18:01:12 ivan
+ * cleaned up the data loss fix;
+ * fixed XON/XOFF handling once more (Cyclades-Z);
+ * general review of the driver routines;
+ * introduction of a mechanism to prevent data loss with slow
+ * printers, by forcing a delay before closing the port.
+ *
+ * Revision 2.1.1.2 1998/02/17 16:50:00 ivan
+ * fixed detection/handling of new CD1400 in Ye boards;
+ * fixed XON/XOFF handling (Cyclades-Z);
+ * fixed data loss caused by a premature port close;
+ * introduction of a flag that holds the CD1400 version ID per port
+ * (used by the CYGETCD1400VER new ioctl).
+ *
+ * Revision 2.1.1.1 1997/12/03 17:31:19 ivan
+ * Code review for the module cleanup routine;
+ * fixed RTS and DTR status report for new CD1400's in get_modem_info;
+ * includes anonymous changes regarding signal_pending.
+ *
+ * Revision 2.1 1997/11/01 17:42:41 ivan
+ * Changes in the driver to support Alpha systems (except 8Zo V_1);
+ * BREAK fix for the Cyclades-Z boards;
+ * driver inactivity control by FW implemented;
+ * introduction of flag that allows driver to take advantage of
+ * a special CD1400 feature related to HW flow control;
+ * added support for the CD1400 rev. J (Cyclom-Y boards);
+ * introduction of ioctls to:
+ * - control the rtsdtr_inv flag (Cyclom-Y);
+ * - control the rflow flag (Cyclom-Y);
+ * - adjust the polling interval (Cyclades-Z);
+ *
+ * Revision 1.36.4.33 1997/06/27 19:00:00 ivan
+ * Fixes related to kernel version conditional
+ * compilation.
+ *
+ * Revision 1.36.4.32 1997/06/14 19:30:00 ivan
+ * Compatibility issues between kernels 2.0.x and
+ * 2.1.x (mainly related to clear_bit function).
+ *
+ * Revision 1.36.4.31 1997/06/03 15:30:00 ivan
+ * Changes to define the memory window according to the
+ * board type.
+ *
+ * Revision 1.36.4.30 1997/05/16 15:30:00 daniel
+ * Changes to support new cycladesZ boards.
+ *
+ * Revision 1.36.4.29 1997/05/12 11:30:00 daniel
+ * Merge of Bentson's and Daniel's version 1.36.4.28.
+ * Corrects bug in cy_detect_pci: check if there are more
+ * ports than the number of static structs allocated.
+ * Warning message during initialization if this driver is
+ * used with the new generation of cycladesZ boards. Those
+ * will be supported only in next release of the driver.
+ * Corrects bug in cy_detect_pci and cy_detect_isa that
+ * returned wrong number of VALID boards, when a cyclomY
+ * was found with no serial modules connected.
+ * Changes to use current (2.1.x) kernel subroutine names
+ * and created macros for compilation with 2.0.x kernel,
+ * instead of the other way around.
+ *
+ * Revision 1.36.4.28 1997/05/?? ??:00:00 bentson
+ * Change queue_task_irq_off to queue_task_irq.
+ * The inline function queue_task_irq_off (tqueue.h)
+ * was removed from latest releases of 2.1.x kernel.
+ * Use of macro __init to mark the initialization
+ * routines, so memory can be reused.
+ * Also incorporate implementation of critical region
+ * in function cleanup_module() created by anonymous
+ * linuxer.
+ *
+ * Revision 1.36.4.28 1997/04/25 16:00:00 daniel
+ * Change to support new firmware that solves DCD problem:
+ * application could fail to receive SIGHUP signal when DCD
+ * varying too fast.
+ *
+ * Revision 1.36.4.27 1997/03/26 10:30:00 daniel
+ * Changed for support linux versions 2.1.X.
+ * Backward compatible with linux versions 2.0.X.
+ * Corrected illegal use of filler field in
+ * CH_CTRL struct.
+ * Deleted some debug messages.
+ *
+ * Revision 1.36.4.26 1997/02/27 12:00:00 daniel
+ * Included check for NULL tty pointer in cyz_poll.
+ *
+ * Revision 1.36.4.25 1997/02/26 16:28:30 bentson
+ * Bill Foster at Blarg! Online services noticed that
+ * some of the switch elements of -Z modem control
+ * lacked a closing "break;"
+ *
+ * Revision 1.36.4.24 1997/02/24 11:00:00 daniel
+ * Changed low water threshold for buffer xmit_buf
+ *
+ * Revision 1.36.4.23 1996/12/02 21:50:16 bentson
+ * Marcio provided fix to modem status fetch for -Z
+ *
+ * Revision 1.36.4.22 1996/10/28 22:41:17 bentson
+ * improve mapping of -Z control page (thanks to Steve
+ * Price <stevep@fa.tdktca.com> for help on this)
+ *
+ * Revision 1.36.4.21 1996/09/10 17:00:10 bentson
+ * shift from CPU-bound to memcopy in cyz_polling operation
+ *
+ * Revision 1.36.4.20 1996/09/09 18:30:32 Bentson
+ * Added support to set and report higher speeds.
+ *
+ * Revision 1.36.4.19c 1996/08/09 10:00:00 Marcio Saito
+ * Some fixes in the HW flow control for the BETA release.
+ * Don't try to register the IRQ.
+ *
+ * Revision 1.36.4.19 1996/08/08 16:23:18 Bentson
+ * make sure "cyc" appears in all kernel messages; all soft interrupts
+ * handled by same routine; recognize out-of-band reception; comment
+ * out some diagnostic messages; leave RTS/CTS flow control to hardware;
+ * fix race condition in -Z buffer management; only -Y needs to explictly
+ * flush chars; tidy up some startup messages;
+ *
+ * Revision 1.36.4.18 1996/07/25 18:57:31 bentson
+ * shift MOD_INC_USE_COUNT location to match
+ * serial.c; purge some diagnostic messages;
+ *
+ * Revision 1.36.4.17 1996/07/25 18:01:08 bentson
+ * enable modem status messages and fetch & process them; note
+ * time of last activity type for each port; set_line_char now
+ * supports more than line 0 and treats 0 baud correctly;
+ * get_modem_info senses rs_status;
+ *
+ * Revision 1.36.4.16 1996/07/20 08:43:15 bentson
+ * barely works--now's time to turn on
+ * more features 'til it breaks
+ *
+ * Revision 1.36.4.15 1996/07/19 22:30:06 bentson
+ * check more -Z board status; shorten boot message
+ *
+ * Revision 1.36.4.14 1996/07/19 22:20:37 bentson
+ * fix reference to ch_ctrl in startup; verify return
+ * values from cyz_issue_cmd and cyz_update_channel;
+ * more stuff to get modem control correct;
+ *
+ * Revision 1.36.4.13 1996/07/11 19:53:33 bentson
+ * more -Z stuff folded in; re-order changes to put -Z stuff
+ * after -Y stuff (to make changes clearer)
+ *
+ * Revision 1.36.4.12 1996/07/11 15:40:55 bentson
+ * Add code to poll Cyclades-Z. Add code to get & set RS-232 control.
+ * Add code to send break. Clear firmware ID word at startup (so
+ * that other code won't talk to inactive board).
+ *
+ * Revision 1.36.4.11 1996/07/09 05:28:29 bentson
+ * add code for -Z in set_line_char
+ *
+ * Revision 1.36.4.10 1996/07/08 19:28:37 bentson
+ * fold more -Z stuff (or in some cases, error messages)
+ * into driver; add text to "don't know what to do" messages.
+ *
+ * Revision 1.36.4.9 1996/07/08 18:38:38 bentson
+ * moved compile-time flags near top of file; cosmetic changes
+ * to narrow text (to allow 2-up printing); changed many declarations
+ * to "static" to limit external symbols; shuffled code order to
+ * coalesce -Y and -Z specific code, also to put internal functions
+ * in order of tty_driver structure; added code to recognize -Z
+ * ports (and for moment, do nothing or report error); add cy_startup
+ * to parse boot command line for extra base addresses for ISA probes;
+ *
+ * Revision 1.36.4.8 1996/06/25 17:40:19 bentson
+ * reorder some code, fix types of some vars (int vs. long),
+ * add cy_setup to support user declared ISA addresses
+ *
+ * Revision 1.36.4.7 1996/06/21 23:06:18 bentson
+ * dump ioctl based firmware load (it's now a user level
+ * program); ensure uninitialzed ports cannot be used
+ *
+ * Revision 1.36.4.6 1996/06/20 23:17:19 bentson
+ * rename vars and restructure some code
+ *
+ * Revision 1.36.4.5 1996/06/14 15:09:44 bentson
+ * get right status back after boot load
+ *
+ * Revision 1.36.4.4 1996/06/13 19:51:44 bentson
+ * successfully loads firmware
+ *
+ * Revision 1.36.4.3 1996/06/13 06:08:33 bentson
+ * add more of the code for the boot/load ioctls
+ *
+ * Revision 1.36.4.2 1996/06/11 21:00:51 bentson
+ * start to add Z functionality--starting with ioctl
+ * for loading firmware
+ *
+ * Revision 1.36.4.1 1996/06/10 18:03:02 bentson
+ * added code to recognize Z/PCI card at initialization; report
+ * presence, but card is not initialized (because firmware needs
+ * to be loaded)
+ *
+ * Revision 1.36.3.8 1996/06/07 16:29:00 bentson
+ * starting minor number at zero; added missing verify_area
+ * as noted by Heiko Eissfeldt <heiko@colossus.escape.de>
+ *
+ * Revision 1.36.3.7 1996/04/19 21:06:18 bentson
+ * remove unneeded boot message & fix CLOCAL hardware flow
+ * control (Miquel van Smoorenburg <miquels@Q.cistron.nl>);
+ * remove unused diagnostic statements; minor 0 is first;
+ *
+ * Revision 1.36.3.6 1996/03/13 13:21:17 marcio
+ * The kernel function vremap (available only in later 1.3.xx kernels)
+ * allows the access to memory addresses above the RAM. This revision
+ * of the driver supports PCI boards below 1Mb (device id 0x100) and
+ * above 1Mb (device id 0x101).
+ *
+ * Revision 1.36.3.5 1996/03/07 15:20:17 bentson
+ * Some global changes to interrupt handling spilled into
+ * this driver--mostly unused arguments in system function
+ * calls. Also added change by Marcio Saito which should
+ * reduce lost interrupts at startup by fast processors.
+ *
+ * Revision 1.36.3.4 1995/11/13 20:45:10 bentson
+ * Changes by Corey Minyard <minyard@wf-rch.cirr.com> distributed
+ * in 1.3.41 kernel to remove a possible race condition, extend
+ * some error messages, and let the driver run as a loadable module
+ * Change by Alan Wendt <alan@ez0.ezlink.com> to remove a
+ * possible race condition.
+ * Change by Marcio Saito <marcio@cyclades.com> to fix PCI addressing.
+ *
+ * Revision 1.36.3.3 1995/11/13 19:44:48 bentson
+ * Changes by Linus Torvalds in 1.3.33 kernel distribution
+ * required due to reordering of driver initialization.
+ * Drivers are now initialized *after* memory management.
+ *
+ * Revision 1.36.3.2 1995/09/08 22:07:14 bentson
+ * remove printk from ISR; fix typo
+ *
+ * Revision 1.36.3.1 1995/09/01 12:00:42 marcio
+ * Minor fixes in the PCI board support. PCI function calls in
+ * conditional compilation (CONFIG_PCI). Thanks to Jim Duncan
+ * <duncan@okay.com>. "bad serial count" message removed.
+ *
+ * Revision 1.36.3 1995/08/22 09:19:42 marcio
+ * Cyclom-Y/PCI support added. Changes in the cy_init routine and
+ * board initialization. Changes in the boot messages. The driver
+ * supports up to 4 boards and 64 ports by default.
+ *
+ * Revision 1.36.1.4 1995/03/29 06:14:14 bentson
+ * disambiguate between Cyclom-16Y and Cyclom-32Ye;
+ *
+ * Revision 1.36.1.3 1995/03/23 22:15:35 bentson
+ * add missing break in modem control block in ioctl switch statement
+ * (discovered by Michael Edward Chastain <mec@jobe.shell.portal.com>);
+ *
+ * Revision 1.36.1.2 1995/03/22 19:16:22 bentson
+ * make sure CTS flow control is set as soon as possible (thanks
+ * to note from David Lambert <lambert@chesapeake.rps.slb.com>);
+ *
+ * Revision 1.36.1.1 1995/03/13 15:44:43 bentson
+ * initialize defaults for receive threshold and stale data timeout;
+ * cosmetic changes;
+ *
+ * Revision 1.36 1995/03/10 23:33:53 bentson
+ * added support of chips 4-7 in 32 port Cyclom-Ye;
+ * fix cy_interrupt pointer dereference problem
+ * (Joe Portman <baron@aa.net>);
+ * give better error response if open is attempted on non-existent port
+ * (Zachariah Vaum <jchryslr@netcom.com>);
+ * correct command timeout (Kenneth Lerman <lerman@@seltd.newnet.com>);
+ * conditional compilation for -16Y on systems with fast, noisy bus;
+ * comment out diagnostic print function;
+ * cleaned up table of base addresses;
+ * set receiver time-out period register to correct value,
+ * set receive threshold to better default values,
+ * set chip timer to more accurate 200 Hz ticking,
+ * add code to monitor and modify receive parameters
+ * (Rik Faith <faith@cs.unc.edu> Nick Simicich
+ * <njs@scifi.emi.net>);
+ *
+ * Revision 1.35 1994/12/16 13:54:18 steffen
+ * additional patch by Marcio Saito for board detection
+ * Accidently left out in 1.34
+ *
+ * Revision 1.34 1994/12/10 12:37:12 steffen
+ * This is the corrected version as suggested by Marcio Saito
+ *
+ * Revision 1.33 1994/12/01 22:41:18 bentson
+ * add hooks to support more high speeds directly; add tytso
+ * patch regarding CLOCAL wakeups
+ *
+ * Revision 1.32 1994/11/23 19:50:04 bentson
+ * allow direct kernel control of higher signalling rates;
+ * look for cards at additional locations
+ *
+ * Revision 1.31 1994/11/16 04:33:28 bentson
+ * ANOTHER fix from Corey Minyard, minyard@wf-rch.cirr.com--
+ * a problem in chars_in_buffer has been resolved by some
+ * small changes; this should yield smoother output
+ *
+ * Revision 1.30 1994/11/16 04:28:05 bentson
+ * Fix from Corey Minyard, Internet: minyard@metronet.com,
+ * UUCP: minyard@wf-rch.cirr.com, WORK: minyardbnr.ca, to
+ * cy_hangup that appears to clear up much (all?) of the
+ * DTR glitches; also he's added/cleaned-up diagnostic messages
+ *
+ * Revision 1.29 1994/11/16 04:16:07 bentson
+ * add change proposed by Ralph Sims, ralphs@halcyon.com, to
+ * operate higher speeds in same way as other serial ports;
+ * add more serial ports (for up to two 16-port muxes).
+ *
+ * Revision 1.28 1994/11/04 00:13:16 root
+ * turn off diagnostic messages
+ *
+ * Revision 1.27 1994/11/03 23:46:37 root
+ * bunch of changes to bring driver into greater conformance
+ * with the serial.c driver (looking for missed fixes)
+ *
+ * Revision 1.26 1994/11/03 22:40:36 root
+ * automatic interrupt probing fixed.
+ *
+ * Revision 1.25 1994/11/03 20:17:02 root
+ * start to implement auto-irq
+ *
+ * Revision 1.24 1994/11/03 18:01:55 root
+ * still working on modem signals--trying not to drop DTR
+ * during the getty/login processes
+ *
+ * Revision 1.23 1994/11/03 17:51:36 root
+ * extend baud rate support; set receive threshold as function
+ * of baud rate; fix some problems with RTS/CTS;
+ *
+ * Revision 1.22 1994/11/02 18:05:35 root
+ * changed arguments to udelay to type long to get
+ * delays to be of correct duration
+ *
+ * Revision 1.21 1994/11/02 17:37:30 root
+ * employ udelay (after calibrating loops_per_second earlier
+ * in init/main.c) instead of using home-grown delay routines
+ *
+ * Revision 1.20 1994/11/02 03:11:38 root
+ * cy_chars_in_buffer forces a return value of 0 to let
+ * login work (don't know why it does); some functions
+ * that were returning EFAULT, now executes the code;
+ * more work on deciding when to disable xmit interrupts;
+ *
+ * Revision 1.19 1994/11/01 20:10:14 root
+ * define routine to start transmission interrupts (by enabling
+ * transmit interrupts); directly enable/disable modem interrupts;
+ *
+ * Revision 1.18 1994/11/01 18:40:45 bentson
+ * Don't always enable transmit interrupts in startup; interrupt on
+ * TxMpty instead of TxRdy to help characters get out before shutdown;
+ * restructure xmit interrupt to check for chars first and quit if
+ * none are ready to go; modem status (MXVRx) is upright, _not_ inverted
+ * (to my view);
+ *
+ * Revision 1.17 1994/10/30 04:39:45 bentson
+ * rename serial_driver and callout_driver to cy_serial_driver and
+ * cy_callout_driver to avoid linkage interference; initialize
+ * info->type to PORT_CIRRUS; ruggedize paranoia test; elide ->port
+ * from cyclades_port structure; add paranoia check to cy_close;
+ *
+ * Revision 1.16 1994/10/30 01:14:33 bentson
+ * change major numbers; add some _early_ return statements;
+ *
+ * Revision 1.15 1994/10/29 06:43:15 bentson
+ * final tidying up for clean compile; enable some error reporting
+ *
+ * Revision 1.14 1994/10/28 20:30:22 Bentson
+ * lots of changes to drag the driver towards the new tty_io
+ * structures and operation. not expected to work, but may
+ * compile cleanly.
+ *
+ * Revision 1.13 1994/07/21 23:08:57 Bentson
+ * add some diagnostic cruft; support 24 lines (for testing
+ * both -8Y and -16Y cards; be more thorough in servicing all
+ * chips during interrupt; add "volatile" a few places to
+ * circumvent compiler optimizations; fix base & offset
+ * computations in block_til_ready (was causing chip 0 to
+ * stop operation)
+ *
+ * Revision 1.12 1994/07/19 16:42:11 Bentson
+ * add some hackery for kernel version 1.1.8; expand
+ * error messages; refine timing for delay loops and
+ * declare loop params volatile
+ *
+ * Revision 1.11 1994/06/11 21:53:10 bentson
+ * get use of save_car right in transmit interrupt service
+ *
+ * Revision 1.10.1.1 1994/06/11 21:31:18 bentson
+ * add some diagnostic printing; try to fix save_car stuff
+ *
+ * Revision 1.10 1994/06/11 20:36:08 bentson
+ * clean up compiler warnings
+ *
+ * Revision 1.9 1994/06/11 19:42:46 bentson
+ * added a bunch of code to support modem signalling
+ *
+ * Revision 1.8 1994/06/11 17:57:07 bentson
+ * recognize break & parity error
+ *
+ * Revision 1.7 1994/06/05 05:51:34 bentson
+ * Reorder baud table to be monotonic; add cli to CP; discard
+ * incoming characters and status if the line isn't open; start to
+ * fold code into cy_throttle; start to port get_serial_info,
+ * set_serial_info, get_modem_info, set_modem_info, and send_break
+ * from serial.c; expand cy_ioctl; relocate and expand config_setup;
+ * get flow control characters from tty struct; invalidate ports w/o
+ * hardware;
+ *
+ * Revision 1.6 1994/05/31 18:42:21 bentson
+ * add a loop-breaker in the interrupt service routine;
+ * note when port is initialized so that it can be shut
+ * down under the right conditions; receive works without
+ * any obvious errors
+ *
+ * Revision 1.5 1994/05/30 00:55:02 bentson
+ * transmit works without obvious errors
+ *
+ * Revision 1.4 1994/05/27 18:46:27 bentson
+ * incorporated more code from lib_y.c; can now print short
+ * strings under interrupt control to port zero; seems to
+ * select ports/channels/lines correctly
+ *
+ * Revision 1.3 1994/05/25 22:12:44 bentson
+ * shifting from multi-port on a card to proper multiplexor
+ * data structures; added skeletons of most routines
+ *
+ * Revision 1.2 1994/05/19 13:21:43 bentson
+ * start to crib from other sources
+ *
+ */
+
+/* If you need to install more boards than NR_CARDS, change the constant
+ in the definition below. No other change is necessary to support up to
+ eight boards. Beyond that you'll have to extend cy_isa_addresses. */
+
+#define NR_CARDS 4
+
+/*
+ If the total number of ports is larger than NR_PORTS, change this
+ constant in the definition below. No other change is necessary to
+ support more boards/ports. */
+
+#define NR_PORTS 256
+
+#define ZE_V1_NPORTS 64
+#define ZO_V1 0
+#define ZO_V2 1
+#define ZE_V1 2
+
+#define SERIAL_PARANOIA_CHECK
+#undef CY_DEBUG_OPEN
+#undef CY_DEBUG_THROTTLE
+#undef CY_DEBUG_OTHER
+#undef CY_DEBUG_IO
+#undef CY_DEBUG_COUNT
+#undef CY_DEBUG_DTR
+#undef CY_DEBUG_WAIT_UNTIL_SENT
+#undef CY_DEBUG_INTERRUPTS
+#undef CY_16Y_HACK
+#undef CY_ENABLE_MONITORING
+#undef CY_PCI_DEBUG
+
+#if 0
+#define PAUSE __asm__("nop");
+#else
+#define PAUSE ;
+#endif
+
+/*
+ * Include section
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/cyclades.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#define CY_LOCK(info,flags) \
+ do { \
+ spin_lock_irqsave(&cy_card[info->card].card_lock, flags); \
+ } while (0)
+
+#define CY_UNLOCK(info,flags) \
+ do { \
+ spin_unlock_irqrestore(&cy_card[info->card].card_lock, flags); \
+ } while (0)
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+
+static void cy_throttle (struct tty_struct *tty);
+static void cy_send_xchar (struct tty_struct *tty, char ch);
+
+#define IS_CYC_Z(card) ((card).num_chips == -1)
+
+#define Z_FPGA_CHECK(card) \
+ ((cy_readl(&((struct RUNTIME_9060 __iomem *) \
+ ((card).ctl_addr))->init_ctrl) & (1<<17)) != 0)
+
+#define ISZLOADED(card) (((ZO_V1==cy_readl(&((struct RUNTIME_9060 __iomem *) \
+ ((card).ctl_addr))->mail_box_0)) || \
+ Z_FPGA_CHECK(card)) && \
+ (ZFIRM_ID==cy_readl(&((struct FIRM_ID __iomem *) \
+ ((card).base_addr+ID_ADDRESS))->signature)))
+
+#ifndef SERIAL_XMIT_SIZE
+#define SERIAL_XMIT_SIZE (min(PAGE_SIZE, 4096))
+#endif
+#define WAKEUP_CHARS 256
+
+#define STD_COM_FLAGS (0)
+
+#define JIFFIES_DIFF(n, j) ((j) - (n))
+
+static struct tty_driver *cy_serial_driver;
+
+#ifdef CONFIG_ISA
+/* This is the address lookup table. The driver will probe for
+ Cyclom-Y/ISA boards at all addresses in here. If you want the
+ driver to probe addresses at a different address, add it to
+ this table. If the driver is probing some other board and
+ causing problems, remove the offending address from this table.
+ The cy_setup function extracts additional addresses from the
+ boot options line. The form is "cyclades=address,address..."
+*/
+
+static unsigned int cy_isa_addresses[] = {
+ 0xD0000,
+ 0xD2000,
+ 0xD4000,
+ 0xD6000,
+ 0xD8000,
+ 0xDA000,
+ 0xDC000,
+ 0xDE000,
+ 0,0,0,0,0,0,0,0
+};
+#define NR_ISA_ADDRS (sizeof(cy_isa_addresses)/sizeof(unsigned char*))
+
+#ifdef MODULE
+static long maddr[NR_CARDS] = { 0, };
+static int irq[NR_CARDS] = { 0, };
+
+module_param_array(maddr, long, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+#endif
+
+#endif /* CONFIG_ISA */
+
+/* This is the per-card data structure containing address, irq, number of
+ channels, etc. This driver supports a maximum of NR_CARDS cards.
+*/
+static struct cyclades_card cy_card[NR_CARDS];
+
+/* This is the per-channel data structure containing pointers, flags
+ and variables for the port. This driver supports a maximum of NR_PORTS.
+*/
+static struct cyclades_port cy_port[NR_PORTS];
+
+static int cy_next_channel; /* next minor available */
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write. We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open. This buffer is
+ * allocated when the first cy_open occurs.
+ */
+static unsigned char *tmp_buf;
+
+/*
+ * This is used to look up the divisor speeds and the timeouts
+ * We're normally limited to 15 distinct baud rates. The extra
+ * are accessed via settings in info->flags.
+ * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ * 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ * HI VHI
+ * 20
+ */
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
+ 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800,115200,150000,
+ 230400, 0};
+
+static char baud_co_25[] = { /* 25 MHz clock option table */
+ /* value => 00 01 02 03 04 */
+ /* divide by 8 32 128 512 2048 */
+ 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02,
+ 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char baud_bpr_25[] = { /* 25 MHz baud rate period table */
+ 0x00, 0xf5, 0xa3, 0x6f, 0x5c, 0x51, 0xf5, 0xa3, 0x51, 0xa3,
+ 0x6d, 0x51, 0xa3, 0x51, 0xa3, 0x51, 0x36, 0x29, 0x1b, 0x15};
+
+static char baud_co_60[] = { /* 60 MHz clock option table (CD1400 J) */
+ /* value => 00 01 02 03 04 */
+ /* divide by 8 32 128 512 2048 */
+ 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03,
+ 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00};
+
+static char baud_bpr_60[] = { /* 60 MHz baud rate period table (CD1400 J) */
+ 0x00, 0x82, 0x21, 0xff, 0xdb, 0xc3, 0x92, 0x62, 0xc3, 0x62,
+ 0x41, 0xc3, 0x62, 0xc3, 0x62, 0xc3, 0x82, 0x62, 0x41, 0x32,
+ 0x21};
+
+static char baud_cor3[] = { /* receive threshold */
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x07,
+ 0x07};
+
+/*
+ * The Cyclades driver implements HW flow control as any serial driver.
+ * The cyclades_port structure member rflow and the vector rflow_thr
+ * allows us to take advantage of a special feature in the CD1400 to avoid
+ * data loss even when the system interrupt latency is too high. These flags
+ * are to be used only with very special applications. Setting these flags
+ * requires the use of a special cable (DTR and RTS reversed). In the new
+ * CD1400-based boards (rev. 6.00 or later), there is no need for special
+ * cables.
+ */
+
+static char rflow_thr[] = { /* rflow threshold */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a};
+
+/* The Cyclom-Ye has placed the sequential chips in non-sequential
+ * address order. This look-up table overcomes that problem.
+ */
+static int cy_chip_offset [] =
+ { 0x0000,
+ 0x0400,
+ 0x0800,
+ 0x0C00,
+ 0x0200,
+ 0x0600,
+ 0x0A00,
+ 0x0E00
+ };
+
+/* PCI related definitions */
+
+static unsigned short cy_pci_nboard;
+static unsigned short cy_isa_nboard;
+static unsigned short cy_nboard;
+#ifdef CONFIG_PCI
+static unsigned short cy_pci_dev_id[] = {
+ PCI_DEVICE_ID_CYCLOM_Y_Lo, /* PCI < 1Mb */
+ PCI_DEVICE_ID_CYCLOM_Y_Hi, /* PCI > 1Mb */
+ PCI_DEVICE_ID_CYCLOM_4Y_Lo, /* 4Y PCI < 1Mb */
+ PCI_DEVICE_ID_CYCLOM_4Y_Hi, /* 4Y PCI > 1Mb */
+ PCI_DEVICE_ID_CYCLOM_8Y_Lo, /* 8Y PCI < 1Mb */
+ PCI_DEVICE_ID_CYCLOM_8Y_Hi, /* 8Y PCI > 1Mb */
+ PCI_DEVICE_ID_CYCLOM_Z_Lo, /* Z PCI < 1Mb */
+ PCI_DEVICE_ID_CYCLOM_Z_Hi, /* Z PCI > 1Mb */
+ 0 /* end of table */
+ };
+#endif
+
+static void cy_start(struct tty_struct *);
+static void set_line_char(struct cyclades_port *);
+static int cyz_issue_cmd(struct cyclades_card *, uclong, ucchar, uclong);
+#ifdef CONFIG_ISA
+static unsigned detect_isa_irq(void __iomem *);
+#endif /* CONFIG_ISA */
+
+static int cyclades_get_proc_info(char *, char **, off_t , int , int *, void *);
+
+#ifndef CONFIG_CYZ_INTR
+static void cyz_poll(unsigned long);
+
+/* The Cyclades-Z polling cycle is defined by this variable */
+static long cyz_polling_cycle = CZ_DEF_POLL;
+
+static int cyz_timeron = 0;
+static struct timer_list cyz_timerlist = TIMER_INITIALIZER(cyz_poll, 0, 0);
+
+#else /* CONFIG_CYZ_INTR */
+static void cyz_rx_restart(unsigned long);
+static struct timer_list cyz_rx_full_timer[NR_PORTS];
+#endif /* CONFIG_CYZ_INTR */
+
+static inline int
+serial_paranoia_check(struct cyclades_port *info,
+ char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+ static const char *badmagic =
+ "cyc Warning: bad magic number for serial struct (%s) in %s\n";
+ static const char *badinfo =
+ "cyc Warning: null cyclades_port for (%s) in %s\n";
+ static const char *badrange =
+ "cyc Warning: cyclades_port out of range for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+
+ if( (long)info < (long)(&cy_port[0])
+ || (long)(&cy_port[NR_PORTS]) < (long)info ){
+ printk(badrange, name, routine);
+ return 1;
+ }
+
+ if (info->magic != CYCLADES_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+} /* serial_paranoia_check */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver
+ * (also known as the "bottom half"). This can be called any
+ * number of times for any channel without harm.
+ */
+static inline void
+cy_sched_event(struct cyclades_port *info, int event)
+{
+ info->event |= 1 << event; /* remember what kind of event and who */
+ schedule_work(&info->tqueue);
+} /* cy_sched_event */
+
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * cy#/_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using cy_sched_event(), and they get done here.
+ *
+ * This is done through one level of indirection--the task queue.
+ * When a hardware interrupt service routine wants service by the
+ * driver's bottom half, it enqueues the appropriate tq_struct (one
+ * per port) to the keventd work queue and sets a request flag
+ * that the work queue be processed.
+ *
+ * Although this may seem unwieldy, it gives the system a way to
+ * pass an argument (in this case the pointer to the cyclades_port
+ * structure) to the bottom half of the driver. Previous kernels
+ * had to poll every port to see if that port needed servicing.
+ */
+static void
+do_softint(void *private_)
+{
+ struct cyclades_port *info = (struct cyclades_port *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+ if (!tty)
+ return;
+
+ if (test_and_clear_bit(Cy_EVENT_HANGUP, &info->event)) {
+ tty_hangup(info->tty);
+ wake_up_interruptible(&info->open_wait);
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ }
+ if (test_and_clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) {
+ wake_up_interruptible(&info->open_wait);
+ }
+#ifdef CONFIG_CYZ_INTR
+ if (test_and_clear_bit(Cy_EVENT_Z_RX_FULL, &info->event)) {
+ if (cyz_rx_full_timer[info->line].function == NULL) {
+ cyz_rx_full_timer[info->line].expires = jiffies + 1;
+ cyz_rx_full_timer[info->line].function = cyz_rx_restart;
+ cyz_rx_full_timer[info->line].data = (unsigned long)info;
+ add_timer(&cyz_rx_full_timer[info->line]);
+ }
+ }
+#endif
+ if (test_and_clear_bit(Cy_EVENT_DELTA_WAKEUP, &info->event)) {
+ wake_up_interruptible(&info->delta_msr_wait);
+ }
+ if (test_and_clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+#ifdef Z_WAKE
+ if (test_and_clear_bit(Cy_EVENT_SHUTDOWN_WAKEUP, &info->event)) {
+ wake_up_interruptible(&info->shutdown_wait);
+ }
+#endif
+} /* do_softint */
+
+
+/***********************************************************/
+/********* Start of block of Cyclom-Y specific code ********/
+
+/* This routine waits up to 1000 micro-seconds for the previous
+ command to the Cirrus chip to complete and then issues the
+ new command. An error is returned if the previous command
+ didn't finish within the time limit.
+
+ This function is only called from inside spinlock-protected code.
+ */
+static int
+cyy_issue_cmd(void __iomem *base_addr, u_char cmd, int index)
+{
+ volatile int i;
+
+ /* Check to see that the previous command has completed */
+ for(i = 0 ; i < 100 ; i++){
+ if (cy_readb(base_addr+(CyCCR<<index)) == 0){
+ break;
+ }
+ udelay(10L);
+ }
+ /* if the CCR never cleared, the previous command
+ didn't finish within the "reasonable time" */
+ if (i == 100) return (-1);
+
+ /* Issue the new command */
+ cy_writeb(base_addr+(CyCCR<<index), cmd);
+
+ return(0);
+} /* cyy_issue_cmd */
+
+#ifdef CONFIG_ISA
+/* ISA interrupt detection code */
+static unsigned
+detect_isa_irq(void __iomem *address)
+{
+ int irq;
+ unsigned long irqs, flags;
+ int save_xir, save_car;
+ int index = 0; /* IRQ probing is only for ISA */
+
+ /* forget possible initially masked and pending IRQ */
+ irq = probe_irq_off(probe_irq_on());
+
+ /* Clear interrupts on the board first */
+ cy_writeb(address + (Cy_ClrIntr<<index), 0);
+ /* Cy_ClrIntr is 0x1800 */
+
+ irqs = probe_irq_on();
+ /* Wait ... */
+ udelay(5000L);
+
+ /* Enable the Tx interrupts on the CD1400 */
+ local_irq_save(flags);
+ cy_writeb(address + (CyCAR<<index), 0);
+ cyy_issue_cmd(address, CyCHAN_CTL|CyENB_XMTR, index);
+
+ cy_writeb(address + (CyCAR<<index), 0);
+ cy_writeb(address + (CySRER<<index),
+ cy_readb(address + (CySRER<<index)) | CyTxRdy);
+ local_irq_restore(flags);
+
+ /* Wait ... */
+ udelay(5000L);
+
+ /* Check which interrupt is in use */
+ irq = probe_irq_off(irqs);
+
+ /* Clean up */
+ save_xir = (u_char) cy_readb(address + (CyTIR<<index));
+ save_car = cy_readb(address + (CyCAR<<index));
+ cy_writeb(address + (CyCAR<<index), (save_xir & 0x3));
+ cy_writeb(address + (CySRER<<index),
+ cy_readb(address + (CySRER<<index)) & ~CyTxRdy);
+ cy_writeb(address + (CyTIR<<index), (save_xir & 0x3f));
+ cy_writeb(address + (CyCAR<<index), (save_car));
+ cy_writeb(address + (Cy_ClrIntr<<index), 0);
+ /* Cy_ClrIntr is 0x1800 */
+
+ return (irq > 0)? irq : 0;
+}
+#endif /* CONFIG_ISA */
+
+/* The real interrupt service routine is called
+ whenever the card wants its hand held--chars
+ received, out buffer empty, modem change, etc.
+ */
+static irqreturn_t
+cyy_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct tty_struct *tty;
+ int status;
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info;
+ void __iomem *base_addr, *card_base_addr;
+ int chip;
+ int save_xir, channel, save_car;
+ char data;
+ volatile int char_count;
+ int outch;
+ int i,j,index;
+ int too_many;
+ int had_work;
+ int mdm_change;
+ int mdm_status;
+
+ if((cinfo = (struct cyclades_card *)dev_id) == 0){
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyy_interrupt: spurious interrupt %d\n\r", irq);
+#endif
+ return IRQ_NONE; /* spurious interrupt */
+ }
+
+ card_base_addr = cinfo->base_addr;
+ index = cinfo->bus_index;
+
+
+ /* This loop checks all chips in the card. Make a note whenever
+ _any_ chip had some work to do, as this is considered an
+ indication that there will be more to do. Only when no chip
+ has any work does this outermost loop exit.
+ */
+ do{
+ had_work = 0;
+ for ( chip = 0 ; chip < cinfo->num_chips ; chip ++) {
+ base_addr = cinfo->base_addr + (cy_chip_offset[chip]<<index);
+ too_many = 0;
+ while ( (status = cy_readb(base_addr+(CySVRR<<index))) != 0x00) {
+ had_work++;
+ /* The purpose of the following test is to ensure that
+ no chip can monopolize the driver. This forces the
+ chips to be checked in a round-robin fashion (after
+ draining each of a bunch (1000) of characters).
+ */
+ if(1000<too_many++){
+ break;
+ }
+ if (status & CySRReceive) { /* reception interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyy_interrupt: rcvd intr, chip %d\n\r", chip);
+#endif
+ /* determine the channel & change to that context */
+ spin_lock(&cinfo->card_lock);
+ save_xir = (u_char) cy_readb(base_addr+(CyRIR<<index));
+ channel = (u_short ) (save_xir & CyIRChannel);
+ i = channel + chip * 4 + cinfo->first_line;
+ info = &cy_port[i];
+ info->last_active = jiffies;
+ save_car = cy_readb(base_addr+(CyCAR<<index));
+ cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+ /* if there is nowhere to put the data, discard it */
+ if(info->tty == 0){
+ j = (cy_readb(base_addr+(CyRIVR<<index)) & CyIVRMask);
+ if ( j == CyIVRRxEx ) { /* exception */
+ data = cy_readb(base_addr+(CyRDSR<<index));
+ } else { /* normal character reception */
+ char_count = cy_readb(base_addr+(CyRDCR<<index));
+ while(char_count--){
+ data = cy_readb(base_addr+(CyRDSR<<index));
+ }
+ }
+ }else{ /* there is an open port for this data */
+ tty = info->tty;
+ j = (cy_readb(base_addr+(CyRIVR<<index)) & CyIVRMask);
+ if ( j == CyIVRRxEx ) { /* exception */
+ data = cy_readb(base_addr+(CyRDSR<<index));
+
+ /* For statistics only */
+ if (data & CyBREAK)
+ info->icount.brk++;
+ else if(data & CyFRAME)
+ info->icount.frame++;
+ else if(data & CyPARITY)
+ info->icount.parity++;
+ else if(data & CyOVERRUN)
+ info->icount.overrun++;
+
+ if(data & info->ignore_status_mask){
+ info->icount.rx++;
+ continue;
+ }
+ if (tty->flip.count < TTY_FLIPBUF_SIZE){
+ tty->flip.count++;
+ if (data & info->read_status_mask){
+ if(data & CyBREAK){
+ *tty->flip.flag_buf_ptr++ =
+ TTY_BREAK;
+ *tty->flip.char_buf_ptr++ =
+ cy_readb(base_addr+(CyRDSR<<index));
+ info->icount.rx++;
+ if (info->flags & ASYNC_SAK){
+ do_SAK(tty);
+ }
+ }else if(data & CyFRAME){
+ *tty->flip.flag_buf_ptr++ =
+ TTY_FRAME;
+ *tty->flip.char_buf_ptr++ =
+ cy_readb(base_addr+(CyRDSR<<index));
+ info->icount.rx++;
+ info->idle_stats.frame_errs++;
+ }else if(data & CyPARITY){
+ *tty->flip.flag_buf_ptr++ =
+ TTY_PARITY;
+ *tty->flip.char_buf_ptr++ =
+ cy_readb(base_addr+(CyRDSR<<index));
+ info->icount.rx++;
+ info->idle_stats.parity_errs++;
+ }else if(data & CyOVERRUN){
+ *tty->flip.flag_buf_ptr++ =
+ TTY_OVERRUN;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ /* If the flip buffer itself is
+ overflowing, we still lose
+ the next incoming character.
+ */
+ if(tty->flip.count
+ < TTY_FLIPBUF_SIZE){
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ =
+ TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ =
+ cy_readb(base_addr+(CyRDSR<<index));
+ info->icount.rx++;
+ }
+ info->idle_stats.overruns++;
+ /* These two conditions may imply */
+ /* a normal read should be done. */
+ /* }else if(data & CyTIMEOUT){ */
+ /* }else if(data & CySPECHAR){ */
+ }else{
+ *tty->flip.flag_buf_ptr++ = 0;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ }
+ }else{
+ *tty->flip.flag_buf_ptr++ = 0;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ }
+ }else{
+ /* there was a software buffer
+ overrun and nothing could be
+ done about it!!! */
+ info->icount.buf_overrun++;
+ info->idle_stats.overruns++;
+ }
+ } else { /* normal character reception */
+ /* load # chars available from the chip */
+ char_count = cy_readb(base_addr+(CyRDCR<<index));
+
+#ifdef CY_ENABLE_MONITORING
+ ++info->mon.int_count;
+ info->mon.char_count += char_count;
+ if (char_count > info->mon.char_max)
+ info->mon.char_max = char_count;
+ info->mon.char_last = char_count;
+#endif
+ while(char_count--){
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE){
+ break;
+ }
+ tty->flip.count++;
+ data = cy_readb(base_addr+(CyRDSR<<index));
+ *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ = data;
+ info->idle_stats.recv_bytes++;
+ info->icount.rx++;
+#ifdef CY_16Y_HACK
+ udelay(10L);
+#endif
+ }
+ info->idle_stats.recv_idle = jiffies;
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+ /* end of service */
+ cy_writeb(base_addr+(CyRIR<<index), (save_xir & 0x3f));
+ cy_writeb(base_addr+(CyCAR<<index), (save_car));
+ spin_unlock(&cinfo->card_lock);
+ }
+
+
+ if (status & CySRTransmit) { /* transmission interrupt */
+ /* Since we only get here when the transmit buffer
+ is empty, we know we can always stuff a dozen
+ characters. */
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyy_interrupt: xmit intr, chip %d\n\r", chip);
+#endif
+
+ /* determine the channel & change to that context */
+ spin_lock(&cinfo->card_lock);
+ save_xir = (u_char) cy_readb(base_addr+(CyTIR<<index));
+ channel = (u_short ) (save_xir & CyIRChannel);
+ i = channel + chip * 4 + cinfo->first_line;
+ save_car = cy_readb(base_addr+(CyCAR<<index));
+ cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+ /* validate the port# (as configured and open) */
+ if( (i < 0) || (NR_PORTS <= i) ){
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+ goto txend;
+ }
+ info = &cy_port[i];
+ info->last_active = jiffies;
+ if(info->tty == 0){
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+ goto txdone;
+ }
+
+ /* load the on-chip space for outbound data */
+ char_count = info->xmit_fifo_size;
+
+ if(info->x_char) { /* send special char */
+ outch = info->x_char;
+ cy_writeb(base_addr+(CyTDR<<index), outch);
+ char_count--;
+ info->icount.tx++;
+ info->x_char = 0;
+ }
+
+ if (info->breakon || info->breakoff) {
+ if (info->breakon) {
+ cy_writeb(base_addr + (CyTDR<<index), 0);
+ cy_writeb(base_addr + (CyTDR<<index), 0x81);
+ info->breakon = 0;
+ char_count -= 2;
+ }
+ if (info->breakoff) {
+ cy_writeb(base_addr + (CyTDR<<index), 0);
+ cy_writeb(base_addr + (CyTDR<<index), 0x83);
+ info->breakoff = 0;
+ char_count -= 2;
+ }
+ }
+
+ while (char_count-- > 0){
+ if (!info->xmit_cnt){
+ if (cy_readb(base_addr+(CySRER<<index))&CyTxMpty) {
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) &
+ ~CyTxMpty);
+ } else {
+ cy_writeb(base_addr+(CySRER<<index),
+ ((cy_readb(base_addr+(CySRER<<index))
+ & ~CyTxRdy)
+ | CyTxMpty));
+ }
+ goto txdone;
+ }
+ if (info->xmit_buf == 0){
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) &
+ ~CyTxRdy);
+ goto txdone;
+ }
+ if (info->tty->stopped || info->tty->hw_stopped){
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) &
+ ~CyTxRdy);
+ goto txdone;
+ }
+ /* Because the Embedded Transmit Commands have
+ been enabled, we must check to see if the
+ escape character, NULL, is being sent. If it
+ is, we must ensure that there is room for it
+ to be doubled in the output stream. Therefore
+ we no longer advance the pointer when the
+ character is fetched, but rather wait until
+ after the check for a NULL output character.
+ This is necessary because there may not be
+ room for the two chars needed to send a NULL.)
+ */
+ outch = info->xmit_buf[info->xmit_tail];
+ if( outch ){
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1)
+ & (SERIAL_XMIT_SIZE - 1);
+ cy_writeb(base_addr+(CyTDR<<index), outch);
+ info->icount.tx++;
+ }else{
+ if(char_count > 1){
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1)
+ & (SERIAL_XMIT_SIZE - 1);
+ cy_writeb(base_addr+(CyTDR<<index),
+ outch);
+ cy_writeb(base_addr+(CyTDR<<index), 0);
+ info->icount.tx++;
+ char_count--;
+ }else{
+ }
+ }
+ }
+
+ txdone:
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+ }
+ txend:
+ /* end of service */
+ cy_writeb(base_addr+(CyTIR<<index),
+ (save_xir & 0x3f));
+ cy_writeb(base_addr+(CyCAR<<index), (save_car));
+ spin_unlock(&cinfo->card_lock);
+ }
+
+ if (status & CySRModem) { /* modem interrupt */
+
+ /* determine the channel & change to that context */
+ spin_lock(&cinfo->card_lock);
+ save_xir = (u_char) cy_readb(base_addr+(CyMIR<<index));
+ channel = (u_short ) (save_xir & CyIRChannel);
+ info = &cy_port[channel + chip * 4
+ + cinfo->first_line];
+ info->last_active = jiffies;
+ save_car = cy_readb(base_addr+(CyCAR<<index));
+ cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+ mdm_change = cy_readb(base_addr+(CyMISR<<index));
+ mdm_status = cy_readb(base_addr+(CyMSVR1<<index));
+
+ if(info->tty == 0){/* no place for data, ignore it*/
+ ;
+ }else{
+ if (mdm_change & CyANY_DELTA) {
+ /* For statistics only */
+ if (mdm_change & CyDCD) info->icount.dcd++;
+ if (mdm_change & CyCTS) info->icount.cts++;
+ if (mdm_change & CyDSR) info->icount.dsr++;
+ if (mdm_change & CyRI) info->icount.rng++;
+
+ cy_sched_event(info, Cy_EVENT_DELTA_WAKEUP);
+ }
+
+ if((mdm_change & CyDCD)
+ && (info->flags & ASYNC_CHECK_CD)){
+ if(mdm_status & CyDCD){
+ cy_sched_event(info,
+ Cy_EVENT_OPEN_WAKEUP);
+ }else{
+ cy_sched_event(info,
+ Cy_EVENT_HANGUP);
+ }
+ }
+ if((mdm_change & CyCTS)
+ && (info->flags & ASYNC_CTS_FLOW)){
+ if(info->tty->hw_stopped){
+ if(mdm_status & CyCTS){
+ /* cy_start isn't used
+ because... !!! */
+ info->tty->hw_stopped = 0;
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) |
+ CyTxRdy);
+ cy_sched_event(info,
+ Cy_EVENT_WRITE_WAKEUP);
+ }
+ }else{
+ if(!(mdm_status & CyCTS)){
+ /* cy_stop isn't used
+ because ... !!! */
+ info->tty->hw_stopped = 1;
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) &
+ ~CyTxRdy);
+ }
+ }
+ }
+ if(mdm_change & CyDSR){
+ }
+ if(mdm_change & CyRI){
+ }
+ }
+ /* end of service */
+ cy_writeb(base_addr+(CyMIR<<index),
+ (save_xir & 0x3f));
+ cy_writeb(base_addr+(CyCAR<<index), save_car);
+ spin_unlock(&cinfo->card_lock);
+ }
+ } /* end while status != 0 */
+ } /* end loop for chips... */
+ } while(had_work);
+
+ /* clear interrupts */
+ spin_lock(&cinfo->card_lock);
+ cy_writeb(card_base_addr + (Cy_ClrIntr<<index), 0);
+ /* Cy_ClrIntr is 0x1800 */
+ spin_unlock(&cinfo->card_lock);
+ return IRQ_HANDLED;
+} /* cyy_interrupt */
+
+/***********************************************************/
+/********* End of block of Cyclom-Y specific code **********/
+/******** Start of block of Cyclades-Z specific code *********/
+/***********************************************************/
+
+static int
+cyz_fetch_msg( struct cyclades_card *cinfo,
+ uclong *channel, ucchar *cmd, uclong *param)
+{
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ unsigned long loc_doorbell;
+
+ firm_id = cinfo->base_addr + ID_ADDRESS;
+ if (!ISZLOADED(*cinfo)){
+ return (-1);
+ }
+ zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+
+ loc_doorbell = cy_readl(&((struct RUNTIME_9060 __iomem *)
+ (cinfo->ctl_addr))->loc_doorbell);
+ if (loc_doorbell){
+ *cmd = (char)(0xff & loc_doorbell);
+ *channel = cy_readl(&board_ctrl->fwcmd_channel);
+ *param = (uclong)cy_readl(&board_ctrl->fwcmd_param);
+ cy_writel(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))->loc_doorbell,
+ 0xffffffff);
+ return 1;
+ }
+ return 0;
+} /* cyz_fetch_msg */
+
+static int
+cyz_issue_cmd( struct cyclades_card *cinfo,
+ uclong channel, ucchar cmd, uclong param)
+{
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ unsigned long __iomem *pci_doorbell;
+ int index;
+
+ firm_id = cinfo->base_addr + ID_ADDRESS;
+ if (!ISZLOADED(*cinfo)){
+ return (-1);
+ }
+ zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+
+ index = 0;
+ pci_doorbell = &((struct RUNTIME_9060 __iomem *) (cinfo->ctl_addr))->pci_doorbell;
+ while( (cy_readl(pci_doorbell) & 0xff) != 0){
+ if (index++ == 1000){
+ return((int)(cy_readl(pci_doorbell) & 0xff));
+ }
+ udelay(50L);
+ }
+ cy_writel(&board_ctrl->hcmd_channel, channel);
+ cy_writel(&board_ctrl->hcmd_param , param);
+ cy_writel(pci_doorbell, (long)cmd);
+
+ return(0);
+} /* cyz_issue_cmd */
+
+static void
+cyz_handle_rx(struct cyclades_port *info,
+ volatile struct CH_CTRL __iomem *ch_ctrl,
+ volatile struct BUF_CTRL __iomem *buf_ctrl)
+{
+ struct cyclades_card *cinfo = &cy_card[info->card];
+ struct tty_struct *tty = info->tty;
+ volatile int char_count;
+#ifdef BLOCKMOVE
+ int small_count;
+#else
+ char data;
+#endif
+ volatile uclong rx_put, rx_get, new_rx_get, rx_bufsize, rx_bufaddr;
+
+ rx_get = new_rx_get = cy_readl(&buf_ctrl->rx_get);
+ rx_put = cy_readl(&buf_ctrl->rx_put);
+ rx_bufsize = cy_readl(&buf_ctrl->rx_bufsize);
+ rx_bufaddr = cy_readl(&buf_ctrl->rx_bufaddr);
+ if (rx_put >= rx_get)
+ char_count = rx_put - rx_get;
+ else
+ char_count = rx_put - rx_get + rx_bufsize;
+
+ if ( char_count ) {
+ info->last_active = jiffies;
+ info->jiffies[1] = jiffies;
+
+#ifdef CY_ENABLE_MONITORING
+ info->mon.int_count++;
+ info->mon.char_count += char_count;
+ if (char_count > info->mon.char_max)
+ info->mon.char_max = char_count;
+ info->mon.char_last = char_count;
+#endif
+ if(tty == 0){
+ /* flush received characters */
+ new_rx_get = (new_rx_get + char_count) & (rx_bufsize - 1);
+ info->rflush_count++;
+ }else{
+#ifdef BLOCKMOVE
+ /* we'd like to use memcpy(t, f, n) and memset(s, c, count)
+ for performance, but because of buffer boundaries, there
+ may be several steps to the operation */
+ while(0 < (small_count =
+ min_t(unsigned int, (rx_bufsize - new_rx_get),
+ min_t(unsigned int, (TTY_FLIPBUF_SIZE - tty->flip.count), char_count))
+ )) {
+ memcpy_fromio(tty->flip.char_buf_ptr,
+ (char *)(cinfo->base_addr
+ + rx_bufaddr + new_rx_get),
+ small_count);
+
+ tty->flip.char_buf_ptr += small_count;
+ memset(tty->flip.flag_buf_ptr, TTY_NORMAL, small_count);
+ tty->flip.flag_buf_ptr += small_count;
+ new_rx_get = (new_rx_get + small_count) & (rx_bufsize - 1);
+ char_count -= small_count;
+ info->icount.rx += small_count;
+ info->idle_stats.recv_bytes += small_count;
+ tty->flip.count += small_count;
+ }
+#else
+ while(char_count--){
+ if (tty->flip.count >= N_TTY_BUF_SIZE - tty->read_cnt)
+ break;
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ break;
+
+ data = cy_readb(cinfo->base_addr + rx_bufaddr + new_rx_get);
+ new_rx_get = (new_rx_get + 1) & (rx_bufsize - 1);
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ = data;
+ info->idle_stats.recv_bytes++;
+ info->icount.rx++;
+ }
+#endif
+#ifdef CONFIG_CYZ_INTR
+ /* Recalculate the number of chars in the RX buffer and issue
+ a cmd in case it's higher than the RX high water mark */
+ rx_put = cy_readl(&buf_ctrl->rx_put);
+ if (rx_put >= rx_get)
+ char_count = rx_put - rx_get;
+ else
+ char_count = rx_put - rx_get + rx_bufsize;
+ if(char_count >= cy_readl(&buf_ctrl->rx_threshold)) {
+ cy_sched_event(info, Cy_EVENT_Z_RX_FULL);
+ }
+#endif
+ info->idle_stats.recv_idle = jiffies;
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+ /* Update rx_get */
+ cy_writel(&buf_ctrl->rx_get, new_rx_get);
+ }
+}
+
+static void
+cyz_handle_tx(struct cyclades_port *info,
+ volatile struct CH_CTRL __iomem *ch_ctrl,
+ volatile struct BUF_CTRL __iomem *buf_ctrl)
+{
+ struct cyclades_card *cinfo = &cy_card[info->card];
+ struct tty_struct *tty = info->tty;
+ char data;
+ volatile int char_count;
+#ifdef BLOCKMOVE
+ int small_count;
+#endif
+ volatile uclong tx_put, tx_get, tx_bufsize, tx_bufaddr;
+
+ if (info->xmit_cnt <= 0) /* Nothing to transmit */
+ return;
+
+ tx_get = cy_readl(&buf_ctrl->tx_get);
+ tx_put = cy_readl(&buf_ctrl->tx_put);
+ tx_bufsize = cy_readl(&buf_ctrl->tx_bufsize);
+ tx_bufaddr = cy_readl(&buf_ctrl->tx_bufaddr);
+ if (tx_put >= tx_get)
+ char_count = tx_get - tx_put - 1 + tx_bufsize;
+ else
+ char_count = tx_get - tx_put - 1;
+
+ if ( char_count ) {
+
+ if( tty == 0 ){
+ goto ztxdone;
+ }
+
+ if(info->x_char) { /* send special char */
+ data = info->x_char;
+
+ cy_writeb((cinfo->base_addr + tx_bufaddr + tx_put), data);
+ tx_put = (tx_put + 1) & (tx_bufsize - 1);
+ info->x_char = 0;
+ char_count--;
+ info->icount.tx++;
+ info->last_active = jiffies;
+ info->jiffies[2] = jiffies;
+ }
+#ifdef BLOCKMOVE
+ while(0 < (small_count =
+ min_t(unsigned int, (tx_bufsize - tx_put),
+ min_t(unsigned int, (SERIAL_XMIT_SIZE - info->xmit_tail),
+ min_t(unsigned int, info->xmit_cnt, char_count))))) {
+
+ memcpy_toio((char *)(cinfo->base_addr + tx_bufaddr + tx_put),
+ &info->xmit_buf[info->xmit_tail],
+ small_count);
+
+ tx_put = (tx_put + small_count) & (tx_bufsize - 1);
+ char_count -= small_count;
+ info->icount.tx += small_count;
+ info->xmit_cnt -= small_count;
+ info->xmit_tail =
+ (info->xmit_tail + small_count) & (SERIAL_XMIT_SIZE - 1);
+ info->last_active = jiffies;
+ info->jiffies[2] = jiffies;
+ }
+#else
+ while (info->xmit_cnt && char_count){
+ data = info->xmit_buf[info->xmit_tail];
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+
+ cy_writeb(cinfo->base_addr + tx_bufaddr + tx_put, data);
+ tx_put = (tx_put + 1) & (tx_bufsize - 1);
+ char_count--;
+ info->icount.tx++;
+ info->last_active = jiffies;
+ info->jiffies[2] = jiffies;
+ }
+#endif
+ ztxdone:
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+ }
+ /* Update tx_put */
+ cy_writel(&buf_ctrl->tx_put, tx_put);
+ }
+}
+
+static void
+cyz_handle_cmd(struct cyclades_card *cinfo)
+{
+ struct tty_struct *tty;
+ struct cyclades_port *info;
+ static volatile struct FIRM_ID __iomem *firm_id;
+ static volatile struct ZFW_CTRL __iomem *zfw_ctrl;
+ static volatile struct BOARD_CTRL __iomem *board_ctrl;
+ static volatile struct CH_CTRL __iomem *ch_ctrl;
+ static volatile struct BUF_CTRL __iomem *buf_ctrl;
+ uclong channel;
+ ucchar cmd;
+ uclong param;
+ uclong hw_ver, fw_ver;
+ int special_count;
+ int delta_count;
+
+ firm_id = cinfo->base_addr + ID_ADDRESS;
+ zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ fw_ver = cy_readl(&board_ctrl->fw_version);
+ hw_ver = cy_readl(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))->mail_box_0);
+
+
+ while(cyz_fetch_msg(cinfo, &channel, &cmd, &param) == 1) {
+ special_count = 0;
+ delta_count = 0;
+ info = &cy_port[channel + cinfo->first_line];
+ if((tty = info->tty) == 0) {
+ continue;
+ }
+ ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+ buf_ctrl = &(zfw_ctrl->buf_ctrl[channel]);
+
+ switch(cmd) {
+ case C_CM_PR_ERROR:
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_FR_ERROR:
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_RXBRK:
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ *tty->flip.char_buf_ptr++ = 0;
+ info->icount.rx++;
+ special_count++;
+ break;
+ case C_CM_MDCD:
+ info->icount.dcd++;
+ delta_count++;
+ if (info->flags & ASYNC_CHECK_CD){
+ if ((fw_ver > 241 ?
+ ((u_long)param) :
+ cy_readl(&ch_ctrl->rs_status)) & C_RS_DCD) {
+ cy_sched_event(info, Cy_EVENT_OPEN_WAKEUP);
+ }else{
+ cy_sched_event(info, Cy_EVENT_HANGUP);
+ }
+ }
+ break;
+ case C_CM_MCTS:
+ info->icount.cts++;
+ delta_count++;
+ break;
+ case C_CM_MRI:
+ info->icount.rng++;
+ delta_count++;
+ break;
+ case C_CM_MDSR:
+ info->icount.dsr++;
+ delta_count++;
+ break;
+#ifdef Z_WAKE
+ case C_CM_IOCTLW:
+ cy_sched_event(info, Cy_EVENT_SHUTDOWN_WAKEUP);
+ break;
+#endif
+#ifdef CONFIG_CYZ_INTR
+ case C_CM_RXHIWM:
+ case C_CM_RXNNDT:
+ case C_CM_INTBACK2:
+ /* Reception Interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyz_interrupt: rcvd intr, card %d, port %ld\n\r",
+ info->card, channel);
+#endif
+ cyz_handle_rx(info, ch_ctrl, buf_ctrl);
+ break;
+ case C_CM_TXBEMPTY:
+ case C_CM_TXLOWWM:
+ case C_CM_INTBACK:
+ /* Transmission Interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyz_interrupt: xmit intr, card %d, port %ld\n\r",
+ info->card, channel);
+#endif
+ cyz_handle_tx(info, ch_ctrl, buf_ctrl);
+ break;
+#endif /* CONFIG_CYZ_INTR */
+ case C_CM_FATAL:
+ /* should do something with this !!! */
+ break;
+ default:
+ break;
+ }
+ if(delta_count)
+ cy_sched_event(info, Cy_EVENT_DELTA_WAKEUP);
+ if(special_count)
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+}
+
+#ifdef CONFIG_CYZ_INTR
+static irqreturn_t
+cyz_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct cyclades_card *cinfo;
+
+ if((cinfo = (struct cyclades_card *)dev_id) == 0){
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyz_interrupt: spurious interrupt %d\n\r", irq);
+#endif
+ return IRQ_NONE; /* spurious interrupt */
+ }
+
+ if (!ISZLOADED(*cinfo)) {
+#ifdef CY_DEBUG_INTERRUPTS
+ printk("cyz_interrupt: board not yet loaded (IRQ%d).\n\r", irq);
+#endif
+ return IRQ_NONE;
+ }
+
+ /* Handle the interrupts */
+ cyz_handle_cmd(cinfo);
+
+ return IRQ_HANDLED;
+} /* cyz_interrupt */
+
+static void
+cyz_rx_restart(unsigned long arg)
+{
+ struct cyclades_port *info = (struct cyclades_port *)arg;
+ int retval;
+ int card = info->card;
+ uclong channel = (info->line) - (cy_card[card].first_line);
+ unsigned long flags;
+
+ CY_LOCK(info, flags);
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_INTBACK2, 0L);
+ if (retval != 0){
+ printk("cyc:cyz_rx_restart retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ cyz_rx_full_timer[info->line].function = NULL;
+ CY_UNLOCK(info, flags);
+}
+
+#else /* CONFIG_CYZ_INTR */
+
+static void
+cyz_poll(unsigned long arg)
+{
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info;
+ struct tty_struct *tty;
+ static volatile struct FIRM_ID *firm_id;
+ static volatile struct ZFW_CTRL *zfw_ctrl;
+ static volatile struct BOARD_CTRL *board_ctrl;
+ static volatile struct CH_CTRL *ch_ctrl;
+ static volatile struct BUF_CTRL *buf_ctrl;
+ int card, port;
+
+ cyz_timerlist.expires = jiffies + (HZ);
+ for (card = 0 ; card < NR_CARDS ; card++){
+ cinfo = &cy_card[card];
+
+ if (!IS_CYC_Z(*cinfo)) continue;
+ if (!ISZLOADED(*cinfo)) continue;
+
+ firm_id = cinfo->base_addr + ID_ADDRESS;
+ zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &(zfw_ctrl->board_ctrl);
+
+ /* Skip first polling cycle to avoid racing conditions with the FW */
+ if (!cinfo->intr_enabled) {
+ cinfo->nports = (int) cy_readl(&board_ctrl->n_channel);
+ cinfo->intr_enabled = 1;
+ continue;
+ }
+
+ cyz_handle_cmd(cinfo);
+
+ for (port = 0 ; port < cinfo->nports ; port++) {
+ info = &cy_port[ port + cinfo->first_line ];
+ tty = info->tty;
+ ch_ctrl = &(zfw_ctrl->ch_ctrl[port]);
+ buf_ctrl = &(zfw_ctrl->buf_ctrl[port]);
+
+ if (!info->throttle)
+ cyz_handle_rx(info, ch_ctrl, buf_ctrl);
+ cyz_handle_tx(info, ch_ctrl, buf_ctrl);
+ }
+ /* poll every 'cyz_polling_cycle' period */
+ cyz_timerlist.expires = jiffies + cyz_polling_cycle;
+ }
+ add_timer(&cyz_timerlist);
+
+ return;
+} /* cyz_poll */
+
+#endif /* CONFIG_CYZ_INTR */
+
+/********** End of block of Cyclades-Z specific code *********/
+/***********************************************************/
+
+
+/* This is called whenever a port becomes active;
+ interrupts are enabled and DTR & RTS are turned on.
+ */
+static int
+startup(struct cyclades_port * info)
+{
+ unsigned long flags;
+ int retval = 0;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+ unsigned long page;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+
+ CY_LOCK(info, flags);
+
+ if (info->flags & ASYNC_INITIALIZED){
+ free_page(page);
+ goto errout;
+ }
+
+ if (!info->type){
+ if (info->tty){
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ free_page(page);
+ goto errout;
+ }
+
+ if (info->xmit_buf)
+ free_page(page);
+ else
+ info->xmit_buf = (unsigned char *) page;
+
+ CY_UNLOCK(info, flags);
+
+ set_line_char(info);
+
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+#ifdef CY_DEBUG_OPEN
+ printk("cyc startup card %d, chip %d, channel %d, base_addr %lx\n",
+ card, chip, channel, (long)base_addr);/**/
+#endif
+
+ CY_LOCK(info, flags);
+
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+
+ cy_writeb(base_addr+(CyRTPR<<index), (info->default_timeout
+ ? info->default_timeout : 0x02)); /* 10ms rx timeout */
+
+ cyy_issue_cmd(base_addr,CyCHAN_CTL|CyENB_RCVR|CyENB_XMTR,index);
+
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+
+#ifdef CY_DEBUG_DTR
+ printk("cyc:startup raising DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) | CyRxData);
+ info->flags |= ASYNC_INITIALIZED;
+
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ info->breakon = info->breakoff = 0;
+ memset((char *)&info->idle_stats, 0, sizeof(info->idle_stats));
+ info->idle_stats.in_use =
+ info->idle_stats.recv_idle =
+ info->idle_stats.xmit_idle = jiffies;
+
+ CY_UNLOCK(info, flags);
+
+ } else {
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+ int retval;
+
+ base_addr = cy_card[card].base_addr;
+
+ firm_id = base_addr + ID_ADDRESS;
+ if (!ISZLOADED(cy_card[card])){
+ return -ENODEV;
+ }
+
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = zfw_ctrl->ch_ctrl;
+
+#ifdef CY_DEBUG_OPEN
+ printk("cyc startup Z card %d, channel %d, base_addr %lx\n",
+ card, channel, (long)base_addr);/**/
+#endif
+
+ CY_LOCK(info, flags);
+
+ cy_writel(&ch_ctrl[channel].op_mode, C_CH_ENABLE);
+#ifdef Z_WAKE
+#ifdef CONFIG_CYZ_INTR
+ cy_writel(&ch_ctrl[channel].intr_enable,
+ C_IN_TXBEMPTY|C_IN_TXLOWWM|C_IN_RXHIWM|C_IN_RXNNDT|
+ C_IN_IOCTLW|
+ C_IN_MDCD);
+#else
+ cy_writel(&ch_ctrl[channel].intr_enable,
+ C_IN_IOCTLW|
+ C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#else
+#ifdef CONFIG_CYZ_INTR
+ cy_writel(&ch_ctrl[channel].intr_enable,
+ C_IN_TXBEMPTY|C_IN_TXLOWWM|C_IN_RXHIWM|C_IN_RXNNDT|
+ C_IN_MDCD);
+#else
+ cy_writel(&ch_ctrl[channel].intr_enable,
+ C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#endif /* Z_WAKE */
+
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_IOCTL, 0L);
+ if (retval != 0){
+ printk("cyc:startup(1) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+
+ /* Flush RX buffers before raising DTR and RTS */
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_FLUSH_RX, 0L);
+ if (retval != 0){
+ printk("cyc:startup(2) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+
+ /* set timeout !!! */
+ /* set RTS and DTR !!! */
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) | C_RS_RTS | C_RS_DTR) ;
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ channel, C_CM_IOCTLM, 0L);
+ if (retval != 0){
+ printk("cyc:startup(3) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:startup raising Z DTR\n");
+#endif
+
+ /* enable send, recv, modem !!! */
+
+ info->flags |= ASYNC_INITIALIZED;
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ info->breakon = info->breakoff = 0;
+ memset((char *)&info->idle_stats, 0, sizeof(info->idle_stats));
+ info->idle_stats.in_use =
+ info->idle_stats.recv_idle =
+ info->idle_stats.xmit_idle = jiffies;
+
+ CY_UNLOCK(info, flags);
+ }
+
+#ifdef CY_DEBUG_OPEN
+ printk(" cyc startup done\n");
+#endif
+ return 0;
+
+errout:
+ CY_UNLOCK(info, flags);
+ return retval;
+} /* startup */
+
+
+static void
+start_xmit( struct cyclades_port *info )
+{
+ unsigned long flags;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), channel);
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) | CyTxRdy);
+ CY_UNLOCK(info, flags);
+ } else {
+#ifdef CONFIG_CYZ_INTR
+ int retval;
+
+ CY_LOCK(info, flags);
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_INTBACK, 0L);
+ if (retval != 0){
+ printk("cyc:start_xmit retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ CY_UNLOCK(info, flags);
+#else /* CONFIG_CYZ_INTR */
+ /* Don't have to do anything at this time */
+#endif /* CONFIG_CYZ_INTR */
+ }
+} /* start_xmit */
+
+/*
+ * This routine shuts down a serial port; interrupts are disabled,
+ * and DTR is dropped if the hangup on close termio flag is on.
+ */
+static void
+shutdown(struct cyclades_port * info)
+{
+ unsigned long flags;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+
+ if (!(info->flags & ASYNC_INITIALIZED)){
+ return;
+ }
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+#ifdef CY_DEBUG_OPEN
+ printk("cyc shutdown Y card %d, chip %d, channel %d, base_addr %lx\n",
+ card, chip, channel, (long)base_addr);
+#endif
+
+ CY_LOCK(info, flags);
+
+ /* Clear delta_msr_wait queue to avoid mem leaks. */
+ wake_up_interruptible(&info->delta_msr_wait);
+
+ if (info->xmit_buf){
+ unsigned char * temp;
+ temp = info->xmit_buf;
+ info->xmit_buf = NULL;
+ free_page((unsigned long) temp);
+ }
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+ cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+ cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc shutdown dropping DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ }
+ cyy_issue_cmd(base_addr,CyCHAN_CTL|CyDIS_RCVR,index);
+ /* it may be appropriate to clear _XMIT at
+ some later date (after testing)!!! */
+
+ if (info->tty){
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->flags &= ~ASYNC_INITIALIZED;
+ CY_UNLOCK(info, flags);
+ } else {
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+ int retval;
+
+ base_addr = cy_card[card].base_addr;
+#ifdef CY_DEBUG_OPEN
+ printk("cyc shutdown Z card %d, channel %d, base_addr %lx\n",
+ card, channel, (long)base_addr);
+#endif
+
+ firm_id = base_addr + ID_ADDRESS;
+ if (!ISZLOADED(cy_card[card])) {
+ return;
+ }
+
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = zfw_ctrl->ch_ctrl;
+
+ CY_LOCK(info, flags);
+
+ if (info->xmit_buf){
+ unsigned char * temp;
+ temp = info->xmit_buf;
+ info->xmit_buf = NULL;
+ free_page((unsigned long) temp);
+ }
+
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+ cy_writel(&ch_ctrl[channel].rs_control,
+ (uclong)(cy_readl(&ch_ctrl[channel].rs_control) &
+ ~(C_RS_RTS | C_RS_DTR)));
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ channel, C_CM_IOCTLM, 0L);
+ if (retval != 0){
+ printk("cyc:shutdown retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:shutdown dropping Z DTR\n");
+#endif
+ }
+
+ if (info->tty){
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->flags &= ~ASYNC_INITIALIZED;
+
+ CY_UNLOCK(info, flags);
+ }
+
+#ifdef CY_DEBUG_OPEN
+ printk(" cyc shutdown done\n");
+#endif
+ return;
+} /* shutdown */
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_open() and friends
+ * ------------------------------------------------------------
+ */
+
+static int
+block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct cyclades_port *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct cyclades_card *cinfo;
+ unsigned long flags;
+ int chip, channel,index;
+ int retval;
+ void __iomem *base_addr;
+
+ cinfo = &cy_card[info->card];
+ channel = info->line - cinfo->first_line;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) {
+ if (info->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&info->close_wait);
+ }
+ return ((info->flags & ASYNC_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+ }
+
+ /*
+ * If non-blocking mode is set, then make the check up front
+ * and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * cy_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef CY_DEBUG_OPEN
+ printk("cyc block_til_ready before block: ttyC%d, count = %d\n",
+ info->line, info->count);/**/
+#endif
+ CY_LOCK(info, flags);
+ if (!tty_hung_up_p(filp))
+ info->count--;
+ CY_UNLOCK(info, flags);
+#ifdef CY_DEBUG_COUNT
+ printk("cyc block_til_ready: (%d): decrementing count to %d\n",
+ current->pid, info->count);
+#endif
+ info->blocked_open++;
+
+ if (!IS_CYC_Z(*cinfo)) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cinfo->bus_index;
+ base_addr = cinfo->base_addr + (cy_chip_offset[chip]<<index);
+
+ while (1) {
+ CY_LOCK(info, flags);
+ if ((tty->termios->c_cflag & CBAUD)){
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc:block_til_ready raising DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ }
+ CY_UNLOCK(info, flags);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp)
+ || !(info->flags & ASYNC_INITIALIZED) ){
+ retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ break;
+ }
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (!(info->flags & ASYNC_CLOSING)
+ && (C_CLOCAL(tty)
+ || (cy_readb(base_addr+(CyMSVR1<<index)) & CyDCD))) {
+ CY_UNLOCK(info, flags);
+ break;
+ }
+ CY_UNLOCK(info, flags);
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef CY_DEBUG_OPEN
+ printk("cyc block_til_ready blocking: ttyC%d, count = %d\n",
+ info->line, info->count);/**/
+#endif
+ schedule();
+ }
+ } else {
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+ int retval;
+
+ base_addr = cinfo->base_addr;
+ firm_id = base_addr + ID_ADDRESS;
+ if (!ISZLOADED(*cinfo)){
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+ return -EINVAL;
+ }
+
+ zfw_ctrl = base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = zfw_ctrl->ch_ctrl;
+
+ while (1) {
+ if ((tty->termios->c_cflag & CBAUD)){
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) |
+ (C_RS_RTS | C_RS_DTR));
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ channel, C_CM_IOCTLM, 0L);
+ if (retval != 0){
+ printk("cyc:block_til_ready retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:block_til_ready raising Z DTR\n");
+#endif
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp)
+ || !(info->flags & ASYNC_INITIALIZED) ){
+ retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ break;
+ }
+ if (!(info->flags & ASYNC_CLOSING)
+ && (C_CLOCAL(tty)
+ || (cy_readl(&ch_ctrl[channel].rs_status) & C_RS_DCD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef CY_DEBUG_OPEN
+ printk("cyc block_til_ready blocking: ttyC%d, count = %d\n",
+ info->line, info->count);/**/
+#endif
+ schedule();
+ }
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!tty_hung_up_p(filp)){
+ info->count++;
+#ifdef CY_DEBUG_COUNT
+ printk("cyc:block_til_ready (%d): incrementing count to %d\n",
+ current->pid, info->count);
+#endif
+ }
+ info->blocked_open--;
+#ifdef CY_DEBUG_OPEN
+ printk("cyc:block_til_ready after blocking: ttyC%d, count = %d\n",
+ info->line, info->count);/**/
+#endif
+ if (retval)
+ return retval;
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+} /* block_til_ready */
+
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * performs the serial-specific initialization for the tty structure.
+ */
+static int
+cy_open(struct tty_struct *tty, struct file * filp)
+{
+ struct cyclades_port *info;
+ int retval, line;
+ unsigned long page;
+
+ line = tty->index;
+ if ((line < 0) || (NR_PORTS <= line)){
+ return -ENODEV;
+ }
+ info = &cy_port[line];
+ if (info->line < 0){
+ return -ENODEV;
+ }
+
+ /* If the card's firmware hasn't been loaded,
+ treat it as absent from the system. This
+ will make the user pay attention.
+ */
+ if (IS_CYC_Z(cy_card[info->card])) {
+ struct cyclades_card *cinfo = &cy_card[info->card];
+ struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS;
+
+ if (!ISZLOADED(*cinfo)) {
+ if (((ZE_V1 ==cy_readl(&((struct RUNTIME_9060 __iomem *)
+ (cinfo->ctl_addr))->mail_box_0)) &&
+ Z_FPGA_CHECK (*cinfo)) &&
+ (ZFIRM_HLT == cy_readl (&firm_id->signature)))
+ {
+ printk ("cyc:Cyclades-Z Error: you need an external power supply for this number of ports.\n\rFirmware halted.\r\n");
+ } else {
+ printk("cyc:Cyclades-Z firmware not yet loaded\n");
+ }
+ return -ENODEV;
+ }
+#ifdef CONFIG_CYZ_INTR
+ else {
+ /* In case this Z board is operating in interrupt mode, its
+ interrupts should be enabled as soon as the first open happens
+ to one of its ports. */
+ if (!cinfo->intr_enabled) {
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+
+ zfw_ctrl = cinfo->base_addr + (cy_readl (&firm_id->zfwctrl_addr) & 0xfffff);
+
+ board_ctrl = &zfw_ctrl->board_ctrl;
+
+ /* Enable interrupts on the PLX chip */
+ cy_writew(cinfo->ctl_addr+0x68,
+ cy_readw(cinfo->ctl_addr+0x68)|0x0900);
+ /* Enable interrupts on the FW */
+ retval = cyz_issue_cmd(cinfo,
+ 0, C_CM_IRQ_ENBL, 0L);
+ if (retval != 0){
+ printk("cyc:IRQ enable retval was %x\n", retval);
+ }
+ cinfo->nports = (int) cy_readl (&board_ctrl->n_channel);
+ cinfo->intr_enabled = 1;
+ }
+ }
+#endif /* CONFIG_CYZ_INTR */
+ /* Make sure this Z port really exists in hardware */
+ if (info->line > (cinfo->first_line + cinfo->nports - 1))
+ return -ENODEV;
+ }
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_open ttyC%d\n", info->line); /* */
+#endif
+ tty->driver_data = info;
+ info->tty = tty;
+ if (serial_paranoia_check(info, tty->name, "cy_open")){
+ return -ENODEV;
+ }
+#ifdef CY_DEBUG_OPEN
+ printk("cyc:cy_open ttyC%d, count = %d\n",
+ info->line, info->count);/**/
+#endif
+ info->count++;
+#ifdef CY_DEBUG_COUNT
+ printk("cyc:cy_open (%d): incrementing count to %d\n",
+ current->pid, info->count);
+#endif
+ if (!tmp_buf) {
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+ if (tmp_buf)
+ free_page(page);
+ else
+ tmp_buf = (unsigned char *) page;
+ }
+
+ /*
+ * If the port is the middle of closing, bail out now
+ */
+ if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) {
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+ return ((info->flags & ASYNC_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+ }
+
+ /*
+ * Start up serial port
+ */
+ retval = startup(info);
+ if (retval){
+ return retval;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef CY_DEBUG_OPEN
+ printk("cyc:cy_open returning after block_til_ready with %d\n",
+ retval);
+#endif
+ return retval;
+ }
+
+ info->throttle = 0;
+
+#ifdef CY_DEBUG_OPEN
+ printk(" cyc:cy_open done\n");/**/
+#endif
+
+ return 0;
+} /* cy_open */
+
+
+/*
+ * cy_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void
+cy_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+ unsigned long orig_jiffies;
+ int char_time;
+
+ if (serial_paranoia_check(info, tty->name, "cy_wait_until_sent"))
+ return;
+
+ if (info->xmit_fifo_size == 0)
+ return; /* Just in case.... */
+
+
+ orig_jiffies = jiffies;
+ /*
+ * Set the check interval to be 1/5 of the estimated time to
+ * send a single character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ *
+ * Note: we have to use pretty tight timings here to satisfy
+ * the NIST-PCTS.
+ */
+ char_time = (info->timeout - HZ/50) / info->xmit_fifo_size;
+ char_time = char_time / 5;
+ if (char_time <= 0)
+ char_time = 1;
+ if (timeout < 0)
+ timeout = 0;
+ if (timeout)
+ char_time = min(char_time, timeout);
+ /*
+ * If the transmitter hasn't cleared in twice the approximate
+ * amount of time to send the entire FIFO, it probably won't
+ * ever clear. This assumes the UART isn't doing flow
+ * control, which is currently the case. Hence, if it ever
+ * takes longer than info->timeout, this is probably due to a
+ * UART bug of some kind. So, we clamp the timeout parameter at
+ * 2*info->timeout.
+ */
+ if (!timeout || timeout > 2*info->timeout)
+ timeout = 2*info->timeout;
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+ printk("In cy_wait_until_sent(%d) check=%lu...", timeout, char_time);
+ printk("jiff=%lu...", jiffies);
+#endif
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+ while (cy_readb(base_addr+(CySRER<<index)) & CyTxRdy) {
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+ printk("Not clean (jiff=%lu)...", jiffies);
+#endif
+ if (msleep_interruptible(jiffies_to_msecs(char_time)))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ } else {
+ // Nothing to do!
+ }
+ /* Run one more char cycle */
+ msleep_interruptible(jiffies_to_msecs(char_time * 5));
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+ printk("Clean (jiff=%lu)...done\n", jiffies);
+#endif
+}
+
+/*
+ * This routine is called when a particular tty device is closed.
+ */
+static void
+cy_close(struct tty_struct *tty, struct file *filp)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_close ttyC%d\n", info->line);
+#endif
+
+ if (!info || serial_paranoia_check(info, tty->name, "cy_close")){
+ return;
+ }
+
+ CY_LOCK(info, flags);
+ /* If the TTY is being hung up, nothing to do */
+ if (tty_hung_up_p(filp)) {
+ CY_UNLOCK(info, flags);
+ return;
+ }
+
+#ifdef CY_DEBUG_OPEN
+ printk("cyc:cy_close ttyC%d, count = %d\n", info->line, info->count);
+#endif
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk("cyc:cy_close: bad serial port count; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+#ifdef CY_DEBUG_COUNT
+ printk("cyc:cy_close at (%d): decrementing count to %d\n",
+ current->pid, info->count - 1);
+#endif
+ if (--info->count < 0) {
+#ifdef CY_DEBUG_COUNT
+ printk("cyc:cyc_close setting count to 0\n");
+#endif
+ info->count = 0;
+ }
+ if (info->count) {
+ CY_UNLOCK(info, flags);
+ return;
+ }
+ info->flags |= ASYNC_CLOSING;
+
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ CY_UNLOCK(info, flags);
+ if (info->closing_wait != CY_CLOSING_WAIT_NONE) {
+ tty_wait_until_sent(tty, info->closing_wait);
+ }
+ CY_LOCK(info, flags);
+
+ if (!IS_CYC_Z(cy_card[info->card])) {
+ int channel = info->line - cy_card[info->card].first_line;
+ int index = cy_card[info->card].bus_index;
+ void __iomem *base_addr = cy_card[info->card].base_addr + (cy_chip_offset[channel>>2] << index);
+ /* Stop accepting input */
+ channel &= 0x03;
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) & ~CyRxData);
+ if (info->flags & ASYNC_INITIALIZED) {
+ /* Waiting for on-board buffers to be empty before closing
+ the port */
+ CY_UNLOCK(info, flags);
+ cy_wait_until_sent(tty, info->timeout);
+ CY_LOCK(info, flags);
+ }
+ } else {
+#ifdef Z_WAKE
+ /* Waiting for on-board buffers to be empty before closing the port */
+ void __iomem *base_addr = cy_card[info->card].base_addr;
+ struct FIRM_ID __iomem *firm_id = base_addr + ID_ADDRESS;
+ struct ZFW_CTRL __iomem *zfw_ctrl = base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ struct CH_CTRL __iomem *ch_ctrl = zfw_ctrl->ch_ctrl;
+ int channel = info->line - cy_card[info->card].first_line;
+ int retval;
+
+ if (cy_readl(&ch_ctrl[channel].flow_status) != C_FS_TXIDLE) {
+ retval = cyz_issue_cmd(&cy_card[info->card], channel,
+ C_CM_IOCTLW, 0L);
+ if (retval != 0){
+ printk("cyc:cy_close retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ CY_UNLOCK(info, flags);
+ interruptible_sleep_on(&info->shutdown_wait);
+ CY_LOCK(info, flags);
+ }
+#endif
+ }
+
+ CY_UNLOCK(info, flags);
+ shutdown(info);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ CY_LOCK(info, flags);
+
+ tty->closing = 0;
+ info->event = 0;
+ info->tty = NULL;
+ if (info->blocked_open) {
+ CY_UNLOCK(info, flags);
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ CY_LOCK(info, flags);
+ }
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+
+#ifdef CY_DEBUG_OTHER
+ printk(" cyc:cy_close done\n");
+#endif
+
+ CY_UNLOCK(info, flags);
+ return;
+} /* cy_close */
+
+
+/* This routine gets called when tty_write has put something into
+ * the write_queue. The characters may come from user space or
+ * kernel space.
+ *
+ * This routine will return the number of characters actually
+ * accepted for writing.
+ *
+ * If the port is not already transmitting stuff, start it off by
+ * enabling interrupts. The interrupt service routine will then
+ * ensure that the characters are sent.
+ * If the port is already active, there is no need to kick it.
+ *
+ */
+static int
+cy_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ int c, ret = 0;
+
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_write ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_write")){
+ return 0;
+ }
+
+ if (!tty || !info->xmit_buf || !tmp_buf){
+ return 0;
+ }
+
+ CY_LOCK(info, flags);
+ while (1) {
+ c = min(count, min((int)(SERIAL_XMIT_SIZE - info->xmit_cnt - 1),
+ (int)(SERIAL_XMIT_SIZE - info->xmit_head)));
+
+ if (c <= 0)
+ break;
+
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ info->xmit_cnt += c;
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+ CY_UNLOCK(info, flags);
+
+ info->idle_stats.xmit_bytes += ret;
+ info->idle_stats.xmit_idle = jiffies;
+
+ if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped) {
+ start_xmit(info);
+ }
+ return ret;
+} /* cy_write */
+
+
+/*
+ * This routine is called by the kernel to write a single
+ * character to the tty device. If the kernel uses this routine,
+ * it must call the flush_chars() routine (if defined) when it is
+ * done stuffing characters into the driver. If there is no room
+ * in the queue, the character is ignored.
+ */
+static void
+cy_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_put_char ttyC%d\n", info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_put_char"))
+ return;
+
+ if (!tty || !info->xmit_buf)
+ return;
+
+ CY_LOCK(info, flags);
+ if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+ CY_UNLOCK(info, flags);
+ return;
+ }
+
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ info->xmit_cnt++;
+ info->idle_stats.xmit_bytes++;
+ info->idle_stats.xmit_idle = jiffies;
+ CY_UNLOCK(info, flags);
+} /* cy_put_char */
+
+
+/*
+ * This routine is called by the kernel after it has written a
+ * series of characters to the tty device using put_char().
+ */
+static void
+cy_flush_chars(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_flush_chars ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_flush_chars"))
+ return;
+
+ if (info->xmit_cnt <= 0 || tty->stopped
+ || tty->hw_stopped || !info->xmit_buf)
+ return;
+
+ start_xmit(info);
+} /* cy_flush_chars */
+
+
+/*
+ * This routine returns the numbers of characters the tty driver
+ * will accept for queuing to be written. This number is subject
+ * to change as output buffers get emptied, or if the output flow
+ * control is activated.
+ */
+static int
+cy_write_room(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ int ret;
+
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_write_room ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_write_room"))
+ return 0;
+ ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ return ret;
+} /* cy_write_room */
+
+
+static int
+cy_chars_in_buffer(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ int card, channel;
+
+ if (serial_paranoia_check(info, tty->name, "cy_chars_in_buffer"))
+ return 0;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+
+#ifdef Z_EXT_CHARS_IN_BUFFER
+ if (!IS_CYC_Z(cy_card[card])) {
+#endif /* Z_EXT_CHARS_IN_BUFFER */
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_chars_in_buffer ttyC%d %d\n",
+ info->line, info->xmit_cnt); /* */
+#endif
+ return info->xmit_cnt;
+#ifdef Z_EXT_CHARS_IN_BUFFER
+ } else {
+ static volatile struct FIRM_ID *firm_id;
+ static volatile struct ZFW_CTRL *zfw_ctrl;
+ static volatile struct CH_CTRL *ch_ctrl;
+ static volatile struct BUF_CTRL *buf_ctrl;
+ int char_count;
+ volatile uclong tx_put, tx_get, tx_bufsize;
+
+ firm_id = cy_card[card].base_addr + ID_ADDRESS;
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+ buf_ctrl = &(zfw_ctrl->buf_ctrl[channel]);
+
+ tx_get = cy_readl(&buf_ctrl->tx_get);
+ tx_put = cy_readl(&buf_ctrl->tx_put);
+ tx_bufsize = cy_readl(&buf_ctrl->tx_bufsize);
+ if (tx_put >= tx_get)
+ char_count = tx_put - tx_get;
+ else
+ char_count = tx_put - tx_get + tx_bufsize;
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_chars_in_buffer ttyC%d %d\n",
+ info->line, info->xmit_cnt + char_count); /* */
+#endif
+ return (info->xmit_cnt + char_count);
+ }
+#endif /* Z_EXT_CHARS_IN_BUFFER */
+} /* cy_chars_in_buffer */
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static void
+cyy_baud_calc(struct cyclades_port *info, uclong baud)
+{
+ int co, co_val, bpr;
+ uclong cy_clock = ((info->chip_rev >= CD1400_REV_J) ? 60000000 : 25000000);
+
+ if (baud == 0) {
+ info->tbpr = info->tco = info->rbpr = info->rco = 0;
+ return;
+ }
+
+ /* determine which prescaler to use */
+ for (co = 4, co_val = 2048; co; co--, co_val >>= 2) {
+ if (cy_clock / co_val / baud > 63)
+ break;
+ }
+
+ bpr = (cy_clock / co_val * 2 / baud + 1) / 2;
+ if (bpr > 255)
+ bpr = 255;
+
+ info->tbpr = info->rbpr = bpr;
+ info->tco = info->rco = co;
+}
+
+/*
+ * This routine finds or computes the various line characteristics.
+ * It used to be called config_setup
+ */
+static void
+set_line_char(struct cyclades_port * info)
+{
+ unsigned long flags;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+ unsigned cflag, iflag;
+ unsigned short chip_number;
+ int baud, baud_rate = 0;
+ int i;
+
+
+ if (!info->tty || !info->tty->termios){
+ return;
+ }
+ if (info->line == -1){
+ return;
+ }
+ cflag = info->tty->termios->c_cflag;
+ iflag = info->tty->termios->c_iflag;
+
+ /*
+ * Set up the tty->alt_speed kludge
+ */
+ if (info->tty) {
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ info->tty->alt_speed = 460800;
+ }
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ chip_number = channel / 4;
+
+ if (!IS_CYC_Z(cy_card[card])) {
+
+ index = cy_card[card].bus_index;
+
+ /* baud rate */
+ baud = tty_get_baud_rate(info->tty);
+ if ((baud == 38400) &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+ if (info->custom_divisor)
+ baud_rate = info->baud / info->custom_divisor;
+ else
+ baud_rate = info->baud;
+ } else if (baud > CD1400_MAX_SPEED) {
+ baud = CD1400_MAX_SPEED;
+ }
+ /* find the baud index */
+ for (i = 0; i < 20; i++) {
+ if (baud == baud_table[i]) {
+ break;
+ }
+ }
+ if (i == 20) {
+ i = 19; /* CD1400_MAX_SPEED */
+ }
+
+ if ((baud == 38400) &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+ cyy_baud_calc(info, baud_rate);
+ } else {
+ if(info->chip_rev >= CD1400_REV_J) {
+ /* It is a CD1400 rev. J or later */
+ info->tbpr = baud_bpr_60[i]; /* Tx BPR */
+ info->tco = baud_co_60[i]; /* Tx CO */
+ info->rbpr = baud_bpr_60[i]; /* Rx BPR */
+ info->rco = baud_co_60[i]; /* Rx CO */
+ } else {
+ info->tbpr = baud_bpr_25[i]; /* Tx BPR */
+ info->tco = baud_co_25[i]; /* Tx CO */
+ info->rbpr = baud_bpr_25[i]; /* Rx BPR */
+ info->rco = baud_co_25[i]; /* Rx CO */
+ }
+ }
+ if (baud_table[i] == 134) {
+ /* get it right for 134.5 baud */
+ info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+ } else if ((baud == 38400) &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+ info->timeout = (info->xmit_fifo_size*HZ*15/baud_rate) + 2;
+ } else if (baud_table[i]) {
+ info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
+ /* this needs to be propagated into the card info */
+ } else {
+ info->timeout = 0;
+ }
+ /* By tradition (is it a standard?) a baud rate of zero
+ implies the line should be/has been closed. A bit
+ later in this routine such a test is performed. */
+
+ /* byte size and parity */
+ info->cor5 = 0;
+ info->cor4 = 0;
+ info->cor3 = (info->default_threshold
+ ? info->default_threshold
+ : baud_cor3[i]); /* receive threshold */
+ info->cor2 = CyETC;
+ switch(cflag & CSIZE){
+ case CS5:
+ info->cor1 = Cy_5_BITS;
+ break;
+ case CS6:
+ info->cor1 = Cy_6_BITS;
+ break;
+ case CS7:
+ info->cor1 = Cy_7_BITS;
+ break;
+ case CS8:
+ info->cor1 = Cy_8_BITS;
+ break;
+ }
+ if(cflag & CSTOPB){
+ info->cor1 |= Cy_2_STOP;
+ }
+ if (cflag & PARENB){
+ if (cflag & PARODD){
+ info->cor1 |= CyPARITY_O;
+ }else{
+ info->cor1 |= CyPARITY_E;
+ }
+ }else{
+ info->cor1 |= CyPARITY_NONE;
+ }
+
+ /* CTS flow control flag */
+ if (cflag & CRTSCTS){
+ info->flags |= ASYNC_CTS_FLOW;
+ info->cor2 |= CyCtsAE;
+ }else{
+ info->flags &= ~ASYNC_CTS_FLOW;
+ info->cor2 &= ~CyCtsAE;
+ }
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ /***********************************************
+ The hardware option, CyRtsAO, presents RTS when
+ the chip has characters to send. Since most modems
+ use RTS as reverse (inbound) flow control, this
+ option is not used. If inbound flow control is
+ necessary, DTR can be programmed to provide the
+ appropriate signals for use with a non-standard
+ cable. Contact Marcio Saito for details.
+ ***********************************************/
+
+ chip = channel>>2;
+ channel &= 0x03;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+
+ /* tx and rx baud rate */
+
+ cy_writeb(base_addr+(CyTCOR<<index), info->tco);
+ cy_writeb(base_addr+(CyTBPR<<index), info->tbpr);
+ cy_writeb(base_addr+(CyRCOR<<index), info->rco);
+ cy_writeb(base_addr+(CyRBPR<<index), info->rbpr);
+
+ /* set line characteristics according configuration */
+
+ cy_writeb(base_addr+(CySCHR1<<index),
+ START_CHAR(info->tty));
+ cy_writeb(base_addr+(CySCHR2<<index),
+ STOP_CHAR(info->tty));
+ cy_writeb(base_addr+(CyCOR1<<index), info->cor1);
+ cy_writeb(base_addr+(CyCOR2<<index), info->cor2);
+ cy_writeb(base_addr+(CyCOR3<<index), info->cor3);
+ cy_writeb(base_addr+(CyCOR4<<index), info->cor4);
+ cy_writeb(base_addr+(CyCOR5<<index), info->cor5);
+
+ cyy_issue_cmd(base_addr,
+ CyCOR_CHANGE|CyCOR1ch|CyCOR2ch|CyCOR3ch,index);
+
+ cy_writeb(base_addr+(CyCAR<<index),
+ (u_char)channel); /* !!! Is this needed? */
+ cy_writeb(base_addr+(CyRTPR<<index), (info->default_timeout
+ ? info->default_timeout
+ : 0x02)); /* 10ms rx timeout */
+
+ if (C_CLOCAL(info->tty)) {
+ /* without modem intr */
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) | CyMdmCh);
+ /* act on 1->0 modem transitions */
+ if ((cflag & CRTSCTS) && info->rflow) {
+ cy_writeb(base_addr+(CyMCOR1<<index),
+ (CyCTS|rflow_thr[i]));
+ } else {
+ cy_writeb(base_addr+(CyMCOR1<<index), CyCTS);
+ }
+ /* act on 0->1 modem transitions */
+ cy_writeb(base_addr+(CyMCOR2<<index), CyCTS);
+ } else {
+ /* without modem intr */
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) | CyMdmCh);
+ /* act on 1->0 modem transitions */
+ if ((cflag & CRTSCTS) && info->rflow) {
+ cy_writeb(base_addr+(CyMCOR1<<index),
+ (CyDSR|CyCTS|CyRI|CyDCD|rflow_thr[i]));
+ } else {
+ cy_writeb(base_addr+(CyMCOR1<<index),
+ CyDSR|CyCTS|CyRI|CyDCD);
+ }
+ /* act on 0->1 modem transitions */
+ cy_writeb(base_addr+(CyMCOR2<<index),
+ CyDSR|CyCTS|CyRI|CyDCD);
+ }
+
+ if(i == 0){ /* baud rate is zero, turn off line */
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+ } else {
+ cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_line_char dropping DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ }else{
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ } else {
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_line_char raising DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ }
+
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ CY_UNLOCK(info, flags);
+
+ } else {
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+ struct BUF_CTRL __iomem *buf_ctrl;
+ uclong sw_flow;
+ int retval;
+
+ firm_id = cy_card[card].base_addr + ID_ADDRESS;
+ if (!ISZLOADED(cy_card[card])) {
+ return;
+ }
+
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+ buf_ctrl = &zfw_ctrl->buf_ctrl[channel];
+
+ /* baud rate */
+ baud = tty_get_baud_rate(info->tty);
+ if ((baud == 38400) &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+ if (info->custom_divisor)
+ baud_rate = info->baud / info->custom_divisor;
+ else
+ baud_rate = info->baud;
+ } else if (baud > CYZ_MAX_SPEED) {
+ baud = CYZ_MAX_SPEED;
+ }
+ cy_writel(&ch_ctrl->comm_baud , baud);
+
+ if (baud == 134) {
+ /* get it right for 134.5 baud */
+ info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+ } else if ((baud == 38400) &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+ info->timeout = (info->xmit_fifo_size*HZ*15/baud_rate) + 2;
+ } else if (baud) {
+ info->timeout = (info->xmit_fifo_size*HZ*15/baud) + 2;
+ /* this needs to be propagated into the card info */
+ } else {
+ info->timeout = 0;
+ }
+
+ /* byte size and parity */
+ switch(cflag & CSIZE){
+ case CS5: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS5); break;
+ case CS6: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS6); break;
+ case CS7: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS7); break;
+ case CS8: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS8); break;
+ }
+ if(cflag & CSTOPB){
+ cy_writel(&ch_ctrl->comm_data_l,
+ cy_readl(&ch_ctrl->comm_data_l) | C_DL_2STOP);
+ }else{
+ cy_writel(&ch_ctrl->comm_data_l,
+ cy_readl(&ch_ctrl->comm_data_l) | C_DL_1STOP);
+ }
+ if (cflag & PARENB){
+ if (cflag & PARODD){
+ cy_writel(&ch_ctrl->comm_parity , C_PR_ODD);
+ }else{
+ cy_writel(&ch_ctrl->comm_parity , C_PR_EVEN);
+ }
+ }else{
+ cy_writel(&ch_ctrl->comm_parity , C_PR_NONE);
+ }
+
+ /* CTS flow control flag */
+ if (cflag & CRTSCTS){
+ cy_writel(&ch_ctrl->hw_flow,
+ cy_readl(&ch_ctrl->hw_flow) | C_RS_CTS | C_RS_RTS);
+ }else{
+ cy_writel(&ch_ctrl->hw_flow,
+ cy_readl(&ch_ctrl->hw_flow) & ~(C_RS_CTS | C_RS_RTS));
+ }
+ /* As the HW flow control is done in firmware, the driver doesn't
+ need to care about it */
+ info->flags &= ~ASYNC_CTS_FLOW;
+
+ /* XON/XOFF/XANY flow control flags */
+ sw_flow = 0;
+ if (iflag & IXON){
+ sw_flow |= C_FL_OXX;
+ if (iflag & IXANY)
+ sw_flow |= C_FL_OIXANY;
+ }
+ cy_writel(&ch_ctrl->sw_flow, sw_flow);
+
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_IOCTL, 0L);
+ if (retval != 0){
+ printk("cyc:set_line_char retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+
+ /* CD sensitivity */
+ if (cflag & CLOCAL){
+ info->flags &= ~ASYNC_CHECK_CD;
+ }else{
+ info->flags |= ASYNC_CHECK_CD;
+ }
+
+ if(baud == 0){ /* baud rate is zero, turn off line */
+ cy_writel(&ch_ctrl->rs_control,
+ cy_readl(&ch_ctrl->rs_control) & ~C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_line_char dropping Z DTR\n");
+#endif
+ }else{
+ cy_writel(&ch_ctrl->rs_control,
+ cy_readl(&ch_ctrl->rs_control) | C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_line_char raising Z DTR\n");
+#endif
+ }
+
+ retval = cyz_issue_cmd( &cy_card[card], channel, C_CM_IOCTLM, 0L);
+ if (retval != 0){
+ printk("cyc:set_line_char(2) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ }
+} /* set_line_char */
+
+
+static int
+get_serial_info(struct cyclades_port * info,
+ struct serial_struct __user * retinfo)
+{
+ struct serial_struct tmp;
+ struct cyclades_card *cinfo = &cy_card[info->card];
+
+ if (!retinfo)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = info->type;
+ tmp.line = info->line;
+ tmp.port = info->card * 0x100 + info->line - cinfo->first_line;
+ tmp.irq = cinfo->irq;
+ tmp.flags = info->flags;
+ tmp.close_delay = info->close_delay;
+ tmp.baud_base = info->baud;
+ tmp.custom_divisor = info->custom_divisor;
+ tmp.hub6 = 0; /*!!!*/
+ return copy_to_user(retinfo,&tmp,sizeof(*retinfo))?-EFAULT:0;
+} /* get_serial_info */
+
+
+static int
+set_serial_info(struct cyclades_port * info,
+ struct serial_struct __user * new_info)
+{
+ struct serial_struct new_serial;
+ struct cyclades_port old_info;
+
+ if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+ return -EFAULT;
+ old_info = *info;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((new_serial.close_delay != info->close_delay) ||
+ (new_serial.baud_base != info->baud) ||
+ ((new_serial.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK) !=
+ (info->flags & ASYNC_FLAGS & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ info->baud = new_serial.baud_base;
+ info->custom_divisor = new_serial.custom_divisor;
+ goto check_and_exit;
+ }
+
+
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+
+ info->baud = new_serial.baud_base;
+ info->custom_divisor = new_serial.custom_divisor;
+ info->flags = ((info->flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ info->close_delay = new_serial.close_delay * HZ/100;
+ info->closing_wait = new_serial.closing_wait * HZ/100;
+
+check_and_exit:
+ if (info->flags & ASYNC_INITIALIZED){
+ set_line_char(info);
+ return 0;
+ }else{
+ return startup(info);
+ }
+} /* set_serial_info */
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int get_lsr_info(struct cyclades_port *info, unsigned int __user *value)
+{
+ int card, chip, channel, index;
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+ void __iomem *base_addr;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ status = cy_readb(base_addr+(CySRER<<index)) & (CyTxRdy|CyTxMpty);
+ CY_UNLOCK(info, flags);
+ result = (status ? 0 : TIOCSER_TEMT);
+ } else {
+ /* Not supported yet */
+ return -EINVAL;
+ }
+ return put_user(result, (unsigned long __user *) value);
+}
+
+static int
+cy_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ int card,chip,channel,index;
+ void __iomem *base_addr;
+ unsigned long flags;
+ unsigned char status;
+ unsigned long lstatus;
+ unsigned int result;
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+
+ if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ status = cy_readb(base_addr+(CyMSVR1<<index));
+ status |= cy_readb(base_addr+(CyMSVR2<<index));
+ CY_UNLOCK(info, flags);
+
+ if (info->rtsdtr_inv) {
+ result = ((status & CyRTS) ? TIOCM_DTR : 0)
+ | ((status & CyDTR) ? TIOCM_RTS : 0);
+ } else {
+ result = ((status & CyRTS) ? TIOCM_RTS : 0)
+ | ((status & CyDTR) ? TIOCM_DTR : 0);
+ }
+ result |= ((status & CyDCD) ? TIOCM_CAR : 0)
+ | ((status & CyRI) ? TIOCM_RNG : 0)
+ | ((status & CyDSR) ? TIOCM_DSR : 0)
+ | ((status & CyCTS) ? TIOCM_CTS : 0);
+ } else {
+ base_addr = cy_card[card].base_addr;
+
+ if (cy_card[card].num_chips != -1){
+ return -EINVAL;
+ }
+
+ firm_id = cy_card[card].base_addr + ID_ADDRESS;
+ if (ISZLOADED(cy_card[card])) {
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = zfw_ctrl->ch_ctrl;
+ lstatus = cy_readl(&ch_ctrl[channel].rs_status);
+ result = ((lstatus & C_RS_RTS) ? TIOCM_RTS : 0)
+ | ((lstatus & C_RS_DTR) ? TIOCM_DTR : 0)
+ | ((lstatus & C_RS_DCD) ? TIOCM_CAR : 0)
+ | ((lstatus & C_RS_RI) ? TIOCM_RNG : 0)
+ | ((lstatus & C_RS_DSR) ? TIOCM_DSR : 0)
+ | ((lstatus & C_RS_CTS) ? TIOCM_CTS : 0);
+ }else{
+ result = 0;
+ return -ENODEV;
+ }
+
+ }
+ return result;
+} /* cy_tiomget */
+
+
+static int
+cy_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ int card,chip,channel,index;
+ void __iomem *base_addr;
+ unsigned long flags;
+ struct FIRM_ID __iomem *firm_id;
+ struct ZFW_CTRL __iomem *zfw_ctrl;
+ struct BOARD_CTRL __iomem *board_ctrl;
+ struct CH_CTRL __iomem *ch_ctrl;
+ int retval;
+
+ if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ if (set & TIOCM_RTS){
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+ } else {
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ }
+ CY_UNLOCK(info, flags);
+ }
+ if (clear & TIOCM_RTS) {
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+ } else {
+ cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+ }
+ CY_UNLOCK(info, flags);
+ }
+ if (set & TIOCM_DTR){
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ } else {
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+ }
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_modem_info raising DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ CY_UNLOCK(info, flags);
+ }
+ if (clear & TIOCM_DTR) {
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+ } else {
+ cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+ }
+
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_modem_info dropping DTR\n");
+ printk(" status: 0x%x, 0x%x\n",
+ cy_readb(base_addr+(CyMSVR1<<index)),
+ cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+ CY_UNLOCK(info, flags);
+ }
+ } else {
+ base_addr = cy_card[card].base_addr;
+
+ firm_id = cy_card[card].base_addr + ID_ADDRESS;
+ if (ISZLOADED(cy_card[card])) {
+ zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+ board_ctrl = &zfw_ctrl->board_ctrl;
+ ch_ctrl = zfw_ctrl->ch_ctrl;
+
+ if (set & TIOCM_RTS){
+ CY_LOCK(info, flags);
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) | C_RS_RTS);
+ CY_UNLOCK(info, flags);
+ }
+ if (clear & TIOCM_RTS) {
+ CY_LOCK(info, flags);
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) & ~C_RS_RTS);
+ CY_UNLOCK(info, flags);
+ }
+ if (set & TIOCM_DTR){
+ CY_LOCK(info, flags);
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) | C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_modem_info raising Z DTR\n");
+#endif
+ CY_UNLOCK(info, flags);
+ }
+ if (clear & TIOCM_DTR) {
+ CY_LOCK(info, flags);
+ cy_writel(&ch_ctrl[channel].rs_control,
+ cy_readl(&ch_ctrl[channel].rs_control) & ~C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+ printk("cyc:set_modem_info clearing Z DTR\n");
+#endif
+ CY_UNLOCK(info, flags);
+ }
+ }else{
+ return -ENODEV;
+ }
+ CY_LOCK(info, flags);
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ channel, C_CM_IOCTLM,0L);
+ if (retval != 0){
+ printk("cyc:set_modem_info retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ CY_UNLOCK(info, flags);
+ }
+ return 0;
+} /* cy_tiocmset */
+
+/*
+ * cy_break() --- routine which turns the break handling on or off
+ */
+static void
+cy_break(struct tty_struct *tty, int break_state)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "cy_break"))
+ return;
+
+ CY_LOCK(info, flags);
+ if (!IS_CYC_Z(cy_card[info->card])) {
+ /* Let the transmit ISR take care of this (since it
+ requires stuffing characters into the output stream).
+ */
+ if (break_state == -1) {
+ if (!info->breakon) {
+ info->breakon = 1;
+ if (!info->xmit_cnt) {
+ CY_UNLOCK(info, flags);
+ start_xmit(info);
+ CY_LOCK(info, flags);
+ }
+ }
+ } else {
+ if (!info->breakoff) {
+ info->breakoff = 1;
+ if (!info->xmit_cnt) {
+ CY_UNLOCK(info, flags);
+ start_xmit(info);
+ CY_LOCK(info, flags);
+ }
+ }
+ }
+ } else {
+ int retval;
+
+ if (break_state == -1) {
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ (info->line) - (cy_card[info->card].first_line),
+ C_CM_SET_BREAK, 0L);
+ if (retval != 0) {
+ printk("cyc:cy_break (set) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ } else {
+ retval = cyz_issue_cmd(&cy_card[info->card],
+ (info->line) - (cy_card[info->card].first_line),
+ C_CM_CLR_BREAK, 0L);
+ if (retval != 0) {
+ printk("cyc:cy_break (clr) retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ }
+ }
+ CY_UNLOCK(info, flags);
+} /* cy_break */
+
+static int
+get_mon_info(struct cyclades_port * info, struct cyclades_monitor __user * mon)
+{
+
+ if(copy_to_user(mon, &info->mon, sizeof(struct cyclades_monitor)))
+ return -EFAULT;
+ info->mon.int_count = 0;
+ info->mon.char_count = 0;
+ info->mon.char_max = 0;
+ info->mon.char_last = 0;
+ return 0;
+}/* get_mon_info */
+
+
+static int
+set_threshold(struct cyclades_port * info, unsigned long value)
+{
+ void __iomem *base_addr;
+ int card,channel,chip,index;
+ unsigned long flags;
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ info->cor3 &= ~CyREC_FIFO;
+ info->cor3 |= value & CyREC_FIFO;
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCOR3<<index), info->cor3);
+ cyy_issue_cmd(base_addr,CyCOR_CHANGE|CyCOR3ch,index);
+ CY_UNLOCK(info, flags);
+ } else {
+ // Nothing to do!
+ }
+ return 0;
+}/* set_threshold */
+
+
+static int
+get_threshold(struct cyclades_port * info, unsigned long __user *value)
+{
+ void __iomem *base_addr;
+ int card,channel,chip,index;
+ unsigned long tmp;
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ tmp = cy_readb(base_addr+(CyCOR3<<index)) & CyREC_FIFO;
+ return put_user(tmp,value);
+ } else {
+ // Nothing to do!
+ return 0;
+ }
+}/* get_threshold */
+
+
+static int
+set_default_threshold(struct cyclades_port * info, unsigned long value)
+{
+ info->default_threshold = value & 0x0f;
+ return 0;
+}/* set_default_threshold */
+
+
+static int
+get_default_threshold(struct cyclades_port * info, unsigned long __user *value)
+{
+ return put_user(info->default_threshold,value);
+}/* get_default_threshold */
+
+
+static int
+set_timeout(struct cyclades_port * info, unsigned long value)
+{
+ void __iomem *base_addr;
+ int card,channel,chip,index;
+ unsigned long flags;
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyRTPR<<index), value & 0xff);
+ CY_UNLOCK(info, flags);
+ } else {
+ // Nothing to do!
+ }
+ return 0;
+}/* set_timeout */
+
+
+static int
+get_timeout(struct cyclades_port * info, unsigned long __user *value)
+{
+ void __iomem *base_addr;
+ int card,channel,chip,index;
+ unsigned long tmp;
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ tmp = cy_readb(base_addr+(CyRTPR<<index));
+ return put_user(tmp,value);
+ } else {
+ // Nothing to do!
+ return 0;
+ }
+}/* get_timeout */
+
+
+static int
+set_default_timeout(struct cyclades_port * info, unsigned long value)
+{
+ info->default_timeout = value & 0xff;
+ return 0;
+}/* set_default_timeout */
+
+
+static int
+get_default_timeout(struct cyclades_port * info, unsigned long __user *value)
+{
+ return put_user(info->default_timeout,value);
+}/* get_default_timeout */
+
+/*
+ * This routine allows the tty driver to implement device-
+ * specific ioctl's. If the ioctl number passed in cmd is
+ * not recognized by the driver, it should return ENOIOCTLCMD.
+ */
+static int
+cy_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ struct cyclades_icount cprev, cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser; /* user space */
+ int ret_val = 0;
+ unsigned long flags;
+ void __user *argp = (void __user *)arg;
+
+ if (serial_paranoia_check(info, tty->name, "cy_ioctl"))
+ return -ENODEV;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_ioctl ttyC%d, cmd = %x arg = %lx\n",
+ info->line, cmd, arg); /* */
+#endif
+
+ switch (cmd) {
+ case CYGETMON:
+ ret_val = get_mon_info(info, argp);
+ break;
+ case CYGETTHRESH:
+ ret_val = get_threshold(info, argp);
+ break;
+ case CYSETTHRESH:
+ ret_val = set_threshold(info, arg);
+ break;
+ case CYGETDEFTHRESH:
+ ret_val = get_default_threshold(info, argp);
+ break;
+ case CYSETDEFTHRESH:
+ ret_val = set_default_threshold(info, arg);
+ break;
+ case CYGETTIMEOUT:
+ ret_val = get_timeout(info, argp);
+ break;
+ case CYSETTIMEOUT:
+ ret_val = set_timeout(info, arg);
+ break;
+ case CYGETDEFTIMEOUT:
+ ret_val = get_default_timeout(info, argp);
+ break;
+ case CYSETDEFTIMEOUT:
+ ret_val = set_default_timeout(info, arg);
+ break;
+ case CYSETRFLOW:
+ info->rflow = (int)arg;
+ ret_val = 0;
+ break;
+ case CYGETRFLOW:
+ ret_val = info->rflow;
+ break;
+ case CYSETRTSDTR_INV:
+ info->rtsdtr_inv = (int)arg;
+ ret_val = 0;
+ break;
+ case CYGETRTSDTR_INV:
+ ret_val = info->rtsdtr_inv;
+ break;
+ case CYGETCARDINFO:
+ if (copy_to_user(argp, &cy_card[info->card],
+ sizeof (struct cyclades_card))) {
+ ret_val = -EFAULT;
+ break;
+ }
+ ret_val = 0;
+ break;
+ case CYGETCD1400VER:
+ ret_val = info->chip_rev;
+ break;
+#ifndef CONFIG_CYZ_INTR
+ case CYZSETPOLLCYCLE:
+ cyz_polling_cycle = (arg * HZ) / 1000;
+ ret_val = 0;
+ break;
+ case CYZGETPOLLCYCLE:
+ ret_val = (cyz_polling_cycle * 1000) / HZ;
+ break;
+#endif /* CONFIG_CYZ_INTR */
+ case CYSETWAIT:
+ info->closing_wait = (unsigned short)arg * HZ/100;
+ ret_val = 0;
+ break;
+ case CYGETWAIT:
+ ret_val = info->closing_wait / (HZ/100);
+ break;
+ case TIOCGSERIAL:
+ ret_val = get_serial_info(info, argp);
+ break;
+ case TIOCSSERIAL:
+ ret_val = set_serial_info(info, argp);
+ break;
+ case TIOCSERGETLSR: /* Get line status register */
+ ret_val = get_lsr_info(info, argp);
+ break;
+ /*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+ case TIOCMIWAIT:
+ CY_LOCK(info, flags);
+ /* note the counters on entry */
+ cprev = info->icount;
+ CY_UNLOCK(info, flags);
+ while (1) {
+ interruptible_sleep_on(&info->delta_msr_wait);
+ /* see if a signal did it */
+ if (signal_pending(current)) {
+ return -ERESTARTSYS;
+ }
+
+ CY_LOCK(info, flags);
+ cnow = info->icount; /* atomic copy */
+ CY_UNLOCK(info, flags);
+
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+ return -EIO; /* no change => error */
+ }
+ if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
+ ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+ return 0;
+ }
+ cprev = cnow;
+ }
+ /* NOTREACHED */
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ CY_LOCK(info, flags);
+ cnow = info->icount;
+ CY_UNLOCK(info, flags);
+ p_cuser = argp;
+ ret_val = put_user(cnow.cts, &p_cuser->cts);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.dsr, &p_cuser->dsr);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.rng, &p_cuser->rng);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.dcd, &p_cuser->dcd);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.rx, &p_cuser->rx);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.tx, &p_cuser->tx);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.frame, &p_cuser->frame);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.overrun, &p_cuser->overrun);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.parity, &p_cuser->parity);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.brk, &p_cuser->brk);
+ if (ret_val) return ret_val;
+ ret_val = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
+ if (ret_val) return ret_val;
+ ret_val = 0;
+ break;
+ default:
+ ret_val = -ENOIOCTLCMD;
+ }
+
+#ifdef CY_DEBUG_OTHER
+ printk(" cyc:cy_ioctl done\n");
+#endif
+
+ return ret_val;
+} /* cy_ioctl */
+
+
+/*
+ * This routine allows the tty driver to be notified when
+ * device's termios settings have changed. Note that a
+ * well-designed tty driver should be prepared to accept the case
+ * where old == NULL, and try to do something rational.
+ */
+static void
+cy_set_termios(struct tty_struct *tty, struct termios * old_termios)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_set_termios ttyC%d\n", info->line);
+#endif
+
+ if ((tty->termios->c_cflag == old_termios->c_cflag) &&
+ ((tty->termios->c_iflag & (IXON|IXANY)) ==
+ (old_termios->c_iflag & (IXON|IXANY))))
+ return;
+ set_line_char(info);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ cy_start(tty);
+ }
+#if 0
+ /*
+ * No need to wake up processes in open wait, since they
+ * sample the CLOCAL flag once, and don't recheck it.
+ * XXX It's not clear whether the current behavior is correct
+ * or not. Hence, this may change.....
+ */
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&info->open_wait);
+#endif
+
+ return;
+} /* cy_set_termios */
+
+/* This function is used to send a high-priority XON/XOFF character to
+ the device.
+*/
+static void
+cy_send_xchar (struct tty_struct *tty, char ch)
+{
+ struct cyclades_port *info = (struct cyclades_port *) tty->driver_data;
+ int card, channel;
+
+ if (serial_paranoia_check (info, tty->name, "cy_send_xchar"))
+ return;
+
+ info->x_char = ch;
+
+ if (ch)
+ cy_start (tty);
+
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+
+ if (IS_CYC_Z (cy_card[card])) {
+ if (ch == STOP_CHAR (tty))
+ cyz_issue_cmd (&cy_card[card], channel, C_CM_SENDXOFF, 0L);
+ else if (ch == START_CHAR (tty))
+ cyz_issue_cmd (&cy_card[card], channel, C_CM_SENDXON, 0L);
+ }
+}
+
+/* This routine is called by the upper-layer tty layer to signal
+ that incoming characters should be throttled because the input
+ buffers are close to full.
+ */
+static void
+cy_throttle(struct tty_struct * tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+
+#ifdef CY_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("cyc:throttle %s: %d....ttyC%d\n",
+ tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty), info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_throttle")){
+ return;
+ }
+
+ card = info->card;
+
+ if (I_IXOFF(tty)) {
+ if (!IS_CYC_Z (cy_card[card]))
+ cy_send_xchar (tty, STOP_CHAR (tty));
+ else
+ info->throttle = 1;
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+ } else {
+ cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+ }
+ CY_UNLOCK(info, flags);
+ } else {
+ info->throttle = 1;
+ }
+ }
+
+ return;
+} /* cy_throttle */
+
+
+/*
+ * This routine notifies the tty driver that it should signal
+ * that characters can now be sent to the tty without fear of
+ * overrunning the input buffers of the line disciplines.
+ */
+static void
+cy_unthrottle(struct tty_struct * tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ void __iomem *base_addr;
+ int card,chip,channel,index;
+
+#ifdef CY_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("cyc:unthrottle %s: %d....ttyC%d\n",
+ tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty), info->line);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_unthrottle")){
+ return;
+ }
+
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else
+ cy_send_xchar (tty, START_CHAR (tty));
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ card = info->card;
+ channel = info->line - cy_card[card].first_line;
+ if (!IS_CYC_Z(cy_card[card])) {
+ chip = channel>>2;
+ channel &= 0x03;
+ index = cy_card[card].bus_index;
+ base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+ if (info->rtsdtr_inv) {
+ cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+ } else {
+ cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+ }
+ CY_UNLOCK(info, flags);
+ } else {
+ info->throttle = 0;
+ }
+ }
+
+ return;
+} /* cy_unthrottle */
+
+
+/* cy_start and cy_stop provide software output flow control as a
+ function of XON/XOFF, software CTS, and other such stuff.
+*/
+static void
+cy_stop(struct tty_struct *tty)
+{
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ void __iomem *base_addr;
+ int chip,channel,index;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_stop ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_stop"))
+ return;
+
+ cinfo = &cy_card[info->card];
+ channel = info->line - cinfo->first_line;
+ if (!IS_CYC_Z(*cinfo)) {
+ index = cinfo->bus_index;
+ chip = channel>>2;
+ channel &= 0x03;
+ base_addr = cy_card[info->card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index),
+ (u_char)(channel & 0x0003)); /* index channel */
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+ CY_UNLOCK(info, flags);
+ } else {
+ // Nothing to do!
+ }
+
+ return;
+} /* cy_stop */
+
+
+static void
+cy_start(struct tty_struct *tty)
+{
+ struct cyclades_card *cinfo;
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ void __iomem *base_addr;
+ int chip,channel,index;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_start ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_start"))
+ return;
+
+ cinfo = &cy_card[info->card];
+ channel = info->line - cinfo->first_line;
+ index = cinfo->bus_index;
+ if (!IS_CYC_Z(*cinfo)) {
+ chip = channel>>2;
+ channel &= 0x03;
+ base_addr = cy_card[info->card].base_addr + (cy_chip_offset[chip]<<index);
+
+ CY_LOCK(info, flags);
+ cy_writeb(base_addr+(CyCAR<<index),
+ (u_char)(channel & 0x0003)); /* index channel */
+ cy_writeb(base_addr+(CySRER<<index),
+ cy_readb(base_addr+(CySRER<<index)) | CyTxRdy);
+ CY_UNLOCK(info, flags);
+ } else {
+ // Nothing to do!
+ }
+
+ return;
+} /* cy_start */
+
+
+static void
+cy_flush_buffer(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ int card, channel, retval;
+ unsigned long flags;
+
+#ifdef CY_DEBUG_IO
+ printk("cyc:cy_flush_buffer ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_flush_buffer"))
+ return;
+
+ card = info->card;
+ channel = (info->line) - (cy_card[card].first_line);
+
+ CY_LOCK(info, flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ CY_UNLOCK(info, flags);
+
+ if (IS_CYC_Z(cy_card[card])) { /* If it is a Z card, flush the on-board
+ buffers as well */
+ CY_LOCK(info, flags);
+ retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_FLUSH_TX, 0L);
+ if (retval != 0) {
+ printk("cyc: flush_buffer retval on ttyC%d was %x\n",
+ info->line, retval);
+ }
+ CY_UNLOCK(info, flags);
+ }
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+} /* cy_flush_buffer */
+
+
+/*
+ * cy_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void
+cy_hangup(struct tty_struct *tty)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef CY_DEBUG_OTHER
+ printk("cyc:cy_hangup ttyC%d\n", info->line); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_hangup"))
+ return;
+
+ cy_flush_buffer(tty);
+ shutdown(info);
+ info->event = 0;
+ info->count = 0;
+#ifdef CY_DEBUG_COUNT
+ printk("cyc:cy_hangup (%d): setting count to 0\n", current->pid);
+#endif
+ info->tty = NULL;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ wake_up_interruptible(&info->open_wait);
+} /* cy_hangup */
+
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_init() and friends
+ *
+ * cy_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/* initialize chips on Cyclom-Y card -- return number of valid
+ chips (which is number of ports/4) */
+static unsigned short __init
+cyy_init_card(void __iomem *true_base_addr,int index)
+{
+ unsigned int chip_number;
+ void __iomem *base_addr;
+
+ cy_writeb(true_base_addr+(Cy_HwReset<<index), 0);
+ /* Cy_HwReset is 0x1400 */
+ cy_writeb(true_base_addr+(Cy_ClrIntr<<index), 0);
+ /* Cy_ClrIntr is 0x1800 */
+ udelay(500L);
+
+ for(chip_number=0; chip_number<CyMAX_CHIPS_PER_CARD; chip_number++){
+ base_addr = true_base_addr + (cy_chip_offset[chip_number]<<index);
+ mdelay(1);
+ if(cy_readb(base_addr+(CyCCR<<index)) != 0x00){
+ /*************
+ printk(" chip #%d at %#6lx is never idle (CCR != 0)\n",
+ chip_number, (unsigned long)base_addr);
+ *************/
+ return chip_number;
+ }
+
+ cy_writeb(base_addr+(CyGFRCR<<index), 0);
+ udelay(10L);
+
+ /* The Cyclom-16Y does not decode address bit 9 and therefore
+ cannot distinguish between references to chip 0 and a non-
+ existent chip 4. If the preceding clearing of the supposed
+ chip 4 GFRCR register appears at chip 0, there is no chip 4
+ and this must be a Cyclom-16Y, not a Cyclom-32Ye.
+ */
+ if (chip_number == 4
+ && cy_readb(true_base_addr
+ + (cy_chip_offset[0]<<index)
+ + (CyGFRCR<<index)) == 0){
+ return chip_number;
+ }
+
+ cy_writeb(base_addr+(CyCCR<<index), CyCHIP_RESET);
+ mdelay(1);
+
+ if(cy_readb(base_addr+(CyGFRCR<<index)) == 0x00){
+ /*
+ printk(" chip #%d at %#6lx is not responding ",
+ chip_number, (unsigned long)base_addr);
+ printk("(GFRCR stayed 0)\n",
+ */
+ return chip_number;
+ }
+ if((0xf0 & (cy_readb(base_addr+(CyGFRCR<<index)))) != 0x40){
+ /*
+ printk(" chip #%d at %#6lx is not valid (GFRCR == %#2x)\n",
+ chip_number, (unsigned long)base_addr,
+ base_addr[CyGFRCR<<index]);
+ */
+ return chip_number;
+ }
+ cy_writeb(base_addr+(CyGCR<<index), CyCH0_SERIAL);
+ if (cy_readb(base_addr+(CyGFRCR<<index)) >= CD1400_REV_J){
+ /* It is a CD1400 rev. J or later */
+ /* Impossible to reach 5ms with this chip.
+ Changed to 2ms instead (f = 500 Hz). */
+ cy_writeb(base_addr+(CyPPR<<index), CyCLOCK_60_2MS);
+ } else {
+ /* f = 200 Hz */
+ cy_writeb(base_addr+(CyPPR<<index), CyCLOCK_25_5MS);
+ }
+
+ /*
+ printk(" chip #%d at %#6lx is rev 0x%2x\n",
+ chip_number, (unsigned long)base_addr,
+ cy_readb(base_addr+(CyGFRCR<<index)));
+ */
+ }
+ return chip_number;
+} /* cyy_init_card */
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_detect_isa() - Probe for Cyclom-Y/ISA boards.
+ * sets global variables and return the number of ISA boards found.
+ * ---------------------------------------------------------------------
+ */
+static int __init
+cy_detect_isa(void)
+{
+#ifdef CONFIG_ISA
+ unsigned short cy_isa_irq,nboard;
+ void __iomem *cy_isa_address;
+ unsigned short i,j,cy_isa_nchan;
+#ifdef MODULE
+ int isparam = 0;
+#endif
+
+ nboard = 0;
+
+#ifdef MODULE
+ /* Check for module parameters */
+ for(i = 0 ; i < NR_CARDS; i++) {
+ if (maddr[i] || i) {
+ isparam = 1;
+ cy_isa_addresses[i] = maddr[i];
+ }
+ if (!maddr[i])
+ break;
+ }
+#endif
+
+ /* scan the address table probing for Cyclom-Y/ISA boards */
+ for (i = 0 ; i < NR_ISA_ADDRS ; i++) {
+ unsigned int isa_address = cy_isa_addresses[i];
+ if (isa_address == 0x0000) {
+ return(nboard);
+ }
+
+ /* probe for CD1400... */
+ cy_isa_address = ioremap(isa_address, CyISA_Ywin);
+ cy_isa_nchan = CyPORTS_PER_CHIP *
+ cyy_init_card(cy_isa_address,0);
+ if (cy_isa_nchan == 0) {
+ continue;
+ }
+
+#ifdef MODULE
+ if (isparam && irq[i])
+ cy_isa_irq = irq[i];
+ else
+#endif
+ /* find out the board's irq by probing */
+ cy_isa_irq = detect_isa_irq(cy_isa_address);
+ if (cy_isa_irq == 0) {
+ printk("Cyclom-Y/ISA found at 0x%lx ",
+ (unsigned long) cy_isa_address);
+ printk("but the IRQ could not be detected.\n");
+ continue;
+ }
+
+ if((cy_next_channel+cy_isa_nchan) > NR_PORTS) {
+ printk("Cyclom-Y/ISA found at 0x%lx ",
+ (unsigned long) cy_isa_address);
+ printk("but no more channels are available.\n");
+ printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+ return(nboard);
+ }
+ /* fill the next cy_card structure available */
+ for (j = 0 ; j < NR_CARDS ; j++) {
+ if (cy_card[j].base_addr == 0) break;
+ }
+ if (j == NR_CARDS) { /* no more cy_cards available */
+ printk("Cyclom-Y/ISA found at 0x%lx ",
+ (unsigned long) cy_isa_address);
+ printk("but no more cards can be used .\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ return(nboard);
+ }
+
+ /* allocate IRQ */
+ if(request_irq(cy_isa_irq, cyy_interrupt,
+ SA_INTERRUPT, "Cyclom-Y", &cy_card[j]))
+ {
+ printk("Cyclom-Y/ISA found at 0x%lx ",
+ (unsigned long) cy_isa_address);
+ printk("but could not allocate IRQ#%d.\n",
+ cy_isa_irq);
+ return(nboard);
+ }
+
+ /* set cy_card */
+ cy_card[j].base_addr = cy_isa_address;
+ cy_card[j].ctl_addr = NULL;
+ cy_card[j].irq = (int) cy_isa_irq;
+ cy_card[j].bus_index = 0;
+ cy_card[j].first_line = cy_next_channel;
+ cy_card[j].num_chips = cy_isa_nchan/4;
+ nboard++;
+
+ /* print message */
+ printk("Cyclom-Y/ISA #%d: 0x%lx-0x%lx, IRQ%d, ",
+ j+1, (unsigned long) cy_isa_address,
+ (unsigned long)(cy_isa_address + (CyISA_Ywin - 1)),
+ cy_isa_irq);
+ printk("%d channels starting from port %d.\n",
+ cy_isa_nchan, cy_next_channel);
+ cy_next_channel += cy_isa_nchan;
+ }
+ return(nboard);
+#else
+ return(0);
+#endif /* CONFIG_ISA */
+} /* cy_detect_isa */
+
+static void
+plx_init(void __iomem *addr, uclong initctl)
+{
+ /* Reset PLX */
+ cy_writel(addr + initctl, cy_readl(addr + initctl) | 0x40000000);
+ udelay(100L);
+ cy_writel(addr + initctl, cy_readl(addr + initctl) & ~0x40000000);
+
+ /* Reload Config. Registers from EEPROM */
+ cy_writel(addr + initctl, cy_readl(addr + initctl) | 0x20000000);
+ udelay(100L);
+ cy_writel(addr + initctl, cy_readl(addr + initctl) & ~0x20000000);
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_detect_pci() - Test PCI bus presence and Cyclom-Ye/PCI.
+ * sets global variables and return the number of PCI boards found.
+ * ---------------------------------------------------------------------
+ */
+static int __init
+cy_detect_pci(void)
+{
+#ifdef CONFIG_PCI
+
+ struct pci_dev *pdev = NULL;
+ unsigned char cyy_rev_id;
+ unsigned char cy_pci_irq = 0;
+ uclong cy_pci_phys0, cy_pci_phys2;
+ void __iomem *cy_pci_addr0, *cy_pci_addr2;
+ unsigned short i,j,cy_pci_nchan, plx_ver;
+ unsigned short device_id,dev_index = 0;
+ uclong mailbox;
+ uclong ZeIndex = 0;
+ void __iomem *Ze_addr0[NR_CARDS], *Ze_addr2[NR_CARDS];
+ uclong Ze_phys0[NR_CARDS], Ze_phys2[NR_CARDS];
+ unsigned char Ze_irq[NR_CARDS];
+ struct pci_dev *Ze_pdev[NR_CARDS];
+
+ for (i = 0; i < NR_CARDS; i++) {
+ /* look for a Cyclades card by vendor and device id */
+ while((device_id = cy_pci_dev_id[dev_index]) != 0) {
+ if((pdev = pci_get_device(PCI_VENDOR_ID_CYCLADES,
+ device_id, pdev)) == NULL) {
+ dev_index++; /* try next device id */
+ } else {
+ break; /* found a board */
+ }
+ }
+
+ if (device_id == 0)
+ break;
+
+ if (pci_enable_device(pdev))
+ continue;
+
+ /* read PCI configuration area */
+ cy_pci_irq = pdev->irq;
+ cy_pci_phys0 = pci_resource_start(pdev, 0);
+ cy_pci_phys2 = pci_resource_start(pdev, 2);
+ pci_read_config_byte(pdev, PCI_REVISION_ID, &cyy_rev_id);
+
+ device_id &= ~PCI_DEVICE_ID_MASK;
+
+ if ((device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo)
+ || (device_id == PCI_DEVICE_ID_CYCLOM_Y_Hi)){
+#ifdef CY_PCI_DEBUG
+ printk("Cyclom-Y/PCI (bus=0x0%x, pci_id=0x%x, ",
+ pdev->bus->number, pdev->devfn);
+ printk("rev_id=%d) IRQ%d\n",
+ cyy_rev_id, (int)cy_pci_irq);
+ printk("Cyclom-Y/PCI:found winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+#endif
+
+ if (pci_resource_flags(pdev, 2) & IORESOURCE_IO) {
+ printk(" Warning: PCI I/O bit incorrectly set. "
+ "Ignoring it...\n");
+ pdev->resource[2].flags &= ~IORESOURCE_IO;
+ }
+
+ /* Although we don't use this I/O region, we should
+ request it from the kernel anyway, to avoid problems
+ with other drivers accessing it. */
+ if (pci_request_regions(pdev, "Cyclom-Y") != 0) {
+ printk(KERN_ERR "cyclades: failed to reserve PCI resources\n");
+ continue;
+ }
+
+#if defined(__alpha__)
+ if (device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo) { /* below 1M? */
+ printk("Cyclom-Y/PCI (bus=0x0%x, pci_id=0x%x, ",
+ pdev->bus->number, pdev->devfn);
+ printk("rev_id=%d) IRQ%d\n",
+ cyy_rev_id, (int)cy_pci_irq);
+ printk("Cyclom-Y/PCI:found winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+ printk("Cyclom-Y/PCI not supported for low addresses in "
+ "Alpha systems.\n");
+ i--;
+ continue;
+ }
+#endif
+ cy_pci_addr0 = ioremap(cy_pci_phys0, CyPCI_Yctl);
+ cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Ywin);
+
+#ifdef CY_PCI_DEBUG
+ printk("Cyclom-Y/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+ (u_long)cy_pci_addr2, (u_long)cy_pci_addr0);
+#endif
+ cy_pci_nchan = (unsigned short)(CyPORTS_PER_CHIP *
+ cyy_init_card(cy_pci_addr2, 1));
+ if(cy_pci_nchan == 0) {
+ printk("Cyclom-Y PCI host card with ");
+ printk("no Serial-Modules at 0x%lx.\n",
+ (ulong) cy_pci_phys2);
+ i--;
+ continue;
+ }
+ if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+ printk("Cyclom-Y/PCI found at 0x%lx ",
+ (ulong) cy_pci_phys2);
+ printk("but no channels are available.\n");
+ printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+ /* fill the next cy_card structure available */
+ for (j = 0 ; j < NR_CARDS ; j++) {
+ if (cy_card[j].base_addr == 0) break;
+ }
+ if (j == NR_CARDS) { /* no more cy_cards available */
+ printk("Cyclom-Y/PCI found at 0x%lx ",
+ (ulong) cy_pci_phys2);
+ printk("but no more cards can be used.\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+
+ /* allocate IRQ */
+ if(request_irq(cy_pci_irq, cyy_interrupt,
+ SA_SHIRQ, "Cyclom-Y", &cy_card[j]))
+ {
+ printk("Cyclom-Y/PCI found at 0x%lx ",
+ (ulong) cy_pci_phys2);
+ printk("but could not allocate IRQ%d.\n",
+ cy_pci_irq);
+ return(i);
+ }
+
+ /* set cy_card */
+ cy_card[j].base_phys = (ulong)cy_pci_phys2;
+ cy_card[j].ctl_phys = (ulong)cy_pci_phys0;
+ cy_card[j].base_addr = cy_pci_addr2;
+ cy_card[j].ctl_addr = cy_pci_addr0;
+ cy_card[j].irq = (int) cy_pci_irq;
+ cy_card[j].bus_index = 1;
+ cy_card[j].first_line = cy_next_channel;
+ cy_card[j].num_chips = cy_pci_nchan/4;
+ cy_card[j].pdev = pdev;
+
+ /* enable interrupts in the PCI interface */
+ plx_ver = cy_readb(cy_pci_addr2 + CyPLX_VER) & 0x0f;
+ switch (plx_ver) {
+ case PLX_9050:
+
+ cy_writeb(cy_pci_addr0+0x4c, 0x43);
+ break;
+
+ case PLX_9060:
+ case PLX_9080:
+ default: /* Old boards, use PLX_9060 */
+
+ plx_init(cy_pci_addr0, 0x6c);
+ /* For some yet unknown reason, once the PLX9060 reloads
+ the EEPROM, the IRQ is lost and, thus, we have to
+ re-write it to the PCI config. registers.
+ This will remain here until we find a permanent fix. */
+ pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, cy_pci_irq);
+
+ cy_writew(cy_pci_addr0+0x68,
+ cy_readw(cy_pci_addr0+0x68)|0x0900);
+ break;
+ }
+
+ /* print message */
+ printk("Cyclom-Y/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+ j+1,
+ (ulong)cy_pci_phys2,
+ (ulong)(cy_pci_phys2 + CyPCI_Ywin - 1),
+ (int)cy_pci_irq);
+ printk("%d channels starting from port %d.\n",
+ cy_pci_nchan, cy_next_channel);
+
+ cy_next_channel += cy_pci_nchan;
+ }else if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Lo){
+ /* print message */
+ printk("Cyclades-Z/PCI (bus=0x0%x, pci_id=0x%x, ",
+ pdev->bus->number, pdev->devfn);
+ printk("rev_id=%d) IRQ%d\n",
+ cyy_rev_id, (int)cy_pci_irq);
+ printk("Cyclades-Z/PCI: found winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+ printk("Cyclades-Z/PCI not supported for low addresses\n");
+ break;
+ }else if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Hi){
+#ifdef CY_PCI_DEBUG
+ printk("Cyclades-Z/PCI (bus=0x0%x, pci_id=0x%x, ",
+ pdev->bus->number, pdev->devfn);
+ printk("rev_id=%d) IRQ%d\n",
+ cyy_rev_id, (int)cy_pci_irq);
+ printk("Cyclades-Z/PCI: found winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+#endif
+ cy_pci_addr0 = ioremap(cy_pci_phys0, CyPCI_Zctl);
+
+ /* Disable interrupts on the PLX before resetting it */
+ cy_writew(cy_pci_addr0+0x68,
+ cy_readw(cy_pci_addr0+0x68) & ~0x0900);
+
+ plx_init(cy_pci_addr0, 0x6c);
+ /* For some yet unknown reason, once the PLX9060 reloads
+ the EEPROM, the IRQ is lost and, thus, we have to
+ re-write it to the PCI config. registers.
+ This will remain here until we find a permanent fix. */
+ pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, cy_pci_irq);
+
+ mailbox = (uclong)cy_readl(&((struct RUNTIME_9060 __iomem *)
+ cy_pci_addr0)->mail_box_0);
+
+ if (pci_resource_flags(pdev, 2) & IORESOURCE_IO) {
+ printk(" Warning: PCI I/O bit incorrectly set. "
+ "Ignoring it...\n");
+ pdev->resource[2].flags &= ~IORESOURCE_IO;
+ }
+
+ /* Although we don't use this I/O region, we should
+ request it from the kernel anyway, to avoid problems
+ with other drivers accessing it. */
+ if (pci_request_regions(pdev, "Cyclades-Z") != 0) {
+ printk(KERN_ERR "cyclades: failed to reserve PCI resources\n");
+ continue;
+ }
+
+ if (mailbox == ZE_V1) {
+ cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Ze_win);
+ if (ZeIndex == NR_CARDS) {
+ printk("Cyclades-Ze/PCI found at 0x%lx ",
+ (ulong)cy_pci_phys2);
+ printk("but no more cards can be used.\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ } else {
+ Ze_phys0[ZeIndex] = cy_pci_phys0;
+ Ze_phys2[ZeIndex] = cy_pci_phys2;
+ Ze_addr0[ZeIndex] = cy_pci_addr0;
+ Ze_addr2[ZeIndex] = cy_pci_addr2;
+ Ze_irq[ZeIndex] = cy_pci_irq;
+ Ze_pdev[ZeIndex] = pdev;
+ ZeIndex++;
+ }
+ i--;
+ continue;
+ } else {
+ cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Zwin);
+ }
+
+#ifdef CY_PCI_DEBUG
+ printk("Cyclades-Z/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_addr2, (ulong)cy_pci_addr0);
+ if (mailbox == ZO_V1) {
+ cy_writel(&((struct RUNTIME_9060 *)
+ (cy_pci_addr0))->loc_addr_base, WIN_CREG);
+ PAUSE
+ printk("Cyclades-8Zo/PCI: FPGA id %lx, ver %lx\n",
+ (ulong)(0xff & cy_readl(&((struct CUSTOM_REG *)
+ (cy_pci_addr2))->fpga_id)),
+ (ulong)(0xff & cy_readl(&((struct CUSTOM_REG *)
+ (cy_pci_addr2))->fpga_version)));
+ cy_writel(&((struct RUNTIME_9060 *)
+ (cy_pci_addr0))->loc_addr_base, WIN_RAM);
+ } else {
+ printk("Cyclades-Z/PCI: New Cyclades-Z board. FPGA not loaded\n");
+ }
+#endif
+ /* The following clears the firmware id word. This ensures
+ that the driver will not attempt to talk to the board
+ until it has been properly initialized.
+ */
+ PAUSE
+ if ((mailbox == ZO_V1) || (mailbox == ZO_V2))
+ cy_writel(cy_pci_addr2 + ID_ADDRESS, 0L);
+
+ /* This must be a Cyclades-8Zo/PCI. The extendable
+ version will have a different device_id and will
+ be allocated its maximum number of ports. */
+ cy_pci_nchan = 8;
+
+ if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+ printk("Cyclades-8Zo/PCI found at 0x%lx ",
+ (ulong)cy_pci_phys2);
+ printk("but no channels are available.\n");
+ printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+
+ /* fill the next cy_card structure available */
+ for (j = 0 ; j < NR_CARDS ; j++) {
+ if (cy_card[j].base_addr == 0) break;
+ }
+ if (j == NR_CARDS) { /* no more cy_cards available */
+ printk("Cyclades-8Zo/PCI found at 0x%lx ",
+ (ulong)cy_pci_phys2);
+ printk("but no more cards can be used.\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+
+#ifdef CONFIG_CYZ_INTR
+ /* allocate IRQ only if board has an IRQ */
+ if( (cy_pci_irq != 0) && (cy_pci_irq != 255) ) {
+ if(request_irq(cy_pci_irq, cyz_interrupt,
+ SA_SHIRQ, "Cyclades-Z", &cy_card[j]))
+ {
+ printk("Cyclom-8Zo/PCI found at 0x%lx ",
+ (ulong) cy_pci_phys2);
+ printk("but could not allocate IRQ%d.\n",
+ cy_pci_irq);
+ return(i);
+ }
+ }
+#endif /* CONFIG_CYZ_INTR */
+
+
+ /* set cy_card */
+ cy_card[j].base_phys = cy_pci_phys2;
+ cy_card[j].ctl_phys = cy_pci_phys0;
+ cy_card[j].base_addr = cy_pci_addr2;
+ cy_card[j].ctl_addr = cy_pci_addr0;
+ cy_card[j].irq = (int) cy_pci_irq;
+ cy_card[j].bus_index = 1;
+ cy_card[j].first_line = cy_next_channel;
+ cy_card[j].num_chips = -1;
+ cy_card[j].pdev = pdev;
+
+ /* print message */
+#ifdef CONFIG_CYZ_INTR
+ /* don't report IRQ if board is no IRQ */
+ if( (cy_pci_irq != 0) && (cy_pci_irq != 255) )
+ printk("Cyclades-8Zo/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+ j+1,(ulong)cy_pci_phys2,
+ (ulong)(cy_pci_phys2 + CyPCI_Zwin - 1),
+ (int)cy_pci_irq);
+ else
+#endif /* CONFIG_CYZ_INTR */
+ printk("Cyclades-8Zo/PCI #%d: 0x%lx-0x%lx, ",
+ j+1,(ulong)cy_pci_phys2,
+ (ulong)(cy_pci_phys2 + CyPCI_Zwin - 1));
+
+ printk("%d channels starting from port %d.\n",
+ cy_pci_nchan,cy_next_channel);
+ cy_next_channel += cy_pci_nchan;
+ }
+ }
+
+ for (; ZeIndex != 0 && i < NR_CARDS; i++) {
+ cy_pci_phys0 = Ze_phys0[0];
+ cy_pci_phys2 = Ze_phys2[0];
+ cy_pci_addr0 = Ze_addr0[0];
+ cy_pci_addr2 = Ze_addr2[0];
+ cy_pci_irq = Ze_irq[0];
+ pdev = Ze_pdev[0];
+ for (j = 0 ; j < ZeIndex-1 ; j++) {
+ Ze_phys0[j] = Ze_phys0[j+1];
+ Ze_phys2[j] = Ze_phys2[j+1];
+ Ze_addr0[j] = Ze_addr0[j+1];
+ Ze_addr2[j] = Ze_addr2[j+1];
+ Ze_irq[j] = Ze_irq[j+1];
+ Ze_pdev[j] = Ze_pdev[j+1];
+ }
+ ZeIndex--;
+ mailbox = (uclong)cy_readl(&((struct RUNTIME_9060 __iomem *)
+ cy_pci_addr0)->mail_box_0);
+#ifdef CY_PCI_DEBUG
+ printk("Cyclades-Z/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+ (ulong)cy_pci_addr2, (ulong)cy_pci_addr0);
+ printk("Cyclades-Z/PCI: New Cyclades-Z board. FPGA not loaded\n");
+#endif
+ PAUSE
+ /* This must be the new Cyclades-Ze/PCI. */
+ cy_pci_nchan = ZE_V1_NPORTS;
+
+ if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+ printk("Cyclades-Ze/PCI found at 0x%lx ",
+ (ulong)cy_pci_phys2);
+ printk("but no channels are available.\n");
+ printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+
+ /* fill the next cy_card structure available */
+ for (j = 0 ; j < NR_CARDS ; j++) {
+ if (cy_card[j].base_addr == 0) break;
+ }
+ if (j == NR_CARDS) { /* no more cy_cards available */
+ printk("Cyclades-Ze/PCI found at 0x%lx ",
+ (ulong)cy_pci_phys2);
+ printk("but no more cards can be used.\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ return(i);
+ }
+
+#ifdef CONFIG_CYZ_INTR
+ /* allocate IRQ only if board has an IRQ */
+ if( (cy_pci_irq != 0) && (cy_pci_irq != 255) ) {
+ if(request_irq(cy_pci_irq, cyz_interrupt,
+ SA_SHIRQ, "Cyclades-Z", &cy_card[j]))
+ {
+ printk("Cyclom-Ze/PCI found at 0x%lx ",
+ (ulong) cy_pci_phys2);
+ printk("but could not allocate IRQ%d.\n",
+ cy_pci_irq);
+ return(i);
+ }
+ }
+#endif /* CONFIG_CYZ_INTR */
+
+ /* set cy_card */
+ cy_card[j].base_phys = cy_pci_phys2;
+ cy_card[j].ctl_phys = cy_pci_phys0;
+ cy_card[j].base_addr = cy_pci_addr2;
+ cy_card[j].ctl_addr = cy_pci_addr0;
+ cy_card[j].irq = (int) cy_pci_irq;
+ cy_card[j].bus_index = 1;
+ cy_card[j].first_line = cy_next_channel;
+ cy_card[j].num_chips = -1;
+ cy_card[j].pdev = pdev;
+
+ /* print message */
+#ifdef CONFIG_CYZ_INTR
+ /* don't report IRQ if board is no IRQ */
+ if( (cy_pci_irq != 0) && (cy_pci_irq != 255) )
+ printk("Cyclades-Ze/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+ j+1,(ulong)cy_pci_phys2,
+ (ulong)(cy_pci_phys2 + CyPCI_Ze_win - 1),
+ (int)cy_pci_irq);
+ else
+#endif /* CONFIG_CYZ_INTR */
+ printk("Cyclades-Ze/PCI #%d: 0x%lx-0x%lx, ",
+ j+1,(ulong)cy_pci_phys2,
+ (ulong)(cy_pci_phys2 + CyPCI_Ze_win - 1));
+
+ printk("%d channels starting from port %d.\n",
+ cy_pci_nchan,cy_next_channel);
+ cy_next_channel += cy_pci_nchan;
+ }
+ if (ZeIndex != 0) {
+ printk("Cyclades-Ze/PCI found at 0x%x ",
+ (unsigned int) Ze_phys2[0]);
+ printk("but no more cards can be used.\n");
+ printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+ }
+ return(i);
+#else
+ return(0);
+#endif /* ifdef CONFIG_PCI */
+} /* cy_detect_pci */
+
+
+/*
+ * This routine prints out the appropriate serial driver version number
+ * and identifies which options were configured into this driver.
+ */
+static inline void
+show_version(void)
+{
+ char *rcsvers, *rcsdate, *tmp;
+ rcsvers = strchr(rcsid, ' '); rcsvers++;
+ tmp = strchr(rcsvers, ' '); *tmp++ = '\0';
+ rcsdate = strchr(tmp, ' '); rcsdate++;
+ tmp = strrchr(rcsdate, ' '); *tmp = '\0';
+ printk("Cyclades driver %s %s\n",
+ rcsvers, rcsdate);
+ printk(" built %s %s\n",
+ __DATE__, __TIME__);
+} /* show_version */
+
+static int
+cyclades_get_proc_info(char *buf, char **start, off_t offset, int length,
+ int *eof, void *data)
+{
+ struct cyclades_port *info;
+ int i;
+ int len=0;
+ off_t begin=0;
+ off_t pos=0;
+ int size;
+ __u32 cur_jifs = jiffies;
+
+ size = sprintf(buf, "Dev TimeOpen BytesOut IdleOut BytesIn IdleIn Overruns Ldisc\n");
+
+ pos += size;
+ len += size;
+
+ /* Output one line for each known port */
+ for (i = 0; i < NR_PORTS && cy_port[i].line >= 0; i++) {
+ info = &cy_port[i];
+
+ if (info->count)
+ size = sprintf(buf+len,
+ "%3d %8lu %10lu %8lu %10lu %8lu %9lu %6ld\n",
+ info->line,
+ JIFFIES_DIFF(info->idle_stats.in_use, cur_jifs) / HZ,
+ info->idle_stats.xmit_bytes,
+ JIFFIES_DIFF(info->idle_stats.xmit_idle, cur_jifs) / HZ,
+ info->idle_stats.recv_bytes,
+ JIFFIES_DIFF(info->idle_stats.recv_idle, cur_jifs) / HZ,
+ info->idle_stats.overruns,
+ (long) info->tty->ldisc.num);
+ else
+ size = sprintf(buf+len,
+ "%3d %8lu %10lu %8lu %10lu %8lu %9lu %6ld\n",
+ info->line, 0L, 0L, 0L, 0L, 0L, 0L, 0L);
+ len += size;
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ goto done;
+ }
+ *eof = 1;
+done:
+ *start = buf + (offset - begin); /* Start of wanted data */
+ len -= (offset - begin); /* Start slop */
+ if (len > length)
+ len = length; /* Ending slop */
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+/* The serial driver boot-time initialization code!
+ Hardware I/O ports are mapped to character special devices on a
+ first found, first allocated manner. That is, this code searches
+ for Cyclom cards in the system. As each is found, it is probed
+ to discover how many chips (and thus how many ports) are present.
+ These ports are mapped to the tty ports 32 and upward in monotonic
+ fashion. If an 8-port card is replaced with a 16-port card, the
+ port mapping on a following card will shift.
+
+ This approach is different from what is used in the other serial
+ device driver because the Cyclom is more properly a multiplexer,
+ not just an aggregation of serial ports on one card.
+
+ If there are more cards with more ports than have been
+ statically allocated above, a warning is printed and the
+ extra ports are ignored.
+ */
+
+static struct tty_operations cy_ops = {
+ .open = cy_open,
+ .close = cy_close,
+ .write = cy_write,
+ .put_char = cy_put_char,
+ .flush_chars = cy_flush_chars,
+ .write_room = cy_write_room,
+ .chars_in_buffer = cy_chars_in_buffer,
+ .flush_buffer = cy_flush_buffer,
+ .ioctl = cy_ioctl,
+ .throttle = cy_throttle,
+ .unthrottle = cy_unthrottle,
+ .set_termios = cy_set_termios,
+ .stop = cy_stop,
+ .start = cy_start,
+ .hangup = cy_hangup,
+ .break_ctl = cy_break,
+ .wait_until_sent = cy_wait_until_sent,
+ .read_proc = cyclades_get_proc_info,
+ .tiocmget = cy_tiocmget,
+ .tiocmset = cy_tiocmset,
+};
+
+static int __init
+cy_init(void)
+{
+ struct cyclades_port *info;
+ struct cyclades_card *cinfo;
+ int number_z_boards = 0;
+ int board,port,i,index;
+ unsigned long mailbox;
+ unsigned short chip_number;
+ int nports;
+
+ cy_serial_driver = alloc_tty_driver(NR_PORTS);
+ if (!cy_serial_driver)
+ return -ENOMEM;
+ show_version();
+
+ /* Initialize the tty_driver structure */
+
+ cy_serial_driver->owner = THIS_MODULE;
+ cy_serial_driver->driver_name = "cyclades";
+ cy_serial_driver->name = "ttyC";
+ cy_serial_driver->devfs_name = "tts/C";
+ cy_serial_driver->major = CYCLADES_MAJOR;
+ cy_serial_driver->minor_start = 0;
+ cy_serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ cy_serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ cy_serial_driver->init_termios = tty_std_termios;
+ cy_serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ cy_serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(cy_serial_driver, &cy_ops);
+
+ if (tty_register_driver(cy_serial_driver))
+ panic("Couldn't register Cyclades serial driver\n");
+
+ for (i = 0; i < NR_CARDS; i++) {
+ /* base_addr=0 indicates board not found */
+ cy_card[i].base_addr = NULL;
+ }
+
+ /* the code below is responsible to find the boards. Each different
+ type of board has its own detection routine. If a board is found,
+ the next cy_card structure available is set by the detection
+ routine. These functions are responsible for checking the
+ availability of cy_card and cy_port data structures and updating
+ the cy_next_channel. */
+
+ /* look for isa boards */
+ cy_isa_nboard = cy_detect_isa();
+
+ /* look for pci boards */
+ cy_pci_nboard = cy_detect_pci();
+
+ cy_nboard = cy_isa_nboard + cy_pci_nboard;
+
+ /* invalidate remaining cy_card structures */
+ for (i = 0 ; i < NR_CARDS ; i++) {
+ if (cy_card[i].base_addr == 0) {
+ cy_card[i].first_line = -1;
+ cy_card[i].ctl_addr = NULL;
+ cy_card[i].irq = 0;
+ cy_card[i].bus_index = 0;
+ cy_card[i].first_line = 0;
+ cy_card[i].num_chips = 0;
+ }
+ }
+ /* invalidate remaining cy_port structures */
+ for (i = cy_next_channel ; i < NR_PORTS ; i++) {
+ cy_port[i].line = -1;
+ cy_port[i].magic = -1;
+ }
+
+ /* initialize per-port data structures for each valid board found */
+ for (board = 0 ; board < cy_nboard ; board++) {
+ cinfo = &cy_card[board];
+ if (cinfo->num_chips == -1) { /* Cyclades-Z */
+ number_z_boards++;
+ mailbox = cy_readl(&((struct RUNTIME_9060 __iomem *)
+ cy_card[board].ctl_addr)->mail_box_0);
+ nports = (mailbox == ZE_V1) ? ZE_V1_NPORTS : 8;
+ cinfo->intr_enabled = 0;
+ cinfo->nports = 0; /* Will be correctly set later, after
+ Z FW is loaded */
+ spin_lock_init(&cinfo->card_lock);
+ for (port = cinfo->first_line ;
+ port < cinfo->first_line + nports;
+ port++)
+ {
+ info = &cy_port[port];
+ info->magic = CYCLADES_MAGIC;
+ info->type = PORT_STARTECH;
+ info->card = board;
+ info->line = port;
+ info->chip_rev = 0;
+ info->flags = STD_COM_FLAGS;
+ info->tty = NULL;
+ if (mailbox == ZO_V1)
+ info->xmit_fifo_size = CYZ_FIFO_SIZE;
+ else
+ info->xmit_fifo_size = 4 * CYZ_FIFO_SIZE;
+ info->cor1 = 0;
+ info->cor2 = 0;
+ info->cor3 = 0;
+ info->cor4 = 0;
+ info->cor5 = 0;
+ info->tbpr = 0;
+ info->tco = 0;
+ info->rbpr = 0;
+ info->rco = 0;
+ info->custom_divisor = 0;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = CLOSING_WAIT_DELAY;
+ info->icount.cts = info->icount.dsr =
+ info->icount.rng = info->icount.dcd = 0;
+ info->icount.rx = info->icount.tx = 0;
+ info->icount.frame = info->icount.parity = 0;
+ info->icount.overrun = info->icount.brk = 0;
+ info->x_char = 0;
+ info->event = 0;
+ info->count = 0;
+ info->blocked_open = 0;
+ info->default_threshold = 0;
+ info->default_timeout = 0;
+ INIT_WORK(&info->tqueue, do_softint, info);
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->shutdown_wait);
+ init_waitqueue_head(&info->delta_msr_wait);
+ /* info->session */
+ /* info->pgrp */
+ info->read_status_mask = 0;
+ /* info->timeout */
+ /* Bentson's vars */
+ info->jiffies[0] = 0;
+ info->jiffies[1] = 0;
+ info->jiffies[2] = 0;
+ info->rflush_count = 0;
+#ifdef CONFIG_CYZ_INTR
+ init_timer(&cyz_rx_full_timer[port]);
+ cyz_rx_full_timer[port].function = NULL;
+#endif
+ }
+ continue;
+ }else{ /* Cyclom-Y of some kind*/
+ index = cinfo->bus_index;
+ spin_lock_init(&cinfo->card_lock);
+ cinfo->nports = CyPORTS_PER_CHIP * cinfo->num_chips;
+ for (port = cinfo->first_line ;
+ port < cinfo->first_line + cinfo->nports ;
+ port++)
+ {
+ info = &cy_port[port];
+ info->magic = CYCLADES_MAGIC;
+ info->type = PORT_CIRRUS;
+ info->card = board;
+ info->line = port;
+ info->flags = STD_COM_FLAGS;
+ info->tty = NULL;
+ info->xmit_fifo_size = CyMAX_CHAR_FIFO;
+ info->cor1 = CyPARITY_NONE|Cy_1_STOP|Cy_8_BITS;
+ info->cor2 = CyETC;
+ info->cor3 = 0x08; /* _very_ small rcv threshold */
+ info->cor4 = 0;
+ info->cor5 = 0;
+ info->custom_divisor = 0;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = CLOSING_WAIT_DELAY;
+ info->icount.cts = info->icount.dsr =
+ info->icount.rng = info->icount.dcd = 0;
+ info->icount.rx = info->icount.tx = 0;
+ info->icount.frame = info->icount.parity = 0;
+ info->icount.overrun = info->icount.brk = 0;
+ chip_number = (port - cinfo->first_line) / 4;
+ if ((info->chip_rev =
+ cy_readb(cinfo->base_addr +
+ (cy_chip_offset[chip_number]<<index) +
+ (CyGFRCR<<index))) >= CD1400_REV_J) {
+ /* It is a CD1400 rev. J or later */
+ info->tbpr = baud_bpr_60[13]; /* Tx BPR */
+ info->tco = baud_co_60[13]; /* Tx CO */
+ info->rbpr = baud_bpr_60[13]; /* Rx BPR */
+ info->rco = baud_co_60[13]; /* Rx CO */
+ info->rflow = 0;
+ info->rtsdtr_inv = 1;
+ } else {
+ info->tbpr = baud_bpr_25[13]; /* Tx BPR */
+ info->tco = baud_co_25[13]; /* Tx CO */
+ info->rbpr = baud_bpr_25[13]; /* Rx BPR */
+ info->rco = baud_co_25[13]; /* Rx CO */
+ info->rflow = 0;
+ info->rtsdtr_inv = 0;
+ }
+ info->x_char = 0;
+ info->event = 0;
+ info->count = 0;
+ info->blocked_open = 0;
+ info->default_threshold = 0;
+ info->default_timeout = 0;
+ INIT_WORK(&info->tqueue, do_softint, info);
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->shutdown_wait);
+ init_waitqueue_head(&info->delta_msr_wait);
+ /* info->session */
+ /* info->pgrp */
+ info->read_status_mask =
+ CyTIMEOUT| CySPECHAR| CyBREAK
+ | CyPARITY| CyFRAME| CyOVERRUN;
+ /* info->timeout */
+ }
+ }
+ }
+
+#ifndef CONFIG_CYZ_INTR
+ if (number_z_boards && !cyz_timeron){
+ cyz_timeron++;
+ cyz_timerlist.expires = jiffies + 1;
+ add_timer(&cyz_timerlist);
+#ifdef CY_PCI_DEBUG
+ printk("Cyclades-Z polling initialized\n");
+#endif
+ }
+#endif /* CONFIG_CYZ_INTR */
+
+ return 0;
+
+} /* cy_init */
+
+static void __exit
+cy_cleanup_module(void)
+{
+ int i, e1;
+
+#ifndef CONFIG_CYZ_INTR
+ if (cyz_timeron){
+ cyz_timeron = 0;
+ del_timer(&cyz_timerlist);
+ }
+#endif /* CONFIG_CYZ_INTR */
+
+ if ((e1 = tty_unregister_driver(cy_serial_driver)))
+ printk("cyc: failed to unregister Cyclades serial driver(%d)\n",
+ e1);
+
+ put_tty_driver(cy_serial_driver);
+
+ for (i = 0; i < NR_CARDS; i++) {
+ if (cy_card[i].base_addr) {
+ iounmap(cy_card[i].base_addr);
+ if (cy_card[i].ctl_addr)
+ iounmap(cy_card[i].ctl_addr);
+ if (cy_card[i].irq
+#ifndef CONFIG_CYZ_INTR
+ && cy_card[i].num_chips != -1 /* not a Z card */
+#endif /* CONFIG_CYZ_INTR */
+ )
+ free_irq(cy_card[i].irq, &cy_card[i]);
+#ifdef CONFIG_PCI
+ if (cy_card[i].pdev)
+ pci_release_regions(cy_card[i].pdev);
+#endif
+ }
+ }
+ if (tmp_buf) {
+ free_page((unsigned long) tmp_buf);
+ tmp_buf = NULL;
+ }
+} /* cy_cleanup_module */
+
+module_init(cy_init);
+module_exit(cy_cleanup_module);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/decserial.c b/drivers/char/decserial.c
new file mode 100644
index 000000000000..aa1440934e95
--- /dev/null
+++ b/drivers/char/decserial.c
@@ -0,0 +1,100 @@
+/*
+ * sercons.c
+ * choose the right serial device at boot time
+ *
+ * triemer 6-SEP-1998
+ * sercons.c is designed to allow the three different kinds
+ * of serial devices under the decstation world to co-exist
+ * in the same kernel. The idea here is to abstract
+ * the pieces of the drivers that are common to this file
+ * so that they do not clash at compile time and runtime.
+ *
+ * HK 16-SEP-1998 v0.002
+ * removed the PROM console as this is not a real serial
+ * device. Added support for PROM console in drivers/char/tty_io.c
+ * instead. Although it may work to enable more than one
+ * console device I strongly recommend to use only one.
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <asm/dec/machtype.h>
+
+#ifdef CONFIG_ZS
+extern int zs_init(void);
+#endif
+
+#ifdef CONFIG_DZ
+extern int dz_init(void);
+#endif
+
+#ifdef CONFIG_SERIAL_CONSOLE
+
+#ifdef CONFIG_ZS
+extern void zs_serial_console_init(void);
+#endif
+
+#ifdef CONFIG_DZ
+extern void dz_serial_console_init(void);
+#endif
+
+#endif
+
+/* rs_init - starts up the serial interface -
+ handle normal case of starting up the serial interface */
+
+#ifdef CONFIG_SERIAL
+
+int __init rs_init(void)
+{
+
+#if defined(CONFIG_ZS) && defined(CONFIG_DZ)
+ if (IOASIC)
+ return zs_init();
+ else
+ return dz_init();
+#else
+
+#ifdef CONFIG_ZS
+ return zs_init();
+#endif
+
+#ifdef CONFIG_DZ
+ return dz_init();
+#endif
+
+#endif
+}
+
+__initcall(rs_init);
+
+#endif
+
+#ifdef CONFIG_SERIAL_CONSOLE
+
+/* serial_console_init handles the special case of starting
+ * up the console on the serial port
+ */
+static int __init decserial_console_init(void)
+{
+#if defined(CONFIG_ZS) && defined(CONFIG_DZ)
+ if (IOASIC)
+ zs_serial_console_init();
+ else
+ dz_serial_console_init();
+#else
+
+#ifdef CONFIG_ZS
+ zs_serial_console_init();
+#endif
+
+#ifdef CONFIG_DZ
+ dz_serial_console_init();
+#endif
+
+#endif
+ return 0;
+}
+console_initcall(decserial_console_init);
+
+#endif
diff --git a/drivers/char/defkeymap.c_shipped b/drivers/char/defkeymap.c_shipped
new file mode 100644
index 000000000000..453a2f1ffa15
--- /dev/null
+++ b/drivers/char/defkeymap.c_shipped
@@ -0,0 +1,262 @@
+/* Do not edit this file! It was automatically generated by */
+/* loadkeys --mktable defkeymap.map > defkeymap.c */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+u_short plain_map[NR_KEYS] = {
+ 0xf200, 0xf01b, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036,
+ 0xf037, 0xf038, 0xf039, 0xf030, 0xf02d, 0xf03d, 0xf07f, 0xf009,
+ 0xfb71, 0xfb77, 0xfb65, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69,
+ 0xfb6f, 0xfb70, 0xf05b, 0xf05d, 0xf201, 0xf702, 0xfb61, 0xfb73,
+ 0xfb64, 0xfb66, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c, 0xf03b,
+ 0xf027, 0xf060, 0xf700, 0xf05c, 0xfb7a, 0xfb78, 0xfb63, 0xfb76,
+ 0xfb62, 0xfb6e, 0xfb6d, 0xf02c, 0xf02e, 0xf02f, 0xf700, 0xf30c,
+ 0xf703, 0xf020, 0xf207, 0xf100, 0xf101, 0xf102, 0xf103, 0xf104,
+ 0xf105, 0xf106, 0xf107, 0xf108, 0xf109, 0xf208, 0xf209, 0xf307,
+ 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301,
+ 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf03c, 0xf10a,
+ 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short shift_map[NR_KEYS] = {
+ 0xf200, 0xf01b, 0xf021, 0xf040, 0xf023, 0xf024, 0xf025, 0xf05e,
+ 0xf026, 0xf02a, 0xf028, 0xf029, 0xf05f, 0xf02b, 0xf07f, 0xf009,
+ 0xfb51, 0xfb57, 0xfb45, 0xfb52, 0xfb54, 0xfb59, 0xfb55, 0xfb49,
+ 0xfb4f, 0xfb50, 0xf07b, 0xf07d, 0xf201, 0xf702, 0xfb41, 0xfb53,
+ 0xfb44, 0xfb46, 0xfb47, 0xfb48, 0xfb4a, 0xfb4b, 0xfb4c, 0xf03a,
+ 0xf022, 0xf07e, 0xf700, 0xf07c, 0xfb5a, 0xfb58, 0xfb43, 0xfb56,
+ 0xfb42, 0xfb4e, 0xfb4d, 0xf03c, 0xf03e, 0xf03f, 0xf700, 0xf30c,
+ 0xf703, 0xf020, 0xf207, 0xf10a, 0xf10b, 0xf10c, 0xf10d, 0xf10e,
+ 0xf10f, 0xf110, 0xf111, 0xf112, 0xf113, 0xf213, 0xf203, 0xf307,
+ 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301,
+ 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf03e, 0xf10a,
+ 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf200, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf20b, 0xf601, 0xf602, 0xf117, 0xf600, 0xf20a, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short altgr_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf040, 0xf200, 0xf024, 0xf200, 0xf200,
+ 0xf07b, 0xf05b, 0xf05d, 0xf07d, 0xf05c, 0xf200, 0xf200, 0xf200,
+ 0xfb71, 0xfb77, 0xf918, 0xfb72, 0xfb74, 0xfb79, 0xfb75, 0xfb69,
+ 0xfb6f, 0xfb70, 0xf200, 0xf07e, 0xf201, 0xf702, 0xf914, 0xfb73,
+ 0xf917, 0xf919, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c, 0xf200,
+ 0xf200, 0xf200, 0xf700, 0xf200, 0xfb7a, 0xfb78, 0xf916, 0xfb76,
+ 0xf915, 0xfb6e, 0xfb6d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c,
+ 0xf703, 0xf200, 0xf207, 0xf50c, 0xf50d, 0xf50e, 0xf50f, 0xf510,
+ 0xf511, 0xf512, 0xf513, 0xf514, 0xf515, 0xf208, 0xf202, 0xf911,
+ 0xf912, 0xf913, 0xf30b, 0xf90e, 0xf90f, 0xf910, 0xf30a, 0xf90b,
+ 0xf90c, 0xf90d, 0xf90a, 0xf310, 0xf206, 0xf200, 0xf07c, 0xf516,
+ 0xf517, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf200, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf000, 0xf01b, 0xf01c, 0xf01d, 0xf01e,
+ 0xf01f, 0xf07f, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf008, 0xf200,
+ 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009,
+ 0xf00f, 0xf010, 0xf01b, 0xf01d, 0xf201, 0xf702, 0xf001, 0xf013,
+ 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c, 0xf200,
+ 0xf007, 0xf000, 0xf700, 0xf01c, 0xf01a, 0xf018, 0xf003, 0xf016,
+ 0xf002, 0xf00e, 0xf00d, 0xf200, 0xf20e, 0xf07f, 0xf700, 0xf30c,
+ 0xf703, 0xf000, 0xf207, 0xf100, 0xf101, 0xf102, 0xf103, 0xf104,
+ 0xf105, 0xf106, 0xf107, 0xf108, 0xf109, 0xf208, 0xf204, 0xf307,
+ 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301,
+ 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf200, 0xf10a,
+ 0xf10b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short shift_ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf000, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf200, 0xf200,
+ 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015, 0xf009,
+ 0xf00f, 0xf010, 0xf200, 0xf200, 0xf201, 0xf702, 0xf001, 0xf013,
+ 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c, 0xf200,
+ 0xf200, 0xf200, 0xf700, 0xf200, 0xf01a, 0xf018, 0xf003, 0xf016,
+ 0xf002, 0xf00e, 0xf00d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c,
+ 0xf703, 0xf200, 0xf207, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf208, 0xf200, 0xf307,
+ 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301,
+ 0xf302, 0xf303, 0xf300, 0xf310, 0xf206, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf200, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short alt_map[NR_KEYS] = {
+ 0xf200, 0xf81b, 0xf831, 0xf832, 0xf833, 0xf834, 0xf835, 0xf836,
+ 0xf837, 0xf838, 0xf839, 0xf830, 0xf82d, 0xf83d, 0xf87f, 0xf809,
+ 0xf871, 0xf877, 0xf865, 0xf872, 0xf874, 0xf879, 0xf875, 0xf869,
+ 0xf86f, 0xf870, 0xf85b, 0xf85d, 0xf80d, 0xf702, 0xf861, 0xf873,
+ 0xf864, 0xf866, 0xf867, 0xf868, 0xf86a, 0xf86b, 0xf86c, 0xf83b,
+ 0xf827, 0xf860, 0xf700, 0xf85c, 0xf87a, 0xf878, 0xf863, 0xf876,
+ 0xf862, 0xf86e, 0xf86d, 0xf82c, 0xf82e, 0xf82f, 0xf700, 0xf30c,
+ 0xf703, 0xf820, 0xf207, 0xf500, 0xf501, 0xf502, 0xf503, 0xf504,
+ 0xf505, 0xf506, 0xf507, 0xf508, 0xf509, 0xf208, 0xf209, 0xf907,
+ 0xf908, 0xf909, 0xf30b, 0xf904, 0xf905, 0xf906, 0xf30a, 0xf901,
+ 0xf902, 0xf903, 0xf900, 0xf310, 0xf206, 0xf200, 0xf83c, 0xf50a,
+ 0xf50b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf01c, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf210, 0xf211, 0xf117, 0xf600, 0xf119, 0xf115, 0xf116,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+u_short ctrl_alt_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf811, 0xf817, 0xf805, 0xf812, 0xf814, 0xf819, 0xf815, 0xf809,
+ 0xf80f, 0xf810, 0xf200, 0xf200, 0xf201, 0xf702, 0xf801, 0xf813,
+ 0xf804, 0xf806, 0xf807, 0xf808, 0xf80a, 0xf80b, 0xf80c, 0xf200,
+ 0xf200, 0xf200, 0xf700, 0xf200, 0xf81a, 0xf818, 0xf803, 0xf816,
+ 0xf802, 0xf80e, 0xf80d, 0xf200, 0xf200, 0xf200, 0xf700, 0xf30c,
+ 0xf703, 0xf200, 0xf207, 0xf500, 0xf501, 0xf502, 0xf503, 0xf504,
+ 0xf505, 0xf506, 0xf507, 0xf508, 0xf509, 0xf208, 0xf200, 0xf307,
+ 0xf308, 0xf309, 0xf30b, 0xf304, 0xf305, 0xf306, 0xf30a, 0xf301,
+ 0xf302, 0xf303, 0xf300, 0xf20c, 0xf206, 0xf200, 0xf200, 0xf50a,
+ 0xf50b, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf30e, 0xf702, 0xf30d, 0xf200, 0xf701, 0xf205, 0xf114, 0xf603,
+ 0xf118, 0xf601, 0xf602, 0xf117, 0xf600, 0xf119, 0xf115, 0xf20c,
+ 0xf11a, 0xf10c, 0xf10d, 0xf11b, 0xf11c, 0xf110, 0xf311, 0xf11d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+ plain_map, shift_map, altgr_map, NULL,
+ ctrl_map, shift_ctrl_map, NULL, NULL,
+ alt_map, NULL, NULL, NULL,
+ ctrl_alt_map, NULL
+};
+
+unsigned int keymap_count = 7;
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+ '\033', '[', '[', 'A', 0,
+ '\033', '[', '[', 'B', 0,
+ '\033', '[', '[', 'C', 0,
+ '\033', '[', '[', 'D', 0,
+ '\033', '[', '[', 'E', 0,
+ '\033', '[', '1', '7', '~', 0,
+ '\033', '[', '1', '8', '~', 0,
+ '\033', '[', '1', '9', '~', 0,
+ '\033', '[', '2', '0', '~', 0,
+ '\033', '[', '2', '1', '~', 0,
+ '\033', '[', '2', '3', '~', 0,
+ '\033', '[', '2', '4', '~', 0,
+ '\033', '[', '2', '5', '~', 0,
+ '\033', '[', '2', '6', '~', 0,
+ '\033', '[', '2', '8', '~', 0,
+ '\033', '[', '2', '9', '~', 0,
+ '\033', '[', '3', '1', '~', 0,
+ '\033', '[', '3', '2', '~', 0,
+ '\033', '[', '3', '3', '~', 0,
+ '\033', '[', '3', '4', '~', 0,
+ '\033', '[', '1', '~', 0,
+ '\033', '[', '2', '~', 0,
+ '\033', '[', '3', '~', 0,
+ '\033', '[', '4', '~', 0,
+ '\033', '[', '5', '~', 0,
+ '\033', '[', '6', '~', 0,
+ '\033', '[', 'M', 0,
+ '\033', '[', 'P', 0,
+};
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0; /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+ func_buf + 0,
+ func_buf + 5,
+ func_buf + 10,
+ func_buf + 15,
+ func_buf + 20,
+ func_buf + 25,
+ func_buf + 31,
+ func_buf + 37,
+ func_buf + 43,
+ func_buf + 49,
+ func_buf + 55,
+ func_buf + 61,
+ func_buf + 67,
+ func_buf + 73,
+ func_buf + 79,
+ func_buf + 85,
+ func_buf + 91,
+ func_buf + 97,
+ func_buf + 103,
+ func_buf + 109,
+ func_buf + 115,
+ func_buf + 120,
+ func_buf + 125,
+ func_buf + 130,
+ func_buf + 135,
+ func_buf + 140,
+ func_buf + 145,
+ NULL,
+ NULL,
+ func_buf + 149,
+ NULL,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+ {'`', 'A', '\300'}, {'`', 'a', '\340'},
+ {'\'', 'A', '\301'}, {'\'', 'a', '\341'},
+ {'^', 'A', '\302'}, {'^', 'a', '\342'},
+ {'~', 'A', '\303'}, {'~', 'a', '\343'},
+ {'"', 'A', '\304'}, {'"', 'a', '\344'},
+ {'O', 'A', '\305'}, {'o', 'a', '\345'},
+ {'0', 'A', '\305'}, {'0', 'a', '\345'},
+ {'A', 'A', '\305'}, {'a', 'a', '\345'},
+ {'A', 'E', '\306'}, {'a', 'e', '\346'},
+ {',', 'C', '\307'}, {',', 'c', '\347'},
+ {'`', 'E', '\310'}, {'`', 'e', '\350'},
+ {'\'', 'E', '\311'}, {'\'', 'e', '\351'},
+ {'^', 'E', '\312'}, {'^', 'e', '\352'},
+ {'"', 'E', '\313'}, {'"', 'e', '\353'},
+ {'`', 'I', '\314'}, {'`', 'i', '\354'},
+ {'\'', 'I', '\315'}, {'\'', 'i', '\355'},
+ {'^', 'I', '\316'}, {'^', 'i', '\356'},
+ {'"', 'I', '\317'}, {'"', 'i', '\357'},
+ {'-', 'D', '\320'}, {'-', 'd', '\360'},
+ {'~', 'N', '\321'}, {'~', 'n', '\361'},
+ {'`', 'O', '\322'}, {'`', 'o', '\362'},
+ {'\'', 'O', '\323'}, {'\'', 'o', '\363'},
+ {'^', 'O', '\324'}, {'^', 'o', '\364'},
+ {'~', 'O', '\325'}, {'~', 'o', '\365'},
+ {'"', 'O', '\326'}, {'"', 'o', '\366'},
+ {'/', 'O', '\330'}, {'/', 'o', '\370'},
+ {'`', 'U', '\331'}, {'`', 'u', '\371'},
+ {'\'', 'U', '\332'}, {'\'', 'u', '\372'},
+ {'^', 'U', '\333'}, {'^', 'u', '\373'},
+ {'"', 'U', '\334'}, {'"', 'u', '\374'},
+ {'\'', 'Y', '\335'}, {'\'', 'y', '\375'},
+ {'T', 'H', '\336'}, {'t', 'h', '\376'},
+ {'s', 's', '\337'}, {'"', 'y', '\377'},
+ {'s', 'z', '\337'}, {'i', 'j', '\377'},
+};
+
+unsigned int accent_table_size = 68;
diff --git a/drivers/char/defkeymap.map b/drivers/char/defkeymap.map
new file mode 100644
index 000000000000..50b30cace261
--- /dev/null
+++ b/drivers/char/defkeymap.map
@@ -0,0 +1,357 @@
+# Default kernel keymap. This uses 7 modifier combinations.
+keymaps 0-2,4-5,8,12
+# Change the above line into
+# keymaps 0-2,4-6,8,12
+# in case you want the entries
+# altgr control keycode 83 = Boot
+# altgr control keycode 111 = Boot
+# below.
+#
+# In fact AltGr is used very little, and one more keymap can
+# be saved by mapping AltGr to Alt (and adapting a few entries):
+# keycode 100 = Alt
+#
+keycode 1 = Escape Escape
+ alt keycode 1 = Meta_Escape
+keycode 2 = one exclam
+ alt keycode 2 = Meta_one
+keycode 3 = two at at
+ control keycode 3 = nul
+ shift control keycode 3 = nul
+ alt keycode 3 = Meta_two
+keycode 4 = three numbersign
+ control keycode 4 = Escape
+ alt keycode 4 = Meta_three
+keycode 5 = four dollar dollar
+ control keycode 5 = Control_backslash
+ alt keycode 5 = Meta_four
+keycode 6 = five percent
+ control keycode 6 = Control_bracketright
+ alt keycode 6 = Meta_five
+keycode 7 = six asciicircum
+ control keycode 7 = Control_asciicircum
+ alt keycode 7 = Meta_six
+keycode 8 = seven ampersand braceleft
+ control keycode 8 = Control_underscore
+ alt keycode 8 = Meta_seven
+keycode 9 = eight asterisk bracketleft
+ control keycode 9 = Delete
+ alt keycode 9 = Meta_eight
+keycode 10 = nine parenleft bracketright
+ alt keycode 10 = Meta_nine
+keycode 11 = zero parenright braceright
+ alt keycode 11 = Meta_zero
+keycode 12 = minus underscore backslash
+ control keycode 12 = Control_underscore
+ shift control keycode 12 = Control_underscore
+ alt keycode 12 = Meta_minus
+keycode 13 = equal plus
+ alt keycode 13 = Meta_equal
+keycode 14 = Delete Delete
+ control keycode 14 = BackSpace
+ alt keycode 14 = Meta_Delete
+keycode 15 = Tab Tab
+ alt keycode 15 = Meta_Tab
+keycode 16 = q
+keycode 17 = w
+keycode 18 = e
+ altgr keycode 18 = Hex_E
+keycode 19 = r
+keycode 20 = t
+keycode 21 = y
+keycode 22 = u
+keycode 23 = i
+keycode 24 = o
+keycode 25 = p
+keycode 26 = bracketleft braceleft
+ control keycode 26 = Escape
+ alt keycode 26 = Meta_bracketleft
+keycode 27 = bracketright braceright asciitilde
+ control keycode 27 = Control_bracketright
+ alt keycode 27 = Meta_bracketright
+keycode 28 = Return
+ alt keycode 28 = Meta_Control_m
+keycode 29 = Control
+keycode 30 = a
+ altgr keycode 30 = Hex_A
+keycode 31 = s
+keycode 32 = d
+ altgr keycode 32 = Hex_D
+keycode 33 = f
+ altgr keycode 33 = Hex_F
+keycode 34 = g
+keycode 35 = h
+keycode 36 = j
+keycode 37 = k
+keycode 38 = l
+keycode 39 = semicolon colon
+ alt keycode 39 = Meta_semicolon
+keycode 40 = apostrophe quotedbl
+ control keycode 40 = Control_g
+ alt keycode 40 = Meta_apostrophe
+keycode 41 = grave asciitilde
+ control keycode 41 = nul
+ alt keycode 41 = Meta_grave
+keycode 42 = Shift
+keycode 43 = backslash bar
+ control keycode 43 = Control_backslash
+ alt keycode 43 = Meta_backslash
+keycode 44 = z
+keycode 45 = x
+keycode 46 = c
+ altgr keycode 46 = Hex_C
+keycode 47 = v
+keycode 48 = b
+ altgr keycode 48 = Hex_B
+keycode 49 = n
+keycode 50 = m
+keycode 51 = comma less
+ alt keycode 51 = Meta_comma
+keycode 52 = period greater
+ control keycode 52 = Compose
+ alt keycode 52 = Meta_period
+keycode 53 = slash question
+ control keycode 53 = Delete
+ alt keycode 53 = Meta_slash
+keycode 54 = Shift
+keycode 55 = KP_Multiply
+keycode 56 = Alt
+keycode 57 = space space
+ control keycode 57 = nul
+ alt keycode 57 = Meta_space
+keycode 58 = Caps_Lock
+keycode 59 = F1 F11 Console_13
+ control keycode 59 = F1
+ alt keycode 59 = Console_1
+ control alt keycode 59 = Console_1
+keycode 60 = F2 F12 Console_14
+ control keycode 60 = F2
+ alt keycode 60 = Console_2
+ control alt keycode 60 = Console_2
+keycode 61 = F3 F13 Console_15
+ control keycode 61 = F3
+ alt keycode 61 = Console_3
+ control alt keycode 61 = Console_3
+keycode 62 = F4 F14 Console_16
+ control keycode 62 = F4
+ alt keycode 62 = Console_4
+ control alt keycode 62 = Console_4
+keycode 63 = F5 F15 Console_17
+ control keycode 63 = F5
+ alt keycode 63 = Console_5
+ control alt keycode 63 = Console_5
+keycode 64 = F6 F16 Console_18
+ control keycode 64 = F6
+ alt keycode 64 = Console_6
+ control alt keycode 64 = Console_6
+keycode 65 = F7 F17 Console_19
+ control keycode 65 = F7
+ alt keycode 65 = Console_7
+ control alt keycode 65 = Console_7
+keycode 66 = F8 F18 Console_20
+ control keycode 66 = F8
+ alt keycode 66 = Console_8
+ control alt keycode 66 = Console_8
+keycode 67 = F9 F19 Console_21
+ control keycode 67 = F9
+ alt keycode 67 = Console_9
+ control alt keycode 67 = Console_9
+keycode 68 = F10 F20 Console_22
+ control keycode 68 = F10
+ alt keycode 68 = Console_10
+ control alt keycode 68 = Console_10
+keycode 69 = Num_Lock
+ shift keycode 69 = Bare_Num_Lock
+keycode 70 = Scroll_Lock Show_Memory Show_Registers
+ control keycode 70 = Show_State
+ alt keycode 70 = Scroll_Lock
+keycode 71 = KP_7
+ alt keycode 71 = Ascii_7
+ altgr keycode 71 = Hex_7
+keycode 72 = KP_8
+ alt keycode 72 = Ascii_8
+ altgr keycode 72 = Hex_8
+keycode 73 = KP_9
+ alt keycode 73 = Ascii_9
+ altgr keycode 73 = Hex_9
+keycode 74 = KP_Subtract
+keycode 75 = KP_4
+ alt keycode 75 = Ascii_4
+ altgr keycode 75 = Hex_4
+keycode 76 = KP_5
+ alt keycode 76 = Ascii_5
+ altgr keycode 76 = Hex_5
+keycode 77 = KP_6
+ alt keycode 77 = Ascii_6
+ altgr keycode 77 = Hex_6
+keycode 78 = KP_Add
+keycode 79 = KP_1
+ alt keycode 79 = Ascii_1
+ altgr keycode 79 = Hex_1
+keycode 80 = KP_2
+ alt keycode 80 = Ascii_2
+ altgr keycode 80 = Hex_2
+keycode 81 = KP_3
+ alt keycode 81 = Ascii_3
+ altgr keycode 81 = Hex_3
+keycode 82 = KP_0
+ alt keycode 82 = Ascii_0
+ altgr keycode 82 = Hex_0
+keycode 83 = KP_Period
+# altgr control keycode 83 = Boot
+ control alt keycode 83 = Boot
+keycode 84 = Last_Console
+keycode 85 =
+keycode 86 = less greater bar
+ alt keycode 86 = Meta_less
+keycode 87 = F11 F11 Console_23
+ control keycode 87 = F11
+ alt keycode 87 = Console_11
+ control alt keycode 87 = Console_11
+keycode 88 = F12 F12 Console_24
+ control keycode 88 = F12
+ alt keycode 88 = Console_12
+ control alt keycode 88 = Console_12
+keycode 89 =
+keycode 90 =
+keycode 91 =
+keycode 92 =
+keycode 93 =
+keycode 94 =
+keycode 95 =
+keycode 96 = KP_Enter
+keycode 97 = Control
+keycode 98 = KP_Divide
+keycode 99 = Control_backslash
+ control keycode 99 = Control_backslash
+ alt keycode 99 = Control_backslash
+keycode 100 = AltGr
+keycode 101 = Break
+keycode 102 = Find
+keycode 103 = Up
+keycode 104 = Prior
+ shift keycode 104 = Scroll_Backward
+keycode 105 = Left
+ alt keycode 105 = Decr_Console
+keycode 106 = Right
+ alt keycode 106 = Incr_Console
+keycode 107 = Select
+keycode 108 = Down
+keycode 109 = Next
+ shift keycode 109 = Scroll_Forward
+keycode 110 = Insert
+keycode 111 = Remove
+# altgr control keycode 111 = Boot
+ control alt keycode 111 = Boot
+keycode 112 = Macro
+keycode 113 = F13
+keycode 114 = F14
+keycode 115 = Help
+keycode 116 = Do
+keycode 117 = F17
+keycode 118 = KP_MinPlus
+keycode 119 = Pause
+keycode 120 =
+keycode 121 =
+keycode 122 =
+keycode 123 =
+keycode 124 =
+keycode 125 =
+keycode 126 =
+keycode 127 =
+string F1 = "\033[[A"
+string F2 = "\033[[B"
+string F3 = "\033[[C"
+string F4 = "\033[[D"
+string F5 = "\033[[E"
+string F6 = "\033[17~"
+string F7 = "\033[18~"
+string F8 = "\033[19~"
+string F9 = "\033[20~"
+string F10 = "\033[21~"
+string F11 = "\033[23~"
+string F12 = "\033[24~"
+string F13 = "\033[25~"
+string F14 = "\033[26~"
+string F15 = "\033[28~"
+string F16 = "\033[29~"
+string F17 = "\033[31~"
+string F18 = "\033[32~"
+string F19 = "\033[33~"
+string F20 = "\033[34~"
+string Find = "\033[1~"
+string Insert = "\033[2~"
+string Remove = "\033[3~"
+string Select = "\033[4~"
+string Prior = "\033[5~"
+string Next = "\033[6~"
+string Macro = "\033[M"
+string Pause = "\033[P"
+compose '`' 'A' to 'À'
+compose '`' 'a' to 'à'
+compose '\'' 'A' to 'Á'
+compose '\'' 'a' to 'á'
+compose '^' 'A' to 'Â'
+compose '^' 'a' to 'â'
+compose '~' 'A' to 'Ã'
+compose '~' 'a' to 'ã'
+compose '"' 'A' to 'Ä'
+compose '"' 'a' to 'ä'
+compose 'O' 'A' to 'Å'
+compose 'o' 'a' to 'å'
+compose '0' 'A' to 'Å'
+compose '0' 'a' to 'å'
+compose 'A' 'A' to 'Å'
+compose 'a' 'a' to 'å'
+compose 'A' 'E' to 'Æ'
+compose 'a' 'e' to 'æ'
+compose ',' 'C' to 'Ç'
+compose ',' 'c' to 'ç'
+compose '`' 'E' to 'È'
+compose '`' 'e' to 'è'
+compose '\'' 'E' to 'É'
+compose '\'' 'e' to 'é'
+compose '^' 'E' to 'Ê'
+compose '^' 'e' to 'ê'
+compose '"' 'E' to 'Ë'
+compose '"' 'e' to 'ë'
+compose '`' 'I' to 'Ì'
+compose '`' 'i' to 'ì'
+compose '\'' 'I' to 'Í'
+compose '\'' 'i' to 'í'
+compose '^' 'I' to 'Î'
+compose '^' 'i' to 'î'
+compose '"' 'I' to 'Ï'
+compose '"' 'i' to 'ï'
+compose '-' 'D' to 'Ð'
+compose '-' 'd' to 'ð'
+compose '~' 'N' to 'Ñ'
+compose '~' 'n' to 'ñ'
+compose '`' 'O' to 'Ò'
+compose '`' 'o' to 'ò'
+compose '\'' 'O' to 'Ó'
+compose '\'' 'o' to 'ó'
+compose '^' 'O' to 'Ô'
+compose '^' 'o' to 'ô'
+compose '~' 'O' to 'Õ'
+compose '~' 'o' to 'õ'
+compose '"' 'O' to 'Ö'
+compose '"' 'o' to 'ö'
+compose '/' 'O' to 'Ø'
+compose '/' 'o' to 'ø'
+compose '`' 'U' to 'Ù'
+compose '`' 'u' to 'ù'
+compose '\'' 'U' to 'Ú'
+compose '\'' 'u' to 'ú'
+compose '^' 'U' to 'Û'
+compose '^' 'u' to 'û'
+compose '"' 'U' to 'Ü'
+compose '"' 'u' to 'ü'
+compose '\'' 'Y' to 'Ý'
+compose '\'' 'y' to 'ý'
+compose 'T' 'H' to 'Þ'
+compose 't' 'h' to 'þ'
+compose 's' 's' to 'ß'
+compose '"' 'y' to 'ÿ'
+compose 's' 'z' to 'ß'
+compose 'i' 'j' to 'ÿ'
diff --git a/drivers/char/digi.h b/drivers/char/digi.h
new file mode 100644
index 000000000000..19df0e879b1b
--- /dev/null
+++ b/drivers/char/digi.h
@@ -0,0 +1,71 @@
+/* Definitions for DigiBoard ditty(1) command. */
+
+#if !defined(TIOCMODG)
+#define TIOCMODG (('d'<<8) | 250) /* get modem ctrl state */
+#define TIOCMODS (('d'<<8) | 251) /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMSET)
+#define TIOCMSET (('d'<<8) | 252) /* set modem ctrl state */
+#define TIOCMGET (('d'<<8) | 253) /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMBIC)
+#define TIOCMBIC (('d'<<8) | 254) /* set modem ctrl state */
+#define TIOCMBIS (('d'<<8) | 255) /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCSDTR)
+#define TIOCSDTR (('e'<<8) | 0) /* set DTR */
+#define TIOCCDTR (('e'<<8) | 1) /* clear DTR */
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA (('e'<<8) | 94) /* Read params */
+
+#define DIGI_SETA (('e'<<8) | 95) /* Set params */
+#define DIGI_SETAW (('e'<<8) | 96) /* Drain & set params */
+#define DIGI_SETAF (('e'<<8) | 97) /* Drain, flush & set params */
+
+#define DIGI_GETFLOW (('e'<<8) | 99) /* Get startc/stopc flow */
+ /* control characters */
+#define DIGI_SETFLOW (('e'<<8) | 100) /* Set startc/stopc flow */
+ /* control characters */
+#define DIGI_GETAFLOW (('e'<<8) | 101) /* Get Aux. startc/stopc */
+ /* flow control chars */
+#define DIGI_SETAFLOW (('e'<<8) | 102) /* Set Aux. startc/stopc */
+ /* flow control chars */
+
+struct digiflow_struct {
+ unsigned char startc; /* flow cntl start char */
+ unsigned char stopc; /* flow cntl stop char */
+};
+
+typedef struct digiflow_struct digiflow_t;
+
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON 0x0001 /* Handle IXON in the FEP */
+#define DIGI_FAST 0x0002 /* Fast baud rates */
+#define RTSPACE 0x0004 /* RTS input flow control */
+#define CTSPACE 0x0008 /* CTS output flow control */
+#define DSRPACE 0x0010 /* DSR output flow control */
+#define DCDPACE 0x0020 /* DCD output flow control */
+#define DTRPACE 0x0040 /* DTR input flow control */
+#define DIGI_FORCEDCD 0x0100 /* Force carrier */
+#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct {
+ unsigned short digi_flags; /* Flags (see above) */
+};
+
+typedef struct digi_struct digi_t;
diff --git a/drivers/char/digi1.h b/drivers/char/digi1.h
new file mode 100644
index 000000000000..184378d23f8c
--- /dev/null
+++ b/drivers/char/digi1.h
@@ -0,0 +1,100 @@
+/* Definitions for DigiBoard ditty(1) command. */
+
+#if !defined(TIOCMODG)
+#define TIOCMODG ('d'<<8) | 250 /* get modem ctrl state */
+#define TIOCMODS ('d'<<8) | 251 /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMSET)
+#define TIOCMSET ('d'<<8) | 252 /* set modem ctrl state */
+#define TIOCMGET ('d'<<8) | 253 /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCMBIC)
+#define TIOCMBIC ('d'<<8) | 254 /* set modem ctrl state */
+#define TIOCMBIS ('d'<<8) | 255 /* set modem ctrl state */
+#endif
+
+#if !defined(TIOCSDTR)
+#define TIOCSDTR ('e'<<8) | 0 /* set DTR */
+#define TIOCCDTR ('e'<<8) | 1 /* clear DTR */
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA ('e'<<8) | 94 /* Read params */
+
+#define DIGI_SETA ('e'<<8) | 95 /* Set params */
+#define DIGI_SETAW ('e'<<8) | 96 /* Drain & set params */
+#define DIGI_SETAF ('e'<<8) | 97 /* Drain, flush & set params */
+
+#define DIGI_GETFLOW ('e'<<8) | 99 /* Get startc/stopc flow */
+ /* control characters */
+#define DIGI_SETFLOW ('e'<<8) | 100 /* Set startc/stopc flow */
+ /* control characters */
+#define DIGI_GETAFLOW ('e'<<8) | 101 /* Get Aux. startc/stopc */
+ /* flow control chars */
+#define DIGI_SETAFLOW ('e'<<8) | 102 /* Set Aux. startc/stopc */
+ /* flow control chars */
+
+#define DIGI_GETINFO ('e'<<8) | 103 /* Fill in digi_info */
+#define DIGI_POLLER ('e'<<8) | 104 /* Turn on/off poller */
+#define DIGI_INIT ('e'<<8) | 105 /* Allow things to run. */
+
+struct digiflow_struct
+{
+ unsigned char startc; /* flow cntl start char */
+ unsigned char stopc; /* flow cntl stop char */
+};
+
+typedef struct digiflow_struct digiflow_t;
+
+
+/************************************************************************
+ * Values for digi_flags
+ ************************************************************************/
+#define DIGI_IXON 0x0001 /* Handle IXON in the FEP */
+#define DIGI_FAST 0x0002 /* Fast baud rates */
+#define RTSPACE 0x0004 /* RTS input flow control */
+#define CTSPACE 0x0008 /* CTS output flow control */
+#define DSRPACE 0x0010 /* DSR output flow control */
+#define DCDPACE 0x0020 /* DCD output flow control */
+#define DTRPACE 0x0040 /* DTR input flow control */
+#define DIGI_FORCEDCD 0x0100 /* Force carrier */
+#define DIGI_ALTPIN 0x0200 /* Alternate RJ-45 pin config */
+#define DIGI_AIXON 0x0400 /* Aux flow control in fep */
+
+
+/************************************************************************
+ * Values for digiDload
+ ************************************************************************/
+#define NORMAL 0
+#define PCI_CTL 1
+
+#define SIZE8 0
+#define SIZE16 1
+#define SIZE32 2
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct
+{
+ unsigned short digi_flags; /* Flags (see above) */
+};
+
+typedef struct digi_struct digi_t;
+
+struct digi_info
+{
+ unsigned long board; /* Which board is this ? */
+ unsigned char status; /* Alive or dead */
+ unsigned char type; /* see epca.h */
+ unsigned char subtype; /* For future XEM, XR, etc ... */
+ unsigned short numports; /* Number of ports configured */
+ unsigned char *port; /* I/O Address */
+ unsigned char *membase; /* DPR Address */
+ unsigned char *version; /* For future ... */
+ unsigned short windowData; /* For future ... */
+} ;
diff --git a/drivers/char/digiFep1.h b/drivers/char/digiFep1.h
new file mode 100644
index 000000000000..c47d7fcb8400
--- /dev/null
+++ b/drivers/char/digiFep1.h
@@ -0,0 +1,136 @@
+
+#define CSTART 0x400L
+#define CMAX 0x800L
+#define ISTART 0x800L
+#define IMAX 0xC00L
+#define CIN 0xD10L
+#define GLOBAL 0xD10L
+#define EIN 0xD18L
+#define FEPSTAT 0xD20L
+#define CHANSTRUCT 0x1000L
+#define RXTXBUF 0x4000L
+
+
+struct global_data
+{
+ volatile ushort cin;
+ volatile ushort cout;
+ volatile ushort cstart;
+ volatile ushort cmax;
+ volatile ushort ein;
+ volatile ushort eout;
+ volatile ushort istart;
+ volatile ushort imax;
+};
+
+
+struct board_chan
+{
+ int filler1;
+ int filler2;
+ volatile ushort tseg;
+ volatile ushort tin;
+ volatile ushort tout;
+ volatile ushort tmax;
+
+ volatile ushort rseg;
+ volatile ushort rin;
+ volatile ushort rout;
+ volatile ushort rmax;
+
+ volatile ushort tlow;
+ volatile ushort rlow;
+ volatile ushort rhigh;
+ volatile ushort incr;
+
+ volatile ushort etime;
+ volatile ushort edelay;
+ volatile unchar *dev;
+
+ volatile ushort iflag;
+ volatile ushort oflag;
+ volatile ushort cflag;
+ volatile ushort gmask;
+
+ volatile ushort col;
+ volatile ushort delay;
+ volatile ushort imask;
+ volatile ushort tflush;
+
+ int filler3;
+ int filler4;
+ int filler5;
+ int filler6;
+
+ volatile unchar num;
+ volatile unchar ract;
+ volatile unchar bstat;
+ volatile unchar tbusy;
+ volatile unchar iempty;
+ volatile unchar ilow;
+ volatile unchar idata;
+ volatile unchar eflag;
+
+ volatile unchar tflag;
+ volatile unchar rflag;
+ volatile unchar xmask;
+ volatile unchar xval;
+ volatile unchar mstat;
+ volatile unchar mchange;
+ volatile unchar mint;
+ volatile unchar lstat;
+
+ volatile unchar mtran;
+ volatile unchar orun;
+ volatile unchar startca;
+ volatile unchar stopca;
+ volatile unchar startc;
+ volatile unchar stopc;
+ volatile unchar vnext;
+ volatile unchar hflow;
+
+ volatile unchar fillc;
+ volatile unchar ochar;
+ volatile unchar omask;
+
+ unchar filler7;
+ unchar filler8[28];
+};
+
+
+#define SRXLWATER 0xE0
+#define SRXHWATER 0xE1
+#define STOUT 0xE2
+#define PAUSETX 0xE3
+#define RESUMETX 0xE4
+#define SAUXONOFFC 0xE6
+#define SENDBREAK 0xE8
+#define SETMODEM 0xE9
+#define SETIFLAGS 0xEA
+#define SONOFFC 0xEB
+#define STXLWATER 0xEC
+#define PAUSERX 0xEE
+#define RESUMERX 0xEF
+#define SETBUFFER 0xF2
+#define SETCOOKED 0xF3
+#define SETHFLOW 0xF4
+#define SETCTRLFLAGS 0xF5
+#define SETVNEXT 0xF6
+
+
+
+#define BREAK_IND 0x01
+#define LOWTX_IND 0x02
+#define EMPTYTX_IND 0x04
+#define DATA_IND 0x08
+#define MODEMCHG_IND 0x20
+
+#define FEP_HUPCL 0002000
+#if 0
+#define RTS 0x02
+#define CD 0x08
+#define DSR 0x10
+#define CTS 0x20
+#define RI 0x40
+#define DTR 0x80
+#endif
diff --git a/drivers/char/digiPCI.h b/drivers/char/digiPCI.h
new file mode 100644
index 000000000000..6ca7819e5069
--- /dev/null
+++ b/drivers/char/digiPCI.h
@@ -0,0 +1,42 @@
+/*************************************************************************
+ * Defines and structure definitions for PCI BIOS Interface
+ *************************************************************************/
+#define PCIMAX 32 /* maximum number of PCI boards */
+
+
+#define PCI_VENDOR_DIGI 0x114F
+#define PCI_DEVICE_EPC 0x0002
+#define PCI_DEVICE_RIGHTSWITCH 0x0003 /* For testing */
+#define PCI_DEVICE_XEM 0x0004
+#define PCI_DEVICE_XR 0x0005
+#define PCI_DEVICE_CX 0x0006
+#define PCI_DEVICE_XRJ 0x0009 /* Jupiter boards with */
+#define PCI_DEVICE_EPCJ 0x000a /* PLX 9060 chip for PCI */
+
+
+/*
+ * On the PCI boards, there is no IO space allocated
+ * The I/O registers will be in the first 3 bytes of the
+ * upper 2MB of the 4MB memory space. The board memory
+ * will be mapped into the low 2MB of the 4MB memory space
+ */
+
+/* Potential location of PCI Bios from E0000 to FFFFF*/
+#define PCI_BIOS_SIZE 0x00020000
+
+/* Size of Memory and I/O for PCI (4MB) */
+#define PCI_RAM_SIZE 0x00400000
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE 0x00200000
+
+/* Offset of I/0 in Memory (2MB) */
+#define PCI_IO_OFFSET 0x00200000
+
+#define MEMOUTB(basemem, pnum, setmemval) *(caddr_t)((basemem) + ( PCI_IO_OFFSET | pnum << 4 | pnum )) = (setmemval)
+#define MEMINB(basemem, pnum) *(caddr_t)((basemem) + (PCI_IO_OFFSET | pnum << 4 | pnum )) /* for PCI I/O */
+
+
+
+
+
diff --git a/drivers/char/drm/Kconfig b/drivers/char/drm/Kconfig
new file mode 100644
index 000000000000..d9a029934678
--- /dev/null
+++ b/drivers/char/drm/Kconfig
@@ -0,0 +1,98 @@
+#
+# Drm device configuration
+#
+# This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+#
+config DRM
+ tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"
+ depends on AGP || AGP=n
+ help
+ Kernel-level support for the Direct Rendering Infrastructure (DRI)
+ introduced in XFree86 4.0. If you say Y here, you need to select
+ the module that's right for your graphics card from the list below.
+ These modules provide support for synchronization, security, and
+ DMA transfers. Please see <http://dri.sourceforge.net/> for more
+ details. You should also select and configure AGP
+ (/dev/agpgart) support.
+
+config DRM_TDFX
+ tristate "3dfx Banshee/Voodoo3+"
+ depends on DRM && PCI
+ help
+ Choose this option if you have a 3dfx Banshee or Voodoo3 (or later),
+ graphics card. If M is selected, the module will be called tdfx.
+
+config DRM_GAMMA
+ tristate "3dlabs GMX 2000"
+ depends on DRM && BROKEN
+ help
+ This is the old gamma driver, please tell me if it might actually
+ work.
+
+config DRM_R128
+ tristate "ATI Rage 128"
+ depends on DRM && PCI
+ help
+ Choose this option if you have an ATI Rage 128 graphics card. If M
+ is selected, the module will be called r128. AGP support for
+ this card is strongly suggested (unless you have a PCI version).
+
+config DRM_RADEON
+ tristate "ATI Radeon"
+ depends on DRM && PCI
+ help
+ Choose this option if you have an ATI Radeon graphics card. There
+ are both PCI and AGP versions. You don't need to choose this to
+ run the Radeon in plain VGA mode. There is a product page at
+ <http://www.ati.com/na/pages/products/pc/radeon32/index.html>.
+ If M is selected, the module will be called radeon.
+
+config DRM_I810
+ tristate "Intel I810"
+ depends on DRM && AGP && AGP_INTEL
+ help
+ Choose this option if you have an Intel I810 graphics card. If M is
+ selected, the module will be called i810. AGP support is required
+ for this driver to work.
+
+choice
+ prompt "Intel 830M, 845G, 852GM, 855GM, 865G"
+ depends on DRM && AGP && AGP_INTEL
+ optional
+
+config DRM_I830
+ tristate "i830 driver"
+ help
+ Choose this option if you have a system that has Intel 830M, 845G,
+ 852GM, 855GM or 865G integrated graphics. If M is selected, the
+ module will be called i830. AGP support is required for this driver
+ to work. This driver will eventually be replaced by the i915 one.
+
+config DRM_I915
+ tristate "i915 driver"
+ help
+ Choose this option if you have a system that has Intel 830M, 845G,
+ 852GM, 855GM 865G or 915G integrated graphics. If M is selected, the
+ module will be called i915. AGP support is required for this driver
+ to work. This driver will eventually replace the I830 driver, when
+ later release of X start to use the new DDX and DRI.
+
+endchoice
+
+config DRM_MGA
+ tristate "Matrox g200/g400"
+ depends on DRM && AGP
+ help
+ Choose this option if you have a Matrox G200, G400 or G450 graphics
+ card. If M is selected, the module will be called mga. AGP
+ support is required for this driver to work.
+
+config DRM_SIS
+ tristate "SiS video cards"
+ depends on DRM && AGP
+ help
+ Choose this option if you have a SiS 630 or compatible video
+ chipset. If M is selected the module will be called sis. AGP
+ support is required for this driver to work.
+
diff --git a/drivers/char/drm/Makefile b/drivers/char/drm/Makefile
new file mode 100644
index 000000000000..23ab26321e9a
--- /dev/null
+++ b/drivers/char/drm/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for the drm device driver. This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+drm-objs := drm_auth.o drm_bufs.o drm_context.o drm_dma.o drm_drawable.o \
+ drm_drv.o drm_fops.o drm_init.o drm_ioctl.o drm_irq.o \
+ drm_lock.o drm_memory.o drm_proc.o drm_stub.o drm_vm.o \
+ drm_agpsupport.o drm_scatter.o ati_pcigart.o drm_pci.o \
+ drm_sysfs.o
+
+gamma-objs := gamma_drv.o gamma_dma.o
+tdfx-objs := tdfx_drv.o
+r128-objs := r128_drv.o r128_cce.o r128_state.o r128_irq.o
+mga-objs := mga_drv.o mga_dma.o mga_state.o mga_warp.o mga_irq.o
+i810-objs := i810_drv.o i810_dma.o
+i830-objs := i830_drv.o i830_dma.o i830_irq.o
+i915-objs := i915_drv.o i915_dma.o i915_irq.o i915_mem.o
+radeon-objs := radeon_drv.o radeon_cp.o radeon_state.o radeon_mem.o radeon_irq.o
+ffb-objs := ffb_drv.o ffb_context.o
+sis-objs := sis_drv.o sis_ds.o sis_mm.o
+
+obj-$(CONFIG_DRM) += drm.o
+obj-$(CONFIG_DRM_GAMMA) += gamma.o
+obj-$(CONFIG_DRM_TDFX) += tdfx.o
+obj-$(CONFIG_DRM_R128) += r128.o
+obj-$(CONFIG_DRM_RADEON)+= radeon.o
+obj-$(CONFIG_DRM_MGA) += mga.o
+obj-$(CONFIG_DRM_I810) += i810.o
+obj-$(CONFIG_DRM_I830) += i830.o
+obj-$(CONFIG_DRM_I915) += i915.o
+obj-$(CONFIG_DRM_FFB) += ffb.o
+obj-$(CONFIG_DRM_SIS) += sis.o
+
diff --git a/drivers/char/drm/README.drm b/drivers/char/drm/README.drm
new file mode 100644
index 000000000000..6441e01e587c
--- /dev/null
+++ b/drivers/char/drm/README.drm
@@ -0,0 +1,46 @@
+************************************************************
+* For the very latest on DRI development, please see: *
+* http://dri.sourceforge.net/ *
+************************************************************
+
+The Direct Rendering Manager (drm) is a device-independent kernel-level
+device driver that provides support for the XFree86 Direct Rendering
+Infrastructure (DRI).
+
+The DRM supports the Direct Rendering Infrastructure (DRI) in four major
+ways:
+
+ 1. The DRM provides synchronized access to the graphics hardware via
+ the use of an optimized two-tiered lock.
+
+ 2. The DRM enforces the DRI security policy for access to the graphics
+ hardware by only allowing authenticated X11 clients access to
+ restricted regions of memory.
+
+ 3. The DRM provides a generic DMA engine, complete with multiple
+ queues and the ability to detect the need for an OpenGL context
+ switch.
+
+ 4. The DRM is extensible via the use of small device-specific modules
+ that rely extensively on the API exported by the DRM module.
+
+
+Documentation on the DRI is available from:
+ http://precisioninsight.com/piinsights.html
+
+For specific information about kernel-level support, see:
+
+ The Direct Rendering Manager, Kernel Support for the Direct Rendering
+ Infrastructure
+ http://precisioninsight.com/dr/drm.html
+
+ Hardware Locking for the Direct Rendering Infrastructure
+ http://precisioninsight.com/dr/locking.html
+
+ A Security Analysis of the Direct Rendering Infrastructure
+ http://precisioninsight.com/dr/security.html
+
+************************************************************
+* For the very latest on DRI development, please see: *
+* http://dri.sourceforge.net/ *
+************************************************************
diff --git a/drivers/char/drm/ati_pcigart.c b/drivers/char/drm/ati_pcigart.c
new file mode 100644
index 000000000000..fdca1876ecd5
--- /dev/null
+++ b/drivers/char/drm/ati_pcigart.c
@@ -0,0 +1,208 @@
+/**
+ * \file ati_pcigart.h
+ * ATI PCI GART support
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Wed Dec 13 21:52:19 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+#if PAGE_SIZE == 65536
+# define ATI_PCIGART_TABLE_ORDER 0
+# define ATI_PCIGART_TABLE_PAGES (1 << 0)
+#elif PAGE_SIZE == 16384
+# define ATI_PCIGART_TABLE_ORDER 1
+# define ATI_PCIGART_TABLE_PAGES (1 << 1)
+#elif PAGE_SIZE == 8192
+# define ATI_PCIGART_TABLE_ORDER 2
+# define ATI_PCIGART_TABLE_PAGES (1 << 2)
+#elif PAGE_SIZE == 4096
+# define ATI_PCIGART_TABLE_ORDER 3
+# define ATI_PCIGART_TABLE_PAGES (1 << 3)
+#else
+# error - PAGE_SIZE not 64K, 16K, 8K or 4K
+#endif
+
+# define ATI_MAX_PCIGART_PAGES 8192 /**< 32 MB aperture, 4K pages */
+# define ATI_PCIGART_PAGE_SIZE 4096 /**< PCI GART page size */
+
+unsigned long drm_ati_alloc_pcigart_table( void )
+{
+ unsigned long address;
+ struct page *page;
+ int i;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ address = __get_free_pages( GFP_KERNEL, ATI_PCIGART_TABLE_ORDER );
+ if ( address == 0UL ) {
+ return 0;
+ }
+
+ page = virt_to_page( address );
+
+ for ( i = 0 ; i < ATI_PCIGART_TABLE_PAGES ; i++, page++ ) {
+ get_page(page);
+ SetPageReserved( page );
+ }
+
+ DRM_DEBUG( "%s: returning 0x%08lx\n", __FUNCTION__, address );
+ return address;
+}
+
+static void drm_ati_free_pcigart_table( unsigned long address )
+{
+ struct page *page;
+ int i;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ page = virt_to_page( address );
+
+ for ( i = 0 ; i < ATI_PCIGART_TABLE_PAGES ; i++, page++ ) {
+ __put_page(page);
+ ClearPageReserved( page );
+ }
+
+ free_pages( address, ATI_PCIGART_TABLE_ORDER );
+}
+
+int drm_ati_pcigart_cleanup( drm_device_t *dev,
+ unsigned long addr,
+ dma_addr_t bus_addr)
+{
+ drm_sg_mem_t *entry = dev->sg;
+ unsigned long pages;
+ int i;
+
+ /* we need to support large memory configurations */
+ if ( !entry ) {
+ DRM_ERROR( "no scatter/gather memory!\n" );
+ return 0;
+ }
+
+ if ( bus_addr ) {
+ pci_unmap_single(dev->pdev, bus_addr,
+ ATI_PCIGART_TABLE_PAGES * PAGE_SIZE,
+ PCI_DMA_TODEVICE);
+
+ pages = ( entry->pages <= ATI_MAX_PCIGART_PAGES )
+ ? entry->pages : ATI_MAX_PCIGART_PAGES;
+
+ for ( i = 0 ; i < pages ; i++ ) {
+ if ( !entry->busaddr[i] ) break;
+ pci_unmap_single(dev->pdev, entry->busaddr[i],
+ PAGE_SIZE, PCI_DMA_TODEVICE);
+ }
+ }
+
+ if ( addr ) {
+ drm_ati_free_pcigart_table( addr );
+ }
+
+ return 1;
+}
+EXPORT_SYMBOL(drm_ati_pcigart_cleanup);
+
+int drm_ati_pcigart_init( drm_device_t *dev,
+ unsigned long *addr,
+ dma_addr_t *bus_addr)
+{
+ drm_sg_mem_t *entry = dev->sg;
+ unsigned long address = 0;
+ unsigned long pages;
+ u32 *pci_gart, page_base, bus_address = 0;
+ int i, j, ret = 0;
+
+ if ( !entry ) {
+ DRM_ERROR( "no scatter/gather memory!\n" );
+ goto done;
+ }
+
+ address = drm_ati_alloc_pcigart_table();
+ if ( !address ) {
+ DRM_ERROR( "cannot allocate PCI GART page!\n" );
+ goto done;
+ }
+
+ if ( !dev->pdev ) {
+ DRM_ERROR( "PCI device unknown!\n" );
+ goto done;
+ }
+
+ bus_address = pci_map_single(dev->pdev, (void *)address,
+ ATI_PCIGART_TABLE_PAGES * PAGE_SIZE,
+ PCI_DMA_TODEVICE);
+ if (bus_address == 0) {
+ DRM_ERROR( "unable to map PCIGART pages!\n" );
+ drm_ati_free_pcigart_table( address );
+ address = 0;
+ goto done;
+ }
+
+ pci_gart = (u32 *)address;
+
+ pages = ( entry->pages <= ATI_MAX_PCIGART_PAGES )
+ ? entry->pages : ATI_MAX_PCIGART_PAGES;
+
+ memset( pci_gart, 0, ATI_MAX_PCIGART_PAGES * sizeof(u32) );
+
+ for ( i = 0 ; i < pages ; i++ ) {
+ /* we need to support large memory configurations */
+ entry->busaddr[i] = pci_map_single(dev->pdev,
+ page_address( entry->pagelist[i] ),
+ PAGE_SIZE,
+ PCI_DMA_TODEVICE);
+ if (entry->busaddr[i] == 0) {
+ DRM_ERROR( "unable to map PCIGART pages!\n" );
+ drm_ati_pcigart_cleanup( dev, address, bus_address );
+ address = 0;
+ bus_address = 0;
+ goto done;
+ }
+ page_base = (u32) entry->busaddr[i];
+
+ for (j = 0; j < (PAGE_SIZE / ATI_PCIGART_PAGE_SIZE); j++) {
+ *pci_gart++ = cpu_to_le32( page_base );
+ page_base += ATI_PCIGART_PAGE_SIZE;
+ }
+ }
+
+ ret = 1;
+
+#if defined(__i386__) || defined(__x86_64__)
+ wbinvd();
+#else
+ mb();
+#endif
+
+done:
+ *addr = address;
+ *bus_addr = bus_address;
+ return ret;
+}
+EXPORT_SYMBOL(drm_ati_pcigart_init);
diff --git a/drivers/char/drm/drm.h b/drivers/char/drm/drm.h
new file mode 100644
index 000000000000..587305282ea8
--- /dev/null
+++ b/drivers/char/drm/drm.h
@@ -0,0 +1,675 @@
+/**
+ * \file drm.h
+ * Header for the Direct Rendering Manager
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ * \par Acknowledgments:
+ * Dec 1999, Richard Henderson <rth@twiddle.net>, move to generic \c cmpxchg.
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#ifndef _DRM_H_
+#define _DRM_H_
+
+#if defined(__linux__)
+#include <linux/config.h>
+#include <asm/ioctl.h> /* For _IO* macros */
+#define DRM_IOCTL_NR(n) _IOC_NR(n)
+#define DRM_IOC_VOID _IOC_NONE
+#define DRM_IOC_READ _IOC_READ
+#define DRM_IOC_WRITE _IOC_WRITE
+#define DRM_IOC_READWRITE _IOC_READ|_IOC_WRITE
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#if defined(__FreeBSD__) && defined(IN_MODULE)
+/* Prevent name collision when including sys/ioccom.h */
+#undef ioctl
+#include <sys/ioccom.h>
+#define ioctl(a,b,c) xf86ioctl(a,b,c)
+#else
+#include <sys/ioccom.h>
+#endif /* __FreeBSD__ && xf86ioctl */
+#define DRM_IOCTL_NR(n) ((n) & 0xff)
+#define DRM_IOC_VOID IOC_VOID
+#define DRM_IOC_READ IOC_OUT
+#define DRM_IOC_WRITE IOC_IN
+#define DRM_IOC_READWRITE IOC_INOUT
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#endif
+
+#define XFREE86_VERSION(major,minor,patch,snap) \
+ ((major << 16) | (minor << 8) | patch)
+
+#ifndef CONFIG_XFREE86_VERSION
+#define CONFIG_XFREE86_VERSION XFREE86_VERSION(4,1,0,0)
+#endif
+
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+#define DRM_PROC_DEVICES "/proc/devices"
+#define DRM_PROC_MISC "/proc/misc"
+#define DRM_PROC_DRM "/proc/drm"
+#define DRM_DEV_DRM "/dev/drm"
+#define DRM_DEV_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+#define DRM_DEV_UID 0
+#define DRM_DEV_GID 0
+#endif
+
+#if CONFIG_XFREE86_VERSION >= XFREE86_VERSION(4,1,0,0)
+#define DRM_MAJOR 226
+#define DRM_MAX_MINOR 15
+#endif
+#define DRM_NAME "drm" /**< Name in kernel, /dev, and /proc */
+#define DRM_MIN_ORDER 5 /**< At least 2^5 bytes = 32 bytes */
+#define DRM_MAX_ORDER 22 /**< Up to 2^22 bytes = 4MB */
+#define DRM_RAM_PERCENT 10 /**< How much system ram can we lock? */
+
+#define _DRM_LOCK_HELD 0x80000000 /**< Hardware lock is held */
+#define _DRM_LOCK_CONT 0x40000000 /**< Hardware lock is contended */
+#define _DRM_LOCK_IS_HELD(lock) ((lock) & _DRM_LOCK_HELD)
+#define _DRM_LOCK_IS_CONT(lock) ((lock) & _DRM_LOCK_CONT)
+#define _DRM_LOCKING_CONTEXT(lock) ((lock) & ~(_DRM_LOCK_HELD|_DRM_LOCK_CONT))
+
+
+typedef unsigned long drm_handle_t;
+typedef unsigned int drm_context_t;
+typedef unsigned int drm_drawable_t;
+typedef unsigned int drm_magic_t;
+
+
+/**
+ * Cliprect.
+ *
+ * \warning: If you change this structure, make sure you change
+ * XF86DRIClipRectRec in the server as well
+ *
+ * \note KW: Actually it's illegal to change either for
+ * backwards-compatibility reasons.
+ */
+typedef struct drm_clip_rect {
+ unsigned short x1;
+ unsigned short y1;
+ unsigned short x2;
+ unsigned short y2;
+} drm_clip_rect_t;
+
+
+/**
+ * Texture region,
+ */
+typedef struct drm_tex_region {
+ unsigned char next;
+ unsigned char prev;
+ unsigned char in_use;
+ unsigned char padding;
+ unsigned int age;
+} drm_tex_region_t;
+
+/**
+ * Hardware lock.
+ *
+ * The lock structure is a simple cache-line aligned integer. To avoid
+ * processor bus contention on a multiprocessor system, there should not be any
+ * other data stored in the same cache line.
+ */
+typedef struct drm_hw_lock {
+ __volatile__ unsigned int lock; /**< lock variable */
+ char padding[60]; /**< Pad to cache line */
+} drm_hw_lock_t;
+
+
+/**
+ * DRM_IOCTL_VERSION ioctl argument type.
+ *
+ * \sa drmGetVersion().
+ */
+typedef struct drm_version {
+ int version_major; /**< Major version */
+ int version_minor; /**< Minor version */
+ int version_patchlevel;/**< Patch level */
+ size_t name_len; /**< Length of name buffer */
+ char __user *name; /**< Name of driver */
+ size_t date_len; /**< Length of date buffer */
+ char __user *date; /**< User-space buffer to hold date */
+ size_t desc_len; /**< Length of desc buffer */
+ char __user *desc; /**< User-space buffer to hold desc */
+} drm_version_t;
+
+
+/**
+ * DRM_IOCTL_GET_UNIQUE ioctl argument type.
+ *
+ * \sa drmGetBusid() and drmSetBusId().
+ */
+typedef struct drm_unique {
+ size_t unique_len; /**< Length of unique */
+ char __user *unique; /**< Unique name for driver instantiation */
+} drm_unique_t;
+
+
+typedef struct drm_list {
+ int count; /**< Length of user-space structures */
+ drm_version_t __user *version;
+} drm_list_t;
+
+
+typedef struct drm_block {
+ int unused;
+} drm_block_t;
+
+
+/**
+ * DRM_IOCTL_CONTROL ioctl argument type.
+ *
+ * \sa drmCtlInstHandler() and drmCtlUninstHandler().
+ */
+typedef struct drm_control {
+ enum {
+ DRM_ADD_COMMAND,
+ DRM_RM_COMMAND,
+ DRM_INST_HANDLER,
+ DRM_UNINST_HANDLER
+ } func;
+ int irq;
+} drm_control_t;
+
+
+/**
+ * Type of memory to map.
+ */
+typedef enum drm_map_type {
+ _DRM_FRAME_BUFFER = 0, /**< WC (no caching), no core dump */
+ _DRM_REGISTERS = 1, /**< no caching, no core dump */
+ _DRM_SHM = 2, /**< shared, cached */
+ _DRM_AGP = 3, /**< AGP/GART */
+ _DRM_SCATTER_GATHER = 4 /**< Scatter/gather memory for PCI DMA */
+} drm_map_type_t;
+
+
+/**
+ * Memory mapping flags.
+ */
+typedef enum drm_map_flags {
+ _DRM_RESTRICTED = 0x01, /**< Cannot be mapped to user-virtual */
+ _DRM_READ_ONLY = 0x02,
+ _DRM_LOCKED = 0x04, /**< shared, cached, locked */
+ _DRM_KERNEL = 0x08, /**< kernel requires access */
+ _DRM_WRITE_COMBINING = 0x10, /**< use write-combining if available */
+ _DRM_CONTAINS_LOCK = 0x20, /**< SHM page that contains lock */
+ _DRM_REMOVABLE = 0x40 /**< Removable mapping */
+} drm_map_flags_t;
+
+
+typedef struct drm_ctx_priv_map {
+ unsigned int ctx_id; /**< Context requesting private mapping */
+ void *handle; /**< Handle of map */
+} drm_ctx_priv_map_t;
+
+
+/**
+ * DRM_IOCTL_GET_MAP, DRM_IOCTL_ADD_MAP and DRM_IOCTL_RM_MAP ioctls
+ * argument type.
+ *
+ * \sa drmAddMap().
+ */
+typedef struct drm_map {
+ unsigned long offset; /**< Requested physical address (0 for SAREA)*/
+ unsigned long size; /**< Requested physical size (bytes) */
+ drm_map_type_t type; /**< Type of memory to map */
+ drm_map_flags_t flags; /**< Flags */
+ void *handle; /**< User-space: "Handle" to pass to mmap() */
+ /**< Kernel-space: kernel-virtual address */
+ int mtrr; /**< MTRR slot used */
+ /* Private data */
+} drm_map_t;
+
+
+/**
+ * DRM_IOCTL_GET_CLIENT ioctl argument type.
+ */
+typedef struct drm_client {
+ int idx; /**< Which client desired? */
+ int auth; /**< Is client authenticated? */
+ unsigned long pid; /**< Process ID */
+ unsigned long uid; /**< User ID */
+ unsigned long magic; /**< Magic */
+ unsigned long iocs; /**< Ioctl count */
+} drm_client_t;
+
+
+typedef enum {
+ _DRM_STAT_LOCK,
+ _DRM_STAT_OPENS,
+ _DRM_STAT_CLOSES,
+ _DRM_STAT_IOCTLS,
+ _DRM_STAT_LOCKS,
+ _DRM_STAT_UNLOCKS,
+ _DRM_STAT_VALUE, /**< Generic value */
+ _DRM_STAT_BYTE, /**< Generic byte counter (1024bytes/K) */
+ _DRM_STAT_COUNT, /**< Generic non-byte counter (1000/k) */
+
+ _DRM_STAT_IRQ, /**< IRQ */
+ _DRM_STAT_PRIMARY, /**< Primary DMA bytes */
+ _DRM_STAT_SECONDARY, /**< Secondary DMA bytes */
+ _DRM_STAT_DMA, /**< DMA */
+ _DRM_STAT_SPECIAL, /**< Special DMA (e.g., priority or polled) */
+ _DRM_STAT_MISSED /**< Missed DMA opportunity */
+
+ /* Add to the *END* of the list */
+} drm_stat_type_t;
+
+
+/**
+ * DRM_IOCTL_GET_STATS ioctl argument type.
+ */
+typedef struct drm_stats {
+ unsigned long count;
+ struct {
+ unsigned long value;
+ drm_stat_type_t type;
+ } data[15];
+} drm_stats_t;
+
+
+/**
+ * Hardware locking flags.
+ */
+typedef enum drm_lock_flags {
+ _DRM_LOCK_READY = 0x01, /**< Wait until hardware is ready for DMA */
+ _DRM_LOCK_QUIESCENT = 0x02, /**< Wait until hardware quiescent */
+ _DRM_LOCK_FLUSH = 0x04, /**< Flush this context's DMA queue first */
+ _DRM_LOCK_FLUSH_ALL = 0x08, /**< Flush all DMA queues first */
+ /* These *HALT* flags aren't supported yet
+ -- they will be used to support the
+ full-screen DGA-like mode. */
+ _DRM_HALT_ALL_QUEUES = 0x10, /**< Halt all current and future queues */
+ _DRM_HALT_CUR_QUEUES = 0x20 /**< Halt all current queues */
+} drm_lock_flags_t;
+
+
+/**
+ * DRM_IOCTL_LOCK, DRM_IOCTL_UNLOCK and DRM_IOCTL_FINISH ioctl argument type.
+ *
+ * \sa drmGetLock() and drmUnlock().
+ */
+typedef struct drm_lock {
+ int context;
+ drm_lock_flags_t flags;
+} drm_lock_t;
+
+
+/**
+ * DMA flags
+ *
+ * \warning
+ * These values \e must match xf86drm.h.
+ *
+ * \sa drm_dma.
+ */
+typedef enum drm_dma_flags {
+ /* Flags for DMA buffer dispatch */
+ _DRM_DMA_BLOCK = 0x01, /**<
+ * Block until buffer dispatched.
+ *
+ * \note The buffer may not yet have
+ * been processed by the hardware --
+ * getting a hardware lock with the
+ * hardware quiescent will ensure
+ * that the buffer has been
+ * processed.
+ */
+ _DRM_DMA_WHILE_LOCKED = 0x02, /**< Dispatch while lock held */
+ _DRM_DMA_PRIORITY = 0x04, /**< High priority dispatch */
+
+ /* Flags for DMA buffer request */
+ _DRM_DMA_WAIT = 0x10, /**< Wait for free buffers */
+ _DRM_DMA_SMALLER_OK = 0x20, /**< Smaller-than-requested buffers OK */
+ _DRM_DMA_LARGER_OK = 0x40 /**< Larger-than-requested buffers OK */
+} drm_dma_flags_t;
+
+
+/**
+ * DRM_IOCTL_ADD_BUFS and DRM_IOCTL_MARK_BUFS ioctl argument type.
+ *
+ * \sa drmAddBufs().
+ */
+typedef struct drm_buf_desc {
+ int count; /**< Number of buffers of this size */
+ int size; /**< Size in bytes */
+ int low_mark; /**< Low water mark */
+ int high_mark; /**< High water mark */
+ enum {
+ _DRM_PAGE_ALIGN = 0x01, /**< Align on page boundaries for DMA */
+ _DRM_AGP_BUFFER = 0x02, /**< Buffer is in AGP space */
+ _DRM_SG_BUFFER = 0x04 /**< Scatter/gather memory buffer */
+ } flags;
+ unsigned long agp_start; /**<
+ * Start address of where the AGP buffers are
+ * in the AGP aperture
+ */
+} drm_buf_desc_t;
+
+
+/**
+ * DRM_IOCTL_INFO_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_info {
+ int count; /**< Entries in list */
+ drm_buf_desc_t __user *list;
+} drm_buf_info_t;
+
+
+/**
+ * DRM_IOCTL_FREE_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_free {
+ int count;
+ int __user *list;
+} drm_buf_free_t;
+
+
+/**
+ * Buffer information
+ *
+ * \sa drm_buf_map.
+ */
+typedef struct drm_buf_pub {
+ int idx; /**< Index into the master buffer list */
+ int total; /**< Buffer size */
+ int used; /**< Amount of buffer in use (for DMA) */
+ void __user *address; /**< Address of buffer */
+} drm_buf_pub_t;
+
+
+/**
+ * DRM_IOCTL_MAP_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_map {
+ int count; /**< Length of the buffer list */
+ void __user *virtual; /**< Mmap'd area in user-virtual */
+ drm_buf_pub_t __user *list; /**< Buffer information */
+} drm_buf_map_t;
+
+
+/**
+ * DRM_IOCTL_DMA ioctl argument type.
+ *
+ * Indices here refer to the offset into the buffer list in drm_buf_get.
+ *
+ * \sa drmDMA().
+ */
+typedef struct drm_dma {
+ int context; /**< Context handle */
+ int send_count; /**< Number of buffers to send */
+ int __user *send_indices; /**< List of handles to buffers */
+ int __user *send_sizes; /**< Lengths of data to send */
+ drm_dma_flags_t flags; /**< Flags */
+ int request_count; /**< Number of buffers requested */
+ int request_size; /**< Desired size for buffers */
+ int __user *request_indices; /**< Buffer information */
+ int __user *request_sizes;
+ int granted_count; /**< Number of buffers granted */
+} drm_dma_t;
+
+
+typedef enum {
+ _DRM_CONTEXT_PRESERVED = 0x01,
+ _DRM_CONTEXT_2DONLY = 0x02
+} drm_ctx_flags_t;
+
+
+/**
+ * DRM_IOCTL_ADD_CTX ioctl argument type.
+ *
+ * \sa drmCreateContext() and drmDestroyContext().
+ */
+typedef struct drm_ctx {
+ drm_context_t handle;
+ drm_ctx_flags_t flags;
+} drm_ctx_t;
+
+
+/**
+ * DRM_IOCTL_RES_CTX ioctl argument type.
+ */
+typedef struct drm_ctx_res {
+ int count;
+ drm_ctx_t __user *contexts;
+} drm_ctx_res_t;
+
+
+/**
+ * DRM_IOCTL_ADD_DRAW and DRM_IOCTL_RM_DRAW ioctl argument type.
+ */
+typedef struct drm_draw {
+ drm_drawable_t handle;
+} drm_draw_t;
+
+
+/**
+ * DRM_IOCTL_GET_MAGIC and DRM_IOCTL_AUTH_MAGIC ioctl argument type.
+ */
+typedef struct drm_auth {
+ drm_magic_t magic;
+} drm_auth_t;
+
+
+/**
+ * DRM_IOCTL_IRQ_BUSID ioctl argument type.
+ *
+ * \sa drmGetInterruptFromBusID().
+ */
+typedef struct drm_irq_busid {
+ int irq; /**< IRQ number */
+ int busnum; /**< bus number */
+ int devnum; /**< device number */
+ int funcnum; /**< function number */
+} drm_irq_busid_t;
+
+
+typedef enum {
+ _DRM_VBLANK_ABSOLUTE = 0x0, /**< Wait for specific vblank sequence number */
+ _DRM_VBLANK_RELATIVE = 0x1, /**< Wait for given number of vblanks */
+ _DRM_VBLANK_SIGNAL = 0x40000000 /**< Send signal instead of blocking */
+} drm_vblank_seq_type_t;
+
+
+#define _DRM_VBLANK_FLAGS_MASK _DRM_VBLANK_SIGNAL
+
+
+struct drm_wait_vblank_request {
+ drm_vblank_seq_type_t type;
+ unsigned int sequence;
+ unsigned long signal;
+};
+
+
+struct drm_wait_vblank_reply {
+ drm_vblank_seq_type_t type;
+ unsigned int sequence;
+ long tval_sec;
+ long tval_usec;
+};
+
+
+/**
+ * DRM_IOCTL_WAIT_VBLANK ioctl argument type.
+ *
+ * \sa drmWaitVBlank().
+ */
+typedef union drm_wait_vblank {
+ struct drm_wait_vblank_request request;
+ struct drm_wait_vblank_reply reply;
+} drm_wait_vblank_t;
+
+
+/**
+ * DRM_IOCTL_AGP_ENABLE ioctl argument type.
+ *
+ * \sa drmAgpEnable().
+ */
+typedef struct drm_agp_mode {
+ unsigned long mode; /**< AGP mode */
+} drm_agp_mode_t;
+
+
+/**
+ * DRM_IOCTL_AGP_ALLOC and DRM_IOCTL_AGP_FREE ioctls argument type.
+ *
+ * \sa drmAgpAlloc() and drmAgpFree().
+ */
+typedef struct drm_agp_buffer {
+ unsigned long size; /**< In bytes -- will round to page boundary */
+ unsigned long handle; /**< Used for binding / unbinding */
+ unsigned long type; /**< Type of memory to allocate */
+ unsigned long physical; /**< Physical used by i810 */
+} drm_agp_buffer_t;
+
+
+/**
+ * DRM_IOCTL_AGP_BIND and DRM_IOCTL_AGP_UNBIND ioctls argument type.
+ *
+ * \sa drmAgpBind() and drmAgpUnbind().
+ */
+typedef struct drm_agp_binding {
+ unsigned long handle; /**< From drm_agp_buffer */
+ unsigned long offset; /**< In bytes -- will round to page boundary */
+} drm_agp_binding_t;
+
+
+/**
+ * DRM_IOCTL_AGP_INFO ioctl argument type.
+ *
+ * \sa drmAgpVersionMajor(), drmAgpVersionMinor(), drmAgpGetMode(),
+ * drmAgpBase(), drmAgpSize(), drmAgpMemoryUsed(), drmAgpMemoryAvail(),
+ * drmAgpVendorId() and drmAgpDeviceId().
+ */
+typedef struct drm_agp_info {
+ int agp_version_major;
+ int agp_version_minor;
+ unsigned long mode;
+ unsigned long aperture_base; /* physical address */
+ unsigned long aperture_size; /* bytes */
+ unsigned long memory_allowed; /* bytes */
+ unsigned long memory_used;
+
+ /* PCI information */
+ unsigned short id_vendor;
+ unsigned short id_device;
+} drm_agp_info_t;
+
+
+/**
+ * DRM_IOCTL_SG_ALLOC ioctl argument type.
+ */
+typedef struct drm_scatter_gather {
+ unsigned long size; /**< In bytes -- will round to page boundary */
+ unsigned long handle; /**< Used for mapping / unmapping */
+} drm_scatter_gather_t;
+
+/**
+ * DRM_IOCTL_SET_VERSION ioctl argument type.
+ */
+typedef struct drm_set_version {
+ int drm_di_major;
+ int drm_di_minor;
+ int drm_dd_major;
+ int drm_dd_minor;
+} drm_set_version_t;
+
+
+#define DRM_IOCTL_BASE 'd'
+#define DRM_IO(nr) _IO(DRM_IOCTL_BASE,nr)
+#define DRM_IOR(nr,type) _IOR(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOW(nr,type) _IOW(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
+
+#define DRM_IOCTL_VERSION DRM_IOWR(0x00, drm_version_t)
+#define DRM_IOCTL_GET_UNIQUE DRM_IOWR(0x01, drm_unique_t)
+#define DRM_IOCTL_GET_MAGIC DRM_IOR( 0x02, drm_auth_t)
+#define DRM_IOCTL_IRQ_BUSID DRM_IOWR(0x03, drm_irq_busid_t)
+#define DRM_IOCTL_GET_MAP DRM_IOWR(0x04, drm_map_t)
+#define DRM_IOCTL_GET_CLIENT DRM_IOWR(0x05, drm_client_t)
+#define DRM_IOCTL_GET_STATS DRM_IOR( 0x06, drm_stats_t)
+#define DRM_IOCTL_SET_VERSION DRM_IOWR(0x07, drm_set_version_t)
+
+#define DRM_IOCTL_SET_UNIQUE DRM_IOW( 0x10, drm_unique_t)
+#define DRM_IOCTL_AUTH_MAGIC DRM_IOW( 0x11, drm_auth_t)
+#define DRM_IOCTL_BLOCK DRM_IOWR(0x12, drm_block_t)
+#define DRM_IOCTL_UNBLOCK DRM_IOWR(0x13, drm_block_t)
+#define DRM_IOCTL_CONTROL DRM_IOW( 0x14, drm_control_t)
+#define DRM_IOCTL_ADD_MAP DRM_IOWR(0x15, drm_map_t)
+#define DRM_IOCTL_ADD_BUFS DRM_IOWR(0x16, drm_buf_desc_t)
+#define DRM_IOCTL_MARK_BUFS DRM_IOW( 0x17, drm_buf_desc_t)
+#define DRM_IOCTL_INFO_BUFS DRM_IOWR(0x18, drm_buf_info_t)
+#define DRM_IOCTL_MAP_BUFS DRM_IOWR(0x19, drm_buf_map_t)
+#define DRM_IOCTL_FREE_BUFS DRM_IOW( 0x1a, drm_buf_free_t)
+
+#define DRM_IOCTL_RM_MAP DRM_IOW( 0x1b, drm_map_t)
+
+#define DRM_IOCTL_SET_SAREA_CTX DRM_IOW( 0x1c, drm_ctx_priv_map_t)
+#define DRM_IOCTL_GET_SAREA_CTX DRM_IOWR(0x1d, drm_ctx_priv_map_t)
+
+#define DRM_IOCTL_ADD_CTX DRM_IOWR(0x20, drm_ctx_t)
+#define DRM_IOCTL_RM_CTX DRM_IOWR(0x21, drm_ctx_t)
+#define DRM_IOCTL_MOD_CTX DRM_IOW( 0x22, drm_ctx_t)
+#define DRM_IOCTL_GET_CTX DRM_IOWR(0x23, drm_ctx_t)
+#define DRM_IOCTL_SWITCH_CTX DRM_IOW( 0x24, drm_ctx_t)
+#define DRM_IOCTL_NEW_CTX DRM_IOW( 0x25, drm_ctx_t)
+#define DRM_IOCTL_RES_CTX DRM_IOWR(0x26, drm_ctx_res_t)
+#define DRM_IOCTL_ADD_DRAW DRM_IOWR(0x27, drm_draw_t)
+#define DRM_IOCTL_RM_DRAW DRM_IOWR(0x28, drm_draw_t)
+#define DRM_IOCTL_DMA DRM_IOWR(0x29, drm_dma_t)
+#define DRM_IOCTL_LOCK DRM_IOW( 0x2a, drm_lock_t)
+#define DRM_IOCTL_UNLOCK DRM_IOW( 0x2b, drm_lock_t)
+#define DRM_IOCTL_FINISH DRM_IOW( 0x2c, drm_lock_t)
+
+#define DRM_IOCTL_AGP_ACQUIRE DRM_IO( 0x30)
+#define DRM_IOCTL_AGP_RELEASE DRM_IO( 0x31)
+#define DRM_IOCTL_AGP_ENABLE DRM_IOW( 0x32, drm_agp_mode_t)
+#define DRM_IOCTL_AGP_INFO DRM_IOR( 0x33, drm_agp_info_t)
+#define DRM_IOCTL_AGP_ALLOC DRM_IOWR(0x34, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_FREE DRM_IOW( 0x35, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_BIND DRM_IOW( 0x36, drm_agp_binding_t)
+#define DRM_IOCTL_AGP_UNBIND DRM_IOW( 0x37, drm_agp_binding_t)
+
+#define DRM_IOCTL_SG_ALLOC DRM_IOW( 0x38, drm_scatter_gather_t)
+#define DRM_IOCTL_SG_FREE DRM_IOW( 0x39, drm_scatter_gather_t)
+
+#define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, drm_wait_vblank_t)
+
+/**
+ * Device specific ioctls should only be in their respective headers
+ * The device specific ioctl range is from 0x40 to 0x79.
+ *
+ * \sa drmCommandNone(), drmCommandRead(), drmCommandWrite(), and
+ * drmCommandReadWrite().
+ */
+#define DRM_COMMAND_BASE 0x40
+
+#endif
diff --git a/drivers/char/drm/drmP.h b/drivers/char/drm/drmP.h
new file mode 100644
index 000000000000..21f4c54e1a8d
--- /dev/null
+++ b/drivers/char/drm/drmP.h
@@ -0,0 +1,1073 @@
+/**
+ * \file drmP.h
+ * Private header for Direct Rendering Manager
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DRM_P_H_
+#define _DRM_P_H_
+
+/* If you want the memory alloc debug functionality, change define below */
+/* #define DEBUG_MEMORY */
+
+#ifdef __KERNEL__
+#ifdef __alpha__
+/* add include of current.h so that "current" is defined
+ * before static inline funcs in wait.h. Doing this so we
+ * can build the DRM (part of PI DRI). 4/21/2000 S + B */
+#include <asm/current.h>
+#endif /* __alpha__ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/file.h>
+#include <linux/pci.h>
+#include <linux/version.h>
+#include <linux/jiffies.h>
+#include <linux/smp_lock.h> /* For (un)lock_kernel */
+#include <linux/mm.h>
+#include <linux/cdev.h>
+#if defined(__alpha__) || defined(__powerpc__)
+#include <asm/pgtable.h> /* For pte_wrprotect */
+#endif
+#include <asm/io.h>
+#include <asm/mman.h>
+#include <asm/uaccess.h>
+#ifdef CONFIG_MTRR
+#include <asm/mtrr.h>
+#endif
+#if defined(CONFIG_AGP) || defined(CONFIG_AGP_MODULE)
+#include <linux/types.h>
+#include <linux/agp_backend.h>
+#endif
+#include <linux/workqueue.h>
+#include <linux/poll.h>
+#include <asm/pgalloc.h>
+#include "drm.h"
+
+#define __OS_HAS_AGP (defined(CONFIG_AGP) || (defined(CONFIG_AGP_MODULE) && defined(MODULE)))
+#define __OS_HAS_MTRR (defined(CONFIG_MTRR))
+
+#include "drm_os_linux.h"
+
+/***********************************************************************/
+/** \name DRM template customization defaults */
+/*@{*/
+
+/* driver capabilities and requirements mask */
+#define DRIVER_USE_AGP 0x1
+#define DRIVER_REQUIRE_AGP 0x2
+#define DRIVER_USE_MTRR 0x4
+#define DRIVER_PCI_DMA 0x8
+#define DRIVER_SG 0x10
+#define DRIVER_HAVE_DMA 0x20
+#define DRIVER_HAVE_IRQ 0x40
+#define DRIVER_IRQ_SHARED 0x80
+#define DRIVER_IRQ_VBL 0x100
+#define DRIVER_DMA_QUEUE 0x200
+
+/***********************************************************************/
+/** \name Begin the DRM... */
+/*@{*/
+
+#define DRM_DEBUG_CODE 2 /**< Include debugging code if > 1, then
+ also include looping detection. */
+
+#define DRM_HASH_SIZE 16 /**< Size of key hash table. Must be power of 2. */
+#define DRM_KERNEL_CONTEXT 0 /**< Change drm_resctx if changed */
+#define DRM_RESERVED_CONTEXTS 1 /**< Change drm_resctx if changed */
+#define DRM_LOOPING_LIMIT 5000000
+#define DRM_BSZ 1024 /**< Buffer size for /dev/drm? output */
+#define DRM_TIME_SLICE (HZ/20) /**< Time slice for GLXContexts */
+#define DRM_LOCK_SLICE 1 /**< Time slice for lock, in jiffies */
+
+#define DRM_FLAG_DEBUG 0x01
+
+#define DRM_MEM_DMA 0
+#define DRM_MEM_SAREA 1
+#define DRM_MEM_DRIVER 2
+#define DRM_MEM_MAGIC 3
+#define DRM_MEM_IOCTLS 4
+#define DRM_MEM_MAPS 5
+#define DRM_MEM_VMAS 6
+#define DRM_MEM_BUFS 7
+#define DRM_MEM_SEGS 8
+#define DRM_MEM_PAGES 9
+#define DRM_MEM_FILES 10
+#define DRM_MEM_QUEUES 11
+#define DRM_MEM_CMDS 12
+#define DRM_MEM_MAPPINGS 13
+#define DRM_MEM_BUFLISTS 14
+#define DRM_MEM_AGPLISTS 15
+#define DRM_MEM_TOTALAGP 16
+#define DRM_MEM_BOUNDAGP 17
+#define DRM_MEM_CTXBITMAP 18
+#define DRM_MEM_STUB 19
+#define DRM_MEM_SGLISTS 20
+#define DRM_MEM_CTXLIST 21
+
+#define DRM_MAX_CTXBITMAP (PAGE_SIZE * 8)
+
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Backward compatibility section */
+/*@{*/
+
+#ifndef MODULE_LICENSE
+#define MODULE_LICENSE(x)
+#endif
+
+#ifndef preempt_disable
+#define preempt_disable()
+#define preempt_enable()
+#endif
+
+#ifndef pte_offset_map
+#define pte_offset_map pte_offset
+#define pte_unmap(pte)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,19)
+static inline struct page * vmalloc_to_page(void * vmalloc_addr)
+{
+ unsigned long addr = (unsigned long) vmalloc_addr;
+ struct page *page = NULL;
+ pgd_t *pgd = pgd_offset_k(addr);
+ pmd_t *pmd;
+ pte_t *ptep, pte;
+
+ if (!pgd_none(*pgd)) {
+ pmd = pmd_offset(pgd, addr);
+ if (!pmd_none(*pmd)) {
+ preempt_disable();
+ ptep = pte_offset_map(pmd, addr);
+ pte = *ptep;
+ if (pte_present(pte))
+ page = pte_page(pte);
+ pte_unmap(ptep);
+ preempt_enable();
+ }
+ }
+ return page;
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#define DRM_RPR_ARG(vma)
+#else
+#define DRM_RPR_ARG(vma) vma,
+#endif
+
+#define VM_OFFSET(vma) ((vma)->vm_pgoff << PAGE_SHIFT)
+
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Macros to make printk easier */
+/*@{*/
+
+/**
+ * Error output.
+ *
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#define DRM_ERROR(fmt, arg...) \
+ printk(KERN_ERR "[" DRM_NAME ":%s] *ERROR* " fmt , __FUNCTION__ , ##arg)
+
+/**
+ * Memory error output.
+ *
+ * \param area memory area where the error occurred.
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#define DRM_MEM_ERROR(area, fmt, arg...) \
+ printk(KERN_ERR "[" DRM_NAME ":%s:%s] *ERROR* " fmt , __FUNCTION__, \
+ drm_mem_stats[area].name , ##arg)
+
+#define DRM_INFO(fmt, arg...) printk(KERN_INFO "[" DRM_NAME "] " fmt , ##arg)
+
+/**
+ * Debug output.
+ *
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#if DRM_DEBUG_CODE
+#define DRM_DEBUG(fmt, arg...) \
+ do { \
+ if ( drm_debug ) \
+ printk(KERN_DEBUG \
+ "[" DRM_NAME ":%s] " fmt , \
+ __FUNCTION__ , ##arg); \
+ } while (0)
+#else
+#define DRM_DEBUG(fmt, arg...) do { } while (0)
+#endif
+
+#define DRM_PROC_LIMIT (PAGE_SIZE-80)
+
+#define DRM_PROC_PRINT(fmt, arg...) \
+ len += sprintf(&buf[len], fmt , ##arg); \
+ if (len > DRM_PROC_LIMIT) { *eof = 1; return len - offset; }
+
+#define DRM_PROC_PRINT_RET(ret, fmt, arg...) \
+ len += sprintf(&buf[len], fmt , ##arg); \
+ if (len > DRM_PROC_LIMIT) { ret; *eof = 1; return len - offset; }
+
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Internal types and structures */
+/*@{*/
+
+#define DRM_ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define DRM_MIN(a,b) ((a)<(b)?(a):(b))
+#define DRM_MAX(a,b) ((a)>(b)?(a):(b))
+
+#define DRM_LEFTCOUNT(x) (((x)->rp + (x)->count - (x)->wp) % ((x)->count + 1))
+#define DRM_BUFCOUNT(x) ((x)->count - DRM_LEFTCOUNT(x))
+#define DRM_WAITCOUNT(dev,idx) DRM_BUFCOUNT(&dev->queuelist[idx]->waitlist)
+
+#define DRM_IF_VERSION(maj, min) (maj << 16 | min)
+/**
+ * Get the private SAREA mapping.
+ *
+ * \param _dev DRM device.
+ * \param _ctx context number.
+ * \param _map output mapping.
+ */
+#define DRM_GET_PRIV_SAREA(_dev, _ctx, _map) do { \
+ (_map) = (_dev)->context_sareas[_ctx]; \
+} while(0)
+
+/**
+ * Test that the hardware lock is held by the caller, returning otherwise.
+ *
+ * \param dev DRM device.
+ * \param filp file pointer of the caller.
+ */
+#define LOCK_TEST_WITH_RETURN( dev, filp ) \
+do { \
+ if ( !_DRM_LOCK_IS_HELD( dev->lock.hw_lock->lock ) || \
+ dev->lock.filp != filp ) { \
+ DRM_ERROR( "%s called without lock held\n", \
+ __FUNCTION__ ); \
+ return -EINVAL; \
+ } \
+} while (0)
+
+/**
+ * Copy and IOCTL return string to user space
+ */
+#define DRM_COPY( name, value ) \
+ len = strlen( value ); \
+ if ( len > name##_len ) len = name##_len; \
+ name##_len = strlen( value ); \
+ if ( len && name ) { \
+ if ( copy_to_user( name, value, len ) ) \
+ return -EFAULT; \
+ }
+
+/**
+ * Ioctl function type.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg argument.
+ */
+typedef int drm_ioctl_t( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+typedef struct drm_ioctl_desc {
+ drm_ioctl_t *func;
+ int auth_needed;
+ int root_only;
+} drm_ioctl_desc_t;
+
+typedef struct drm_devstate {
+ pid_t owner; /**< X server pid holding x_lock */
+} drm_devstate_t;
+
+typedef struct drm_magic_entry {
+ drm_magic_t magic;
+ struct drm_file *priv;
+ struct drm_magic_entry *next;
+} drm_magic_entry_t;
+
+typedef struct drm_magic_head {
+ struct drm_magic_entry *head;
+ struct drm_magic_entry *tail;
+} drm_magic_head_t;
+
+typedef struct drm_vma_entry {
+ struct vm_area_struct *vma;
+ struct drm_vma_entry *next;
+ pid_t pid;
+} drm_vma_entry_t;
+
+/**
+ * DMA buffer.
+ */
+typedef struct drm_buf {
+ int idx; /**< Index into master buflist */
+ int total; /**< Buffer size */
+ int order; /**< log-base-2(total) */
+ int used; /**< Amount of buffer in use (for DMA) */
+ unsigned long offset; /**< Byte offset (used internally) */
+ void *address; /**< Address of buffer */
+ unsigned long bus_address; /**< Bus address of buffer */
+ struct drm_buf *next; /**< Kernel-only: used for free list */
+ __volatile__ int waiting; /**< On kernel DMA queue */
+ __volatile__ int pending; /**< On hardware DMA queue */
+ wait_queue_head_t dma_wait; /**< Processes waiting */
+ struct file *filp; /**< Pointer to holding file descr */
+ int context; /**< Kernel queue for this buffer */
+ int while_locked;/**< Dispatch this buffer while locked */
+ enum {
+ DRM_LIST_NONE = 0,
+ DRM_LIST_FREE = 1,
+ DRM_LIST_WAIT = 2,
+ DRM_LIST_PEND = 3,
+ DRM_LIST_PRIO = 4,
+ DRM_LIST_RECLAIM = 5
+ } list; /**< Which list we're on */
+
+ int dev_priv_size; /**< Size of buffer private storage */
+ void *dev_private; /**< Per-buffer private storage */
+} drm_buf_t;
+
+
+/** bufs is one longer than it has to be */
+typedef struct drm_waitlist {
+ int count; /**< Number of possible buffers */
+ drm_buf_t **bufs; /**< List of pointers to buffers */
+ drm_buf_t **rp; /**< Read pointer */
+ drm_buf_t **wp; /**< Write pointer */
+ drm_buf_t **end; /**< End pointer */
+ spinlock_t read_lock;
+ spinlock_t write_lock;
+} drm_waitlist_t;
+
+typedef struct drm_freelist {
+ int initialized; /**< Freelist in use */
+ atomic_t count; /**< Number of free buffers */
+ drm_buf_t *next; /**< End pointer */
+
+ wait_queue_head_t waiting; /**< Processes waiting on free bufs */
+ int low_mark; /**< Low water mark */
+ int high_mark; /**< High water mark */
+ atomic_t wfh; /**< If waiting for high mark */
+ spinlock_t lock;
+} drm_freelist_t;
+
+/**
+ * Buffer entry. There is one of this for each buffer size order.
+ */
+typedef struct drm_buf_entry {
+ int buf_size; /**< size */
+ int buf_count; /**< number of buffers */
+ drm_buf_t *buflist; /**< buffer list */
+ int seg_count;
+ int page_order;
+ unsigned long *seglist;
+
+ drm_freelist_t freelist;
+} drm_buf_entry_t;
+
+/** File private data */
+typedef struct drm_file {
+ int authenticated;
+ int minor;
+ pid_t pid;
+ uid_t uid;
+ drm_magic_t magic;
+ unsigned long ioctl_count;
+ struct drm_file *next;
+ struct drm_file *prev;
+ struct drm_head *head;
+ int remove_auth_on_close;
+ unsigned long lock_count;
+ void *driver_priv;
+} drm_file_t;
+
+/** Wait queue */
+typedef struct drm_queue {
+ atomic_t use_count; /**< Outstanding uses (+1) */
+ atomic_t finalization; /**< Finalization in progress */
+ atomic_t block_count; /**< Count of processes waiting */
+ atomic_t block_read; /**< Queue blocked for reads */
+ wait_queue_head_t read_queue; /**< Processes waiting on block_read */
+ atomic_t block_write; /**< Queue blocked for writes */
+ wait_queue_head_t write_queue; /**< Processes waiting on block_write */
+#if 1
+ atomic_t total_queued; /**< Total queued statistic */
+ atomic_t total_flushed;/**< Total flushes statistic */
+ atomic_t total_locks; /**< Total locks statistics */
+#endif
+ drm_ctx_flags_t flags; /**< Context preserving and 2D-only */
+ drm_waitlist_t waitlist; /**< Pending buffers */
+ wait_queue_head_t flush_queue; /**< Processes waiting until flush */
+} drm_queue_t;
+
+/**
+ * Lock data.
+ */
+typedef struct drm_lock_data {
+ drm_hw_lock_t *hw_lock; /**< Hardware lock */
+ struct file *filp; /**< File descr of lock holder (0=kernel) */
+ wait_queue_head_t lock_queue; /**< Queue of blocked processes */
+ unsigned long lock_time; /**< Time of last lock in jiffies */
+} drm_lock_data_t;
+
+/**
+ * DMA data.
+ */
+typedef struct drm_device_dma {
+
+ drm_buf_entry_t bufs[DRM_MAX_ORDER+1]; /**< buffers, grouped by their size order */
+ int buf_count; /**< total number of buffers */
+ drm_buf_t **buflist; /**< Vector of pointers into drm_device_dma::bufs */
+ int seg_count;
+ int page_count; /**< number of pages */
+ unsigned long *pagelist; /**< page list */
+ unsigned long byte_count;
+ enum {
+ _DRM_DMA_USE_AGP = 0x01,
+ _DRM_DMA_USE_SG = 0x02
+ } flags;
+
+} drm_device_dma_t;
+
+/**
+ * AGP memory entry. Stored as a doubly linked list.
+ */
+typedef struct drm_agp_mem {
+ unsigned long handle; /**< handle */
+ DRM_AGP_MEM *memory;
+ unsigned long bound; /**< address */
+ int pages;
+ struct drm_agp_mem *prev; /**< previous entry */
+ struct drm_agp_mem *next; /**< next entry */
+} drm_agp_mem_t;
+
+/**
+ * AGP data.
+ *
+ * \sa drm_agp_init() and drm_device::agp.
+ */
+typedef struct drm_agp_head {
+ DRM_AGP_KERN agp_info; /**< AGP device information */
+ drm_agp_mem_t *memory; /**< memory entries */
+ unsigned long mode; /**< AGP mode */
+ struct agp_bridge_data *bridge;
+ int enabled; /**< whether the AGP bus as been enabled */
+ int acquired; /**< whether the AGP device has been acquired */
+ unsigned long base;
+ int agp_mtrr;
+ int cant_use_aperture;
+ unsigned long page_mask;
+} drm_agp_head_t;
+
+/**
+ * Scatter-gather memory.
+ */
+typedef struct drm_sg_mem {
+ unsigned long handle;
+ void *virtual;
+ int pages;
+ struct page **pagelist;
+ dma_addr_t *busaddr;
+} drm_sg_mem_t;
+
+typedef struct drm_sigdata {
+ int context;
+ drm_hw_lock_t *lock;
+} drm_sigdata_t;
+
+/**
+ * Mappings list
+ */
+typedef struct drm_map_list {
+ struct list_head head; /**< list head */
+ drm_map_t *map; /**< mapping */
+} drm_map_list_t;
+
+typedef drm_map_t drm_local_map_t;
+
+/**
+ * Context handle list
+ */
+typedef struct drm_ctx_list {
+ struct list_head head; /**< list head */
+ drm_context_t handle; /**< context handle */
+ drm_file_t *tag; /**< associated fd private data */
+} drm_ctx_list_t;
+
+
+typedef struct drm_vbl_sig {
+ struct list_head head;
+ unsigned int sequence;
+ struct siginfo info;
+ struct task_struct *task;
+} drm_vbl_sig_t;
+
+
+/**
+ * DRM driver structure. This structure represent the common code for
+ * a family of cards. There will one drm_device for each card present
+ * in this family
+ */
+struct drm_device;
+
+struct drm_driver {
+ int (*preinit)(struct drm_device *, unsigned long flags);
+ void (*prerelease)(struct drm_device *, struct file *filp);
+ void (*pretakedown)(struct drm_device *);
+ int (*postcleanup)(struct drm_device *);
+ int (*presetup)(struct drm_device *);
+ int (*postsetup)(struct drm_device *);
+ int (*dma_ioctl)( DRM_IOCTL_ARGS );
+ int (*open_helper)(struct drm_device *, drm_file_t *);
+ void (*free_filp_priv)(struct drm_device *, drm_file_t *);
+ void (*release)(struct drm_device *, struct file *filp);
+ void (*dma_ready)(struct drm_device *);
+ int (*dma_quiescent)(struct drm_device *);
+ int (*context_ctor)(struct drm_device *dev, int context);
+ int (*context_dtor)(struct drm_device *dev, int context);
+ int (*kernel_context_switch)(struct drm_device *dev, int old, int new);
+ void (*kernel_context_switch_unlock)(struct drm_device *dev, drm_lock_t *lock);
+ int (*vblank_wait)(struct drm_device *dev, unsigned int *sequence);
+ /* these have to be filled in */
+ int (*postinit)(struct drm_device *, unsigned long flags);
+ irqreturn_t (*irq_handler)( DRM_IRQ_ARGS );
+ void (*irq_preinstall)(struct drm_device *dev);
+ void (*irq_postinstall)(struct drm_device *dev);
+ void (*irq_uninstall)(struct drm_device *dev);
+ void (*reclaim_buffers)(struct drm_device *dev, struct file *filp);
+ unsigned long (*get_map_ofs)(drm_map_t *map);
+ unsigned long (*get_reg_ofs)(struct drm_device *dev);
+ void (*set_version)(struct drm_device *dev, drm_set_version_t *sv);
+ int (*version)(drm_version_t *version);
+ u32 driver_features;
+ int dev_priv_size;
+ drm_ioctl_desc_t *ioctls;
+ int num_ioctls;
+ struct file_operations fops;
+ struct pci_driver pci_driver;
+};
+
+/**
+ * DRM head structure. This structure represent a video head on a card
+ * that may contain multiple heads. Embed one per head of these in the
+ * private drm_device structure.
+ */
+typedef struct drm_head {
+ int minor; /**< Minor device number */
+ struct drm_device *dev;
+ struct proc_dir_entry *dev_root; /**< proc directory entry */
+ dev_t device; /**< Device number for mknod */
+ struct class_device *dev_class;
+} drm_head_t;
+
+/**
+ * DRM device structure. This structure represent a complete card that
+ * may contain multiple heads.
+ */
+typedef struct drm_device {
+ char *unique; /**< Unique identifier: e.g., busid */
+ int unique_len; /**< Length of unique field */
+ char *devname; /**< For /proc/interrupts */
+ int if_version; /**< Highest interface version set */
+
+ int blocked; /**< Blocked due to VC switch? */
+
+ /** \name Locks */
+ /*@{*/
+ spinlock_t count_lock; /**< For inuse, drm_device::open_count, drm_device::buf_use */
+ struct semaphore struct_sem; /**< For others */
+ /*@}*/
+
+ /** \name Usage Counters */
+ /*@{*/
+ int open_count; /**< Outstanding files open */
+ atomic_t ioctl_count; /**< Outstanding IOCTLs pending */
+ atomic_t vma_count; /**< Outstanding vma areas open */
+ int buf_use; /**< Buffers in use -- cannot alloc */
+ atomic_t buf_alloc; /**< Buffer allocation in progress */
+ /*@}*/
+
+ /** \name Performance counters */
+ /*@{*/
+ unsigned long counters;
+ drm_stat_type_t types[15];
+ atomic_t counts[15];
+ /*@}*/
+
+ /** \name Authentication */
+ /*@{*/
+ drm_file_t *file_first; /**< file list head */
+ drm_file_t *file_last; /**< file list tail */
+ drm_magic_head_t magiclist[DRM_HASH_SIZE]; /**< magic hash table */
+ /*@}*/
+
+ /** \name Memory management */
+ /*@{*/
+ drm_map_list_t *maplist; /**< Linked list of regions */
+ int map_count; /**< Number of mappable regions */
+
+ /** \name Context handle management */
+ /*@{*/
+ drm_ctx_list_t *ctxlist; /**< Linked list of context handles */
+ int ctx_count; /**< Number of context handles */
+ struct semaphore ctxlist_sem; /**< For ctxlist */
+
+ drm_map_t **context_sareas; /**< per-context SAREA's */
+ int max_context;
+
+ drm_vma_entry_t *vmalist; /**< List of vmas (for debugging) */
+ drm_lock_data_t lock; /**< Information on hardware lock */
+ /*@}*/
+
+ /** \name DMA queues (contexts) */
+ /*@{*/
+ int queue_count; /**< Number of active DMA queues */
+ int queue_reserved; /**< Number of reserved DMA queues */
+ int queue_slots; /**< Actual length of queuelist */
+ drm_queue_t **queuelist; /**< Vector of pointers to DMA queues */
+ drm_device_dma_t *dma; /**< Optional pointer for DMA support */
+ /*@}*/
+
+ /** \name Context support */
+ /*@{*/
+ int irq; /**< Interrupt used by board */
+ int irq_enabled; /**< True if irq handler is enabled */
+ __volatile__ long context_flag; /**< Context swapping flag */
+ __volatile__ long interrupt_flag; /**< Interruption handler flag */
+ __volatile__ long dma_flag; /**< DMA dispatch flag */
+ struct timer_list timer; /**< Timer for delaying ctx switch */
+ wait_queue_head_t context_wait; /**< Processes waiting on ctx switch */
+ int last_checked; /**< Last context checked for DMA */
+ int last_context; /**< Last current context */
+ unsigned long last_switch; /**< jiffies at last context switch */
+ /*@}*/
+
+ struct work_struct work;
+ /** \name VBLANK IRQ support */
+ /*@{*/
+
+ wait_queue_head_t vbl_queue; /**< VBLANK wait queue */
+ atomic_t vbl_received;
+ spinlock_t vbl_lock;
+ drm_vbl_sig_t vbl_sigs; /**< signal list to send on VBLANK */
+ unsigned int vbl_pending;
+
+ /*@}*/
+ cycles_t ctx_start;
+ cycles_t lck_start;
+
+ char buf[DRM_BSZ]; /**< Output buffer */
+ char *buf_rp; /**< Read pointer */
+ char *buf_wp; /**< Write pointer */
+ char *buf_end; /**< End pointer */
+ struct fasync_struct *buf_async;/**< Processes waiting for SIGIO */
+ wait_queue_head_t buf_readers; /**< Processes waiting to read */
+ wait_queue_head_t buf_writers; /**< Processes waiting to ctx switch */
+
+ drm_agp_head_t *agp; /**< AGP data */
+
+ struct pci_dev *pdev; /**< PCI device structure */
+ int pci_domain; /**< PCI bus domain number */
+ int pci_bus; /**< PCI bus number */
+ int pci_slot; /**< PCI slot number */
+ int pci_func; /**< PCI function number */
+#ifdef __alpha__
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,3)
+ struct pci_controler *hose;
+#else
+ struct pci_controller *hose;
+#endif
+#endif
+ drm_sg_mem_t *sg; /**< Scatter gather memory */
+ unsigned long *ctx_bitmap; /**< context bitmap */
+ void *dev_private; /**< device private data */
+ drm_sigdata_t sigdata; /**< For block_all_signals */
+ sigset_t sigmask;
+
+ struct drm_driver *driver;
+ drm_local_map_t *agp_buffer_map;
+ drm_head_t primary; /**< primary screen head */
+} drm_device_t;
+
+static __inline__ int drm_core_check_feature(struct drm_device *dev, int feature)
+{
+ return ((dev->driver->driver_features & feature) ? 1 : 0);
+}
+
+#if __OS_HAS_AGP
+static inline int drm_core_has_AGP(struct drm_device *dev)
+{
+ return drm_core_check_feature(dev, DRIVER_USE_AGP);
+}
+#else
+#define drm_core_has_AGP(dev) (0)
+#endif
+
+#if __OS_HAS_MTRR
+static inline int drm_core_has_MTRR(struct drm_device *dev)
+{
+ return drm_core_check_feature(dev, DRIVER_USE_MTRR);
+}
+#else
+#define drm_core_has_MTRR(dev) (0)
+#endif
+
+/******************************************************************/
+/** \name Internal function definitions */
+/*@{*/
+
+ /* Misc. support (drm_init.h) */
+extern int drm_flags;
+extern void drm_parse_options( char *s );
+extern int drm_cpu_valid( void );
+
+ /* Driver support (drm_drv.h) */
+extern int drm_init(struct drm_driver *driver);
+extern void drm_exit(struct drm_driver *driver);
+extern int drm_version(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_takedown(drm_device_t * dev);
+
+ /* Device support (drm_fops.h) */
+extern int drm_open(struct inode *inode, struct file *filp);
+extern int drm_stub_open(struct inode *inode, struct file *filp);
+extern int drm_open_helper(struct inode *inode, struct file *filp,
+ drm_device_t *dev);
+extern int drm_flush(struct file *filp);
+extern int drm_fasync(int fd, struct file *filp, int on);
+extern int drm_release(struct inode *inode, struct file *filp);
+
+ /* Mapping support (drm_vm.h) */
+extern void drm_vm_open(struct vm_area_struct *vma);
+extern void drm_vm_close(struct vm_area_struct *vma);
+extern void drm_vm_shm_close(struct vm_area_struct *vma);
+extern int drm_mmap_dma(struct file *filp,
+ struct vm_area_struct *vma);
+extern int drm_mmap(struct file *filp, struct vm_area_struct *vma);
+extern unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait);
+extern ssize_t drm_read(struct file *filp, char __user *buf, size_t count, loff_t *off);
+
+ /* Memory management support (drm_memory.h) */
+#include "drm_memory.h"
+extern void drm_mem_init(void);
+extern int drm_mem_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+extern void *drm_calloc(size_t nmemb, size_t size, int area);
+extern void *drm_realloc(void *oldpt, size_t oldsize, size_t size,
+ int area);
+extern unsigned long drm_alloc_pages(int order, int area);
+extern void drm_free_pages(unsigned long address, int order,
+ int area);
+extern void *drm_ioremap(unsigned long offset, unsigned long size, drm_device_t *dev);
+extern void *drm_ioremap_nocache(unsigned long offset, unsigned long size,
+ drm_device_t *dev);
+extern void drm_ioremapfree(void *pt, unsigned long size, drm_device_t *dev);
+
+extern DRM_AGP_MEM *drm_alloc_agp(struct agp_bridge_data *bridge, int pages, u32 type);
+extern int drm_free_agp(DRM_AGP_MEM *handle, int pages);
+extern int drm_bind_agp(DRM_AGP_MEM *handle, unsigned int start);
+extern int drm_unbind_agp(DRM_AGP_MEM *handle);
+
+ /* Misc. IOCTL support (drm_ioctl.h) */
+extern int drm_irq_by_busid(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_getunique(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_setunique(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_getmap(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_getclient(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_getstats(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_setversion(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+
+ /* Context IOCTL support (drm_context.h) */
+extern int drm_resctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_addctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_modctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_getctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_switchctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_newctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_rmctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+extern int drm_context_switch(drm_device_t *dev, int old, int new);
+extern int drm_context_switch_complete(drm_device_t *dev, int new);
+
+extern int drm_ctxbitmap_init( drm_device_t *dev );
+extern void drm_ctxbitmap_cleanup( drm_device_t *dev );
+extern void drm_ctxbitmap_free( drm_device_t *dev, int ctx_handle );
+
+extern int drm_setsareactx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_getsareactx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+ /* Drawable IOCTL support (drm_drawable.h) */
+extern int drm_adddraw(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_rmdraw(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+
+
+ /* Authentication IOCTL support (drm_auth.h) */
+extern int drm_add_magic(drm_device_t *dev, drm_file_t *priv,
+ drm_magic_t magic);
+extern int drm_remove_magic(drm_device_t *dev, drm_magic_t magic);
+extern int drm_getmagic(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_authmagic(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+
+ /* Placeholder for ioctls past */
+extern int drm_noop(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+
+ /* Locking IOCTL support (drm_lock.h) */
+extern int drm_lock(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_unlock(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_lock_take(__volatile__ unsigned int *lock,
+ unsigned int context);
+extern int drm_lock_transfer(drm_device_t *dev,
+ __volatile__ unsigned int *lock,
+ unsigned int context);
+extern int drm_lock_free(drm_device_t *dev,
+ __volatile__ unsigned int *lock,
+ unsigned int context);
+extern int drm_notifier(void *priv);
+
+ /* Buffer management support (drm_bufs.h) */
+extern int drm_order( unsigned long size );
+extern int drm_addmap( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_rmmap( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_addbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_infobufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_markbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_freebufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_mapbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+ /* DMA support (drm_dma.h) */
+extern int drm_dma_setup(drm_device_t *dev);
+extern void drm_dma_takedown(drm_device_t *dev);
+extern void drm_free_buffer(drm_device_t *dev, drm_buf_t *buf);
+extern void drm_core_reclaim_buffers(drm_device_t *dev, struct file *filp);
+
+ /* IRQ support (drm_irq.h) */
+extern int drm_control( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int drm_irq_install( drm_device_t *dev );
+extern int drm_irq_uninstall( drm_device_t *dev );
+extern irqreturn_t drm_irq_handler( DRM_IRQ_ARGS );
+extern void drm_driver_irq_preinstall( drm_device_t *dev );
+extern void drm_driver_irq_postinstall( drm_device_t *dev );
+extern void drm_driver_irq_uninstall( drm_device_t *dev );
+
+extern int drm_wait_vblank(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_vblank_wait(drm_device_t *dev, unsigned int *vbl_seq);
+extern void drm_vbl_send_signals( drm_device_t *dev );
+
+ /* AGP/GART support (drm_agpsupport.h) */
+extern drm_agp_head_t *drm_agp_init(drm_device_t *dev);
+extern int drm_agp_acquire(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern void drm_agp_do_release(drm_device_t *dev);
+extern int drm_agp_release(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_enable(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_info(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_alloc(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_free(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_unbind(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_agp_bind(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern DRM_AGP_MEM *drm_agp_allocate_memory(struct agp_bridge_data *bridge, size_t pages, u32 type);
+extern int drm_agp_free_memory(DRM_AGP_MEM *handle);
+extern int drm_agp_bind_memory(DRM_AGP_MEM *handle, off_t start);
+extern int drm_agp_unbind_memory(DRM_AGP_MEM *handle);
+
+ /* Stub support (drm_stub.h) */
+extern int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
+ struct drm_driver *driver);
+extern int drm_put_dev(drm_device_t * dev);
+extern int drm_get_head(drm_device_t * dev, drm_head_t *head);
+extern int drm_put_head(drm_head_t * head);
+extern unsigned int drm_debug;
+extern unsigned int drm_cards_limit;
+extern drm_head_t **drm_heads;
+extern struct drm_sysfs_class *drm_class;
+extern struct proc_dir_entry *drm_proc_root;
+
+ /* Proc support (drm_proc.h) */
+extern int drm_proc_init(drm_device_t *dev,
+ int minor,
+ struct proc_dir_entry *root,
+ struct proc_dir_entry **dev_root);
+extern int drm_proc_cleanup(int minor,
+ struct proc_dir_entry *root,
+ struct proc_dir_entry *dev_root);
+
+ /* Scatter Gather Support (drm_scatter.h) */
+extern void drm_sg_cleanup(drm_sg_mem_t *entry);
+extern int drm_sg_alloc(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int drm_sg_free(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+
+ /* ATI PCIGART support (ati_pcigart.h) */
+extern int drm_ati_pcigart_init(drm_device_t *dev,
+ unsigned long *addr,
+ dma_addr_t *bus_addr);
+extern int drm_ati_pcigart_cleanup(drm_device_t *dev,
+ unsigned long addr,
+ dma_addr_t bus_addr);
+
+extern void *drm_pci_alloc(drm_device_t * dev, size_t size,
+ size_t align, dma_addr_t maxaddr,
+ dma_addr_t * busaddr);
+
+extern void drm_pci_free(drm_device_t * dev, size_t size,
+ void *vaddr, dma_addr_t busaddr);
+
+ /* sysfs support (drm_sysfs.c) */
+struct drm_sysfs_class;
+extern struct drm_sysfs_class *drm_sysfs_create(struct module *owner,
+ char *name);
+extern void drm_sysfs_destroy(struct drm_sysfs_class *cs);
+extern struct class_device *drm_sysfs_device_add(struct drm_sysfs_class *cs,
+ dev_t dev,
+ struct device *device,
+ const char *fmt, ...);
+extern void drm_sysfs_device_remove(dev_t dev);
+
+
+/* Inline replacements for DRM_IOREMAP macros */
+static __inline__ void drm_core_ioremap(struct drm_map *map, struct drm_device *dev)
+{
+ map->handle = drm_ioremap( map->offset, map->size, dev );
+}
+
+static __inline__ void drm_core_ioremap_nocache(struct drm_map *map, struct drm_device *dev)
+{
+ map->handle = drm_ioremap_nocache(map->offset, map->size, dev);
+}
+
+static __inline__ void drm_core_ioremapfree(struct drm_map *map, struct drm_device *dev)
+{
+ if ( map->handle && map->size )
+ drm_ioremapfree( map->handle, map->size, dev );
+}
+
+static __inline__ struct drm_map *drm_core_findmap(struct drm_device *dev, unsigned long offset)
+{
+ struct list_head *_list;
+ list_for_each( _list, &dev->maplist->head ) {
+ drm_map_list_t *_entry = list_entry( _list, drm_map_list_t, head );
+ if ( _entry->map &&
+ _entry->map->offset == offset ) {
+ return _entry->map;
+ }
+ }
+ return NULL;
+}
+
+static __inline__ void drm_core_dropmap(struct drm_map *map)
+{
+}
+
+#ifndef DEBUG_MEMORY
+/** Wrapper around kmalloc() */
+static __inline__ void *drm_alloc(size_t size, int area)
+{
+ return kmalloc(size, GFP_KERNEL);
+}
+
+/** Wrapper around kfree() */
+static __inline__ void drm_free(void *pt, size_t size, int area)
+{
+ kfree(pt);
+}
+#else
+extern void *drm_alloc(size_t size, int area);
+extern void drm_free(void *pt, size_t size, int area);
+#endif
+
+/*@}*/
+
+extern unsigned long drm_core_get_map_ofs(drm_map_t *map);
+extern unsigned long drm_core_get_reg_ofs(struct drm_device *dev);
+
+#endif /* __KERNEL__ */
+#endif
diff --git a/drivers/char/drm/drm_agpsupport.c b/drivers/char/drm/drm_agpsupport.c
new file mode 100644
index 000000000000..8d94c0b5fa44
--- /dev/null
+++ b/drivers/char/drm/drm_agpsupport.c
@@ -0,0 +1,448 @@
+/**
+ * \file drm_agpsupport.h
+ * DRM support for AGP/GART backend
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include <linux/module.h>
+
+#if __OS_HAS_AGP
+
+/**
+ * AGP information ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a (output) drm_agp_info structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been initialized and acquired and fills in the
+ * drm_agp_info structure with the information in drm_agp_head::agp_info.
+ */
+int drm_agp_info(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ DRM_AGP_KERN *kern;
+ drm_agp_info_t info;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+
+ kern = &dev->agp->agp_info;
+ info.agp_version_major = kern->version.major;
+ info.agp_version_minor = kern->version.minor;
+ info.mode = kern->mode;
+ info.aperture_base = kern->aper_base;
+ info.aperture_size = kern->aper_size * 1024 * 1024;
+ info.memory_allowed = kern->max_memory << PAGE_SHIFT;
+ info.memory_used = kern->current_memory << PAGE_SHIFT;
+ info.id_vendor = kern->device->vendor;
+ info.id_device = kern->device->device;
+
+ if (copy_to_user((drm_agp_info_t __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Acquire the AGP device (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device hasn't been acquired before and calls
+ * agp_acquire().
+ */
+int drm_agp_acquire(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ if (!dev->agp)
+ return -ENODEV;
+ if (dev->agp->acquired)
+ return -EBUSY;
+ if (!(dev->agp->bridge = agp_backend_acquire(dev->pdev)))
+ return -ENODEV;
+ dev->agp->acquired = 1;
+ return 0;
+}
+
+/**
+ * Release the AGP device (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been acquired and calls agp_backend_release().
+ */
+int drm_agp_release(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+ agp_backend_release(dev->agp->bridge);
+ dev->agp->acquired = 0;
+ return 0;
+
+}
+
+/**
+ * Release the AGP device.
+ *
+ * Calls agp_backend_release().
+ */
+void drm_agp_do_release(drm_device_t *dev)
+{
+ agp_backend_release(dev->agp->bridge);
+}
+
+/**
+ * Enable the AGP bus.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_mode structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been acquired but not enabled, and calls
+ * agp_enable().
+ */
+int drm_agp_enable(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_agp_mode_t mode;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+
+ if (copy_from_user(&mode, (drm_agp_mode_t __user *)arg, sizeof(mode)))
+ return -EFAULT;
+
+ dev->agp->mode = mode.mode;
+ agp_enable(dev->agp->bridge, mode.mode);
+ dev->agp->base = dev->agp->agp_info.aper_base;
+ dev->agp->enabled = 1;
+ return 0;
+}
+
+/**
+ * Allocate AGP memory.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_buffer structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and has been acquired, allocates the
+ * memory via alloc_agp() and creates a drm_agp_mem entry for it.
+ */
+int drm_agp_alloc(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_agp_buffer_t request;
+ drm_agp_mem_t *entry;
+ DRM_AGP_MEM *memory;
+ unsigned long pages;
+ u32 type;
+ drm_agp_buffer_t __user *argp = (void __user *)arg;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+ if (copy_from_user(&request, argp, sizeof(request)))
+ return -EFAULT;
+ if (!(entry = drm_alloc(sizeof(*entry), DRM_MEM_AGPLISTS)))
+ return -ENOMEM;
+
+ memset(entry, 0, sizeof(*entry));
+
+ pages = (request.size + PAGE_SIZE - 1) / PAGE_SIZE;
+ type = (u32) request.type;
+
+ if (!(memory = drm_alloc_agp(dev->agp->bridge, pages, type))) {
+ drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+ return -ENOMEM;
+ }
+
+ entry->handle = (unsigned long)memory->key + 1;
+ entry->memory = memory;
+ entry->bound = 0;
+ entry->pages = pages;
+ entry->prev = NULL;
+ entry->next = dev->agp->memory;
+ if (dev->agp->memory)
+ dev->agp->memory->prev = entry;
+ dev->agp->memory = entry;
+
+ request.handle = entry->handle;
+ request.physical = memory->physical;
+
+ if (copy_to_user(argp, &request, sizeof(request))) {
+ dev->agp->memory = entry->next;
+ dev->agp->memory->prev = NULL;
+ drm_free_agp(memory, pages);
+ drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+ return -EFAULT;
+ }
+ return 0;
+}
+
+/**
+ * Search for the AGP memory entry associated with a handle.
+ *
+ * \param dev DRM device structure.
+ * \param handle AGP memory handle.
+ * \return pointer to the drm_agp_mem structure associated with \p handle.
+ *
+ * Walks through drm_agp_head::memory until finding a matching handle.
+ */
+static drm_agp_mem_t *drm_agp_lookup_entry(drm_device_t *dev,
+ unsigned long handle)
+{
+ drm_agp_mem_t *entry;
+
+ for (entry = dev->agp->memory; entry; entry = entry->next) {
+ if (entry->handle == handle)
+ return entry;
+ }
+ return NULL;
+}
+
+/**
+ * Unbind AGP memory from the GATT (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_binding structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and acquired, looks-up the AGP memory
+ * entry and passes it to the unbind_agp() function.
+ */
+int drm_agp_unbind(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_agp_binding_t request;
+ drm_agp_mem_t *entry;
+ int ret;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+ if (copy_from_user(&request, (drm_agp_binding_t __user *)arg, sizeof(request)))
+ return -EFAULT;
+ if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+ return -EINVAL;
+ if (!entry->bound)
+ return -EINVAL;
+ ret = drm_unbind_agp(entry->memory);
+ if (ret == 0)
+ entry->bound = 0;
+ return ret;
+}
+
+/**
+ * Bind AGP memory into the GATT (ioctl)
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_binding structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and has been acquired and that no memory
+ * is currently bound into the GATT. Looks-up the AGP memory entry and passes
+ * it to bind_agp() function.
+ */
+int drm_agp_bind(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_agp_binding_t request;
+ drm_agp_mem_t *entry;
+ int retcode;
+ int page;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+ if (copy_from_user(&request, (drm_agp_binding_t __user *)arg, sizeof(request)))
+ return -EFAULT;
+ if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+ return -EINVAL;
+ if (entry->bound)
+ return -EINVAL;
+ page = (request.offset + PAGE_SIZE - 1) / PAGE_SIZE;
+ if ((retcode = drm_bind_agp(entry->memory, page)))
+ return retcode;
+ entry->bound = dev->agp->base + (page << PAGE_SHIFT);
+ DRM_DEBUG("base = 0x%lx entry->bound = 0x%lx\n",
+ dev->agp->base, entry->bound);
+ return 0;
+}
+
+/**
+ * Free AGP memory (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_buffer structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and has been acquired and looks up the
+ * AGP memory entry. If the memory it's currently bound, unbind it via
+ * unbind_agp(). Frees it via free_agp() as well as the entry itself
+ * and unlinks from the doubly linked list it's inserted in.
+ */
+int drm_agp_free(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_agp_buffer_t request;
+ drm_agp_mem_t *entry;
+
+ if (!dev->agp || !dev->agp->acquired)
+ return -EINVAL;
+ if (copy_from_user(&request, (drm_agp_buffer_t __user *)arg, sizeof(request)))
+ return -EFAULT;
+ if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+ return -EINVAL;
+ if (entry->bound)
+ drm_unbind_agp(entry->memory);
+
+ if (entry->prev)
+ entry->prev->next = entry->next;
+ else
+ dev->agp->memory = entry->next;
+
+ if (entry->next)
+ entry->next->prev = entry->prev;
+
+ drm_free_agp(entry->memory, entry->pages);
+ drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+ return 0;
+}
+
+/**
+ * Initialize the AGP resources.
+ *
+ * \return pointer to a drm_agp_head structure.
+ *
+ */
+drm_agp_head_t *drm_agp_init(drm_device_t *dev)
+{
+ drm_agp_head_t *head = NULL;
+
+ if (!(head = drm_alloc(sizeof(*head), DRM_MEM_AGPLISTS)))
+ return NULL;
+ memset((void *)head, 0, sizeof(*head));
+ head->bridge = agp_find_bridge(dev->pdev);
+ if (!head->bridge) {
+ if (!(head->bridge = agp_backend_acquire(dev->pdev))) {
+ drm_free(head, sizeof(*head), DRM_MEM_AGPLISTS);
+ return NULL;
+ }
+ agp_copy_info(head->bridge, &head->agp_info);
+ agp_backend_release(head->bridge);
+ } else {
+ agp_copy_info(head->bridge, &head->agp_info);
+ }
+ if (head->agp_info.chipset == NOT_SUPPORTED) {
+ drm_free(head, sizeof(*head), DRM_MEM_AGPLISTS);
+ return NULL;
+ }
+ head->memory = NULL;
+#if LINUX_VERSION_CODE <= 0x020408
+ head->cant_use_aperture = 0;
+ head->page_mask = ~(0xfff);
+#else
+ head->cant_use_aperture = head->agp_info.cant_use_aperture;
+ head->page_mask = head->agp_info.page_mask;
+#endif
+
+ return head;
+}
+
+/** Calls agp_allocate_memory() */
+DRM_AGP_MEM *drm_agp_allocate_memory(struct agp_bridge_data *bridge, size_t pages, u32 type)
+{
+ return agp_allocate_memory(bridge, pages, type);
+}
+
+/** Calls agp_free_memory() */
+int drm_agp_free_memory(DRM_AGP_MEM *handle)
+{
+ if (!handle)
+ return 0;
+ agp_free_memory(handle);
+ return 1;
+}
+
+/** Calls agp_bind_memory() */
+int drm_agp_bind_memory(DRM_AGP_MEM *handle, off_t start)
+{
+ if (!handle)
+ return -EINVAL;
+ return agp_bind_memory(handle, start);
+}
+
+/** Calls agp_unbind_memory() */
+int drm_agp_unbind_memory(DRM_AGP_MEM *handle)
+{
+ if (!handle)
+ return -EINVAL;
+ return agp_unbind_memory(handle);
+}
+
+#endif /* __OS_HAS_AGP */
diff --git a/drivers/char/drm/drm_auth.c b/drivers/char/drm/drm_auth.c
new file mode 100644
index 000000000000..b428761c4e91
--- /dev/null
+++ b/drivers/char/drm/drm_auth.c
@@ -0,0 +1,230 @@
+/**
+ * \file drm_auth.h
+ * IOCTLs for authentication
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Generate a hash key from a magic.
+ *
+ * \param magic magic.
+ * \return hash key.
+ *
+ * The key is the modulus of the hash table size, #DRM_HASH_SIZE, which must be
+ * a power of 2.
+ */
+static int drm_hash_magic(drm_magic_t magic)
+{
+ return magic & (DRM_HASH_SIZE-1);
+}
+
+/**
+ * Find the file with the given magic number.
+ *
+ * \param dev DRM device.
+ * \param magic magic number.
+ *
+ * Searches in drm_device::magiclist within all files with the same hash key
+ * the one with matching magic number, while holding the drm_device::struct_sem
+ * lock.
+ */
+static drm_file_t *drm_find_file(drm_device_t *dev, drm_magic_t magic)
+{
+ drm_file_t *retval = NULL;
+ drm_magic_entry_t *pt;
+ int hash = drm_hash_magic(magic);
+
+ down(&dev->struct_sem);
+ for (pt = dev->magiclist[hash].head; pt; pt = pt->next) {
+ if (pt->magic == magic) {
+ retval = pt->priv;
+ break;
+ }
+ }
+ up(&dev->struct_sem);
+ return retval;
+}
+
+/**
+ * Adds a magic number.
+ *
+ * \param dev DRM device.
+ * \param priv file private data.
+ * \param magic magic number.
+ *
+ * Creates a drm_magic_entry structure and appends to the linked list
+ * associated the magic number hash key in drm_device::magiclist, while holding
+ * the drm_device::struct_sem lock.
+ */
+int drm_add_magic(drm_device_t *dev, drm_file_t *priv, drm_magic_t magic)
+{
+ int hash;
+ drm_magic_entry_t *entry;
+
+ DRM_DEBUG("%d\n", magic);
+
+ hash = drm_hash_magic(magic);
+ entry = drm_alloc(sizeof(*entry), DRM_MEM_MAGIC);
+ if (!entry) return -ENOMEM;
+ memset(entry, 0, sizeof(*entry));
+ entry->magic = magic;
+ entry->priv = priv;
+ entry->next = NULL;
+
+ down(&dev->struct_sem);
+ if (dev->magiclist[hash].tail) {
+ dev->magiclist[hash].tail->next = entry;
+ dev->magiclist[hash].tail = entry;
+ } else {
+ dev->magiclist[hash].head = entry;
+ dev->magiclist[hash].tail = entry;
+ }
+ up(&dev->struct_sem);
+
+ return 0;
+}
+
+/**
+ * Remove a magic number.
+ *
+ * \param dev DRM device.
+ * \param magic magic number.
+ *
+ * Searches and unlinks the entry in drm_device::magiclist with the magic
+ * number hash key, while holding the drm_device::struct_sem lock.
+ */
+int drm_remove_magic(drm_device_t *dev, drm_magic_t magic)
+{
+ drm_magic_entry_t *prev = NULL;
+ drm_magic_entry_t *pt;
+ int hash;
+
+
+ DRM_DEBUG("%d\n", magic);
+ hash = drm_hash_magic(magic);
+
+ down(&dev->struct_sem);
+ for (pt = dev->magiclist[hash].head; pt; prev = pt, pt = pt->next) {
+ if (pt->magic == magic) {
+ if (dev->magiclist[hash].head == pt) {
+ dev->magiclist[hash].head = pt->next;
+ }
+ if (dev->magiclist[hash].tail == pt) {
+ dev->magiclist[hash].tail = prev;
+ }
+ if (prev) {
+ prev->next = pt->next;
+ }
+ up(&dev->struct_sem);
+ return 0;
+ }
+ }
+ up(&dev->struct_sem);
+
+ drm_free(pt, sizeof(*pt), DRM_MEM_MAGIC);
+
+ return -EINVAL;
+}
+
+/**
+ * Get a unique magic number (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a resulting drm_auth structure.
+ * \return zero on success, or a negative number on failure.
+ *
+ * If there is a magic number in drm_file::magic then use it, otherwise
+ * searches an unique non-zero magic number and add it associating it with \p
+ * filp.
+ */
+int drm_getmagic(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ static drm_magic_t sequence = 0;
+ static DEFINE_SPINLOCK(lock);
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_auth_t auth;
+
+ /* Find unique magic */
+ if (priv->magic) {
+ auth.magic = priv->magic;
+ } else {
+ do {
+ spin_lock(&lock);
+ if (!sequence) ++sequence; /* reserve 0 */
+ auth.magic = sequence++;
+ spin_unlock(&lock);
+ } while (drm_find_file(dev, auth.magic));
+ priv->magic = auth.magic;
+ drm_add_magic(dev, priv, auth.magic);
+ }
+
+ DRM_DEBUG("%u\n", auth.magic);
+ if (copy_to_user((drm_auth_t __user *)arg, &auth, sizeof(auth)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Authenticate with a magic.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_auth structure.
+ * \return zero if authentication successed, or a negative number otherwise.
+ *
+ * Checks if \p filp is associated with the magic number passed in \arg.
+ */
+int drm_authmagic(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_auth_t auth;
+ drm_file_t *file;
+
+ if (copy_from_user(&auth, (drm_auth_t __user *)arg, sizeof(auth)))
+ return -EFAULT;
+ DRM_DEBUG("%u\n", auth.magic);
+ if ((file = drm_find_file(dev, auth.magic))) {
+ file->authenticated = 1;
+ drm_remove_magic(dev, auth.magic);
+ return 0;
+ }
+ return -EINVAL;
+}
diff --git a/drivers/char/drm/drm_bufs.c b/drivers/char/drm/drm_bufs.c
new file mode 100644
index 000000000000..4113bcba67fe
--- /dev/null
+++ b/drivers/char/drm/drm_bufs.c
@@ -0,0 +1,1270 @@
+/**
+ * \file drm_bufs.h
+ * Generic buffer template
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+/**
+ * Compute size order. Returns the exponent of the smaller power of two which
+ * is greater or equal to given number.
+ *
+ * \param size size.
+ * \return order.
+ *
+ * \todo Can be made faster.
+ */
+int drm_order( unsigned long size )
+{
+ int order;
+ unsigned long tmp;
+
+ for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++)
+ ;
+
+ if (size & (size - 1))
+ ++order;
+
+ return order;
+}
+EXPORT_SYMBOL(drm_order);
+
+/**
+ * Ioctl to specify a range of memory that is available for mapping by a non-root process.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_map structure.
+ * \return zero on success or a negative value on error.
+ *
+ * Adjusts the memory offset to its absolute value according to the mapping
+ * type. Adds the map to the map list drm_device::maplist. Adds MTRR's where
+ * applicable and if supported by the kernel.
+ */
+int drm_addmap( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_map_t *map;
+ drm_map_t __user *argp = (void __user *)arg;
+ drm_map_list_t *list;
+
+ if ( !(filp->f_mode & 3) ) return -EACCES; /* Require read/write */
+
+ map = drm_alloc( sizeof(*map), DRM_MEM_MAPS );
+ if ( !map )
+ return -ENOMEM;
+
+ if ( copy_from_user( map, argp, sizeof(*map) ) ) {
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EFAULT;
+ }
+
+ /* Only allow shared memory to be removable since we only keep enough
+ * book keeping information about shared memory to allow for removal
+ * when processes fork.
+ */
+ if ( (map->flags & _DRM_REMOVABLE) && map->type != _DRM_SHM ) {
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EINVAL;
+ }
+ DRM_DEBUG( "offset = 0x%08lx, size = 0x%08lx, type = %d\n",
+ map->offset, map->size, map->type );
+ if ( (map->offset & (~PAGE_MASK)) || (map->size & (~PAGE_MASK)) ) {
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EINVAL;
+ }
+ map->mtrr = -1;
+ map->handle = NULL;
+
+ switch ( map->type ) {
+ case _DRM_REGISTERS:
+ case _DRM_FRAME_BUFFER:
+#if !defined(__sparc__) && !defined(__alpha__) && !defined(__ia64__)
+ if ( map->offset + map->size < map->offset ||
+ map->offset < virt_to_phys(high_memory) ) {
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EINVAL;
+ }
+#endif
+#ifdef __alpha__
+ map->offset += dev->hose->mem_space->start;
+#endif
+ if (drm_core_has_MTRR(dev)) {
+ if ( map->type == _DRM_FRAME_BUFFER ||
+ (map->flags & _DRM_WRITE_COMBINING) ) {
+ map->mtrr = mtrr_add( map->offset, map->size,
+ MTRR_TYPE_WRCOMB, 1 );
+ }
+ }
+ if (map->type == _DRM_REGISTERS)
+ map->handle = drm_ioremap( map->offset, map->size,
+ dev );
+ break;
+
+ case _DRM_SHM:
+ map->handle = vmalloc_32(map->size);
+ DRM_DEBUG( "%lu %d %p\n",
+ map->size, drm_order( map->size ), map->handle );
+ if ( !map->handle ) {
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -ENOMEM;
+ }
+ map->offset = (unsigned long)map->handle;
+ if ( map->flags & _DRM_CONTAINS_LOCK ) {
+ /* Prevent a 2nd X Server from creating a 2nd lock */
+ if (dev->lock.hw_lock != NULL) {
+ vfree( map->handle );
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EBUSY;
+ }
+ dev->sigdata.lock =
+ dev->lock.hw_lock = map->handle; /* Pointer to lock */
+ }
+ break;
+ case _DRM_AGP:
+ if (drm_core_has_AGP(dev)) {
+#ifdef __alpha__
+ map->offset += dev->hose->mem_space->start;
+#endif
+ map->offset += dev->agp->base;
+ map->mtrr = dev->agp->agp_mtrr; /* for getmap */
+ }
+ break;
+ case _DRM_SCATTER_GATHER:
+ if (!dev->sg) {
+ drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+ return -EINVAL;
+ }
+ map->offset += dev->sg->handle;
+ break;
+
+ default:
+ drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+ return -EINVAL;
+ }
+
+ list = drm_alloc(sizeof(*list), DRM_MEM_MAPS);
+ if(!list) {
+ drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+ return -EINVAL;
+ }
+ memset(list, 0, sizeof(*list));
+ list->map = map;
+
+ down(&dev->struct_sem);
+ list_add(&list->head, &dev->maplist->head);
+ up(&dev->struct_sem);
+
+ if ( copy_to_user( argp, map, sizeof(*map) ) )
+ return -EFAULT;
+ if ( map->type != _DRM_SHM ) {
+ if ( copy_to_user( &argp->handle,
+ &map->offset,
+ sizeof(map->offset) ) )
+ return -EFAULT;
+ }
+ return 0;
+}
+
+
+/**
+ * Remove a map private from list and deallocate resources if the mapping
+ * isn't in use.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_map_t structure.
+ * \return zero on success or a negative value on error.
+ *
+ * Searches the map on drm_device::maplist, removes it from the list, see if
+ * its being used, and free any associate resource (such as MTRR's) if it's not
+ * being on use.
+ *
+ * \sa addmap().
+ */
+int drm_rmmap(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ struct list_head *list;
+ drm_map_list_t *r_list = NULL;
+ drm_vma_entry_t *pt, *prev;
+ drm_map_t *map;
+ drm_map_t request;
+ int found_maps = 0;
+
+ if (copy_from_user(&request, (drm_map_t __user *)arg,
+ sizeof(request))) {
+ return -EFAULT;
+ }
+
+ down(&dev->struct_sem);
+ list = &dev->maplist->head;
+ list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+
+ if(r_list->map &&
+ r_list->map->handle == request.handle &&
+ r_list->map->flags & _DRM_REMOVABLE) break;
+ }
+
+ /* List has wrapped around to the head pointer, or its empty we didn't
+ * find anything.
+ */
+ if(list == (&dev->maplist->head)) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+ map = r_list->map;
+ list_del(list);
+ drm_free(list, sizeof(*list), DRM_MEM_MAPS);
+
+ for (pt = dev->vmalist, prev = NULL; pt; prev = pt, pt = pt->next) {
+ if (pt->vma->vm_private_data == map) found_maps++;
+ }
+
+ if(!found_maps) {
+ switch (map->type) {
+ case _DRM_REGISTERS:
+ case _DRM_FRAME_BUFFER:
+ if (drm_core_has_MTRR(dev)) {
+ if (map->mtrr >= 0) {
+ int retcode;
+ retcode = mtrr_del(map->mtrr,
+ map->offset,
+ map->size);
+ DRM_DEBUG("mtrr_del = %d\n", retcode);
+ }
+ }
+ drm_ioremapfree(map->handle, map->size, dev);
+ break;
+ case _DRM_SHM:
+ vfree(map->handle);
+ break;
+ case _DRM_AGP:
+ case _DRM_SCATTER_GATHER:
+ break;
+ }
+ drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+ }
+ up(&dev->struct_sem);
+ return 0;
+}
+
+/**
+ * Cleanup after an error on one of the addbufs() functions.
+ *
+ * \param entry buffer entry where the error occurred.
+ *
+ * Frees any pages and buffers associated with the given entry.
+ */
+static void drm_cleanup_buf_error(drm_device_t *dev, drm_buf_entry_t *entry)
+{
+ int i;
+
+ if (entry->seg_count) {
+ for (i = 0; i < entry->seg_count; i++) {
+ if (entry->seglist[i]) {
+ drm_free_pages(entry->seglist[i],
+ entry->page_order,
+ DRM_MEM_DMA);
+ }
+ }
+ drm_free(entry->seglist,
+ entry->seg_count *
+ sizeof(*entry->seglist),
+ DRM_MEM_SEGS);
+
+ entry->seg_count = 0;
+ }
+
+ if (entry->buf_count) {
+ for (i = 0; i < entry->buf_count; i++) {
+ if (entry->buflist[i].dev_private) {
+ drm_free(entry->buflist[i].dev_private,
+ entry->buflist[i].dev_priv_size,
+ DRM_MEM_BUFS);
+ }
+ }
+ drm_free(entry->buflist,
+ entry->buf_count *
+ sizeof(*entry->buflist),
+ DRM_MEM_BUFS);
+
+ entry->buf_count = 0;
+ }
+}
+
+#if __OS_HAS_AGP
+/**
+ * Add AGP buffers for DMA transfers (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_desc_t request.
+ * \return zero on success or a negative number on failure.
+ *
+ * After some sanity checks creates a drm_buf structure for each buffer and
+ * reallocates the buffer list of the same size order to accommodate the new
+ * buffers.
+ */
+int drm_addbufs_agp( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_desc_t request;
+ drm_buf_entry_t *entry;
+ drm_buf_t *buf;
+ unsigned long offset;
+ unsigned long agp_offset;
+ int count;
+ int order;
+ int size;
+ int alignment;
+ int page_order;
+ int total;
+ int byte_count;
+ int i;
+ drm_buf_t **temp_buflist;
+ drm_buf_desc_t __user *argp = (void __user *)arg;
+
+ if ( !dma ) return -EINVAL;
+
+ if ( copy_from_user( &request, argp,
+ sizeof(request) ) )
+ return -EFAULT;
+
+ count = request.count;
+ order = drm_order( request.size );
+ size = 1 << order;
+
+ alignment = (request.flags & _DRM_PAGE_ALIGN)
+ ? PAGE_ALIGN(size) : size;
+ page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+ total = PAGE_SIZE << page_order;
+
+ byte_count = 0;
+ agp_offset = dev->agp->base + request.agp_start;
+
+ DRM_DEBUG( "count: %d\n", count );
+ DRM_DEBUG( "order: %d\n", order );
+ DRM_DEBUG( "size: %d\n", size );
+ DRM_DEBUG( "agp_offset: %lu\n", agp_offset );
+ DRM_DEBUG( "alignment: %d\n", alignment );
+ DRM_DEBUG( "page_order: %d\n", page_order );
+ DRM_DEBUG( "total: %d\n", total );
+
+ if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+ if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+ spin_lock( &dev->count_lock );
+ if ( dev->buf_use ) {
+ spin_unlock( &dev->count_lock );
+ return -EBUSY;
+ }
+ atomic_inc( &dev->buf_alloc );
+ spin_unlock( &dev->count_lock );
+
+ down( &dev->struct_sem );
+ entry = &dma->bufs[order];
+ if ( entry->buf_count ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM; /* May only call once for each order */
+ }
+
+ if (count < 0 || count > 4096) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -EINVAL;
+ }
+
+ entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+ DRM_MEM_BUFS );
+ if ( !entry->buflist ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+ entry->buf_size = size;
+ entry->page_order = page_order;
+
+ offset = 0;
+
+ while ( entry->buf_count < count ) {
+ buf = &entry->buflist[entry->buf_count];
+ buf->idx = dma->buf_count + entry->buf_count;
+ buf->total = alignment;
+ buf->order = order;
+ buf->used = 0;
+
+ buf->offset = (dma->byte_count + offset);
+ buf->bus_address = agp_offset + offset;
+ buf->address = (void *)(agp_offset + offset);
+ buf->next = NULL;
+ buf->waiting = 0;
+ buf->pending = 0;
+ init_waitqueue_head( &buf->dma_wait );
+ buf->filp = NULL;
+
+ buf->dev_priv_size = dev->driver->dev_priv_size;
+ buf->dev_private = drm_alloc( buf->dev_priv_size,
+ DRM_MEM_BUFS );
+ if(!buf->dev_private) {
+ /* Set count correctly so we free the proper amount. */
+ entry->buf_count = count;
+ drm_cleanup_buf_error(dev,entry);
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( buf->dev_private, 0, buf->dev_priv_size );
+
+ DRM_DEBUG( "buffer %d @ %p\n",
+ entry->buf_count, buf->address );
+
+ offset += alignment;
+ entry->buf_count++;
+ byte_count += PAGE_SIZE << page_order;
+ }
+
+ DRM_DEBUG( "byte_count: %d\n", byte_count );
+
+ temp_buflist = drm_realloc( dma->buflist,
+ dma->buf_count * sizeof(*dma->buflist),
+ (dma->buf_count + entry->buf_count)
+ * sizeof(*dma->buflist),
+ DRM_MEM_BUFS );
+ if(!temp_buflist) {
+ /* Free the entry because it isn't valid */
+ drm_cleanup_buf_error(dev,entry);
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ dma->buflist = temp_buflist;
+
+ for ( i = 0 ; i < entry->buf_count ; i++ ) {
+ dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+ }
+
+ dma->buf_count += entry->buf_count;
+ dma->byte_count += byte_count;
+
+ DRM_DEBUG( "dma->buf_count : %d\n", dma->buf_count );
+ DRM_DEBUG( "entry->buf_count : %d\n", entry->buf_count );
+
+ up( &dev->struct_sem );
+
+ request.count = entry->buf_count;
+ request.size = size;
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) )
+ return -EFAULT;
+
+ dma->flags = _DRM_DMA_USE_AGP;
+
+ atomic_dec( &dev->buf_alloc );
+ return 0;
+}
+#endif /* __OS_HAS_AGP */
+
+int drm_addbufs_pci( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_desc_t request;
+ int count;
+ int order;
+ int size;
+ int total;
+ int page_order;
+ drm_buf_entry_t *entry;
+ unsigned long page;
+ drm_buf_t *buf;
+ int alignment;
+ unsigned long offset;
+ int i;
+ int byte_count;
+ int page_count;
+ unsigned long *temp_pagelist;
+ drm_buf_t **temp_buflist;
+ drm_buf_desc_t __user *argp = (void __user *)arg;
+
+ if (!drm_core_check_feature(dev, DRIVER_PCI_DMA)) return -EINVAL;
+ if ( !dma ) return -EINVAL;
+
+ if ( copy_from_user( &request, argp, sizeof(request) ) )
+ return -EFAULT;
+
+ count = request.count;
+ order = drm_order( request.size );
+ size = 1 << order;
+
+ DRM_DEBUG( "count=%d, size=%d (%d), order=%d, queue_count=%d\n",
+ request.count, request.size, size,
+ order, dev->queue_count );
+
+ if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+ if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+ alignment = (request.flags & _DRM_PAGE_ALIGN)
+ ? PAGE_ALIGN(size) : size;
+ page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+ total = PAGE_SIZE << page_order;
+
+ spin_lock( &dev->count_lock );
+ if ( dev->buf_use ) {
+ spin_unlock( &dev->count_lock );
+ return -EBUSY;
+ }
+ atomic_inc( &dev->buf_alloc );
+ spin_unlock( &dev->count_lock );
+
+ down( &dev->struct_sem );
+ entry = &dma->bufs[order];
+ if ( entry->buf_count ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM; /* May only call once for each order */
+ }
+
+ if (count < 0 || count > 4096) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -EINVAL;
+ }
+
+ entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+ DRM_MEM_BUFS );
+ if ( !entry->buflist ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+ entry->seglist = drm_alloc( count * sizeof(*entry->seglist),
+ DRM_MEM_SEGS );
+ if ( !entry->seglist ) {
+ drm_free( entry->buflist,
+ count * sizeof(*entry->buflist),
+ DRM_MEM_BUFS );
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( entry->seglist, 0, count * sizeof(*entry->seglist) );
+
+ /* Keep the original pagelist until we know all the allocations
+ * have succeeded
+ */
+ temp_pagelist = drm_alloc( (dma->page_count + (count << page_order))
+ * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES );
+ if (!temp_pagelist) {
+ drm_free( entry->buflist,
+ count * sizeof(*entry->buflist),
+ DRM_MEM_BUFS );
+ drm_free( entry->seglist,
+ count * sizeof(*entry->seglist),
+ DRM_MEM_SEGS );
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memcpy(temp_pagelist,
+ dma->pagelist,
+ dma->page_count * sizeof(*dma->pagelist));
+ DRM_DEBUG( "pagelist: %d entries\n",
+ dma->page_count + (count << page_order) );
+
+ entry->buf_size = size;
+ entry->page_order = page_order;
+ byte_count = 0;
+ page_count = 0;
+
+ while ( entry->buf_count < count ) {
+ page = drm_alloc_pages( page_order, DRM_MEM_DMA );
+ if ( !page ) {
+ /* Set count correctly so we free the proper amount. */
+ entry->buf_count = count;
+ entry->seg_count = count;
+ drm_cleanup_buf_error(dev, entry);
+ drm_free( temp_pagelist,
+ (dma->page_count + (count << page_order))
+ * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES );
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ entry->seglist[entry->seg_count++] = page;
+ for ( i = 0 ; i < (1 << page_order) ; i++ ) {
+ DRM_DEBUG( "page %d @ 0x%08lx\n",
+ dma->page_count + page_count,
+ page + PAGE_SIZE * i );
+ temp_pagelist[dma->page_count + page_count++]
+ = page + PAGE_SIZE * i;
+ }
+ for ( offset = 0 ;
+ offset + size <= total && entry->buf_count < count ;
+ offset += alignment, ++entry->buf_count ) {
+ buf = &entry->buflist[entry->buf_count];
+ buf->idx = dma->buf_count + entry->buf_count;
+ buf->total = alignment;
+ buf->order = order;
+ buf->used = 0;
+ buf->offset = (dma->byte_count + byte_count + offset);
+ buf->address = (void *)(page + offset);
+ buf->next = NULL;
+ buf->waiting = 0;
+ buf->pending = 0;
+ init_waitqueue_head( &buf->dma_wait );
+ buf->filp = NULL;
+
+ buf->dev_priv_size = dev->driver->dev_priv_size;
+ buf->dev_private = drm_alloc( buf->dev_priv_size,
+ DRM_MEM_BUFS );
+ if(!buf->dev_private) {
+ /* Set count correctly so we free the proper amount. */
+ entry->buf_count = count;
+ entry->seg_count = count;
+ drm_cleanup_buf_error(dev,entry);
+ drm_free( temp_pagelist,
+ (dma->page_count + (count << page_order))
+ * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES );
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( buf->dev_private, 0, buf->dev_priv_size );
+
+ DRM_DEBUG( "buffer %d @ %p\n",
+ entry->buf_count, buf->address );
+ }
+ byte_count += PAGE_SIZE << page_order;
+ }
+
+ temp_buflist = drm_realloc( dma->buflist,
+ dma->buf_count * sizeof(*dma->buflist),
+ (dma->buf_count + entry->buf_count)
+ * sizeof(*dma->buflist),
+ DRM_MEM_BUFS );
+ if (!temp_buflist) {
+ /* Free the entry because it isn't valid */
+ drm_cleanup_buf_error(dev,entry);
+ drm_free( temp_pagelist,
+ (dma->page_count + (count << page_order))
+ * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES );
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ dma->buflist = temp_buflist;
+
+ for ( i = 0 ; i < entry->buf_count ; i++ ) {
+ dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+ }
+
+ /* No allocations failed, so now we can replace the orginal pagelist
+ * with the new one.
+ */
+ if (dma->page_count) {
+ drm_free(dma->pagelist,
+ dma->page_count * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES);
+ }
+ dma->pagelist = temp_pagelist;
+
+ dma->buf_count += entry->buf_count;
+ dma->seg_count += entry->seg_count;
+ dma->page_count += entry->seg_count << page_order;
+ dma->byte_count += PAGE_SIZE * (entry->seg_count << page_order);
+
+ up( &dev->struct_sem );
+
+ request.count = entry->buf_count;
+ request.size = size;
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) )
+ return -EFAULT;
+
+ atomic_dec( &dev->buf_alloc );
+ return 0;
+
+}
+
+int drm_addbufs_sg( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_desc_t __user *argp = (void __user *)arg;
+ drm_buf_desc_t request;
+ drm_buf_entry_t *entry;
+ drm_buf_t *buf;
+ unsigned long offset;
+ unsigned long agp_offset;
+ int count;
+ int order;
+ int size;
+ int alignment;
+ int page_order;
+ int total;
+ int byte_count;
+ int i;
+ drm_buf_t **temp_buflist;
+
+ if (!drm_core_check_feature(dev, DRIVER_SG)) return -EINVAL;
+
+ if ( !dma ) return -EINVAL;
+
+ if ( copy_from_user( &request, argp, sizeof(request) ) )
+ return -EFAULT;
+
+ count = request.count;
+ order = drm_order( request.size );
+ size = 1 << order;
+
+ alignment = (request.flags & _DRM_PAGE_ALIGN)
+ ? PAGE_ALIGN(size) : size;
+ page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+ total = PAGE_SIZE << page_order;
+
+ byte_count = 0;
+ agp_offset = request.agp_start;
+
+ DRM_DEBUG( "count: %d\n", count );
+ DRM_DEBUG( "order: %d\n", order );
+ DRM_DEBUG( "size: %d\n", size );
+ DRM_DEBUG( "agp_offset: %lu\n", agp_offset );
+ DRM_DEBUG( "alignment: %d\n", alignment );
+ DRM_DEBUG( "page_order: %d\n", page_order );
+ DRM_DEBUG( "total: %d\n", total );
+
+ if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+ if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+ spin_lock( &dev->count_lock );
+ if ( dev->buf_use ) {
+ spin_unlock( &dev->count_lock );
+ return -EBUSY;
+ }
+ atomic_inc( &dev->buf_alloc );
+ spin_unlock( &dev->count_lock );
+
+ down( &dev->struct_sem );
+ entry = &dma->bufs[order];
+ if ( entry->buf_count ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM; /* May only call once for each order */
+ }
+
+ if (count < 0 || count > 4096) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -EINVAL;
+ }
+
+ entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+ DRM_MEM_BUFS );
+ if ( !entry->buflist ) {
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+ entry->buf_size = size;
+ entry->page_order = page_order;
+
+ offset = 0;
+
+ while ( entry->buf_count < count ) {
+ buf = &entry->buflist[entry->buf_count];
+ buf->idx = dma->buf_count + entry->buf_count;
+ buf->total = alignment;
+ buf->order = order;
+ buf->used = 0;
+
+ buf->offset = (dma->byte_count + offset);
+ buf->bus_address = agp_offset + offset;
+ buf->address = (void *)(agp_offset + offset + dev->sg->handle);
+ buf->next = NULL;
+ buf->waiting = 0;
+ buf->pending = 0;
+ init_waitqueue_head( &buf->dma_wait );
+ buf->filp = NULL;
+
+ buf->dev_priv_size = dev->driver->dev_priv_size;
+ buf->dev_private = drm_alloc( buf->dev_priv_size,
+ DRM_MEM_BUFS );
+ if(!buf->dev_private) {
+ /* Set count correctly so we free the proper amount. */
+ entry->buf_count = count;
+ drm_cleanup_buf_error(dev,entry);
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+
+ memset( buf->dev_private, 0, buf->dev_priv_size );
+
+ DRM_DEBUG( "buffer %d @ %p\n",
+ entry->buf_count, buf->address );
+
+ offset += alignment;
+ entry->buf_count++;
+ byte_count += PAGE_SIZE << page_order;
+ }
+
+ DRM_DEBUG( "byte_count: %d\n", byte_count );
+
+ temp_buflist = drm_realloc( dma->buflist,
+ dma->buf_count * sizeof(*dma->buflist),
+ (dma->buf_count + entry->buf_count)
+ * sizeof(*dma->buflist),
+ DRM_MEM_BUFS );
+ if(!temp_buflist) {
+ /* Free the entry because it isn't valid */
+ drm_cleanup_buf_error(dev,entry);
+ up( &dev->struct_sem );
+ atomic_dec( &dev->buf_alloc );
+ return -ENOMEM;
+ }
+ dma->buflist = temp_buflist;
+
+ for ( i = 0 ; i < entry->buf_count ; i++ ) {
+ dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+ }
+
+ dma->buf_count += entry->buf_count;
+ dma->byte_count += byte_count;
+
+ DRM_DEBUG( "dma->buf_count : %d\n", dma->buf_count );
+ DRM_DEBUG( "entry->buf_count : %d\n", entry->buf_count );
+
+ up( &dev->struct_sem );
+
+ request.count = entry->buf_count;
+ request.size = size;
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) )
+ return -EFAULT;
+
+ dma->flags = _DRM_DMA_USE_SG;
+
+ atomic_dec( &dev->buf_alloc );
+ return 0;
+}
+
+/**
+ * Add buffers for DMA transfers (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_desc_t request.
+ * \return zero on success or a negative number on failure.
+ *
+ * According with the memory type specified in drm_buf_desc::flags and the
+ * build options, it dispatches the call either to addbufs_agp(),
+ * addbufs_sg() or addbufs_pci() for AGP, scatter-gather or consistent
+ * PCI memory respectively.
+ */
+int drm_addbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_buf_desc_t request;
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ return -EINVAL;
+
+ if ( copy_from_user( &request, (drm_buf_desc_t __user *)arg,
+ sizeof(request) ) )
+ return -EFAULT;
+
+#if __OS_HAS_AGP
+ if ( request.flags & _DRM_AGP_BUFFER )
+ return drm_addbufs_agp( inode, filp, cmd, arg );
+ else
+#endif
+ if ( request.flags & _DRM_SG_BUFFER )
+ return drm_addbufs_sg( inode, filp, cmd, arg );
+ else
+ return drm_addbufs_pci( inode, filp, cmd, arg );
+}
+
+
+/**
+ * Get information about the buffer mappings.
+ *
+ * This was originally mean for debugging purposes, or by a sophisticated
+ * client library to determine how best to use the available buffers (e.g.,
+ * large buffers can be used for image transfer).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_info structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Increments drm_device::buf_use while holding the drm_device::count_lock
+ * lock, preventing of allocating more buffers after this call. Information
+ * about each requested buffer is then copied into user space.
+ */
+int drm_infobufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_info_t request;
+ drm_buf_info_t __user *argp = (void __user *)arg;
+ int i;
+ int count;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ return -EINVAL;
+
+ if ( !dma ) return -EINVAL;
+
+ spin_lock( &dev->count_lock );
+ if ( atomic_read( &dev->buf_alloc ) ) {
+ spin_unlock( &dev->count_lock );
+ return -EBUSY;
+ }
+ ++dev->buf_use; /* Can't allocate more after this call */
+ spin_unlock( &dev->count_lock );
+
+ if ( copy_from_user( &request, argp, sizeof(request) ) )
+ return -EFAULT;
+
+ for ( i = 0, count = 0 ; i < DRM_MAX_ORDER + 1 ; i++ ) {
+ if ( dma->bufs[i].buf_count ) ++count;
+ }
+
+ DRM_DEBUG( "count = %d\n", count );
+
+ if ( request.count >= count ) {
+ for ( i = 0, count = 0 ; i < DRM_MAX_ORDER + 1 ; i++ ) {
+ if ( dma->bufs[i].buf_count ) {
+ drm_buf_desc_t __user *to = &request.list[count];
+ drm_buf_entry_t *from = &dma->bufs[i];
+ drm_freelist_t *list = &dma->bufs[i].freelist;
+ if ( copy_to_user( &to->count,
+ &from->buf_count,
+ sizeof(from->buf_count) ) ||
+ copy_to_user( &to->size,
+ &from->buf_size,
+ sizeof(from->buf_size) ) ||
+ copy_to_user( &to->low_mark,
+ &list->low_mark,
+ sizeof(list->low_mark) ) ||
+ copy_to_user( &to->high_mark,
+ &list->high_mark,
+ sizeof(list->high_mark) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d %d %d %d %d\n",
+ i,
+ dma->bufs[i].buf_count,
+ dma->bufs[i].buf_size,
+ dma->bufs[i].freelist.low_mark,
+ dma->bufs[i].freelist.high_mark );
+ ++count;
+ }
+ }
+ }
+ request.count = count;
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) )
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * Specifies a low and high water mark for buffer allocation
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg a pointer to a drm_buf_desc structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies that the size order is bounded between the admissible orders and
+ * updates the respective drm_device_dma::bufs entry low and high water mark.
+ *
+ * \note This ioctl is deprecated and mostly never used.
+ */
+int drm_markbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_desc_t request;
+ int order;
+ drm_buf_entry_t *entry;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ return -EINVAL;
+
+ if ( !dma ) return -EINVAL;
+
+ if ( copy_from_user( &request,
+ (drm_buf_desc_t __user *)arg,
+ sizeof(request) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d, %d, %d\n",
+ request.size, request.low_mark, request.high_mark );
+ order = drm_order( request.size );
+ if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+ entry = &dma->bufs[order];
+
+ if ( request.low_mark < 0 || request.low_mark > entry->buf_count )
+ return -EINVAL;
+ if ( request.high_mark < 0 || request.high_mark > entry->buf_count )
+ return -EINVAL;
+
+ entry->freelist.low_mark = request.low_mark;
+ entry->freelist.high_mark = request.high_mark;
+
+ return 0;
+}
+
+/**
+ * Unreserve the buffers in list, previously reserved using drmDMA.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_free structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls free_buffer() for each used buffer.
+ * This function is primarily used for debugging.
+ */
+int drm_freebufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_free_t request;
+ int i;
+ int idx;
+ drm_buf_t *buf;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ return -EINVAL;
+
+ if ( !dma ) return -EINVAL;
+
+ if ( copy_from_user( &request,
+ (drm_buf_free_t __user *)arg,
+ sizeof(request) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d\n", request.count );
+ for ( i = 0 ; i < request.count ; i++ ) {
+ if ( copy_from_user( &idx,
+ &request.list[i],
+ sizeof(idx) ) )
+ return -EFAULT;
+ if ( idx < 0 || idx >= dma->buf_count ) {
+ DRM_ERROR( "Index %d (of %d max)\n",
+ idx, dma->buf_count - 1 );
+ return -EINVAL;
+ }
+ buf = dma->buflist[idx];
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "Process %d freeing buffer not owned\n",
+ current->pid );
+ return -EINVAL;
+ }
+ drm_free_buffer( dev, buf );
+ }
+
+ return 0;
+}
+
+/**
+ * Maps all of the DMA buffers into client-virtual space (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Maps the AGP or SG buffer region with do_mmap(), and copies information
+ * about each buffer into user space. The PCI buffers are already mapped on the
+ * addbufs_pci() call.
+ */
+int drm_mapbufs( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_map_t __user *argp = (void __user *)arg;
+ int retcode = 0;
+ const int zero = 0;
+ unsigned long virtual;
+ unsigned long address;
+ drm_buf_map_t request;
+ int i;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ return -EINVAL;
+
+ if ( !dma ) return -EINVAL;
+
+ spin_lock( &dev->count_lock );
+ if ( atomic_read( &dev->buf_alloc ) ) {
+ spin_unlock( &dev->count_lock );
+ return -EBUSY;
+ }
+ dev->buf_use++; /* Can't allocate more after this call */
+ spin_unlock( &dev->count_lock );
+
+ if ( copy_from_user( &request, argp, sizeof(request) ) )
+ return -EFAULT;
+
+ if ( request.count >= dma->buf_count ) {
+ if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP)) ||
+ (drm_core_check_feature(dev, DRIVER_SG) && (dma->flags & _DRM_DMA_USE_SG)) ) {
+ drm_map_t *map = dev->agp_buffer_map;
+
+ if ( !map ) {
+ retcode = -EINVAL;
+ goto done;
+ }
+
+#if LINUX_VERSION_CODE <= 0x020402
+ down( &current->mm->mmap_sem );
+#else
+ down_write( &current->mm->mmap_sem );
+#endif
+ virtual = do_mmap( filp, 0, map->size,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED,
+ (unsigned long)map->offset );
+#if LINUX_VERSION_CODE <= 0x020402
+ up( &current->mm->mmap_sem );
+#else
+ up_write( &current->mm->mmap_sem );
+#endif
+ } else {
+#if LINUX_VERSION_CODE <= 0x020402
+ down( &current->mm->mmap_sem );
+#else
+ down_write( &current->mm->mmap_sem );
+#endif
+ virtual = do_mmap( filp, 0, dma->byte_count,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED, 0 );
+#if LINUX_VERSION_CODE <= 0x020402
+ up( &current->mm->mmap_sem );
+#else
+ up_write( &current->mm->mmap_sem );
+#endif
+ }
+ if ( virtual > -1024UL ) {
+ /* Real error */
+ retcode = (signed long)virtual;
+ goto done;
+ }
+ request.virtual = (void __user *)virtual;
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ if ( copy_to_user( &request.list[i].idx,
+ &dma->buflist[i]->idx,
+ sizeof(request.list[0].idx) ) ) {
+ retcode = -EFAULT;
+ goto done;
+ }
+ if ( copy_to_user( &request.list[i].total,
+ &dma->buflist[i]->total,
+ sizeof(request.list[0].total) ) ) {
+ retcode = -EFAULT;
+ goto done;
+ }
+ if ( copy_to_user( &request.list[i].used,
+ &zero,
+ sizeof(zero) ) ) {
+ retcode = -EFAULT;
+ goto done;
+ }
+ address = virtual + dma->buflist[i]->offset; /* *** */
+ if ( copy_to_user( &request.list[i].address,
+ &address,
+ sizeof(address) ) ) {
+ retcode = -EFAULT;
+ goto done;
+ }
+ }
+ }
+ done:
+ request.count = dma->buf_count;
+ DRM_DEBUG( "%d buffers, retcode = %d\n", request.count, retcode );
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) )
+ return -EFAULT;
+
+ return retcode;
+}
+
diff --git a/drivers/char/drm/drm_context.c b/drivers/char/drm/drm_context.c
new file mode 100644
index 000000000000..f15c86c57875
--- /dev/null
+++ b/drivers/char/drm/drm_context.c
@@ -0,0 +1,578 @@
+/**
+ * \file drm_context.h
+ * IOCTLs for generic contexts
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Nov 24 18:31:37 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * ChangeLog:
+ * 2001-11-16 Torsten Duwe <duwe@caldera.de>
+ * added context constructor/destructor hooks,
+ * needed by SiS driver's memory management.
+ */
+
+#include "drmP.h"
+
+/******************************************************************/
+/** \name Context bitmap support */
+/*@{*/
+
+/**
+ * Free a handle from the context bitmap.
+ *
+ * \param dev DRM device.
+ * \param ctx_handle context handle.
+ *
+ * Clears the bit specified by \p ctx_handle in drm_device::ctx_bitmap and the entry
+ * in drm_device::context_sareas, while holding the drm_device::struct_sem
+ * lock.
+ */
+void drm_ctxbitmap_free( drm_device_t *dev, int ctx_handle )
+{
+ if ( ctx_handle < 0 ) goto failed;
+ if ( !dev->ctx_bitmap ) goto failed;
+
+ if ( ctx_handle < DRM_MAX_CTXBITMAP ) {
+ down(&dev->struct_sem);
+ clear_bit( ctx_handle, dev->ctx_bitmap );
+ dev->context_sareas[ctx_handle] = NULL;
+ up(&dev->struct_sem);
+ return;
+ }
+failed:
+ DRM_ERROR( "Attempt to free invalid context handle: %d\n",
+ ctx_handle );
+ return;
+}
+
+/**
+ * Context bitmap allocation.
+ *
+ * \param dev DRM device.
+ * \return (non-negative) context handle on success or a negative number on failure.
+ *
+ * Find the first zero bit in drm_device::ctx_bitmap and (re)allocates
+ * drm_device::context_sareas to accommodate the new entry while holding the
+ * drm_device::struct_sem lock.
+ */
+int drm_ctxbitmap_next( drm_device_t *dev )
+{
+ int bit;
+
+ if(!dev->ctx_bitmap) return -1;
+
+ down(&dev->struct_sem);
+ bit = find_first_zero_bit( dev->ctx_bitmap, DRM_MAX_CTXBITMAP );
+ if ( bit < DRM_MAX_CTXBITMAP ) {
+ set_bit( bit, dev->ctx_bitmap );
+ DRM_DEBUG( "drm_ctxbitmap_next bit : %d\n", bit );
+ if((bit+1) > dev->max_context) {
+ dev->max_context = (bit+1);
+ if(dev->context_sareas) {
+ drm_map_t **ctx_sareas;
+
+ ctx_sareas = drm_realloc(dev->context_sareas,
+ (dev->max_context - 1) *
+ sizeof(*dev->context_sareas),
+ dev->max_context *
+ sizeof(*dev->context_sareas),
+ DRM_MEM_MAPS);
+ if(!ctx_sareas) {
+ clear_bit(bit, dev->ctx_bitmap);
+ up(&dev->struct_sem);
+ return -1;
+ }
+ dev->context_sareas = ctx_sareas;
+ dev->context_sareas[bit] = NULL;
+ } else {
+ /* max_context == 1 at this point */
+ dev->context_sareas = drm_alloc(
+ dev->max_context *
+ sizeof(*dev->context_sareas),
+ DRM_MEM_MAPS);
+ if(!dev->context_sareas) {
+ clear_bit(bit, dev->ctx_bitmap);
+ up(&dev->struct_sem);
+ return -1;
+ }
+ dev->context_sareas[bit] = NULL;
+ }
+ }
+ up(&dev->struct_sem);
+ return bit;
+ }
+ up(&dev->struct_sem);
+ return -1;
+}
+
+/**
+ * Context bitmap initialization.
+ *
+ * \param dev DRM device.
+ *
+ * Allocates and initialize drm_device::ctx_bitmap and drm_device::context_sareas, while holding
+ * the drm_device::struct_sem lock.
+ */
+int drm_ctxbitmap_init( drm_device_t *dev )
+{
+ int i;
+ int temp;
+
+ down(&dev->struct_sem);
+ dev->ctx_bitmap = (unsigned long *) drm_alloc( PAGE_SIZE,
+ DRM_MEM_CTXBITMAP );
+ if ( dev->ctx_bitmap == NULL ) {
+ up(&dev->struct_sem);
+ return -ENOMEM;
+ }
+ memset( (void *)dev->ctx_bitmap, 0, PAGE_SIZE );
+ dev->context_sareas = NULL;
+ dev->max_context = -1;
+ up(&dev->struct_sem);
+
+ for ( i = 0 ; i < DRM_RESERVED_CONTEXTS ; i++ ) {
+ temp = drm_ctxbitmap_next( dev );
+ DRM_DEBUG( "drm_ctxbitmap_init : %d\n", temp );
+ }
+
+ return 0;
+}
+
+/**
+ * Context bitmap cleanup.
+ *
+ * \param dev DRM device.
+ *
+ * Frees drm_device::ctx_bitmap and drm_device::context_sareas, while holding
+ * the drm_device::struct_sem lock.
+ */
+void drm_ctxbitmap_cleanup( drm_device_t *dev )
+{
+ down(&dev->struct_sem);
+ if( dev->context_sareas ) drm_free( dev->context_sareas,
+ sizeof(*dev->context_sareas) *
+ dev->max_context,
+ DRM_MEM_MAPS );
+ drm_free( (void *)dev->ctx_bitmap, PAGE_SIZE, DRM_MEM_CTXBITMAP );
+ up(&dev->struct_sem);
+}
+
+/*@}*/
+
+/******************************************************************/
+/** \name Per Context SAREA Support */
+/*@{*/
+
+/**
+ * Get per-context SAREA.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_priv_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Gets the map from drm_device::context_sareas with the handle specified and
+ * returns its handle.
+ */
+int drm_getsareactx(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_priv_map_t __user *argp = (void __user *)arg;
+ drm_ctx_priv_map_t request;
+ drm_map_t *map;
+
+ if (copy_from_user(&request, argp, sizeof(request)))
+ return -EFAULT;
+
+ down(&dev->struct_sem);
+ if (dev->max_context < 0 || request.ctx_id >= (unsigned) dev->max_context) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+
+ map = dev->context_sareas[request.ctx_id];
+ up(&dev->struct_sem);
+
+ request.handle = map->handle;
+ if (copy_to_user(argp, &request, sizeof(request)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Set per-context SAREA.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_priv_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches the mapping specified in \p arg and update the entry in
+ * drm_device::context_sareas with it.
+ */
+int drm_setsareactx(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_priv_map_t request;
+ drm_map_t *map = NULL;
+ drm_map_list_t *r_list = NULL;
+ struct list_head *list;
+
+ if (copy_from_user(&request,
+ (drm_ctx_priv_map_t __user *)arg,
+ sizeof(request)))
+ return -EFAULT;
+
+ down(&dev->struct_sem);
+ list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ if(r_list->map &&
+ r_list->map->handle == request.handle)
+ goto found;
+ }
+bad:
+ up(&dev->struct_sem);
+ return -EINVAL;
+
+found:
+ map = r_list->map;
+ if (!map) goto bad;
+ if (dev->max_context < 0)
+ goto bad;
+ if (request.ctx_id >= (unsigned) dev->max_context)
+ goto bad;
+ dev->context_sareas[request.ctx_id] = map;
+ up(&dev->struct_sem);
+ return 0;
+}
+
+/*@}*/
+
+/******************************************************************/
+/** \name The actual DRM context handling routines */
+/*@{*/
+
+/**
+ * Switch context.
+ *
+ * \param dev DRM device.
+ * \param old old context handle.
+ * \param new new context handle.
+ * \return zero on success or a negative number on failure.
+ *
+ * Attempt to set drm_device::context_flag.
+ */
+int drm_context_switch( drm_device_t *dev, int old, int new )
+{
+ if ( test_and_set_bit( 0, &dev->context_flag ) ) {
+ DRM_ERROR( "Reentering -- FIXME\n" );
+ return -EBUSY;
+ }
+
+
+ DRM_DEBUG( "Context switch from %d to %d\n", old, new );
+
+ if ( new == dev->last_context ) {
+ clear_bit( 0, &dev->context_flag );
+ return 0;
+ }
+
+ return 0;
+}
+
+/**
+ * Complete context switch.
+ *
+ * \param dev DRM device.
+ * \param new new context handle.
+ * \return zero on success or a negative number on failure.
+ *
+ * Updates drm_device::last_context and drm_device::last_switch. Verifies the
+ * hardware lock is held, clears the drm_device::context_flag and wakes up
+ * drm_device::context_wait.
+ */
+int drm_context_switch_complete( drm_device_t *dev, int new )
+{
+ dev->last_context = new; /* PRE/POST: This is the _only_ writer. */
+ dev->last_switch = jiffies;
+
+ if ( !_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) ) {
+ DRM_ERROR( "Lock isn't held after context switch\n" );
+ }
+
+ /* If a context switch is ever initiated
+ when the kernel holds the lock, release
+ that lock here. */
+ clear_bit( 0, &dev->context_flag );
+ wake_up( &dev->context_wait );
+
+ return 0;
+}
+
+/**
+ * Reserve contexts.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_res structure.
+ * \return zero on success or a negative number on failure.
+ */
+int drm_resctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_ctx_res_t res;
+ drm_ctx_t __user *argp = (void __user *)arg;
+ drm_ctx_t ctx;
+ int i;
+
+ if ( copy_from_user( &res, argp, sizeof(res) ) )
+ return -EFAULT;
+
+ if ( res.count >= DRM_RESERVED_CONTEXTS ) {
+ memset( &ctx, 0, sizeof(ctx) );
+ for ( i = 0 ; i < DRM_RESERVED_CONTEXTS ; i++ ) {
+ ctx.handle = i;
+ if ( copy_to_user( &res.contexts[i],
+ &i, sizeof(i) ) )
+ return -EFAULT;
+ }
+ }
+ res.count = DRM_RESERVED_CONTEXTS;
+
+ if ( copy_to_user( argp, &res, sizeof(res) ) )
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Add context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Get a new handle for the context and copy to userspace.
+ */
+int drm_addctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_list_t * ctx_entry;
+ drm_ctx_t __user *argp = (void __user *)arg;
+ drm_ctx_t ctx;
+
+ if ( copy_from_user( &ctx, argp, sizeof(ctx) ) )
+ return -EFAULT;
+
+ ctx.handle = drm_ctxbitmap_next( dev );
+ if ( ctx.handle == DRM_KERNEL_CONTEXT ) {
+ /* Skip kernel's context and get a new one. */
+ ctx.handle = drm_ctxbitmap_next( dev );
+ }
+ DRM_DEBUG( "%d\n", ctx.handle );
+ if ( ctx.handle == -1 ) {
+ DRM_DEBUG( "Not enough free contexts.\n" );
+ /* Should this return -EBUSY instead? */
+ return -ENOMEM;
+ }
+
+ if ( ctx.handle != DRM_KERNEL_CONTEXT )
+ {
+ if (dev->driver->context_ctor)
+ dev->driver->context_ctor(dev, ctx.handle);
+ }
+
+ ctx_entry = drm_alloc( sizeof(*ctx_entry), DRM_MEM_CTXLIST );
+ if ( !ctx_entry ) {
+ DRM_DEBUG("out of memory\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD( &ctx_entry->head );
+ ctx_entry->handle = ctx.handle;
+ ctx_entry->tag = priv;
+
+ down( &dev->ctxlist_sem );
+ list_add( &ctx_entry->head, &dev->ctxlist->head );
+ ++dev->ctx_count;
+ up( &dev->ctxlist_sem );
+
+ if ( copy_to_user( argp, &ctx, sizeof(ctx) ) )
+ return -EFAULT;
+ return 0;
+}
+
+int drm_modctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ /* This does nothing */
+ return 0;
+}
+
+/**
+ * Get context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ */
+int drm_getctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_ctx_t __user *argp = (void __user *)arg;
+ drm_ctx_t ctx;
+
+ if ( copy_from_user( &ctx, argp, sizeof(ctx) ) )
+ return -EFAULT;
+
+ /* This is 0, because we don't handle any context flags */
+ ctx.flags = 0;
+
+ if ( copy_to_user( argp, &ctx, sizeof(ctx) ) )
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Switch context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls context_switch().
+ */
+int drm_switchctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_t ctx;
+
+ if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d\n", ctx.handle );
+ return drm_context_switch( dev, dev->last_context, ctx.handle );
+}
+
+/**
+ * New context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls context_switch_complete().
+ */
+int drm_newctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_t ctx;
+
+ if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d\n", ctx.handle );
+ drm_context_switch_complete( dev, ctx.handle );
+
+ return 0;
+}
+
+/**
+ * Remove context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * If not the special kernel context, calls ctxbitmap_free() to free the specified context.
+ */
+int drm_rmctx( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ctx_t ctx;
+
+ if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+ return -EFAULT;
+
+ DRM_DEBUG( "%d\n", ctx.handle );
+ if ( ctx.handle == DRM_KERNEL_CONTEXT + 1 ) {
+ priv->remove_auth_on_close = 1;
+ }
+ if ( ctx.handle != DRM_KERNEL_CONTEXT ) {
+ if (dev->driver->context_dtor)
+ dev->driver->context_dtor(dev, ctx.handle);
+ drm_ctxbitmap_free( dev, ctx.handle );
+ }
+
+ down( &dev->ctxlist_sem );
+ if ( !list_empty( &dev->ctxlist->head ) ) {
+ drm_ctx_list_t *pos, *n;
+
+ list_for_each_entry_safe( pos, n, &dev->ctxlist->head, head ) {
+ if ( pos->handle == ctx.handle ) {
+ list_del( &pos->head );
+ drm_free( pos, sizeof(*pos), DRM_MEM_CTXLIST );
+ --dev->ctx_count;
+ }
+ }
+ }
+ up( &dev->ctxlist_sem );
+
+ return 0;
+}
+
+/*@}*/
+
diff --git a/drivers/char/drm/drm_core.h b/drivers/char/drm/drm_core.h
new file mode 100644
index 000000000000..cc97bb906dda
--- /dev/null
+++ b/drivers/char/drm/drm_core.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2004 Jon Smirl <jonsmirl@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * VIA, S3 GRAPHICS, AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#define CORE_AUTHOR "Gareth Hughes, Leif Delgass, José Fonseca, Jon Smirl"
+
+#define CORE_NAME "drm"
+#define CORE_DESC "DRM shared core routines"
+#define CORE_DATE "20040925"
+
+#define DRM_IF_MAJOR 1
+#define DRM_IF_MINOR 2
+
+#define CORE_MAJOR 1
+#define CORE_MINOR 0
+#define CORE_PATCHLEVEL 0
diff --git a/drivers/char/drm/drm_dma.c b/drivers/char/drm/drm_dma.c
new file mode 100644
index 000000000000..4a28c053c98b
--- /dev/null
+++ b/drivers/char/drm/drm_dma.c
@@ -0,0 +1,180 @@
+/**
+ * \file drm_dma.h
+ * DMA IOCTL and function support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Initialize the DMA data.
+ *
+ * \param dev DRM device.
+ * \return zero on success or a negative value on failure.
+ *
+ * Allocate and initialize a drm_device_dma structure.
+ */
+int drm_dma_setup( drm_device_t *dev )
+{
+ int i;
+
+ dev->dma = drm_alloc( sizeof(*dev->dma), DRM_MEM_DRIVER );
+ if ( !dev->dma )
+ return -ENOMEM;
+
+ memset( dev->dma, 0, sizeof(*dev->dma) );
+
+ for ( i = 0 ; i <= DRM_MAX_ORDER ; i++ )
+ memset(&dev->dma->bufs[i], 0, sizeof(dev->dma->bufs[0]));
+
+ return 0;
+}
+
+/**
+ * Cleanup the DMA resources.
+ *
+ * \param dev DRM device.
+ *
+ * Free all pages associated with DMA buffers, the buffers and pages lists, and
+ * finally the the drm_device::dma structure itself.
+ */
+void drm_dma_takedown(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i, j;
+
+ if (!dma) return;
+
+ /* Clear dma buffers */
+ for (i = 0; i <= DRM_MAX_ORDER; i++) {
+ if (dma->bufs[i].seg_count) {
+ DRM_DEBUG("order %d: buf_count = %d,"
+ " seg_count = %d\n",
+ i,
+ dma->bufs[i].buf_count,
+ dma->bufs[i].seg_count);
+ for (j = 0; j < dma->bufs[i].seg_count; j++) {
+ if (dma->bufs[i].seglist[j]) {
+ drm_free_pages(dma->bufs[i].seglist[j],
+ dma->bufs[i].page_order,
+ DRM_MEM_DMA);
+ }
+ }
+ drm_free(dma->bufs[i].seglist,
+ dma->bufs[i].seg_count
+ * sizeof(*dma->bufs[0].seglist),
+ DRM_MEM_SEGS);
+ }
+ if (dma->bufs[i].buf_count) {
+ for (j = 0; j < dma->bufs[i].buf_count; j++) {
+ if (dma->bufs[i].buflist[j].dev_private) {
+ drm_free(dma->bufs[i].buflist[j].dev_private,
+ dma->bufs[i].buflist[j].dev_priv_size,
+ DRM_MEM_BUFS);
+ }
+ }
+ drm_free(dma->bufs[i].buflist,
+ dma->bufs[i].buf_count *
+ sizeof(*dma->bufs[0].buflist),
+ DRM_MEM_BUFS);
+ }
+ }
+
+ if (dma->buflist) {
+ drm_free(dma->buflist,
+ dma->buf_count * sizeof(*dma->buflist),
+ DRM_MEM_BUFS);
+ }
+
+ if (dma->pagelist) {
+ drm_free(dma->pagelist,
+ dma->page_count * sizeof(*dma->pagelist),
+ DRM_MEM_PAGES);
+ }
+ drm_free(dev->dma, sizeof(*dev->dma), DRM_MEM_DRIVER);
+ dev->dma = NULL;
+}
+
+
+/**
+ * Free a buffer.
+ *
+ * \param dev DRM device.
+ * \param buf buffer to free.
+ *
+ * Resets the fields of \p buf.
+ */
+void drm_free_buffer(drm_device_t *dev, drm_buf_t *buf)
+{
+ if (!buf) return;
+
+ buf->waiting = 0;
+ buf->pending = 0;
+ buf->filp = NULL;
+ buf->used = 0;
+
+ if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE) && waitqueue_active(&buf->dma_wait)) {
+ wake_up_interruptible(&buf->dma_wait);
+ }
+}
+
+/**
+ * Reclaim the buffers.
+ *
+ * \param filp file pointer.
+ *
+ * Frees each buffer associated with \p filp not already on the hardware.
+ */
+void drm_core_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+
+ if (!dma) return;
+ for (i = 0; i < dma->buf_count; i++) {
+ if (dma->buflist[i]->filp == filp) {
+ switch (dma->buflist[i]->list) {
+ case DRM_LIST_NONE:
+ drm_free_buffer(dev, dma->buflist[i]);
+ break;
+ case DRM_LIST_WAIT:
+ dma->buflist[i]->list = DRM_LIST_RECLAIM;
+ break;
+ default:
+ /* Buffer already on hardware. */
+ break;
+ }
+ }
+ }
+}
+EXPORT_SYMBOL(drm_core_reclaim_buffers);
+
diff --git a/drivers/char/drm/drm_drawable.c b/drivers/char/drm/drm_drawable.c
new file mode 100644
index 000000000000..e8e8e42be4c7
--- /dev/null
+++ b/drivers/char/drm/drm_drawable.c
@@ -0,0 +1,56 @@
+/**
+ * \file drm_drawable.h
+ * IOCTLs for drawables
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/** No-op. */
+int drm_adddraw(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_draw_t draw;
+
+ draw.handle = 0; /* NOOP */
+ DRM_DEBUG("%d\n", draw.handle);
+ if (copy_to_user((drm_draw_t __user *)arg, &draw, sizeof(draw)))
+ return -EFAULT;
+ return 0;
+}
+
+/** No-op. */
+int drm_rmdraw(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ return 0; /* NOOP */
+}
diff --git a/drivers/char/drm/drm_drv.c b/drivers/char/drm/drm_drv.c
new file mode 100644
index 000000000000..1e37ed0c6b8d
--- /dev/null
+++ b/drivers/char/drm/drm_drv.c
@@ -0,0 +1,531 @@
+/**
+ * \file drm_drv.h
+ * Generic driver template
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ *
+ * To use this template, you must at least define the following (samples
+ * given for the MGA driver):
+ *
+ * \code
+ * #define DRIVER_AUTHOR "VA Linux Systems, Inc."
+ *
+ * #define DRIVER_NAME "mga"
+ * #define DRIVER_DESC "Matrox G200/G400"
+ * #define DRIVER_DATE "20001127"
+ *
+ * #define DRIVER_IOCTL_COUNT DRM_ARRAY_SIZE( mga_ioctls )
+ *
+ * #define drm_x mga_##x
+ * \endcode
+ */
+
+/*
+ * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include "drm_core.h"
+
+/** Ioctl table */
+drm_ioctl_desc_t drm_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_IOCTL_VERSION)] = { drm_version, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_UNIQUE)] = { drm_getunique, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_MAGIC)] = { drm_getmagic, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_IRQ_BUSID)] = { drm_irq_by_busid, 0, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_MAP)] = { drm_getmap, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_CLIENT)] = { drm_getclient, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_STATS)] = { drm_getstats, 0, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_SET_VERSION)] = { drm_setversion, 0, 1 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_SET_UNIQUE)] = { drm_setunique, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_BLOCK)] = { drm_noop, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_UNBLOCK)] = { drm_noop, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AUTH_MAGIC)] = { drm_authmagic, 1, 1 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_ADD_MAP)] = { drm_addmap, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_RM_MAP)] = { drm_rmmap, 1, 0 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_SET_SAREA_CTX)] = { drm_setsareactx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_SAREA_CTX)] = { drm_getsareactx, 1, 0 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_ADD_CTX)] = { drm_addctx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_RM_CTX)] = { drm_rmctx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_MOD_CTX)] = { drm_modctx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_GET_CTX)] = { drm_getctx, 1, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_SWITCH_CTX)] = { drm_switchctx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_NEW_CTX)] = { drm_newctx, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_RES_CTX)] = { drm_resctx, 1, 0 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_ADD_DRAW)] = { drm_adddraw, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_RM_DRAW)] = { drm_rmdraw, 1, 1 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_LOCK)] = { drm_lock, 1, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_UNLOCK)] = { drm_unlock, 1, 0 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_FINISH)] = { drm_noop, 1, 0 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_ADD_BUFS)] = { drm_addbufs, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_MARK_BUFS)] = { drm_markbufs, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_INFO_BUFS)] = { drm_infobufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_MAP_BUFS)] = { drm_mapbufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_FREE_BUFS)] = { drm_freebufs, 1, 0 },
+ /* The DRM_IOCTL_DMA ioctl should be defined by the driver. */
+
+ [DRM_IOCTL_NR(DRM_IOCTL_CONTROL)] = { drm_control, 1, 1 },
+
+#if __OS_HAS_AGP
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_ACQUIRE)] = { drm_agp_acquire, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_RELEASE)] = { drm_agp_release, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_ENABLE)] = { drm_agp_enable, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_INFO)] = { drm_agp_info, 1, 0 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_ALLOC)] = { drm_agp_alloc, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_FREE)] = { drm_agp_free, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_BIND)] = { drm_agp_bind, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_AGP_UNBIND)] = { drm_agp_unbind, 1, 1 },
+#endif
+
+ [DRM_IOCTL_NR(DRM_IOCTL_SG_ALLOC)] = { drm_sg_alloc, 1, 1 },
+ [DRM_IOCTL_NR(DRM_IOCTL_SG_FREE)] = { drm_sg_free, 1, 1 },
+
+ [DRM_IOCTL_NR(DRM_IOCTL_WAIT_VBLANK)] = { drm_wait_vblank, 0, 0 },
+};
+
+#define DRIVER_IOCTL_COUNT DRM_ARRAY_SIZE( drm_ioctls )
+
+/**
+ * Take down the DRM device.
+ *
+ * \param dev DRM device structure.
+ *
+ * Frees every resource in \p dev.
+ *
+ * \sa drm_device and setup().
+ */
+int drm_takedown( drm_device_t *dev )
+{
+ drm_magic_entry_t *pt, *next;
+ drm_map_t *map;
+ drm_map_list_t *r_list;
+ struct list_head *list, *list_next;
+ drm_vma_entry_t *vma, *vma_next;
+ int i;
+
+ DRM_DEBUG( "\n" );
+
+ if (dev->driver->pretakedown)
+ dev->driver->pretakedown(dev);
+
+ if (dev->unique) {
+ drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
+ dev->unique = NULL;
+ dev->unique_len = 0;
+ }
+
+ if ( dev->irq_enabled ) drm_irq_uninstall( dev );
+
+ down( &dev->struct_sem );
+ del_timer( &dev->timer );
+
+ /* Clear pid list */
+ for ( i = 0 ; i < DRM_HASH_SIZE ; i++ ) {
+ for ( pt = dev->magiclist[i].head ; pt ; pt = next ) {
+ next = pt->next;
+ drm_free( pt, sizeof(*pt), DRM_MEM_MAGIC );
+ }
+ dev->magiclist[i].head = dev->magiclist[i].tail = NULL;
+ }
+
+ /* Clear AGP information */
+ if (drm_core_has_AGP(dev) && dev->agp) {
+ drm_agp_mem_t *entry;
+ drm_agp_mem_t *nexte;
+
+ /* Remove AGP resources, but leave dev->agp
+ intact until drv_cleanup is called. */
+ for ( entry = dev->agp->memory ; entry ; entry = nexte ) {
+ nexte = entry->next;
+ if ( entry->bound ) drm_unbind_agp( entry->memory );
+ drm_free_agp( entry->memory, entry->pages );
+ drm_free( entry, sizeof(*entry), DRM_MEM_AGPLISTS );
+ }
+ dev->agp->memory = NULL;
+
+ if ( dev->agp->acquired ) drm_agp_do_release(dev);
+
+ dev->agp->acquired = 0;
+ dev->agp->enabled = 0;
+ }
+
+ /* Clear vma list (only built for debugging) */
+ if ( dev->vmalist ) {
+ for ( vma = dev->vmalist ; vma ; vma = vma_next ) {
+ vma_next = vma->next;
+ drm_free( vma, sizeof(*vma), DRM_MEM_VMAS );
+ }
+ dev->vmalist = NULL;
+ }
+
+ if( dev->maplist ) {
+ list_for_each_safe( list, list_next, &dev->maplist->head ) {
+ r_list = (drm_map_list_t *)list;
+
+ if ( ( map = r_list->map ) ) {
+ switch ( map->type ) {
+ case _DRM_REGISTERS:
+ case _DRM_FRAME_BUFFER:
+ if (drm_core_has_MTRR(dev)) {
+ if ( map->mtrr >= 0 ) {
+ int retcode;
+ retcode = mtrr_del( map->mtrr,
+ map->offset,
+ map->size );
+ DRM_DEBUG( "mtrr_del=%d\n", retcode );
+ }
+ }
+ drm_ioremapfree( map->handle, map->size, dev );
+ break;
+ case _DRM_SHM:
+ vfree(map->handle);
+ break;
+
+ case _DRM_AGP:
+ /* Do nothing here, because this is all
+ * handled in the AGP/GART driver.
+ */
+ break;
+ case _DRM_SCATTER_GATHER:
+ /* Handle it */
+ if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg) {
+ drm_sg_cleanup(dev->sg);
+ dev->sg = NULL;
+ }
+ break;
+ }
+ drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+ }
+ list_del( list );
+ drm_free(r_list, sizeof(*r_list), DRM_MEM_MAPS);
+ }
+ drm_free(dev->maplist, sizeof(*dev->maplist), DRM_MEM_MAPS);
+ dev->maplist = NULL;
+ }
+
+ if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE) && dev->queuelist ) {
+ for ( i = 0 ; i < dev->queue_count ; i++ ) {
+ if ( dev->queuelist[i] ) {
+ drm_free( dev->queuelist[i],
+ sizeof(*dev->queuelist[0]),
+ DRM_MEM_QUEUES );
+ dev->queuelist[i] = NULL;
+ }
+ }
+ drm_free( dev->queuelist,
+ dev->queue_slots * sizeof(*dev->queuelist),
+ DRM_MEM_QUEUES );
+ dev->queuelist = NULL;
+ }
+ dev->queue_count = 0;
+
+ if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ drm_dma_takedown( dev );
+
+ if ( dev->lock.hw_lock ) {
+ dev->sigdata.lock = dev->lock.hw_lock = NULL; /* SHM removed */
+ dev->lock.filp = NULL;
+ wake_up_interruptible( &dev->lock.lock_queue );
+ }
+ up( &dev->struct_sem );
+
+ return 0;
+}
+
+
+
+/**
+ * Module initialization. Called via init_module at module load time, or via
+ * linux/init/main.c (this is not currently supported).
+ *
+ * \return zero on success or a negative number on failure.
+ *
+ * Initializes an array of drm_device structures, and attempts to
+ * initialize all available devices, using consecutive minors, registering the
+ * stubs and initializing the AGP device.
+ *
+ * Expands the \c DRIVER_PREINIT and \c DRIVER_POST_INIT macros before and
+ * after the initialization for driver customization.
+ */
+int drm_init( struct drm_driver *driver )
+{
+ struct pci_dev *pdev = NULL;
+ struct pci_device_id *pid;
+ int i;
+
+ DRM_DEBUG( "\n" );
+
+ drm_mem_init();
+
+ for (i=0; driver->pci_driver.id_table[i].vendor != 0; i++) {
+ pid = (struct pci_device_id *)&driver->pci_driver.id_table[i];
+
+ pdev=NULL;
+ /* pass back in pdev to account for multiple identical cards */
+ while ((pdev = pci_get_subsys(pid->vendor, pid->device, pid->subvendor, pid->subdevice, pdev)) != NULL) {
+ /* stealth mode requires a manual probe */
+ pci_dev_get(pdev);
+ drm_get_dev(pdev, pid, driver);
+ }
+ }
+ return 0;
+}
+EXPORT_SYMBOL(drm_init);
+
+/**
+ * Called via cleanup_module() at module unload time.
+ *
+ * Cleans up all DRM device, calling takedown().
+ *
+ * \sa drm_init().
+ */
+static void drm_cleanup( drm_device_t *dev )
+{
+ DRM_DEBUG( "\n" );
+
+ if (!dev) {
+ DRM_ERROR("cleanup called no dev\n");
+ return;
+ }
+
+ drm_takedown( dev );
+
+ drm_ctxbitmap_cleanup( dev );
+
+ if (drm_core_has_MTRR(dev) && drm_core_has_AGP(dev) &&
+ dev->agp && dev->agp->agp_mtrr >= 0) {
+ int retval;
+ retval = mtrr_del( dev->agp->agp_mtrr,
+ dev->agp->agp_info.aper_base,
+ dev->agp->agp_info.aper_size*1024*1024 );
+ DRM_DEBUG( "mtrr_del=%d\n", retval );
+ }
+
+ if (drm_core_has_AGP(dev) && dev->agp ) {
+ drm_free( dev->agp, sizeof(*dev->agp), DRM_MEM_AGPLISTS );
+ dev->agp = NULL;
+ }
+
+ if (dev->driver->postcleanup)
+ dev->driver->postcleanup(dev);
+
+ drm_put_head(&dev->primary);
+ if ( drm_put_dev(dev) )
+ DRM_ERROR( "Cannot unload module\n" );
+}
+
+void drm_exit (struct drm_driver *driver)
+{
+ int i;
+ drm_device_t *dev = NULL;
+ drm_head_t *head;
+
+ DRM_DEBUG( "\n" );
+
+ for (i = 0; i < drm_cards_limit; i++) {
+ head = drm_heads[i];
+ if (!head)
+ continue;
+ if (!head->dev)
+ continue;
+ if (head->dev->driver!=driver)
+ continue;
+ dev=head->dev;
+ }
+ if (dev) {
+ /* release the pci driver */
+ if (dev->pdev)
+ pci_dev_put(dev->pdev);
+ drm_cleanup(dev);
+ }
+ DRM_INFO( "Module unloaded\n" );
+}
+EXPORT_SYMBOL(drm_exit);
+
+/** File operations structure */
+static struct file_operations drm_stub_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_stub_open
+};
+
+static int __init drm_core_init(void)
+{
+ int ret = -ENOMEM;
+
+ drm_cards_limit = (drm_cards_limit < DRM_MAX_MINOR + 1 ? drm_cards_limit : DRM_MAX_MINOR + 1);
+ drm_heads = drm_calloc(drm_cards_limit,
+ sizeof(*drm_heads), DRM_MEM_STUB);
+ if(!drm_heads)
+ goto err_p1;
+
+ if (register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops))
+ goto err_p1;
+
+ drm_class = drm_sysfs_create(THIS_MODULE, "drm");
+ if (IS_ERR(drm_class)) {
+ printk (KERN_ERR "DRM: Error creating drm class.\n");
+ ret = PTR_ERR(drm_class);
+ goto err_p2;
+ }
+
+ drm_proc_root = create_proc_entry("dri", S_IFDIR, NULL);
+ if (!drm_proc_root) {
+ DRM_ERROR("Cannot create /proc/dri\n");
+ ret = -1;
+ goto err_p3;
+ }
+
+ DRM_INFO( "Initialized %s %d.%d.%d %s\n",
+ CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL,
+ CORE_DATE);
+ return 0;
+err_p3:
+ drm_sysfs_destroy(drm_class);
+err_p2:
+ unregister_chrdev(DRM_MAJOR, "drm");
+ drm_free(drm_heads, sizeof(*drm_heads) * drm_cards_limit, DRM_MEM_STUB);
+err_p1:
+ return ret;
+}
+
+static void __exit drm_core_exit (void)
+{
+ remove_proc_entry("dri", NULL);
+ drm_sysfs_destroy(drm_class);
+
+ unregister_chrdev(DRM_MAJOR, "drm");
+
+ drm_free(drm_heads, sizeof(*drm_heads) *
+ drm_cards_limit, DRM_MEM_STUB);
+}
+
+
+module_init( drm_core_init );
+module_exit( drm_core_exit );
+
+
+/**
+ * Get version information
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_version structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Fills in the version information in \p arg.
+ */
+int drm_version( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_version_t __user *argp = (void __user *)arg;
+ drm_version_t version;
+ int ret;
+
+ if ( copy_from_user( &version, argp, sizeof(version) ) )
+ return -EFAULT;
+
+ /* version is a required function to return the personality module version */
+ if ((ret = dev->driver->version(&version)))
+ return ret;
+
+ if ( copy_to_user( argp, &version, sizeof(version) ) )
+ return -EFAULT;
+ return 0;
+}
+
+
+
+/**
+ * Called whenever a process performs an ioctl on /dev/drm.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or negative number on failure.
+ *
+ * Looks up the ioctl function in the ::ioctls table, checking for root
+ * previleges if so required, and dispatches to the respective function.
+ */
+int drm_ioctl( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_ioctl_desc_t *ioctl;
+ drm_ioctl_t *func;
+ unsigned int nr = DRM_IOCTL_NR(cmd);
+ int retcode = -EINVAL;
+
+ atomic_inc( &dev->ioctl_count );
+ atomic_inc( &dev->counts[_DRM_STAT_IOCTLS] );
+ ++priv->ioctl_count;
+
+ DRM_DEBUG( "pid=%d, cmd=0x%02x, nr=0x%02x, dev 0x%lx, auth=%d\n",
+ current->pid, cmd, nr, (long)old_encode_dev(priv->head->device),
+ priv->authenticated );
+
+ if (nr < DRIVER_IOCTL_COUNT)
+ ioctl = &drm_ioctls[nr];
+ else if ((nr >= DRM_COMMAND_BASE) && (nr < DRM_COMMAND_BASE + dev->driver->num_ioctls))
+ ioctl = &dev->driver->ioctls[nr - DRM_COMMAND_BASE];
+ else
+ goto err_i1;
+
+ func = ioctl->func;
+ /* is there a local override? */
+ if ((nr == DRM_IOCTL_NR(DRM_IOCTL_DMA)) && dev->driver->dma_ioctl)
+ func = dev->driver->dma_ioctl;
+
+ if ( !func ) {
+ DRM_DEBUG( "no function\n" );
+ retcode = -EINVAL;
+ } else if ( ( ioctl->root_only && !capable( CAP_SYS_ADMIN ) )||
+ ( ioctl->auth_needed && !priv->authenticated ) ) {
+ retcode = -EACCES;
+ } else {
+ retcode = func( inode, filp, cmd, arg );
+ }
+
+err_i1:
+ atomic_dec( &dev->ioctl_count );
+ if (retcode) DRM_DEBUG( "ret = %x\n", retcode);
+ return retcode;
+}
+EXPORT_SYMBOL(drm_ioctl);
+
diff --git a/drivers/char/drm/drm_fops.c b/drivers/char/drm/drm_fops.c
new file mode 100644
index 000000000000..906794247aeb
--- /dev/null
+++ b/drivers/char/drm/drm_fops.c
@@ -0,0 +1,451 @@
+/**
+ * \file drm_fops.h
+ * File operations for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Daryll Strauss <daryll@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan 4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include <linux/poll.h>
+
+static int drm_setup( drm_device_t *dev )
+{
+ int i;
+ int ret;
+
+ if (dev->driver->presetup)
+ {
+ ret=dev->driver->presetup(dev);
+ if (ret!=0)
+ return ret;
+ }
+
+ atomic_set( &dev->ioctl_count, 0 );
+ atomic_set( &dev->vma_count, 0 );
+ dev->buf_use = 0;
+ atomic_set( &dev->buf_alloc, 0 );
+
+ if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ {
+ i = drm_dma_setup( dev );
+ if ( i < 0 )
+ return i;
+ }
+
+ for ( i = 0 ; i < DRM_ARRAY_SIZE(dev->counts) ; i++ )
+ atomic_set( &dev->counts[i], 0 );
+
+ for ( i = 0 ; i < DRM_HASH_SIZE ; i++ ) {
+ dev->magiclist[i].head = NULL;
+ dev->magiclist[i].tail = NULL;
+ }
+
+ dev->maplist = drm_alloc(sizeof(*dev->maplist),
+ DRM_MEM_MAPS);
+ if(dev->maplist == NULL) return -ENOMEM;
+ memset(dev->maplist, 0, sizeof(*dev->maplist));
+ INIT_LIST_HEAD(&dev->maplist->head);
+
+ dev->ctxlist = drm_alloc(sizeof(*dev->ctxlist),
+ DRM_MEM_CTXLIST);
+ if(dev->ctxlist == NULL) return -ENOMEM;
+ memset(dev->ctxlist, 0, sizeof(*dev->ctxlist));
+ INIT_LIST_HEAD(&dev->ctxlist->head);
+
+ dev->vmalist = NULL;
+ dev->sigdata.lock = dev->lock.hw_lock = NULL;
+ init_waitqueue_head( &dev->lock.lock_queue );
+ dev->queue_count = 0;
+ dev->queue_reserved = 0;
+ dev->queue_slots = 0;
+ dev->queuelist = NULL;
+ dev->irq_enabled = 0;
+ dev->context_flag = 0;
+ dev->interrupt_flag = 0;
+ dev->dma_flag = 0;
+ dev->last_context = 0;
+ dev->last_switch = 0;
+ dev->last_checked = 0;
+ init_waitqueue_head( &dev->context_wait );
+ dev->if_version = 0;
+
+ dev->ctx_start = 0;
+ dev->lck_start = 0;
+
+ dev->buf_rp = dev->buf;
+ dev->buf_wp = dev->buf;
+ dev->buf_end = dev->buf + DRM_BSZ;
+ dev->buf_async = NULL;
+ init_waitqueue_head( &dev->buf_readers );
+ init_waitqueue_head( &dev->buf_writers );
+
+ DRM_DEBUG( "\n" );
+
+ /*
+ * The kernel's context could be created here, but is now created
+ * in drm_dma_enqueue. This is more resource-efficient for
+ * hardware that does not do DMA, but may mean that
+ * drm_select_queue fails between the time the interrupt is
+ * initialized and the time the queues are initialized.
+ */
+ if (dev->driver->postsetup)
+ dev->driver->postsetup(dev);
+
+ return 0;
+}
+
+/**
+ * Open file.
+ *
+ * \param inode device inode
+ * \param filp file pointer.
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches the DRM device with the same minor number, calls open_helper(), and
+ * increments the device open count. If the open count was previous at zero,
+ * i.e., it's the first that the device is open, then calls setup().
+ */
+int drm_open( struct inode *inode, struct file *filp )
+{
+ drm_device_t *dev = NULL;
+ int minor = iminor(inode);
+ int retcode = 0;
+
+ if (!((minor >= 0) && (minor < drm_cards_limit)))
+ return -ENODEV;
+
+ if (!drm_heads[minor])
+ return -ENODEV;
+
+ if (!(dev = drm_heads[minor]->dev))
+ return -ENODEV;
+
+ retcode = drm_open_helper( inode, filp, dev );
+ if ( !retcode ) {
+ atomic_inc( &dev->counts[_DRM_STAT_OPENS] );
+ spin_lock( &dev->count_lock );
+ if ( !dev->open_count++ ) {
+ spin_unlock( &dev->count_lock );
+ return drm_setup( dev );
+ }
+ spin_unlock( &dev->count_lock );
+ }
+
+ return retcode;
+}
+EXPORT_SYMBOL(drm_open);
+
+/**
+ * Release file.
+ *
+ * \param inode device inode
+ * \param filp file pointer.
+ * \return zero on success or a negative number on failure.
+ *
+ * If the hardware lock is held then free it, and take it again for the kernel
+ * context since it's necessary to reclaim buffers. Unlink the file private
+ * data from its list and free it. Decreases the open count and if it reaches
+ * zero calls takedown().
+ */
+int drm_release( struct inode *inode, struct file *filp )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev;
+ int retcode = 0;
+
+ lock_kernel();
+ dev = priv->head->dev;
+
+ DRM_DEBUG( "open_count = %d\n", dev->open_count );
+
+ if (dev->driver->prerelease)
+ dev->driver->prerelease(dev, filp);
+
+ /* ========================================================
+ * Begin inline drm_release
+ */
+
+ DRM_DEBUG( "pid = %d, device = 0x%lx, open_count = %d\n",
+ current->pid, (long)old_encode_dev(priv->head->device), dev->open_count );
+
+ if ( priv->lock_count && dev->lock.hw_lock &&
+ _DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) &&
+ dev->lock.filp == filp ) {
+ DRM_DEBUG( "File %p released, freeing lock for context %d\n",
+ filp,
+ _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock) );
+
+ if (dev->driver->release)
+ dev->driver->release(dev, filp);
+
+ drm_lock_free( dev, &dev->lock.hw_lock->lock,
+ _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock) );
+
+ /* FIXME: may require heavy-handed reset of
+ hardware at this point, possibly
+ processed via a callback to the X
+ server. */
+ }
+ else if ( dev->driver->release && priv->lock_count && dev->lock.hw_lock ) {
+ /* The lock is required to reclaim buffers */
+ DECLARE_WAITQUEUE( entry, current );
+
+ add_wait_queue( &dev->lock.lock_queue, &entry );
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if ( !dev->lock.hw_lock ) {
+ /* Device has been unregistered */
+ retcode = -EINTR;
+ break;
+ }
+ if ( drm_lock_take( &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT ) ) {
+ dev->lock.filp = filp;
+ dev->lock.lock_time = jiffies;
+ atomic_inc( &dev->counts[_DRM_STAT_LOCKS] );
+ break; /* Got lock */
+ }
+ /* Contention */
+ schedule();
+ if ( signal_pending( current ) ) {
+ retcode = -ERESTARTSYS;
+ break;
+ }
+ }
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue( &dev->lock.lock_queue, &entry );
+ if( !retcode ) {
+ if (dev->driver->release)
+ dev->driver->release(dev, filp);
+ drm_lock_free( dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT );
+ }
+ }
+
+ if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+ {
+ dev->driver->reclaim_buffers(dev, filp);
+ }
+
+ drm_fasync( -1, filp, 0 );
+
+ down( &dev->ctxlist_sem );
+ if ( !list_empty( &dev->ctxlist->head ) ) {
+ drm_ctx_list_t *pos, *n;
+
+ list_for_each_entry_safe( pos, n, &dev->ctxlist->head, head ) {
+ if ( pos->tag == priv &&
+ pos->handle != DRM_KERNEL_CONTEXT ) {
+ if (dev->driver->context_dtor)
+ dev->driver->context_dtor(dev, pos->handle);
+
+ drm_ctxbitmap_free( dev, pos->handle );
+
+ list_del( &pos->head );
+ drm_free( pos, sizeof(*pos), DRM_MEM_CTXLIST );
+ --dev->ctx_count;
+ }
+ }
+ }
+ up( &dev->ctxlist_sem );
+
+ down( &dev->struct_sem );
+ if ( priv->remove_auth_on_close == 1 ) {
+ drm_file_t *temp = dev->file_first;
+ while ( temp ) {
+ temp->authenticated = 0;
+ temp = temp->next;
+ }
+ }
+ if ( priv->prev ) {
+ priv->prev->next = priv->next;
+ } else {
+ dev->file_first = priv->next;
+ }
+ if ( priv->next ) {
+ priv->next->prev = priv->prev;
+ } else {
+ dev->file_last = priv->prev;
+ }
+ up( &dev->struct_sem );
+
+ if (dev->driver->free_filp_priv)
+ dev->driver->free_filp_priv(dev, priv);
+
+ drm_free( priv, sizeof(*priv), DRM_MEM_FILES );
+
+ /* ========================================================
+ * End inline drm_release
+ */
+
+ atomic_inc( &dev->counts[_DRM_STAT_CLOSES] );
+ spin_lock( &dev->count_lock );
+ if ( !--dev->open_count ) {
+ if ( atomic_read( &dev->ioctl_count ) || dev->blocked ) {
+ DRM_ERROR( "Device busy: %d %d\n",
+ atomic_read( &dev->ioctl_count ),
+ dev->blocked );
+ spin_unlock( &dev->count_lock );
+ unlock_kernel();
+ return -EBUSY;
+ }
+ spin_unlock( &dev->count_lock );
+ unlock_kernel();
+ return drm_takedown( dev );
+ }
+ spin_unlock( &dev->count_lock );
+
+ unlock_kernel();
+
+ return retcode;
+}
+EXPORT_SYMBOL(drm_release);
+
+/**
+ * Called whenever a process opens /dev/drm.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param dev device.
+ * \return zero on success or a negative number on failure.
+ *
+ * Creates and initializes a drm_file structure for the file private data in \p
+ * filp and add it into the double linked list in \p dev.
+ */
+int drm_open_helper(struct inode *inode, struct file *filp, drm_device_t *dev)
+{
+ int minor = iminor(inode);
+ drm_file_t *priv;
+ int ret;
+
+ if (filp->f_flags & O_EXCL) return -EBUSY; /* No exclusive opens */
+ if (!drm_cpu_valid()) return -EINVAL;
+
+ DRM_DEBUG("pid = %d, minor = %d\n", current->pid, minor);
+
+ priv = drm_alloc(sizeof(*priv), DRM_MEM_FILES);
+ if(!priv) return -ENOMEM;
+
+ memset(priv, 0, sizeof(*priv));
+ filp->private_data = priv;
+ priv->uid = current->euid;
+ priv->pid = current->pid;
+ priv->minor = minor;
+ priv->head = drm_heads[minor];
+ priv->ioctl_count = 0;
+ priv->authenticated = capable(CAP_SYS_ADMIN);
+ priv->lock_count = 0;
+
+ if (dev->driver->open_helper) {
+ ret=dev->driver->open_helper(dev, priv);
+ if (ret < 0)
+ goto out_free;
+ }
+
+ down(&dev->struct_sem);
+ if (!dev->file_last) {
+ priv->next = NULL;
+ priv->prev = NULL;
+ dev->file_first = priv;
+ dev->file_last = priv;
+ } else {
+ priv->next = NULL;
+ priv->prev = dev->file_last;
+ dev->file_last->next = priv;
+ dev->file_last = priv;
+ }
+ up(&dev->struct_sem);
+
+#ifdef __alpha__
+ /*
+ * Default the hose
+ */
+ if (!dev->hose) {
+ struct pci_dev *pci_dev;
+ pci_dev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+ if (pci_dev) {
+ dev->hose = pci_dev->sysdata;
+ pci_dev_put(pci_dev);
+ }
+ if (!dev->hose) {
+ struct pci_bus *b = pci_bus_b(pci_root_buses.next);
+ if (b) dev->hose = b->sysdata;
+ }
+ }
+#endif
+
+ return 0;
+out_free:
+ drm_free(priv, sizeof(*priv), DRM_MEM_FILES);
+ filp->private_data=NULL;
+ return ret;
+}
+
+/** No-op. */
+int drm_flush(struct file *filp)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
+ current->pid, (long)old_encode_dev(priv->head->device), dev->open_count);
+ return 0;
+}
+EXPORT_SYMBOL(drm_flush);
+
+/** No-op. */
+int drm_fasync(int fd, struct file *filp, int on)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ int retcode;
+
+ DRM_DEBUG("fd = %d, device = 0x%lx\n", fd, (long)old_encode_dev(priv->head->device));
+ retcode = fasync_helper(fd, filp, on, &dev->buf_async);
+ if (retcode < 0) return retcode;
+ return 0;
+}
+EXPORT_SYMBOL(drm_fasync);
+
+/** No-op. */
+unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait)
+{
+ return 0;
+}
+EXPORT_SYMBOL(drm_poll);
+
+
+/** No-op. */
+ssize_t drm_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
+{
+ return 0;
+}
diff --git a/drivers/char/drm/drm_init.c b/drivers/char/drm/drm_init.c
new file mode 100644
index 000000000000..62883b749e97
--- /dev/null
+++ b/drivers/char/drm/drm_init.c
@@ -0,0 +1,52 @@
+/**
+ * \file drm_init.h
+ * Setup/Cleanup for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan 4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Check whether DRI will run on this CPU.
+ *
+ * \return non-zero if the DRI will run on this CPU, or zero otherwise.
+ */
+int drm_cpu_valid(void)
+{
+#if defined(__i386__)
+ if (boot_cpu_data.x86 == 3) return 0; /* No cmpxchg on a 386 */
+#endif
+#if defined(__sparc__) && !defined(__sparc_v9__)
+ return 0; /* No cmpxchg before v9 sparc. */
+#endif
+ return 1;
+}
diff --git a/drivers/char/drm/drm_ioctl.c b/drivers/char/drm/drm_ioctl.c
new file mode 100644
index 000000000000..39afda0ccabe
--- /dev/null
+++ b/drivers/char/drm/drm_ioctl.c
@@ -0,0 +1,370 @@
+/**
+ * \file drm_ioctl.h
+ * IOCTL processing for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Jan 8 09:01:26 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include "drm_core.h"
+
+#include "linux/pci.h"
+
+/**
+ * Get the bus id.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_unique structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Copies the bus id from drm_device::unique into user space.
+ */
+int drm_getunique(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_unique_t __user *argp = (void __user *)arg;
+ drm_unique_t u;
+
+ if (copy_from_user(&u, argp, sizeof(u)))
+ return -EFAULT;
+ if (u.unique_len >= dev->unique_len) {
+ if (copy_to_user(u.unique, dev->unique, dev->unique_len))
+ return -EFAULT;
+ }
+ u.unique_len = dev->unique_len;
+ if (copy_to_user(argp, &u, sizeof(u)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Set the bus id.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_unique structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Copies the bus id from userspace into drm_device::unique, and verifies that
+ * it matches the device this DRM is attached to (EINVAL otherwise). Deprecated
+ * in interface version 1.1 and will return EBUSY when setversion has requested
+ * version 1.1 or greater.
+ */
+int drm_setunique(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_unique_t u;
+ int domain, bus, slot, func, ret;
+
+ if (dev->unique_len || dev->unique) return -EBUSY;
+
+ if (copy_from_user(&u, (drm_unique_t __user *)arg, sizeof(u)))
+ return -EFAULT;
+
+ if (!u.unique_len || u.unique_len > 1024) return -EINVAL;
+
+ dev->unique_len = u.unique_len;
+ dev->unique = drm_alloc(u.unique_len + 1, DRM_MEM_DRIVER);
+ if(!dev->unique) return -ENOMEM;
+ if (copy_from_user(dev->unique, u.unique, dev->unique_len))
+ return -EFAULT;
+
+ dev->unique[dev->unique_len] = '\0';
+
+ dev->devname = drm_alloc(strlen(dev->driver->pci_driver.name) + strlen(dev->unique) + 2,
+ DRM_MEM_DRIVER);
+ if (!dev->devname)
+ return -ENOMEM;
+
+ sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name, dev->unique);
+
+ /* Return error if the busid submitted doesn't match the device's actual
+ * busid.
+ */
+ ret = sscanf(dev->unique, "PCI:%d:%d:%d", &bus, &slot, &func);
+ if (ret != 3)
+ return DRM_ERR(EINVAL);
+ domain = bus >> 8;
+ bus &= 0xff;
+
+ if ((domain != dev->pci_domain) ||
+ (bus != dev->pci_bus) ||
+ (slot != dev->pci_slot) ||
+ (func != dev->pci_func))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int
+drm_set_busid(drm_device_t *dev)
+{
+ if (dev->unique != NULL)
+ return EBUSY;
+
+ dev->unique_len = 20;
+ dev->unique = drm_alloc(dev->unique_len + 1, DRM_MEM_DRIVER);
+ if (dev->unique == NULL)
+ return ENOMEM;
+
+ snprintf(dev->unique, dev->unique_len, "pci:%04x:%02x:%02x.%d",
+ dev->pci_domain, dev->pci_bus, dev->pci_slot, dev->pci_func);
+
+ dev->devname = drm_alloc(strlen(dev->driver->pci_driver.name) + dev->unique_len + 2,
+ DRM_MEM_DRIVER);
+ if (dev->devname == NULL)
+ return ENOMEM;
+
+ sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name, dev->unique);
+
+ return 0;
+}
+
+
+/**
+ * Get a mapping information.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_map structure.
+ *
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches for the mapping with the specified offset and copies its information
+ * into userspace
+ */
+int drm_getmap( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_map_t __user *argp = (void __user *)arg;
+ drm_map_t map;
+ drm_map_list_t *r_list = NULL;
+ struct list_head *list;
+ int idx;
+ int i;
+
+ if (copy_from_user(&map, argp, sizeof(map)))
+ return -EFAULT;
+ idx = map.offset;
+
+ down(&dev->struct_sem);
+ if (idx < 0) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+
+ i = 0;
+ list_for_each(list, &dev->maplist->head) {
+ if(i == idx) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ break;
+ }
+ i++;
+ }
+ if(!r_list || !r_list->map) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+
+ map.offset = r_list->map->offset;
+ map.size = r_list->map->size;
+ map.type = r_list->map->type;
+ map.flags = r_list->map->flags;
+ map.handle = r_list->map->handle;
+ map.mtrr = r_list->map->mtrr;
+ up(&dev->struct_sem);
+
+ if (copy_to_user(argp, &map, sizeof(map))) return -EFAULT;
+ return 0;
+}
+
+/**
+ * Get client information.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_client structure.
+ *
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches for the client with the specified index and copies its information
+ * into userspace
+ */
+int drm_getclient( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_client_t __user *argp = (void __user *)arg;
+ drm_client_t client;
+ drm_file_t *pt;
+ int idx;
+ int i;
+
+ if (copy_from_user(&client, argp, sizeof(client)))
+ return -EFAULT;
+ idx = client.idx;
+ down(&dev->struct_sem);
+ for (i = 0, pt = dev->file_first; i < idx && pt; i++, pt = pt->next)
+ ;
+
+ if (!pt) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+ client.auth = pt->authenticated;
+ client.pid = pt->pid;
+ client.uid = pt->uid;
+ client.magic = pt->magic;
+ client.iocs = pt->ioctl_count;
+ up(&dev->struct_sem);
+
+ if (copy_to_user((drm_client_t __user *)arg, &client, sizeof(client)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Get statistics information.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_stats structure.
+ *
+ * \return zero on success or a negative number on failure.
+ */
+int drm_getstats( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_stats_t stats;
+ int i;
+
+ memset(&stats, 0, sizeof(stats));
+
+ down(&dev->struct_sem);
+
+ for (i = 0; i < dev->counters; i++) {
+ if (dev->types[i] == _DRM_STAT_LOCK)
+ stats.data[i].value
+ = (dev->lock.hw_lock
+ ? dev->lock.hw_lock->lock : 0);
+ else
+ stats.data[i].value = atomic_read(&dev->counts[i]);
+ stats.data[i].type = dev->types[i];
+ }
+
+ stats.count = dev->counters;
+
+ up(&dev->struct_sem);
+
+ if (copy_to_user((drm_stats_t __user *)arg, &stats, sizeof(stats)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Setversion ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Sets the requested interface version
+ */
+int drm_setversion(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_set_version_t sv;
+ drm_set_version_t retv;
+ int if_version;
+ drm_set_version_t __user *argp = (void __user *)data;
+ drm_version_t version;
+
+ DRM_COPY_FROM_USER_IOCTL(sv, argp, sizeof(sv));
+
+ memset(&version, 0, sizeof(version));
+
+ dev->driver->version(&version);
+ retv.drm_di_major = DRM_IF_MAJOR;
+ retv.drm_di_minor = DRM_IF_MINOR;
+ retv.drm_dd_major = version.version_major;
+ retv.drm_dd_minor = version.version_minor;
+
+ DRM_COPY_TO_USER_IOCTL(argp, retv, sizeof(sv));
+
+ if (sv.drm_di_major != -1) {
+ if (sv.drm_di_major != DRM_IF_MAJOR ||
+ sv.drm_di_minor < 0 || sv.drm_di_minor > DRM_IF_MINOR)
+ return EINVAL;
+ if_version = DRM_IF_VERSION(sv.drm_di_major, sv.drm_dd_minor);
+ dev->if_version = DRM_MAX(if_version, dev->if_version);
+ if (sv.drm_di_minor >= 1) {
+ /*
+ * Version 1.1 includes tying of DRM to specific device
+ */
+ drm_set_busid(dev);
+ }
+ }
+
+ if (sv.drm_dd_major != -1) {
+ if (sv.drm_dd_major != version.version_major ||
+ sv.drm_dd_minor < 0 || sv.drm_dd_minor > version.version_minor)
+ return EINVAL;
+
+ if (dev->driver->set_version)
+ dev->driver->set_version(dev, &sv);
+ }
+ return 0;
+}
+
+/** No-op ioctl. */
+int drm_noop(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ DRM_DEBUG("\n");
+ return 0;
+}
diff --git a/drivers/char/drm/drm_irq.c b/drivers/char/drm/drm_irq.c
new file mode 100644
index 000000000000..2e236ebcf27b
--- /dev/null
+++ b/drivers/char/drm/drm_irq.c
@@ -0,0 +1,370 @@
+/**
+ * \file drm_irq.h
+ * IRQ support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+#include <linux/interrupt.h> /* For task queue support */
+
+/**
+ * Get interrupt from bus id.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_irq_busid structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Finds the PCI device with the specified bus id and gets its IRQ number.
+ * This IOCTL is deprecated, and will now return EINVAL for any busid not equal
+ * to that of the device that this DRM instance attached to.
+ */
+int drm_irq_by_busid(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_irq_busid_t __user *argp = (void __user *)arg;
+ drm_irq_busid_t p;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ return -EINVAL;
+
+ if (copy_from_user(&p, argp, sizeof(p)))
+ return -EFAULT;
+
+ if ((p.busnum >> 8) != dev->pci_domain ||
+ (p.busnum & 0xff) != dev->pci_bus ||
+ p.devnum != dev->pci_slot ||
+ p.funcnum != dev->pci_func)
+ return -EINVAL;
+
+ p.irq = dev->irq;
+
+ DRM_DEBUG("%d:%d:%d => IRQ %d\n",
+ p.busnum, p.devnum, p.funcnum, p.irq);
+ if (copy_to_user(argp, &p, sizeof(p)))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * Install IRQ handler.
+ *
+ * \param dev DRM device.
+ * \param irq IRQ number.
+ *
+ * Initializes the IRQ related data, and setups drm_device::vbl_queue. Installs the handler, calling the driver
+ * \c drm_driver_irq_preinstall() and \c drm_driver_irq_postinstall() functions
+ * before and after the installation.
+ */
+int drm_irq_install( drm_device_t *dev )
+{
+ int ret;
+ unsigned long sh_flags=0;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ return -EINVAL;
+
+ if ( dev->irq == 0 )
+ return -EINVAL;
+
+ down( &dev->struct_sem );
+
+ /* Driver must have been initialized */
+ if ( !dev->dev_private ) {
+ up( &dev->struct_sem );
+ return -EINVAL;
+ }
+
+ if ( dev->irq_enabled ) {
+ up( &dev->struct_sem );
+ return -EBUSY;
+ }
+ dev->irq_enabled = 1;
+ up( &dev->struct_sem );
+
+ DRM_DEBUG( "%s: irq=%d\n", __FUNCTION__, dev->irq );
+
+ if (drm_core_check_feature(dev, DRIVER_IRQ_VBL)) {
+ init_waitqueue_head(&dev->vbl_queue);
+
+ spin_lock_init( &dev->vbl_lock );
+
+ INIT_LIST_HEAD( &dev->vbl_sigs.head );
+
+ dev->vbl_pending = 0;
+ }
+
+ /* Before installing handler */
+ dev->driver->irq_preinstall(dev);
+
+ /* Install handler */
+ if (drm_core_check_feature(dev, DRIVER_IRQ_SHARED))
+ sh_flags = SA_SHIRQ;
+
+ ret = request_irq( dev->irq, dev->driver->irq_handler,
+ sh_flags, dev->devname, dev );
+ if ( ret < 0 ) {
+ down( &dev->struct_sem );
+ dev->irq_enabled = 0;
+ up( &dev->struct_sem );
+ return ret;
+ }
+
+ /* After installing handler */
+ dev->driver->irq_postinstall(dev);
+
+ return 0;
+}
+
+/**
+ * Uninstall the IRQ handler.
+ *
+ * \param dev DRM device.
+ *
+ * Calls the driver's \c drm_driver_irq_uninstall() function, and stops the irq.
+ */
+int drm_irq_uninstall( drm_device_t *dev )
+{
+ int irq_enabled;
+
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ return -EINVAL;
+
+ down( &dev->struct_sem );
+ irq_enabled = dev->irq_enabled;
+ dev->irq_enabled = 0;
+ up( &dev->struct_sem );
+
+ if ( !irq_enabled )
+ return -EINVAL;
+
+ DRM_DEBUG( "%s: irq=%d\n", __FUNCTION__, dev->irq );
+
+ dev->driver->irq_uninstall(dev);
+
+ free_irq( dev->irq, dev );
+
+ return 0;
+}
+EXPORT_SYMBOL(drm_irq_uninstall);
+
+/**
+ * IRQ control ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_control structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls irq_install() or irq_uninstall() according to \p arg.
+ */
+int drm_control( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_control_t ctl;
+
+ /* if we haven't irq we fallback for compatibility reasons - this used to be a separate function in drm_dma.h */
+
+ if ( copy_from_user( &ctl, (drm_control_t __user *)arg, sizeof(ctl) ) )
+ return -EFAULT;
+
+ switch ( ctl.func ) {
+ case DRM_INST_HANDLER:
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ return 0;
+ if (dev->if_version < DRM_IF_VERSION(1, 2) &&
+ ctl.irq != dev->irq)
+ return -EINVAL;
+ return drm_irq_install( dev );
+ case DRM_UNINST_HANDLER:
+ if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ return 0;
+ return drm_irq_uninstall( dev );
+ default:
+ return -EINVAL;
+ }
+}
+
+/**
+ * Wait for VBLANK.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param data user argument, pointing to a drm_wait_vblank structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the IRQ is installed.
+ *
+ * If a signal is requested checks if this task has already scheduled the same signal
+ * for the same vblank sequence number - nothing to be done in
+ * that case. If the number of tasks waiting for the interrupt exceeds 100 the
+ * function fails. Otherwise adds a new entry to drm_device::vbl_sigs for this
+ * task.
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+int drm_wait_vblank( DRM_IOCTL_ARGS )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_wait_vblank_t __user *argp = (void __user *)data;
+ drm_wait_vblank_t vblwait;
+ struct timeval now;
+ int ret = 0;
+ unsigned int flags;
+
+ if (!drm_core_check_feature(dev, DRIVER_IRQ_VBL))
+ return -EINVAL;
+
+ if (!dev->irq)
+ return -EINVAL;
+
+ DRM_COPY_FROM_USER_IOCTL( vblwait, argp, sizeof(vblwait) );
+
+ switch ( vblwait.request.type & ~_DRM_VBLANK_FLAGS_MASK ) {
+ case _DRM_VBLANK_RELATIVE:
+ vblwait.request.sequence += atomic_read( &dev->vbl_received );
+ vblwait.request.type &= ~_DRM_VBLANK_RELATIVE;
+ case _DRM_VBLANK_ABSOLUTE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ flags = vblwait.request.type & _DRM_VBLANK_FLAGS_MASK;
+
+ if ( flags & _DRM_VBLANK_SIGNAL ) {
+ unsigned long irqflags;
+ drm_vbl_sig_t *vbl_sig;
+
+ vblwait.reply.sequence = atomic_read( &dev->vbl_received );
+
+ spin_lock_irqsave( &dev->vbl_lock, irqflags );
+
+ /* Check if this task has already scheduled the same signal
+ * for the same vblank sequence number; nothing to be done in
+ * that case
+ */
+ list_for_each_entry( vbl_sig, &dev->vbl_sigs.head, head ) {
+ if (vbl_sig->sequence == vblwait.request.sequence
+ && vbl_sig->info.si_signo == vblwait.request.signal
+ && vbl_sig->task == current)
+ {
+ spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+ goto done;
+ }
+ }
+
+ if ( dev->vbl_pending >= 100 ) {
+ spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+ return -EBUSY;
+ }
+
+ dev->vbl_pending++;
+
+ spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+
+ if ( !( vbl_sig = drm_alloc( sizeof( drm_vbl_sig_t ), DRM_MEM_DRIVER ) ) ) {
+ return -ENOMEM;
+ }
+
+ memset( (void *)vbl_sig, 0, sizeof(*vbl_sig) );
+
+ vbl_sig->sequence = vblwait.request.sequence;
+ vbl_sig->info.si_signo = vblwait.request.signal;
+ vbl_sig->task = current;
+
+ spin_lock_irqsave( &dev->vbl_lock, irqflags );
+
+ list_add_tail( (struct list_head *) vbl_sig, &dev->vbl_sigs.head );
+
+ spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+ } else {
+ if (dev->driver->vblank_wait)
+ ret = dev->driver->vblank_wait( dev, &vblwait.request.sequence );
+
+ do_gettimeofday( &now );
+ vblwait.reply.tval_sec = now.tv_sec;
+ vblwait.reply.tval_usec = now.tv_usec;
+ }
+
+done:
+ DRM_COPY_TO_USER_IOCTL( argp, vblwait, sizeof(vblwait) );
+
+ return ret;
+}
+
+/**
+ * Send the VBLANK signals.
+ *
+ * \param dev DRM device.
+ *
+ * Sends a signal for each task in drm_device::vbl_sigs and empties the list.
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+void drm_vbl_send_signals( drm_device_t *dev )
+{
+ struct list_head *list, *tmp;
+ drm_vbl_sig_t *vbl_sig;
+ unsigned int vbl_seq = atomic_read( &dev->vbl_received );
+ unsigned long flags;
+
+ spin_lock_irqsave( &dev->vbl_lock, flags );
+
+ list_for_each_safe( list, tmp, &dev->vbl_sigs.head ) {
+ vbl_sig = list_entry( list, drm_vbl_sig_t, head );
+ if ( ( vbl_seq - vbl_sig->sequence ) <= (1<<23) ) {
+ vbl_sig->info.si_code = vbl_seq;
+ send_sig_info( vbl_sig->info.si_signo, &vbl_sig->info, vbl_sig->task );
+
+ list_del( list );
+
+ drm_free( vbl_sig, sizeof(*vbl_sig), DRM_MEM_DRIVER );
+
+ dev->vbl_pending--;
+ }
+ }
+
+ spin_unlock_irqrestore( &dev->vbl_lock, flags );
+}
+EXPORT_SYMBOL(drm_vbl_send_signals);
+
+
diff --git a/drivers/char/drm/drm_lock.c b/drivers/char/drm/drm_lock.c
new file mode 100644
index 000000000000..d0d6fc661625
--- /dev/null
+++ b/drivers/char/drm/drm_lock.c
@@ -0,0 +1,303 @@
+/**
+ * \file drm_lock.h
+ * IOCTLs for locking
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Lock ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Add the current task to the lock wait queue, and attempt to take to lock.
+ */
+int drm_lock( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ DECLARE_WAITQUEUE( entry, current );
+ drm_lock_t lock;
+ int ret = 0;
+
+ ++priv->lock_count;
+
+ if ( copy_from_user( &lock, (drm_lock_t __user *)arg, sizeof(lock) ) )
+ return -EFAULT;
+
+ if ( lock.context == DRM_KERNEL_CONTEXT ) {
+ DRM_ERROR( "Process %d using kernel context %d\n",
+ current->pid, lock.context );
+ return -EINVAL;
+ }
+
+ DRM_DEBUG( "%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n",
+ lock.context, current->pid,
+ dev->lock.hw_lock->lock, lock.flags );
+
+ if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE))
+ if ( lock.context < 0 )
+ return -EINVAL;
+
+ add_wait_queue( &dev->lock.lock_queue, &entry );
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if ( !dev->lock.hw_lock ) {
+ /* Device has been unregistered */
+ ret = -EINTR;
+ break;
+ }
+ if ( drm_lock_take( &dev->lock.hw_lock->lock,
+ lock.context ) ) {
+ dev->lock.filp = filp;
+ dev->lock.lock_time = jiffies;
+ atomic_inc( &dev->counts[_DRM_STAT_LOCKS] );
+ break; /* Got lock */
+ }
+
+ /* Contention */
+ schedule();
+ if ( signal_pending( current ) ) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+ }
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue( &dev->lock.lock_queue, &entry );
+
+ sigemptyset( &dev->sigmask );
+ sigaddset( &dev->sigmask, SIGSTOP );
+ sigaddset( &dev->sigmask, SIGTSTP );
+ sigaddset( &dev->sigmask, SIGTTIN );
+ sigaddset( &dev->sigmask, SIGTTOU );
+ dev->sigdata.context = lock.context;
+ dev->sigdata.lock = dev->lock.hw_lock;
+ block_all_signals( drm_notifier,
+ &dev->sigdata, &dev->sigmask );
+
+ if (dev->driver->dma_ready && (lock.flags & _DRM_LOCK_READY))
+ dev->driver->dma_ready(dev);
+
+ if ( dev->driver->dma_quiescent && (lock.flags & _DRM_LOCK_QUIESCENT ))
+ return dev->driver->dma_quiescent(dev);
+
+ /* dev->driver->kernel_context_switch isn't used by any of the x86
+ * drivers but is used by the Sparc driver.
+ */
+
+ if (dev->driver->kernel_context_switch &&
+ dev->last_context != lock.context) {
+ dev->driver->kernel_context_switch(dev, dev->last_context,
+ lock.context);
+ }
+ DRM_DEBUG( "%d %s\n", lock.context, ret ? "interrupted" : "has lock" );
+
+ return ret;
+}
+
+/**
+ * Unlock ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Transfer and free the lock.
+ */
+int drm_unlock( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_lock_t lock;
+
+ if ( copy_from_user( &lock, (drm_lock_t __user *)arg, sizeof(lock) ) )
+ return -EFAULT;
+
+ if ( lock.context == DRM_KERNEL_CONTEXT ) {
+ DRM_ERROR( "Process %d using kernel context %d\n",
+ current->pid, lock.context );
+ return -EINVAL;
+ }
+
+ atomic_inc( &dev->counts[_DRM_STAT_UNLOCKS] );
+
+ /* kernel_context_switch isn't used by any of the x86 drm
+ * modules but is required by the Sparc driver.
+ */
+ if (dev->driver->kernel_context_switch_unlock)
+ dev->driver->kernel_context_switch_unlock(dev, &lock);
+ else {
+ drm_lock_transfer( dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT );
+
+ if ( drm_lock_free( dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT ) ) {
+ DRM_ERROR( "\n" );
+ }
+ }
+
+ unblock_all_signals();
+ return 0;
+}
+
+/**
+ * Take the heavyweight lock.
+ *
+ * \param lock lock pointer.
+ * \param context locking context.
+ * \return one if the lock is held, or zero otherwise.
+ *
+ * Attempt to mark the lock as held by the given context, via the \p cmpxchg instruction.
+ */
+int drm_lock_take(__volatile__ unsigned int *lock, unsigned int context)
+{
+ unsigned int old, new, prev;
+
+ do {
+ old = *lock;
+ if (old & _DRM_LOCK_HELD) new = old | _DRM_LOCK_CONT;
+ else new = context | _DRM_LOCK_HELD;
+ prev = cmpxchg(lock, old, new);
+ } while (prev != old);
+ if (_DRM_LOCKING_CONTEXT(old) == context) {
+ if (old & _DRM_LOCK_HELD) {
+ if (context != DRM_KERNEL_CONTEXT) {
+ DRM_ERROR("%d holds heavyweight lock\n",
+ context);
+ }
+ return 0;
+ }
+ }
+ if (new == (context | _DRM_LOCK_HELD)) {
+ /* Have lock */
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * This takes a lock forcibly and hands it to context. Should ONLY be used
+ * inside *_unlock to give lock to kernel before calling *_dma_schedule.
+ *
+ * \param dev DRM device.
+ * \param lock lock pointer.
+ * \param context locking context.
+ * \return always one.
+ *
+ * Resets the lock file pointer.
+ * Marks the lock as held by the given context, via the \p cmpxchg instruction.
+ */
+int drm_lock_transfer(drm_device_t *dev,
+ __volatile__ unsigned int *lock, unsigned int context)
+{
+ unsigned int old, new, prev;
+
+ dev->lock.filp = NULL;
+ do {
+ old = *lock;
+ new = context | _DRM_LOCK_HELD;
+ prev = cmpxchg(lock, old, new);
+ } while (prev != old);
+ return 1;
+}
+
+/**
+ * Free lock.
+ *
+ * \param dev DRM device.
+ * \param lock lock.
+ * \param context context.
+ *
+ * Resets the lock file pointer.
+ * Marks the lock as not held, via the \p cmpxchg instruction. Wakes any task
+ * waiting on the lock queue.
+ */
+int drm_lock_free(drm_device_t *dev,
+ __volatile__ unsigned int *lock, unsigned int context)
+{
+ unsigned int old, new, prev;
+
+ dev->lock.filp = NULL;
+ do {
+ old = *lock;
+ new = 0;
+ prev = cmpxchg(lock, old, new);
+ } while (prev != old);
+ if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) {
+ DRM_ERROR("%d freed heavyweight lock held by %d\n",
+ context,
+ _DRM_LOCKING_CONTEXT(old));
+ return 1;
+ }
+ wake_up_interruptible(&dev->lock.lock_queue);
+ return 0;
+}
+
+/**
+ * If we get here, it means that the process has called DRM_IOCTL_LOCK
+ * without calling DRM_IOCTL_UNLOCK.
+ *
+ * If the lock is not held, then let the signal proceed as usual. If the lock
+ * is held, then set the contended flag and keep the signal blocked.
+ *
+ * \param priv pointer to a drm_sigdata structure.
+ * \return one if the signal should be delivered normally, or zero if the
+ * signal should be blocked.
+ */
+int drm_notifier(void *priv)
+{
+ drm_sigdata_t *s = (drm_sigdata_t *)priv;
+ unsigned int old, new, prev;
+
+
+ /* Allow signal delivery if lock isn't held */
+ if (!s->lock || !_DRM_LOCK_IS_HELD(s->lock->lock)
+ || _DRM_LOCKING_CONTEXT(s->lock->lock) != s->context) return 1;
+
+ /* Otherwise, set flag to force call to
+ drmUnlock */
+ do {
+ old = s->lock->lock;
+ new = old | _DRM_LOCK_CONT;
+ prev = cmpxchg(&s->lock->lock, old, new);
+ } while (prev != old);
+ return 0;
+}
diff --git a/drivers/char/drm/drm_memory.c b/drivers/char/drm/drm_memory.c
new file mode 100644
index 000000000000..7f53f756c052
--- /dev/null
+++ b/drivers/char/drm/drm_memory.c
@@ -0,0 +1,181 @@
+/**
+ * \file drm_memory.h
+ * Memory management wrappers for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Thu Feb 4 14:00:34 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/highmem.h>
+#include "drmP.h"
+
+#ifdef DEBUG_MEMORY
+#include "drm_memory_debug.h"
+#else
+
+/** No-op. */
+void drm_mem_init(void)
+{
+}
+
+/**
+ * Called when "/proc/dri/%dev%/mem" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param len requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ *
+ * No-op.
+ */
+int drm_mem_info(char *buf, char **start, off_t offset,
+ int len, int *eof, void *data)
+{
+ return 0;
+}
+
+/** Wrapper around kmalloc() */
+void *drm_calloc(size_t nmemb, size_t size, int area)
+{
+ void *addr;
+
+ addr = kmalloc(size * nmemb, GFP_KERNEL);
+ if (addr != NULL)
+ memset((void *)addr, 0, size * nmemb);
+
+ return addr;
+}
+EXPORT_SYMBOL(drm_calloc);
+
+/** Wrapper around kmalloc() and kfree() */
+void *drm_realloc(void *oldpt, size_t oldsize, size_t size, int area)
+{
+ void *pt;
+
+ if (!(pt = kmalloc(size, GFP_KERNEL))) return NULL;
+ if (oldpt && oldsize) {
+ memcpy(pt, oldpt, oldsize);
+ kfree(oldpt);
+ }
+ return pt;
+}
+
+/**
+ * Allocate pages.
+ *
+ * \param order size order.
+ * \param area memory area. (Not used.)
+ * \return page address on success, or zero on failure.
+ *
+ * Allocate and reserve free pages.
+ */
+unsigned long drm_alloc_pages(int order, int area)
+{
+ unsigned long address;
+ unsigned long bytes = PAGE_SIZE << order;
+ unsigned long addr;
+ unsigned int sz;
+
+ address = __get_free_pages(GFP_KERNEL, order);
+ if (!address)
+ return 0;
+
+ /* Zero */
+ memset((void *)address, 0, bytes);
+
+ /* Reserve */
+ for (addr = address, sz = bytes;
+ sz > 0;
+ addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+ SetPageReserved(virt_to_page(addr));
+ }
+
+ return address;
+}
+
+/**
+ * Free pages.
+ *
+ * \param address address of the pages to free.
+ * \param order size order.
+ * \param area memory area. (Not used.)
+ *
+ * Unreserve and free pages allocated by alloc_pages().
+ */
+void drm_free_pages(unsigned long address, int order, int area)
+{
+ unsigned long bytes = PAGE_SIZE << order;
+ unsigned long addr;
+ unsigned int sz;
+
+ if (!address)
+ return;
+
+ /* Unreserve */
+ for (addr = address, sz = bytes;
+ sz > 0;
+ addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+ ClearPageReserved(virt_to_page(addr));
+ }
+
+ free_pages(address, order);
+}
+
+
+#if __OS_HAS_AGP
+/** Wrapper around agp_allocate_memory() */
+DRM_AGP_MEM *drm_alloc_agp(struct agp_bridge_data *bridge, int pages, u32 type)
+{
+ return drm_agp_allocate_memory(bridge, pages, type);
+}
+
+/** Wrapper around agp_free_memory() */
+int drm_free_agp(DRM_AGP_MEM *handle, int pages)
+{
+ return drm_agp_free_memory(handle) ? 0 : -EINVAL;
+}
+
+/** Wrapper around agp_bind_memory() */
+int drm_bind_agp(DRM_AGP_MEM *handle, unsigned int start)
+{
+ return drm_agp_bind_memory(handle, start);
+}
+
+/** Wrapper around agp_unbind_memory() */
+int drm_unbind_agp(DRM_AGP_MEM *handle)
+{
+ return drm_agp_unbind_memory(handle);
+}
+#endif /* agp */
+#endif /* debug_memory */
diff --git a/drivers/char/drm/drm_memory.h b/drivers/char/drm/drm_memory.h
new file mode 100644
index 000000000000..422b94268709
--- /dev/null
+++ b/drivers/char/drm/drm_memory.h
@@ -0,0 +1,197 @@
+/**
+ * \file drm_memory.h
+ * Memory management wrappers for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Thu Feb 4 14:00:34 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/highmem.h>
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+/**
+ * Cut down version of drm_memory_debug.h, which used to be called
+ * drm_memory.h.
+ */
+
+#if __OS_HAS_AGP
+
+#include <linux/vmalloc.h>
+
+#ifdef HAVE_PAGE_AGP
+#include <asm/agp.h>
+#else
+# ifdef __powerpc__
+# define PAGE_AGP __pgprot(_PAGE_KERNEL | _PAGE_NO_CACHE)
+# else
+# define PAGE_AGP PAGE_KERNEL
+# endif
+#endif
+
+/*
+ * Find the drm_map that covers the range [offset, offset+size).
+ */
+static inline drm_map_t *
+drm_lookup_map (unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ struct list_head *list;
+ drm_map_list_t *r_list;
+ drm_map_t *map;
+
+ list_for_each(list, &dev->maplist->head) {
+ r_list = (drm_map_list_t *) list;
+ map = r_list->map;
+ if (!map)
+ continue;
+ if (map->offset <= offset && (offset + size) <= (map->offset + map->size))
+ return map;
+ }
+ return NULL;
+}
+
+static inline void *
+agp_remap (unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ unsigned long *phys_addr_map, i, num_pages = PAGE_ALIGN(size) / PAGE_SIZE;
+ struct drm_agp_mem *agpmem;
+ struct page **page_map;
+ void *addr;
+
+ size = PAGE_ALIGN(size);
+
+#ifdef __alpha__
+ offset -= dev->hose->mem_space->start;
+#endif
+
+ for (agpmem = dev->agp->memory; agpmem; agpmem = agpmem->next)
+ if (agpmem->bound <= offset
+ && (agpmem->bound + (agpmem->pages << PAGE_SHIFT)) >= (offset + size))
+ break;
+ if (!agpmem)
+ return NULL;
+
+ /*
+ * OK, we're mapping AGP space on a chipset/platform on which memory accesses by
+ * the CPU do not get remapped by the GART. We fix this by using the kernel's
+ * page-table instead (that's probably faster anyhow...).
+ */
+ /* note: use vmalloc() because num_pages could be large... */
+ page_map = vmalloc(num_pages * sizeof(struct page *));
+ if (!page_map)
+ return NULL;
+
+ phys_addr_map = agpmem->memory->memory + (offset - agpmem->bound) / PAGE_SIZE;
+ for (i = 0; i < num_pages; ++i)
+ page_map[i] = pfn_to_page(phys_addr_map[i] >> PAGE_SHIFT);
+ addr = vmap(page_map, num_pages, VM_IOREMAP, PAGE_AGP);
+ vfree(page_map);
+
+ return addr;
+}
+
+static inline unsigned long
+drm_follow_page (void *vaddr)
+{
+ pgd_t *pgd = pgd_offset_k((unsigned long) vaddr);
+ pud_t *pud = pud_offset(pgd, (unsigned long) vaddr);
+ pmd_t *pmd = pmd_offset(pud, (unsigned long) vaddr);
+ pte_t *ptep = pte_offset_kernel(pmd, (unsigned long) vaddr);
+ return pte_pfn(*ptep) << PAGE_SHIFT;
+}
+
+#else /* __OS_HAS_AGP */
+
+static inline drm_map_t *drm_lookup_map(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ return NULL;
+}
+
+static inline void *agp_remap(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ return NULL;
+}
+
+static inline unsigned long drm_follow_page (void *vaddr)
+{
+ return 0;
+}
+
+#endif
+
+static inline void *drm_ioremap(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture) {
+ drm_map_t *map = drm_lookup_map(offset, size, dev);
+
+ if (map && map->type == _DRM_AGP)
+ return agp_remap(offset, size, dev);
+ }
+ return ioremap(offset, size);
+}
+
+static inline void *drm_ioremap_nocache(unsigned long offset, unsigned long size,
+ drm_device_t *dev)
+{
+ if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture) {
+ drm_map_t *map = drm_lookup_map(offset, size, dev);
+
+ if (map && map->type == _DRM_AGP)
+ return agp_remap(offset, size, dev);
+ }
+ return ioremap_nocache(offset, size);
+}
+
+static inline void drm_ioremapfree(void *pt, unsigned long size, drm_device_t *dev)
+{
+ /*
+ * This is a bit ugly. It would be much cleaner if the DRM API would use separate
+ * routines for handling mappings in the AGP space. Hopefully this can be done in
+ * a future revision of the interface...
+ */
+ if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture
+ && ((unsigned long) pt >= VMALLOC_START && (unsigned long) pt < VMALLOC_END))
+ {
+ unsigned long offset;
+ drm_map_t *map;
+
+ offset = drm_follow_page(pt) | ((unsigned long) pt & ~PAGE_MASK);
+ map = drm_lookup_map(offset, size, dev);
+ if (map && map->type == _DRM_AGP) {
+ vunmap(pt);
+ return;
+ }
+ }
+
+ iounmap(pt);
+}
+
+
diff --git a/drivers/char/drm/drm_memory_debug.h b/drivers/char/drm/drm_memory_debug.h
new file mode 100644
index 000000000000..2c82e69a7fd2
--- /dev/null
+++ b/drivers/char/drm/drm_memory_debug.h
@@ -0,0 +1,459 @@
+/**
+ * \file drm_memory.h
+ * Memory management wrappers for DRM.
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+
+typedef struct drm_mem_stats {
+ const char *name;
+ int succeed_count;
+ int free_count;
+ int fail_count;
+ unsigned long bytes_allocated;
+ unsigned long bytes_freed;
+} drm_mem_stats_t;
+
+static DEFINE_SPINLOCK(DRM(mem_lock));
+static unsigned long DRM(ram_available) = 0; /* In pages */
+static unsigned long DRM(ram_used) = 0;
+static drm_mem_stats_t DRM(mem_stats)[] = {
+ [DRM_MEM_DMA] = { "dmabufs" },
+ [DRM_MEM_SAREA] = { "sareas" },
+ [DRM_MEM_DRIVER] = { "driver" },
+ [DRM_MEM_MAGIC] = { "magic" },
+ [DRM_MEM_IOCTLS] = { "ioctltab" },
+ [DRM_MEM_MAPS] = { "maplist" },
+ [DRM_MEM_VMAS] = { "vmalist" },
+ [DRM_MEM_BUFS] = { "buflist" },
+ [DRM_MEM_SEGS] = { "seglist" },
+ [DRM_MEM_PAGES] = { "pagelist" },
+ [DRM_MEM_FILES] = { "files" },
+ [DRM_MEM_QUEUES] = { "queues" },
+ [DRM_MEM_CMDS] = { "commands" },
+ [DRM_MEM_MAPPINGS] = { "mappings" },
+ [DRM_MEM_BUFLISTS] = { "buflists" },
+ [DRM_MEM_AGPLISTS] = { "agplist" },
+ [DRM_MEM_SGLISTS] = { "sglist" },
+ [DRM_MEM_TOTALAGP] = { "totalagp" },
+ [DRM_MEM_BOUNDAGP] = { "boundagp" },
+ [DRM_MEM_CTXBITMAP] = { "ctxbitmap"},
+ [DRM_MEM_CTXLIST] = { "ctxlist" },
+ [DRM_MEM_STUB] = { "stub" },
+ { NULL, 0, } /* Last entry must be null */
+};
+
+void DRM(mem_init)(void)
+{
+ drm_mem_stats_t *mem;
+ struct sysinfo si;
+
+ for (mem = DRM(mem_stats); mem->name; ++mem) {
+ mem->succeed_count = 0;
+ mem->free_count = 0;
+ mem->fail_count = 0;
+ mem->bytes_allocated = 0;
+ mem->bytes_freed = 0;
+ }
+
+ si_meminfo(&si);
+ DRM(ram_available) = si.totalram;
+ DRM(ram_used) = 0;
+}
+
+/* drm_mem_info is called whenever a process reads /dev/drm/mem. */
+
+static int DRM(_mem_info)(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data)
+{
+ drm_mem_stats_t *pt;
+ int len = 0;
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *eof = 0;
+ *start = &buf[offset];
+
+ DRM_PROC_PRINT(" total counts "
+ " | outstanding \n");
+ DRM_PROC_PRINT("type alloc freed fail bytes freed"
+ " | allocs bytes\n\n");
+ DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu kB |\n",
+ "system", 0, 0, 0,
+ DRM(ram_available) << (PAGE_SHIFT - 10));
+ DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu kB |\n",
+ "locked", 0, 0, 0, DRM(ram_used) >> 10);
+ DRM_PROC_PRINT("\n");
+ for (pt = DRM(mem_stats); pt->name; pt++) {
+ DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu %10lu | %6d %10ld\n",
+ pt->name,
+ pt->succeed_count,
+ pt->free_count,
+ pt->fail_count,
+ pt->bytes_allocated,
+ pt->bytes_freed,
+ pt->succeed_count - pt->free_count,
+ (long)pt->bytes_allocated
+ - (long)pt->bytes_freed);
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+int DRM(mem_info)(char *buf, char **start, off_t offset,
+ int len, int *eof, void *data)
+{
+ int ret;
+
+ spin_lock(&DRM(mem_lock));
+ ret = DRM(_mem_info)(buf, start, offset, len, eof, data);
+ spin_unlock(&DRM(mem_lock));
+ return ret;
+}
+
+void *DRM(alloc)(size_t size, int area)
+{
+ void *pt;
+
+ if (!size) {
+ DRM_MEM_ERROR(area, "Allocating 0 bytes\n");
+ return NULL;
+ }
+
+ if (!(pt = kmalloc(size, GFP_KERNEL))) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[area].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return NULL;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[area].succeed_count;
+ DRM(mem_stats)[area].bytes_allocated += size;
+ spin_unlock(&DRM(mem_lock));
+ return pt;
+}
+
+void *DRM(calloc)(size_t nmemb, size_t size, int area)
+{
+ void *addr;
+
+ addr = DRM(alloc)(nmemb * size, area);
+ if (addr != NULL)
+ memset((void *)addr, 0, size * nmemb);
+
+ return addr;
+}
+
+void *DRM(realloc)(void *oldpt, size_t oldsize, size_t size, int area)
+{
+ void *pt;
+
+ if (!(pt = DRM(alloc)(size, area))) return NULL;
+ if (oldpt && oldsize) {
+ memcpy(pt, oldpt, oldsize);
+ DRM(free)(oldpt, oldsize, area);
+ }
+ return pt;
+}
+
+void DRM(free)(void *pt, size_t size, int area)
+{
+ int alloc_count;
+ int free_count;
+
+ if (!pt) DRM_MEM_ERROR(area, "Attempt to free NULL pointer\n");
+ else kfree(pt);
+ spin_lock(&DRM(mem_lock));
+ DRM(mem_stats)[area].bytes_freed += size;
+ free_count = ++DRM(mem_stats)[area].free_count;
+ alloc_count = DRM(mem_stats)[area].succeed_count;
+ spin_unlock(&DRM(mem_lock));
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(area, "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+}
+
+unsigned long DRM(alloc_pages)(int order, int area)
+{
+ unsigned long address;
+ unsigned long bytes = PAGE_SIZE << order;
+ unsigned long addr;
+ unsigned int sz;
+
+ spin_lock(&DRM(mem_lock));
+ if ((DRM(ram_used) >> PAGE_SHIFT)
+ > (DRM_RAM_PERCENT * DRM(ram_available)) / 100) {
+ spin_unlock(&DRM(mem_lock));
+ return 0;
+ }
+ spin_unlock(&DRM(mem_lock));
+
+ address = __get_free_pages(GFP_KERNEL, order);
+ if (!address) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[area].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return 0;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[area].succeed_count;
+ DRM(mem_stats)[area].bytes_allocated += bytes;
+ DRM(ram_used) += bytes;
+ spin_unlock(&DRM(mem_lock));
+
+
+ /* Zero outside the lock */
+ memset((void *)address, 0, bytes);
+
+ /* Reserve */
+ for (addr = address, sz = bytes;
+ sz > 0;
+ addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+ SetPageReserved(virt_to_page(addr));
+ }
+
+ return address;
+}
+
+void DRM(free_pages)(unsigned long address, int order, int area)
+{
+ unsigned long bytes = PAGE_SIZE << order;
+ int alloc_count;
+ int free_count;
+ unsigned long addr;
+ unsigned int sz;
+
+ if (!address) {
+ DRM_MEM_ERROR(area, "Attempt to free address 0\n");
+ } else {
+ /* Unreserve */
+ for (addr = address, sz = bytes;
+ sz > 0;
+ addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+ ClearPageReserved(virt_to_page(addr));
+ }
+ free_pages(address, order);
+ }
+
+ spin_lock(&DRM(mem_lock));
+ free_count = ++DRM(mem_stats)[area].free_count;
+ alloc_count = DRM(mem_stats)[area].succeed_count;
+ DRM(mem_stats)[area].bytes_freed += bytes;
+ DRM(ram_used) -= bytes;
+ spin_unlock(&DRM(mem_lock));
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(area,
+ "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+}
+
+void *DRM(ioremap)(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ void *pt;
+
+ if (!size) {
+ DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+ "Mapping 0 bytes at 0x%08lx\n", offset);
+ return NULL;
+ }
+
+ if (!(pt = drm_ioremap(offset, size, dev))) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_MAPPINGS].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return NULL;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+ DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_allocated += size;
+ spin_unlock(&DRM(mem_lock));
+ return pt;
+}
+
+void *DRM(ioremap_nocache)(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+ void *pt;
+
+ if (!size) {
+ DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+ "Mapping 0 bytes at 0x%08lx\n", offset);
+ return NULL;
+ }
+
+ if (!(pt = drm_ioremap_nocache(offset, size, dev))) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_MAPPINGS].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return NULL;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+ DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_allocated += size;
+ spin_unlock(&DRM(mem_lock));
+ return pt;
+}
+
+void DRM(ioremapfree)(void *pt, unsigned long size, drm_device_t *dev)
+{
+ int alloc_count;
+ int free_count;
+
+ if (!pt)
+ DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+ "Attempt to free NULL pointer\n");
+ else
+ drm_ioremapfree(pt, size, dev);
+
+ spin_lock(&DRM(mem_lock));
+ DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_freed += size;
+ free_count = ++DRM(mem_stats)[DRM_MEM_MAPPINGS].free_count;
+ alloc_count = DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+ spin_unlock(&DRM(mem_lock));
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+ "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+}
+
+#if __OS_HAS_AGP
+
+DRM_AGP_MEM *DRM(alloc_agp)(int pages, u32 type)
+{
+ DRM_AGP_MEM *handle;
+
+ if (!pages) {
+ DRM_MEM_ERROR(DRM_MEM_TOTALAGP, "Allocating 0 pages\n");
+ return NULL;
+ }
+
+ if ((handle = DRM(agp_allocate_memory)(pages, type))) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_TOTALAGP].succeed_count;
+ DRM(mem_stats)[DRM_MEM_TOTALAGP].bytes_allocated
+ += pages << PAGE_SHIFT;
+ spin_unlock(&DRM(mem_lock));
+ return handle;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_TOTALAGP].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return NULL;
+}
+
+int DRM(free_agp)(DRM_AGP_MEM *handle, int pages)
+{
+ int alloc_count;
+ int free_count;
+ int retval = -EINVAL;
+
+ if (!handle) {
+ DRM_MEM_ERROR(DRM_MEM_TOTALAGP,
+ "Attempt to free NULL AGP handle\n");
+ return retval;
+ }
+
+ if (DRM(agp_free_memory)(handle)) {
+ spin_lock(&DRM(mem_lock));
+ free_count = ++DRM(mem_stats)[DRM_MEM_TOTALAGP].free_count;
+ alloc_count = DRM(mem_stats)[DRM_MEM_TOTALAGP].succeed_count;
+ DRM(mem_stats)[DRM_MEM_TOTALAGP].bytes_freed
+ += pages << PAGE_SHIFT;
+ spin_unlock(&DRM(mem_lock));
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(DRM_MEM_TOTALAGP,
+ "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+ return 0;
+ }
+ return retval;
+}
+
+int DRM(bind_agp)(DRM_AGP_MEM *handle, unsigned int start)
+{
+ int retcode = -EINVAL;
+
+ if (!handle) {
+ DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+ "Attempt to bind NULL AGP handle\n");
+ return retcode;
+ }
+
+ if (!(retcode = DRM(agp_bind_memory)(handle, start))) {
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_BOUNDAGP].succeed_count;
+ DRM(mem_stats)[DRM_MEM_BOUNDAGP].bytes_allocated
+ += handle->page_count << PAGE_SHIFT;
+ spin_unlock(&DRM(mem_lock));
+ return retcode;
+ }
+ spin_lock(&DRM(mem_lock));
+ ++DRM(mem_stats)[DRM_MEM_BOUNDAGP].fail_count;
+ spin_unlock(&DRM(mem_lock));
+ return retcode;
+}
+
+int DRM(unbind_agp)(DRM_AGP_MEM *handle)
+{
+ int alloc_count;
+ int free_count;
+ int retcode = -EINVAL;
+
+ if (!handle) {
+ DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+ "Attempt to unbind NULL AGP handle\n");
+ return retcode;
+ }
+
+ if ((retcode = DRM(agp_unbind_memory)(handle))) return retcode;
+ spin_lock(&DRM(mem_lock));
+ free_count = ++DRM(mem_stats)[DRM_MEM_BOUNDAGP].free_count;
+ alloc_count = DRM(mem_stats)[DRM_MEM_BOUNDAGP].succeed_count;
+ DRM(mem_stats)[DRM_MEM_BOUNDAGP].bytes_freed
+ += handle->page_count << PAGE_SHIFT;
+ spin_unlock(&DRM(mem_lock));
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+ "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+ return retcode;
+}
+#endif
diff --git a/drivers/char/drm/drm_os_linux.h b/drivers/char/drm/drm_os_linux.h
new file mode 100644
index 000000000000..b14cd370dea5
--- /dev/null
+++ b/drivers/char/drm/drm_os_linux.h
@@ -0,0 +1,149 @@
+/**
+ * \file drm_os_linux.h
+ * OS abstraction macros.
+ */
+
+
+#include <linux/interrupt.h> /* For task queue support */
+#include <linux/delay.h>
+
+/** File pointer type */
+#define DRMFILE struct file *
+/** Ioctl arguments */
+#define DRM_IOCTL_ARGS struct inode *inode, struct file *filp, unsigned int cmd, unsigned long data
+#define DRM_ERR(d) -(d)
+/** Current process ID */
+#define DRM_CURRENTPID current->pid
+#define DRM_UDELAY(d) udelay(d)
+/** Read a byte from a MMIO region */
+#define DRM_READ8(map, offset) readb(((void __iomem *)(map)->handle) + (offset))
+/** Read a word from a MMIO region */
+#define DRM_READ16(map, offset) readw(((void __iomem *)(map)->handle) + (offset))
+/** Read a dword from a MMIO region */
+#define DRM_READ32(map, offset) readl(((void __iomem *)(map)->handle) + (offset))
+/** Write a byte into a MMIO region */
+#define DRM_WRITE8(map, offset, val) writeb(val, ((void __iomem *)(map)->handle) + (offset))
+/** Write a word into a MMIO region */
+#define DRM_WRITE16(map, offset, val) writew(val, ((void __iomem *)(map)->handle) + (offset))
+/** Write a dword into a MMIO region */
+#define DRM_WRITE32(map, offset, val) writel(val, ((void __iomem *)(map)->handle) + (offset))
+/** Read memory barrier */
+#define DRM_READMEMORYBARRIER() rmb()
+/** Write memory barrier */
+#define DRM_WRITEMEMORYBARRIER() wmb()
+/** Read/write memory barrier */
+#define DRM_MEMORYBARRIER() mb()
+/** DRM device local declaration */
+#define DRM_DEVICE drm_file_t *priv = filp->private_data; \
+ drm_device_t *dev = priv->head->dev
+
+/** IRQ handler arguments and return type and values */
+#define DRM_IRQ_ARGS int irq, void *arg, struct pt_regs *regs
+
+/** AGP types */
+#if __OS_HAS_AGP
+#define DRM_AGP_MEM struct agp_memory
+#define DRM_AGP_KERN struct agp_kern_info
+#else
+/* define some dummy types for non AGP supporting kernels */
+struct no_agp_kern {
+ unsigned long aper_base;
+ unsigned long aper_size;
+};
+#define DRM_AGP_MEM int
+#define DRM_AGP_KERN struct no_agp_kern
+#endif
+
+#if !(__OS_HAS_MTRR)
+static __inline__ int mtrr_add (unsigned long base, unsigned long size,
+ unsigned int type, char increment)
+{
+ return -ENODEV;
+}
+
+static __inline__ int mtrr_del (int reg, unsigned long base,
+ unsigned long size)
+{
+ return -ENODEV;
+}
+#define MTRR_TYPE_WRCOMB 1
+
+#endif
+
+/** Task queue handler arguments */
+#define DRM_TASKQUEUE_ARGS void *arg
+
+/** For data going into the kernel through the ioctl argument */
+#define DRM_COPY_FROM_USER_IOCTL(arg1, arg2, arg3) \
+ if ( copy_from_user(&arg1, arg2, arg3) ) \
+ return -EFAULT
+/** For data going from the kernel through the ioctl argument */
+#define DRM_COPY_TO_USER_IOCTL(arg1, arg2, arg3) \
+ if ( copy_to_user(arg1, &arg2, arg3) ) \
+ return -EFAULT
+/** Other copying of data to kernel space */
+#define DRM_COPY_FROM_USER(arg1, arg2, arg3) \
+ copy_from_user(arg1, arg2, arg3)
+/** Other copying of data from kernel space */
+#define DRM_COPY_TO_USER(arg1, arg2, arg3) \
+ copy_to_user(arg1, arg2, arg3)
+/* Macros for copyfrom user, but checking readability only once */
+#define DRM_VERIFYAREA_READ( uaddr, size ) \
+ (access_ok( VERIFY_READ, uaddr, size ) ? 0 : -EFAULT)
+#define DRM_COPY_FROM_USER_UNCHECKED(arg1, arg2, arg3) \
+ __copy_from_user(arg1, arg2, arg3)
+#define DRM_COPY_TO_USER_UNCHECKED(arg1, arg2, arg3) \
+ __copy_to_user(arg1, arg2, arg3)
+#define DRM_GET_USER_UNCHECKED(val, uaddr) \
+ __get_user(val, uaddr)
+
+#define DRM_GET_PRIV_WITH_RETURN(_priv, _filp) _priv = _filp->private_data
+
+/**
+ * Get the pointer to the SAREA.
+ *
+ * Searches the SAREA on the mapping lists and points drm_device::sarea to it.
+ */
+#define DRM_GETSAREA() \
+do { \
+ drm_map_list_t *entry; \
+ list_for_each_entry( entry, &dev->maplist->head, head ) { \
+ if ( entry->map && \
+ entry->map->type == _DRM_SHM && \
+ (entry->map->flags & _DRM_CONTAINS_LOCK) ) { \
+ dev_priv->sarea = entry->map; \
+ break; \
+ } \
+ } \
+} while (0)
+
+#define DRM_HZ HZ
+
+#define DRM_WAIT_ON( ret, queue, timeout, condition ) \
+do { \
+ DECLARE_WAITQUEUE(entry, current); \
+ unsigned long end = jiffies + (timeout); \
+ add_wait_queue(&(queue), &entry); \
+ \
+ for (;;) { \
+ __set_current_state(TASK_INTERRUPTIBLE); \
+ if (condition) \
+ break; \
+ if (time_after_eq(jiffies, end)) { \
+ ret = -EBUSY; \
+ break; \
+ } \
+ schedule_timeout((HZ/100 > 1) ? HZ/100 : 1); \
+ if (signal_pending(current)) { \
+ ret = -EINTR; \
+ break; \
+ } \
+ } \
+ __set_current_state(TASK_RUNNING); \
+ remove_wait_queue(&(queue), &entry); \
+} while (0)
+
+
+#define DRM_WAKEUP( queue ) wake_up_interruptible( queue )
+#define DRM_INIT_WAITQUEUE( queue ) init_waitqueue_head( queue )
+
diff --git a/drivers/char/drm/drm_pci.c b/drivers/char/drm/drm_pci.c
new file mode 100644
index 000000000000..192e8762571c
--- /dev/null
+++ b/drivers/char/drm/drm_pci.c
@@ -0,0 +1,140 @@
+/* drm_pci.h -- PCI DMA memory management wrappers for DRM -*- linux-c -*- */
+/**
+ * \file drm_pci.c
+ * \brief Functions and ioctls to manage PCI memory
+ *
+ * \warning These interfaces aren't stable yet.
+ *
+ * \todo Implement the remaining ioctl's for the PCI pools.
+ * \todo The wrappers here are so thin that they would be better off inlined..
+ *
+ * \author Jose Fonseca <jrfonseca@tungstengraphics.com>
+ * \author Leif Delgass <ldelgass@retinalburn.net>
+ */
+
+/*
+ * Copyright 2003 Jos�Fonseca.
+ * Copyright 2003 Leif Delgass.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/pci.h>
+#include "drmP.h"
+
+/**********************************************************************/
+/** \name PCI memory */
+/*@{*/
+
+/**
+ * \brief Allocate a PCI consistent memory block, for DMA.
+ */
+void *drm_pci_alloc(drm_device_t * dev, size_t size, size_t align,
+ dma_addr_t maxaddr, dma_addr_t * busaddr)
+{
+ void *address;
+#if DRM_DEBUG_MEMORY
+ int area = DRM_MEM_DMA;
+
+ spin_lock(&drm_mem_lock);
+ if ((drm_ram_used >> PAGE_SHIFT)
+ > (DRM_RAM_PERCENT * drm_ram_available) / 100) {
+ spin_unlock(&drm_mem_lock);
+ return 0;
+ }
+ spin_unlock(&drm_mem_lock);
+#endif
+
+ /* pci_alloc_consistent only guarantees alignment to the smallest
+ * PAGE_SIZE order which is greater than or equal to the requested size.
+ * Return NULL here for now to make sure nobody tries for larger alignment
+ */
+ if (align > size)
+ return NULL;
+
+ if (pci_set_dma_mask(dev->pdev, maxaddr) != 0) {
+ DRM_ERROR("Setting pci dma mask failed\n");
+ return NULL;
+ }
+
+ address = pci_alloc_consistent(dev->pdev, size, busaddr);
+
+#if DRM_DEBUG_MEMORY
+ if (address == NULL) {
+ spin_lock(&drm_mem_lock);
+ ++drm_mem_stats[area].fail_count;
+ spin_unlock(&drm_mem_lock);
+ return NULL;
+ }
+
+ spin_lock(&drm_mem_lock);
+ ++drm_mem_stats[area].succeed_count;
+ drm_mem_stats[area].bytes_allocated += size;
+ drm_ram_used += size;
+ spin_unlock(&drm_mem_lock);
+#else
+ if (address == NULL)
+ return NULL;
+#endif
+
+ memset(address, 0, size);
+
+ return address;
+}
+EXPORT_SYMBOL(drm_pci_alloc);
+
+/**
+ * \brief Free a PCI consistent memory block.
+ */
+void
+drm_pci_free(drm_device_t * dev, size_t size, void *vaddr, dma_addr_t busaddr)
+{
+#if DRM_DEBUG_MEMORY
+ int area = DRM_MEM_DMA;
+ int alloc_count;
+ int free_count;
+#endif
+
+ if (!vaddr) {
+#if DRM_DEBUG_MEMORY
+ DRM_MEM_ERROR(area, "Attempt to free address 0\n");
+#endif
+ } else {
+ pci_free_consistent(dev->pdev, size, vaddr, busaddr);
+ }
+
+#if DRM_DEBUG_MEMORY
+ spin_lock(&drm_mem_lock);
+ free_count = ++drm_mem_stats[area].free_count;
+ alloc_count = drm_mem_stats[area].succeed_count;
+ drm_mem_stats[area].bytes_freed += size;
+ drm_ram_used -= size;
+ spin_unlock(&drm_mem_lock);
+ if (free_count > alloc_count) {
+ DRM_MEM_ERROR(area,
+ "Excess frees: %d frees, %d allocs\n",
+ free_count, alloc_count);
+ }
+#endif
+
+}
+EXPORT_SYMBOL(drm_pci_free);
+
+/*@}*/
diff --git a/drivers/char/drm/drm_pciids.h b/drivers/char/drm/drm_pciids.h
new file mode 100644
index 000000000000..54a2914e3a32
--- /dev/null
+++ b/drivers/char/drm/drm_pciids.h
@@ -0,0 +1,224 @@
+/*
+ This file is auto-generated from the drm_pciids.txt in the DRM CVS
+ Please contact dri-devel@lists.sf.net to add new cards to this list
+*/
+#define radeon_PCI_IDS \
+ {0x1002, 0x4136, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|CHIP_IS_IGP}, \
+ {0x1002, 0x4137, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|CHIP_IS_IGP}, \
+ {0x1002, 0x4144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+ {0x1002, 0x4145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+ {0x1002, 0x4146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+ {0x1002, 0x4147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+ {0x1002, 0x4150, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4151, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4152, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4153, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4154, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4156, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+ {0x1002, 0x4237, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS250|CHIP_IS_IGP}, \
+ {0x1002, 0x4242, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x4243, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x4336, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4337, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4437, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS250|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4964, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+ {0x1002, 0x4965, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+ {0x1002, 0x4966, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+ {0x1002, 0x4967, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+ {0x1002, 0x4C57, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C58, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C59, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C5A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C65, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C66, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4C67, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x4E50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x5144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+ {0x1002, 0x5145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+ {0x1002, 0x5146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+ {0x1002, 0x5147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+ {0x1002, 0x5148, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x5149, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x514F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x5157, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \
+ {0x1002, 0x5158, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \
+ {0x1002, 0x5159, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+ {0x1002, 0x515A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+ {0x1002, 0x515E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+ {0x1002, 0x5168, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x5169, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x516A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x516B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x516C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+ {0x1002, 0x5834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+ {0x1002, 0x5835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x5836, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+ {0x1002, 0x5837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+ {0x1002, 0x5960, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5961, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5962, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5963, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5964, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5968, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5969, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+ {0x1002, 0x596A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x596B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5c61, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x5c62, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0x1002, 0x5c63, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|CHIP_IS_MOBILITY}, \
+ {0x1002, 0x5c64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+ {0, 0, 0}
+
+#define r128_PCI_IDS \
+ {0x1002, 0x4c45, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c46, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4d46, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4d4c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5041, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5042, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5043, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5044, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5045, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5046, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5047, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5048, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5049, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x504F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5057, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5245, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5246, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5247, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x524b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x524c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x534d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5446, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x544C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x5452, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define mga_PCI_IDS \
+ {0x102b, 0x0521, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x102b, 0x0525, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x102b, 0x2527, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define mach64_PCI_IDS \
+ {0x1002, 0x4749, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4750, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4751, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4742, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4744, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c51, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c42, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c44, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x474c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x474f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4752, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4753, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x474d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x474e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c52, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c53, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c4d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1002, 0x4c4e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define sisdrv_PCI_IDS \
+ {0x1039, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1039, 0x5300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1039, 0x6300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1039, 0x7300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define tdfx_PCI_IDS \
+ {0x121a, 0x0003, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x121a, 0x0004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x121a, 0x0005, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x121a, 0x0007, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x121a, 0x0009, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x121a, 0x000b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define viadrv_PCI_IDS \
+ {0x1106, 0x3022, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1106, 0x3122, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1106, 0x7205, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x1106, 0x7204, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define i810_PCI_IDS \
+ {0x8086, 0x7121, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x7123, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x7125, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x1132, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define i830_PCI_IDS \
+ {0x8086, 0x3577, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2562, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x3582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2572, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define gamma_PCI_IDS \
+ {0x3d3d, 0x0008, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define savage_PCI_IDS \
+ {0x5333, 0x8a22, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8a23, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c10, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c11, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c13, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c20, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c21, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c22, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c24, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c26, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8c2f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8a25, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8a26, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8d01, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8d02, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x5333, 0x8d04, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
+#define ffb_PCI_IDS \
+ {0, 0, 0}
+
+#define i915_PCI_IDS \
+ {0x8086, 0x3577, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2562, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x3582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2572, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0x8086, 0x2592, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+ {0, 0, 0}
+
diff --git a/drivers/char/drm/drm_proc.c b/drivers/char/drm/drm_proc.c
new file mode 100644
index 000000000000..6e06e8c6a516
--- /dev/null
+++ b/drivers/char/drm/drm_proc.c
@@ -0,0 +1,539 @@
+/**
+ * \file drm_proc.h
+ * /proc support for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ *
+ * \par Acknowledgements:
+ * Matthew J Sottek <matthew.j.sottek@intel.com> sent in a patch to fix
+ * the problem with the proc files not outputting all their information.
+ */
+
+/*
+ * Created: Mon Jan 11 09:48:47 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+static int drm_name_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+static int drm_vm_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+static int drm_clients_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+static int drm_queues_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+static int drm_bufs_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+#if DRM_DEBUG_CODE
+static int drm_vma_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data);
+#endif
+
+/**
+ * Proc file list.
+ */
+struct drm_proc_list {
+ const char *name; /**< file name */
+ int (*f)(char *, char **, off_t, int, int *, void *); /**< proc callback*/
+} drm_proc_list[] = {
+ { "name", drm_name_info },
+ { "mem", drm_mem_info },
+ { "vm", drm_vm_info },
+ { "clients", drm_clients_info },
+ { "queues", drm_queues_info },
+ { "bufs", drm_bufs_info },
+#if DRM_DEBUG_CODE
+ { "vma", drm_vma_info },
+#endif
+};
+#define DRM_PROC_ENTRIES (sizeof(drm_proc_list)/sizeof(drm_proc_list[0]))
+
+/**
+ * Initialize the DRI proc filesystem for a device.
+ *
+ * \param dev DRM device.
+ * \param minor device minor number.
+ * \param root DRI proc dir entry.
+ * \param dev_root resulting DRI device proc dir entry.
+ * \return root entry pointer on success, or NULL on failure.
+ *
+ * Create the DRI proc root entry "/proc/dri", the device proc root entry
+ * "/proc/dri/%minor%/", and each entry in proc_list as
+ * "/proc/dri/%minor%/%name%".
+ */
+int drm_proc_init(drm_device_t *dev, int minor,
+ struct proc_dir_entry *root,
+ struct proc_dir_entry **dev_root)
+{
+ struct proc_dir_entry *ent;
+ int i, j;
+ char name[64];
+
+ sprintf(name, "%d", minor);
+ *dev_root = create_proc_entry(name, S_IFDIR, root);
+ if (!*dev_root) {
+ DRM_ERROR("Cannot create /proc/dri/%s\n", name);
+ return -1;
+ }
+
+ for (i = 0; i < DRM_PROC_ENTRIES; i++) {
+ ent = create_proc_entry(drm_proc_list[i].name,
+ S_IFREG|S_IRUGO, *dev_root);
+ if (!ent) {
+ DRM_ERROR("Cannot create /proc/dri/%s/%s\n",
+ name, drm_proc_list[i].name);
+ for (j = 0; j < i; j++)
+ remove_proc_entry(drm_proc_list[i].name,
+ *dev_root);
+ remove_proc_entry(name, root);
+ return -1;
+ }
+ ent->read_proc = drm_proc_list[i].f;
+ ent->data = dev;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Cleanup the proc filesystem resources.
+ *
+ * \param minor device minor number.
+ * \param root DRI proc dir entry.
+ * \param dev_root DRI device proc dir entry.
+ * \return always zero.
+ *
+ * Remove all proc entries created by proc_init().
+ */
+int drm_proc_cleanup(int minor, struct proc_dir_entry *root,
+ struct proc_dir_entry *dev_root)
+{
+ int i;
+ char name[64];
+
+ if (!root || !dev_root) return 0;
+
+ for (i = 0; i < DRM_PROC_ENTRIES; i++)
+ remove_proc_entry(drm_proc_list[i].name, dev_root);
+ sprintf(name, "%d", minor);
+ remove_proc_entry(name, root);
+
+ return 0;
+}
+
+/**
+ * Called when "/proc/dri/.../name" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ *
+ * Prints the device name together with the bus id if available.
+ */
+static int drm_name_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ if (dev->unique) {
+ DRM_PROC_PRINT("%s %s %s\n",
+ dev->driver->pci_driver.name, pci_name(dev->pdev), dev->unique);
+ } else {
+ DRM_PROC_PRINT("%s %s\n", dev->driver->pci_driver.name, pci_name(dev->pdev));
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+/**
+ * Called when "/proc/dri/.../vm" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ *
+ * Prints information about all mappings in drm_device::maplist.
+ */
+static int drm__vm_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+ drm_map_t *map;
+ drm_map_list_t *r_list;
+ struct list_head *list;
+
+ /* Hardcoded from _DRM_FRAME_BUFFER,
+ _DRM_REGISTERS, _DRM_SHM, _DRM_AGP, and
+ _DRM_SCATTER_GATHER. */
+ const char *types[] = { "FB", "REG", "SHM", "AGP", "SG" };
+ const char *type;
+ int i;
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ DRM_PROC_PRINT("slot offset size type flags "
+ "address mtrr\n\n");
+ i = 0;
+ if (dev->maplist != NULL) list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ map = r_list->map;
+ if(!map) continue;
+ if (map->type < 0 || map->type > 4) type = "??";
+ else type = types[map->type];
+ DRM_PROC_PRINT("%4d 0x%08lx 0x%08lx %4.4s 0x%02x 0x%08lx ",
+ i,
+ map->offset,
+ map->size,
+ type,
+ map->flags,
+ (unsigned long)map->handle);
+ if (map->mtrr < 0) {
+ DRM_PROC_PRINT("none\n");
+ } else {
+ DRM_PROC_PRINT("%4d\n", map->mtrr);
+ }
+ i++;
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+/**
+ * Simply calls _vm_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_vm_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int ret;
+
+ down(&dev->struct_sem);
+ ret = drm__vm_info(buf, start, offset, request, eof, data);
+ up(&dev->struct_sem);
+ return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../queues" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__queues_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+ int i;
+ drm_queue_t *q;
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ DRM_PROC_PRINT(" ctx/flags use fin"
+ " blk/rw/rwf wait flushed queued"
+ " locks\n\n");
+ for (i = 0; i < dev->queue_count; i++) {
+ q = dev->queuelist[i];
+ atomic_inc(&q->use_count);
+ DRM_PROC_PRINT_RET(atomic_dec(&q->use_count),
+ "%5d/0x%03x %5d %5d"
+ " %5d/%c%c/%c%c%c %5Zd\n",
+ i,
+ q->flags,
+ atomic_read(&q->use_count),
+ atomic_read(&q->finalization),
+ atomic_read(&q->block_count),
+ atomic_read(&q->block_read) ? 'r' : '-',
+ atomic_read(&q->block_write) ? 'w' : '-',
+ waitqueue_active(&q->read_queue) ? 'r':'-',
+ waitqueue_active(&q->write_queue) ? 'w':'-',
+ waitqueue_active(&q->flush_queue) ? 'f':'-',
+ DRM_BUFCOUNT(&q->waitlist));
+ atomic_dec(&q->use_count);
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+/**
+ * Simply calls _queues_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_queues_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int ret;
+
+ down(&dev->struct_sem);
+ ret = drm__queues_info(buf, start, offset, request, eof, data);
+ up(&dev->struct_sem);
+ return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../bufs" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__bufs_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+
+ if (!dma || offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ DRM_PROC_PRINT(" o size count free segs pages kB\n\n");
+ for (i = 0; i <= DRM_MAX_ORDER; i++) {
+ if (dma->bufs[i].buf_count)
+ DRM_PROC_PRINT("%2d %8d %5d %5d %5d %5d %5ld\n",
+ i,
+ dma->bufs[i].buf_size,
+ dma->bufs[i].buf_count,
+ atomic_read(&dma->bufs[i]
+ .freelist.count),
+ dma->bufs[i].seg_count,
+ dma->bufs[i].seg_count
+ *(1 << dma->bufs[i].page_order),
+ (dma->bufs[i].seg_count
+ * (1 << dma->bufs[i].page_order))
+ * PAGE_SIZE / 1024);
+ }
+ DRM_PROC_PRINT("\n");
+ for (i = 0; i < dma->buf_count; i++) {
+ if (i && !(i%32)) DRM_PROC_PRINT("\n");
+ DRM_PROC_PRINT(" %d", dma->buflist[i]->list);
+ }
+ DRM_PROC_PRINT("\n");
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+/**
+ * Simply calls _bufs_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_bufs_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int ret;
+
+ down(&dev->struct_sem);
+ ret = drm__bufs_info(buf, start, offset, request, eof, data);
+ up(&dev->struct_sem);
+ return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../clients" is read.
+ *
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__clients_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+ drm_file_t *priv;
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ DRM_PROC_PRINT("a dev pid uid magic ioctls\n\n");
+ for (priv = dev->file_first; priv; priv = priv->next) {
+ DRM_PROC_PRINT("%c %3d %5d %5d %10u %10lu\n",
+ priv->authenticated ? 'y' : 'n',
+ priv->minor,
+ priv->pid,
+ priv->uid,
+ priv->magic,
+ priv->ioctl_count);
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+/**
+ * Simply calls _clients_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_clients_info(char *buf, char **start, off_t offset,
+ int request, int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int ret;
+
+ down(&dev->struct_sem);
+ ret = drm__clients_info(buf, start, offset, request, eof, data);
+ up(&dev->struct_sem);
+ return ret;
+}
+
+#if DRM_DEBUG_CODE
+
+static int drm__vma_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int len = 0;
+ drm_vma_entry_t *pt;
+ struct vm_area_struct *vma;
+#if defined(__i386__)
+ unsigned int pgprot;
+#endif
+
+ if (offset > DRM_PROC_LIMIT) {
+ *eof = 1;
+ return 0;
+ }
+
+ *start = &buf[offset];
+ *eof = 0;
+
+ DRM_PROC_PRINT("vma use count: %d, high_memory = %p, 0x%08lx\n",
+ atomic_read(&dev->vma_count),
+ high_memory, virt_to_phys(high_memory));
+ for (pt = dev->vmalist; pt; pt = pt->next) {
+ if (!(vma = pt->vma)) continue;
+ DRM_PROC_PRINT("\n%5d 0x%08lx-0x%08lx %c%c%c%c%c%c 0x%08lx",
+ pt->pid,
+ vma->vm_start,
+ vma->vm_end,
+ vma->vm_flags & VM_READ ? 'r' : '-',
+ vma->vm_flags & VM_WRITE ? 'w' : '-',
+ vma->vm_flags & VM_EXEC ? 'x' : '-',
+ vma->vm_flags & VM_MAYSHARE ? 's' : 'p',
+ vma->vm_flags & VM_LOCKED ? 'l' : '-',
+ vma->vm_flags & VM_IO ? 'i' : '-',
+ VM_OFFSET(vma));
+
+#if defined(__i386__)
+ pgprot = pgprot_val(vma->vm_page_prot);
+ DRM_PROC_PRINT(" %c%c%c%c%c%c%c%c%c",
+ pgprot & _PAGE_PRESENT ? 'p' : '-',
+ pgprot & _PAGE_RW ? 'w' : 'r',
+ pgprot & _PAGE_USER ? 'u' : 's',
+ pgprot & _PAGE_PWT ? 't' : 'b',
+ pgprot & _PAGE_PCD ? 'u' : 'c',
+ pgprot & _PAGE_ACCESSED ? 'a' : '-',
+ pgprot & _PAGE_DIRTY ? 'd' : '-',
+ pgprot & _PAGE_PSE ? 'm' : 'k',
+ pgprot & _PAGE_GLOBAL ? 'g' : 'l' );
+#endif
+ DRM_PROC_PRINT("\n");
+ }
+
+ if (len > request + offset) return request;
+ *eof = 1;
+ return len - offset;
+}
+
+static int drm_vma_info(char *buf, char **start, off_t offset, int request,
+ int *eof, void *data)
+{
+ drm_device_t *dev = (drm_device_t *)data;
+ int ret;
+
+ down(&dev->struct_sem);
+ ret = drm__vma_info(buf, start, offset, request, eof, data);
+ up(&dev->struct_sem);
+ return ret;
+}
+#endif
+
+
diff --git a/drivers/char/drm/drm_sarea.h b/drivers/char/drm/drm_sarea.h
new file mode 100644
index 000000000000..de782ed2f03a
--- /dev/null
+++ b/drivers/char/drm/drm_sarea.h
@@ -0,0 +1,78 @@
+/**
+ * \file drm_sarea.h
+ * \brief SAREA definitions
+ *
+ * \author Michel Dänzer <michel@daenzer.net>
+ */
+
+/*
+ * Copyright 2002 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DRM_SAREA_H_
+#define _DRM_SAREA_H_
+
+#include "drm.h"
+
+/* SAREA area needs to be at least a page */
+#if defined(__alpha__)
+#define SAREA_MAX 0x2000
+#elif defined(__ia64__)
+#define SAREA_MAX 0x10000 /* 64kB */
+#else
+/* Intel 830M driver needs at least 8k SAREA */
+#define SAREA_MAX 0x2000
+#endif
+
+/** Maximum number of drawables in the SAREA */
+#define SAREA_MAX_DRAWABLES 256
+
+#define SAREA_DRAWABLE_CLAIMED_ENTRY 0x80000000
+
+/** SAREA drawable */
+typedef struct drm_sarea_drawable {
+ unsigned int stamp;
+ unsigned int flags;
+} drm_sarea_drawable_t;
+
+/** SAREA frame */
+typedef struct drm_sarea_frame {
+ unsigned int x;
+ unsigned int y;
+ unsigned int width;
+ unsigned int height;
+ unsigned int fullscreen;
+} drm_sarea_frame_t;
+
+/** SAREA */
+typedef struct drm_sarea {
+ /** first thing is always the DRM locking structure */
+ drm_hw_lock_t lock;
+ /** \todo Use readers/writer lock for drm_sarea::drawable_lock */
+ drm_hw_lock_t drawable_lock;
+ drm_sarea_drawable_t drawableTable[SAREA_MAX_DRAWABLES]; /**< drawables */
+ drm_sarea_frame_t frame; /**< frame */
+ drm_context_t dummy_context;
+} drm_sarea_t;
+
+#endif /* _DRM_SAREA_H_ */
diff --git a/drivers/char/drm/drm_scatter.c b/drivers/char/drm/drm_scatter.c
new file mode 100644
index 000000000000..54fddb6ea2d1
--- /dev/null
+++ b/drivers/char/drm/drm_scatter.c
@@ -0,0 +1,231 @@
+/**
+ * \file drm_scatter.h
+ * IOCTLs to manage scatter/gather memory
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Dec 18 23:20:54 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+#define DEBUG_SCATTER 0
+
+void drm_sg_cleanup( drm_sg_mem_t *entry )
+{
+ struct page *page;
+ int i;
+
+ for ( i = 0 ; i < entry->pages ; i++ ) {
+ page = entry->pagelist[i];
+ if ( page )
+ ClearPageReserved( page );
+ }
+
+ vfree( entry->virtual );
+
+ drm_free( entry->busaddr,
+ entry->pages * sizeof(*entry->busaddr),
+ DRM_MEM_PAGES );
+ drm_free( entry->pagelist,
+ entry->pages * sizeof(*entry->pagelist),
+ DRM_MEM_PAGES );
+ drm_free( entry,
+ sizeof(*entry),
+ DRM_MEM_SGLISTS );
+}
+
+int drm_sg_alloc( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_scatter_gather_t __user *argp = (void __user *)arg;
+ drm_scatter_gather_t request;
+ drm_sg_mem_t *entry;
+ unsigned long pages, i, j;
+
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ if (!drm_core_check_feature(dev, DRIVER_SG))
+ return -EINVAL;
+
+ if ( dev->sg )
+ return -EINVAL;
+
+ if ( copy_from_user( &request, argp, sizeof(request) ) )
+ return -EFAULT;
+
+ entry = drm_alloc( sizeof(*entry), DRM_MEM_SGLISTS );
+ if ( !entry )
+ return -ENOMEM;
+
+ memset( entry, 0, sizeof(*entry) );
+
+ pages = (request.size + PAGE_SIZE - 1) / PAGE_SIZE;
+ DRM_DEBUG( "sg size=%ld pages=%ld\n", request.size, pages );
+
+ entry->pages = pages;
+ entry->pagelist = drm_alloc( pages * sizeof(*entry->pagelist),
+ DRM_MEM_PAGES );
+ if ( !entry->pagelist ) {
+ drm_free( entry, sizeof(*entry), DRM_MEM_SGLISTS );
+ return -ENOMEM;
+ }
+
+ memset(entry->pagelist, 0, pages * sizeof(*entry->pagelist));
+
+ entry->busaddr = drm_alloc( pages * sizeof(*entry->busaddr),
+ DRM_MEM_PAGES );
+ if ( !entry->busaddr ) {
+ drm_free( entry->pagelist,
+ entry->pages * sizeof(*entry->pagelist),
+ DRM_MEM_PAGES );
+ drm_free( entry,
+ sizeof(*entry),
+ DRM_MEM_SGLISTS );
+ return -ENOMEM;
+ }
+ memset( (void *)entry->busaddr, 0, pages * sizeof(*entry->busaddr) );
+
+ entry->virtual = vmalloc_32( pages << PAGE_SHIFT );
+ if ( !entry->virtual ) {
+ drm_free( entry->busaddr,
+ entry->pages * sizeof(*entry->busaddr),
+ DRM_MEM_PAGES );
+ drm_free( entry->pagelist,
+ entry->pages * sizeof(*entry->pagelist),
+ DRM_MEM_PAGES );
+ drm_free( entry,
+ sizeof(*entry),
+ DRM_MEM_SGLISTS );
+ return -ENOMEM;
+ }
+
+ /* This also forces the mapping of COW pages, so our page list
+ * will be valid. Please don't remove it...
+ */
+ memset( entry->virtual, 0, pages << PAGE_SHIFT );
+
+ entry->handle = (unsigned long)entry->virtual;
+
+ DRM_DEBUG( "sg alloc handle = %08lx\n", entry->handle );
+ DRM_DEBUG( "sg alloc virtual = %p\n", entry->virtual );
+
+ for ( i = entry->handle, j = 0 ; j < pages ; i += PAGE_SIZE, j++ ) {
+ entry->pagelist[j] = vmalloc_to_page((void *)i);
+ if (!entry->pagelist[j])
+ goto failed;
+ SetPageReserved(entry->pagelist[j]);
+ }
+
+ request.handle = entry->handle;
+
+ if ( copy_to_user( argp, &request, sizeof(request) ) ) {
+ drm_sg_cleanup( entry );
+ return -EFAULT;
+ }
+
+ dev->sg = entry;
+
+#if DEBUG_SCATTER
+ /* Verify that each page points to its virtual address, and vice
+ * versa.
+ */
+ {
+ int error = 0;
+
+ for ( i = 0 ; i < pages ; i++ ) {
+ unsigned long *tmp;
+
+ tmp = page_address( entry->pagelist[i] );
+ for ( j = 0 ;
+ j < PAGE_SIZE / sizeof(unsigned long) ;
+ j++, tmp++ ) {
+ *tmp = 0xcafebabe;
+ }
+ tmp = (unsigned long *)((u8 *)entry->virtual +
+ (PAGE_SIZE * i));
+ for( j = 0 ;
+ j < PAGE_SIZE / sizeof(unsigned long) ;
+ j++, tmp++ ) {
+ if ( *tmp != 0xcafebabe && error == 0 ) {
+ error = 1;
+ DRM_ERROR( "Scatter allocation error, "
+ "pagelist does not match "
+ "virtual mapping\n" );
+ }
+ }
+ tmp = page_address( entry->pagelist[i] );
+ for(j = 0 ;
+ j < PAGE_SIZE / sizeof(unsigned long) ;
+ j++, tmp++) {
+ *tmp = 0;
+ }
+ }
+ if (error == 0)
+ DRM_ERROR( "Scatter allocation matches pagelist\n" );
+ }
+#endif
+
+ return 0;
+
+ failed:
+ drm_sg_cleanup( entry );
+ return -ENOMEM;
+}
+
+int drm_sg_free( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_scatter_gather_t request;
+ drm_sg_mem_t *entry;
+
+ if (!drm_core_check_feature(dev, DRIVER_SG))
+ return -EINVAL;
+
+ if ( copy_from_user( &request,
+ (drm_scatter_gather_t __user *)arg,
+ sizeof(request) ) )
+ return -EFAULT;
+
+ entry = dev->sg;
+ dev->sg = NULL;
+
+ if ( !entry || entry->handle != request.handle )
+ return -EINVAL;
+
+ DRM_DEBUG( "sg free virtual = %p\n", entry->virtual );
+
+ drm_sg_cleanup( entry );
+
+ return 0;
+}
diff --git a/drivers/char/drm/drm_stub.c b/drivers/char/drm/drm_stub.c
new file mode 100644
index 000000000000..8ccbdef7bb3e
--- /dev/null
+++ b/drivers/char/drm/drm_stub.c
@@ -0,0 +1,319 @@
+/**
+ * \file drm_stub.h
+ * Stub support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ */
+
+/*
+ * Created: Fri Jan 19 10:48:35 2001 by faith@acm.org
+ *
+ * Copyright 2001 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include "drmP.h"
+#include "drm_core.h"
+
+unsigned int drm_cards_limit = 16; /* Enough for one machine */
+unsigned int drm_debug = 0; /* 1 to enable debug output */
+EXPORT_SYMBOL(drm_debug);
+
+MODULE_AUTHOR( CORE_AUTHOR );
+MODULE_DESCRIPTION( CORE_DESC );
+MODULE_LICENSE("GPL and additional rights");
+MODULE_PARM_DESC(cards_limit, "Maximum number of graphics cards");
+MODULE_PARM_DESC(debug, "Enable debug output");
+
+module_param_named(cards_limit, drm_cards_limit, int, 0444);
+module_param_named(debug, drm_debug, int, 0666);
+
+drm_head_t **drm_heads;
+struct drm_sysfs_class *drm_class;
+struct proc_dir_entry *drm_proc_root;
+
+static int drm_fill_in_dev(drm_device_t *dev, struct pci_dev *pdev, const struct pci_device_id *ent, struct drm_driver *driver)
+{
+ int retcode;
+
+ spin_lock_init(&dev->count_lock);
+ init_timer( &dev->timer );
+ sema_init( &dev->struct_sem, 1 );
+ sema_init( &dev->ctxlist_sem, 1 );
+
+ dev->pdev = pdev;
+
+#ifdef __alpha__
+ dev->hose = pdev->sysdata;
+ dev->pci_domain = dev->hose->bus->number;
+#else
+ dev->pci_domain = 0;
+#endif
+ dev->pci_bus = pdev->bus->number;
+ dev->pci_slot = PCI_SLOT(pdev->devfn);
+ dev->pci_func = PCI_FUNC(pdev->devfn);
+ dev->irq = pdev->irq;
+
+ /* the DRM has 6 basic counters */
+ dev->counters = 6;
+ dev->types[0] = _DRM_STAT_LOCK;
+ dev->types[1] = _DRM_STAT_OPENS;
+ dev->types[2] = _DRM_STAT_CLOSES;
+ dev->types[3] = _DRM_STAT_IOCTLS;
+ dev->types[4] = _DRM_STAT_LOCKS;
+ dev->types[5] = _DRM_STAT_UNLOCKS;
+
+ dev->driver = driver;
+
+ if (dev->driver->preinit)
+ if ((retcode = dev->driver->preinit(dev, ent->driver_data)))
+ goto error_out_unreg;
+
+ if (drm_core_has_AGP(dev)) {
+ dev->agp = drm_agp_init(dev);
+ if (drm_core_check_feature(dev, DRIVER_REQUIRE_AGP) && (dev->agp == NULL)) {
+ DRM_ERROR( "Cannot initialize the agpgart module.\n" );
+ retcode = -EINVAL;
+ goto error_out_unreg;
+ }
+ if (drm_core_has_MTRR(dev)) {
+ if (dev->agp)
+ dev->agp->agp_mtrr = mtrr_add( dev->agp->agp_info.aper_base,
+ dev->agp->agp_info.aper_size*1024*1024,
+ MTRR_TYPE_WRCOMB,
+ 1 );
+ }
+ }
+
+ retcode = drm_ctxbitmap_init( dev );
+ if( retcode ) {
+ DRM_ERROR( "Cannot allocate memory for context bitmap.\n" );
+ goto error_out_unreg;
+ }
+
+ return 0;
+
+error_out_unreg:
+ drm_takedown(dev);
+ return retcode;
+}
+
+/**
+ * File \c open operation.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ *
+ * Puts the dev->fops corresponding to the device minor number into
+ * \p filp, call the \c open method, and restore the file operations.
+ */
+int drm_stub_open(struct inode *inode, struct file *filp)
+{
+ drm_device_t *dev = NULL;
+ int minor = iminor(inode);
+ int err = -ENODEV;
+ struct file_operations *old_fops;
+
+ DRM_DEBUG("\n");
+
+ if (!((minor >= 0) && (minor < drm_cards_limit)))
+ return -ENODEV;
+
+ if (!drm_heads[minor])
+ return -ENODEV;
+
+ if (!(dev = drm_heads[minor]->dev))
+ return -ENODEV;
+
+ old_fops = filp->f_op;
+ filp->f_op = fops_get(&dev->driver->fops);
+ if (filp->f_op->open && (err = filp->f_op->open(inode, filp))) {
+ fops_put(filp->f_op);
+ filp->f_op = fops_get(old_fops);
+ }
+ fops_put(old_fops);
+
+ return err;
+}
+
+
+/**
+ * Register.
+ *
+ * \param pdev - PCI device structure
+ * \param ent entry from the PCI ID table with device type flags
+ * \return zero on success or a negative number on failure.
+ *
+ * Attempt to gets inter module "drm" information. If we are first
+ * then register the character device and inter module information.
+ * Try and register, if we fail to register, backout previous work.
+ */
+int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
+ struct drm_driver *driver)
+{
+ drm_device_t *dev;
+ int ret;
+
+ DRM_DEBUG("\n");
+
+ dev = drm_calloc(1, sizeof(*dev), DRM_MEM_STUB);
+ if (!dev)
+ return -ENOMEM;
+
+ pci_enable_device(pdev);
+
+ if ((ret = drm_fill_in_dev(dev, pdev, ent, driver))) {
+ printk(KERN_ERR "DRM: Fill_in_dev failed.\n");
+ goto err_g1;
+ }
+ if ((ret = drm_get_head(dev, &dev->primary)))
+ goto err_g1;
+
+ /* postinit is a required function to display the signon banner */
+ /* drivers add secondary heads here if needed */
+ if ((ret = dev->driver->postinit(dev, ent->driver_data)))
+ goto err_g1;
+
+ return 0;
+
+err_g1:
+ drm_free(dev, sizeof(*dev), DRM_MEM_STUB);
+ return ret;
+}
+EXPORT_SYMBOL(drm_get_dev);
+
+/**
+ * Get a secondary minor number.
+ *
+ * \param dev device data structure
+ * \param sec-minor structure to hold the assigned minor
+ * \return negative number on failure.
+ *
+ * Search an empty entry and initialize it to the given parameters, and
+ * create the proc init entry via proc_init(). This routines assigns
+ * minor numbers to secondary heads of multi-headed cards
+ */
+int drm_get_head(drm_device_t *dev, drm_head_t *head)
+{
+ drm_head_t **heads = drm_heads;
+ int ret;
+ int minor;
+
+ DRM_DEBUG("\n");
+
+ for (minor = 0; minor < drm_cards_limit; minor++, heads++) {
+ if (!*heads) {
+
+ *head = (drm_head_t) {
+ .dev = dev,
+ .device = MKDEV(DRM_MAJOR, minor),
+ .minor = minor,
+ };
+
+ if ((ret = drm_proc_init(dev, minor, drm_proc_root, &head->dev_root))) {
+ printk (KERN_ERR "DRM: Failed to initialize /proc/dri.\n");
+ goto err_g1;
+ }
+
+
+ head->dev_class = drm_sysfs_device_add(drm_class,
+ MKDEV(DRM_MAJOR,
+ minor),
+ &dev->pdev->dev,
+ "card%d", minor);
+ if (IS_ERR(head->dev_class)) {
+ printk(KERN_ERR "DRM: Error sysfs_device_add.\n");
+ ret = PTR_ERR(head->dev_class);
+ goto err_g2;
+ }
+ *heads = head;
+
+ DRM_DEBUG("new minor assigned %d\n", minor);
+ return 0;
+ }
+ }
+ DRM_ERROR("out of minors\n");
+ return -ENOMEM;
+err_g2:
+ drm_proc_cleanup(minor, drm_proc_root, head->dev_root);
+err_g1:
+ *head = (drm_head_t) {.dev = NULL};
+ return ret;
+}
+
+
+/**
+ * Put a device minor number.
+ *
+ * \param dev device data structure
+ * \return always zero
+ *
+ * Cleans up the proc resources. If it is the last minor then release the foreign
+ * "drm" data, otherwise unregisters the "drm" data, frees the dev list and
+ * unregisters the character device.
+ */
+int drm_put_dev(drm_device_t * dev)
+{
+ DRM_DEBUG("release primary %s\n", dev->driver->pci_driver.name);
+
+ if (dev->unique) {
+ drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
+ dev->unique = NULL;
+ dev->unique_len = 0;
+ }
+ if (dev->devname) {
+ drm_free(dev->devname, strlen(dev->devname) + 1,
+ DRM_MEM_DRIVER);
+ dev->devname = NULL;
+ }
+ drm_free(dev, sizeof(*dev), DRM_MEM_STUB);
+ return 0;
+}
+
+/**
+ * Put a secondary minor number.
+ *
+ * \param sec_minor - structure to be released
+ * \return always zero
+ *
+ * Cleans up the proc resources. Not legal for this to be the
+ * last minor released.
+ *
+ */
+int drm_put_head(drm_head_t *head)
+{
+ int minor = head->minor;
+
+ DRM_DEBUG("release secondary minor %d\n", minor);
+
+ drm_proc_cleanup(minor, drm_proc_root, head->dev_root);
+ drm_sysfs_device_remove(MKDEV(DRM_MAJOR, head->minor));
+
+ *head = (drm_head_t){.dev = NULL};
+
+ drm_heads[minor] = NULL;
+
+ return 0;
+}
+
diff --git a/drivers/char/drm/drm_sysfs.c b/drivers/char/drm/drm_sysfs.c
new file mode 100644
index 000000000000..2fc10c4bbcdf
--- /dev/null
+++ b/drivers/char/drm/drm_sysfs.c
@@ -0,0 +1,208 @@
+/*
+ * drm_sysfs.c - Modifications to drm_sysfs_class.c to support
+ * extra sysfs attribute from DRM. Normal drm_sysfs_class
+ * does not allow adding attributes.
+ *
+ * Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
+ * Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (c) 2003-2004 IBM Corp.
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include <linux/err.h>
+
+#include "drm_core.h"
+
+struct drm_sysfs_class {
+ struct class_device_attribute attr;
+ struct class class;
+};
+#define to_drm_sysfs_class(d) container_of(d, struct drm_sysfs_class, class)
+
+struct simple_dev {
+ struct list_head node;
+ dev_t dev;
+ struct class_device class_dev;
+};
+#define to_simple_dev(d) container_of(d, struct simple_dev, class_dev)
+
+static LIST_HEAD(simple_dev_list);
+static DEFINE_SPINLOCK(simple_dev_list_lock);
+
+static void release_simple_dev(struct class_device *class_dev)
+{
+ struct simple_dev *s_dev = to_simple_dev(class_dev);
+ kfree(s_dev);
+}
+
+static ssize_t show_dev(struct class_device *class_dev, char *buf)
+{
+ struct simple_dev *s_dev = to_simple_dev(class_dev);
+ return print_dev_t(buf, s_dev->dev);
+}
+
+static void drm_sysfs_class_release(struct class *class)
+{
+ struct drm_sysfs_class *cs = to_drm_sysfs_class(class);
+ kfree(cs);
+}
+
+/* Display the version of drm_core. This doesn't work right in current design */
+static ssize_t version_show(struct class *dev, char *buf)
+{
+ return sprintf(buf, "%s %d.%d.%d %s\n", CORE_NAME, CORE_MAJOR,
+ CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
+}
+
+static CLASS_ATTR(version, S_IRUGO, version_show, NULL);
+
+/**
+ * drm_sysfs_create - create a struct drm_sysfs_class structure
+ * @owner: pointer to the module that is to "own" this struct drm_sysfs_class
+ * @name: pointer to a string for the name of this class.
+ *
+ * This is used to create a struct drm_sysfs_class pointer that can then be used
+ * in calls to drm_sysfs_device_add().
+ *
+ * Note, the pointer created here is to be destroyed when finished by making a
+ * call to drm_sysfs_destroy().
+ */
+struct drm_sysfs_class *drm_sysfs_create(struct module *owner, char *name)
+{
+ struct drm_sysfs_class *cs;
+ int retval;
+
+ cs = kmalloc(sizeof(*cs), GFP_KERNEL);
+ if (!cs) {
+ retval = -ENOMEM;
+ goto error;
+ }
+ memset(cs, 0x00, sizeof(*cs));
+
+ cs->class.name = name;
+ cs->class.class_release = drm_sysfs_class_release;
+ cs->class.release = release_simple_dev;
+
+ cs->attr.attr.name = "dev";
+ cs->attr.attr.mode = S_IRUGO;
+ cs->attr.attr.owner = owner;
+ cs->attr.show = show_dev;
+ cs->attr.store = NULL;
+
+ retval = class_register(&cs->class);
+ if (retval)
+ goto error;
+ class_create_file(&cs->class, &class_attr_version);
+
+ return cs;
+
+ error:
+ kfree(cs);
+ return ERR_PTR(retval);
+}
+
+/**
+ * drm_sysfs_destroy - destroys a struct drm_sysfs_class structure
+ * @cs: pointer to the struct drm_sysfs_class that is to be destroyed
+ *
+ * Note, the pointer to be destroyed must have been created with a call to
+ * drm_sysfs_create().
+ */
+void drm_sysfs_destroy(struct drm_sysfs_class *cs)
+{
+ if ((cs == NULL) || (IS_ERR(cs)))
+ return;
+
+ class_unregister(&cs->class);
+}
+
+/**
+ * drm_sysfs_device_add - adds a class device to sysfs for a character driver
+ * @cs: pointer to the struct drm_sysfs_class that this device should be registered to.
+ * @dev: the dev_t for the device to be added.
+ * @device: a pointer to a struct device that is assiociated with this class device.
+ * @fmt: string for the class device's name
+ *
+ * A struct class_device will be created in sysfs, registered to the specified
+ * class. A "dev" file will be created, showing the dev_t for the device. The
+ * pointer to the struct class_device will be returned from the call. Any further
+ * sysfs files that might be required can be created using this pointer.
+ * Note: the struct drm_sysfs_class passed to this function must have previously been
+ * created with a call to drm_sysfs_create().
+ */
+struct class_device *drm_sysfs_device_add(struct drm_sysfs_class *cs, dev_t dev,
+ struct device *device,
+ const char *fmt, ...)
+{
+ va_list args;
+ struct simple_dev *s_dev = NULL;
+ int retval;
+
+ if ((cs == NULL) || (IS_ERR(cs))) {
+ retval = -ENODEV;
+ goto error;
+ }
+
+ s_dev = kmalloc(sizeof(*s_dev), GFP_KERNEL);
+ if (!s_dev) {
+ retval = -ENOMEM;
+ goto error;
+ }
+ memset(s_dev, 0x00, sizeof(*s_dev));
+
+ s_dev->dev = dev;
+ s_dev->class_dev.dev = device;
+ s_dev->class_dev.class = &cs->class;
+
+ va_start(args, fmt);
+ vsnprintf(s_dev->class_dev.class_id, BUS_ID_SIZE, fmt, args);
+ va_end(args);
+ retval = class_device_register(&s_dev->class_dev);
+ if (retval)
+ goto error;
+
+ class_device_create_file(&s_dev->class_dev, &cs->attr);
+
+ spin_lock(&simple_dev_list_lock);
+ list_add(&s_dev->node, &simple_dev_list);
+ spin_unlock(&simple_dev_list_lock);
+
+ return &s_dev->class_dev;
+
+ error:
+ kfree(s_dev);
+ return ERR_PTR(retval);
+}
+
+/**
+ * drm_sysfs_device_remove - removes a class device that was created with drm_sysfs_device_add()
+ * @dev: the dev_t of the device that was previously registered.
+ *
+ * This call unregisters and cleans up a class device that was created with a
+ * call to drm_sysfs_device_add()
+ */
+void drm_sysfs_device_remove(dev_t dev)
+{
+ struct simple_dev *s_dev = NULL;
+ int found = 0;
+
+ spin_lock(&simple_dev_list_lock);
+ list_for_each_entry(s_dev, &simple_dev_list, node) {
+ if (s_dev->dev == dev) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ list_del(&s_dev->node);
+ spin_unlock(&simple_dev_list_lock);
+ class_device_unregister(&s_dev->class_dev);
+ } else {
+ spin_unlock(&simple_dev_list_lock);
+ }
+}
diff --git a/drivers/char/drm/drm_vm.c b/drivers/char/drm/drm_vm.c
new file mode 100644
index 000000000000..fc72f30f312b
--- /dev/null
+++ b/drivers/char/drm/drm_vm.c
@@ -0,0 +1,678 @@
+/**
+ * \file drm_vm.h
+ * Memory mapping for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan 4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#if defined(__ia64__)
+#include <linux/efi.h>
+#endif
+
+
+/**
+ * \c nopage method for AGP virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ *
+ * Find the right map and if it's AGP memory find the real physical page to
+ * map, get the page, increment the use count and return it.
+ */
+#if __OS_HAS_AGP
+static __inline__ struct page *drm_do_vm_nopage(struct vm_area_struct *vma,
+ unsigned long address)
+{
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_map_t *map = NULL;
+ drm_map_list_t *r_list;
+ struct list_head *list;
+
+ /*
+ * Find the right map
+ */
+ if (!drm_core_has_AGP(dev))
+ goto vm_nopage_error;
+
+ if(!dev->agp || !dev->agp->cant_use_aperture) goto vm_nopage_error;
+
+ list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ map = r_list->map;
+ if (!map) continue;
+ if (map->offset == VM_OFFSET(vma)) break;
+ }
+
+ if (map && map->type == _DRM_AGP) {
+ unsigned long offset = address - vma->vm_start;
+ unsigned long baddr = VM_OFFSET(vma) + offset;
+ struct drm_agp_mem *agpmem;
+ struct page *page;
+
+#ifdef __alpha__
+ /*
+ * Adjust to a bus-relative address
+ */
+ baddr -= dev->hose->mem_space->start;
+#endif
+
+ /*
+ * It's AGP memory - find the real physical page to map
+ */
+ for(agpmem = dev->agp->memory; agpmem; agpmem = agpmem->next) {
+ if (agpmem->bound <= baddr &&
+ agpmem->bound + agpmem->pages * PAGE_SIZE > baddr)
+ break;
+ }
+
+ if (!agpmem) goto vm_nopage_error;
+
+ /*
+ * Get the page, inc the use count, and return it
+ */
+ offset = (baddr - agpmem->bound) >> PAGE_SHIFT;
+ page = virt_to_page(__va(agpmem->memory->memory[offset]));
+ get_page(page);
+
+ DRM_DEBUG("baddr = 0x%lx page = 0x%p, offset = 0x%lx, count=%d\n",
+ baddr, __va(agpmem->memory->memory[offset]), offset,
+ page_count(page));
+
+ return page;
+ }
+vm_nopage_error:
+ return NOPAGE_SIGBUS; /* Disallow mremap */
+}
+#else /* __OS_HAS_AGP */
+static __inline__ struct page *drm_do_vm_nopage(struct vm_area_struct *vma,
+ unsigned long address)
+{
+ return NOPAGE_SIGBUS;
+}
+#endif /* __OS_HAS_AGP */
+
+/**
+ * \c nopage method for shared virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ *
+ * Get the the mapping, find the real physical page to map, get the page, and
+ * return it.
+ */
+static __inline__ struct page *drm_do_vm_shm_nopage(struct vm_area_struct *vma,
+ unsigned long address)
+{
+ drm_map_t *map = (drm_map_t *)vma->vm_private_data;
+ unsigned long offset;
+ unsigned long i;
+ struct page *page;
+
+ if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+ if (!map) return NOPAGE_OOM; /* Nothing allocated */
+
+ offset = address - vma->vm_start;
+ i = (unsigned long)map->handle + offset;
+ page = vmalloc_to_page((void *)i);
+ if (!page)
+ return NOPAGE_OOM;
+ get_page(page);
+
+ DRM_DEBUG("shm_nopage 0x%lx\n", address);
+ return page;
+}
+
+
+/**
+ * \c close method for shared virtual memory.
+ *
+ * \param vma virtual memory area.
+ *
+ * Deletes map information if we are the last
+ * person to close a mapping and it's not in the global maplist.
+ */
+void drm_vm_shm_close(struct vm_area_struct *vma)
+{
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_vma_entry_t *pt, *prev, *next;
+ drm_map_t *map;
+ drm_map_list_t *r_list;
+ struct list_head *list;
+ int found_maps = 0;
+
+ DRM_DEBUG("0x%08lx,0x%08lx\n",
+ vma->vm_start, vma->vm_end - vma->vm_start);
+ atomic_dec(&dev->vma_count);
+
+ map = vma->vm_private_data;
+
+ down(&dev->struct_sem);
+ for (pt = dev->vmalist, prev = NULL; pt; pt = next) {
+ next = pt->next;
+ if (pt->vma->vm_private_data == map) found_maps++;
+ if (pt->vma == vma) {
+ if (prev) {
+ prev->next = pt->next;
+ } else {
+ dev->vmalist = pt->next;
+ }
+ drm_free(pt, sizeof(*pt), DRM_MEM_VMAS);
+ } else {
+ prev = pt;
+ }
+ }
+ /* We were the only map that was found */
+ if(found_maps == 1 &&
+ map->flags & _DRM_REMOVABLE) {
+ /* Check to see if we are in the maplist, if we are not, then
+ * we delete this mappings information.
+ */
+ found_maps = 0;
+ list = &dev->maplist->head;
+ list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ if (r_list->map == map) found_maps++;
+ }
+
+ if(!found_maps) {
+ switch (map->type) {
+ case _DRM_REGISTERS:
+ case _DRM_FRAME_BUFFER:
+ if (drm_core_has_MTRR(dev) && map->mtrr >= 0) {
+ int retcode;
+ retcode = mtrr_del(map->mtrr,
+ map->offset,
+ map->size);
+ DRM_DEBUG("mtrr_del = %d\n", retcode);
+ }
+ drm_ioremapfree(map->handle, map->size, dev);
+ break;
+ case _DRM_SHM:
+ vfree(map->handle);
+ break;
+ case _DRM_AGP:
+ case _DRM_SCATTER_GATHER:
+ break;
+ }
+ drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+ }
+ }
+ up(&dev->struct_sem);
+}
+
+/**
+ * \c nopage method for DMA virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ *
+ * Determine the page number from the page offset and get it from drm_device_dma::pagelist.
+ */
+static __inline__ struct page *drm_do_vm_dma_nopage(struct vm_area_struct *vma,
+ unsigned long address)
+{
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ unsigned long offset;
+ unsigned long page_nr;
+ struct page *page;
+
+ if (!dma) return NOPAGE_SIGBUS; /* Error */
+ if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+ if (!dma->pagelist) return NOPAGE_OOM ; /* Nothing allocated */
+
+ offset = address - vma->vm_start; /* vm_[pg]off[set] should be 0 */
+ page_nr = offset >> PAGE_SHIFT;
+ page = virt_to_page((dma->pagelist[page_nr] +
+ (offset & (~PAGE_MASK))));
+
+ get_page(page);
+
+ DRM_DEBUG("dma_nopage 0x%lx (page %lu)\n", address, page_nr);
+ return page;
+}
+
+/**
+ * \c nopage method for scatter-gather virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ *
+ * Determine the map offset from the page offset and get it from drm_sg_mem::pagelist.
+ */
+static __inline__ struct page *drm_do_vm_sg_nopage(struct vm_area_struct *vma,
+ unsigned long address)
+{
+ drm_map_t *map = (drm_map_t *)vma->vm_private_data;
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_sg_mem_t *entry = dev->sg;
+ unsigned long offset;
+ unsigned long map_offset;
+ unsigned long page_offset;
+ struct page *page;
+
+ if (!entry) return NOPAGE_SIGBUS; /* Error */
+ if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+ if (!entry->pagelist) return NOPAGE_OOM ; /* Nothing allocated */
+
+
+ offset = address - vma->vm_start;
+ map_offset = map->offset - dev->sg->handle;
+ page_offset = (offset >> PAGE_SHIFT) + (map_offset >> PAGE_SHIFT);
+ page = entry->pagelist[page_offset];
+ get_page(page);
+
+ return page;
+}
+
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
+
+static struct page *drm_vm_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int *type) {
+ if (type) *type = VM_FAULT_MINOR;
+ return drm_do_vm_nopage(vma, address);
+}
+
+static struct page *drm_vm_shm_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int *type) {
+ if (type) *type = VM_FAULT_MINOR;
+ return drm_do_vm_shm_nopage(vma, address);
+}
+
+static struct page *drm_vm_dma_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int *type) {
+ if (type) *type = VM_FAULT_MINOR;
+ return drm_do_vm_dma_nopage(vma, address);
+}
+
+static struct page *drm_vm_sg_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int *type) {
+ if (type) *type = VM_FAULT_MINOR;
+ return drm_do_vm_sg_nopage(vma, address);
+}
+
+#else /* LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0) */
+
+static struct page *drm_vm_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int unused) {
+ return drm_do_vm_nopage(vma, address);
+}
+
+static struct page *drm_vm_shm_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int unused) {
+ return drm_do_vm_shm_nopage(vma, address);
+}
+
+static struct page *drm_vm_dma_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int unused) {
+ return drm_do_vm_dma_nopage(vma, address);
+}
+
+static struct page *drm_vm_sg_nopage(struct vm_area_struct *vma,
+ unsigned long address,
+ int unused) {
+ return drm_do_vm_sg_nopage(vma, address);
+}
+
+#endif
+
+
+/** AGP virtual memory operations */
+static struct vm_operations_struct drm_vm_ops = {
+ .nopage = drm_vm_nopage,
+ .open = drm_vm_open,
+ .close = drm_vm_close,
+};
+
+/** Shared virtual memory operations */
+static struct vm_operations_struct drm_vm_shm_ops = {
+ .nopage = drm_vm_shm_nopage,
+ .open = drm_vm_open,
+ .close = drm_vm_shm_close,
+};
+
+/** DMA virtual memory operations */
+static struct vm_operations_struct drm_vm_dma_ops = {
+ .nopage = drm_vm_dma_nopage,
+ .open = drm_vm_open,
+ .close = drm_vm_close,
+};
+
+/** Scatter-gather virtual memory operations */
+static struct vm_operations_struct drm_vm_sg_ops = {
+ .nopage = drm_vm_sg_nopage,
+ .open = drm_vm_open,
+ .close = drm_vm_close,
+};
+
+
+/**
+ * \c open method for shared virtual memory.
+ *
+ * \param vma virtual memory area.
+ *
+ * Create a new drm_vma_entry structure as the \p vma private data entry and
+ * add it to drm_device::vmalist.
+ */
+void drm_vm_open(struct vm_area_struct *vma)
+{
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_vma_entry_t *vma_entry;
+
+ DRM_DEBUG("0x%08lx,0x%08lx\n",
+ vma->vm_start, vma->vm_end - vma->vm_start);
+ atomic_inc(&dev->vma_count);
+
+ vma_entry = drm_alloc(sizeof(*vma_entry), DRM_MEM_VMAS);
+ if (vma_entry) {
+ down(&dev->struct_sem);
+ vma_entry->vma = vma;
+ vma_entry->next = dev->vmalist;
+ vma_entry->pid = current->pid;
+ dev->vmalist = vma_entry;
+ up(&dev->struct_sem);
+ }
+}
+
+/**
+ * \c close method for all virtual memory types.
+ *
+ * \param vma virtual memory area.
+ *
+ * Search the \p vma private data entry in drm_device::vmalist, unlink it, and
+ * free it.
+ */
+void drm_vm_close(struct vm_area_struct *vma)
+{
+ drm_file_t *priv = vma->vm_file->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_vma_entry_t *pt, *prev;
+
+ DRM_DEBUG("0x%08lx,0x%08lx\n",
+ vma->vm_start, vma->vm_end - vma->vm_start);
+ atomic_dec(&dev->vma_count);
+
+ down(&dev->struct_sem);
+ for (pt = dev->vmalist, prev = NULL; pt; prev = pt, pt = pt->next) {
+ if (pt->vma == vma) {
+ if (prev) {
+ prev->next = pt->next;
+ } else {
+ dev->vmalist = pt->next;
+ }
+ drm_free(pt, sizeof(*pt), DRM_MEM_VMAS);
+ break;
+ }
+ }
+ up(&dev->struct_sem);
+}
+
+/**
+ * mmap DMA memory.
+ *
+ * \param filp file pointer.
+ * \param vma virtual memory area.
+ * \return zero on success or a negative number on failure.
+ *
+ * Sets the virtual memory area operations structure to vm_dma_ops, the file
+ * pointer, and calls vm_open().
+ */
+int drm_mmap_dma(struct file *filp, struct vm_area_struct *vma)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev;
+ drm_device_dma_t *dma;
+ unsigned long length = vma->vm_end - vma->vm_start;
+
+ lock_kernel();
+ dev = priv->head->dev;
+ dma = dev->dma;
+ DRM_DEBUG("start = 0x%lx, end = 0x%lx, offset = 0x%lx\n",
+ vma->vm_start, vma->vm_end, VM_OFFSET(vma));
+
+ /* Length must match exact page count */
+ if (!dma || (length >> PAGE_SHIFT) != dma->page_count) {
+ unlock_kernel();
+ return -EINVAL;
+ }
+ unlock_kernel();
+
+ vma->vm_ops = &drm_vm_dma_ops;
+
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+ vma->vm_flags |= VM_LOCKED | VM_SHM; /* Don't swap */
+#else
+ vma->vm_flags |= VM_RESERVED; /* Don't swap */
+#endif
+
+ vma->vm_file = filp; /* Needed for drm_vm_open() */
+ drm_vm_open(vma);
+ return 0;
+}
+
+unsigned long drm_core_get_map_ofs(drm_map_t *map)
+{
+ return map->offset;
+}
+EXPORT_SYMBOL(drm_core_get_map_ofs);
+
+unsigned long drm_core_get_reg_ofs(struct drm_device *dev)
+{
+#ifdef __alpha__
+ return dev->hose->dense_mem_base - dev->hose->mem_space->start;
+#else
+ return 0;
+#endif
+}
+EXPORT_SYMBOL(drm_core_get_reg_ofs);
+
+/**
+ * mmap DMA memory.
+ *
+ * \param filp file pointer.
+ * \param vma virtual memory area.
+ * \return zero on success or a negative number on failure.
+ *
+ * If the virtual memory area has no offset associated with it then it's a DMA
+ * area, so calls mmap_dma(). Otherwise searches the map in drm_device::maplist,
+ * checks that the restricted flag is not set, sets the virtual memory operations
+ * according to the mapping type and remaps the pages. Finally sets the file
+ * pointer and calls vm_open().
+ */
+int drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_map_t *map = NULL;
+ drm_map_list_t *r_list;
+ unsigned long offset = 0;
+ struct list_head *list;
+
+ DRM_DEBUG("start = 0x%lx, end = 0x%lx, offset = 0x%lx\n",
+ vma->vm_start, vma->vm_end, VM_OFFSET(vma));
+
+ if ( !priv->authenticated ) return -EACCES;
+
+ /* We check for "dma". On Apple's UniNorth, it's valid to have
+ * the AGP mapped at physical address 0
+ * --BenH.
+ */
+ if (!VM_OFFSET(vma)
+#if __OS_HAS_AGP
+ && (!dev->agp || dev->agp->agp_info.device->vendor != PCI_VENDOR_ID_APPLE)
+#endif
+ )
+ return drm_mmap_dma(filp, vma);
+
+ /* A sequential search of a linked list is
+ fine here because: 1) there will only be
+ about 5-10 entries in the list and, 2) a
+ DRI client only has to do this mapping
+ once, so it doesn't have to be optimized
+ for performance, even if the list was a
+ bit longer. */
+ list_for_each(list, &dev->maplist->head) {
+ unsigned long off;
+
+ r_list = list_entry(list, drm_map_list_t, head);
+ map = r_list->map;
+ if (!map) continue;
+ off = dev->driver->get_map_ofs(map);
+ if (off == VM_OFFSET(vma)) break;
+ }
+
+ if (!map || ((map->flags&_DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN)))
+ return -EPERM;
+
+ /* Check for valid size. */
+ if (map->size != vma->vm_end - vma->vm_start) return -EINVAL;
+
+ if (!capable(CAP_SYS_ADMIN) && (map->flags & _DRM_READ_ONLY)) {
+ vma->vm_flags &= ~(VM_WRITE | VM_MAYWRITE);
+#if defined(__i386__) || defined(__x86_64__)
+ pgprot_val(vma->vm_page_prot) &= ~_PAGE_RW;
+#else
+ /* Ye gads this is ugly. With more thought
+ we could move this up higher and use
+ `protection_map' instead. */
+ vma->vm_page_prot = __pgprot(pte_val(pte_wrprotect(
+ __pte(pgprot_val(vma->vm_page_prot)))));
+#endif
+ }
+
+ switch (map->type) {
+ case _DRM_AGP:
+ if (drm_core_has_AGP(dev) && dev->agp->cant_use_aperture) {
+ /*
+ * On some platforms we can't talk to bus dma address from the CPU, so for
+ * memory of type DRM_AGP, we'll deal with sorting out the real physical
+ * pages and mappings in nopage()
+ */
+#if defined(__powerpc__)
+ pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE;
+#endif
+ vma->vm_ops = &drm_vm_ops;
+ break;
+ }
+ /* fall through to _DRM_FRAME_BUFFER... */
+ case _DRM_FRAME_BUFFER:
+ case _DRM_REGISTERS:
+ if (VM_OFFSET(vma) >= __pa(high_memory)) {
+#if defined(__i386__) || defined(__x86_64__)
+ if (boot_cpu_data.x86 > 3 && map->type != _DRM_AGP) {
+ pgprot_val(vma->vm_page_prot) |= _PAGE_PCD;
+ pgprot_val(vma->vm_page_prot) &= ~_PAGE_PWT;
+ }
+#elif defined(__powerpc__)
+ pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE | _PAGE_GUARDED;
+#endif
+ vma->vm_flags |= VM_IO; /* not in core dump */
+ }
+#if defined(__ia64__)
+ if (efi_range_is_wc(vma->vm_start, vma->vm_end -
+ vma->vm_start))
+ vma->vm_page_prot =
+ pgprot_writecombine(vma->vm_page_prot);
+ else
+ vma->vm_page_prot =
+ pgprot_noncached(vma->vm_page_prot);
+#endif
+ offset = dev->driver->get_reg_ofs(dev);
+#ifdef __sparc__
+ if (io_remap_pfn_range(DRM_RPR_ARG(vma) vma->vm_start,
+ (VM_OFFSET(vma) + offset) >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot))
+#else
+ if (io_remap_pfn_range(vma, vma->vm_start,
+ (VM_OFFSET(vma) + offset) >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot))
+#endif
+ return -EAGAIN;
+ DRM_DEBUG(" Type = %d; start = 0x%lx, end = 0x%lx,"
+ " offset = 0x%lx\n",
+ map->type,
+ vma->vm_start, vma->vm_end, VM_OFFSET(vma) + offset);
+ vma->vm_ops = &drm_vm_ops;
+ break;
+ case _DRM_SHM:
+ vma->vm_ops = &drm_vm_shm_ops;
+ vma->vm_private_data = (void *)map;
+ /* Don't let this area swap. Change when
+ DRM_KERNEL advisory is supported. */
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+ vma->vm_flags |= VM_LOCKED;
+#else
+ vma->vm_flags |= VM_RESERVED;
+#endif
+ break;
+ case _DRM_SCATTER_GATHER:
+ vma->vm_ops = &drm_vm_sg_ops;
+ vma->vm_private_data = (void *)map;
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+ vma->vm_flags |= VM_LOCKED;
+#else
+ vma->vm_flags |= VM_RESERVED;
+#endif
+ break;
+ default:
+ return -EINVAL; /* This should never happen. */
+ }
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+ vma->vm_flags |= VM_LOCKED | VM_SHM; /* Don't swap */
+#else
+ vma->vm_flags |= VM_RESERVED; /* Don't swap */
+#endif
+
+ vma->vm_file = filp; /* Needed for drm_vm_open() */
+ drm_vm_open(vma);
+ return 0;
+}
+EXPORT_SYMBOL(drm_mmap);
diff --git a/drivers/char/drm/ffb_context.c b/drivers/char/drm/ffb_context.c
new file mode 100644
index 000000000000..f51812078010
--- /dev/null
+++ b/drivers/char/drm/ffb_context.c
@@ -0,0 +1,551 @@
+/* $Id: ffb_context.c,v 1.5 2001/08/09 17:47:51 davem Exp $
+ * ffb_context.c: Creator/Creator3D DRI/DRM context switching.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ *
+ * Almost entirely stolen from tdfx_context.c, see there
+ * for authors.
+ */
+
+#include <linux/sched.h>
+#include <asm/upa.h>
+
+#include "ffb.h"
+#include "drmP.h"
+
+#include "ffb_drv.h"
+
+static int DRM(alloc_queue)(drm_device_t *dev, int is_2d_only)
+{
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+ int i;
+
+ for (i = 0; i < FFB_MAX_CTXS; i++) {
+ if (fpriv->hw_state[i] == NULL)
+ break;
+ }
+ if (i == FFB_MAX_CTXS)
+ return -1;
+
+ fpriv->hw_state[i] = kmalloc(sizeof(struct ffb_hw_context), GFP_KERNEL);
+ if (fpriv->hw_state[i] == NULL)
+ return -1;
+
+ fpriv->hw_state[i]->is_2d_only = is_2d_only;
+
+ /* Plus one because 0 is the special DRM_KERNEL_CONTEXT. */
+ return i + 1;
+}
+
+static void ffb_save_context(ffb_dev_priv_t *fpriv, int idx)
+{
+ ffb_fbcPtr ffb = fpriv->regs;
+ struct ffb_hw_context *ctx;
+ int i;
+
+ ctx = fpriv->hw_state[idx - 1];
+ if (idx == 0 || ctx == NULL)
+ return;
+
+ if (ctx->is_2d_only) {
+ /* 2D applications only care about certain pieces
+ * of state.
+ */
+ ctx->drawop = upa_readl(&ffb->drawop);
+ ctx->ppc = upa_readl(&ffb->ppc);
+ ctx->wid = upa_readl(&ffb->wid);
+ ctx->fg = upa_readl(&ffb->fg);
+ ctx->bg = upa_readl(&ffb->bg);
+ ctx->xclip = upa_readl(&ffb->xclip);
+ ctx->fbc = upa_readl(&ffb->fbc);
+ ctx->rop = upa_readl(&ffb->rop);
+ ctx->cmp = upa_readl(&ffb->cmp);
+ ctx->matchab = upa_readl(&ffb->matchab);
+ ctx->magnab = upa_readl(&ffb->magnab);
+ ctx->pmask = upa_readl(&ffb->pmask);
+ ctx->xpmask = upa_readl(&ffb->xpmask);
+ ctx->lpat = upa_readl(&ffb->lpat);
+ ctx->fontxy = upa_readl(&ffb->fontxy);
+ ctx->fontw = upa_readl(&ffb->fontw);
+ ctx->fontinc = upa_readl(&ffb->fontinc);
+
+ /* stencil/stencilctl only exists on FFB2+ and later
+ * due to the introduction of 3DRAM-III.
+ */
+ if (fpriv->ffb_type == ffb2_vertical_plus ||
+ fpriv->ffb_type == ffb2_horizontal_plus) {
+ ctx->stencil = upa_readl(&ffb->stencil);
+ ctx->stencilctl = upa_readl(&ffb->stencilctl);
+ }
+
+ for (i = 0; i < 32; i++)
+ ctx->area_pattern[i] = upa_readl(&ffb->pattern[i]);
+ ctx->ucsr = upa_readl(&ffb->ucsr);
+ return;
+ }
+
+ /* Fetch drawop. */
+ ctx->drawop = upa_readl(&ffb->drawop);
+
+ /* If we were saving the vertex registers, this is where
+ * we would do it. We would save 32 32-bit words starting
+ * at ffb->suvtx.
+ */
+
+ /* Capture rendering attributes. */
+
+ ctx->ppc = upa_readl(&ffb->ppc); /* Pixel Processor Control */
+ ctx->wid = upa_readl(&ffb->wid); /* Current WID */
+ ctx->fg = upa_readl(&ffb->fg); /* Constant FG color */
+ ctx->bg = upa_readl(&ffb->bg); /* Constant BG color */
+ ctx->consty = upa_readl(&ffb->consty); /* Constant Y */
+ ctx->constz = upa_readl(&ffb->constz); /* Constant Z */
+ ctx->xclip = upa_readl(&ffb->xclip); /* X plane clip */
+ ctx->dcss = upa_readl(&ffb->dcss); /* Depth Cue Scale Slope */
+ ctx->vclipmin = upa_readl(&ffb->vclipmin); /* Primary XY clip, minimum */
+ ctx->vclipmax = upa_readl(&ffb->vclipmax); /* Primary XY clip, maximum */
+ ctx->vclipzmin = upa_readl(&ffb->vclipzmin); /* Primary Z clip, minimum */
+ ctx->vclipzmax = upa_readl(&ffb->vclipzmax); /* Primary Z clip, maximum */
+ ctx->dcsf = upa_readl(&ffb->dcsf); /* Depth Cue Scale Front Bound */
+ ctx->dcsb = upa_readl(&ffb->dcsb); /* Depth Cue Scale Back Bound */
+ ctx->dczf = upa_readl(&ffb->dczf); /* Depth Cue Scale Z Front */
+ ctx->dczb = upa_readl(&ffb->dczb); /* Depth Cue Scale Z Back */
+ ctx->blendc = upa_readl(&ffb->blendc); /* Alpha Blend Control */
+ ctx->blendc1 = upa_readl(&ffb->blendc1); /* Alpha Blend Color 1 */
+ ctx->blendc2 = upa_readl(&ffb->blendc2); /* Alpha Blend Color 2 */
+ ctx->fbc = upa_readl(&ffb->fbc); /* Frame Buffer Control */
+ ctx->rop = upa_readl(&ffb->rop); /* Raster Operation */
+ ctx->cmp = upa_readl(&ffb->cmp); /* Compare Controls */
+ ctx->matchab = upa_readl(&ffb->matchab); /* Buffer A/B Match Ops */
+ ctx->matchc = upa_readl(&ffb->matchc); /* Buffer C Match Ops */
+ ctx->magnab = upa_readl(&ffb->magnab); /* Buffer A/B Magnitude Ops */
+ ctx->magnc = upa_readl(&ffb->magnc); /* Buffer C Magnitude Ops */
+ ctx->pmask = upa_readl(&ffb->pmask); /* RGB Plane Mask */
+ ctx->xpmask = upa_readl(&ffb->xpmask); /* X Plane Mask */
+ ctx->ypmask = upa_readl(&ffb->ypmask); /* Y Plane Mask */
+ ctx->zpmask = upa_readl(&ffb->zpmask); /* Z Plane Mask */
+
+ /* Auxiliary Clips. */
+ ctx->auxclip0min = upa_readl(&ffb->auxclip[0].min);
+ ctx->auxclip0max = upa_readl(&ffb->auxclip[0].max);
+ ctx->auxclip1min = upa_readl(&ffb->auxclip[1].min);
+ ctx->auxclip1max = upa_readl(&ffb->auxclip[1].max);
+ ctx->auxclip2min = upa_readl(&ffb->auxclip[2].min);
+ ctx->auxclip2max = upa_readl(&ffb->auxclip[2].max);
+ ctx->auxclip3min = upa_readl(&ffb->auxclip[3].min);
+ ctx->auxclip3max = upa_readl(&ffb->auxclip[3].max);
+
+ ctx->lpat = upa_readl(&ffb->lpat); /* Line Pattern */
+ ctx->fontxy = upa_readl(&ffb->fontxy); /* XY Font Coordinate */
+ ctx->fontw = upa_readl(&ffb->fontw); /* Font Width */
+ ctx->fontinc = upa_readl(&ffb->fontinc); /* Font X/Y Increment */
+
+ /* These registers/features only exist on FFB2 and later chips. */
+ if (fpriv->ffb_type >= ffb2_prototype) {
+ ctx->dcss1 = upa_readl(&ffb->dcss1); /* Depth Cue Scale Slope 1 */
+ ctx->dcss2 = upa_readl(&ffb->dcss2); /* Depth Cue Scale Slope 2 */
+ ctx->dcss2 = upa_readl(&ffb->dcss3); /* Depth Cue Scale Slope 3 */
+ ctx->dcs2 = upa_readl(&ffb->dcs2); /* Depth Cue Scale 2 */
+ ctx->dcs3 = upa_readl(&ffb->dcs3); /* Depth Cue Scale 3 */
+ ctx->dcs4 = upa_readl(&ffb->dcs4); /* Depth Cue Scale 4 */
+ ctx->dcd2 = upa_readl(&ffb->dcd2); /* Depth Cue Depth 2 */
+ ctx->dcd3 = upa_readl(&ffb->dcd3); /* Depth Cue Depth 3 */
+ ctx->dcd4 = upa_readl(&ffb->dcd4); /* Depth Cue Depth 4 */
+
+ /* And stencil/stencilctl only exists on FFB2+ and later
+ * due to the introduction of 3DRAM-III.
+ */
+ if (fpriv->ffb_type == ffb2_vertical_plus ||
+ fpriv->ffb_type == ffb2_horizontal_plus) {
+ ctx->stencil = upa_readl(&ffb->stencil);
+ ctx->stencilctl = upa_readl(&ffb->stencilctl);
+ }
+ }
+
+ /* Save the 32x32 area pattern. */
+ for (i = 0; i < 32; i++)
+ ctx->area_pattern[i] = upa_readl(&ffb->pattern[i]);
+
+ /* Finally, stash away the User Constol/Status Register. */
+ ctx->ucsr = upa_readl(&ffb->ucsr);
+}
+
+static void ffb_restore_context(ffb_dev_priv_t *fpriv, int old, int idx)
+{
+ ffb_fbcPtr ffb = fpriv->regs;
+ struct ffb_hw_context *ctx;
+ int i;
+
+ ctx = fpriv->hw_state[idx - 1];
+ if (idx == 0 || ctx == NULL)
+ return;
+
+ if (ctx->is_2d_only) {
+ /* 2D applications only care about certain pieces
+ * of state.
+ */
+ upa_writel(ctx->drawop, &ffb->drawop);
+
+ /* If we were restoring the vertex registers, this is where
+ * we would do it. We would restore 32 32-bit words starting
+ * at ffb->suvtx.
+ */
+
+ upa_writel(ctx->ppc, &ffb->ppc);
+ upa_writel(ctx->wid, &ffb->wid);
+ upa_writel(ctx->fg, &ffb->fg);
+ upa_writel(ctx->bg, &ffb->bg);
+ upa_writel(ctx->xclip, &ffb->xclip);
+ upa_writel(ctx->fbc, &ffb->fbc);
+ upa_writel(ctx->rop, &ffb->rop);
+ upa_writel(ctx->cmp, &ffb->cmp);
+ upa_writel(ctx->matchab, &ffb->matchab);
+ upa_writel(ctx->magnab, &ffb->magnab);
+ upa_writel(ctx->pmask, &ffb->pmask);
+ upa_writel(ctx->xpmask, &ffb->xpmask);
+ upa_writel(ctx->lpat, &ffb->lpat);
+ upa_writel(ctx->fontxy, &ffb->fontxy);
+ upa_writel(ctx->fontw, &ffb->fontw);
+ upa_writel(ctx->fontinc, &ffb->fontinc);
+
+ /* stencil/stencilctl only exists on FFB2+ and later
+ * due to the introduction of 3DRAM-III.
+ */
+ if (fpriv->ffb_type == ffb2_vertical_plus ||
+ fpriv->ffb_type == ffb2_horizontal_plus) {
+ upa_writel(ctx->stencil, &ffb->stencil);
+ upa_writel(ctx->stencilctl, &ffb->stencilctl);
+ upa_writel(0x80000000, &ffb->fbc);
+ upa_writel((ctx->stencilctl | 0x80000),
+ &ffb->rawstencilctl);
+ upa_writel(ctx->fbc, &ffb->fbc);
+ }
+
+ for (i = 0; i < 32; i++)
+ upa_writel(ctx->area_pattern[i], &ffb->pattern[i]);
+ upa_writel((ctx->ucsr & 0xf0000), &ffb->ucsr);
+ return;
+ }
+
+ /* Restore drawop. */
+ upa_writel(ctx->drawop, &ffb->drawop);
+
+ /* If we were restoring the vertex registers, this is where
+ * we would do it. We would restore 32 32-bit words starting
+ * at ffb->suvtx.
+ */
+
+ /* Restore rendering attributes. */
+
+ upa_writel(ctx->ppc, &ffb->ppc); /* Pixel Processor Control */
+ upa_writel(ctx->wid, &ffb->wid); /* Current WID */
+ upa_writel(ctx->fg, &ffb->fg); /* Constant FG color */
+ upa_writel(ctx->bg, &ffb->bg); /* Constant BG color */
+ upa_writel(ctx->consty, &ffb->consty); /* Constant Y */
+ upa_writel(ctx->constz, &ffb->constz); /* Constant Z */
+ upa_writel(ctx->xclip, &ffb->xclip); /* X plane clip */
+ upa_writel(ctx->dcss, &ffb->dcss); /* Depth Cue Scale Slope */
+ upa_writel(ctx->vclipmin, &ffb->vclipmin); /* Primary XY clip, minimum */
+ upa_writel(ctx->vclipmax, &ffb->vclipmax); /* Primary XY clip, maximum */
+ upa_writel(ctx->vclipzmin, &ffb->vclipzmin); /* Primary Z clip, minimum */
+ upa_writel(ctx->vclipzmax, &ffb->vclipzmax); /* Primary Z clip, maximum */
+ upa_writel(ctx->dcsf, &ffb->dcsf); /* Depth Cue Scale Front Bound */
+ upa_writel(ctx->dcsb, &ffb->dcsb); /* Depth Cue Scale Back Bound */
+ upa_writel(ctx->dczf, &ffb->dczf); /* Depth Cue Scale Z Front */
+ upa_writel(ctx->dczb, &ffb->dczb); /* Depth Cue Scale Z Back */
+ upa_writel(ctx->blendc, &ffb->blendc); /* Alpha Blend Control */
+ upa_writel(ctx->blendc1, &ffb->blendc1); /* Alpha Blend Color 1 */
+ upa_writel(ctx->blendc2, &ffb->blendc2); /* Alpha Blend Color 2 */
+ upa_writel(ctx->fbc, &ffb->fbc); /* Frame Buffer Control */
+ upa_writel(ctx->rop, &ffb->rop); /* Raster Operation */
+ upa_writel(ctx->cmp, &ffb->cmp); /* Compare Controls */
+ upa_writel(ctx->matchab, &ffb->matchab); /* Buffer A/B Match Ops */
+ upa_writel(ctx->matchc, &ffb->matchc); /* Buffer C Match Ops */
+ upa_writel(ctx->magnab, &ffb->magnab); /* Buffer A/B Magnitude Ops */
+ upa_writel(ctx->magnc, &ffb->magnc); /* Buffer C Magnitude Ops */
+ upa_writel(ctx->pmask, &ffb->pmask); /* RGB Plane Mask */
+ upa_writel(ctx->xpmask, &ffb->xpmask); /* X Plane Mask */
+ upa_writel(ctx->ypmask, &ffb->ypmask); /* Y Plane Mask */
+ upa_writel(ctx->zpmask, &ffb->zpmask); /* Z Plane Mask */
+
+ /* Auxiliary Clips. */
+ upa_writel(ctx->auxclip0min, &ffb->auxclip[0].min);
+ upa_writel(ctx->auxclip0max, &ffb->auxclip[0].max);
+ upa_writel(ctx->auxclip1min, &ffb->auxclip[1].min);
+ upa_writel(ctx->auxclip1max, &ffb->auxclip[1].max);
+ upa_writel(ctx->auxclip2min, &ffb->auxclip[2].min);
+ upa_writel(ctx->auxclip2max, &ffb->auxclip[2].max);
+ upa_writel(ctx->auxclip3min, &ffb->auxclip[3].min);
+ upa_writel(ctx->auxclip3max, &ffb->auxclip[3].max);
+
+ upa_writel(ctx->lpat, &ffb->lpat); /* Line Pattern */
+ upa_writel(ctx->fontxy, &ffb->fontxy); /* XY Font Coordinate */
+ upa_writel(ctx->fontw, &ffb->fontw); /* Font Width */
+ upa_writel(ctx->fontinc, &ffb->fontinc); /* Font X/Y Increment */
+
+ /* These registers/features only exist on FFB2 and later chips. */
+ if (fpriv->ffb_type >= ffb2_prototype) {
+ upa_writel(ctx->dcss1, &ffb->dcss1); /* Depth Cue Scale Slope 1 */
+ upa_writel(ctx->dcss2, &ffb->dcss2); /* Depth Cue Scale Slope 2 */
+ upa_writel(ctx->dcss3, &ffb->dcss2); /* Depth Cue Scale Slope 3 */
+ upa_writel(ctx->dcs2, &ffb->dcs2); /* Depth Cue Scale 2 */
+ upa_writel(ctx->dcs3, &ffb->dcs3); /* Depth Cue Scale 3 */
+ upa_writel(ctx->dcs4, &ffb->dcs4); /* Depth Cue Scale 4 */
+ upa_writel(ctx->dcd2, &ffb->dcd2); /* Depth Cue Depth 2 */
+ upa_writel(ctx->dcd3, &ffb->dcd3); /* Depth Cue Depth 3 */
+ upa_writel(ctx->dcd4, &ffb->dcd4); /* Depth Cue Depth 4 */
+
+ /* And stencil/stencilctl only exists on FFB2+ and later
+ * due to the introduction of 3DRAM-III.
+ */
+ if (fpriv->ffb_type == ffb2_vertical_plus ||
+ fpriv->ffb_type == ffb2_horizontal_plus) {
+ /* Unfortunately, there is a hardware bug on
+ * the FFB2+ chips which prevents a normal write
+ * to the stencil control register from working
+ * as it should.
+ *
+ * The state controlled by the FFB stencilctl register
+ * really gets transferred to the per-buffer instances
+ * of the stencilctl register in the 3DRAM chips.
+ *
+ * The bug is that FFB does not update buffer C correctly,
+ * so we have to do it by hand for them.
+ */
+
+ /* This will update buffers A and B. */
+ upa_writel(ctx->stencil, &ffb->stencil);
+ upa_writel(ctx->stencilctl, &ffb->stencilctl);
+
+ /* Force FFB to use buffer C 3dram regs. */
+ upa_writel(0x80000000, &ffb->fbc);
+ upa_writel((ctx->stencilctl | 0x80000),
+ &ffb->rawstencilctl);
+
+ /* Now restore the correct FBC controls. */
+ upa_writel(ctx->fbc, &ffb->fbc);
+ }
+ }
+
+ /* Restore the 32x32 area pattern. */
+ for (i = 0; i < 32; i++)
+ upa_writel(ctx->area_pattern[i], &ffb->pattern[i]);
+
+ /* Finally, stash away the User Constol/Status Register.
+ * The only state we really preserve here is the picking
+ * control.
+ */
+ upa_writel((ctx->ucsr & 0xf0000), &ffb->ucsr);
+}
+
+#define FFB_UCSR_FB_BUSY 0x01000000
+#define FFB_UCSR_RP_BUSY 0x02000000
+#define FFB_UCSR_ALL_BUSY (FFB_UCSR_RP_BUSY|FFB_UCSR_FB_BUSY)
+
+static void FFBWait(ffb_fbcPtr ffb)
+{
+ int limit = 100000;
+
+ do {
+ u32 regval = upa_readl(&ffb->ucsr);
+
+ if ((regval & FFB_UCSR_ALL_BUSY) == 0)
+ break;
+ } while (--limit);
+}
+
+int ffb_driver_context_switch(drm_device_t *dev, int old, int new)
+{
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+
+#ifdef DRM_DMA_HISTOGRAM
+ dev->ctx_start = get_cycles();
+#endif
+
+ DRM_DEBUG("Context switch from %d to %d\n", old, new);
+
+ if (new == dev->last_context ||
+ dev->last_context == 0) {
+ dev->last_context = new;
+ return 0;
+ }
+
+ FFBWait(fpriv->regs);
+ ffb_save_context(fpriv, old);
+ ffb_restore_context(fpriv, old, new);
+ FFBWait(fpriv->regs);
+
+ dev->last_context = new;
+
+ return 0;
+}
+
+int ffb_driver_resctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_ctx_res_t res;
+ drm_ctx_t ctx;
+ int i;
+
+ DRM_DEBUG("%d\n", DRM_RESERVED_CONTEXTS);
+ if (copy_from_user(&res, (drm_ctx_res_t __user *)arg, sizeof(res)))
+ return -EFAULT;
+ if (res.count >= DRM_RESERVED_CONTEXTS) {
+ memset(&ctx, 0, sizeof(ctx));
+ for (i = 0; i < DRM_RESERVED_CONTEXTS; i++) {
+ ctx.handle = i;
+ if (copy_to_user(&res.contexts[i],
+ &i,
+ sizeof(i)))
+ return -EFAULT;
+ }
+ }
+ res.count = DRM_RESERVED_CONTEXTS;
+ if (copy_to_user((drm_ctx_res_t __user *)arg, &res, sizeof(res)))
+ return -EFAULT;
+ return 0;
+}
+
+
+int ffb_driver_addctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+ int idx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ idx = DRM(alloc_queue)(dev, (ctx.flags & _DRM_CONTEXT_2DONLY));
+ if (idx < 0)
+ return -ENFILE;
+
+ DRM_DEBUG("%d\n", ctx.handle);
+ ctx.handle = idx;
+ if (copy_to_user((drm_ctx_t __user *)arg, &ctx, sizeof(ctx)))
+ return -EFAULT;
+ return 0;
+}
+
+int ffb_driver_modctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+ struct ffb_hw_context *hwctx;
+ drm_ctx_t ctx;
+ int idx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+
+ idx = ctx.handle;
+ if (idx <= 0 || idx >= FFB_MAX_CTXS)
+ return -EINVAL;
+
+ hwctx = fpriv->hw_state[idx - 1];
+ if (hwctx == NULL)
+ return -EINVAL;
+
+ if ((ctx.flags & _DRM_CONTEXT_2DONLY) == 0)
+ hwctx->is_2d_only = 0;
+ else
+ hwctx->is_2d_only = 1;
+
+ return 0;
+}
+
+int ffb_driver_getctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+ struct ffb_hw_context *hwctx;
+ drm_ctx_t ctx;
+ int idx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+
+ idx = ctx.handle;
+ if (idx <= 0 || idx >= FFB_MAX_CTXS)
+ return -EINVAL;
+
+ hwctx = fpriv->hw_state[idx - 1];
+ if (hwctx == NULL)
+ return -EINVAL;
+
+ if (hwctx->is_2d_only != 0)
+ ctx.flags = _DRM_CONTEXT_2DONLY;
+ else
+ ctx.flags = 0;
+
+ if (copy_to_user((drm_ctx_t __user *)arg, &ctx, sizeof(ctx)))
+ return -EFAULT;
+
+ return 0;
+}
+
+int ffb_driver_switchctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+ return ffb_driver_context_switch(dev, dev->last_context, ctx.handle);
+}
+
+int ffb_driver_newctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_ctx_t ctx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+
+ return 0;
+}
+
+int ffb_driver_rmctx(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_ctx_t ctx;
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+ int idx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+
+ idx = ctx.handle - 1;
+ if (idx < 0 || idx >= FFB_MAX_CTXS)
+ return -EINVAL;
+
+ if (fpriv->hw_state[idx] != NULL) {
+ kfree(fpriv->hw_state[idx]);
+ fpriv->hw_state[idx] = NULL;
+ }
+ return 0;
+}
+
+void ffb_set_context_ioctls(void)
+{
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_ADD_CTX)].func = ffb_driver_addctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_RM_CTX)].func = ffb_driver_rmctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_MOD_CTX)].func = ffb_driver_modctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_GET_CTX)].func = ffb_driver_getctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_SWITCH_CTX)].func = ffb_driver_switchctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_NEW_CTX)].func = ffb_driver_newctx;
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_RES_CTX)].func = ffb_driver_resctx;
+
+}
diff --git a/drivers/char/drm/ffb_drv.c b/drivers/char/drm/ffb_drv.c
new file mode 100644
index 000000000000..ec614fff8f04
--- /dev/null
+++ b/drivers/char/drm/ffb_drv.c
@@ -0,0 +1,365 @@
+/* $Id: ffb_drv.c,v 1.16 2001/10/18 16:00:24 davem Exp $
+ * ffb_drv.c: Creator/Creator3D direct rendering driver.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/config.h>
+#include "ffb.h"
+#include "drmP.h"
+
+#include "ffb_drv.h"
+
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <asm/shmparam.h>
+#include <asm/oplib.h>
+#include <asm/upa.h>
+
+#define DRIVER_AUTHOR "David S. Miller"
+
+#define DRIVER_NAME "ffb"
+#define DRIVER_DESC "Creator/Creator3D"
+#define DRIVER_DATE "20000517"
+
+#define DRIVER_MAJOR 0
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 1
+
+typedef struct _ffb_position_t {
+ int node;
+ int root;
+} ffb_position_t;
+
+static ffb_position_t *ffb_position;
+
+static void get_ffb_type(ffb_dev_priv_t *ffb_priv, int instance)
+{
+ volatile unsigned char *strap_bits;
+ unsigned char val;
+
+ strap_bits = (volatile unsigned char *)
+ (ffb_priv->card_phys_base + 0x00200000UL);
+
+ /* Don't ask, you have to read the value twice for whatever
+ * reason to get correct contents.
+ */
+ val = upa_readb(strap_bits);
+ val = upa_readb(strap_bits);
+ switch (val & 0x78) {
+ case (0x0 << 5) | (0x0 << 3):
+ ffb_priv->ffb_type = ffb1_prototype;
+ printk("ffb%d: Detected FFB1 pre-FCS prototype\n", instance);
+ break;
+ case (0x0 << 5) | (0x1 << 3):
+ ffb_priv->ffb_type = ffb1_standard;
+ printk("ffb%d: Detected FFB1\n", instance);
+ break;
+ case (0x0 << 5) | (0x3 << 3):
+ ffb_priv->ffb_type = ffb1_speedsort;
+ printk("ffb%d: Detected FFB1-SpeedSort\n", instance);
+ break;
+ case (0x1 << 5) | (0x0 << 3):
+ ffb_priv->ffb_type = ffb2_prototype;
+ printk("ffb%d: Detected FFB2/vertical pre-FCS prototype\n", instance);
+ break;
+ case (0x1 << 5) | (0x1 << 3):
+ ffb_priv->ffb_type = ffb2_vertical;
+ printk("ffb%d: Detected FFB2/vertical\n", instance);
+ break;
+ case (0x1 << 5) | (0x2 << 3):
+ ffb_priv->ffb_type = ffb2_vertical_plus;
+ printk("ffb%d: Detected FFB2+/vertical\n", instance);
+ break;
+ case (0x2 << 5) | (0x0 << 3):
+ ffb_priv->ffb_type = ffb2_horizontal;
+ printk("ffb%d: Detected FFB2/horizontal\n", instance);
+ break;
+ case (0x2 << 5) | (0x2 << 3):
+ ffb_priv->ffb_type = ffb2_horizontal;
+ printk("ffb%d: Detected FFB2+/horizontal\n", instance);
+ break;
+ default:
+ ffb_priv->ffb_type = ffb2_vertical;
+ printk("ffb%d: Unknown boardID[%08x], assuming FFB2\n", instance, val);
+ break;
+ };
+}
+
+static void ffb_apply_upa_parent_ranges(int parent,
+ struct linux_prom64_registers *regs)
+{
+ struct linux_prom64_ranges ranges[PROMREG_MAX];
+ char name[128];
+ int len, i;
+
+ prom_getproperty(parent, "name", name, sizeof(name));
+ if (strcmp(name, "upa") != 0)
+ return;
+
+ len = prom_getproperty(parent, "ranges", (void *) ranges, sizeof(ranges));
+ if (len <= 0)
+ return;
+
+ len /= sizeof(struct linux_prom64_ranges);
+ for (i = 0; i < len; i++) {
+ struct linux_prom64_ranges *rng = &ranges[i];
+ u64 phys_addr = regs->phys_addr;
+
+ if (phys_addr >= rng->ot_child_base &&
+ phys_addr < (rng->ot_child_base + rng->or_size)) {
+ regs->phys_addr -= rng->ot_child_base;
+ regs->phys_addr += rng->ot_parent_base;
+ return;
+ }
+ }
+
+ return;
+}
+
+static int ffb_init_one(drm_device_t *dev, int prom_node, int parent_node,
+ int instance)
+{
+ struct linux_prom64_registers regs[2*PROMREG_MAX];
+ ffb_dev_priv_t *ffb_priv = (ffb_dev_priv_t *)dev->dev_private;
+ int i;
+
+ ffb_priv->prom_node = prom_node;
+ if (prom_getproperty(ffb_priv->prom_node, "reg",
+ (void *)regs, sizeof(regs)) <= 0) {
+ return -EINVAL;
+ }
+ ffb_apply_upa_parent_ranges(parent_node, &regs[0]);
+ ffb_priv->card_phys_base = regs[0].phys_addr;
+ ffb_priv->regs = (ffb_fbcPtr)
+ (regs[0].phys_addr + 0x00600000UL);
+ get_ffb_type(ffb_priv, instance);
+ for (i = 0; i < FFB_MAX_CTXS; i++)
+ ffb_priv->hw_state[i] = NULL;
+
+ return 0;
+}
+
+static drm_map_t *ffb_find_map(struct file *filp, unsigned long off)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev;
+ drm_map_list_t *r_list;
+ struct list_head *list;
+ drm_map_t *map;
+
+ if (!priv || (dev = priv->dev) == NULL)
+ return NULL;
+
+ list_for_each(list, &dev->maplist->head) {
+ unsigned long uoff;
+
+ r_list = (drm_map_list_t *)list;
+ map = r_list->map;
+ if (!map)
+ continue;
+ uoff = (map->offset & 0xffffffff);
+ if (uoff == off)
+ return map;
+ }
+
+ return NULL;
+}
+
+unsigned long ffb_get_unmapped_area(struct file *filp,
+ unsigned long hint,
+ unsigned long len,
+ unsigned long pgoff,
+ unsigned long flags)
+{
+ drm_map_t *map = ffb_find_map(filp, pgoff << PAGE_SHIFT);
+ unsigned long addr = -ENOMEM;
+
+ if (!map)
+ return get_unmapped_area(NULL, hint, len, pgoff, flags);
+
+ if (map->type == _DRM_FRAME_BUFFER ||
+ map->type == _DRM_REGISTERS) {
+#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
+ addr = get_fb_unmapped_area(filp, hint, len, pgoff, flags);
+#else
+ addr = get_unmapped_area(NULL, hint, len, pgoff, flags);
+#endif
+ } else if (map->type == _DRM_SHM && SHMLBA > PAGE_SIZE) {
+ unsigned long slack = SHMLBA - PAGE_SIZE;
+
+ addr = get_unmapped_area(NULL, hint, len + slack, pgoff, flags);
+ if (!(addr & ~PAGE_MASK)) {
+ unsigned long kvirt = (unsigned long) map->handle;
+
+ if ((kvirt & (SHMLBA - 1)) != (addr & (SHMLBA - 1))) {
+ unsigned long koff, aoff;
+
+ koff = kvirt & (SHMLBA - 1);
+ aoff = addr & (SHMLBA - 1);
+ if (koff < aoff)
+ koff += SHMLBA;
+
+ addr += (koff - aoff);
+ }
+ }
+ } else {
+ addr = get_unmapped_area(NULL, hint, len, pgoff, flags);
+ }
+
+ return addr;
+}
+
+static int ffb_presetup(drm_device_t *dev)
+{
+ ffb_dev_priv_t *ffb_priv;
+ int ret = 0;
+ int i = 0;
+
+ /* Check for the case where no device was found. */
+ if (ffb_position == NULL)
+ return -ENODEV;
+
+ /* code used to use numdevs no numdevs anymore */
+ ffb_priv = kmalloc(sizeof(ffb_dev_priv_t), GFP_KERNEL);
+ if (!ffb_priv)
+ return -ENOMEM;
+ memset(ffb_priv, 0, sizeof(*ffb_priv));
+ dev->dev_private = ffb_priv;
+
+ ret = ffb_init_one(dev,
+ ffb_position[i].node,
+ ffb_position[i].root,
+ i);
+ return ret;
+}
+
+static void ffb_driver_release(drm_device_t *dev, struct file *filp)
+{
+ ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+ int context = _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock);
+ int idx;
+
+ idx = context - 1;
+ if (fpriv &&
+ context != DRM_KERNEL_CONTEXT &&
+ fpriv->hw_state[idx] != NULL) {
+ kfree(fpriv->hw_state[idx]);
+ fpriv->hw_state[idx] = NULL;
+ }
+}
+
+static void ffb_driver_pretakedown(drm_device_t *dev)
+{
+ if (dev->dev_private) kfree(dev->dev_private);
+}
+
+static int ffb_driver_postcleanup(drm_device_t *dev)
+{
+ if (ffb_position != NULL) kfree(ffb_position);
+ return 0;
+}
+
+static void ffb_driver_kernel_context_switch_unlock(struct drm_device *dev, drm_lock_t *lock)
+{
+ dev->lock.filp = 0;
+ {
+ __volatile__ unsigned int *plock = &dev->lock.hw_lock->lock;
+ unsigned int old, new, prev, ctx;
+
+ ctx = lock->context;
+ do {
+ old = *plock;
+ new = ctx;
+ prev = cmpxchg(plock, old, new);
+ } while (prev != old);
+ }
+ wake_up_interruptible(&dev->lock.lock_queue);
+}
+
+static unsigned long ffb_driver_get_map_ofs(drm_map_t *map)
+{
+ return (map->offset & 0xffffffff);
+}
+
+static unsigned long ffb_driver_get_reg_ofs(drm_device_t *dev)
+{
+ ffb_dev_priv_t *ffb_priv = (ffb_dev_priv_t *)dev->dev_private;
+
+ if (ffb_priv)
+ return ffb_priv->card_phys_base;
+
+ return 0;
+}
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->minor
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static drm_ioctl_desc_t ioctls[] = {
+
+};
+
+static struct drm_driver driver = {
+ .driver_features = 0,
+ .dev_priv_size = sizeof(u32),
+ .release = ffb_driver_release,
+ .presetup = ffb_presetup,
+ .pretakedown = ffb_driver_pretakedown,
+ .postcleanup = ffb_driver_postcleanup,
+ .kernel_context_switch = ffb_driver_context_switch,
+ .kernel_context_switch_unlock = ffb_driver_kernel_context_switch_unlock,
+ .get_map_ofs = ffb_driver_get_map_ofs,
+ .get_reg_ofs = ffb_driver_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = ioctls,
+ .num_ioctls = DRM_ARRAY_SIZE(ioctls),
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+};
+
+static int __init ffb_init(void)
+{
+ return -ENODEV;
+}
+
+static void __exit ffb_exit(void)
+{
+}
+
+module_init(ffb_init);
+module_exit(ffb_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/ffb_drv.h b/drivers/char/drm/ffb_drv.h
new file mode 100644
index 000000000000..8bf7f1e143f1
--- /dev/null
+++ b/drivers/char/drm/ffb_drv.h
@@ -0,0 +1,286 @@
+/* $Id: ffb_drv.h,v 1.1 2000/06/01 04:24:39 davem Exp $
+ * ffb_drv.h: Creator/Creator3D direct rendering driver.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ */
+
+/* Auxilliary clips. */
+typedef struct {
+ volatile unsigned int min;
+ volatile unsigned int max;
+} ffb_auxclip, *ffb_auxclipPtr;
+
+/* FFB register set. */
+typedef struct _ffb_fbc {
+ /* Next vertex registers, on the right we list which drawops
+ * use said register and the logical name the register has in
+ * that context.
+ */ /* DESCRIPTION DRAWOP(NAME) */
+/*0x00*/unsigned int pad1[3]; /* Reserved */
+/*0x0c*/volatile unsigned int alpha; /* ALPHA Transparency */
+/*0x10*/volatile unsigned int red; /* RED */
+/*0x14*/volatile unsigned int green; /* GREEN */
+/*0x18*/volatile unsigned int blue; /* BLUE */
+/*0x1c*/volatile unsigned int z; /* DEPTH */
+/*0x20*/volatile unsigned int y; /* Y triangle(DOYF) */
+ /* aadot(DYF) */
+ /* ddline(DYF) */
+ /* aaline(DYF) */
+/*0x24*/volatile unsigned int x; /* X triangle(DOXF) */
+ /* aadot(DXF) */
+ /* ddline(DXF) */
+ /* aaline(DXF) */
+/*0x28*/unsigned int pad2[2]; /* Reserved */
+/*0x30*/volatile unsigned int ryf; /* Y (alias to DOYF) ddline(RYF) */
+ /* aaline(RYF) */
+ /* triangle(RYF) */
+/*0x34*/volatile unsigned int rxf; /* X ddline(RXF) */
+ /* aaline(RXF) */
+ /* triangle(RXF) */
+/*0x38*/unsigned int pad3[2]; /* Reserved */
+/*0x40*/volatile unsigned int dmyf; /* Y (alias to DOYF) triangle(DMYF) */
+/*0x44*/volatile unsigned int dmxf; /* X triangle(DMXF) */
+/*0x48*/unsigned int pad4[2]; /* Reserved */
+/*0x50*/volatile unsigned int ebyi; /* Y (alias to RYI) polygon(EBYI) */
+/*0x54*/volatile unsigned int ebxi; /* X polygon(EBXI) */
+/*0x58*/unsigned int pad5[2]; /* Reserved */
+/*0x60*/volatile unsigned int by; /* Y brline(RYI) */
+ /* fastfill(OP) */
+ /* polygon(YI) */
+ /* rectangle(YI) */
+ /* bcopy(SRCY) */
+ /* vscroll(SRCY) */
+/*0x64*/volatile unsigned int bx; /* X brline(RXI) */
+ /* polygon(XI) */
+ /* rectangle(XI) */
+ /* bcopy(SRCX) */
+ /* vscroll(SRCX) */
+ /* fastfill(GO) */
+/*0x68*/volatile unsigned int dy; /* destination Y fastfill(DSTY) */
+ /* bcopy(DSRY) */
+ /* vscroll(DSRY) */
+/*0x6c*/volatile unsigned int dx; /* destination X fastfill(DSTX) */
+ /* bcopy(DSTX) */
+ /* vscroll(DSTX) */
+/*0x70*/volatile unsigned int bh; /* Y (alias to RYI) brline(DYI) */
+ /* dot(DYI) */
+ /* polygon(ETYI) */
+ /* Height fastfill(H) */
+ /* bcopy(H) */
+ /* vscroll(H) */
+ /* Y count fastfill(NY) */
+/*0x74*/volatile unsigned int bw; /* X dot(DXI) */
+ /* brline(DXI) */
+ /* polygon(ETXI) */
+ /* fastfill(W) */
+ /* bcopy(W) */
+ /* vscroll(W) */
+ /* fastfill(NX) */
+/*0x78*/unsigned int pad6[2]; /* Reserved */
+/*0x80*/unsigned int pad7[32]; /* Reserved */
+
+ /* Setup Unit's vertex state register */
+/*100*/ volatile unsigned int suvtx;
+/*104*/ unsigned int pad8[63]; /* Reserved */
+
+ /* Frame Buffer Control Registers */
+/*200*/ volatile unsigned int ppc; /* Pixel Processor Control */
+/*204*/ volatile unsigned int wid; /* Current WID */
+/*208*/ volatile unsigned int fg; /* FG data */
+/*20c*/ volatile unsigned int bg; /* BG data */
+/*210*/ volatile unsigned int consty; /* Constant Y */
+/*214*/ volatile unsigned int constz; /* Constant Z */
+/*218*/ volatile unsigned int xclip; /* X Clip */
+/*21c*/ volatile unsigned int dcss; /* Depth Cue Scale Slope */
+/*220*/ volatile unsigned int vclipmin; /* Viewclip XY Min Bounds */
+/*224*/ volatile unsigned int vclipmax; /* Viewclip XY Max Bounds */
+/*228*/ volatile unsigned int vclipzmin; /* Viewclip Z Min Bounds */
+/*22c*/ volatile unsigned int vclipzmax; /* Viewclip Z Max Bounds */
+/*230*/ volatile unsigned int dcsf; /* Depth Cue Scale Front Bound */
+/*234*/ volatile unsigned int dcsb; /* Depth Cue Scale Back Bound */
+/*238*/ volatile unsigned int dczf; /* Depth Cue Z Front */
+/*23c*/ volatile unsigned int dczb; /* Depth Cue Z Back */
+/*240*/ unsigned int pad9; /* Reserved */
+/*244*/ volatile unsigned int blendc; /* Alpha Blend Control */
+/*248*/ volatile unsigned int blendc1; /* Alpha Blend Color 1 */
+/*24c*/ volatile unsigned int blendc2; /* Alpha Blend Color 2 */
+/*250*/ volatile unsigned int fbramitc; /* FB RAM Interleave Test Control */
+/*254*/ volatile unsigned int fbc; /* Frame Buffer Control */
+/*258*/ volatile unsigned int rop; /* Raster OPeration */
+/*25c*/ volatile unsigned int cmp; /* Frame Buffer Compare */
+/*260*/ volatile unsigned int matchab; /* Buffer AB Match Mask */
+/*264*/ volatile unsigned int matchc; /* Buffer C(YZ) Match Mask */
+/*268*/ volatile unsigned int magnab; /* Buffer AB Magnitude Mask */
+/*26c*/ volatile unsigned int magnc; /* Buffer C(YZ) Magnitude Mask */
+/*270*/ volatile unsigned int fbcfg0; /* Frame Buffer Config 0 */
+/*274*/ volatile unsigned int fbcfg1; /* Frame Buffer Config 1 */
+/*278*/ volatile unsigned int fbcfg2; /* Frame Buffer Config 2 */
+/*27c*/ volatile unsigned int fbcfg3; /* Frame Buffer Config 3 */
+/*280*/ volatile unsigned int ppcfg; /* Pixel Processor Config */
+/*284*/ volatile unsigned int pick; /* Picking Control */
+/*288*/ volatile unsigned int fillmode; /* FillMode */
+/*28c*/ volatile unsigned int fbramwac; /* FB RAM Write Address Control */
+/*290*/ volatile unsigned int pmask; /* RGB PlaneMask */
+/*294*/ volatile unsigned int xpmask; /* X PlaneMask */
+/*298*/ volatile unsigned int ypmask; /* Y PlaneMask */
+/*29c*/ volatile unsigned int zpmask; /* Z PlaneMask */
+/*2a0*/ ffb_auxclip auxclip[4]; /* Auxilliary Viewport Clip */
+
+ /* New 3dRAM III support regs */
+/*2c0*/ volatile unsigned int rawblend2;
+/*2c4*/ volatile unsigned int rawpreblend;
+/*2c8*/ volatile unsigned int rawstencil;
+/*2cc*/ volatile unsigned int rawstencilctl;
+/*2d0*/ volatile unsigned int threedram1;
+/*2d4*/ volatile unsigned int threedram2;
+/*2d8*/ volatile unsigned int passin;
+/*2dc*/ volatile unsigned int rawclrdepth;
+/*2e0*/ volatile unsigned int rawpmask;
+/*2e4*/ volatile unsigned int rawcsrc;
+/*2e8*/ volatile unsigned int rawmatch;
+/*2ec*/ volatile unsigned int rawmagn;
+/*2f0*/ volatile unsigned int rawropblend;
+/*2f4*/ volatile unsigned int rawcmp;
+/*2f8*/ volatile unsigned int rawwac;
+/*2fc*/ volatile unsigned int fbramid;
+
+/*300*/ volatile unsigned int drawop; /* Draw OPeration */
+/*304*/ unsigned int pad10[2]; /* Reserved */
+/*30c*/ volatile unsigned int lpat; /* Line Pattern control */
+/*310*/ unsigned int pad11; /* Reserved */
+/*314*/ volatile unsigned int fontxy; /* XY Font coordinate */
+/*318*/ volatile unsigned int fontw; /* Font Width */
+/*31c*/ volatile unsigned int fontinc; /* Font Increment */
+/*320*/ volatile unsigned int font; /* Font bits */
+/*324*/ unsigned int pad12[3]; /* Reserved */
+/*330*/ volatile unsigned int blend2;
+/*334*/ volatile unsigned int preblend;
+/*338*/ volatile unsigned int stencil;
+/*33c*/ volatile unsigned int stencilctl;
+
+/*340*/ unsigned int pad13[4]; /* Reserved */
+/*350*/ volatile unsigned int dcss1; /* Depth Cue Scale Slope 1 */
+/*354*/ volatile unsigned int dcss2; /* Depth Cue Scale Slope 2 */
+/*358*/ volatile unsigned int dcss3; /* Depth Cue Scale Slope 3 */
+/*35c*/ volatile unsigned int widpmask;
+/*360*/ volatile unsigned int dcs2;
+/*364*/ volatile unsigned int dcs3;
+/*368*/ volatile unsigned int dcs4;
+/*36c*/ unsigned int pad14; /* Reserved */
+/*370*/ volatile unsigned int dcd2;
+/*374*/ volatile unsigned int dcd3;
+/*378*/ volatile unsigned int dcd4;
+/*37c*/ unsigned int pad15; /* Reserved */
+/*380*/ volatile unsigned int pattern[32]; /* area Pattern */
+/*400*/ unsigned int pad16[8]; /* Reserved */
+/*420*/ volatile unsigned int reset; /* chip RESET */
+/*424*/ unsigned int pad17[247]; /* Reserved */
+/*800*/ volatile unsigned int devid; /* Device ID */
+/*804*/ unsigned int pad18[63]; /* Reserved */
+/*900*/ volatile unsigned int ucsr; /* User Control & Status Register */
+/*904*/ unsigned int pad19[31]; /* Reserved */
+/*980*/ volatile unsigned int mer; /* Mode Enable Register */
+/*984*/ unsigned int pad20[1439]; /* Reserved */
+} ffb_fbc, *ffb_fbcPtr;
+
+struct ffb_hw_context {
+ int is_2d_only;
+
+ unsigned int ppc;
+ unsigned int wid;
+ unsigned int fg;
+ unsigned int bg;
+ unsigned int consty;
+ unsigned int constz;
+ unsigned int xclip;
+ unsigned int dcss;
+ unsigned int vclipmin;
+ unsigned int vclipmax;
+ unsigned int vclipzmin;
+ unsigned int vclipzmax;
+ unsigned int dcsf;
+ unsigned int dcsb;
+ unsigned int dczf;
+ unsigned int dczb;
+ unsigned int blendc;
+ unsigned int blendc1;
+ unsigned int blendc2;
+ unsigned int fbc;
+ unsigned int rop;
+ unsigned int cmp;
+ unsigned int matchab;
+ unsigned int matchc;
+ unsigned int magnab;
+ unsigned int magnc;
+ unsigned int pmask;
+ unsigned int xpmask;
+ unsigned int ypmask;
+ unsigned int zpmask;
+ unsigned int auxclip0min;
+ unsigned int auxclip0max;
+ unsigned int auxclip1min;
+ unsigned int auxclip1max;
+ unsigned int auxclip2min;
+ unsigned int auxclip2max;
+ unsigned int auxclip3min;
+ unsigned int auxclip3max;
+ unsigned int drawop;
+ unsigned int lpat;
+ unsigned int fontxy;
+ unsigned int fontw;
+ unsigned int fontinc;
+ unsigned int area_pattern[32];
+ unsigned int ucsr;
+ unsigned int stencil;
+ unsigned int stencilctl;
+ unsigned int dcss1;
+ unsigned int dcss2;
+ unsigned int dcss3;
+ unsigned int dcs2;
+ unsigned int dcs3;
+ unsigned int dcs4;
+ unsigned int dcd2;
+ unsigned int dcd3;
+ unsigned int dcd4;
+ unsigned int mer;
+};
+
+#define FFB_MAX_CTXS 32
+
+enum ffb_chip_type {
+ ffb1_prototype = 0, /* Early pre-FCS FFB */
+ ffb1_standard, /* First FCS FFB, 100Mhz UPA, 66MHz gclk */
+ ffb1_speedsort, /* Second FCS FFB, 100Mhz UPA, 75MHz gclk */
+ ffb2_prototype, /* Early pre-FCS vertical FFB2 */
+ ffb2_vertical, /* First FCS FFB2/vertical, 100Mhz UPA, 100MHZ gclk,
+ 75(SingleBuffer)/83(DoubleBuffer) MHz fclk */
+ ffb2_vertical_plus, /* Second FCS FFB2/vertical, same timings */
+ ffb2_horizontal, /* First FCS FFB2/horizontal, same timings as FFB2/vert */
+ ffb2_horizontal_plus, /* Second FCS FFB2/horizontal, same timings */
+ afb_m3, /* FCS Elite3D, 3 float chips */
+ afb_m6 /* FCS Elite3D, 6 float chips */
+};
+
+typedef struct ffb_dev_priv {
+ /* Misc software state. */
+ int prom_node;
+ enum ffb_chip_type ffb_type;
+ u64 card_phys_base;
+ struct miscdevice miscdev;
+
+ /* Controller registers. */
+ ffb_fbcPtr regs;
+
+ /* Context table. */
+ struct ffb_hw_context *hw_state[FFB_MAX_CTXS];
+} ffb_dev_priv_t;
+
+extern unsigned long ffb_get_unmapped_area(struct file *filp,
+ unsigned long hint,
+ unsigned long len,
+ unsigned long pgoff,
+ unsigned long flags);
+extern void ffb_set_context_ioctls(void);
+extern drm_ioctl_desc_t DRM(ioctls)[];
+
+extern int ffb_driver_context_switch(drm_device_t *dev, int old, int new);
diff --git a/drivers/char/drm/gamma_context.h b/drivers/char/drm/gamma_context.h
new file mode 100644
index 000000000000..d11b507f87ee
--- /dev/null
+++ b/drivers/char/drm/gamma_context.h
@@ -0,0 +1,492 @@
+/* drm_context.h -- IOCTLs for generic contexts -*- linux-c -*-
+ * Created: Fri Nov 24 18:31:37 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ * ChangeLog:
+ * 2001-11-16 Torsten Duwe <duwe@caldera.de>
+ * added context constructor/destructor hooks,
+ * needed by SiS driver's memory management.
+ */
+
+/* ================================================================
+ * Old-style context support -- only used by gamma.
+ */
+
+
+/* The drm_read and drm_write_string code (especially that which manages
+ the circular buffer), is based on Alessandro Rubini's LINUX DEVICE
+ DRIVERS (Cambridge: O'Reilly, 1998), pages 111-113. */
+
+ssize_t gamma_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ int left;
+ int avail;
+ int send;
+ int cur;
+
+ DRM_DEBUG("%p, %p\n", dev->buf_rp, dev->buf_wp);
+
+ while (dev->buf_rp == dev->buf_wp) {
+ DRM_DEBUG(" sleeping\n");
+ if (filp->f_flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+ interruptible_sleep_on(&dev->buf_readers);
+ if (signal_pending(current)) {
+ DRM_DEBUG(" interrupted\n");
+ return -ERESTARTSYS;
+ }
+ DRM_DEBUG(" awake\n");
+ }
+
+ left = (dev->buf_rp + DRM_BSZ - dev->buf_wp) % DRM_BSZ;
+ avail = DRM_BSZ - left;
+ send = DRM_MIN(avail, count);
+
+ while (send) {
+ if (dev->buf_wp > dev->buf_rp) {
+ cur = DRM_MIN(send, dev->buf_wp - dev->buf_rp);
+ } else {
+ cur = DRM_MIN(send, dev->buf_end - dev->buf_rp);
+ }
+ if (copy_to_user(buf, dev->buf_rp, cur))
+ return -EFAULT;
+ dev->buf_rp += cur;
+ if (dev->buf_rp == dev->buf_end) dev->buf_rp = dev->buf;
+ send -= cur;
+ }
+
+ wake_up_interruptible(&dev->buf_writers);
+ return DRM_MIN(avail, count);
+}
+
+
+/* In an incredibly convoluted setup, the kernel module actually calls
+ * back into the X server to perform context switches on behalf of the
+ * 3d clients.
+ */
+int DRM(write_string)(drm_device_t *dev, const char *s)
+{
+ int left = (dev->buf_rp + DRM_BSZ - dev->buf_wp) % DRM_BSZ;
+ int send = strlen(s);
+ int count;
+
+ DRM_DEBUG("%d left, %d to send (%p, %p)\n",
+ left, send, dev->buf_rp, dev->buf_wp);
+
+ if (left == 1 || dev->buf_wp != dev->buf_rp) {
+ DRM_ERROR("Buffer not empty (%d left, wp = %p, rp = %p)\n",
+ left,
+ dev->buf_wp,
+ dev->buf_rp);
+ }
+
+ while (send) {
+ if (dev->buf_wp >= dev->buf_rp) {
+ count = DRM_MIN(send, dev->buf_end - dev->buf_wp);
+ if (count == left) --count; /* Leave a hole */
+ } else {
+ count = DRM_MIN(send, dev->buf_rp - dev->buf_wp - 1);
+ }
+ strncpy(dev->buf_wp, s, count);
+ dev->buf_wp += count;
+ if (dev->buf_wp == dev->buf_end) dev->buf_wp = dev->buf;
+ send -= count;
+ }
+
+ if (dev->buf_async) kill_fasync(&dev->buf_async, SIGIO, POLL_IN);
+
+ DRM_DEBUG("waking\n");
+ wake_up_interruptible(&dev->buf_readers);
+ return 0;
+}
+
+unsigned int gamma_fops_poll(struct file *filp, struct poll_table_struct *wait)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+
+ poll_wait(filp, &dev->buf_readers, wait);
+ if (dev->buf_wp != dev->buf_rp) return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+int DRM(context_switch)(drm_device_t *dev, int old, int new)
+{
+ char buf[64];
+ drm_queue_t *q;
+
+ if (test_and_set_bit(0, &dev->context_flag)) {
+ DRM_ERROR("Reentering -- FIXME\n");
+ return -EBUSY;
+ }
+
+ DRM_DEBUG("Context switch from %d to %d\n", old, new);
+
+ if (new >= dev->queue_count) {
+ clear_bit(0, &dev->context_flag);
+ return -EINVAL;
+ }
+
+ if (new == dev->last_context) {
+ clear_bit(0, &dev->context_flag);
+ return 0;
+ }
+
+ q = dev->queuelist[new];
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) == 1) {
+ atomic_dec(&q->use_count);
+ clear_bit(0, &dev->context_flag);
+ return -EINVAL;
+ }
+
+ /* This causes the X server to wake up & do a bunch of hardware
+ * interaction to actually effect the context switch.
+ */
+ sprintf(buf, "C %d %d\n", old, new);
+ DRM(write_string)(dev, buf);
+
+ atomic_dec(&q->use_count);
+
+ return 0;
+}
+
+int DRM(context_switch_complete)(drm_device_t *dev, int new)
+{
+ drm_device_dma_t *dma = dev->dma;
+
+ dev->last_context = new; /* PRE/POST: This is the _only_ writer. */
+ dev->last_switch = jiffies;
+
+ if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
+ DRM_ERROR("Lock isn't held after context switch\n");
+ }
+
+ if (!dma || !(dma->next_buffer && dma->next_buffer->while_locked)) {
+ if (DRM(lock_free)(dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT)) {
+ DRM_ERROR("Cannot free lock\n");
+ }
+ }
+
+ clear_bit(0, &dev->context_flag);
+ wake_up_interruptible(&dev->context_wait);
+
+ return 0;
+}
+
+static int DRM(init_queue)(drm_device_t *dev, drm_queue_t *q, drm_ctx_t *ctx)
+{
+ DRM_DEBUG("\n");
+
+ if (atomic_read(&q->use_count) != 1
+ || atomic_read(&q->finalization)
+ || atomic_read(&q->block_count)) {
+ DRM_ERROR("New queue is already in use: u%d f%d b%d\n",
+ atomic_read(&q->use_count),
+ atomic_read(&q->finalization),
+ atomic_read(&q->block_count));
+ }
+
+ atomic_set(&q->finalization, 0);
+ atomic_set(&q->block_count, 0);
+ atomic_set(&q->block_read, 0);
+ atomic_set(&q->block_write, 0);
+ atomic_set(&q->total_queued, 0);
+ atomic_set(&q->total_flushed, 0);
+ atomic_set(&q->total_locks, 0);
+
+ init_waitqueue_head(&q->write_queue);
+ init_waitqueue_head(&q->read_queue);
+ init_waitqueue_head(&q->flush_queue);
+
+ q->flags = ctx->flags;
+
+ DRM(waitlist_create)(&q->waitlist, dev->dma->buf_count);
+
+ return 0;
+}
+
+
+/* drm_alloc_queue:
+PRE: 1) dev->queuelist[0..dev->queue_count] is allocated and will not
+ disappear (so all deallocation must be done after IOCTLs are off)
+ 2) dev->queue_count < dev->queue_slots
+ 3) dev->queuelist[i].use_count == 0 and
+ dev->queuelist[i].finalization == 0 if i not in use
+POST: 1) dev->queuelist[i].use_count == 1
+ 2) dev->queue_count < dev->queue_slots */
+
+static int DRM(alloc_queue)(drm_device_t *dev)
+{
+ int i;
+ drm_queue_t *queue;
+ int oldslots;
+ int newslots;
+ /* Check for a free queue */
+ for (i = 0; i < dev->queue_count; i++) {
+ atomic_inc(&dev->queuelist[i]->use_count);
+ if (atomic_read(&dev->queuelist[i]->use_count) == 1
+ && !atomic_read(&dev->queuelist[i]->finalization)) {
+ DRM_DEBUG("%d (free)\n", i);
+ return i;
+ }
+ atomic_dec(&dev->queuelist[i]->use_count);
+ }
+ /* Allocate a new queue */
+ down(&dev->struct_sem);
+
+ queue = DRM(alloc)(sizeof(*queue), DRM_MEM_QUEUES);
+ memset(queue, 0, sizeof(*queue));
+ atomic_set(&queue->use_count, 1);
+
+ ++dev->queue_count;
+ if (dev->queue_count >= dev->queue_slots) {
+ oldslots = dev->queue_slots * sizeof(*dev->queuelist);
+ if (!dev->queue_slots) dev->queue_slots = 1;
+ dev->queue_slots *= 2;
+ newslots = dev->queue_slots * sizeof(*dev->queuelist);
+
+ dev->queuelist = DRM(realloc)(dev->queuelist,
+ oldslots,
+ newslots,
+ DRM_MEM_QUEUES);
+ if (!dev->queuelist) {
+ up(&dev->struct_sem);
+ DRM_DEBUG("out of memory\n");
+ return -ENOMEM;
+ }
+ }
+ dev->queuelist[dev->queue_count-1] = queue;
+
+ up(&dev->struct_sem);
+ DRM_DEBUG("%d (new)\n", dev->queue_count - 1);
+ return dev->queue_count - 1;
+}
+
+int DRM(resctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_ctx_res_t __user *argp = (void __user *)arg;
+ drm_ctx_res_t res;
+ drm_ctx_t ctx;
+ int i;
+
+ DRM_DEBUG("%d\n", DRM_RESERVED_CONTEXTS);
+ if (copy_from_user(&res, argp, sizeof(res)))
+ return -EFAULT;
+ if (res.count >= DRM_RESERVED_CONTEXTS) {
+ memset(&ctx, 0, sizeof(ctx));
+ for (i = 0; i < DRM_RESERVED_CONTEXTS; i++) {
+ ctx.handle = i;
+ if (copy_to_user(&res.contexts[i],
+ &i,
+ sizeof(i)))
+ return -EFAULT;
+ }
+ }
+ res.count = DRM_RESERVED_CONTEXTS;
+ if (copy_to_user(argp, &res, sizeof(res)))
+ return -EFAULT;
+ return 0;
+}
+
+int DRM(addctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+ drm_ctx_t __user *argp = (void __user *)arg;
+
+ if (copy_from_user(&ctx, argp, sizeof(ctx)))
+ return -EFAULT;
+ if ((ctx.handle = DRM(alloc_queue)(dev)) == DRM_KERNEL_CONTEXT) {
+ /* Init kernel's context and get a new one. */
+ DRM(init_queue)(dev, dev->queuelist[ctx.handle], &ctx);
+ ctx.handle = DRM(alloc_queue)(dev);
+ }
+ DRM(init_queue)(dev, dev->queuelist[ctx.handle], &ctx);
+ DRM_DEBUG("%d\n", ctx.handle);
+ if (copy_to_user(argp, &ctx, sizeof(ctx)))
+ return -EFAULT;
+ return 0;
+}
+
+int DRM(modctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+ drm_queue_t *q;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+
+ DRM_DEBUG("%d\n", ctx.handle);
+
+ if (ctx.handle < 0 || ctx.handle >= dev->queue_count) return -EINVAL;
+ q = dev->queuelist[ctx.handle];
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) == 1) {
+ /* No longer in use */
+ atomic_dec(&q->use_count);
+ return -EINVAL;
+ }
+
+ if (DRM_BUFCOUNT(&q->waitlist)) {
+ atomic_dec(&q->use_count);
+ return -EBUSY;
+ }
+
+ q->flags = ctx.flags;
+
+ atomic_dec(&q->use_count);
+ return 0;
+}
+
+int DRM(getctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t __user *argp = (void __user *)arg;
+ drm_ctx_t ctx;
+ drm_queue_t *q;
+
+ if (copy_from_user(&ctx, argp, sizeof(ctx)))
+ return -EFAULT;
+
+ DRM_DEBUG("%d\n", ctx.handle);
+
+ if (ctx.handle >= dev->queue_count) return -EINVAL;
+ q = dev->queuelist[ctx.handle];
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) == 1) {
+ /* No longer in use */
+ atomic_dec(&q->use_count);
+ return -EINVAL;
+ }
+
+ ctx.flags = q->flags;
+ atomic_dec(&q->use_count);
+
+ if (copy_to_user(argp, &ctx, sizeof(ctx)))
+ return -EFAULT;
+
+ return 0;
+}
+
+int DRM(switchctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+ return DRM(context_switch)(dev, dev->last_context, ctx.handle);
+}
+
+int DRM(newctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+ DRM(context_switch_complete)(dev, ctx.handle);
+
+ return 0;
+}
+
+int DRM(rmctx)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_t ctx;
+ drm_queue_t *q;
+ drm_buf_t *buf;
+
+ if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+ return -EFAULT;
+ DRM_DEBUG("%d\n", ctx.handle);
+
+ if (ctx.handle >= dev->queue_count) return -EINVAL;
+ q = dev->queuelist[ctx.handle];
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) == 1) {
+ /* No longer in use */
+ atomic_dec(&q->use_count);
+ return -EINVAL;
+ }
+
+ atomic_inc(&q->finalization); /* Mark queue in finalization state */
+ atomic_sub(2, &q->use_count); /* Mark queue as unused (pending
+ finalization) */
+
+ while (test_and_set_bit(0, &dev->interrupt_flag)) {
+ schedule();
+ if (signal_pending(current)) {
+ clear_bit(0, &dev->interrupt_flag);
+ return -EINTR;
+ }
+ }
+ /* Remove queued buffers */
+ while ((buf = DRM(waitlist_get)(&q->waitlist))) {
+ DRM(free_buffer)(dev, buf);
+ }
+ clear_bit(0, &dev->interrupt_flag);
+
+ /* Wakeup blocked processes */
+ wake_up_interruptible(&q->read_queue);
+ wake_up_interruptible(&q->write_queue);
+ wake_up_interruptible(&q->flush_queue);
+
+ /* Finalization over. Queue is made
+ available when both use_count and
+ finalization become 0, which won't
+ happen until all the waiting processes
+ stop waiting. */
+ atomic_dec(&q->finalization);
+ return 0;
+}
+
diff --git a/drivers/char/drm/gamma_dma.c b/drivers/char/drm/gamma_dma.c
new file mode 100644
index 000000000000..e486fb8d31e9
--- /dev/null
+++ b/drivers/char/drm/gamma_dma.c
@@ -0,0 +1,946 @@
+/* gamma_dma.c -- DMA support for GMX 2000 -*- linux-c -*-
+ * Created: Fri Mar 19 14:30:16 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ */
+
+#include "gamma.h"
+#include "drmP.h"
+#include "drm.h"
+#include "gamma_drm.h"
+#include "gamma_drv.h"
+
+#include <linux/interrupt.h> /* For task queue support */
+#include <linux/delay.h>
+
+static inline void gamma_dma_dispatch(drm_device_t *dev, unsigned long address,
+ unsigned long length)
+{
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ mb();
+ while ( GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+ cpu_relax();
+
+ GAMMA_WRITE(GAMMA_DMAADDRESS, address);
+
+ while (GAMMA_READ(GAMMA_GCOMMANDSTATUS) != 4)
+ cpu_relax();
+
+ GAMMA_WRITE(GAMMA_DMACOUNT, length / 4);
+}
+
+void gamma_dma_quiescent_single(drm_device_t *dev)
+{
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ while (GAMMA_READ(GAMMA_DMACOUNT))
+ cpu_relax();
+
+ while (GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+ cpu_relax();
+
+ GAMMA_WRITE(GAMMA_FILTERMODE, 1 << 10);
+ GAMMA_WRITE(GAMMA_SYNC, 0);
+
+ do {
+ while (!GAMMA_READ(GAMMA_OUTFIFOWORDS))
+ cpu_relax();
+ } while (GAMMA_READ(GAMMA_OUTPUTFIFO) != GAMMA_SYNC_TAG);
+}
+
+void gamma_dma_quiescent_dual(drm_device_t *dev)
+{
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ while (GAMMA_READ(GAMMA_DMACOUNT))
+ cpu_relax();
+
+ while (GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+ cpu_relax();
+
+ GAMMA_WRITE(GAMMA_BROADCASTMASK, 3);
+ GAMMA_WRITE(GAMMA_FILTERMODE, 1 << 10);
+ GAMMA_WRITE(GAMMA_SYNC, 0);
+
+ /* Read from first MX */
+ do {
+ while (!GAMMA_READ(GAMMA_OUTFIFOWORDS))
+ cpu_relax();
+ } while (GAMMA_READ(GAMMA_OUTPUTFIFO) != GAMMA_SYNC_TAG);
+
+ /* Read from second MX */
+ do {
+ while (!GAMMA_READ(GAMMA_OUTFIFOWORDS + 0x10000))
+ cpu_relax();
+ } while (GAMMA_READ(GAMMA_OUTPUTFIFO + 0x10000) != GAMMA_SYNC_TAG);
+}
+
+void gamma_dma_ready(drm_device_t *dev)
+{
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ while (GAMMA_READ(GAMMA_DMACOUNT))
+ cpu_relax();
+}
+
+static inline int gamma_dma_is_ready(drm_device_t *dev)
+{
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ return (!GAMMA_READ(GAMMA_DMACOUNT));
+}
+
+irqreturn_t gamma_driver_irq_handler( DRM_IRQ_ARGS )
+{
+ drm_device_t *dev = (drm_device_t *)arg;
+ drm_device_dma_t *dma = dev->dma;
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+
+ /* FIXME: should check whether we're actually interested in the interrupt? */
+ atomic_inc(&dev->counts[6]); /* _DRM_STAT_IRQ */
+
+ while (GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+ cpu_relax();
+
+ GAMMA_WRITE(GAMMA_GDELAYTIMER, 0xc350/2); /* 0x05S */
+ GAMMA_WRITE(GAMMA_GCOMMANDINTFLAGS, 8);
+ GAMMA_WRITE(GAMMA_GINTFLAGS, 0x2001);
+ if (gamma_dma_is_ready(dev)) {
+ /* Free previous buffer */
+ if (test_and_set_bit(0, &dev->dma_flag))
+ return IRQ_HANDLED;
+ if (dma->this_buffer) {
+ gamma_free_buffer(dev, dma->this_buffer);
+ dma->this_buffer = NULL;
+ }
+ clear_bit(0, &dev->dma_flag);
+
+ /* Dispatch new buffer */
+ schedule_work(&dev->work);
+ }
+ return IRQ_HANDLED;
+}
+
+/* Only called by gamma_dma_schedule. */
+static int gamma_do_dma(drm_device_t *dev, int locked)
+{
+ unsigned long address;
+ unsigned long length;
+ drm_buf_t *buf;
+ int retcode = 0;
+ drm_device_dma_t *dma = dev->dma;
+
+ if (test_and_set_bit(0, &dev->dma_flag)) return -EBUSY;
+
+
+ if (!dma->next_buffer) {
+ DRM_ERROR("No next_buffer\n");
+ clear_bit(0, &dev->dma_flag);
+ return -EINVAL;
+ }
+
+ buf = dma->next_buffer;
+ /* WE NOW ARE ON LOGICAL PAGES!! - using page table setup in dma_init */
+ /* So we pass the buffer index value into the physical page offset */
+ address = buf->idx << 12;
+ length = buf->used;
+
+ DRM_DEBUG("context %d, buffer %d (%ld bytes)\n",
+ buf->context, buf->idx, length);
+
+ if (buf->list == DRM_LIST_RECLAIM) {
+ gamma_clear_next_buffer(dev);
+ gamma_free_buffer(dev, buf);
+ clear_bit(0, &dev->dma_flag);
+ return -EINVAL;
+ }
+
+ if (!length) {
+ DRM_ERROR("0 length buffer\n");
+ gamma_clear_next_buffer(dev);
+ gamma_free_buffer(dev, buf);
+ clear_bit(0, &dev->dma_flag);
+ return 0;
+ }
+
+ if (!gamma_dma_is_ready(dev)) {
+ clear_bit(0, &dev->dma_flag);
+ return -EBUSY;
+ }
+
+ if (buf->while_locked) {
+ if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
+ DRM_ERROR("Dispatching buffer %d from pid %d"
+ " \"while locked\", but no lock held\n",
+ buf->idx, current->pid);
+ }
+ } else {
+ if (!locked && !gamma_lock_take(&dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT)) {
+ clear_bit(0, &dev->dma_flag);
+ return -EBUSY;
+ }
+ }
+
+ if (dev->last_context != buf->context
+ && !(dev->queuelist[buf->context]->flags
+ & _DRM_CONTEXT_PRESERVED)) {
+ /* PRE: dev->last_context != buf->context */
+ if (DRM(context_switch)(dev, dev->last_context,
+ buf->context)) {
+ DRM(clear_next_buffer)(dev);
+ DRM(free_buffer)(dev, buf);
+ }
+ retcode = -EBUSY;
+ goto cleanup;
+
+ /* POST: we will wait for the context
+ switch and will dispatch on a later call
+ when dev->last_context == buf->context.
+ NOTE WE HOLD THE LOCK THROUGHOUT THIS
+ TIME! */
+ }
+
+ gamma_clear_next_buffer(dev);
+ buf->pending = 1;
+ buf->waiting = 0;
+ buf->list = DRM_LIST_PEND;
+
+ /* WE NOW ARE ON LOGICAL PAGES!!! - overriding address */
+ address = buf->idx << 12;
+
+ gamma_dma_dispatch(dev, address, length);
+ gamma_free_buffer(dev, dma->this_buffer);
+ dma->this_buffer = buf;
+
+ atomic_inc(&dev->counts[7]); /* _DRM_STAT_DMA */
+ atomic_add(length, &dev->counts[8]); /* _DRM_STAT_PRIMARY */
+
+ if (!buf->while_locked && !dev->context_flag && !locked) {
+ if (gamma_lock_free(dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT)) {
+ DRM_ERROR("\n");
+ }
+ }
+cleanup:
+
+ clear_bit(0, &dev->dma_flag);
+
+
+ return retcode;
+}
+
+static void gamma_dma_timer_bh(unsigned long dev)
+{
+ gamma_dma_schedule((drm_device_t *)dev, 0);
+}
+
+void gamma_irq_immediate_bh(void *dev)
+{
+ gamma_dma_schedule(dev, 0);
+}
+
+int gamma_dma_schedule(drm_device_t *dev, int locked)
+{
+ int next;
+ drm_queue_t *q;
+ drm_buf_t *buf;
+ int retcode = 0;
+ int processed = 0;
+ int missed;
+ int expire = 20;
+ drm_device_dma_t *dma = dev->dma;
+
+ if (test_and_set_bit(0, &dev->interrupt_flag)) {
+ /* Not reentrant */
+ atomic_inc(&dev->counts[10]); /* _DRM_STAT_MISSED */
+ return -EBUSY;
+ }
+ missed = atomic_read(&dev->counts[10]);
+
+
+again:
+ if (dev->context_flag) {
+ clear_bit(0, &dev->interrupt_flag);
+ return -EBUSY;
+ }
+ if (dma->next_buffer) {
+ /* Unsent buffer that was previously
+ selected, but that couldn't be sent
+ because the lock could not be obtained
+ or the DMA engine wasn't ready. Try
+ again. */
+ if (!(retcode = gamma_do_dma(dev, locked))) ++processed;
+ } else {
+ do {
+ next = gamma_select_queue(dev, gamma_dma_timer_bh);
+ if (next >= 0) {
+ q = dev->queuelist[next];
+ buf = gamma_waitlist_get(&q->waitlist);
+ dma->next_buffer = buf;
+ dma->next_queue = q;
+ if (buf && buf->list == DRM_LIST_RECLAIM) {
+ gamma_clear_next_buffer(dev);
+ gamma_free_buffer(dev, buf);
+ }
+ }
+ } while (next >= 0 && !dma->next_buffer);
+ if (dma->next_buffer) {
+ if (!(retcode = gamma_do_dma(dev, locked))) {
+ ++processed;
+ }
+ }
+ }
+
+ if (--expire) {
+ if (missed != atomic_read(&dev->counts[10])) {
+ if (gamma_dma_is_ready(dev)) goto again;
+ }
+ if (processed && gamma_dma_is_ready(dev)) {
+ processed = 0;
+ goto again;
+ }
+ }
+
+ clear_bit(0, &dev->interrupt_flag);
+
+ return retcode;
+}
+
+static int gamma_dma_priority(struct file *filp,
+ drm_device_t *dev, drm_dma_t *d)
+{
+ unsigned long address;
+ unsigned long length;
+ int must_free = 0;
+ int retcode = 0;
+ int i;
+ int idx;
+ drm_buf_t *buf;
+ drm_buf_t *last_buf = NULL;
+ drm_device_dma_t *dma = dev->dma;
+ int *send_indices = NULL;
+ int *send_sizes = NULL;
+
+ DECLARE_WAITQUEUE(entry, current);
+
+ /* Turn off interrupt handling */
+ while (test_and_set_bit(0, &dev->interrupt_flag)) {
+ schedule();
+ if (signal_pending(current)) return -EINTR;
+ }
+ if (!(d->flags & _DRM_DMA_WHILE_LOCKED)) {
+ while (!gamma_lock_take(&dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT)) {
+ schedule();
+ if (signal_pending(current)) {
+ clear_bit(0, &dev->interrupt_flag);
+ return -EINTR;
+ }
+ }
+ ++must_free;
+ }
+
+ send_indices = DRM(alloc)(d->send_count * sizeof(*send_indices),
+ DRM_MEM_DRIVER);
+ if (send_indices == NULL)
+ return -ENOMEM;
+ if (copy_from_user(send_indices, d->send_indices,
+ d->send_count * sizeof(*send_indices))) {
+ retcode = -EFAULT;
+ goto cleanup;
+ }
+
+ send_sizes = DRM(alloc)(d->send_count * sizeof(*send_sizes),
+ DRM_MEM_DRIVER);
+ if (send_sizes == NULL)
+ return -ENOMEM;
+ if (copy_from_user(send_sizes, d->send_sizes,
+ d->send_count * sizeof(*send_sizes))) {
+ retcode = -EFAULT;
+ goto cleanup;
+ }
+
+ for (i = 0; i < d->send_count; i++) {
+ idx = send_indices[i];
+ if (idx < 0 || idx >= dma->buf_count) {
+ DRM_ERROR("Index %d (of %d max)\n",
+ send_indices[i], dma->buf_count - 1);
+ continue;
+ }
+ buf = dma->buflist[ idx ];
+ if (buf->filp != filp) {
+ DRM_ERROR("Process %d using buffer not owned\n",
+ current->pid);
+ retcode = -EINVAL;
+ goto cleanup;
+ }
+ if (buf->list != DRM_LIST_NONE) {
+ DRM_ERROR("Process %d using buffer on list %d\n",
+ current->pid, buf->list);
+ retcode = -EINVAL;
+ goto cleanup;
+ }
+ /* This isn't a race condition on
+ buf->list, since our concern is the
+ buffer reclaim during the time the
+ process closes the /dev/drm? handle, so
+ it can't also be doing DMA. */
+ buf->list = DRM_LIST_PRIO;
+ buf->used = send_sizes[i];
+ buf->context = d->context;
+ buf->while_locked = d->flags & _DRM_DMA_WHILE_LOCKED;
+ address = (unsigned long)buf->address;
+ length = buf->used;
+ if (!length) {
+ DRM_ERROR("0 length buffer\n");
+ }
+ if (buf->pending) {
+ DRM_ERROR("Sending pending buffer:"
+ " buffer %d, offset %d\n",
+ send_indices[i], i);
+ retcode = -EINVAL;
+ goto cleanup;
+ }
+ if (buf->waiting) {
+ DRM_ERROR("Sending waiting buffer:"
+ " buffer %d, offset %d\n",
+ send_indices[i], i);
+ retcode = -EINVAL;
+ goto cleanup;
+ }
+ buf->pending = 1;
+
+ if (dev->last_context != buf->context
+ && !(dev->queuelist[buf->context]->flags
+ & _DRM_CONTEXT_PRESERVED)) {
+ add_wait_queue(&dev->context_wait, &entry);
+ current->state = TASK_INTERRUPTIBLE;
+ /* PRE: dev->last_context != buf->context */
+ DRM(context_switch)(dev, dev->last_context,
+ buf->context);
+ /* POST: we will wait for the context
+ switch and will dispatch on a later call
+ when dev->last_context == buf->context.
+ NOTE WE HOLD THE LOCK THROUGHOUT THIS
+ TIME! */
+ schedule();
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&dev->context_wait, &entry);
+ if (signal_pending(current)) {
+ retcode = -EINTR;
+ goto cleanup;
+ }
+ if (dev->last_context != buf->context) {
+ DRM_ERROR("Context mismatch: %d %d\n",
+ dev->last_context,
+ buf->context);
+ }
+ }
+
+ gamma_dma_dispatch(dev, address, length);
+ atomic_inc(&dev->counts[9]); /* _DRM_STAT_SPECIAL */
+ atomic_add(length, &dev->counts[8]); /* _DRM_STAT_PRIMARY */
+
+ if (last_buf) {
+ gamma_free_buffer(dev, last_buf);
+ }
+ last_buf = buf;
+ }
+
+
+cleanup:
+ if (last_buf) {
+ gamma_dma_ready(dev);
+ gamma_free_buffer(dev, last_buf);
+ }
+ if (send_indices)
+ DRM(free)(send_indices, d->send_count * sizeof(*send_indices),
+ DRM_MEM_DRIVER);
+ if (send_sizes)
+ DRM(free)(send_sizes, d->send_count * sizeof(*send_sizes),
+ DRM_MEM_DRIVER);
+
+ if (must_free && !dev->context_flag) {
+ if (gamma_lock_free(dev, &dev->lock.hw_lock->lock,
+ DRM_KERNEL_CONTEXT)) {
+ DRM_ERROR("\n");
+ }
+ }
+ clear_bit(0, &dev->interrupt_flag);
+ return retcode;
+}
+
+static int gamma_dma_send_buffers(struct file *filp,
+ drm_device_t *dev, drm_dma_t *d)
+{
+ DECLARE_WAITQUEUE(entry, current);
+ drm_buf_t *last_buf = NULL;
+ int retcode = 0;
+ drm_device_dma_t *dma = dev->dma;
+ int send_index;
+
+ if (get_user(send_index, &d->send_indices[d->send_count-1]))
+ return -EFAULT;
+
+ if (d->flags & _DRM_DMA_BLOCK) {
+ last_buf = dma->buflist[send_index];
+ add_wait_queue(&last_buf->dma_wait, &entry);
+ }
+
+ if ((retcode = gamma_dma_enqueue(filp, d))) {
+ if (d->flags & _DRM_DMA_BLOCK)
+ remove_wait_queue(&last_buf->dma_wait, &entry);
+ return retcode;
+ }
+
+ gamma_dma_schedule(dev, 0);
+
+ if (d->flags & _DRM_DMA_BLOCK) {
+ DRM_DEBUG("%d waiting\n", current->pid);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ if (!last_buf->waiting && !last_buf->pending)
+ break; /* finished */
+ schedule();
+ if (signal_pending(current)) {
+ retcode = -EINTR; /* Can't restart */
+ break;
+ }
+ }
+ current->state = TASK_RUNNING;
+ DRM_DEBUG("%d running\n", current->pid);
+ remove_wait_queue(&last_buf->dma_wait, &entry);
+ if (!retcode
+ || (last_buf->list==DRM_LIST_PEND && !last_buf->pending)) {
+ if (!waitqueue_active(&last_buf->dma_wait)) {
+ gamma_free_buffer(dev, last_buf);
+ }
+ }
+ if (retcode) {
+ DRM_ERROR("ctx%d w%d p%d c%ld i%d l%d pid:%d\n",
+ d->context,
+ last_buf->waiting,
+ last_buf->pending,
+ (long)DRM_WAITCOUNT(dev, d->context),
+ last_buf->idx,
+ last_buf->list,
+ current->pid);
+ }
+ }
+ return retcode;
+}
+
+int gamma_dma(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_device_dma_t *dma = dev->dma;
+ int retcode = 0;
+ drm_dma_t __user *argp = (void __user *)arg;
+ drm_dma_t d;
+
+ if (copy_from_user(&d, argp, sizeof(d)))
+ return -EFAULT;
+
+ if (d.send_count < 0 || d.send_count > dma->buf_count) {
+ DRM_ERROR("Process %d trying to send %d buffers (of %d max)\n",
+ current->pid, d.send_count, dma->buf_count);
+ return -EINVAL;
+ }
+
+ if (d.request_count < 0 || d.request_count > dma->buf_count) {
+ DRM_ERROR("Process %d trying to get %d buffers (of %d max)\n",
+ current->pid, d.request_count, dma->buf_count);
+ return -EINVAL;
+ }
+
+ if (d.send_count) {
+ if (d.flags & _DRM_DMA_PRIORITY)
+ retcode = gamma_dma_priority(filp, dev, &d);
+ else
+ retcode = gamma_dma_send_buffers(filp, dev, &d);
+ }
+
+ d.granted_count = 0;
+
+ if (!retcode && d.request_count) {
+ retcode = gamma_dma_get_buffers(filp, &d);
+ }
+
+ DRM_DEBUG("%d returning, granted = %d\n",
+ current->pid, d.granted_count);
+ if (copy_to_user(argp, &d, sizeof(d)))
+ return -EFAULT;
+
+ return retcode;
+}
+
+/* =============================================================
+ * DMA initialization, cleanup
+ */
+
+static int gamma_do_init_dma( drm_device_t *dev, drm_gamma_init_t *init )
+{
+ drm_gamma_private_t *dev_priv;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ int i;
+ struct list_head *list;
+ unsigned long *pgt;
+
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ dev_priv = DRM(alloc)( sizeof(drm_gamma_private_t),
+ DRM_MEM_DRIVER );
+ if ( !dev_priv )
+ return -ENOMEM;
+
+ dev->dev_private = (void *)dev_priv;
+
+ memset( dev_priv, 0, sizeof(drm_gamma_private_t) );
+
+ dev_priv->num_rast = init->num_rast;
+
+ list_for_each(list, &dev->maplist->head) {
+ drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+ if( r_list->map &&
+ r_list->map->type == _DRM_SHM &&
+ r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+ dev_priv->sarea = r_list->map;
+ break;
+ }
+ }
+
+ dev_priv->mmio0 = drm_core_findmap(dev, init->mmio0);
+ dev_priv->mmio1 = drm_core_findmap(dev, init->mmio1);
+ dev_priv->mmio2 = drm_core_findmap(dev, init->mmio2);
+ dev_priv->mmio3 = drm_core_findmap(dev, init->mmio3);
+
+ dev_priv->sarea_priv = (drm_gamma_sarea_t *)
+ ((u8 *)dev_priv->sarea->handle +
+ init->sarea_priv_offset);
+
+ if (init->pcimode) {
+ buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+ pgt = buf->address;
+
+ for (i = 0; i < GLINT_DRI_BUF_COUNT; i++) {
+ buf = dma->buflist[i];
+ *pgt = virt_to_phys((void*)buf->address) | 0x07;
+ pgt++;
+ }
+
+ buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+ } else {
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ drm_core_ioremap( dev->agp_buffer_map, dev);
+
+ buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+ pgt = buf->address;
+
+ for (i = 0; i < GLINT_DRI_BUF_COUNT; i++) {
+ buf = dma->buflist[i];
+ *pgt = (unsigned long)buf->address + 0x07;
+ pgt++;
+ }
+
+ buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+
+ while (GAMMA_READ(GAMMA_INFIFOSPACE) < 1);
+ GAMMA_WRITE( GAMMA_GDMACONTROL, 0xe);
+ }
+ while (GAMMA_READ(GAMMA_INFIFOSPACE) < 2);
+ GAMMA_WRITE( GAMMA_PAGETABLEADDR, virt_to_phys((void*)buf->address) );
+ GAMMA_WRITE( GAMMA_PAGETABLELENGTH, 2 );
+
+ return 0;
+}
+
+int gamma_do_cleanup_dma( drm_device_t *dev )
+{
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if (drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+ if ( dev->irq_enabled )
+ DRM(irq_uninstall)(dev);
+
+ if ( dev->dev_private ) {
+
+ if ( dev->agp_buffer_map != NULL )
+ drm_core_ioremapfree( dev->agp_buffer_map, dev );
+
+ DRM(free)( dev->dev_private, sizeof(drm_gamma_private_t),
+ DRM_MEM_DRIVER );
+ dev->dev_private = NULL;
+ }
+
+ return 0;
+}
+
+int gamma_dma_init( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_gamma_init_t init;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( copy_from_user( &init, (drm_gamma_init_t __user *)arg, sizeof(init) ) )
+ return -EFAULT;
+
+ switch ( init.func ) {
+ case GAMMA_INIT_DMA:
+ return gamma_do_init_dma( dev, &init );
+ case GAMMA_CLEANUP_DMA:
+ return gamma_do_cleanup_dma( dev );
+ }
+
+ return -EINVAL;
+}
+
+static int gamma_do_copy_dma( drm_device_t *dev, drm_gamma_copy_t *copy )
+{
+ drm_device_dma_t *dma = dev->dma;
+ unsigned int *screenbuf;
+
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ /* We've DRM_RESTRICTED this DMA buffer */
+
+ screenbuf = dma->buflist[ GLINT_DRI_BUF_COUNT + 1 ]->address;
+
+#if 0
+ *buffer++ = 0x180; /* Tag (FilterMode) */
+ *buffer++ = 0x200; /* Allow FBColor through */
+ *buffer++ = 0x53B; /* Tag */
+ *buffer++ = copy->Pitch;
+ *buffer++ = 0x53A; /* Tag */
+ *buffer++ = copy->SrcAddress;
+ *buffer++ = 0x539; /* Tag */
+ *buffer++ = copy->WidthHeight; /* Initiates transfer */
+ *buffer++ = 0x53C; /* Tag - DMAOutputAddress */
+ *buffer++ = virt_to_phys((void*)screenbuf);
+ *buffer++ = 0x53D; /* Tag - DMAOutputCount */
+ *buffer++ = copy->Count; /* Reads HostOutFifo BLOCKS until ..*/
+
+ /* Data now sitting in dma->buflist[ GLINT_DRI_BUF_COUNT + 1 ] */
+ /* Now put it back to the screen */
+
+ *buffer++ = 0x180; /* Tag (FilterMode) */
+ *buffer++ = 0x400; /* Allow Sync through */
+ *buffer++ = 0x538; /* Tag - DMARectangleReadTarget */
+ *buffer++ = 0x155; /* FBSourceData | count */
+ *buffer++ = 0x537; /* Tag */
+ *buffer++ = copy->Pitch;
+ *buffer++ = 0x536; /* Tag */
+ *buffer++ = copy->DstAddress;
+ *buffer++ = 0x535; /* Tag */
+ *buffer++ = copy->WidthHeight; /* Initiates transfer */
+ *buffer++ = 0x530; /* Tag - DMAAddr */
+ *buffer++ = virt_to_phys((void*)screenbuf);
+ *buffer++ = 0x531;
+ *buffer++ = copy->Count; /* initiates DMA transfer of color data */
+#endif
+
+ /* need to dispatch it now */
+
+ return 0;
+}
+
+int gamma_dma_copy( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_gamma_copy_t copy;
+
+ if ( copy_from_user( &copy, (drm_gamma_copy_t __user *)arg, sizeof(copy) ) )
+ return -EFAULT;
+
+ return gamma_do_copy_dma( dev, &copy );
+}
+
+/* =============================================================
+ * Per Context SAREA Support
+ */
+
+int gamma_getsareactx(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_priv_map_t __user *argp = (void __user *)arg;
+ drm_ctx_priv_map_t request;
+ drm_map_t *map;
+
+ if (copy_from_user(&request, argp, sizeof(request)))
+ return -EFAULT;
+
+ down(&dev->struct_sem);
+ if ((int)request.ctx_id >= dev->max_context) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+
+ map = dev->context_sareas[request.ctx_id];
+ up(&dev->struct_sem);
+
+ request.handle = map->handle;
+ if (copy_to_user(argp, &request, sizeof(request)))
+ return -EFAULT;
+ return 0;
+}
+
+int gamma_setsareactx(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ drm_ctx_priv_map_t request;
+ drm_map_t *map = NULL;
+ drm_map_list_t *r_list;
+ struct list_head *list;
+
+ if (copy_from_user(&request,
+ (drm_ctx_priv_map_t __user *)arg,
+ sizeof(request)))
+ return -EFAULT;
+
+ down(&dev->struct_sem);
+ r_list = NULL;
+ list_for_each(list, &dev->maplist->head) {
+ r_list = list_entry(list, drm_map_list_t, head);
+ if(r_list->map &&
+ r_list->map->handle == request.handle) break;
+ }
+ if (list == &(dev->maplist->head)) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+ map = r_list->map;
+ up(&dev->struct_sem);
+
+ if (!map) return -EINVAL;
+
+ down(&dev->struct_sem);
+ if ((int)request.ctx_id >= dev->max_context) {
+ up(&dev->struct_sem);
+ return -EINVAL;
+ }
+ dev->context_sareas[request.ctx_id] = map;
+ up(&dev->struct_sem);
+ return 0;
+}
+
+void gamma_driver_irq_preinstall( drm_device_t *dev ) {
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+
+ while(GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+ cpu_relax();
+
+ GAMMA_WRITE( GAMMA_GCOMMANDMODE, 0x00000004 );
+ GAMMA_WRITE( GAMMA_GDMACONTROL, 0x00000000 );
+}
+
+void gamma_driver_irq_postinstall( drm_device_t *dev ) {
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+
+ while(GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+ cpu_relax();
+
+ GAMMA_WRITE( GAMMA_GINTENABLE, 0x00002001 );
+ GAMMA_WRITE( GAMMA_COMMANDINTENABLE, 0x00000008 );
+ GAMMA_WRITE( GAMMA_GDELAYTIMER, 0x00039090 );
+}
+
+void gamma_driver_irq_uninstall( drm_device_t *dev ) {
+ drm_gamma_private_t *dev_priv =
+ (drm_gamma_private_t *)dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ while(GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+ cpu_relax();
+
+ GAMMA_WRITE( GAMMA_GDELAYTIMER, 0x00000000 );
+ GAMMA_WRITE( GAMMA_COMMANDINTENABLE, 0x00000000 );
+ GAMMA_WRITE( GAMMA_GINTENABLE, 0x00000000 );
+}
+
+extern drm_ioctl_desc_t DRM(ioctls)[];
+
+static int gamma_driver_preinit(drm_device_t *dev)
+{
+ /* reset the finish ioctl */
+ DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_FINISH)].func = DRM(finish);
+ return 0;
+}
+
+static void gamma_driver_pretakedown(drm_device_t *dev)
+{
+ gamma_do_cleanup_dma(dev);
+}
+
+static void gamma_driver_dma_ready(drm_device_t *dev)
+{
+ gamma_dma_ready(dev);
+}
+
+static int gamma_driver_dma_quiescent(drm_device_t *dev)
+{
+ drm_gamma_private_t *dev_priv = (
+ drm_gamma_private_t *)dev->dev_private;
+ if (dev_priv->num_rast == 2)
+ gamma_dma_quiescent_dual(dev);
+ else gamma_dma_quiescent_single(dev);
+ return 0;
+}
+
+void gamma_driver_register_fns(drm_device_t *dev)
+{
+ dev->driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ;
+ DRM(fops).read = gamma_fops_read;
+ DRM(fops).poll = gamma_fops_poll;
+ dev->driver.preinit = gamma_driver_preinit;
+ dev->driver.pretakedown = gamma_driver_pretakedown;
+ dev->driver.dma_ready = gamma_driver_dma_ready;
+ dev->driver.dma_quiescent = gamma_driver_dma_quiescent;
+ dev->driver.dma_flush_block_and_flush = gamma_flush_block_and_flush;
+ dev->driver.dma_flush_unblock = gamma_flush_unblock;
+}
diff --git a/drivers/char/drm/gamma_drm.h b/drivers/char/drm/gamma_drm.h
new file mode 100644
index 000000000000..20819ded0e15
--- /dev/null
+++ b/drivers/char/drm/gamma_drm.h
@@ -0,0 +1,90 @@
+#ifndef _GAMMA_DRM_H_
+#define _GAMMA_DRM_H_
+
+typedef struct _drm_gamma_tex_region {
+ unsigned char next, prev; /* indices to form a circular LRU */
+ unsigned char in_use; /* owned by a client, or free? */
+ int age; /* tracked by clients to update local LRU's */
+} drm_gamma_tex_region_t;
+
+typedef struct {
+ unsigned int GDeltaMode;
+ unsigned int GDepthMode;
+ unsigned int GGeometryMode;
+ unsigned int GTransformMode;
+} drm_gamma_context_regs_t;
+
+typedef struct _drm_gamma_sarea {
+ drm_gamma_context_regs_t context_state;
+
+ unsigned int dirty;
+
+
+ /* Maintain an LRU of contiguous regions of texture space. If
+ * you think you own a region of texture memory, and it has an
+ * age different to the one you set, then you are mistaken and
+ * it has been stolen by another client. If global texAge
+ * hasn't changed, there is no need to walk the list.
+ *
+ * These regions can be used as a proxy for the fine-grained
+ * texture information of other clients - by maintaining them
+ * in the same lru which is used to age their own textures,
+ * clients have an approximate lru for the whole of global
+ * texture space, and can make informed decisions as to which
+ * areas to kick out. There is no need to choose whether to
+ * kick out your own texture or someone else's - simply eject
+ * them all in LRU order.
+ */
+
+#define GAMMA_NR_TEX_REGIONS 64
+ drm_gamma_tex_region_t texList[GAMMA_NR_TEX_REGIONS+1];
+ /* Last elt is sentinal */
+ int texAge; /* last time texture was uploaded */
+ int last_enqueue; /* last time a buffer was enqueued */
+ int last_dispatch; /* age of the most recently dispatched buffer */
+ int last_quiescent; /* */
+ int ctxOwner; /* last context to upload state */
+
+ int vertex_prim;
+} drm_gamma_sarea_t;
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmGamma.h)
+ */
+
+/* Gamma specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_IOCTL_GAMMA_INIT DRM_IOW( 0x40, drm_gamma_init_t)
+#define DRM_IOCTL_GAMMA_COPY DRM_IOW( 0x41, drm_gamma_copy_t)
+
+typedef struct drm_gamma_copy {
+ unsigned int DMAOutputAddress;
+ unsigned int DMAOutputCount;
+ unsigned int DMAReadGLINTSource;
+ unsigned int DMARectangleWriteAddress;
+ unsigned int DMARectangleWriteLinePitch;
+ unsigned int DMARectangleWrite;
+ unsigned int DMARectangleReadAddress;
+ unsigned int DMARectangleReadLinePitch;
+ unsigned int DMARectangleRead;
+ unsigned int DMARectangleReadTarget;
+} drm_gamma_copy_t;
+
+typedef struct drm_gamma_init {
+ enum {
+ GAMMA_INIT_DMA = 0x01,
+ GAMMA_CLEANUP_DMA = 0x02
+ } func;
+
+ int sarea_priv_offset;
+ int pcimode;
+ unsigned int mmio0;
+ unsigned int mmio1;
+ unsigned int mmio2;
+ unsigned int mmio3;
+ unsigned int buffers_offset;
+ int num_rast;
+} drm_gamma_init_t;
+
+#endif /* _GAMMA_DRM_H_ */
diff --git a/drivers/char/drm/gamma_drv.c b/drivers/char/drm/gamma_drv.c
new file mode 100644
index 000000000000..e7e64b62792a
--- /dev/null
+++ b/drivers/char/drm/gamma_drv.c
@@ -0,0 +1,59 @@
+/* gamma.c -- 3dlabs GMX 2000 driver -*- linux-c -*-
+ * Created: Mon Jan 4 08:58:31 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "gamma.h"
+#include "drmP.h"
+#include "drm.h"
+#include "gamma_drm.h"
+#include "gamma_drv.h"
+
+#include "drm_auth.h"
+#include "drm_agpsupport.h"
+#include "drm_bufs.h"
+#include "gamma_context.h" /* NOTE! */
+#include "drm_dma.h"
+#include "gamma_old_dma.h" /* NOTE */
+#include "drm_drawable.h"
+#include "drm_drv.h"
+
+#include "drm_fops.h"
+#include "drm_init.h"
+#include "drm_ioctl.h"
+#include "drm_irq.h"
+#include "gamma_lists.h" /* NOTE */
+#include "drm_lock.h"
+#include "gamma_lock.h" /* NOTE */
+#include "drm_memory.h"
+#include "drm_proc.h"
+#include "drm_vm.h"
+#include "drm_stub.h"
+#include "drm_scatter.h"
diff --git a/drivers/char/drm/gamma_drv.h b/drivers/char/drm/gamma_drv.h
new file mode 100644
index 000000000000..146fcc6253cd
--- /dev/null
+++ b/drivers/char/drm/gamma_drv.h
@@ -0,0 +1,147 @@
+/* gamma_drv.h -- Private header for 3dlabs GMX 2000 driver -*- linux-c -*-
+ * Created: Mon Jan 4 10:05:05 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ */
+
+#ifndef _GAMMA_DRV_H_
+#define _GAMMA_DRV_H_
+
+typedef struct drm_gamma_private {
+ drm_gamma_sarea_t *sarea_priv;
+ drm_map_t *sarea;
+ drm_map_t *mmio0;
+ drm_map_t *mmio1;
+ drm_map_t *mmio2;
+ drm_map_t *mmio3;
+ int num_rast;
+} drm_gamma_private_t;
+
+ /* gamma_dma.c */
+extern int gamma_dma_init( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int gamma_dma_copy( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+extern int gamma_do_cleanup_dma( drm_device_t *dev );
+extern void gamma_dma_ready(drm_device_t *dev);
+extern void gamma_dma_quiescent_single(drm_device_t *dev);
+extern void gamma_dma_quiescent_dual(drm_device_t *dev);
+
+ /* gamma_dma.c */
+extern int gamma_dma_schedule(drm_device_t *dev, int locked);
+extern int gamma_dma(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int gamma_find_devices(void);
+extern int gamma_found(void);
+
+/* Gamma-specific code pulled from drm_fops.h:
+ */
+extern int DRM(finish)(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+extern int DRM(flush_unblock)(drm_device_t *dev, int context,
+ drm_lock_flags_t flags);
+extern int DRM(flush_block_and_flush)(drm_device_t *dev, int context,
+ drm_lock_flags_t flags);
+
+/* Gamma-specific code pulled from drm_dma.h:
+ */
+extern void DRM(clear_next_buffer)(drm_device_t *dev);
+extern int DRM(select_queue)(drm_device_t *dev,
+ void (*wrapper)(unsigned long));
+extern int DRM(dma_enqueue)(struct file *filp, drm_dma_t *dma);
+extern int DRM(dma_get_buffers)(struct file *filp, drm_dma_t *dma);
+
+
+/* Gamma-specific code pulled from drm_lists.h (now renamed gamma_lists.h):
+ */
+extern int DRM(waitlist_create)(drm_waitlist_t *bl, int count);
+extern int DRM(waitlist_destroy)(drm_waitlist_t *bl);
+extern int DRM(waitlist_put)(drm_waitlist_t *bl, drm_buf_t *buf);
+extern drm_buf_t *DRM(waitlist_get)(drm_waitlist_t *bl);
+extern int DRM(freelist_create)(drm_freelist_t *bl, int count);
+extern int DRM(freelist_destroy)(drm_freelist_t *bl);
+extern int DRM(freelist_put)(drm_device_t *dev, drm_freelist_t *bl,
+ drm_buf_t *buf);
+extern drm_buf_t *DRM(freelist_get)(drm_freelist_t *bl, int block);
+
+/* externs for gamma changes to the ops */
+extern struct file_operations DRM(fops);
+extern unsigned int gamma_fops_poll(struct file *filp, struct poll_table_struct *wait);
+extern ssize_t gamma_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *off);
+
+
+#define GLINT_DRI_BUF_COUNT 256
+
+#define GAMMA_OFF(reg) \
+ ((reg < 0x1000) \
+ ? reg \
+ : ((reg < 0x10000) \
+ ? (reg - 0x1000) \
+ : ((reg < 0x11000) \
+ ? (reg - 0x10000) \
+ : (reg - 0x11000))))
+
+#define GAMMA_BASE(reg) ((unsigned long) \
+ ((reg < 0x1000) ? dev_priv->mmio0->handle : \
+ ((reg < 0x10000) ? dev_priv->mmio1->handle : \
+ ((reg < 0x11000) ? dev_priv->mmio2->handle : \
+ dev_priv->mmio3->handle))))
+#define GAMMA_ADDR(reg) (GAMMA_BASE(reg) + GAMMA_OFF(reg))
+#define GAMMA_DEREF(reg) *(__volatile__ int *)GAMMA_ADDR(reg)
+#define GAMMA_READ(reg) GAMMA_DEREF(reg)
+#define GAMMA_WRITE(reg,val) do { GAMMA_DEREF(reg) = val; } while (0)
+
+#define GAMMA_BROADCASTMASK 0x9378
+#define GAMMA_COMMANDINTENABLE 0x0c48
+#define GAMMA_DMAADDRESS 0x0028
+#define GAMMA_DMACOUNT 0x0030
+#define GAMMA_FILTERMODE 0x8c00
+#define GAMMA_GCOMMANDINTFLAGS 0x0c50
+#define GAMMA_GCOMMANDMODE 0x0c40
+#define GAMMA_QUEUED_DMA_MODE 1<<1
+#define GAMMA_GCOMMANDSTATUS 0x0c60
+#define GAMMA_GDELAYTIMER 0x0c38
+#define GAMMA_GDMACONTROL 0x0060
+#define GAMMA_USE_AGP 1<<1
+#define GAMMA_GINTENABLE 0x0808
+#define GAMMA_GINTFLAGS 0x0810
+#define GAMMA_INFIFOSPACE 0x0018
+#define GAMMA_OUTFIFOWORDS 0x0020
+#define GAMMA_OUTPUTFIFO 0x2000
+#define GAMMA_SYNC 0x8c40
+#define GAMMA_SYNC_TAG 0x0188
+#define GAMMA_PAGETABLEADDR 0x0C00
+#define GAMMA_PAGETABLELENGTH 0x0C08
+
+#define GAMMA_PASSTHROUGH 0x1FE
+#define GAMMA_DMAADDRTAG 0x530
+#define GAMMA_DMACOUNTTAG 0x531
+#define GAMMA_COMMANDINTTAG 0x532
+
+#endif
diff --git a/drivers/char/drm/gamma_lists.h b/drivers/char/drm/gamma_lists.h
new file mode 100644
index 000000000000..2d93f412b96b
--- /dev/null
+++ b/drivers/char/drm/gamma_lists.h
@@ -0,0 +1,215 @@
+/* drm_lists.h -- Buffer list handling routines -*- linux-c -*-
+ * Created: Mon Apr 19 20:54:22 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+
+
+int DRM(waitlist_create)(drm_waitlist_t *bl, int count)
+{
+ if (bl->count) return -EINVAL;
+
+ bl->bufs = DRM(alloc)((bl->count + 2) * sizeof(*bl->bufs),
+ DRM_MEM_BUFLISTS);
+
+ if(!bl->bufs) return -ENOMEM;
+ memset(bl->bufs, 0, sizeof(*bl->bufs));
+ bl->count = count;
+ bl->rp = bl->bufs;
+ bl->wp = bl->bufs;
+ bl->end = &bl->bufs[bl->count+1];
+ spin_lock_init(&bl->write_lock);
+ spin_lock_init(&bl->read_lock);
+ return 0;
+}
+
+int DRM(waitlist_destroy)(drm_waitlist_t *bl)
+{
+ if (bl->rp != bl->wp) return -EINVAL;
+ if (bl->bufs) DRM(free)(bl->bufs,
+ (bl->count + 2) * sizeof(*bl->bufs),
+ DRM_MEM_BUFLISTS);
+ bl->count = 0;
+ bl->bufs = NULL;
+ bl->rp = NULL;
+ bl->wp = NULL;
+ bl->end = NULL;
+ return 0;
+}
+
+int DRM(waitlist_put)(drm_waitlist_t *bl, drm_buf_t *buf)
+{
+ int left;
+ unsigned long flags;
+
+ left = DRM_LEFTCOUNT(bl);
+ if (!left) {
+ DRM_ERROR("Overflow while adding buffer %d from filp %p\n",
+ buf->idx, buf->filp);
+ return -EINVAL;
+ }
+ buf->list = DRM_LIST_WAIT;
+
+ spin_lock_irqsave(&bl->write_lock, flags);
+ *bl->wp = buf;
+ if (++bl->wp >= bl->end) bl->wp = bl->bufs;
+ spin_unlock_irqrestore(&bl->write_lock, flags);
+
+ return 0;
+}
+
+drm_buf_t *DRM(waitlist_get)(drm_waitlist_t *bl)
+{
+ drm_buf_t *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bl->read_lock, flags);
+ buf = *bl->rp;
+ if (bl->rp == bl->wp) {
+ spin_unlock_irqrestore(&bl->read_lock, flags);
+ return NULL;
+ }
+ if (++bl->rp >= bl->end) bl->rp = bl->bufs;
+ spin_unlock_irqrestore(&bl->read_lock, flags);
+
+ return buf;
+}
+
+int DRM(freelist_create)(drm_freelist_t *bl, int count)
+{
+ atomic_set(&bl->count, 0);
+ bl->next = NULL;
+ init_waitqueue_head(&bl->waiting);
+ bl->low_mark = 0;
+ bl->high_mark = 0;
+ atomic_set(&bl->wfh, 0);
+ spin_lock_init(&bl->lock);
+ ++bl->initialized;
+ return 0;
+}
+
+int DRM(freelist_destroy)(drm_freelist_t *bl)
+{
+ atomic_set(&bl->count, 0);
+ bl->next = NULL;
+ return 0;
+}
+
+int DRM(freelist_put)(drm_device_t *dev, drm_freelist_t *bl, drm_buf_t *buf)
+{
+ drm_device_dma_t *dma = dev->dma;
+
+ if (!dma) {
+ DRM_ERROR("No DMA support\n");
+ return 1;
+ }
+
+ if (buf->waiting || buf->pending || buf->list == DRM_LIST_FREE) {
+ DRM_ERROR("Freed buffer %d: w%d, p%d, l%d\n",
+ buf->idx, buf->waiting, buf->pending, buf->list);
+ }
+ if (!bl) return 1;
+ buf->list = DRM_LIST_FREE;
+
+ spin_lock(&bl->lock);
+ buf->next = bl->next;
+ bl->next = buf;
+ spin_unlock(&bl->lock);
+
+ atomic_inc(&bl->count);
+ if (atomic_read(&bl->count) > dma->buf_count) {
+ DRM_ERROR("%d of %d buffers free after addition of %d\n",
+ atomic_read(&bl->count), dma->buf_count, buf->idx);
+ return 1;
+ }
+ /* Check for high water mark */
+ if (atomic_read(&bl->wfh) && atomic_read(&bl->count)>=bl->high_mark) {
+ atomic_set(&bl->wfh, 0);
+ wake_up_interruptible(&bl->waiting);
+ }
+ return 0;
+}
+
+static drm_buf_t *DRM(freelist_try)(drm_freelist_t *bl)
+{
+ drm_buf_t *buf;
+
+ if (!bl) return NULL;
+
+ /* Get buffer */
+ spin_lock(&bl->lock);
+ if (!bl->next) {
+ spin_unlock(&bl->lock);
+ return NULL;
+ }
+ buf = bl->next;
+ bl->next = bl->next->next;
+ spin_unlock(&bl->lock);
+
+ atomic_dec(&bl->count);
+ buf->next = NULL;
+ buf->list = DRM_LIST_NONE;
+ if (buf->waiting || buf->pending) {
+ DRM_ERROR("Free buffer %d: w%d, p%d, l%d\n",
+ buf->idx, buf->waiting, buf->pending, buf->list);
+ }
+
+ return buf;
+}
+
+drm_buf_t *DRM(freelist_get)(drm_freelist_t *bl, int block)
+{
+ drm_buf_t *buf = NULL;
+ DECLARE_WAITQUEUE(entry, current);
+
+ if (!bl || !bl->initialized) return NULL;
+
+ /* Check for low water mark */
+ if (atomic_read(&bl->count) <= bl->low_mark) /* Became low */
+ atomic_set(&bl->wfh, 1);
+ if (atomic_read(&bl->wfh)) {
+ if (block) {
+ add_wait_queue(&bl->waiting, &entry);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ if (!atomic_read(&bl->wfh)
+ && (buf = DRM(freelist_try)(bl))) break;
+ schedule();
+ if (signal_pending(current)) break;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&bl->waiting, &entry);
+ }
+ return buf;
+ }
+
+ return DRM(freelist_try)(bl);
+}
+
diff --git a/drivers/char/drm/gamma_lock.h b/drivers/char/drm/gamma_lock.h
new file mode 100644
index 000000000000..ddec67e4ed16
--- /dev/null
+++ b/drivers/char/drm/gamma_lock.h
@@ -0,0 +1,140 @@
+/* lock.c -- IOCTLs for locking -*- linux-c -*-
+ * Created: Tue Feb 2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+
+/* Gamma-specific code extracted from drm_lock.h:
+ */
+static int DRM(flush_queue)(drm_device_t *dev, int context)
+{
+ DECLARE_WAITQUEUE(entry, current);
+ int ret = 0;
+ drm_queue_t *q = dev->queuelist[context];
+
+ DRM_DEBUG("\n");
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) > 1) {
+ atomic_inc(&q->block_write);
+ add_wait_queue(&q->flush_queue, &entry);
+ atomic_inc(&q->block_count);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ if (!DRM_BUFCOUNT(&q->waitlist)) break;
+ schedule();
+ if (signal_pending(current)) {
+ ret = -EINTR; /* Can't restart */
+ break;
+ }
+ }
+ atomic_dec(&q->block_count);
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&q->flush_queue, &entry);
+ }
+ atomic_dec(&q->use_count);
+
+ /* NOTE: block_write is still incremented!
+ Use drm_flush_unlock_queue to decrement. */
+ return ret;
+}
+
+static int DRM(flush_unblock_queue)(drm_device_t *dev, int context)
+{
+ drm_queue_t *q = dev->queuelist[context];
+
+ DRM_DEBUG("\n");
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->use_count) > 1) {
+ if (atomic_read(&q->block_write)) {
+ atomic_dec(&q->block_write);
+ wake_up_interruptible(&q->write_queue);
+ }
+ }
+ atomic_dec(&q->use_count);
+ return 0;
+}
+
+int DRM(flush_block_and_flush)(drm_device_t *dev, int context,
+ drm_lock_flags_t flags)
+{
+ int ret = 0;
+ int i;
+
+ DRM_DEBUG("\n");
+
+ if (flags & _DRM_LOCK_FLUSH) {
+ ret = DRM(flush_queue)(dev, DRM_KERNEL_CONTEXT);
+ if (!ret) ret = DRM(flush_queue)(dev, context);
+ }
+ if (flags & _DRM_LOCK_FLUSH_ALL) {
+ for (i = 0; !ret && i < dev->queue_count; i++) {
+ ret = DRM(flush_queue)(dev, i);
+ }
+ }
+ return ret;
+}
+
+int DRM(flush_unblock)(drm_device_t *dev, int context, drm_lock_flags_t flags)
+{
+ int ret = 0;
+ int i;
+
+ DRM_DEBUG("\n");
+
+ if (flags & _DRM_LOCK_FLUSH) {
+ ret = DRM(flush_unblock_queue)(dev, DRM_KERNEL_CONTEXT);
+ if (!ret) ret = DRM(flush_unblock_queue)(dev, context);
+ }
+ if (flags & _DRM_LOCK_FLUSH_ALL) {
+ for (i = 0; !ret && i < dev->queue_count; i++) {
+ ret = DRM(flush_unblock_queue)(dev, i);
+ }
+ }
+
+ return ret;
+}
+
+int DRM(finish)(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ int ret = 0;
+ drm_lock_t lock;
+
+ DRM_DEBUG("\n");
+
+ if (copy_from_user(&lock, (drm_lock_t __user *)arg, sizeof(lock)))
+ return -EFAULT;
+ ret = DRM(flush_block_and_flush)(dev, lock.context, lock.flags);
+ DRM(flush_unblock)(dev, lock.context, lock.flags);
+ return ret;
+}
diff --git a/drivers/char/drm/gamma_old_dma.h b/drivers/char/drm/gamma_old_dma.h
new file mode 100644
index 000000000000..abdd454aab9f
--- /dev/null
+++ b/drivers/char/drm/gamma_old_dma.h
@@ -0,0 +1,313 @@
+/* drm_dma.c -- DMA IOCTL and function support -*- linux-c -*-
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+
+/* Gamma-specific code pulled from drm_dma.h:
+ */
+
+void DRM(clear_next_buffer)(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+
+ dma->next_buffer = NULL;
+ if (dma->next_queue && !DRM_BUFCOUNT(&dma->next_queue->waitlist)) {
+ wake_up_interruptible(&dma->next_queue->flush_queue);
+ }
+ dma->next_queue = NULL;
+}
+
+int DRM(select_queue)(drm_device_t *dev, void (*wrapper)(unsigned long))
+{
+ int i;
+ int candidate = -1;
+ int j = jiffies;
+
+ if (!dev) {
+ DRM_ERROR("No device\n");
+ return -1;
+ }
+ if (!dev->queuelist || !dev->queuelist[DRM_KERNEL_CONTEXT]) {
+ /* This only happens between the time the
+ interrupt is initialized and the time
+ the queues are initialized. */
+ return -1;
+ }
+
+ /* Doing "while locked" DMA? */
+ if (DRM_WAITCOUNT(dev, DRM_KERNEL_CONTEXT)) {
+ return DRM_KERNEL_CONTEXT;
+ }
+
+ /* If there are buffers on the last_context
+ queue, and we have not been executing
+ this context very long, continue to
+ execute this context. */
+ if (dev->last_switch <= j
+ && dev->last_switch + DRM_TIME_SLICE > j
+ && DRM_WAITCOUNT(dev, dev->last_context)) {
+ return dev->last_context;
+ }
+
+ /* Otherwise, find a candidate */
+ for (i = dev->last_checked + 1; i < dev->queue_count; i++) {
+ if (DRM_WAITCOUNT(dev, i)) {
+ candidate = dev->last_checked = i;
+ break;
+ }
+ }
+
+ if (candidate < 0) {
+ for (i = 0; i < dev->queue_count; i++) {
+ if (DRM_WAITCOUNT(dev, i)) {
+ candidate = dev->last_checked = i;
+ break;
+ }
+ }
+ }
+
+ if (wrapper
+ && candidate >= 0
+ && candidate != dev->last_context
+ && dev->last_switch <= j
+ && dev->last_switch + DRM_TIME_SLICE > j) {
+ if (dev->timer.expires != dev->last_switch + DRM_TIME_SLICE) {
+ del_timer(&dev->timer);
+ dev->timer.function = wrapper;
+ dev->timer.data = (unsigned long)dev;
+ dev->timer.expires = dev->last_switch+DRM_TIME_SLICE;
+ add_timer(&dev->timer);
+ }
+ return -1;
+ }
+
+ return candidate;
+}
+
+
+int DRM(dma_enqueue)(struct file *filp, drm_dma_t *d)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ int i;
+ drm_queue_t *q;
+ drm_buf_t *buf;
+ int idx;
+ int while_locked = 0;
+ drm_device_dma_t *dma = dev->dma;
+ int *ind;
+ int err;
+ DECLARE_WAITQUEUE(entry, current);
+
+ DRM_DEBUG("%d\n", d->send_count);
+
+ if (d->flags & _DRM_DMA_WHILE_LOCKED) {
+ int context = dev->lock.hw_lock->lock;
+
+ if (!_DRM_LOCK_IS_HELD(context)) {
+ DRM_ERROR("No lock held during \"while locked\""
+ " request\n");
+ return -EINVAL;
+ }
+ if (d->context != _DRM_LOCKING_CONTEXT(context)
+ && _DRM_LOCKING_CONTEXT(context) != DRM_KERNEL_CONTEXT) {
+ DRM_ERROR("Lock held by %d while %d makes"
+ " \"while locked\" request\n",
+ _DRM_LOCKING_CONTEXT(context),
+ d->context);
+ return -EINVAL;
+ }
+ q = dev->queuelist[DRM_KERNEL_CONTEXT];
+ while_locked = 1;
+ } else {
+ q = dev->queuelist[d->context];
+ }
+
+
+ atomic_inc(&q->use_count);
+ if (atomic_read(&q->block_write)) {
+ add_wait_queue(&q->write_queue, &entry);
+ atomic_inc(&q->block_count);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ if (!atomic_read(&q->block_write)) break;
+ schedule();
+ if (signal_pending(current)) {
+ atomic_dec(&q->use_count);
+ remove_wait_queue(&q->write_queue, &entry);
+ return -EINTR;
+ }
+ }
+ atomic_dec(&q->block_count);
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&q->write_queue, &entry);
+ }
+
+ ind = DRM(alloc)(d->send_count * sizeof(int), DRM_MEM_DRIVER);
+ if (!ind)
+ return -ENOMEM;
+
+ if (copy_from_user(ind, d->send_indices, d->send_count * sizeof(int))) {
+ err = -EFAULT;
+ goto out;
+ }
+
+ err = -EINVAL;
+ for (i = 0; i < d->send_count; i++) {
+ idx = ind[i];
+ if (idx < 0 || idx >= dma->buf_count) {
+ DRM_ERROR("Index %d (of %d max)\n",
+ ind[i], dma->buf_count - 1);
+ goto out;
+ }
+ buf = dma->buflist[ idx ];
+ if (buf->filp != filp) {
+ DRM_ERROR("Process %d using buffer not owned\n",
+ current->pid);
+ goto out;
+ }
+ if (buf->list != DRM_LIST_NONE) {
+ DRM_ERROR("Process %d using buffer %d on list %d\n",
+ current->pid, buf->idx, buf->list);
+ goto out;
+ }
+ buf->used = ind[i];
+ buf->while_locked = while_locked;
+ buf->context = d->context;
+ if (!buf->used) {
+ DRM_ERROR("Queueing 0 length buffer\n");
+ }
+ if (buf->pending) {
+ DRM_ERROR("Queueing pending buffer:"
+ " buffer %d, offset %d\n",
+ ind[i], i);
+ goto out;
+ }
+ if (buf->waiting) {
+ DRM_ERROR("Queueing waiting buffer:"
+ " buffer %d, offset %d\n",
+ ind[i], i);
+ goto out;
+ }
+ buf->waiting = 1;
+ if (atomic_read(&q->use_count) == 1
+ || atomic_read(&q->finalization)) {
+ DRM(free_buffer)(dev, buf);
+ } else {
+ DRM(waitlist_put)(&q->waitlist, buf);
+ atomic_inc(&q->total_queued);
+ }
+ }
+ atomic_dec(&q->use_count);
+
+ return 0;
+
+out:
+ DRM(free)(ind, d->send_count * sizeof(int), DRM_MEM_DRIVER);
+ atomic_dec(&q->use_count);
+ return err;
+}
+
+static int DRM(dma_get_buffers_of_order)(struct file *filp, drm_dma_t *d,
+ int order)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->dev;
+ int i;
+ drm_buf_t *buf;
+ drm_device_dma_t *dma = dev->dma;
+
+ for (i = d->granted_count; i < d->request_count; i++) {
+ buf = DRM(freelist_get)(&dma->bufs[order].freelist,
+ d->flags & _DRM_DMA_WAIT);
+ if (!buf) break;
+ if (buf->pending || buf->waiting) {
+ DRM_ERROR("Free buffer %d in use: filp %p (w%d, p%d)\n",
+ buf->idx,
+ buf->filp,
+ buf->waiting,
+ buf->pending);
+ }
+ buf->filp = filp;
+ if (copy_to_user(&d->request_indices[i],
+ &buf->idx,
+ sizeof(buf->idx)))
+ return -EFAULT;
+
+ if (copy_to_user(&d->request_sizes[i],
+ &buf->total,
+ sizeof(buf->total)))
+ return -EFAULT;
+
+ ++d->granted_count;
+ }
+ return 0;
+}
+
+
+int DRM(dma_get_buffers)(struct file *filp, drm_dma_t *dma)
+{
+ int order;
+ int retcode = 0;
+ int tmp_order;
+
+ order = DRM(order)(dma->request_size);
+
+ dma->granted_count = 0;
+ retcode = DRM(dma_get_buffers_of_order)(filp, dma, order);
+
+ if (dma->granted_count < dma->request_count
+ && (dma->flags & _DRM_DMA_SMALLER_OK)) {
+ for (tmp_order = order - 1;
+ !retcode
+ && dma->granted_count < dma->request_count
+ && tmp_order >= DRM_MIN_ORDER;
+ --tmp_order) {
+
+ retcode = DRM(dma_get_buffers_of_order)(filp, dma,
+ tmp_order);
+ }
+ }
+
+ if (dma->granted_count < dma->request_count
+ && (dma->flags & _DRM_DMA_LARGER_OK)) {
+ for (tmp_order = order + 1;
+ !retcode
+ && dma->granted_count < dma->request_count
+ && tmp_order <= DRM_MAX_ORDER;
+ ++tmp_order) {
+
+ retcode = DRM(dma_get_buffers_of_order)(filp, dma,
+ tmp_order);
+ }
+ }
+ return 0;
+}
+
diff --git a/drivers/char/drm/i810_dma.c b/drivers/char/drm/i810_dma.c
new file mode 100644
index 000000000000..24857cc6c23b
--- /dev/null
+++ b/drivers/char/drm/i810_dma.c
@@ -0,0 +1,1385 @@
+/* i810_dma.c -- DMA support for the i810 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i810_drm.h"
+#include "i810_drv.h"
+#include <linux/interrupt.h> /* For task queue support */
+#include <linux/delay.h>
+#include <linux/pagemap.h>
+
+#define I810_BUF_FREE 2
+#define I810_BUF_CLIENT 1
+#define I810_BUF_HARDWARE 0
+
+#define I810_BUF_UNMAPPED 0
+#define I810_BUF_MAPPED 1
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,2)
+#define down_write down
+#define up_write up
+#endif
+
+static drm_buf_t *i810_freelist_get(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+ int used;
+
+ /* Linear search might not be the best solution */
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ /* In use is already a pointer */
+ used = cmpxchg(buf_priv->in_use, I810_BUF_FREE,
+ I810_BUF_CLIENT);
+ if (used == I810_BUF_FREE) {
+ return buf;
+ }
+ }
+ return NULL;
+}
+
+/* This should only be called if the buffer is not sent to the hardware
+ * yet, the hardware updates in use for us once its on the ring buffer.
+ */
+
+static int i810_freelist_put(drm_device_t *dev, drm_buf_t *buf)
+{
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ int used;
+
+ /* In use is already a pointer */
+ used = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT, I810_BUF_FREE);
+ if (used != I810_BUF_CLIENT) {
+ DRM_ERROR("Freeing buffer thats not in use : %d\n", buf->idx);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct file_operations i810_buffer_fops = {
+ .open = drm_open,
+ .flush = drm_flush,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = i810_mmap_buffers,
+ .fasync = drm_fasync,
+};
+
+int i810_mmap_buffers(struct file *filp, struct vm_area_struct *vma)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev;
+ drm_i810_private_t *dev_priv;
+ drm_buf_t *buf;
+ drm_i810_buf_priv_t *buf_priv;
+
+ lock_kernel();
+ dev = priv->head->dev;
+ dev_priv = dev->dev_private;
+ buf = dev_priv->mmap_buffer;
+ buf_priv = buf->dev_private;
+
+ vma->vm_flags |= (VM_IO | VM_DONTCOPY);
+ vma->vm_file = filp;
+
+ buf_priv->currently_mapped = I810_BUF_MAPPED;
+ unlock_kernel();
+
+ if (io_remap_pfn_range(vma, vma->vm_start,
+ VM_OFFSET(vma) >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot)) return -EAGAIN;
+ return 0;
+}
+
+static int i810_map_buffer(drm_buf_t *buf, struct file *filp)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ struct file_operations *old_fops;
+ int retcode = 0;
+
+ if (buf_priv->currently_mapped == I810_BUF_MAPPED)
+ return -EINVAL;
+
+ down_write( &current->mm->mmap_sem );
+ old_fops = filp->f_op;
+ filp->f_op = &i810_buffer_fops;
+ dev_priv->mmap_buffer = buf;
+ buf_priv->virtual = (void *)do_mmap(filp, 0, buf->total,
+ PROT_READ|PROT_WRITE,
+ MAP_SHARED,
+ buf->bus_address);
+ dev_priv->mmap_buffer = NULL;
+ filp->f_op = old_fops;
+ if ((unsigned long)buf_priv->virtual > -1024UL) {
+ /* Real error */
+ DRM_ERROR("mmap error\n");
+ retcode = (signed int)buf_priv->virtual;
+ buf_priv->virtual = NULL;
+ }
+ up_write( &current->mm->mmap_sem );
+
+ return retcode;
+}
+
+static int i810_unmap_buffer(drm_buf_t *buf)
+{
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ int retcode = 0;
+
+ if (buf_priv->currently_mapped != I810_BUF_MAPPED)
+ return -EINVAL;
+
+ down_write(&current->mm->mmap_sem);
+ retcode = do_munmap(current->mm,
+ (unsigned long)buf_priv->virtual,
+ (size_t) buf->total);
+ up_write(&current->mm->mmap_sem);
+
+ buf_priv->currently_mapped = I810_BUF_UNMAPPED;
+ buf_priv->virtual = NULL;
+
+ return retcode;
+}
+
+static int i810_dma_get_buffer(drm_device_t *dev, drm_i810_dma_t *d,
+ struct file *filp)
+{
+ drm_buf_t *buf;
+ drm_i810_buf_priv_t *buf_priv;
+ int retcode = 0;
+
+ buf = i810_freelist_get(dev);
+ if (!buf) {
+ retcode = -ENOMEM;
+ DRM_DEBUG("retcode=%d\n", retcode);
+ return retcode;
+ }
+
+ retcode = i810_map_buffer(buf, filp);
+ if (retcode) {
+ i810_freelist_put(dev, buf);
+ DRM_ERROR("mapbuf failed, retcode %d\n", retcode);
+ return retcode;
+ }
+ buf->filp = filp;
+ buf_priv = buf->dev_private;
+ d->granted = 1;
+ d->request_idx = buf->idx;
+ d->request_size = buf->total;
+ d->virtual = buf_priv->virtual;
+
+ return retcode;
+}
+
+static int i810_dma_cleanup(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if (drm_core_check_feature(dev, DRIVER_HAVE_IRQ) && dev->irq_enabled)
+ drm_irq_uninstall(dev);
+
+ if (dev->dev_private) {
+ int i;
+ drm_i810_private_t *dev_priv =
+ (drm_i810_private_t *) dev->dev_private;
+
+ if (dev_priv->ring.virtual_start) {
+ drm_ioremapfree((void *) dev_priv->ring.virtual_start,
+ dev_priv->ring.Size, dev);
+ }
+ if (dev_priv->hw_status_page) {
+ pci_free_consistent(dev->pdev, PAGE_SIZE,
+ dev_priv->hw_status_page,
+ dev_priv->dma_status_page);
+ /* Need to rewrite hardware status page */
+ I810_WRITE(0x02080, 0x1ffff000);
+ }
+ drm_free(dev->dev_private, sizeof(drm_i810_private_t),
+ DRM_MEM_DRIVER);
+ dev->dev_private = NULL;
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ if ( buf_priv->kernel_virtual && buf->total )
+ drm_ioremapfree(buf_priv->kernel_virtual, buf->total, dev);
+ }
+ }
+ return 0;
+}
+
+static int i810_wait_ring(drm_device_t *dev, int n)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_ring_buffer_t *ring = &(dev_priv->ring);
+ int iters = 0;
+ unsigned long end;
+ unsigned int last_head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+
+ end = jiffies + (HZ*3);
+ while (ring->space < n) {
+ ring->head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->space = ring->head - (ring->tail+8);
+ if (ring->space < 0) ring->space += ring->Size;
+
+ if (ring->head != last_head) {
+ end = jiffies + (HZ*3);
+ last_head = ring->head;
+ }
+
+ iters++;
+ if (time_before(end, jiffies)) {
+ DRM_ERROR("space: %d wanted %d\n", ring->space, n);
+ DRM_ERROR("lockup\n");
+ goto out_wait_ring;
+ }
+ udelay(1);
+ }
+
+out_wait_ring:
+ return iters;
+}
+
+static void i810_kernel_lost_context(drm_device_t *dev)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_ring_buffer_t *ring = &(dev_priv->ring);
+
+ ring->head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->tail = I810_READ(LP_RING + RING_TAIL);
+ ring->space = ring->head - (ring->tail+8);
+ if (ring->space < 0) ring->space += ring->Size;
+}
+
+static int i810_freelist_init(drm_device_t *dev, drm_i810_private_t *dev_priv)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int my_idx = 24;
+ u32 *hw_status = (u32 *)(dev_priv->hw_status_page + my_idx);
+ int i;
+
+ if (dma->buf_count > 1019) {
+ /* Not enough space in the status page for the freelist */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+ buf_priv->in_use = hw_status++;
+ buf_priv->my_use_idx = my_idx;
+ my_idx += 4;
+
+ *buf_priv->in_use = I810_BUF_FREE;
+
+ buf_priv->kernel_virtual = drm_ioremap(buf->bus_address,
+ buf->total, dev);
+ }
+ return 0;
+}
+
+static int i810_dma_initialize(drm_device_t *dev,
+ drm_i810_private_t *dev_priv,
+ drm_i810_init_t *init)
+{
+ struct list_head *list;
+
+ memset(dev_priv, 0, sizeof(drm_i810_private_t));
+
+ list_for_each(list, &dev->maplist->head) {
+ drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+ if (r_list->map &&
+ r_list->map->type == _DRM_SHM &&
+ r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+ dev_priv->sarea_map = r_list->map;
+ break;
+ }
+ }
+ if (!dev_priv->sarea_map) {
+ dev->dev_private = (void *)dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("can not find sarea!\n");
+ return -EINVAL;
+ }
+ dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+ if (!dev_priv->mmio_map) {
+ dev->dev_private = (void *)dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("can not find mmio map!\n");
+ return -EINVAL;
+ }
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ if (!dev->agp_buffer_map) {
+ dev->dev_private = (void *)dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("can not find dma buffer map!\n");
+ return -EINVAL;
+ }
+
+ dev_priv->sarea_priv = (drm_i810_sarea_t *)
+ ((u8 *)dev_priv->sarea_map->handle +
+ init->sarea_priv_offset);
+
+ dev_priv->ring.Start = init->ring_start;
+ dev_priv->ring.End = init->ring_end;
+ dev_priv->ring.Size = init->ring_size;
+
+ dev_priv->ring.virtual_start = drm_ioremap(dev->agp->base +
+ init->ring_start,
+ init->ring_size, dev);
+
+ if (dev_priv->ring.virtual_start == NULL) {
+ dev->dev_private = (void *) dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("can not ioremap virtual address for"
+ " ring buffer\n");
+ return -ENOMEM;
+ }
+
+ dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+
+ dev_priv->w = init->w;
+ dev_priv->h = init->h;
+ dev_priv->pitch = init->pitch;
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->depth_offset = init->depth_offset;
+ dev_priv->front_offset = init->front_offset;
+
+ dev_priv->overlay_offset = init->overlay_offset;
+ dev_priv->overlay_physical = init->overlay_physical;
+
+ dev_priv->front_di1 = init->front_offset | init->pitch_bits;
+ dev_priv->back_di1 = init->back_offset | init->pitch_bits;
+ dev_priv->zi1 = init->depth_offset | init->pitch_bits;
+
+ /* Program Hardware Status Page */
+ dev_priv->hw_status_page =
+ pci_alloc_consistent(dev->pdev, PAGE_SIZE,
+ &dev_priv->dma_status_page);
+ if (!dev_priv->hw_status_page) {
+ dev->dev_private = (void *)dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("Can not allocate hardware status page\n");
+ return -ENOMEM;
+ }
+ memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+ DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+ I810_WRITE(0x02080, dev_priv->dma_status_page);
+ DRM_DEBUG("Enabled hardware status page\n");
+
+ /* Now we need to init our freelist */
+ if (i810_freelist_init(dev, dev_priv) != 0) {
+ dev->dev_private = (void *)dev_priv;
+ i810_dma_cleanup(dev);
+ DRM_ERROR("Not enough space in the status page for"
+ " the freelist\n");
+ return -ENOMEM;
+ }
+ dev->dev_private = (void *)dev_priv;
+
+ return 0;
+}
+
+/* i810 DRM version 1.1 used a smaller init structure with different
+ * ordering of values than is currently used (drm >= 1.2). There is
+ * no defined way to detect the XFree version to correct this problem,
+ * however by checking using this procedure we can detect the correct
+ * thing to do.
+ *
+ * #1 Read the Smaller init structure from user-space
+ * #2 Verify the overlay_physical is a valid physical address, or NULL
+ * If it isn't then we have a v1.1 client. Fix up params.
+ * If it is, then we have a 1.2 client... get the rest of the data.
+ */
+static int i810_dma_init_compat(drm_i810_init_t *init, unsigned long arg)
+{
+
+ /* Get v1.1 init data */
+ if (copy_from_user(init, (drm_i810_pre12_init_t __user *)arg,
+ sizeof(drm_i810_pre12_init_t))) {
+ return -EFAULT;
+ }
+
+ if ((!init->overlay_physical) || (init->overlay_physical > 4096)) {
+
+ /* This is a v1.2 client, just get the v1.2 init data */
+ DRM_INFO("Using POST v1.2 init.\n");
+ if (copy_from_user(init, (drm_i810_init_t __user *)arg,
+ sizeof(drm_i810_init_t))) {
+ return -EFAULT;
+ }
+ } else {
+
+ /* This is a v1.1 client, fix the params */
+ DRM_INFO("Using PRE v1.2 init.\n");
+ init->pitch_bits = init->h;
+ init->pitch = init->w;
+ init->h = init->overlay_physical;
+ init->w = init->overlay_offset;
+ init->overlay_physical = 0;
+ init->overlay_offset = 0;
+ }
+
+ return 0;
+}
+
+static int i810_dma_init(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv;
+ drm_i810_init_t init;
+ int retcode = 0;
+
+ /* Get only the init func */
+ if (copy_from_user(&init, (void __user *)arg, sizeof(drm_i810_init_func_t)))
+ return -EFAULT;
+
+ switch(init.func) {
+ case I810_INIT_DMA:
+ /* This case is for backward compatibility. It
+ * handles XFree 4.1.0 and 4.2.0, and has to
+ * do some parameter checking as described below.
+ * It will someday go away.
+ */
+ retcode = i810_dma_init_compat(&init, arg);
+ if (retcode)
+ return retcode;
+
+ dev_priv = drm_alloc(sizeof(drm_i810_private_t),
+ DRM_MEM_DRIVER);
+ if (dev_priv == NULL)
+ return -ENOMEM;
+ retcode = i810_dma_initialize(dev, dev_priv, &init);
+ break;
+
+ default:
+ case I810_INIT_DMA_1_4:
+ DRM_INFO("Using v1.4 init.\n");
+ if (copy_from_user(&init, (drm_i810_init_t __user *)arg,
+ sizeof(drm_i810_init_t))) {
+ return -EFAULT;
+ }
+ dev_priv = drm_alloc(sizeof(drm_i810_private_t),
+ DRM_MEM_DRIVER);
+ if (dev_priv == NULL)
+ return -ENOMEM;
+ retcode = i810_dma_initialize(dev, dev_priv, &init);
+ break;
+
+ case I810_CLEANUP_DMA:
+ DRM_INFO("DMA Cleanup\n");
+ retcode = i810_dma_cleanup(dev);
+ break;
+ }
+
+ return retcode;
+}
+
+
+
+/* Most efficient way to verify state for the i810 is as it is
+ * emitted. Non-conformant state is silently dropped.
+ *
+ * Use 'volatile' & local var tmp to force the emitted values to be
+ * identical to the verified ones.
+ */
+static void i810EmitContextVerified( drm_device_t *dev,
+ volatile unsigned int *code )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ int i, j = 0;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( I810_CTX_SETUP_SIZE );
+
+ OUT_RING( GFX_OP_COLOR_FACTOR );
+ OUT_RING( code[I810_CTXREG_CF1] );
+
+ OUT_RING( GFX_OP_STIPPLE );
+ OUT_RING( code[I810_CTXREG_ST1] );
+
+ for ( i = 4 ; i < I810_CTX_SETUP_SIZE ; i++ ) {
+ tmp = code[i];
+
+ if ((tmp & (7<<29)) == (3<<29) &&
+ (tmp & (0x1f<<24)) < (0x1d<<24))
+ {
+ OUT_RING( tmp );
+ j++;
+ }
+ else printk("constext state dropped!!!\n");
+ }
+
+ if (j & 1)
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+static void i810EmitTexVerified( drm_device_t *dev,
+ volatile unsigned int *code )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ int i, j = 0;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( I810_TEX_SETUP_SIZE );
+
+ OUT_RING( GFX_OP_MAP_INFO );
+ OUT_RING( code[I810_TEXREG_MI1] );
+ OUT_RING( code[I810_TEXREG_MI2] );
+ OUT_RING( code[I810_TEXREG_MI3] );
+
+ for ( i = 4 ; i < I810_TEX_SETUP_SIZE ; i++ ) {
+ tmp = code[i];
+
+ if ((tmp & (7<<29)) == (3<<29) &&
+ (tmp & (0x1f<<24)) < (0x1d<<24))
+ {
+ OUT_RING( tmp );
+ j++;
+ }
+ else printk("texture state dropped!!!\n");
+ }
+
+ if (j & 1)
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+
+/* Need to do some additional checking when setting the dest buffer.
+ */
+static void i810EmitDestVerified( drm_device_t *dev,
+ volatile unsigned int *code )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( I810_DEST_SETUP_SIZE + 2 );
+
+ tmp = code[I810_DESTREG_DI1];
+ if (tmp == dev_priv->front_di1 || tmp == dev_priv->back_di1) {
+ OUT_RING( CMD_OP_DESTBUFFER_INFO );
+ OUT_RING( tmp );
+ } else
+ DRM_DEBUG("bad di1 %x (allow %x or %x)\n",
+ tmp, dev_priv->front_di1, dev_priv->back_di1);
+
+ /* invarient:
+ */
+ OUT_RING( CMD_OP_Z_BUFFER_INFO );
+ OUT_RING( dev_priv->zi1 );
+
+ OUT_RING( GFX_OP_DESTBUFFER_VARS );
+ OUT_RING( code[I810_DESTREG_DV1] );
+
+ OUT_RING( GFX_OP_DRAWRECT_INFO );
+ OUT_RING( code[I810_DESTREG_DR1] );
+ OUT_RING( code[I810_DESTREG_DR2] );
+ OUT_RING( code[I810_DESTREG_DR3] );
+ OUT_RING( code[I810_DESTREG_DR4] );
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+
+
+static void i810EmitState( drm_device_t *dev )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+
+ DRM_DEBUG("%s %x\n", __FUNCTION__, dirty);
+
+ if (dirty & I810_UPLOAD_BUFFERS) {
+ i810EmitDestVerified( dev, sarea_priv->BufferState );
+ sarea_priv->dirty &= ~I810_UPLOAD_BUFFERS;
+ }
+
+ if (dirty & I810_UPLOAD_CTX) {
+ i810EmitContextVerified( dev, sarea_priv->ContextState );
+ sarea_priv->dirty &= ~I810_UPLOAD_CTX;
+ }
+
+ if (dirty & I810_UPLOAD_TEX0) {
+ i810EmitTexVerified( dev, sarea_priv->TexState[0] );
+ sarea_priv->dirty &= ~I810_UPLOAD_TEX0;
+ }
+
+ if (dirty & I810_UPLOAD_TEX1) {
+ i810EmitTexVerified( dev, sarea_priv->TexState[1] );
+ sarea_priv->dirty &= ~I810_UPLOAD_TEX1;
+ }
+}
+
+
+
+/* need to verify
+ */
+static void i810_dma_dispatch_clear( drm_device_t *dev, int flags,
+ unsigned int clear_color,
+ unsigned int clear_zval )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int pitch = dev_priv->pitch;
+ int cpp = 2;
+ int i;
+ RING_LOCALS;
+
+ if ( dev_priv->current_page == 1 ) {
+ unsigned int tmp = flags;
+
+ flags &= ~(I810_FRONT | I810_BACK);
+ if (tmp & I810_FRONT) flags |= I810_BACK;
+ if (tmp & I810_BACK) flags |= I810_FRONT;
+ }
+
+ i810_kernel_lost_context(dev);
+
+ if (nbox > I810_NR_SAREA_CLIPRECTS)
+ nbox = I810_NR_SAREA_CLIPRECTS;
+
+ for (i = 0 ; i < nbox ; i++, pbox++) {
+ unsigned int x = pbox->x1;
+ unsigned int y = pbox->y1;
+ unsigned int width = (pbox->x2 - x) * cpp;
+ unsigned int height = pbox->y2 - y;
+ unsigned int start = y * pitch + x * cpp;
+
+ if (pbox->x1 > pbox->x2 ||
+ pbox->y1 > pbox->y2 ||
+ pbox->x2 > dev_priv->w ||
+ pbox->y2 > dev_priv->h)
+ continue;
+
+ if ( flags & I810_FRONT ) {
+ BEGIN_LP_RING( 6 );
+ OUT_RING( BR00_BITBLT_CLIENT |
+ BR00_OP_COLOR_BLT | 0x3 );
+ OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+ OUT_RING( (height << 16) | width );
+ OUT_RING( start );
+ OUT_RING( clear_color );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+
+ if ( flags & I810_BACK ) {
+ BEGIN_LP_RING( 6 );
+ OUT_RING( BR00_BITBLT_CLIENT |
+ BR00_OP_COLOR_BLT | 0x3 );
+ OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+ OUT_RING( (height << 16) | width );
+ OUT_RING( dev_priv->back_offset + start );
+ OUT_RING( clear_color );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+
+ if ( flags & I810_DEPTH ) {
+ BEGIN_LP_RING( 6 );
+ OUT_RING( BR00_BITBLT_CLIENT |
+ BR00_OP_COLOR_BLT | 0x3 );
+ OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+ OUT_RING( (height << 16) | width );
+ OUT_RING( dev_priv->depth_offset + start );
+ OUT_RING( clear_zval );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+ }
+}
+
+static void i810_dma_dispatch_swap( drm_device_t *dev )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int pitch = dev_priv->pitch;
+ int cpp = 2;
+ int i;
+ RING_LOCALS;
+
+ DRM_DEBUG("swapbuffers\n");
+
+ i810_kernel_lost_context(dev);
+
+ if (nbox > I810_NR_SAREA_CLIPRECTS)
+ nbox = I810_NR_SAREA_CLIPRECTS;
+
+ for (i = 0 ; i < nbox; i++, pbox++)
+ {
+ unsigned int w = pbox->x2 - pbox->x1;
+ unsigned int h = pbox->y2 - pbox->y1;
+ unsigned int dst = pbox->x1*cpp + pbox->y1*pitch;
+ unsigned int start = dst;
+
+ if (pbox->x1 > pbox->x2 ||
+ pbox->y1 > pbox->y2 ||
+ pbox->x2 > dev_priv->w ||
+ pbox->y2 > dev_priv->h)
+ continue;
+
+ BEGIN_LP_RING( 6 );
+ OUT_RING( BR00_BITBLT_CLIENT | BR00_OP_SRC_COPY_BLT | 0x4 );
+ OUT_RING( pitch | (0xCC << 16));
+ OUT_RING( (h << 16) | (w * cpp));
+ if (dev_priv->current_page == 0)
+ OUT_RING(dev_priv->front_offset + start);
+ else
+ OUT_RING(dev_priv->back_offset + start);
+ OUT_RING( pitch );
+ if (dev_priv->current_page == 0)
+ OUT_RING(dev_priv->back_offset + start);
+ else
+ OUT_RING(dev_priv->front_offset + start);
+ ADVANCE_LP_RING();
+ }
+}
+
+
+static void i810_dma_dispatch_vertex(drm_device_t *dev,
+ drm_buf_t *buf,
+ int discard,
+ int used)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_clip_rect_t *box = sarea_priv->boxes;
+ int nbox = sarea_priv->nbox;
+ unsigned long address = (unsigned long)buf->bus_address;
+ unsigned long start = address - dev->agp->base;
+ int i = 0;
+ RING_LOCALS;
+
+ i810_kernel_lost_context(dev);
+
+ if (nbox > I810_NR_SAREA_CLIPRECTS)
+ nbox = I810_NR_SAREA_CLIPRECTS;
+
+ if (used > 4*1024)
+ used = 0;
+
+ if (sarea_priv->dirty)
+ i810EmitState( dev );
+
+ if (buf_priv->currently_mapped == I810_BUF_MAPPED) {
+ unsigned int prim = (sarea_priv->vertex_prim & PR_MASK);
+
+ *(u32 *)buf_priv->kernel_virtual = ((GFX_OP_PRIMITIVE | prim | ((used/4)-2)));
+
+ if (used & 4) {
+ *(u32 *)((u32)buf_priv->kernel_virtual + used) = 0;
+ used += 4;
+ }
+
+ i810_unmap_buffer(buf);
+ }
+
+ if (used) {
+ do {
+ if (i < nbox) {
+ BEGIN_LP_RING(4);
+ OUT_RING( GFX_OP_SCISSOR | SC_UPDATE_SCISSOR |
+ SC_ENABLE );
+ OUT_RING( GFX_OP_SCISSOR_INFO );
+ OUT_RING( box[i].x1 | (box[i].y1<<16) );
+ OUT_RING( (box[i].x2-1) | ((box[i].y2-1)<<16) );
+ ADVANCE_LP_RING();
+ }
+
+ BEGIN_LP_RING(4);
+ OUT_RING( CMD_OP_BATCH_BUFFER );
+ OUT_RING( start | BB1_PROTECTED );
+ OUT_RING( start + used - 4 );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ } while (++i < nbox);
+ }
+
+ if (discard) {
+ dev_priv->counter++;
+
+ (void) cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+ I810_BUF_HARDWARE);
+
+ BEGIN_LP_RING(8);
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( 20 );
+ OUT_RING( dev_priv->counter );
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( buf_priv->my_use_idx );
+ OUT_RING( I810_BUF_FREE );
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+}
+
+static void i810_dma_dispatch_flip( drm_device_t *dev )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ int pitch = dev_priv->pitch;
+ RING_LOCALS;
+
+ DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n",
+ __FUNCTION__,
+ dev_priv->current_page,
+ dev_priv->sarea_priv->pf_current_page);
+
+ i810_kernel_lost_context(dev);
+
+ BEGIN_LP_RING( 2 );
+ OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ BEGIN_LP_RING( I810_DEST_SETUP_SIZE + 2 );
+ /* On i815 at least ASYNC is buggy */
+ /* pitch<<5 is from 11.2.8 p158,
+ its the pitch / 8 then left shifted 8,
+ so (pitch >> 3) << 8 */
+ OUT_RING( CMD_OP_FRONTBUFFER_INFO | (pitch<<5) /*| ASYNC_FLIP */ );
+ if ( dev_priv->current_page == 0 ) {
+ OUT_RING( dev_priv->back_offset );
+ dev_priv->current_page = 1;
+ } else {
+ OUT_RING( dev_priv->front_offset );
+ dev_priv->current_page = 0;
+ }
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ BEGIN_LP_RING(2);
+ OUT_RING( CMD_OP_WAIT_FOR_EVENT | WAIT_FOR_PLANE_A_FLIP );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ /* Increment the frame counter. The client-side 3D driver must
+ * throttle the framerate by waiting for this value before
+ * performing the swapbuffer ioctl.
+ */
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+
+}
+
+static void i810_dma_quiescent(drm_device_t *dev)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+/* printk("%s\n", __FUNCTION__); */
+
+ i810_kernel_lost_context(dev);
+
+ BEGIN_LP_RING(4);
+ OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ i810_wait_ring( dev, dev_priv->ring.Size - 8 );
+}
+
+static int i810_flush_queue(drm_device_t *dev)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ int i, ret = 0;
+ RING_LOCALS;
+
+/* printk("%s\n", __FUNCTION__); */
+
+ i810_kernel_lost_context(dev);
+
+ BEGIN_LP_RING(2);
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ i810_wait_ring( dev, dev_priv->ring.Size - 8 );
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+ int used = cmpxchg(buf_priv->in_use, I810_BUF_HARDWARE,
+ I810_BUF_FREE);
+
+ if (used == I810_BUF_HARDWARE)
+ DRM_DEBUG("reclaimed from HARDWARE\n");
+ if (used == I810_BUF_CLIENT)
+ DRM_DEBUG("still on client\n");
+ }
+
+ return ret;
+}
+
+/* Must be called with the lock held */
+void i810_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+
+ if (!dma) return;
+ if (!dev->dev_private) return;
+ if (!dma->buflist) return;
+
+ i810_flush_queue(dev);
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+ if (buf->filp == filp && buf_priv) {
+ int used = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+ I810_BUF_FREE);
+
+ if (used == I810_BUF_CLIENT)
+ DRM_DEBUG("reclaimed from client\n");
+ if (buf_priv->currently_mapped == I810_BUF_MAPPED)
+ buf_priv->currently_mapped = I810_BUF_UNMAPPED;
+ }
+ }
+}
+
+int i810_flush_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ i810_flush_queue(dev);
+ return 0;
+}
+
+
+static int i810_dma_vertex(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+ dev_priv->sarea_priv;
+ drm_i810_vertex_t vertex;
+
+ if (copy_from_user(&vertex, (drm_i810_vertex_t __user *)arg, sizeof(vertex)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ DRM_DEBUG("i810 dma vertex, idx %d used %d discard %d\n",
+ vertex.idx, vertex.used, vertex.discard);
+
+ if (vertex.idx < 0 || vertex.idx > dma->buf_count)
+ return -EINVAL;
+
+ i810_dma_dispatch_vertex( dev,
+ dma->buflist[ vertex.idx ],
+ vertex.discard, vertex.used );
+
+ atomic_add(vertex.used, &dev->counts[_DRM_STAT_SECONDARY]);
+ atomic_inc(&dev->counts[_DRM_STAT_DMA]);
+ sarea_priv->last_enqueue = dev_priv->counter-1;
+ sarea_priv->last_dispatch = (int) hw_status[5];
+
+ return 0;
+}
+
+
+
+static int i810_clear_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_clear_t clear;
+
+ if (copy_from_user(&clear, (drm_i810_clear_t __user *)arg, sizeof(clear)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ /* GH: Someone's doing nasty things... */
+ if (!dev->dev_private) {
+ return -EINVAL;
+ }
+
+ i810_dma_dispatch_clear( dev, clear.flags,
+ clear.clear_color,
+ clear.clear_depth );
+ return 0;
+}
+
+static int i810_swap_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ DRM_DEBUG("i810_swap_bufs\n");
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ i810_dma_dispatch_swap( dev );
+ return 0;
+}
+
+static int i810_getage(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+ dev_priv->sarea_priv;
+
+ sarea_priv->last_dispatch = (int) hw_status[5];
+ return 0;
+}
+
+static int i810_getbuf(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ int retcode = 0;
+ drm_i810_dma_t d;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+ dev_priv->sarea_priv;
+
+ if (copy_from_user(&d, (drm_i810_dma_t __user *)arg, sizeof(d)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ d.granted = 0;
+
+ retcode = i810_dma_get_buffer(dev, &d, filp);
+
+ DRM_DEBUG("i810_dma: %d returning %d, granted = %d\n",
+ current->pid, retcode, d.granted);
+
+ if (copy_to_user((drm_dma_t __user *)arg, &d, sizeof(d)))
+ return -EFAULT;
+ sarea_priv->last_dispatch = (int) hw_status[5];
+
+ return retcode;
+}
+
+static int i810_copybuf(struct inode *inode,
+ struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ /* Never copy - 2.4.x doesn't need it */
+ return 0;
+}
+
+static int i810_docopy(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ /* Never copy - 2.4.x doesn't need it */
+ return 0;
+}
+
+static void i810_dma_dispatch_mc(drm_device_t *dev, drm_buf_t *buf, int used,
+ unsigned int last_render)
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+ drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned long address = (unsigned long)buf->bus_address;
+ unsigned long start = address - dev->agp->base;
+ int u;
+ RING_LOCALS;
+
+ i810_kernel_lost_context(dev);
+
+ u = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+ I810_BUF_HARDWARE);
+ if (u != I810_BUF_CLIENT) {
+ DRM_DEBUG("MC found buffer that isn't mine!\n");
+ }
+
+ if (used > 4*1024)
+ used = 0;
+
+ sarea_priv->dirty = 0x7f;
+
+ DRM_DEBUG("dispatch mc addr 0x%lx, used 0x%x\n",
+ address, used);
+
+ dev_priv->counter++;
+ DRM_DEBUG("dispatch counter : %ld\n", dev_priv->counter);
+ DRM_DEBUG("i810_dma_dispatch_mc\n");
+ DRM_DEBUG("start : %lx\n", start);
+ DRM_DEBUG("used : %d\n", used);
+ DRM_DEBUG("start + used - 4 : %ld\n", start + used - 4);
+
+ if (buf_priv->currently_mapped == I810_BUF_MAPPED) {
+ if (used & 4) {
+ *(u32 *)((u32)buf_priv->virtual + used) = 0;
+ used += 4;
+ }
+
+ i810_unmap_buffer(buf);
+ }
+ BEGIN_LP_RING(4);
+ OUT_RING( CMD_OP_BATCH_BUFFER );
+ OUT_RING( start | BB1_PROTECTED );
+ OUT_RING( start + used - 4 );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+
+ BEGIN_LP_RING(8);
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( buf_priv->my_use_idx );
+ OUT_RING( I810_BUF_FREE );
+ OUT_RING( 0 );
+
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( 16 );
+ OUT_RING( last_render );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+}
+
+static int i810_dma_mc(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+ dev_priv->sarea_priv;
+ drm_i810_mc_t mc;
+
+ if (copy_from_user(&mc, (drm_i810_mc_t __user *)arg, sizeof(mc)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (mc.idx >= dma->buf_count || mc.idx < 0)
+ return -EINVAL;
+
+ i810_dma_dispatch_mc(dev, dma->buflist[mc.idx], mc.used,
+ mc.last_render );
+
+ atomic_add(mc.used, &dev->counts[_DRM_STAT_SECONDARY]);
+ atomic_inc(&dev->counts[_DRM_STAT_DMA]);
+ sarea_priv->last_enqueue = dev_priv->counter-1;
+ sarea_priv->last_dispatch = (int) hw_status[5];
+
+ return 0;
+}
+
+static int i810_rstatus(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+ return (int)(((u32 *)(dev_priv->hw_status_page))[4]);
+}
+
+static int i810_ov0_info(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+ drm_i810_overlay_t data;
+
+ data.offset = dev_priv->overlay_offset;
+ data.physical = dev_priv->overlay_physical;
+ if (copy_to_user((drm_i810_overlay_t __user *)arg,&data,sizeof(data)))
+ return -EFAULT;
+ return 0;
+}
+
+static int i810_fstatus(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ return I810_READ(0x30008);
+}
+
+static int i810_ov0_flip(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ //Tell the overlay to update
+ I810_WRITE(0x30000,dev_priv->overlay_physical | 0x80000000);
+
+ return 0;
+}
+
+
+/* Not sure why this isn't set all the time:
+ */
+static void i810_do_init_pageflip( drm_device_t *dev )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+ dev_priv->page_flipping = 1;
+ dev_priv->current_page = 0;
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static int i810_do_cleanup_pageflip( drm_device_t *dev )
+{
+ drm_i810_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+ if (dev_priv->current_page != 0)
+ i810_dma_dispatch_flip( dev );
+
+ dev_priv->page_flipping = 0;
+ return 0;
+}
+
+static int i810_flip_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i810_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (!dev_priv->page_flipping)
+ i810_do_init_pageflip( dev );
+
+ i810_dma_dispatch_flip( dev );
+ return 0;
+}
+
+void i810_driver_pretakedown(drm_device_t *dev)
+{
+ i810_dma_cleanup( dev );
+}
+
+void i810_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+ if (dev->dev_private) {
+ drm_i810_private_t *dev_priv = dev->dev_private;
+ if (dev_priv->page_flipping) {
+ i810_do_cleanup_pageflip(dev);
+ }
+ }
+}
+
+void i810_driver_release(drm_device_t *dev, struct file *filp)
+{
+ i810_reclaim_buffers(dev, filp);
+}
+
+int i810_driver_dma_quiescent(drm_device_t *dev)
+{
+ i810_dma_quiescent( dev );
+ return 0;
+}
+
+drm_ioctl_desc_t i810_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_I810_INIT)] = { i810_dma_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_I810_VERTEX)] = { i810_dma_vertex, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_CLEAR)] = { i810_clear_bufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_FLUSH)] = { i810_flush_ioctl, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_GETAGE)] = { i810_getage, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_GETBUF)] = { i810_getbuf, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_SWAP)] = { i810_swap_bufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_COPY)] = { i810_copybuf, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_DOCOPY)] = { i810_docopy, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_OV0INFO)] = { i810_ov0_info, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_FSTATUS)] = { i810_fstatus, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_OV0FLIP)] = { i810_ov0_flip, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_MC)] = { i810_dma_mc, 1, 1 },
+ [DRM_IOCTL_NR(DRM_I810_RSTATUS)] = { i810_rstatus, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I810_FLIP)] = { i810_flip_bufs, 1, 0 }
+};
+
+int i810_max_ioctl = DRM_ARRAY_SIZE(i810_ioctls);
diff --git a/drivers/char/drm/i810_drm.h b/drivers/char/drm/i810_drm.h
new file mode 100644
index 000000000000..73ac40563b1d
--- /dev/null
+++ b/drivers/char/drm/i810_drm.h
@@ -0,0 +1,289 @@
+#ifndef _I810_DRM_H_
+#define _I810_DRM_H_
+
+/* WARNING: These defines must be the same as what the Xserver uses.
+ * if you change them, you must change the defines in the Xserver.
+ */
+
+#ifndef _I810_DEFINES_
+#define _I810_DEFINES_
+
+#define I810_DMA_BUF_ORDER 12
+#define I810_DMA_BUF_SZ (1<<I810_DMA_BUF_ORDER)
+#define I810_DMA_BUF_NR 256
+#define I810_NR_SAREA_CLIPRECTS 8
+
+/* Each region is a minimum of 64k, and there are at most 64 of them.
+ */
+#define I810_NR_TEX_REGIONS 64
+#define I810_LOG_MIN_TEX_REGION_SIZE 16
+#endif
+
+#define I810_UPLOAD_TEX0IMAGE 0x1 /* handled clientside */
+#define I810_UPLOAD_TEX1IMAGE 0x2 /* handled clientside */
+#define I810_UPLOAD_CTX 0x4
+#define I810_UPLOAD_BUFFERS 0x8
+#define I810_UPLOAD_TEX0 0x10
+#define I810_UPLOAD_TEX1 0x20
+#define I810_UPLOAD_CLIPRECTS 0x40
+
+
+/* Indices into buf.Setup where various bits of state are mirrored per
+ * context and per buffer. These can be fired at the card as a unit,
+ * or in a piecewise fashion as required.
+ */
+
+/* Destbuffer state
+ * - backbuffer linear offset and pitch -- invarient in the current dri
+ * - zbuffer linear offset and pitch -- also invarient
+ * - drawing origin in back and depth buffers.
+ *
+ * Keep the depth/back buffer state here to accommodate private buffers
+ * in the future.
+ */
+#define I810_DESTREG_DI0 0 /* CMD_OP_DESTBUFFER_INFO (2 dwords) */
+#define I810_DESTREG_DI1 1
+#define I810_DESTREG_DV0 2 /* GFX_OP_DESTBUFFER_VARS (2 dwords) */
+#define I810_DESTREG_DV1 3
+#define I810_DESTREG_DR0 4 /* GFX_OP_DRAWRECT_INFO (4 dwords) */
+#define I810_DESTREG_DR1 5
+#define I810_DESTREG_DR2 6
+#define I810_DESTREG_DR3 7
+#define I810_DESTREG_DR4 8
+#define I810_DEST_SETUP_SIZE 10
+
+/* Context state
+ */
+#define I810_CTXREG_CF0 0 /* GFX_OP_COLOR_FACTOR */
+#define I810_CTXREG_CF1 1
+#define I810_CTXREG_ST0 2 /* GFX_OP_STIPPLE */
+#define I810_CTXREG_ST1 3
+#define I810_CTXREG_VF 4 /* GFX_OP_VERTEX_FMT */
+#define I810_CTXREG_MT 5 /* GFX_OP_MAP_TEXELS */
+#define I810_CTXREG_MC0 6 /* GFX_OP_MAP_COLOR_STAGES - stage 0 */
+#define I810_CTXREG_MC1 7 /* GFX_OP_MAP_COLOR_STAGES - stage 1 */
+#define I810_CTXREG_MC2 8 /* GFX_OP_MAP_COLOR_STAGES - stage 2 */
+#define I810_CTXREG_MA0 9 /* GFX_OP_MAP_ALPHA_STAGES - stage 0 */
+#define I810_CTXREG_MA1 10 /* GFX_OP_MAP_ALPHA_STAGES - stage 1 */
+#define I810_CTXREG_MA2 11 /* GFX_OP_MAP_ALPHA_STAGES - stage 2 */
+#define I810_CTXREG_SDM 12 /* GFX_OP_SRC_DEST_MONO */
+#define I810_CTXREG_FOG 13 /* GFX_OP_FOG_COLOR */
+#define I810_CTXREG_B1 14 /* GFX_OP_BOOL_1 */
+#define I810_CTXREG_B2 15 /* GFX_OP_BOOL_2 */
+#define I810_CTXREG_LCS 16 /* GFX_OP_LINEWIDTH_CULL_SHADE_MODE */
+#define I810_CTXREG_PV 17 /* GFX_OP_PV_RULE -- Invarient! */
+#define I810_CTXREG_ZA 18 /* GFX_OP_ZBIAS_ALPHAFUNC */
+#define I810_CTXREG_AA 19 /* GFX_OP_ANTIALIAS */
+#define I810_CTX_SETUP_SIZE 20
+
+/* Texture state (per tex unit)
+ */
+#define I810_TEXREG_MI0 0 /* GFX_OP_MAP_INFO (4 dwords) */
+#define I810_TEXREG_MI1 1
+#define I810_TEXREG_MI2 2
+#define I810_TEXREG_MI3 3
+#define I810_TEXREG_MF 4 /* GFX_OP_MAP_FILTER */
+#define I810_TEXREG_MLC 5 /* GFX_OP_MAP_LOD_CTL */
+#define I810_TEXREG_MLL 6 /* GFX_OP_MAP_LOD_LIMITS */
+#define I810_TEXREG_MCS 7 /* GFX_OP_MAP_COORD_SETS ??? */
+#define I810_TEX_SETUP_SIZE 8
+
+/* Flags for clear ioctl
+ */
+#define I810_FRONT 0x1
+#define I810_BACK 0x2
+#define I810_DEPTH 0x4
+
+typedef enum _drm_i810_init_func {
+ I810_INIT_DMA = 0x01,
+ I810_CLEANUP_DMA = 0x02,
+ I810_INIT_DMA_1_4 = 0x03
+ } drm_i810_init_func_t;
+
+/* This is the init structure after v1.2 */
+typedef struct _drm_i810_init {
+ drm_i810_init_func_t func;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+ int ring_map_idx;
+ int buffer_map_idx;
+#else
+ unsigned int mmio_offset;
+ unsigned int buffers_offset;
+#endif
+ int sarea_priv_offset;
+ unsigned int ring_start;
+ unsigned int ring_end;
+ unsigned int ring_size;
+ unsigned int front_offset;
+ unsigned int back_offset;
+ unsigned int depth_offset;
+ unsigned int overlay_offset;
+ unsigned int overlay_physical;
+ unsigned int w;
+ unsigned int h;
+ unsigned int pitch;
+ unsigned int pitch_bits;
+} drm_i810_init_t;
+
+/* This is the init structure prior to v1.2 */
+typedef struct _drm_i810_pre12_init {
+ drm_i810_init_func_t func;
+ unsigned int mmio_offset;
+ unsigned int buffers_offset;
+ int sarea_priv_offset;
+ unsigned int ring_start;
+ unsigned int ring_end;
+ unsigned int ring_size;
+ unsigned int front_offset;
+ unsigned int back_offset;
+ unsigned int depth_offset;
+ unsigned int w;
+ unsigned int h;
+ unsigned int pitch;
+ unsigned int pitch_bits;
+} drm_i810_pre12_init_t;
+
+/* Warning: If you change the SAREA structure you must change the Xserver
+ * structure as well */
+
+typedef struct _drm_i810_tex_region {
+ unsigned char next, prev; /* indices to form a circular LRU */
+ unsigned char in_use; /* owned by a client, or free? */
+ int age; /* tracked by clients to update local LRU's */
+} drm_i810_tex_region_t;
+
+typedef struct _drm_i810_sarea {
+ unsigned int ContextState[I810_CTX_SETUP_SIZE];
+ unsigned int BufferState[I810_DEST_SETUP_SIZE];
+ unsigned int TexState[2][I810_TEX_SETUP_SIZE];
+ unsigned int dirty;
+
+ unsigned int nbox;
+ drm_clip_rect_t boxes[I810_NR_SAREA_CLIPRECTS];
+
+ /* Maintain an LRU of contiguous regions of texture space. If
+ * you think you own a region of texture memory, and it has an
+ * age different to the one you set, then you are mistaken and
+ * it has been stolen by another client. If global texAge
+ * hasn't changed, there is no need to walk the list.
+ *
+ * These regions can be used as a proxy for the fine-grained
+ * texture information of other clients - by maintaining them
+ * in the same lru which is used to age their own textures,
+ * clients have an approximate lru for the whole of global
+ * texture space, and can make informed decisions as to which
+ * areas to kick out. There is no need to choose whether to
+ * kick out your own texture or someone else's - simply eject
+ * them all in LRU order.
+ */
+
+ drm_i810_tex_region_t texList[I810_NR_TEX_REGIONS+1];
+ /* Last elt is sentinal */
+ int texAge; /* last time texture was uploaded */
+ int last_enqueue; /* last time a buffer was enqueued */
+ int last_dispatch; /* age of the most recently dispatched buffer */
+ int last_quiescent; /* */
+ int ctxOwner; /* last context to upload state */
+
+ int vertex_prim;
+
+ int pf_enabled; /* is pageflipping allowed? */
+ int pf_active;
+ int pf_current_page; /* which buffer is being displayed? */
+} drm_i810_sarea_t;
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmMga.h)
+ */
+
+/* i810 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I810_INIT 0x00
+#define DRM_I810_VERTEX 0x01
+#define DRM_I810_CLEAR 0x02
+#define DRM_I810_FLUSH 0x03
+#define DRM_I810_GETAGE 0x04
+#define DRM_I810_GETBUF 0x05
+#define DRM_I810_SWAP 0x06
+#define DRM_I810_COPY 0x07
+#define DRM_I810_DOCOPY 0x08
+#define DRM_I810_OV0INFO 0x09
+#define DRM_I810_FSTATUS 0x0a
+#define DRM_I810_OV0FLIP 0x0b
+#define DRM_I810_MC 0x0c
+#define DRM_I810_RSTATUS 0x0d
+#define DRM_I810_FLIP 0x0e
+
+#define DRM_IOCTL_I810_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_I810_INIT, drm_i810_init_t)
+#define DRM_IOCTL_I810_VERTEX DRM_IOW( DRM_COMMAND_BASE + DRM_I810_VERTEX, drm_i810_vertex_t)
+#define DRM_IOCTL_I810_CLEAR DRM_IOW( DRM_COMMAND_BASE + DRM_I810_CLEAR, drm_i810_clear_t)
+#define DRM_IOCTL_I810_FLUSH DRM_IO( DRM_COMMAND_BASE + DRM_I810_FLUSH)
+#define DRM_IOCTL_I810_GETAGE DRM_IO( DRM_COMMAND_BASE + DRM_I810_GETAGE)
+#define DRM_IOCTL_I810_GETBUF DRM_IOWR(DRM_COMMAND_BASE + DRM_I810_GETBUF, drm_i810_dma_t)
+#define DRM_IOCTL_I810_SWAP DRM_IO( DRM_COMMAND_BASE + DRM_I810_SWAP)
+#define DRM_IOCTL_I810_COPY DRM_IOW( DRM_COMMAND_BASE + DRM_I810_COPY, drm_i810_copy_t)
+#define DRM_IOCTL_I810_DOCOPY DRM_IO( DRM_COMMAND_BASE + DRM_I810_DOCOPY)
+#define DRM_IOCTL_I810_OV0INFO DRM_IOR( DRM_COMMAND_BASE + DRM_I810_OV0INFO, drm_i810_overlay_t)
+#define DRM_IOCTL_I810_FSTATUS DRM_IO ( DRM_COMMAND_BASE + DRM_I810_FSTATUS)
+#define DRM_IOCTL_I810_OV0FLIP DRM_IO ( DRM_COMMAND_BASE + DRM_I810_OV0FLIP)
+#define DRM_IOCTL_I810_MC DRM_IOW( DRM_COMMAND_BASE + DRM_I810_MC, drm_i810_mc_t)
+#define DRM_IOCTL_I810_RSTATUS DRM_IO ( DRM_COMMAND_BASE + DRM_I810_RSTATUS)
+#define DRM_IOCTL_I810_FLIP DRM_IO ( DRM_COMMAND_BASE + DRM_I810_FLIP)
+
+typedef struct _drm_i810_clear {
+ int clear_color;
+ int clear_depth;
+ int flags;
+} drm_i810_clear_t;
+
+/* These may be placeholders if we have more cliprects than
+ * I810_NR_SAREA_CLIPRECTS. In that case, the client sets discard to
+ * false, indicating that the buffer will be dispatched again with a
+ * new set of cliprects.
+ */
+typedef struct _drm_i810_vertex {
+ int idx; /* buffer index */
+ int used; /* nr bytes in use */
+ int discard; /* client is finished with the buffer? */
+} drm_i810_vertex_t;
+
+typedef struct _drm_i810_copy_t {
+ int idx; /* buffer index */
+ int used; /* nr bytes in use */
+ void *address; /* Address to copy from */
+} drm_i810_copy_t;
+
+#define PR_TRIANGLES (0x0<<18)
+#define PR_TRISTRIP_0 (0x1<<18)
+#define PR_TRISTRIP_1 (0x2<<18)
+#define PR_TRIFAN (0x3<<18)
+#define PR_POLYGON (0x4<<18)
+#define PR_LINES (0x5<<18)
+#define PR_LINESTRIP (0x6<<18)
+#define PR_RECTS (0x7<<18)
+#define PR_MASK (0x7<<18)
+
+
+typedef struct drm_i810_dma {
+ void *virtual;
+ int request_idx;
+ int request_size;
+ int granted;
+} drm_i810_dma_t;
+
+typedef struct _drm_i810_overlay_t {
+ unsigned int offset; /* Address of the Overlay Regs */
+ unsigned int physical;
+} drm_i810_overlay_t;
+
+typedef struct _drm_i810_mc {
+ int idx; /* buffer index */
+ int used; /* nr bytes in use */
+ int num_blocks; /* number of GFXBlocks */
+ int *length; /* List of lengths for GFXBlocks (FUTURE)*/
+ unsigned int last_render; /* Last Render Request */
+} drm_i810_mc_t;
+
+
+#endif /* _I810_DRM_H_ */
diff --git a/drivers/char/drm/i810_drv.c b/drivers/char/drm/i810_drv.c
new file mode 100644
index 000000000000..ff51b3259af9
--- /dev/null
+++ b/drivers/char/drm/i810_drv.c
@@ -0,0 +1,126 @@
+/* i810_drv.c -- I810 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "i810_drm.h"
+#include "i810_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ /* i810 has 4 more counters */
+ dev->counters += 4;
+ dev->types[6] = _DRM_STAT_IRQ;
+ dev->types[7] = _DRM_STAT_PRIMARY;
+ dev->types[8] = _DRM_STAT_SECONDARY;
+ dev->types[9] = _DRM_STAT_DMA;
+
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ i810_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i810_ioctls[];
+extern int i810_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_DMA_QUEUE,
+ .dev_priv_size = sizeof(drm_i810_buf_priv_t),
+ .pretakedown = i810_driver_pretakedown,
+ .prerelease = i810_driver_prerelease,
+ .release = i810_driver_release,
+ .dma_quiescent = i810_driver_dma_quiescent,
+ .reclaim_buffers = i810_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = i810_ioctls,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ },
+};
+
+static int __init i810_init(void)
+{
+ driver.num_ioctls = i810_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit i810_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(i810_init);
+module_exit(i810_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i810_drv.h b/drivers/char/drm/i810_drv.h
new file mode 100644
index 000000000000..fa23ca454e57
--- /dev/null
+++ b/drivers/char/drm/i810_drv.h
@@ -0,0 +1,236 @@
+/* i810_drv.h -- Private header for the Matrox g200/g400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ *
+ */
+
+#ifndef _I810_DRV_H_
+#define _I810_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "VA Linux Systems Inc."
+
+#define DRIVER_NAME "i810"
+#define DRIVER_DESC "Intel i810"
+#define DRIVER_DATE "20030605"
+
+/* Interface history
+ *
+ * 1.1 - XFree86 4.1
+ * 1.2 - XvMC interfaces
+ * - XFree86 4.2
+ * 1.2.1 - Disable copying code (leave stub ioctls for backwards compatibility)
+ * - Remove requirement for interrupt (leave stubs again)
+ * 1.3 - Add page flipping.
+ * 1.4 - fix DRM interface
+ */
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 4
+#define DRIVER_PATCHLEVEL 0
+
+typedef struct drm_i810_buf_priv {
+ u32 *in_use;
+ int my_use_idx;
+ int currently_mapped;
+ void *virtual;
+ void *kernel_virtual;
+} drm_i810_buf_priv_t;
+
+typedef struct _drm_i810_ring_buffer{
+ int tail_mask;
+ unsigned long Start;
+ unsigned long End;
+ unsigned long Size;
+ u8 *virtual_start;
+ int head;
+ int tail;
+ int space;
+} drm_i810_ring_buffer_t;
+
+typedef struct drm_i810_private {
+ drm_map_t *sarea_map;
+ drm_map_t *mmio_map;
+
+ drm_i810_sarea_t *sarea_priv;
+ drm_i810_ring_buffer_t ring;
+
+ void *hw_status_page;
+ unsigned long counter;
+
+ dma_addr_t dma_status_page;
+
+ drm_buf_t *mmap_buffer;
+
+
+ u32 front_di1, back_di1, zi1;
+
+ int back_offset;
+ int depth_offset;
+ int overlay_offset;
+ int overlay_physical;
+ int w, h;
+ int pitch;
+ int back_pitch;
+ int depth_pitch;
+
+ int do_boxes;
+ int dma_used;
+
+ int current_page;
+ int page_flipping;
+
+ wait_queue_head_t irq_queue;
+ atomic_t irq_received;
+ atomic_t irq_emitted;
+
+ int front_offset;
+} drm_i810_private_t;
+
+ /* i810_dma.c */
+extern void i810_reclaim_buffers(drm_device_t *dev, struct file *filp);
+extern int i810_mmap_buffers(struct file *filp, struct vm_area_struct *vma);
+
+extern int i810_driver_dma_quiescent(drm_device_t *dev);
+extern void i810_driver_release(drm_device_t *dev, struct file *filp);
+extern void i810_driver_pretakedown(drm_device_t *dev);
+extern void i810_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+#define I810_BASE(reg) ((unsigned long) \
+ dev_priv->mmio_map->handle)
+#define I810_ADDR(reg) (I810_BASE(reg) + reg)
+#define I810_DEREF(reg) *(__volatile__ int *)I810_ADDR(reg)
+#define I810_READ(reg) I810_DEREF(reg)
+#define I810_WRITE(reg,val) do { I810_DEREF(reg) = val; } while (0)
+#define I810_DEREF16(reg) *(__volatile__ u16 *)I810_ADDR(reg)
+#define I810_READ16(reg) I810_DEREF16(reg)
+#define I810_WRITE16(reg,val) do { I810_DEREF16(reg) = val; } while (0)
+
+#define I810_VERBOSE 0
+#define RING_LOCALS unsigned int outring, ringmask; \
+ volatile char *virt;
+
+#define BEGIN_LP_RING(n) do { \
+ if (I810_VERBOSE) \
+ DRM_DEBUG("BEGIN_LP_RING(%d) in %s\n", n, __FUNCTION__); \
+ if (dev_priv->ring.space < n*4) \
+ i810_wait_ring(dev, n*4); \
+ dev_priv->ring.space -= n*4; \
+ outring = dev_priv->ring.tail; \
+ ringmask = dev_priv->ring.tail_mask; \
+ virt = dev_priv->ring.virtual_start; \
+} while (0)
+
+#define ADVANCE_LP_RING() do { \
+ if (I810_VERBOSE) DRM_DEBUG("ADVANCE_LP_RING\n"); \
+ dev_priv->ring.tail = outring; \
+ I810_WRITE(LP_RING + RING_TAIL, outring); \
+} while(0)
+
+#define OUT_RING(n) do { \
+ if (I810_VERBOSE) DRM_DEBUG(" OUT_RING %x\n", (int)(n)); \
+ *(volatile unsigned int *)(virt + outring) = n; \
+ outring += 4; \
+ outring &= ringmask; \
+} while (0)
+
+#define GFX_OP_USER_INTERRUPT ((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT ((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD (7<<23)
+#define CMD_STORE_DWORD_IDX ((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER ((0x0<<29)|(0x30<<23)|0x1)
+
+#define INST_PARSER_CLIENT 0x00000000
+#define INST_OP_FLUSH 0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+
+#define BB1_START_ADDR_MASK (~0x7)
+#define BB1_PROTECTED (1<<0)
+#define BB1_UNPROTECTED (0<<0)
+#define BB2_END_ADDR_MASK (~0x7)
+
+#define I810REG_HWSTAM 0x02098
+#define I810REG_INT_IDENTITY_R 0x020a4
+#define I810REG_INT_MASK_R 0x020a8
+#define I810REG_INT_ENABLE_R 0x020a0
+
+#define LP_RING 0x2030
+#define HP_RING 0x2040
+#define RING_TAIL 0x00
+#define TAIL_ADDR 0x000FFFF8
+#define RING_HEAD 0x04
+#define HEAD_WRAP_COUNT 0xFFE00000
+#define HEAD_WRAP_ONE 0x00200000
+#define HEAD_ADDR 0x001FFFFC
+#define RING_START 0x08
+#define START_ADDR 0x00FFFFF8
+#define RING_LEN 0x0C
+#define RING_NR_PAGES 0x000FF000
+#define RING_REPORT_MASK 0x00000006
+#define RING_REPORT_64K 0x00000002
+#define RING_REPORT_128K 0x00000004
+#define RING_NO_REPORT 0x00000000
+#define RING_VALID_MASK 0x00000001
+#define RING_VALID 0x00000001
+#define RING_INVALID 0x00000000
+
+#define GFX_OP_SCISSOR ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR (0x1<<1)
+#define SC_ENABLE_MASK (0x1<<0)
+#define SC_ENABLE (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK (0xffff<<16)
+#define SCI_XMIN_MASK (0xffff<<0)
+#define SCI_YMAX_MASK (0xffff<<16)
+#define SCI_XMAX_MASK (0xffff<<0)
+
+#define GFX_OP_COLOR_FACTOR ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO ((0x3<<29)|(0x1d<<24)|0x2)
+#define GFX_OP_DESTBUFFER_VARS ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+#define GFX_OP_PRIMITIVE ((0x3<<29)|(0x1f<<24))
+
+#define CMD_OP_Z_BUFFER_INFO ((0x0<<29)|(0x16<<23))
+#define CMD_OP_DESTBUFFER_INFO ((0x0<<29)|(0x15<<23))
+#define CMD_OP_FRONTBUFFER_INFO ((0x0<<29)|(0x14<<23))
+#define CMD_OP_WAIT_FOR_EVENT ((0x0<<29)|(0x03<<23))
+
+#define BR00_BITBLT_CLIENT 0x40000000
+#define BR00_OP_COLOR_BLT 0x10000000
+#define BR00_OP_SRC_COPY_BLT 0x10C00000
+#define BR13_SOLID_PATTERN 0x80000000
+
+#define WAIT_FOR_PLANE_A_SCANLINES (1<<1)
+#define WAIT_FOR_PLANE_A_FLIP (1<<2)
+#define WAIT_FOR_VBLANK (1<<3)
+
+#endif
diff --git a/drivers/char/drm/i830_dma.c b/drivers/char/drm/i830_dma.c
new file mode 100644
index 000000000000..98adccf8e434
--- /dev/null
+++ b/drivers/char/drm/i830_dma.c
@@ -0,0 +1,1588 @@
+/* i830_dma.c -- DMA support for the I830 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ * Abraham vd Merwe <abraham@2d3d.co.za>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+#include <linux/interrupt.h> /* For task queue support */
+#include <linux/pagemap.h> /* For FASTCALL on unlock_page() */
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+
+#define I830_BUF_FREE 2
+#define I830_BUF_CLIENT 1
+#define I830_BUF_HARDWARE 0
+
+#define I830_BUF_UNMAPPED 0
+#define I830_BUF_MAPPED 1
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,2)
+#define down_write down
+#define up_write up
+#endif
+
+static drm_buf_t *i830_freelist_get(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+ int used;
+
+ /* Linear search might not be the best solution */
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ /* In use is already a pointer */
+ used = cmpxchg(buf_priv->in_use, I830_BUF_FREE,
+ I830_BUF_CLIENT);
+ if(used == I830_BUF_FREE) {
+ return buf;
+ }
+ }
+ return NULL;
+}
+
+/* This should only be called if the buffer is not sent to the hardware
+ * yet, the hardware updates in use for us once its on the ring buffer.
+ */
+
+static int i830_freelist_put(drm_device_t *dev, drm_buf_t *buf)
+{
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ int used;
+
+ /* In use is already a pointer */
+ used = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT, I830_BUF_FREE);
+ if(used != I830_BUF_CLIENT) {
+ DRM_ERROR("Freeing buffer thats not in use : %d\n", buf->idx);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct file_operations i830_buffer_fops = {
+ .open = drm_open,
+ .flush = drm_flush,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = i830_mmap_buffers,
+ .fasync = drm_fasync,
+};
+
+int i830_mmap_buffers(struct file *filp, struct vm_area_struct *vma)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev;
+ drm_i830_private_t *dev_priv;
+ drm_buf_t *buf;
+ drm_i830_buf_priv_t *buf_priv;
+
+ lock_kernel();
+ dev = priv->head->dev;
+ dev_priv = dev->dev_private;
+ buf = dev_priv->mmap_buffer;
+ buf_priv = buf->dev_private;
+
+ vma->vm_flags |= (VM_IO | VM_DONTCOPY);
+ vma->vm_file = filp;
+
+ buf_priv->currently_mapped = I830_BUF_MAPPED;
+ unlock_kernel();
+
+ if (io_remap_pfn_range(vma, vma->vm_start,
+ VM_OFFSET(vma) >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot)) return -EAGAIN;
+ return 0;
+}
+
+static int i830_map_buffer(drm_buf_t *buf, struct file *filp)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ struct file_operations *old_fops;
+ unsigned long virtual;
+ int retcode = 0;
+
+ if(buf_priv->currently_mapped == I830_BUF_MAPPED) return -EINVAL;
+
+ down_write( &current->mm->mmap_sem );
+ old_fops = filp->f_op;
+ filp->f_op = &i830_buffer_fops;
+ dev_priv->mmap_buffer = buf;
+ virtual = do_mmap(filp, 0, buf->total, PROT_READ|PROT_WRITE,
+ MAP_SHARED, buf->bus_address);
+ dev_priv->mmap_buffer = NULL;
+ filp->f_op = old_fops;
+ if (IS_ERR((void *)virtual)) { /* ugh */
+ /* Real error */
+ DRM_ERROR("mmap error\n");
+ retcode = virtual;
+ buf_priv->virtual = NULL;
+ } else {
+ buf_priv->virtual = (void __user *)virtual;
+ }
+ up_write( &current->mm->mmap_sem );
+
+ return retcode;
+}
+
+static int i830_unmap_buffer(drm_buf_t *buf)
+{
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ int retcode = 0;
+
+ if(buf_priv->currently_mapped != I830_BUF_MAPPED)
+ return -EINVAL;
+
+ down_write(&current->mm->mmap_sem);
+ retcode = do_munmap(current->mm,
+ (unsigned long)buf_priv->virtual,
+ (size_t) buf->total);
+ up_write(&current->mm->mmap_sem);
+
+ buf_priv->currently_mapped = I830_BUF_UNMAPPED;
+ buf_priv->virtual = NULL;
+
+ return retcode;
+}
+
+static int i830_dma_get_buffer(drm_device_t *dev, drm_i830_dma_t *d,
+ struct file *filp)
+{
+ drm_buf_t *buf;
+ drm_i830_buf_priv_t *buf_priv;
+ int retcode = 0;
+
+ buf = i830_freelist_get(dev);
+ if (!buf) {
+ retcode = -ENOMEM;
+ DRM_DEBUG("retcode=%d\n", retcode);
+ return retcode;
+ }
+
+ retcode = i830_map_buffer(buf, filp);
+ if(retcode) {
+ i830_freelist_put(dev, buf);
+ DRM_ERROR("mapbuf failed, retcode %d\n", retcode);
+ return retcode;
+ }
+ buf->filp = filp;
+ buf_priv = buf->dev_private;
+ d->granted = 1;
+ d->request_idx = buf->idx;
+ d->request_size = buf->total;
+ d->virtual = buf_priv->virtual;
+
+ return retcode;
+}
+
+static int i830_dma_cleanup(drm_device_t *dev)
+{
+ drm_device_dma_t *dma = dev->dma;
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+ if (dev->dev_private) {
+ int i;
+ drm_i830_private_t *dev_priv =
+ (drm_i830_private_t *) dev->dev_private;
+
+ if (dev_priv->ring.virtual_start) {
+ drm_ioremapfree((void *) dev_priv->ring.virtual_start,
+ dev_priv->ring.Size, dev);
+ }
+ if (dev_priv->hw_status_page) {
+ pci_free_consistent(dev->pdev, PAGE_SIZE,
+ dev_priv->hw_status_page,
+ dev_priv->dma_status_page);
+ /* Need to rewrite hardware status page */
+ I830_WRITE(0x02080, 0x1ffff000);
+ }
+
+ drm_free(dev->dev_private, sizeof(drm_i830_private_t),
+ DRM_MEM_DRIVER);
+ dev->dev_private = NULL;
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ if ( buf_priv->kernel_virtual && buf->total )
+ drm_ioremapfree(buf_priv->kernel_virtual, buf->total, dev);
+ }
+ }
+ return 0;
+}
+
+int i830_wait_ring(drm_device_t *dev, int n, const char *caller)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_ring_buffer_t *ring = &(dev_priv->ring);
+ int iters = 0;
+ unsigned long end;
+ unsigned int last_head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+
+ end = jiffies + (HZ*3);
+ while (ring->space < n) {
+ ring->head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->space = ring->head - (ring->tail+8);
+ if (ring->space < 0) ring->space += ring->Size;
+
+ if (ring->head != last_head) {
+ end = jiffies + (HZ*3);
+ last_head = ring->head;
+ }
+
+ iters++;
+ if(time_before(end, jiffies)) {
+ DRM_ERROR("space: %d wanted %d\n", ring->space, n);
+ DRM_ERROR("lockup\n");
+ goto out_wait_ring;
+ }
+ udelay(1);
+ dev_priv->sarea_priv->perf_boxes |= I830_BOX_WAIT;
+ }
+
+out_wait_ring:
+ return iters;
+}
+
+static void i830_kernel_lost_context(drm_device_t *dev)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_ring_buffer_t *ring = &(dev_priv->ring);
+
+ ring->head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->tail = I830_READ(LP_RING + RING_TAIL) & TAIL_ADDR;
+ ring->space = ring->head - (ring->tail+8);
+ if (ring->space < 0) ring->space += ring->Size;
+
+ if (ring->head == ring->tail)
+ dev_priv->sarea_priv->perf_boxes |= I830_BOX_RING_EMPTY;
+}
+
+static int i830_freelist_init(drm_device_t *dev, drm_i830_private_t *dev_priv)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int my_idx = 36;
+ u32 *hw_status = (u32 *)(dev_priv->hw_status_page + my_idx);
+ int i;
+
+ if(dma->buf_count > 1019) {
+ /* Not enough space in the status page for the freelist */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+
+ buf_priv->in_use = hw_status++;
+ buf_priv->my_use_idx = my_idx;
+ my_idx += 4;
+
+ *buf_priv->in_use = I830_BUF_FREE;
+
+ buf_priv->kernel_virtual = drm_ioremap(buf->bus_address,
+ buf->total, dev);
+ }
+ return 0;
+}
+
+static int i830_dma_initialize(drm_device_t *dev,
+ drm_i830_private_t *dev_priv,
+ drm_i830_init_t *init)
+{
+ struct list_head *list;
+
+ memset(dev_priv, 0, sizeof(drm_i830_private_t));
+
+ list_for_each(list, &dev->maplist->head) {
+ drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+ if( r_list->map &&
+ r_list->map->type == _DRM_SHM &&
+ r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+ dev_priv->sarea_map = r_list->map;
+ break;
+ }
+ }
+
+ if(!dev_priv->sarea_map) {
+ dev->dev_private = (void *)dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("can not find sarea!\n");
+ return -EINVAL;
+ }
+ dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+ if(!dev_priv->mmio_map) {
+ dev->dev_private = (void *)dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("can not find mmio map!\n");
+ return -EINVAL;
+ }
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ if(!dev->agp_buffer_map) {
+ dev->dev_private = (void *)dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("can not find dma buffer map!\n");
+ return -EINVAL;
+ }
+
+ dev_priv->sarea_priv = (drm_i830_sarea_t *)
+ ((u8 *)dev_priv->sarea_map->handle +
+ init->sarea_priv_offset);
+
+ dev_priv->ring.Start = init->ring_start;
+ dev_priv->ring.End = init->ring_end;
+ dev_priv->ring.Size = init->ring_size;
+
+ dev_priv->ring.virtual_start = drm_ioremap(dev->agp->base +
+ init->ring_start,
+ init->ring_size, dev);
+
+ if (dev_priv->ring.virtual_start == NULL) {
+ dev->dev_private = (void *) dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("can not ioremap virtual address for"
+ " ring buffer\n");
+ return -ENOMEM;
+ }
+
+ dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+
+ dev_priv->w = init->w;
+ dev_priv->h = init->h;
+ dev_priv->pitch = init->pitch;
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->depth_offset = init->depth_offset;
+ dev_priv->front_offset = init->front_offset;
+
+ dev_priv->front_di1 = init->front_offset | init->pitch_bits;
+ dev_priv->back_di1 = init->back_offset | init->pitch_bits;
+ dev_priv->zi1 = init->depth_offset | init->pitch_bits;
+
+ DRM_DEBUG("front_di1 %x\n", dev_priv->front_di1);
+ DRM_DEBUG("back_offset %x\n", dev_priv->back_offset);
+ DRM_DEBUG("back_di1 %x\n", dev_priv->back_di1);
+ DRM_DEBUG("pitch_bits %x\n", init->pitch_bits);
+
+ dev_priv->cpp = init->cpp;
+ /* We are using separate values as placeholders for mechanisms for
+ * private backbuffer/depthbuffer usage.
+ */
+
+ dev_priv->back_pitch = init->back_pitch;
+ dev_priv->depth_pitch = init->depth_pitch;
+ dev_priv->do_boxes = 0;
+ dev_priv->use_mi_batchbuffer_start = 0;
+
+ /* Program Hardware Status Page */
+ dev_priv->hw_status_page =
+ pci_alloc_consistent(dev->pdev, PAGE_SIZE,
+ &dev_priv->dma_status_page);
+ if (!dev_priv->hw_status_page) {
+ dev->dev_private = (void *)dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("Can not allocate hardware status page\n");
+ return -ENOMEM;
+ }
+ memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+ DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+ I830_WRITE(0x02080, dev_priv->dma_status_page);
+ DRM_DEBUG("Enabled hardware status page\n");
+
+ /* Now we need to init our freelist */
+ if(i830_freelist_init(dev, dev_priv) != 0) {
+ dev->dev_private = (void *)dev_priv;
+ i830_dma_cleanup(dev);
+ DRM_ERROR("Not enough space in the status page for"
+ " the freelist\n");
+ return -ENOMEM;
+ }
+ dev->dev_private = (void *)dev_priv;
+
+ return 0;
+}
+
+static int i830_dma_init(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv;
+ drm_i830_init_t init;
+ int retcode = 0;
+
+ if (copy_from_user(&init, (void * __user) arg, sizeof(init)))
+ return -EFAULT;
+
+ switch(init.func) {
+ case I830_INIT_DMA:
+ dev_priv = drm_alloc(sizeof(drm_i830_private_t),
+ DRM_MEM_DRIVER);
+ if(dev_priv == NULL) return -ENOMEM;
+ retcode = i830_dma_initialize(dev, dev_priv, &init);
+ break;
+ case I830_CLEANUP_DMA:
+ retcode = i830_dma_cleanup(dev);
+ break;
+ default:
+ retcode = -EINVAL;
+ break;
+ }
+
+ return retcode;
+}
+
+#define GFX_OP_STIPPLE ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define ST1_ENABLE (1<<16)
+#define ST1_MASK (0xffff)
+
+/* Most efficient way to verify state for the i830 is as it is
+ * emitted. Non-conformant state is silently dropped.
+ */
+static void i830EmitContextVerified( drm_device_t *dev,
+ unsigned int *code )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ int i, j = 0;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( I830_CTX_SETUP_SIZE + 4 );
+
+ for ( i = 0 ; i < I830_CTXREG_BLENDCOLR0 ; i++ ) {
+ tmp = code[i];
+ if ((tmp & (7<<29)) == CMD_3D &&
+ (tmp & (0x1f<<24)) < (0x1d<<24)) {
+ OUT_RING( tmp );
+ j++;
+ } else {
+ DRM_ERROR("Skipping %d\n", i);
+ }
+ }
+
+ OUT_RING( STATE3D_CONST_BLEND_COLOR_CMD );
+ OUT_RING( code[I830_CTXREG_BLENDCOLR] );
+ j += 2;
+
+ for ( i = I830_CTXREG_VF ; i < I830_CTXREG_MCSB0 ; i++ ) {
+ tmp = code[i];
+ if ((tmp & (7<<29)) == CMD_3D &&
+ (tmp & (0x1f<<24)) < (0x1d<<24)) {
+ OUT_RING( tmp );
+ j++;
+ } else {
+ DRM_ERROR("Skipping %d\n", i);
+ }
+ }
+
+ OUT_RING( STATE3D_MAP_COORD_SETBIND_CMD );
+ OUT_RING( code[I830_CTXREG_MCSB1] );
+ j += 2;
+
+ if (j & 1)
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+static void i830EmitTexVerified( drm_device_t *dev, unsigned int *code )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ int i, j = 0;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ if (code[I830_TEXREG_MI0] == GFX_OP_MAP_INFO ||
+ (code[I830_TEXREG_MI0] & ~(0xf*LOAD_TEXTURE_MAP0)) ==
+ (STATE3D_LOAD_STATE_IMMEDIATE_2|4)) {
+
+ BEGIN_LP_RING( I830_TEX_SETUP_SIZE );
+
+ OUT_RING( code[I830_TEXREG_MI0] ); /* TM0LI */
+ OUT_RING( code[I830_TEXREG_MI1] ); /* TM0S0 */
+ OUT_RING( code[I830_TEXREG_MI2] ); /* TM0S1 */
+ OUT_RING( code[I830_TEXREG_MI3] ); /* TM0S2 */
+ OUT_RING( code[I830_TEXREG_MI4] ); /* TM0S3 */
+ OUT_RING( code[I830_TEXREG_MI5] ); /* TM0S4 */
+
+ for ( i = 6 ; i < I830_TEX_SETUP_SIZE ; i++ ) {
+ tmp = code[i];
+ OUT_RING( tmp );
+ j++;
+ }
+
+ if (j & 1)
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+ }
+ else
+ printk("rejected packet %x\n", code[0]);
+}
+
+static void i830EmitTexBlendVerified( drm_device_t *dev,
+ unsigned int *code,
+ unsigned int num)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ int i, j = 0;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ if (!num)
+ return;
+
+ BEGIN_LP_RING( num + 1 );
+
+ for ( i = 0 ; i < num ; i++ ) {
+ tmp = code[i];
+ OUT_RING( tmp );
+ j++;
+ }
+
+ if (j & 1)
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+static void i830EmitTexPalette( drm_device_t *dev,
+ unsigned int *palette,
+ int number,
+ int is_shared )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ int i;
+ RING_LOCALS;
+
+ return;
+
+ BEGIN_LP_RING( 258 );
+
+ if(is_shared == 1) {
+ OUT_RING(CMD_OP_MAP_PALETTE_LOAD |
+ MAP_PALETTE_NUM(0) |
+ MAP_PALETTE_BOTH);
+ } else {
+ OUT_RING(CMD_OP_MAP_PALETTE_LOAD | MAP_PALETTE_NUM(number));
+ }
+ for(i = 0; i < 256; i++) {
+ OUT_RING(palette[i]);
+ }
+ OUT_RING(0);
+ /* KW: WHERE IS THE ADVANCE_LP_RING? This is effectively a noop!
+ */
+}
+
+/* Need to do some additional checking when setting the dest buffer.
+ */
+static void i830EmitDestVerified( drm_device_t *dev,
+ unsigned int *code )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ unsigned int tmp;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( I830_DEST_SETUP_SIZE + 10 );
+
+
+ tmp = code[I830_DESTREG_CBUFADDR];
+ if (tmp == dev_priv->front_di1 || tmp == dev_priv->back_di1) {
+ if (((int)outring) & 8) {
+ OUT_RING(0);
+ OUT_RING(0);
+ }
+
+ OUT_RING( CMD_OP_DESTBUFFER_INFO );
+ OUT_RING( BUF_3D_ID_COLOR_BACK |
+ BUF_3D_PITCH(dev_priv->back_pitch * dev_priv->cpp) |
+ BUF_3D_USE_FENCE);
+ OUT_RING( tmp );
+ OUT_RING( 0 );
+
+ OUT_RING( CMD_OP_DESTBUFFER_INFO );
+ OUT_RING( BUF_3D_ID_DEPTH | BUF_3D_USE_FENCE |
+ BUF_3D_PITCH(dev_priv->depth_pitch * dev_priv->cpp));
+ OUT_RING( dev_priv->zi1 );
+ OUT_RING( 0 );
+ } else {
+ DRM_ERROR("bad di1 %x (allow %x or %x)\n",
+ tmp, dev_priv->front_di1, dev_priv->back_di1);
+ }
+
+ /* invarient:
+ */
+
+
+ OUT_RING( GFX_OP_DESTBUFFER_VARS );
+ OUT_RING( code[I830_DESTREG_DV1] );
+
+ OUT_RING( GFX_OP_DRAWRECT_INFO );
+ OUT_RING( code[I830_DESTREG_DR1] );
+ OUT_RING( code[I830_DESTREG_DR2] );
+ OUT_RING( code[I830_DESTREG_DR3] );
+ OUT_RING( code[I830_DESTREG_DR4] );
+
+ /* Need to verify this */
+ tmp = code[I830_DESTREG_SENABLE];
+ if((tmp & ~0x3) == GFX_OP_SCISSOR_ENABLE) {
+ OUT_RING( tmp );
+ } else {
+ DRM_ERROR("bad scissor enable\n");
+ OUT_RING( 0 );
+ }
+
+ OUT_RING( GFX_OP_SCISSOR_RECT );
+ OUT_RING( code[I830_DESTREG_SR1] );
+ OUT_RING( code[I830_DESTREG_SR2] );
+ OUT_RING( 0 );
+
+ ADVANCE_LP_RING();
+}
+
+static void i830EmitStippleVerified( drm_device_t *dev,
+ unsigned int *code )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ BEGIN_LP_RING( 2 );
+ OUT_RING( GFX_OP_STIPPLE );
+ OUT_RING( code[1] );
+ ADVANCE_LP_RING();
+}
+
+
+static void i830EmitState( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+
+ DRM_DEBUG("%s %x\n", __FUNCTION__, dirty);
+
+ if (dirty & I830_UPLOAD_BUFFERS) {
+ i830EmitDestVerified( dev, sarea_priv->BufferState );
+ sarea_priv->dirty &= ~I830_UPLOAD_BUFFERS;
+ }
+
+ if (dirty & I830_UPLOAD_CTX) {
+ i830EmitContextVerified( dev, sarea_priv->ContextState );
+ sarea_priv->dirty &= ~I830_UPLOAD_CTX;
+ }
+
+ if (dirty & I830_UPLOAD_TEX0) {
+ i830EmitTexVerified( dev, sarea_priv->TexState[0] );
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX0;
+ }
+
+ if (dirty & I830_UPLOAD_TEX1) {
+ i830EmitTexVerified( dev, sarea_priv->TexState[1] );
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX1;
+ }
+
+ if (dirty & I830_UPLOAD_TEXBLEND0) {
+ i830EmitTexBlendVerified( dev, sarea_priv->TexBlendState[0],
+ sarea_priv->TexBlendStateWordsUsed[0]);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND0;
+ }
+
+ if (dirty & I830_UPLOAD_TEXBLEND1) {
+ i830EmitTexBlendVerified( dev, sarea_priv->TexBlendState[1],
+ sarea_priv->TexBlendStateWordsUsed[1]);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND1;
+ }
+
+ if (dirty & I830_UPLOAD_TEX_PALETTE_SHARED) {
+ i830EmitTexPalette(dev, sarea_priv->Palette[0], 0, 1);
+ } else {
+ if (dirty & I830_UPLOAD_TEX_PALETTE_N(0)) {
+ i830EmitTexPalette(dev, sarea_priv->Palette[0], 0, 0);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(0);
+ }
+ if (dirty & I830_UPLOAD_TEX_PALETTE_N(1)) {
+ i830EmitTexPalette(dev, sarea_priv->Palette[1], 1, 0);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(1);
+ }
+
+ /* 1.3:
+ */
+#if 0
+ if (dirty & I830_UPLOAD_TEX_PALETTE_N(2)) {
+ i830EmitTexPalette(dev, sarea_priv->Palette2[0], 0, 0);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(2);
+ }
+ if (dirty & I830_UPLOAD_TEX_PALETTE_N(3)) {
+ i830EmitTexPalette(dev, sarea_priv->Palette2[1], 1, 0);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(2);
+ }
+#endif
+ }
+
+ /* 1.3:
+ */
+ if (dirty & I830_UPLOAD_STIPPLE) {
+ i830EmitStippleVerified( dev,
+ sarea_priv->StippleState);
+ sarea_priv->dirty &= ~I830_UPLOAD_STIPPLE;
+ }
+
+ if (dirty & I830_UPLOAD_TEX2) {
+ i830EmitTexVerified( dev, sarea_priv->TexState2 );
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX2;
+ }
+
+ if (dirty & I830_UPLOAD_TEX3) {
+ i830EmitTexVerified( dev, sarea_priv->TexState3 );
+ sarea_priv->dirty &= ~I830_UPLOAD_TEX3;
+ }
+
+
+ if (dirty & I830_UPLOAD_TEXBLEND2) {
+ i830EmitTexBlendVerified(
+ dev,
+ sarea_priv->TexBlendState2,
+ sarea_priv->TexBlendStateWordsUsed2);
+
+ sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND2;
+ }
+
+ if (dirty & I830_UPLOAD_TEXBLEND3) {
+ i830EmitTexBlendVerified(
+ dev,
+ sarea_priv->TexBlendState3,
+ sarea_priv->TexBlendStateWordsUsed3);
+ sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND3;
+ }
+}
+
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void i830_fill_box( drm_device_t *dev,
+ int x, int y, int w, int h,
+ int r, int g, int b )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ u32 color;
+ unsigned int BR13, CMD;
+ RING_LOCALS;
+
+ BR13 = (0xF0 << 16) | (dev_priv->pitch * dev_priv->cpp) | (1<<24);
+ CMD = XY_COLOR_BLT_CMD;
+ x += dev_priv->sarea_priv->boxes[0].x1;
+ y += dev_priv->sarea_priv->boxes[0].y1;
+
+ if (dev_priv->cpp == 4) {
+ BR13 |= (1<<25);
+ CMD |= (XY_COLOR_BLT_WRITE_ALPHA | XY_COLOR_BLT_WRITE_RGB);
+ color = (((0xff) << 24) | (r << 16) | (g << 8) | b);
+ } else {
+ color = (((r & 0xf8) << 8) |
+ ((g & 0xfc) << 3) |
+ ((b & 0xf8) >> 3));
+ }
+
+ BEGIN_LP_RING( 6 );
+ OUT_RING( CMD );
+ OUT_RING( BR13 );
+ OUT_RING( (y << 16) | x );
+ OUT_RING( ((y+h) << 16) | (x+w) );
+
+ if ( dev_priv->current_page == 1 ) {
+ OUT_RING( dev_priv->front_offset );
+ } else {
+ OUT_RING( dev_priv->back_offset );
+ }
+
+ OUT_RING( color );
+ ADVANCE_LP_RING();
+}
+
+static void i830_cp_performance_boxes( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+
+ /* Purple box for page flipping
+ */
+ if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_FLIP )
+ i830_fill_box( dev, 4, 4, 8, 8, 255, 0, 255 );
+
+ /* Red box if we have to wait for idle at any point
+ */
+ if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_WAIT )
+ i830_fill_box( dev, 16, 4, 8, 8, 255, 0, 0 );
+
+ /* Blue box: lost context?
+ */
+ if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_LOST_CONTEXT )
+ i830_fill_box( dev, 28, 4, 8, 8, 0, 0, 255 );
+
+ /* Yellow box for texture swaps
+ */
+ if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_TEXTURE_LOAD )
+ i830_fill_box( dev, 40, 4, 8, 8, 255, 255, 0 );
+
+ /* Green box if hardware never idles (as far as we can tell)
+ */
+ if ( !(dev_priv->sarea_priv->perf_boxes & I830_BOX_RING_EMPTY) )
+ i830_fill_box( dev, 64, 4, 8, 8, 0, 255, 0 );
+
+
+ /* Draw bars indicating number of buffers allocated
+ * (not a great measure, easily confused)
+ */
+ if (dev_priv->dma_used) {
+ int bar = dev_priv->dma_used / 10240;
+ if (bar > 100) bar = 100;
+ if (bar < 1) bar = 1;
+ i830_fill_box( dev, 4, 16, bar, 4, 196, 128, 128 );
+ dev_priv->dma_used = 0;
+ }
+
+ dev_priv->sarea_priv->perf_boxes = 0;
+}
+
+static void i830_dma_dispatch_clear( drm_device_t *dev, int flags,
+ unsigned int clear_color,
+ unsigned int clear_zval,
+ unsigned int clear_depthmask)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int pitch = dev_priv->pitch;
+ int cpp = dev_priv->cpp;
+ int i;
+ unsigned int BR13, CMD, D_CMD;
+ RING_LOCALS;
+
+
+ if ( dev_priv->current_page == 1 ) {
+ unsigned int tmp = flags;
+
+ flags &= ~(I830_FRONT | I830_BACK);
+ if ( tmp & I830_FRONT ) flags |= I830_BACK;
+ if ( tmp & I830_BACK ) flags |= I830_FRONT;
+ }
+
+ i830_kernel_lost_context(dev);
+
+ switch(cpp) {
+ case 2:
+ BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24);
+ D_CMD = CMD = XY_COLOR_BLT_CMD;
+ break;
+ case 4:
+ BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24) | (1<<25);
+ CMD = (XY_COLOR_BLT_CMD | XY_COLOR_BLT_WRITE_ALPHA |
+ XY_COLOR_BLT_WRITE_RGB);
+ D_CMD = XY_COLOR_BLT_CMD;
+ if(clear_depthmask & 0x00ffffff)
+ D_CMD |= XY_COLOR_BLT_WRITE_RGB;
+ if(clear_depthmask & 0xff000000)
+ D_CMD |= XY_COLOR_BLT_WRITE_ALPHA;
+ break;
+ default:
+ BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24);
+ D_CMD = CMD = XY_COLOR_BLT_CMD;
+ break;
+ }
+
+ if (nbox > I830_NR_SAREA_CLIPRECTS)
+ nbox = I830_NR_SAREA_CLIPRECTS;
+
+ for (i = 0 ; i < nbox ; i++, pbox++) {
+ if (pbox->x1 > pbox->x2 ||
+ pbox->y1 > pbox->y2 ||
+ pbox->x2 > dev_priv->w ||
+ pbox->y2 > dev_priv->h)
+ continue;
+
+ if ( flags & I830_FRONT ) {
+ DRM_DEBUG("clear front\n");
+ BEGIN_LP_RING( 6 );
+ OUT_RING( CMD );
+ OUT_RING( BR13 );
+ OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+ OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+ OUT_RING( dev_priv->front_offset );
+ OUT_RING( clear_color );
+ ADVANCE_LP_RING();
+ }
+
+ if ( flags & I830_BACK ) {
+ DRM_DEBUG("clear back\n");
+ BEGIN_LP_RING( 6 );
+ OUT_RING( CMD );
+ OUT_RING( BR13 );
+ OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+ OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+ OUT_RING( dev_priv->back_offset );
+ OUT_RING( clear_color );
+ ADVANCE_LP_RING();
+ }
+
+ if ( flags & I830_DEPTH ) {
+ DRM_DEBUG("clear depth\n");
+ BEGIN_LP_RING( 6 );
+ OUT_RING( D_CMD );
+ OUT_RING( BR13 );
+ OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+ OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+ OUT_RING( dev_priv->depth_offset );
+ OUT_RING( clear_zval );
+ ADVANCE_LP_RING();
+ }
+ }
+}
+
+static void i830_dma_dispatch_swap( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int pitch = dev_priv->pitch;
+ int cpp = dev_priv->cpp;
+ int i;
+ unsigned int CMD, BR13;
+ RING_LOCALS;
+
+ DRM_DEBUG("swapbuffers\n");
+
+ i830_kernel_lost_context(dev);
+
+ if (dev_priv->do_boxes)
+ i830_cp_performance_boxes( dev );
+
+ switch(cpp) {
+ case 2:
+ BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24);
+ CMD = XY_SRC_COPY_BLT_CMD;
+ break;
+ case 4:
+ BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24) | (1<<25);
+ CMD = (XY_SRC_COPY_BLT_CMD | XY_SRC_COPY_BLT_WRITE_ALPHA |
+ XY_SRC_COPY_BLT_WRITE_RGB);
+ break;
+ default:
+ BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24);
+ CMD = XY_SRC_COPY_BLT_CMD;
+ break;
+ }
+
+
+ if (nbox > I830_NR_SAREA_CLIPRECTS)
+ nbox = I830_NR_SAREA_CLIPRECTS;
+
+ for (i = 0 ; i < nbox; i++, pbox++)
+ {
+ if (pbox->x1 > pbox->x2 ||
+ pbox->y1 > pbox->y2 ||
+ pbox->x2 > dev_priv->w ||
+ pbox->y2 > dev_priv->h)
+ continue;
+
+ DRM_DEBUG("dispatch swap %d,%d-%d,%d!\n",
+ pbox->x1, pbox->y1,
+ pbox->x2, pbox->y2);
+
+ BEGIN_LP_RING( 8 );
+ OUT_RING( CMD );
+ OUT_RING( BR13 );
+ OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+ OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+
+ if (dev_priv->current_page == 0)
+ OUT_RING( dev_priv->front_offset );
+ else
+ OUT_RING( dev_priv->back_offset );
+
+ OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+ OUT_RING( BR13 & 0xffff );
+
+ if (dev_priv->current_page == 0)
+ OUT_RING( dev_priv->back_offset );
+ else
+ OUT_RING( dev_priv->front_offset );
+
+ ADVANCE_LP_RING();
+ }
+}
+
+static void i830_dma_dispatch_flip( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n",
+ __FUNCTION__,
+ dev_priv->current_page,
+ dev_priv->sarea_priv->pf_current_page);
+
+ i830_kernel_lost_context(dev);
+
+ if (dev_priv->do_boxes) {
+ dev_priv->sarea_priv->perf_boxes |= I830_BOX_FLIP;
+ i830_cp_performance_boxes( dev );
+ }
+
+
+ BEGIN_LP_RING( 2 );
+ OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ BEGIN_LP_RING( 6 );
+ OUT_RING( CMD_OP_DISPLAYBUFFER_INFO | ASYNC_FLIP );
+ OUT_RING( 0 );
+ if ( dev_priv->current_page == 0 ) {
+ OUT_RING( dev_priv->back_offset );
+ dev_priv->current_page = 1;
+ } else {
+ OUT_RING( dev_priv->front_offset );
+ dev_priv->current_page = 0;
+ }
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+
+ BEGIN_LP_RING( 2 );
+ OUT_RING( MI_WAIT_FOR_EVENT |
+ MI_WAIT_FOR_PLANE_A_FLIP );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static void i830_dma_dispatch_vertex(drm_device_t *dev,
+ drm_buf_t *buf,
+ int discard,
+ int used)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+ drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_clip_rect_t *box = sarea_priv->boxes;
+ int nbox = sarea_priv->nbox;
+ unsigned long address = (unsigned long)buf->bus_address;
+ unsigned long start = address - dev->agp->base;
+ int i = 0, u;
+ RING_LOCALS;
+
+ i830_kernel_lost_context(dev);
+
+ if (nbox > I830_NR_SAREA_CLIPRECTS)
+ nbox = I830_NR_SAREA_CLIPRECTS;
+
+ if (discard) {
+ u = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT,
+ I830_BUF_HARDWARE);
+ if(u != I830_BUF_CLIENT) {
+ DRM_DEBUG("xxxx 2\n");
+ }
+ }
+
+ if (used > 4*1023)
+ used = 0;
+
+ if (sarea_priv->dirty)
+ i830EmitState( dev );
+
+ DRM_DEBUG("dispatch vertex addr 0x%lx, used 0x%x nbox %d\n",
+ address, used, nbox);
+
+ dev_priv->counter++;
+ DRM_DEBUG( "dispatch counter : %ld\n", dev_priv->counter);
+ DRM_DEBUG( "i830_dma_dispatch\n");
+ DRM_DEBUG( "start : %lx\n", start);
+ DRM_DEBUG( "used : %d\n", used);
+ DRM_DEBUG( "start + used - 4 : %ld\n", start + used - 4);
+
+ if (buf_priv->currently_mapped == I830_BUF_MAPPED) {
+ u32 *vp = buf_priv->kernel_virtual;
+
+ vp[0] = (GFX_OP_PRIMITIVE |
+ sarea_priv->vertex_prim |
+ ((used/4)-2));
+
+ if (dev_priv->use_mi_batchbuffer_start) {
+ vp[used/4] = MI_BATCH_BUFFER_END;
+ used += 4;
+ }
+
+ if (used & 4) {
+ vp[used/4] = 0;
+ used += 4;
+ }
+
+ i830_unmap_buffer(buf);
+ }
+
+ if (used) {
+ do {
+ if (i < nbox) {
+ BEGIN_LP_RING(6);
+ OUT_RING( GFX_OP_DRAWRECT_INFO );
+ OUT_RING( sarea_priv->BufferState[I830_DESTREG_DR1] );
+ OUT_RING( box[i].x1 | (box[i].y1<<16) );
+ OUT_RING( box[i].x2 | (box[i].y2<<16) );
+ OUT_RING( sarea_priv->BufferState[I830_DESTREG_DR4] );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+
+ if (dev_priv->use_mi_batchbuffer_start) {
+ BEGIN_LP_RING(2);
+ OUT_RING( MI_BATCH_BUFFER_START | (2<<6) );
+ OUT_RING( start | MI_BATCH_NON_SECURE );
+ ADVANCE_LP_RING();
+ }
+ else {
+ BEGIN_LP_RING(4);
+ OUT_RING( MI_BATCH_BUFFER );
+ OUT_RING( start | MI_BATCH_NON_SECURE );
+ OUT_RING( start + used - 4 );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+
+ } while (++i < nbox);
+ }
+
+ if (discard) {
+ dev_priv->counter++;
+
+ (void) cmpxchg(buf_priv->in_use, I830_BUF_CLIENT,
+ I830_BUF_HARDWARE);
+
+ BEGIN_LP_RING(8);
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( 20 );
+ OUT_RING( dev_priv->counter );
+ OUT_RING( CMD_STORE_DWORD_IDX );
+ OUT_RING( buf_priv->my_use_idx );
+ OUT_RING( I830_BUF_FREE );
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+ }
+}
+
+
+static void i830_dma_quiescent(drm_device_t *dev)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ i830_kernel_lost_context(dev);
+
+ BEGIN_LP_RING(4);
+ OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ i830_wait_ring( dev, dev_priv->ring.Size - 8, __FUNCTION__ );
+}
+
+static int i830_flush_queue(drm_device_t *dev)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ int i, ret = 0;
+ RING_LOCALS;
+
+ i830_kernel_lost_context(dev);
+
+ BEGIN_LP_RING(2);
+ OUT_RING( CMD_REPORT_HEAD );
+ OUT_RING( 0 );
+ ADVANCE_LP_RING();
+
+ i830_wait_ring( dev, dev_priv->ring.Size - 8, __FUNCTION__ );
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+
+ int used = cmpxchg(buf_priv->in_use, I830_BUF_HARDWARE,
+ I830_BUF_FREE);
+
+ if (used == I830_BUF_HARDWARE)
+ DRM_DEBUG("reclaimed from HARDWARE\n");
+ if (used == I830_BUF_CLIENT)
+ DRM_DEBUG("still on client\n");
+ }
+
+ return ret;
+}
+
+/* Must be called with the lock held */
+void i830_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+
+ if (!dma) return;
+ if (!dev->dev_private) return;
+ if (!dma->buflist) return;
+
+ i830_flush_queue(dev);
+
+ for (i = 0; i < dma->buf_count; i++) {
+ drm_buf_t *buf = dma->buflist[ i ];
+ drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+
+ if (buf->filp == filp && buf_priv) {
+ int used = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT,
+ I830_BUF_FREE);
+
+ if (used == I830_BUF_CLIENT)
+ DRM_DEBUG("reclaimed from client\n");
+ if(buf_priv->currently_mapped == I830_BUF_MAPPED)
+ buf_priv->currently_mapped = I830_BUF_UNMAPPED;
+ }
+ }
+}
+
+static int i830_flush_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ i830_flush_queue(dev);
+ return 0;
+}
+
+static int i830_dma_vertex(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_device_dma_t *dma = dev->dma;
+ drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *)
+ dev_priv->sarea_priv;
+ drm_i830_vertex_t vertex;
+
+ if (copy_from_user(&vertex, (drm_i830_vertex_t __user *)arg, sizeof(vertex)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ DRM_DEBUG("i830 dma vertex, idx %d used %d discard %d\n",
+ vertex.idx, vertex.used, vertex.discard);
+
+ if(vertex.idx < 0 || vertex.idx > dma->buf_count) return -EINVAL;
+
+ i830_dma_dispatch_vertex( dev,
+ dma->buflist[ vertex.idx ],
+ vertex.discard, vertex.used );
+
+ sarea_priv->last_enqueue = dev_priv->counter-1;
+ sarea_priv->last_dispatch = (int) hw_status[5];
+
+ return 0;
+}
+
+static int i830_clear_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_clear_t clear;
+
+ if (copy_from_user(&clear, (drm_i830_clear_t __user *)arg, sizeof(clear)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ /* GH: Someone's doing nasty things... */
+ if (!dev->dev_private) {
+ return -EINVAL;
+ }
+
+ i830_dma_dispatch_clear( dev, clear.flags,
+ clear.clear_color,
+ clear.clear_depth,
+ clear.clear_depthmask);
+ return 0;
+}
+
+static int i830_swap_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+
+ DRM_DEBUG("i830_swap_bufs\n");
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ i830_dma_dispatch_swap( dev );
+ return 0;
+}
+
+
+
+/* Not sure why this isn't set all the time:
+ */
+static void i830_do_init_pageflip( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+ dev_priv->page_flipping = 1;
+ dev_priv->current_page = 0;
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static int i830_do_cleanup_pageflip( drm_device_t *dev )
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+ if (dev_priv->current_page != 0)
+ i830_dma_dispatch_flip( dev );
+
+ dev_priv->page_flipping = 0;
+ return 0;
+}
+
+static int i830_flip_bufs(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (!dev_priv->page_flipping)
+ i830_do_init_pageflip( dev );
+
+ i830_dma_dispatch_flip( dev );
+ return 0;
+}
+
+static int i830_getage(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *)
+ dev_priv->sarea_priv;
+
+ sarea_priv->last_dispatch = (int) hw_status[5];
+ return 0;
+}
+
+static int i830_getbuf(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ int retcode = 0;
+ drm_i830_dma_t d;
+ drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *)
+ dev_priv->sarea_priv;
+
+ DRM_DEBUG("getbuf\n");
+ if (copy_from_user(&d, (drm_i830_dma_t __user *)arg, sizeof(d)))
+ return -EFAULT;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ d.granted = 0;
+
+ retcode = i830_dma_get_buffer(dev, &d, filp);
+
+ DRM_DEBUG("i830_dma: %d returning %d, granted = %d\n",
+ current->pid, retcode, d.granted);
+
+ if (copy_to_user((drm_dma_t __user *)arg, &d, sizeof(d)))
+ return -EFAULT;
+ sarea_priv->last_dispatch = (int) hw_status[5];
+
+ return retcode;
+}
+
+static int i830_copybuf(struct inode *inode,
+ struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ /* Never copy - 2.4.x doesn't need it */
+ return 0;
+}
+
+static int i830_docopy(struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ return 0;
+}
+
+
+
+static int i830_getparam( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_getparam_t param;
+ int value;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return -EINVAL;
+ }
+
+ if (copy_from_user(&param, (drm_i830_getparam_t __user *)arg, sizeof(param) ))
+ return -EFAULT;
+
+ switch( param.param ) {
+ case I830_PARAM_IRQ_ACTIVE:
+ value = dev->irq_enabled;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ( copy_to_user( param.value, &value, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+
+static int i830_setparam( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_setparam_t param;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return -EINVAL;
+ }
+
+ if (copy_from_user(&param, (drm_i830_setparam_t __user *)arg, sizeof(param) ))
+ return -EFAULT;
+
+ switch( param.param ) {
+ case I830_SETPARAM_USE_MI_BATCHBUFFER_START:
+ dev_priv->use_mi_batchbuffer_start = param.value;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+void i830_driver_pretakedown(drm_device_t *dev)
+{
+ i830_dma_cleanup( dev );
+}
+
+void i830_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+ if (dev->dev_private) {
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ if (dev_priv->page_flipping) {
+ i830_do_cleanup_pageflip(dev);
+ }
+ }
+}
+
+void i830_driver_release(drm_device_t *dev, struct file *filp)
+{
+ i830_reclaim_buffers(dev, filp);
+}
+
+int i830_driver_dma_quiescent(drm_device_t *dev)
+{
+ i830_dma_quiescent( dev );
+ return 0;
+}
+
+drm_ioctl_desc_t i830_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_I830_INIT)] = { i830_dma_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_I830_VERTEX)] = { i830_dma_vertex, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_CLEAR)] = { i830_clear_bufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_FLUSH)] = { i830_flush_ioctl, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_GETAGE)] = { i830_getage, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_GETBUF)] = { i830_getbuf, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_SWAP)] = { i830_swap_bufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_COPY)] = { i830_copybuf, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_DOCOPY)] = { i830_docopy, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_FLIP)] = { i830_flip_bufs, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_IRQ_EMIT)] = { i830_irq_emit, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_IRQ_WAIT)] = { i830_irq_wait, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_GETPARAM)] = { i830_getparam, 1, 0 },
+ [DRM_IOCTL_NR(DRM_I830_SETPARAM)] = { i830_setparam, 1, 0 }
+};
+
+int i830_max_ioctl = DRM_ARRAY_SIZE(i830_ioctls);
diff --git a/drivers/char/drm/i830_drm.h b/drivers/char/drm/i830_drm.h
new file mode 100644
index 000000000000..03382c0beee3
--- /dev/null
+++ b/drivers/char/drm/i830_drm.h
@@ -0,0 +1,350 @@
+#ifndef _I830_DRM_H_
+#define _I830_DRM_H_
+
+/* WARNING: These defines must be the same as what the Xserver uses.
+ * if you change them, you must change the defines in the Xserver.
+ *
+ * KW: Actually, you can't ever change them because doing so would
+ * break backwards compatibility.
+ */
+
+#ifndef _I830_DEFINES_
+#define _I830_DEFINES_
+
+#define I830_DMA_BUF_ORDER 12
+#define I830_DMA_BUF_SZ (1<<I830_DMA_BUF_ORDER)
+#define I830_DMA_BUF_NR 256
+#define I830_NR_SAREA_CLIPRECTS 8
+
+/* Each region is a minimum of 64k, and there are at most 64 of them.
+ */
+#define I830_NR_TEX_REGIONS 64
+#define I830_LOG_MIN_TEX_REGION_SIZE 16
+
+/* KW: These aren't correct but someone set them to two and then
+ * released the module. Now we can't change them as doing so would
+ * break backwards compatibility.
+ */
+#define I830_TEXTURE_COUNT 2
+#define I830_TEXBLEND_COUNT I830_TEXTURE_COUNT
+
+#define I830_TEXBLEND_SIZE 12 /* (4 args + op) * 2 + COLOR_FACTOR */
+
+#define I830_UPLOAD_CTX 0x1
+#define I830_UPLOAD_BUFFERS 0x2
+#define I830_UPLOAD_CLIPRECTS 0x4
+#define I830_UPLOAD_TEX0_IMAGE 0x100 /* handled clientside */
+#define I830_UPLOAD_TEX0_CUBE 0x200 /* handled clientside */
+#define I830_UPLOAD_TEX1_IMAGE 0x400 /* handled clientside */
+#define I830_UPLOAD_TEX1_CUBE 0x800 /* handled clientside */
+#define I830_UPLOAD_TEX2_IMAGE 0x1000 /* handled clientside */
+#define I830_UPLOAD_TEX2_CUBE 0x2000 /* handled clientside */
+#define I830_UPLOAD_TEX3_IMAGE 0x4000 /* handled clientside */
+#define I830_UPLOAD_TEX3_CUBE 0x8000 /* handled clientside */
+#define I830_UPLOAD_TEX_N_IMAGE(n) (0x100 << (n * 2))
+#define I830_UPLOAD_TEX_N_CUBE(n) (0x200 << (n * 2))
+#define I830_UPLOAD_TEXIMAGE_MASK 0xff00
+#define I830_UPLOAD_TEX0 0x10000
+#define I830_UPLOAD_TEX1 0x20000
+#define I830_UPLOAD_TEX2 0x40000
+#define I830_UPLOAD_TEX3 0x80000
+#define I830_UPLOAD_TEX_N(n) (0x10000 << (n))
+#define I830_UPLOAD_TEX_MASK 0xf0000
+#define I830_UPLOAD_TEXBLEND0 0x100000
+#define I830_UPLOAD_TEXBLEND1 0x200000
+#define I830_UPLOAD_TEXBLEND2 0x400000
+#define I830_UPLOAD_TEXBLEND3 0x800000
+#define I830_UPLOAD_TEXBLEND_N(n) (0x100000 << (n))
+#define I830_UPLOAD_TEXBLEND_MASK 0xf00000
+#define I830_UPLOAD_TEX_PALETTE_N(n) (0x1000000 << (n))
+#define I830_UPLOAD_TEX_PALETTE_SHARED 0x4000000
+#define I830_UPLOAD_STIPPLE 0x8000000
+
+/* Indices into buf.Setup where various bits of state are mirrored per
+ * context and per buffer. These can be fired at the card as a unit,
+ * or in a piecewise fashion as required.
+ */
+
+/* Destbuffer state
+ * - backbuffer linear offset and pitch -- invarient in the current dri
+ * - zbuffer linear offset and pitch -- also invarient
+ * - drawing origin in back and depth buffers.
+ *
+ * Keep the depth/back buffer state here to accommodate private buffers
+ * in the future.
+ */
+
+#define I830_DESTREG_CBUFADDR 0
+#define I830_DESTREG_DBUFADDR 1
+#define I830_DESTREG_DV0 2
+#define I830_DESTREG_DV1 3
+#define I830_DESTREG_SENABLE 4
+#define I830_DESTREG_SR0 5
+#define I830_DESTREG_SR1 6
+#define I830_DESTREG_SR2 7
+#define I830_DESTREG_DR0 8
+#define I830_DESTREG_DR1 9
+#define I830_DESTREG_DR2 10
+#define I830_DESTREG_DR3 11
+#define I830_DESTREG_DR4 12
+#define I830_DEST_SETUP_SIZE 13
+
+/* Context state
+ */
+#define I830_CTXREG_STATE1 0
+#define I830_CTXREG_STATE2 1
+#define I830_CTXREG_STATE3 2
+#define I830_CTXREG_STATE4 3
+#define I830_CTXREG_STATE5 4
+#define I830_CTXREG_IALPHAB 5
+#define I830_CTXREG_STENCILTST 6
+#define I830_CTXREG_ENABLES_1 7
+#define I830_CTXREG_ENABLES_2 8
+#define I830_CTXREG_AA 9
+#define I830_CTXREG_FOGCOLOR 10
+#define I830_CTXREG_BLENDCOLR0 11
+#define I830_CTXREG_BLENDCOLR 12 /* Dword 1 of 2 dword command */
+#define I830_CTXREG_VF 13
+#define I830_CTXREG_VF2 14
+#define I830_CTXREG_MCSB0 15
+#define I830_CTXREG_MCSB1 16
+#define I830_CTX_SETUP_SIZE 17
+
+/* 1.3: Stipple state
+ */
+#define I830_STPREG_ST0 0
+#define I830_STPREG_ST1 1
+#define I830_STP_SETUP_SIZE 2
+
+
+/* Texture state (per tex unit)
+ */
+
+#define I830_TEXREG_MI0 0 /* GFX_OP_MAP_INFO (6 dwords) */
+#define I830_TEXREG_MI1 1
+#define I830_TEXREG_MI2 2
+#define I830_TEXREG_MI3 3
+#define I830_TEXREG_MI4 4
+#define I830_TEXREG_MI5 5
+#define I830_TEXREG_MF 6 /* GFX_OP_MAP_FILTER */
+#define I830_TEXREG_MLC 7 /* GFX_OP_MAP_LOD_CTL */
+#define I830_TEXREG_MLL 8 /* GFX_OP_MAP_LOD_LIMITS */
+#define I830_TEXREG_MCS 9 /* GFX_OP_MAP_COORD_SETS */
+#define I830_TEX_SETUP_SIZE 10
+
+#define I830_TEXREG_TM0LI 0 /* load immediate 2 texture map n */
+#define I830_TEXREG_TM0S0 1
+#define I830_TEXREG_TM0S1 2
+#define I830_TEXREG_TM0S2 3
+#define I830_TEXREG_TM0S3 4
+#define I830_TEXREG_TM0S4 5
+#define I830_TEXREG_NOP0 6 /* noop */
+#define I830_TEXREG_NOP1 7 /* noop */
+#define I830_TEXREG_NOP2 8 /* noop */
+#define __I830_TEXREG_MCS 9 /* GFX_OP_MAP_COORD_SETS -- shared */
+#define __I830_TEX_SETUP_SIZE 10
+
+#define I830_FRONT 0x1
+#define I830_BACK 0x2
+#define I830_DEPTH 0x4
+
+#endif /* _I830_DEFINES_ */
+
+typedef struct _drm_i830_init {
+ enum {
+ I830_INIT_DMA = 0x01,
+ I830_CLEANUP_DMA = 0x02
+ } func;
+ unsigned int mmio_offset;
+ unsigned int buffers_offset;
+ int sarea_priv_offset;
+ unsigned int ring_start;
+ unsigned int ring_end;
+ unsigned int ring_size;
+ unsigned int front_offset;
+ unsigned int back_offset;
+ unsigned int depth_offset;
+ unsigned int w;
+ unsigned int h;
+ unsigned int pitch;
+ unsigned int pitch_bits;
+ unsigned int back_pitch;
+ unsigned int depth_pitch;
+ unsigned int cpp;
+} drm_i830_init_t;
+
+/* Warning: If you change the SAREA structure you must change the Xserver
+ * structure as well */
+
+typedef struct _drm_i830_tex_region {
+ unsigned char next, prev; /* indices to form a circular LRU */
+ unsigned char in_use; /* owned by a client, or free? */
+ int age; /* tracked by clients to update local LRU's */
+} drm_i830_tex_region_t;
+
+typedef struct _drm_i830_sarea {
+ unsigned int ContextState[I830_CTX_SETUP_SIZE];
+ unsigned int BufferState[I830_DEST_SETUP_SIZE];
+ unsigned int TexState[I830_TEXTURE_COUNT][I830_TEX_SETUP_SIZE];
+ unsigned int TexBlendState[I830_TEXBLEND_COUNT][I830_TEXBLEND_SIZE];
+ unsigned int TexBlendStateWordsUsed[I830_TEXBLEND_COUNT];
+ unsigned int Palette[2][256];
+ unsigned int dirty;
+
+ unsigned int nbox;
+ drm_clip_rect_t boxes[I830_NR_SAREA_CLIPRECTS];
+
+ /* Maintain an LRU of contiguous regions of texture space. If
+ * you think you own a region of texture memory, and it has an
+ * age different to the one you set, then you are mistaken and
+ * it has been stolen by another client. If global texAge
+ * hasn't changed, there is no need to walk the list.
+ *
+ * These regions can be used as a proxy for the fine-grained
+ * texture information of other clients - by maintaining them
+ * in the same lru which is used to age their own textures,
+ * clients have an approximate lru for the whole of global
+ * texture space, and can make informed decisions as to which
+ * areas to kick out. There is no need to choose whether to
+ * kick out your own texture or someone else's - simply eject
+ * them all in LRU order.
+ */
+
+ drm_i830_tex_region_t texList[I830_NR_TEX_REGIONS+1];
+ /* Last elt is sentinal */
+ int texAge; /* last time texture was uploaded */
+ int last_enqueue; /* last time a buffer was enqueued */
+ int last_dispatch; /* age of the most recently dispatched buffer */
+ int last_quiescent; /* */
+ int ctxOwner; /* last context to upload state */
+
+ int vertex_prim;
+
+ int pf_enabled; /* is pageflipping allowed? */
+ int pf_active;
+ int pf_current_page; /* which buffer is being displayed? */
+
+ int perf_boxes; /* performance boxes to be displayed */
+
+ /* Here's the state for texunits 2,3:
+ */
+ unsigned int TexState2[I830_TEX_SETUP_SIZE];
+ unsigned int TexBlendState2[I830_TEXBLEND_SIZE];
+ unsigned int TexBlendStateWordsUsed2;
+
+ unsigned int TexState3[I830_TEX_SETUP_SIZE];
+ unsigned int TexBlendState3[I830_TEXBLEND_SIZE];
+ unsigned int TexBlendStateWordsUsed3;
+
+ unsigned int StippleState[I830_STP_SETUP_SIZE];
+} drm_i830_sarea_t;
+
+/* Flags for perf_boxes
+ */
+#define I830_BOX_RING_EMPTY 0x1 /* populated by kernel */
+#define I830_BOX_FLIP 0x2 /* populated by kernel */
+#define I830_BOX_WAIT 0x4 /* populated by kernel & client */
+#define I830_BOX_TEXTURE_LOAD 0x8 /* populated by kernel */
+#define I830_BOX_LOST_CONTEXT 0x10 /* populated by client */
+
+
+/* I830 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I830_INIT 0x00
+#define DRM_I830_VERTEX 0x01
+#define DRM_I830_CLEAR 0x02
+#define DRM_I830_FLUSH 0x03
+#define DRM_I830_GETAGE 0x04
+#define DRM_I830_GETBUF 0x05
+#define DRM_I830_SWAP 0x06
+#define DRM_I830_COPY 0x07
+#define DRM_I830_DOCOPY 0x08
+#define DRM_I830_FLIP 0x09
+#define DRM_I830_IRQ_EMIT 0x0a
+#define DRM_I830_IRQ_WAIT 0x0b
+#define DRM_I830_GETPARAM 0x0c
+#define DRM_I830_SETPARAM 0x0d
+
+#define DRM_IOCTL_I830_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_INIT, drm_i830_init_t)
+#define DRM_IOCTL_I830_VERTEX DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_VERTEX, drm_i830_vertex_t)
+#define DRM_IOCTL_I830_CLEAR DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_CLEAR, drm_i830_clear_t)
+#define DRM_IOCTL_I830_FLUSH DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_FLUSH)
+#define DRM_IOCTL_I830_GETAGE DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_GETAGE)
+#define DRM_IOCTL_I830_GETBUF DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_GETBUF, drm_i830_dma_t)
+#define DRM_IOCTL_I830_SWAP DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_SWAP)
+#define DRM_IOCTL_I830_COPY DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_COPY, drm_i830_copy_t)
+#define DRM_IOCTL_I830_DOCOPY DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_DOCOPY)
+#define DRM_IOCTL_I830_FLIP DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_FLIP)
+#define DRM_IOCTL_I830_IRQ_EMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_IRQ_EMIT, drm_i830_irq_emit_t)
+#define DRM_IOCTL_I830_IRQ_WAIT DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_IRQ_WAIT, drm_i830_irq_wait_t)
+#define DRM_IOCTL_I830_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_GETPARAM, drm_i830_getparam_t)
+#define DRM_IOCTL_I830_SETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_SETPARAM, drm_i830_setparam_t)
+
+typedef struct _drm_i830_clear {
+ int clear_color;
+ int clear_depth;
+ int flags;
+ unsigned int clear_colormask;
+ unsigned int clear_depthmask;
+} drm_i830_clear_t;
+
+
+
+/* These may be placeholders if we have more cliprects than
+ * I830_NR_SAREA_CLIPRECTS. In that case, the client sets discard to
+ * false, indicating that the buffer will be dispatched again with a
+ * new set of cliprects.
+ */
+typedef struct _drm_i830_vertex {
+ int idx; /* buffer index */
+ int used; /* nr bytes in use */
+ int discard; /* client is finished with the buffer? */
+} drm_i830_vertex_t;
+
+typedef struct _drm_i830_copy_t {
+ int idx; /* buffer index */
+ int used; /* nr bytes in use */
+ void __user *address; /* Address to copy from */
+} drm_i830_copy_t;
+
+typedef struct drm_i830_dma {
+ void __user *virtual;
+ int request_idx;
+ int request_size;
+ int granted;
+} drm_i830_dma_t;
+
+
+/* 1.3: Userspace can request & wait on irq's:
+ */
+typedef struct drm_i830_irq_emit {
+ int __user *irq_seq;
+} drm_i830_irq_emit_t;
+
+typedef struct drm_i830_irq_wait {
+ int irq_seq;
+} drm_i830_irq_wait_t;
+
+
+/* 1.3: New ioctl to query kernel params:
+ */
+#define I830_PARAM_IRQ_ACTIVE 1
+
+typedef struct drm_i830_getparam {
+ int param;
+ int __user *value;
+} drm_i830_getparam_t;
+
+
+/* 1.3: New ioctl to set kernel params:
+ */
+#define I830_SETPARAM_USE_MI_BATCHBUFFER_START 1
+
+typedef struct drm_i830_setparam {
+ int param;
+ int value;
+} drm_i830_setparam_t;
+
+
+#endif /* _I830_DRM_H_ */
diff --git a/drivers/char/drm/i830_drv.c b/drivers/char/drm/i830_drv.c
new file mode 100644
index 000000000000..aa80ad6a5ee0
--- /dev/null
+++ b/drivers/char/drm/i830_drv.c
@@ -0,0 +1,137 @@
+/* i830_drv.c -- I810 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ * Abraham vd Merwe <abraham@2d3d.co.za>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+
+#include "drm_pciids.h"
+
+int postinit( struct drm_device *dev, unsigned long flags )
+{
+ dev->counters += 4;
+ dev->types[6] = _DRM_STAT_IRQ;
+ dev->types[7] = _DRM_STAT_PRIMARY;
+ dev->types[8] = _DRM_STAT_SECONDARY;
+ dev->types[9] = _DRM_STAT_DMA;
+
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ i830_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i830_ioctls[];
+extern int i830_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_DMA_QUEUE,
+#if USE_IRQS
+ .driver_features |= DRIVER_HAVE_IRQ | DRIVER_SHARED_IRQ,
+#endif
+ .dev_priv_size = sizeof(drm_i830_buf_priv_t),
+ .pretakedown = i830_driver_pretakedown,
+ .prerelease = i830_driver_prerelease,
+ .release = i830_driver_release,
+ .dma_quiescent = i830_driver_dma_quiescent,
+ .reclaim_buffers = i830_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+#if USE_IRQS
+ .irq_preinstall = i830_driver_irq_preinstall,
+ .irq_postinstall = i830_driver_irq_postinstall,
+ .irq_uninstall = i830_driver_irq_uninstall,
+ .irq_handler = i830_driver_irq_handler,
+#endif
+ .postinit = postinit,
+ .version = version,
+ .ioctls = i830_ioctls,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+
+};
+
+static int __init i830_init(void)
+{
+ driver.num_ioctls = i830_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit i830_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(i830_init);
+module_exit(i830_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i830_drv.h b/drivers/char/drm/i830_drv.h
new file mode 100644
index 000000000000..d4b2d093d6ab
--- /dev/null
+++ b/drivers/char/drm/i830_drv.h
@@ -0,0 +1,301 @@
+/* i830_drv.h -- Private header for the I830 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ *
+ */
+
+#ifndef _I830_DRV_H_
+#define _I830_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "VA Linux Systems Inc."
+
+#define DRIVER_NAME "i830"
+#define DRIVER_DESC "Intel 830M"
+#define DRIVER_DATE "20021108"
+
+/* Interface history:
+ *
+ * 1.1: Original.
+ * 1.2: ?
+ * 1.3: New irq emit/wait ioctls.
+ * New pageflip ioctl.
+ * New getparam ioctl.
+ * State for texunits 3&4 in sarea.
+ * New (alternative) layout for texture state.
+ */
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 3
+#define DRIVER_PATCHLEVEL 2
+
+/* Driver will work either way: IRQ's save cpu time when waiting for
+ * the card, but are subject to subtle interactions between bios,
+ * hardware and the driver.
+ */
+/* XXX: Add vblank support? */
+#define USE_IRQS 0
+
+typedef struct drm_i830_buf_priv {
+ u32 *in_use;
+ int my_use_idx;
+ int currently_mapped;
+ void __user *virtual;
+ void *kernel_virtual;
+} drm_i830_buf_priv_t;
+
+typedef struct _drm_i830_ring_buffer{
+ int tail_mask;
+ unsigned long Start;
+ unsigned long End;
+ unsigned long Size;
+ u8 *virtual_start;
+ int head;
+ int tail;
+ int space;
+} drm_i830_ring_buffer_t;
+
+typedef struct drm_i830_private {
+ drm_map_t *sarea_map;
+ drm_map_t *mmio_map;
+
+ drm_i830_sarea_t *sarea_priv;
+ drm_i830_ring_buffer_t ring;
+
+ void * hw_status_page;
+ unsigned long counter;
+
+ dma_addr_t dma_status_page;
+
+ drm_buf_t *mmap_buffer;
+
+ u32 front_di1, back_di1, zi1;
+
+ int back_offset;
+ int depth_offset;
+ int front_offset;
+ int w, h;
+ int pitch;
+ int back_pitch;
+ int depth_pitch;
+ unsigned int cpp;
+
+ int do_boxes;
+ int dma_used;
+
+ int current_page;
+ int page_flipping;
+
+ wait_queue_head_t irq_queue;
+ atomic_t irq_received;
+ atomic_t irq_emitted;
+
+ int use_mi_batchbuffer_start;
+
+} drm_i830_private_t;
+
+/* i830_dma.c */
+extern void i830_reclaim_buffers(drm_device_t *dev, struct file *filp);
+
+extern int i830_mmap_buffers(struct file *filp, struct vm_area_struct *vma);
+
+/* i830_irq.c */
+extern int i830_irq_emit( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+extern int i830_irq_wait( struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg );
+
+extern irqreturn_t i830_driver_irq_handler( DRM_IRQ_ARGS );
+extern void i830_driver_irq_preinstall( drm_device_t *dev );
+extern void i830_driver_irq_postinstall( drm_device_t *dev );
+extern void i830_driver_irq_uninstall( drm_device_t *dev );
+extern void i830_driver_pretakedown(drm_device_t *dev);
+extern void i830_driver_release(drm_device_t *dev, struct file *filp);
+extern int i830_driver_dma_quiescent(drm_device_t *dev);
+extern void i830_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+#define I830_BASE(reg) ((unsigned long) \
+ dev_priv->mmio_map->handle)
+#define I830_ADDR(reg) (I830_BASE(reg) + reg)
+#define I830_DEREF(reg) *(__volatile__ unsigned int *)I830_ADDR(reg)
+#define I830_READ(reg) readl((volatile u32 *)I830_ADDR(reg))
+#define I830_WRITE(reg,val) writel(val, (volatile u32 *)I830_ADDR(reg))
+#define I830_DEREF16(reg) *(__volatile__ u16 *)I830_ADDR(reg)
+#define I830_READ16(reg) I830_DEREF16(reg)
+#define I830_WRITE16(reg,val) do { I830_DEREF16(reg) = val; } while (0)
+
+
+
+#define I830_VERBOSE 0
+
+#define RING_LOCALS unsigned int outring, ringmask, outcount; \
+ volatile char *virt;
+
+#define BEGIN_LP_RING(n) do { \
+ if (I830_VERBOSE) \
+ printk("BEGIN_LP_RING(%d) in %s\n", \
+ n, __FUNCTION__); \
+ if (dev_priv->ring.space < n*4) \
+ i830_wait_ring(dev, n*4, __FUNCTION__); \
+ outcount = 0; \
+ outring = dev_priv->ring.tail; \
+ ringmask = dev_priv->ring.tail_mask; \
+ virt = dev_priv->ring.virtual_start; \
+} while (0)
+
+
+#define OUT_RING(n) do { \
+ if (I830_VERBOSE) printk(" OUT_RING %x\n", (int)(n)); \
+ *(volatile unsigned int *)(virt + outring) = n; \
+ outcount++; \
+ outring += 4; \
+ outring &= ringmask; \
+} while (0)
+
+#define ADVANCE_LP_RING() do { \
+ if (I830_VERBOSE) printk("ADVANCE_LP_RING %x\n", outring); \
+ dev_priv->ring.tail = outring; \
+ dev_priv->ring.space -= outcount * 4; \
+ I830_WRITE(LP_RING + RING_TAIL, outring); \
+} while(0)
+
+extern int i830_wait_ring(drm_device_t *dev, int n, const char *caller);
+
+
+#define GFX_OP_USER_INTERRUPT ((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT ((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD (7<<23)
+#define CMD_STORE_DWORD_IDX ((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER ((0x0<<29)|(0x30<<23)|0x1)
+
+#define STATE3D_LOAD_STATE_IMMEDIATE_2 ((0x3<<29)|(0x1d<<24)|(0x03<<16))
+#define LOAD_TEXTURE_MAP0 (1<<11)
+
+#define INST_PARSER_CLIENT 0x00000000
+#define INST_OP_FLUSH 0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+
+#define BB1_START_ADDR_MASK (~0x7)
+#define BB1_PROTECTED (1<<0)
+#define BB1_UNPROTECTED (0<<0)
+#define BB2_END_ADDR_MASK (~0x7)
+
+#define I830REG_HWSTAM 0x02098
+#define I830REG_INT_IDENTITY_R 0x020a4
+#define I830REG_INT_MASK_R 0x020a8
+#define I830REG_INT_ENABLE_R 0x020a0
+
+#define I830_IRQ_RESERVED ((1<<13)|(3<<2))
+
+
+#define LP_RING 0x2030
+#define HP_RING 0x2040
+#define RING_TAIL 0x00
+#define TAIL_ADDR 0x001FFFF8
+#define RING_HEAD 0x04
+#define HEAD_WRAP_COUNT 0xFFE00000
+#define HEAD_WRAP_ONE 0x00200000
+#define HEAD_ADDR 0x001FFFFC
+#define RING_START 0x08
+#define START_ADDR 0x0xFFFFF000
+#define RING_LEN 0x0C
+#define RING_NR_PAGES 0x001FF000
+#define RING_REPORT_MASK 0x00000006
+#define RING_REPORT_64K 0x00000002
+#define RING_REPORT_128K 0x00000004
+#define RING_NO_REPORT 0x00000000
+#define RING_VALID_MASK 0x00000001
+#define RING_VALID 0x00000001
+#define RING_INVALID 0x00000000
+
+#define GFX_OP_SCISSOR ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR (0x1<<1)
+#define SC_ENABLE_MASK (0x1<<0)
+#define SC_ENABLE (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK (0xffff<<16)
+#define SCI_XMIN_MASK (0xffff<<0)
+#define SCI_YMAX_MASK (0xffff<<16)
+#define SCI_XMAX_MASK (0xffff<<0)
+
+#define GFX_OP_SCISSOR_ENABLE ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define GFX_OP_SCISSOR_RECT ((0x3<<29)|(0x1d<<24)|(0x81<<16)|1)
+#define GFX_OP_COLOR_FACTOR ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO ((0x3<<29)|(0x1d<<24)|0x4)
+#define GFX_OP_DESTBUFFER_VARS ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+#define GFX_OP_PRIMITIVE ((0x3<<29)|(0x1f<<24))
+
+#define CMD_OP_DESTBUFFER_INFO ((0x3<<29)|(0x1d<<24)|(0x8e<<16)|1)
+
+#define CMD_OP_DISPLAYBUFFER_INFO ((0x0<<29)|(0x14<<23)|2)
+#define ASYNC_FLIP (1<<22)
+
+#define CMD_3D (0x3<<29)
+#define STATE3D_CONST_BLEND_COLOR_CMD (CMD_3D|(0x1d<<24)|(0x88<<16))
+#define STATE3D_MAP_COORD_SETBIND_CMD (CMD_3D|(0x1d<<24)|(0x02<<16))
+
+#define BR00_BITBLT_CLIENT 0x40000000
+#define BR00_OP_COLOR_BLT 0x10000000
+#define BR00_OP_SRC_COPY_BLT 0x10C00000
+#define BR13_SOLID_PATTERN 0x80000000
+
+#define BUF_3D_ID_COLOR_BACK (0x3<<24)
+#define BUF_3D_ID_DEPTH (0x7<<24)
+#define BUF_3D_USE_FENCE (1<<23)
+#define BUF_3D_PITCH(x) (((x)/4)<<2)
+
+#define CMD_OP_MAP_PALETTE_LOAD ((3<<29)|(0x1d<<24)|(0x82<<16)|255)
+#define MAP_PALETTE_NUM(x) ((x<<8) & (1<<8))
+#define MAP_PALETTE_BOTH (1<<11)
+
+#define XY_COLOR_BLT_CMD ((2<<29)|(0x50<<22)|0x4)
+#define XY_COLOR_BLT_WRITE_ALPHA (1<<21)
+#define XY_COLOR_BLT_WRITE_RGB (1<<20)
+
+#define XY_SRC_COPY_BLT_CMD ((2<<29)|(0x53<<22)|6)
+#define XY_SRC_COPY_BLT_WRITE_ALPHA (1<<21)
+#define XY_SRC_COPY_BLT_WRITE_RGB (1<<20)
+
+#define MI_BATCH_BUFFER ((0x30<<23)|1)
+#define MI_BATCH_BUFFER_START (0x31<<23)
+#define MI_BATCH_BUFFER_END (0xA<<23)
+#define MI_BATCH_NON_SECURE (1)
+
+#define MI_WAIT_FOR_EVENT ((0x3<<23))
+#define MI_WAIT_FOR_PLANE_A_FLIP (1<<2)
+#define MI_WAIT_FOR_PLANE_A_SCANLINES (1<<1)
+
+#define MI_LOAD_SCAN_LINES_INCL ((0x12<<23))
+
+#endif
+
diff --git a/drivers/char/drm/i830_irq.c b/drivers/char/drm/i830_irq.c
new file mode 100644
index 000000000000..6d7729ffe2dc
--- /dev/null
+++ b/drivers/char/drm/i830_irq.c
@@ -0,0 +1,204 @@
+/* i830_dma.c -- DMA support for the I830 -*- linux-c -*-
+ *
+ * Copyright 2002 Tungsten Graphics, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Keith Whitwell <keith@tungstengraphics.com>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+#include <linux/interrupt.h> /* For task queue support */
+#include <linux/delay.h>
+
+
+irqreturn_t i830_driver_irq_handler( DRM_IRQ_ARGS )
+{
+ drm_device_t *dev = (drm_device_t *)arg;
+ drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+ u16 temp;
+
+ temp = I830_READ16(I830REG_INT_IDENTITY_R);
+ DRM_DEBUG("%x\n", temp);
+
+ if ( !( temp & 2 ) )
+ return IRQ_NONE;
+
+ I830_WRITE16(I830REG_INT_IDENTITY_R, temp);
+
+ atomic_inc(&dev_priv->irq_received);
+ wake_up_interruptible(&dev_priv->irq_queue);
+
+ return IRQ_HANDLED;
+}
+
+
+int i830_emit_irq(drm_device_t *dev)
+{
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ atomic_inc(&dev_priv->irq_emitted);
+
+ BEGIN_LP_RING(2);
+ OUT_RING( 0 );
+ OUT_RING( GFX_OP_USER_INTERRUPT );
+ ADVANCE_LP_RING();
+
+ return atomic_read(&dev_priv->irq_emitted);
+}
+
+
+int i830_wait_irq(drm_device_t *dev, int irq_nr)
+{
+ drm_i830_private_t *dev_priv =
+ (drm_i830_private_t *)dev->dev_private;
+ DECLARE_WAITQUEUE(entry, current);
+ unsigned long end = jiffies + HZ*3;
+ int ret = 0;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ if (atomic_read(&dev_priv->irq_received) >= irq_nr)
+ return 0;
+
+ dev_priv->sarea_priv->perf_boxes |= I830_BOX_WAIT;
+
+ add_wait_queue(&dev_priv->irq_queue, &entry);
+
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if (atomic_read(&dev_priv->irq_received) >= irq_nr)
+ break;
+ if((signed)(end - jiffies) <= 0) {
+ DRM_ERROR("timeout iir %x imr %x ier %x hwstam %x\n",
+ I830_READ16( I830REG_INT_IDENTITY_R ),
+ I830_READ16( I830REG_INT_MASK_R ),
+ I830_READ16( I830REG_INT_ENABLE_R ),
+ I830_READ16( I830REG_HWSTAM ));
+
+ ret = -EBUSY; /* Lockup? Missed irq? */
+ break;
+ }
+ schedule_timeout(HZ*3);
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&dev_priv->irq_queue, &entry);
+ return ret;
+}
+
+
+/* Needs the lock as it touches the ring.
+ */
+int i830_irq_emit( struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_irq_emit_t emit;
+ int result;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return -EINVAL;
+ }
+
+ if (copy_from_user( &emit, (drm_i830_irq_emit_t __user *)arg, sizeof(emit) ))
+ return -EFAULT;
+
+ result = i830_emit_irq( dev );
+
+ if ( copy_to_user( emit.irq_seq, &result, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+
+/* Doesn't need the hardware lock.
+ */
+int i830_irq_wait( struct inode *inode, struct file *filp, unsigned int cmd,
+ unsigned long arg )
+{
+ drm_file_t *priv = filp->private_data;
+ drm_device_t *dev = priv->head->dev;
+ drm_i830_private_t *dev_priv = dev->dev_private;
+ drm_i830_irq_wait_t irqwait;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return -EINVAL;
+ }
+
+ if (copy_from_user( &irqwait, (drm_i830_irq_wait_t __user *)arg,
+ sizeof(irqwait) ))
+ return -EFAULT;
+
+ return i830_wait_irq( dev, irqwait.irq_seq );
+}
+
+
+/* drm_dma.h hooks
+*/
+void i830_driver_irq_preinstall( drm_device_t *dev ) {
+ drm_i830_private_t *dev_priv =
+ (drm_i830_private_t *)dev->dev_private;
+
+ I830_WRITE16( I830REG_HWSTAM, 0xffff );
+ I830_WRITE16( I830REG_INT_MASK_R, 0x0 );
+ I830_WRITE16( I830REG_INT_ENABLE_R, 0x0 );
+ atomic_set(&dev_priv->irq_received, 0);
+ atomic_set(&dev_priv->irq_emitted, 0);
+ init_waitqueue_head(&dev_priv->irq_queue);
+}
+
+void i830_driver_irq_postinstall( drm_device_t *dev ) {
+ drm_i830_private_t *dev_priv =
+ (drm_i830_private_t *)dev->dev_private;
+
+ I830_WRITE16( I830REG_INT_ENABLE_R, 0x2 );
+}
+
+void i830_driver_irq_uninstall( drm_device_t *dev ) {
+ drm_i830_private_t *dev_priv =
+ (drm_i830_private_t *)dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ I830_WRITE16( I830REG_INT_MASK_R, 0xffff );
+ I830_WRITE16( I830REG_INT_ENABLE_R, 0x0 );
+}
diff --git a/drivers/char/drm/i915_dma.c b/drivers/char/drm/i915_dma.c
new file mode 100644
index 000000000000..7300a09dbd5c
--- /dev/null
+++ b/drivers/char/drm/i915_dma.c
@@ -0,0 +1,725 @@
+/* i915_dma.c -- DMA support for the I915 -*- linux-c -*-
+ */
+/**************************************************************************
+ *
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+drm_ioctl_desc_t i915_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_I915_INIT)] = {i915_dma_init, 1, 1},
+ [DRM_IOCTL_NR(DRM_I915_FLUSH)] = {i915_flush_ioctl, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_FLIP)] = {i915_flip_bufs, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_BATCHBUFFER)] = {i915_batchbuffer, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_IRQ_EMIT)] = {i915_irq_emit, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_IRQ_WAIT)] = {i915_irq_wait, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_GETPARAM)] = {i915_getparam, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_SETPARAM)] = {i915_setparam, 1, 1},
+ [DRM_IOCTL_NR(DRM_I915_ALLOC)] = {i915_mem_alloc, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_FREE)] = {i915_mem_free, 1, 0},
+ [DRM_IOCTL_NR(DRM_I915_INIT_HEAP)] = {i915_mem_init_heap, 1, 1},
+ [DRM_IOCTL_NR(DRM_I915_CMDBUFFER)] = {i915_cmdbuffer, 1, 0}
+};
+
+int i915_max_ioctl = DRM_ARRAY_SIZE(i915_ioctls);
+
+/* Really want an OS-independent resettable timer. Would like to have
+ * this loop run for (eg) 3 sec, but have the timer reset every time
+ * the head pointer changes, so that EBUSY only happens if the ring
+ * actually stalls for (eg) 3 seconds.
+ */
+int i915_wait_ring(drm_device_t * dev, int n, const char *caller)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_ring_buffer_t *ring = &(dev_priv->ring);
+ u32 last_head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ int i;
+
+ for (i = 0; i < 10000; i++) {
+ ring->head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->space = ring->head - (ring->tail + 8);
+ if (ring->space < 0)
+ ring->space += ring->Size;
+ if (ring->space >= n)
+ return 0;
+
+ dev_priv->sarea_priv->perf_boxes |= I915_BOX_WAIT;
+
+ if (ring->head != last_head)
+ i = 0;
+
+ last_head = ring->head;
+ }
+
+ return DRM_ERR(EBUSY);
+}
+
+void i915_kernel_lost_context(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_ring_buffer_t *ring = &(dev_priv->ring);
+
+ ring->head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+ ring->tail = I915_READ(LP_RING + RING_TAIL) & TAIL_ADDR;
+ ring->space = ring->head - (ring->tail + 8);
+ if (ring->space < 0)
+ ring->space += ring->Size;
+
+ if (ring->head == ring->tail)
+ dev_priv->sarea_priv->perf_boxes |= I915_BOX_RING_EMPTY;
+}
+
+int i915_dma_cleanup(drm_device_t * dev)
+{
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if (dev->irq)
+ drm_irq_uninstall (dev);
+
+ if (dev->dev_private) {
+ drm_i915_private_t *dev_priv =
+ (drm_i915_private_t *) dev->dev_private;
+
+ if (dev_priv->ring.virtual_start) {
+ drm_core_ioremapfree( &dev_priv->ring.map, dev);
+ }
+
+ if (dev_priv->hw_status_page) {
+ drm_pci_free(dev, PAGE_SIZE, dev_priv->hw_status_page,
+ dev_priv->dma_status_page);
+ /* Need to rewrite hardware status page */
+ I915_WRITE(0x02080, 0x1ffff000);
+ }
+
+ drm_free (dev->dev_private, sizeof(drm_i915_private_t),
+ DRM_MEM_DRIVER);
+
+ dev->dev_private = NULL;
+ }
+
+ return 0;
+}
+
+static int i915_initialize(drm_device_t * dev,
+ drm_i915_private_t * dev_priv,
+ drm_i915_init_t * init)
+{
+ memset(dev_priv, 0, sizeof(drm_i915_private_t));
+
+ DRM_GETSAREA();
+ if (!dev_priv->sarea) {
+ DRM_ERROR("can not find sarea!\n");
+ dev->dev_private = (void *)dev_priv;
+ i915_dma_cleanup(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+ if (!dev_priv->mmio_map) {
+ dev->dev_private = (void *)dev_priv;
+ i915_dma_cleanup(dev);
+ DRM_ERROR("can not find mmio map!\n");
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->sarea_priv = (drm_i915_sarea_t *)
+ ((u8 *) dev_priv->sarea->handle + init->sarea_priv_offset);
+
+ dev_priv->ring.Start = init->ring_start;
+ dev_priv->ring.End = init->ring_end;
+ dev_priv->ring.Size = init->ring_size;
+ dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+
+ dev_priv->ring.map.offset = init->ring_start;
+ dev_priv->ring.map.size = init->ring_size;
+ dev_priv->ring.map.type = 0;
+ dev_priv->ring.map.flags = 0;
+ dev_priv->ring.map.mtrr = 0;
+
+ drm_core_ioremap( &dev_priv->ring.map, dev );
+
+ if (dev_priv->ring.map.handle == NULL) {
+ dev->dev_private = (void *)dev_priv;
+ i915_dma_cleanup(dev);
+ DRM_ERROR("can not ioremap virtual address for"
+ " ring buffer\n");
+ return DRM_ERR(ENOMEM);
+ }
+
+ dev_priv->ring.virtual_start = dev_priv->ring.map.handle;
+
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->front_offset = init->front_offset;
+ dev_priv->current_page = 0;
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+
+ /* We are using separate values as placeholders for mechanisms for
+ * private backbuffer/depthbuffer usage.
+ */
+ dev_priv->use_mi_batchbuffer_start = 0;
+
+ /* Allow hardware batchbuffers unless told otherwise.
+ */
+ dev_priv->allow_batchbuffer = 1;
+
+ /* Program Hardware Status Page */
+ dev_priv->hw_status_page = drm_pci_alloc(dev, PAGE_SIZE, PAGE_SIZE,
+ 0xffffffff,
+ &dev_priv->dma_status_page);
+
+ if (!dev_priv->hw_status_page) {
+ dev->dev_private = (void *)dev_priv;
+ i915_dma_cleanup(dev);
+ DRM_ERROR("Can not allocate hardware status page\n");
+ return DRM_ERR(ENOMEM);
+ }
+ memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+ DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+ I915_WRITE(0x02080, dev_priv->dma_status_page);
+ DRM_DEBUG("Enabled hardware status page\n");
+
+ dev->dev_private = (void *)dev_priv;
+
+ return 0;
+}
+
+static int i915_resume(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ if (!dev_priv->sarea) {
+ DRM_ERROR("can not find sarea!\n");
+ return DRM_ERR(EINVAL);
+ }
+
+ if (!dev_priv->mmio_map) {
+ DRM_ERROR("can not find mmio map!\n");
+ return DRM_ERR(EINVAL);
+ }
+
+ if (dev_priv->ring.map.handle == NULL) {
+ DRM_ERROR("can not ioremap virtual address for"
+ " ring buffer\n");
+ return DRM_ERR(ENOMEM);
+ }
+
+ /* Program Hardware Status Page */
+ if (!dev_priv->hw_status_page) {
+ DRM_ERROR("Can not find hardware status page\n");
+ return DRM_ERR(EINVAL);
+ }
+ DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+ I915_WRITE(0x02080, dev_priv->dma_status_page);
+ DRM_DEBUG("Enabled hardware status page\n");
+
+ return 0;
+}
+
+int i915_dma_init(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv;
+ drm_i915_init_t init;
+ int retcode = 0;
+
+ DRM_COPY_FROM_USER_IOCTL(init, (drm_i915_init_t __user *) data,
+ sizeof(init));
+
+ switch (init.func) {
+ case I915_INIT_DMA:
+ dev_priv = drm_alloc (sizeof(drm_i915_private_t),
+ DRM_MEM_DRIVER);
+ if (dev_priv == NULL)
+ return DRM_ERR(ENOMEM);
+ retcode = i915_initialize(dev, dev_priv, &init);
+ break;
+ case I915_CLEANUP_DMA:
+ retcode = i915_dma_cleanup(dev);
+ break;
+ case I915_RESUME_DMA:
+ retcode = i915_resume(dev);
+ break;
+ default:
+ retcode = -EINVAL;
+ break;
+ }
+
+ return retcode;
+}
+
+/* Implement basically the same security restrictions as hardware does
+ * for MI_BATCH_NON_SECURE. These can be made stricter at any time.
+ *
+ * Most of the calculations below involve calculating the size of a
+ * particular instruction. It's important to get the size right as
+ * that tells us where the next instruction to check is. Any illegal
+ * instruction detected will be given a size of zero, which is a
+ * signal to abort the rest of the buffer.
+ */
+static int do_validate_cmd(int cmd)
+{
+ switch (((cmd >> 29) & 0x7)) {
+ case 0x0:
+ switch ((cmd >> 23) & 0x3f) {
+ case 0x0:
+ return 1; /* MI_NOOP */
+ case 0x4:
+ return 1; /* MI_FLUSH */
+ default:
+ return 0; /* disallow everything else */
+ }
+ break;
+ case 0x1:
+ return 0; /* reserved */
+ case 0x2:
+ return (cmd & 0xff) + 2; /* 2d commands */
+ case 0x3:
+ if (((cmd >> 24) & 0x1f) <= 0x18)
+ return 1;
+
+ switch ((cmd >> 24) & 0x1f) {
+ case 0x1c:
+ return 1;
+ case 0x1d:
+ switch ((cmd>>16)&0xff) {
+ case 0x3:
+ return (cmd & 0x1f) + 2;
+ case 0x4:
+ return (cmd & 0xf) + 2;
+ default:
+ return (cmd & 0xffff) + 2;
+ }
+ case 0x1e:
+ if (cmd & (1 << 23))
+ return (cmd & 0xffff) + 1;
+ else
+ return 1;
+ case 0x1f:
+ if ((cmd & (1 << 23)) == 0) /* inline vertices */
+ return (cmd & 0x1ffff) + 2;
+ else if (cmd & (1 << 17)) /* indirect random */
+ if ((cmd & 0xffff) == 0)
+ return 0; /* unknown length, too hard */
+ else
+ return (((cmd & 0xffff) + 1) / 2) + 1;
+ else
+ return 2; /* indirect sequential */
+ default:
+ return 0;
+ }
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int validate_cmd(int cmd)
+{
+ int ret = do_validate_cmd(cmd);
+
+/* printk("validate_cmd( %x ): %d\n", cmd, ret); */
+
+ return ret;
+}
+
+static int i915_emit_cmds(drm_device_t * dev, int __user * buffer, int dwords)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ int i;
+ RING_LOCALS;
+
+ for (i = 0; i < dwords;) {
+ int cmd, sz;
+
+ if (DRM_COPY_FROM_USER_UNCHECKED(&cmd, &buffer[i], sizeof(cmd)))
+ return DRM_ERR(EINVAL);
+
+/* printk("%d/%d ", i, dwords); */
+
+ if ((sz = validate_cmd(cmd)) == 0 || i + sz > dwords)
+ return DRM_ERR(EINVAL);
+
+ BEGIN_LP_RING(sz);
+ OUT_RING(cmd);
+
+ while (++i, --sz) {
+ if (DRM_COPY_FROM_USER_UNCHECKED(&cmd, &buffer[i],
+ sizeof(cmd))) {
+ return DRM_ERR(EINVAL);
+ }
+ OUT_RING(cmd);
+ }
+ ADVANCE_LP_RING();
+ }
+
+ return 0;
+}
+
+static int i915_emit_box(drm_device_t * dev,
+ drm_clip_rect_t __user * boxes,
+ int i, int DR1, int DR4)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_clip_rect_t box;
+ RING_LOCALS;
+
+ if (DRM_COPY_FROM_USER_UNCHECKED(&box, &boxes[i], sizeof(box))) {
+ return EFAULT;
+ }
+
+ if (box.y2 <= box.y1 || box.x2 <= box.x1 || box.y2 <= 0 || box.x2 <= 0) {
+ DRM_ERROR("Bad box %d,%d..%d,%d\n",
+ box.x1, box.y1, box.x2, box.y2);
+ return DRM_ERR(EINVAL);
+ }
+
+ BEGIN_LP_RING(6);
+ OUT_RING(GFX_OP_DRAWRECT_INFO);
+ OUT_RING(DR1);
+ OUT_RING((box.x1 & 0xffff) | (box.y1 << 16));
+ OUT_RING(((box.x2 - 1) & 0xffff) | ((box.y2 - 1) << 16));
+ OUT_RING(DR4);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ return 0;
+}
+
+static int i915_dispatch_cmdbuffer(drm_device_t * dev,
+ drm_i915_cmdbuffer_t * cmd)
+{
+ int nbox = cmd->num_cliprects;
+ int i = 0, count, ret;
+
+ if (cmd->sz & 0x3) {
+ DRM_ERROR("alignment");
+ return DRM_ERR(EINVAL);
+ }
+
+ i915_kernel_lost_context(dev);
+
+ count = nbox ? nbox : 1;
+
+ for (i = 0; i < count; i++) {
+ if (i < nbox) {
+ ret = i915_emit_box(dev, cmd->cliprects, i,
+ cmd->DR1, cmd->DR4);
+ if (ret)
+ return ret;
+ }
+
+ ret = i915_emit_cmds(dev, (int __user *)cmd->buf, cmd->sz / 4);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int i915_dispatch_batchbuffer(drm_device_t * dev,
+ drm_i915_batchbuffer_t * batch)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_clip_rect_t __user *boxes = batch->cliprects;
+ int nbox = batch->num_cliprects;
+ int i = 0, count;
+ RING_LOCALS;
+
+ if ((batch->start | batch->used) & 0x7) {
+ DRM_ERROR("alignment");
+ return DRM_ERR(EINVAL);
+ }
+
+ i915_kernel_lost_context(dev);
+
+ count = nbox ? nbox : 1;
+
+ for (i = 0; i < count; i++) {
+ if (i < nbox) {
+ int ret = i915_emit_box(dev, boxes, i,
+ batch->DR1, batch->DR4);
+ if (ret)
+ return ret;
+ }
+
+ if (dev_priv->use_mi_batchbuffer_start) {
+ BEGIN_LP_RING(2);
+ OUT_RING(MI_BATCH_BUFFER_START | (2 << 6));
+ OUT_RING(batch->start | MI_BATCH_NON_SECURE);
+ ADVANCE_LP_RING();
+ } else {
+ BEGIN_LP_RING(4);
+ OUT_RING(MI_BATCH_BUFFER);
+ OUT_RING(batch->start | MI_BATCH_NON_SECURE);
+ OUT_RING(batch->start + batch->used - 4);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+ }
+ }
+
+ dev_priv->sarea_priv->last_enqueue = dev_priv->counter++;
+
+ BEGIN_LP_RING(4);
+ OUT_RING(CMD_STORE_DWORD_IDX);
+ OUT_RING(20);
+ OUT_RING(dev_priv->counter);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ return 0;
+}
+
+static int i915_dispatch_flip(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ DRM_DEBUG("%s: page=%d pfCurrentPage=%d\n",
+ __FUNCTION__,
+ dev_priv->current_page,
+ dev_priv->sarea_priv->pf_current_page);
+
+ i915_kernel_lost_context(dev);
+
+ BEGIN_LP_RING(2);
+ OUT_RING(INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ BEGIN_LP_RING(6);
+ OUT_RING(CMD_OP_DISPLAYBUFFER_INFO | ASYNC_FLIP);
+ OUT_RING(0);
+ if (dev_priv->current_page == 0) {
+ OUT_RING(dev_priv->back_offset);
+ dev_priv->current_page = 1;
+ } else {
+ OUT_RING(dev_priv->front_offset);
+ dev_priv->current_page = 0;
+ }
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ BEGIN_LP_RING(2);
+ OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_PLANE_A_FLIP);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ dev_priv->sarea_priv->last_enqueue = dev_priv->counter++;
+
+ BEGIN_LP_RING(4);
+ OUT_RING(CMD_STORE_DWORD_IDX);
+ OUT_RING(20);
+ OUT_RING(dev_priv->counter);
+ OUT_RING(0);
+ ADVANCE_LP_RING();
+
+ dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+ return 0;
+}
+
+static int i915_quiescent(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+
+ i915_kernel_lost_context(dev);
+ return i915_wait_ring(dev, dev_priv->ring.Size - 8, __FUNCTION__);
+}
+
+int i915_flush_ioctl(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ return i915_quiescent(dev);
+}
+
+int i915_batchbuffer(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i915_sarea_t *sarea_priv = (drm_i915_sarea_t *)
+ dev_priv->sarea_priv;
+ drm_i915_batchbuffer_t batch;
+ int ret;
+
+ if (!dev_priv->allow_batchbuffer) {
+ DRM_ERROR("Batchbuffer ioctl disabled\n");
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(batch, (drm_i915_batchbuffer_t __user *) data,
+ sizeof(batch));
+
+ DRM_DEBUG("i915 batchbuffer, start %x used %d cliprects %d\n",
+ batch.start, batch.used, batch.num_cliprects);
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (batch.num_cliprects && DRM_VERIFYAREA_READ(batch.cliprects,
+ batch.num_cliprects *
+ sizeof(drm_clip_rect_t)))
+ return DRM_ERR(EFAULT);
+
+ ret = i915_dispatch_batchbuffer(dev, &batch);
+
+ sarea_priv->last_dispatch = (int)hw_status[5];
+ return ret;
+}
+
+int i915_cmdbuffer(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ u32 *hw_status = dev_priv->hw_status_page;
+ drm_i915_sarea_t *sarea_priv = (drm_i915_sarea_t *)
+ dev_priv->sarea_priv;
+ drm_i915_cmdbuffer_t cmdbuf;
+ int ret;
+
+ DRM_COPY_FROM_USER_IOCTL(cmdbuf, (drm_i915_cmdbuffer_t __user *) data,
+ sizeof(cmdbuf));
+
+ DRM_DEBUG("i915 cmdbuffer, buf %p sz %d cliprects %d\n",
+ cmdbuf.buf, cmdbuf.sz, cmdbuf.num_cliprects);
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (cmdbuf.num_cliprects &&
+ DRM_VERIFYAREA_READ(cmdbuf.cliprects,
+ cmdbuf.num_cliprects *
+ sizeof(drm_clip_rect_t))) {
+ DRM_ERROR("Fault accessing cliprects\n");
+ return DRM_ERR(EFAULT);
+ }
+
+ ret = i915_dispatch_cmdbuffer(dev, &cmdbuf);
+ if (ret) {
+ DRM_ERROR("i915_dispatch_cmdbuffer failed\n");
+ return ret;
+ }
+
+ sarea_priv->last_dispatch = (int)hw_status[5];
+ return 0;
+}
+
+int i915_do_cleanup_pageflip(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+ if (dev_priv->current_page != 0)
+ i915_dispatch_flip(dev);
+
+ return 0;
+}
+
+int i915_flip_bufs(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ return i915_dispatch_flip(dev);
+}
+
+int i915_getparam(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_getparam_t param;
+ int value;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(param, (drm_i915_getparam_t __user *) data,
+ sizeof(param));
+
+ switch (param.param) {
+ case I915_PARAM_IRQ_ACTIVE:
+ value = dev->irq ? 1 : 0;
+ break;
+ case I915_PARAM_ALLOW_BATCHBUFFER:
+ value = dev_priv->allow_batchbuffer ? 1 : 0;
+ break;
+ default:
+ DRM_ERROR("Unkown parameter %d\n", param.param);
+ return DRM_ERR(EINVAL);
+ }
+
+ if (DRM_COPY_TO_USER(param.value, &value, sizeof(int))) {
+ DRM_ERROR("DRM_COPY_TO_USER failed\n");
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+int i915_setparam(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_setparam_t param;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(param, (drm_i915_setparam_t __user *) data,
+ sizeof(param));
+
+ switch (param.param) {
+ case I915_SETPARAM_USE_MI_BATCHBUFFER_START:
+ dev_priv->use_mi_batchbuffer_start = param.value;
+ break;
+ case I915_SETPARAM_TEX_LRU_LOG_GRANULARITY:
+ dev_priv->tex_lru_log_granularity = param.value;
+ break;
+ case I915_SETPARAM_ALLOW_BATCHBUFFER:
+ dev_priv->allow_batchbuffer = param.value;
+ break;
+ default:
+ DRM_ERROR("unknown parameter %d\n", param.param);
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
+
+void i915_driver_pretakedown(drm_device_t *dev)
+{
+ if ( dev->dev_private ) {
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ i915_mem_takedown( &(dev_priv->agp_heap) );
+ }
+ i915_dma_cleanup( dev );
+}
+
+void i915_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+ if ( dev->dev_private ) {
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ i915_mem_release( dev, filp, dev_priv->agp_heap );
+ }
+}
+
diff --git a/drivers/char/drm/i915_drm.h b/drivers/char/drm/i915_drm.h
new file mode 100644
index 000000000000..7e55edf45c4f
--- /dev/null
+++ b/drivers/char/drm/i915_drm.h
@@ -0,0 +1,167 @@
+#ifndef _I915_DRM_H_
+#define _I915_DRM_H_
+
+/* Please note that modifications to all structs defined here are
+ * subject to backwards-compatibility constraints.
+ */
+
+#include "drm.h"
+
+/* Each region is a minimum of 16k, and there are at most 255 of them.
+ */
+#define I915_NR_TEX_REGIONS 255 /* table size 2k - maximum due to use
+ * of chars for next/prev indices */
+#define I915_LOG_MIN_TEX_REGION_SIZE 14
+
+typedef struct _drm_i915_init {
+ enum {
+ I915_INIT_DMA = 0x01,
+ I915_CLEANUP_DMA = 0x02,
+ I915_RESUME_DMA = 0x03
+ } func;
+ unsigned int mmio_offset;
+ int sarea_priv_offset;
+ unsigned int ring_start;
+ unsigned int ring_end;
+ unsigned int ring_size;
+ unsigned int front_offset;
+ unsigned int back_offset;
+ unsigned int depth_offset;
+ unsigned int w;
+ unsigned int h;
+ unsigned int pitch;
+ unsigned int pitch_bits;
+ unsigned int back_pitch;
+ unsigned int depth_pitch;
+ unsigned int cpp;
+ unsigned int chipset;
+} drm_i915_init_t;
+
+typedef struct _drm_i915_sarea {
+ drm_tex_region_t texList[I915_NR_TEX_REGIONS + 1];
+ int last_upload; /* last time texture was uploaded */
+ int last_enqueue; /* last time a buffer was enqueued */
+ int last_dispatch; /* age of the most recently dispatched buffer */
+ int ctxOwner; /* last context to upload state */
+ int texAge;
+ int pf_enabled; /* is pageflipping allowed? */
+ int pf_active;
+ int pf_current_page; /* which buffer is being displayed? */
+ int perf_boxes; /* performance boxes to be displayed */
+} drm_i915_sarea_t;
+
+/* Flags for perf_boxes
+ */
+#define I915_BOX_RING_EMPTY 0x1
+#define I915_BOX_FLIP 0x2
+#define I915_BOX_WAIT 0x4
+#define I915_BOX_TEXTURE_LOAD 0x8
+#define I915_BOX_LOST_CONTEXT 0x10
+
+/* I915 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I915_INIT 0x00
+#define DRM_I915_FLUSH 0x01
+#define DRM_I915_FLIP 0x02
+#define DRM_I915_BATCHBUFFER 0x03
+#define DRM_I915_IRQ_EMIT 0x04
+#define DRM_I915_IRQ_WAIT 0x05
+#define DRM_I915_GETPARAM 0x06
+#define DRM_I915_SETPARAM 0x07
+#define DRM_I915_ALLOC 0x08
+#define DRM_I915_FREE 0x09
+#define DRM_I915_INIT_HEAP 0x0a
+#define DRM_I915_CMDBUFFER 0x0b
+
+#define DRM_IOCTL_I915_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t)
+#define DRM_IOCTL_I915_FLUSH DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH)
+#define DRM_IOCTL_I915_FLIP DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLIP)
+#define DRM_IOCTL_I915_BATCHBUFFER DRM_IOW( DRM_COMMAND_BASE + DRM_I915_BATCHBUFFER, drm_i915_batchbuffer_t)
+#define DRM_IOCTL_I915_IRQ_EMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_IRQ_EMIT, drm_i915_irq_emit_t)
+#define DRM_IOCTL_I915_IRQ_WAIT DRM_IOW( DRM_COMMAND_BASE + DRM_I915_IRQ_WAIT, drm_i915_irq_wait_t)
+#define DRM_IOCTL_I915_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GETPARAM, drm_i915_getparam_t)
+#define DRM_IOCTL_I915_SETPARAM DRM_IOW( DRM_COMMAND_BASE + DRM_I915_SETPARAM, drm_i915_setparam_t)
+#define DRM_IOCTL_I915_ALLOC DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_ALLOC, drm_i915_mem_alloc_t)
+#define DRM_IOCTL_I915_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_I915_FREE, drm_i915_mem_free_t)
+#define DRM_IOCTL_I915_INIT_HEAP DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT_HEAP, drm_i915_mem_init_heap_t)
+#define DRM_IOCTL_I915_CMDBUFFER DRM_IOW( DRM_COMMAND_BASE + DRM_I915_CMDBUFFER, drm_i915_cmdbuffer_t)
+
+/* Allow drivers to submit batchbuffers directly to hardware, relying
+ * on the security mechanisms provided by hardware.
+ */
+typedef struct _drm_i915_batchbuffer {
+ int start; /* agp offset */
+ int used; /* nr bytes in use */
+ int DR1; /* hw flags for GFX_OP_DRAWRECT_INFO */
+ int DR4; /* window origin for GFX_OP_DRAWRECT_INFO */
+ int num_cliprects; /* mulitpass with multiple cliprects? */
+ drm_clip_rect_t __user *cliprects; /* pointer to userspace cliprects */
+} drm_i915_batchbuffer_t;
+
+/* As above, but pass a pointer to userspace buffer which can be
+ * validated by the kernel prior to sending to hardware.
+ */
+typedef struct _drm_i915_cmdbuffer {
+ char __user *buf; /* pointer to userspace command buffer */
+ int sz; /* nr bytes in buf */
+ int DR1; /* hw flags for GFX_OP_DRAWRECT_INFO */
+ int DR4; /* window origin for GFX_OP_DRAWRECT_INFO */
+ int num_cliprects; /* mulitpass with multiple cliprects? */
+ drm_clip_rect_t __user *cliprects; /* pointer to userspace cliprects */
+} drm_i915_cmdbuffer_t;
+
+/* Userspace can request & wait on irq's:
+ */
+typedef struct drm_i915_irq_emit {
+ int __user *irq_seq;
+} drm_i915_irq_emit_t;
+
+typedef struct drm_i915_irq_wait {
+ int irq_seq;
+} drm_i915_irq_wait_t;
+
+/* Ioctl to query kernel params:
+ */
+#define I915_PARAM_IRQ_ACTIVE 1
+#define I915_PARAM_ALLOW_BATCHBUFFER 2
+
+typedef struct drm_i915_getparam {
+ int param;
+ int __user *value;
+} drm_i915_getparam_t;
+
+/* Ioctl to set kernel params:
+ */
+#define I915_SETPARAM_USE_MI_BATCHBUFFER_START 1
+#define I915_SETPARAM_TEX_LRU_LOG_GRANULARITY 2
+#define I915_SETPARAM_ALLOW_BATCHBUFFER 3
+
+typedef struct drm_i915_setparam {
+ int param;
+ int value;
+} drm_i915_setparam_t;
+
+/* A memory manager for regions of shared memory:
+ */
+#define I915_MEM_REGION_AGP 1
+
+typedef struct drm_i915_mem_alloc {
+ int region;
+ int alignment;
+ int size;
+ int __user *region_offset; /* offset from start of fb or agp */
+} drm_i915_mem_alloc_t;
+
+typedef struct drm_i915_mem_free {
+ int region;
+ int region_offset;
+} drm_i915_mem_free_t;
+
+typedef struct drm_i915_mem_init_heap {
+ int region;
+ int size;
+ int start;
+} drm_i915_mem_init_heap_t;
+
+#endif /* _I915_DRM_H_ */
diff --git a/drivers/char/drm/i915_drv.c b/drivers/char/drm/i915_drv.c
new file mode 100644
index 000000000000..002b7082e21b
--- /dev/null
+++ b/drivers/char/drm/i915_drv.c
@@ -0,0 +1,104 @@
+/* i915_drv.c -- i830,i845,i855,i865,i915 driver -*- linux-c -*-
+ */
+
+/**************************************************************************
+ *
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+#include "drm_pciids.h"
+
+int postinit( struct drm_device *dev, unsigned long flags )
+{
+ dev->counters += 4;
+ dev->types[6] = _DRM_STAT_IRQ;
+ dev->types[7] = _DRM_STAT_PRIMARY;
+ dev->types[8] = _DRM_STAT_SECONDARY;
+ dev->types[9] = _DRM_STAT_DMA;
+
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ i915_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i915_ioctls[];
+extern int i915_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR |
+ DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED,
+ .pretakedown = i915_driver_pretakedown,
+ .prerelease = i915_driver_prerelease,
+ .irq_preinstall = i915_driver_irq_preinstall,
+ .irq_postinstall = i915_driver_irq_postinstall,
+ .irq_uninstall = i915_driver_irq_uninstall,
+ .irq_handler = i915_driver_irq_handler,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = i915_ioctls,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init i915_init(void)
+{
+ driver.num_ioctls = i915_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit i915_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(i915_init);
+module_exit(i915_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i915_drv.h b/drivers/char/drm/i915_drv.h
new file mode 100644
index 000000000000..f6ca92a565db
--- /dev/null
+++ b/drivers/char/drm/i915_drv.h
@@ -0,0 +1,243 @@
+/* i915_drv.h -- Private header for the I915 driver -*- linux-c -*-
+ */
+/**************************************************************************
+ *
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ **************************************************************************/
+
+#ifndef _I915_DRV_H_
+#define _I915_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "Tungsten Graphics, Inc."
+
+#define DRIVER_NAME "i915"
+#define DRIVER_DESC "Intel Graphics"
+#define DRIVER_DATE "20040405"
+
+/* Interface history:
+ *
+ * 1.1: Original.
+ */
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 1
+#define DRIVER_PATCHLEVEL 0
+
+/* We use our own dma mechanisms, not the drm template code. However,
+ * the shared IRQ code is useful to us:
+ */
+#define __HAVE_PM 1
+
+typedef struct _drm_i915_ring_buffer {
+ int tail_mask;
+ unsigned long Start;
+ unsigned long End;
+ unsigned long Size;
+ u8 *virtual_start;
+ int head;
+ int tail;
+ int space;
+ drm_local_map_t map;
+} drm_i915_ring_buffer_t;
+
+struct mem_block {
+ struct mem_block *next;
+ struct mem_block *prev;
+ int start;
+ int size;
+ DRMFILE filp; /* 0: free, -1: heap, other: real files */
+};
+
+typedef struct drm_i915_private {
+ drm_local_map_t *sarea;
+ drm_local_map_t *mmio_map;
+
+ drm_i915_sarea_t *sarea_priv;
+ drm_i915_ring_buffer_t ring;
+
+ void *hw_status_page;
+ unsigned long counter;
+ dma_addr_t dma_status_page;
+
+ int back_offset;
+ int front_offset;
+ int current_page;
+ int page_flipping;
+ int use_mi_batchbuffer_start;
+
+ wait_queue_head_t irq_queue;
+ atomic_t irq_received;
+ atomic_t irq_emitted;
+
+ int tex_lru_log_granularity;
+ int allow_batchbuffer;
+ struct mem_block *agp_heap;
+} drm_i915_private_t;
+
+ /* i915_dma.c */
+extern int i915_dma_init(DRM_IOCTL_ARGS);
+extern int i915_dma_cleanup(drm_device_t * dev);
+extern int i915_flush_ioctl(DRM_IOCTL_ARGS);
+extern int i915_batchbuffer(DRM_IOCTL_ARGS);
+extern int i915_flip_bufs(DRM_IOCTL_ARGS);
+extern int i915_getparam(DRM_IOCTL_ARGS);
+extern int i915_setparam(DRM_IOCTL_ARGS);
+extern int i915_cmdbuffer(DRM_IOCTL_ARGS);
+extern void i915_kernel_lost_context(drm_device_t * dev);
+extern void i915_driver_pretakedown(drm_device_t *dev);
+extern void i915_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+/* i915_irq.c */
+extern int i915_irq_emit(DRM_IOCTL_ARGS);
+extern int i915_irq_wait(DRM_IOCTL_ARGS);
+extern int i915_wait_irq(drm_device_t * dev, int irq_nr);
+extern int i915_emit_irq(drm_device_t * dev);
+
+extern irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS);
+extern void i915_driver_irq_preinstall(drm_device_t *dev);
+extern void i915_driver_irq_postinstall(drm_device_t *dev);
+extern void i915_driver_irq_uninstall(drm_device_t *dev);
+
+/* i915_mem.c */
+extern int i915_mem_alloc(DRM_IOCTL_ARGS);
+extern int i915_mem_free(DRM_IOCTL_ARGS);
+extern int i915_mem_init_heap(DRM_IOCTL_ARGS);
+extern void i915_mem_takedown(struct mem_block **heap);
+extern void i915_mem_release(drm_device_t * dev,
+ DRMFILE filp, struct mem_block *heap);
+
+#define I915_READ(reg) DRM_READ32(dev_priv->mmio_map, reg)
+#define I915_WRITE(reg,val) DRM_WRITE32(dev_priv->mmio_map, reg, val)
+#define I915_READ16(reg) DRM_READ16(dev_priv->mmio_map, reg)
+#define I915_WRITE16(reg,val) DRM_WRITE16(dev_priv->mmio_map, reg, val)
+
+#define I915_VERBOSE 0
+
+#define RING_LOCALS unsigned int outring, ringmask, outcount; \
+ volatile char *virt;
+
+#define BEGIN_LP_RING(n) do { \
+ if (I915_VERBOSE) \
+ DRM_DEBUG("BEGIN_LP_RING(%d) in %s\n", \
+ n, __FUNCTION__); \
+ if (dev_priv->ring.space < n*4) \
+ i915_wait_ring(dev, n*4, __FUNCTION__); \
+ outcount = 0; \
+ outring = dev_priv->ring.tail; \
+ ringmask = dev_priv->ring.tail_mask; \
+ virt = dev_priv->ring.virtual_start; \
+} while (0)
+
+#define OUT_RING(n) do { \
+ if (I915_VERBOSE) DRM_DEBUG(" OUT_RING %x\n", (int)(n)); \
+ *(volatile unsigned int *)(virt + outring) = n; \
+ outcount++; \
+ outring += 4; \
+ outring &= ringmask; \
+} while (0)
+
+#define ADVANCE_LP_RING() do { \
+ if (I915_VERBOSE) DRM_DEBUG("ADVANCE_LP_RING %x\n", outring); \
+ dev_priv->ring.tail = outring; \
+ dev_priv->ring.space -= outcount * 4; \
+ I915_WRITE(LP_RING + RING_TAIL, outring); \
+} while(0)
+
+extern int i915_wait_ring(drm_device_t * dev, int n, const char *caller);
+
+#define GFX_OP_USER_INTERRUPT ((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT ((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD (7<<23)
+#define CMD_STORE_DWORD_IDX ((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER ((0x0<<29)|(0x30<<23)|0x1)
+
+#define INST_PARSER_CLIENT 0x00000000
+#define INST_OP_FLUSH 0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+#define BB1_START_ADDR_MASK (~0x7)
+#define BB1_PROTECTED (1<<0)
+#define BB1_UNPROTECTED (0<<0)
+#define BB2_END_ADDR_MASK (~0x7)
+
+#define I915REG_HWSTAM 0x02098
+#define I915REG_INT_IDENTITY_R 0x020a4
+#define I915REG_INT_MASK_R 0x020a8
+#define I915REG_INT_ENABLE_R 0x020a0
+
+#define SRX_INDEX 0x3c4
+#define SRX_DATA 0x3c5
+#define SR01 1
+#define SR01_SCREEN_OFF (1<<5)
+
+#define PPCR 0x61204
+#define PPCR_ON (1<<0)
+
+#define ADPA 0x61100
+#define ADPA_DPMS_MASK (~(3<<10))
+#define ADPA_DPMS_ON (0<<10)
+#define ADPA_DPMS_SUSPEND (1<<10)
+#define ADPA_DPMS_STANDBY (2<<10)
+#define ADPA_DPMS_OFF (3<<10)
+
+#define NOPID 0x2094
+#define LP_RING 0x2030
+#define HP_RING 0x2040
+#define RING_TAIL 0x00
+#define TAIL_ADDR 0x001FFFF8
+#define RING_HEAD 0x04
+#define HEAD_WRAP_COUNT 0xFFE00000
+#define HEAD_WRAP_ONE 0x00200000
+#define HEAD_ADDR 0x001FFFFC
+#define RING_START 0x08
+#define START_ADDR 0x0xFFFFF000
+#define RING_LEN 0x0C
+#define RING_NR_PAGES 0x001FF000
+#define RING_REPORT_MASK 0x00000006
+#define RING_REPORT_64K 0x00000002
+#define RING_REPORT_128K 0x00000004
+#define RING_NO_REPORT 0x00000000
+#define RING_VALID_MASK 0x00000001
+#define RING_VALID 0x00000001
+#define RING_INVALID 0x00000000
+
+#define GFX_OP_SCISSOR ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR (0x1<<1)
+#define SC_ENABLE_MASK (0x1<<0)
+#define SC_ENABLE (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK (0xffff<<16)
+#define SCI_XMIN_MASK (0xffff<<0)
+#define SCI_YMAX_MASK (0xffff<<16)
+#define SCI_XMAX_MASK (0xffff<<0)
+
+#define GFX_OP_SCISSOR_ENABLE ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define GFX_OP_SCISSOR_RECT ((0x3<<29)|(0x1d<<24)|(0x81<<16)|1)
+#define GFX_OP_COLOR_FACTOR ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO ((0x3<<29)|(0x1d<<24)|0x4)
+#define GFX_OP_DESTBUFFER_VARS ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+
+#define MI_BATCH_BUFFER ((0x30<<23)|1)
+#define MI_BATCH_BUFFER_START (0x31<<23)
+#define MI_BATCH_BUFFER_END (0xA<<23)
+#define MI_BATCH_NON_SECURE (1)
+
+#define MI_WAIT_FOR_EVENT ((0x3<<23))
+#define MI_WAIT_FOR_PLANE_A_FLIP (1<<2)
+#define MI_WAIT_FOR_PLANE_A_SCANLINES (1<<1)
+
+#define MI_LOAD_SCAN_LINES_INCL ((0x12<<23))
+
+#define CMD_OP_DISPLAYBUFFER_INFO ((0x0<<29)|(0x14<<23)|2)
+#define ASYNC_FLIP (1<<22)
+
+#define CMD_OP_DESTBUFFER_INFO ((0x3<<29)|(0x1d<<24)|(0x8e<<16)|1)
+
+#endif
diff --git a/drivers/char/drm/i915_irq.c b/drivers/char/drm/i915_irq.c
new file mode 100644
index 000000000000..b0239262a84a
--- /dev/null
+++ b/drivers/char/drm/i915_irq.c
@@ -0,0 +1,161 @@
+/* i915_dma.c -- DMA support for the I915 -*- linux-c -*-
+ */
+/**************************************************************************
+ *
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+#define USER_INT_FLAG 0x2
+#define MAX_NOPID ((u32)~0)
+#define READ_BREADCRUMB(dev_priv) (((u32*)(dev_priv->hw_status_page))[5])
+
+irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
+{
+ drm_device_t *dev = (drm_device_t *) arg;
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ u16 temp;
+
+ temp = I915_READ16(I915REG_INT_IDENTITY_R);
+ temp &= USER_INT_FLAG;
+
+ DRM_DEBUG("%s flag=%08x\n", __FUNCTION__, temp);
+
+ if (temp == 0)
+ return IRQ_NONE;
+
+ I915_WRITE16(I915REG_INT_IDENTITY_R, temp);
+ DRM_WAKEUP(&dev_priv->irq_queue);
+
+ return IRQ_HANDLED;
+}
+
+int i915_emit_irq(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ u32 ret;
+ RING_LOCALS;
+
+ i915_kernel_lost_context(dev);
+
+ DRM_DEBUG("%s\n", __FUNCTION__);
+
+ ret = dev_priv->counter;
+
+ BEGIN_LP_RING(2);
+ OUT_RING(0);
+ OUT_RING(GFX_OP_USER_INTERRUPT);
+ ADVANCE_LP_RING();
+
+ return ret;
+}
+
+int i915_wait_irq(drm_device_t * dev, int irq_nr)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ int ret = 0;
+
+ DRM_DEBUG("%s irq_nr=%d breadcrumb=%d\n", __FUNCTION__, irq_nr,
+ READ_BREADCRUMB(dev_priv));
+
+ if (READ_BREADCRUMB(dev_priv) >= irq_nr)
+ return 0;
+
+ dev_priv->sarea_priv->perf_boxes |= I915_BOX_WAIT;
+
+ DRM_WAIT_ON(ret, dev_priv->irq_queue, 3 * DRM_HZ,
+ READ_BREADCRUMB(dev_priv) >= irq_nr);
+
+ if (ret == DRM_ERR(EBUSY)) {
+ DRM_ERROR("%s: EBUSY -- rec: %d emitted: %d\n",
+ __FUNCTION__,
+ READ_BREADCRUMB(dev_priv), (int)dev_priv->counter);
+ }
+
+ dev_priv->sarea_priv->last_dispatch = READ_BREADCRUMB(dev_priv);
+ return ret;
+}
+
+/* Needs the lock as it touches the ring.
+ */
+int i915_irq_emit(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_irq_emit_t emit;
+ int result;
+
+ LOCK_TEST_WITH_RETURN(dev, filp);
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(emit, (drm_i915_irq_emit_t __user *) data,
+ sizeof(emit));
+
+ result = i915_emit_irq(dev);
+
+ if (DRM_COPY_TO_USER(emit.irq_seq, &result, sizeof(int))) {
+ DRM_ERROR("copy_to_user\n");
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+/* Doesn't need the hardware lock.
+ */
+int i915_irq_wait(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_irq_wait_t irqwait;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(irqwait, (drm_i915_irq_wait_t __user *) data,
+ sizeof(irqwait));
+
+ return i915_wait_irq(dev, irqwait.irq_seq);
+}
+
+/* drm_dma.h hooks
+*/
+void i915_driver_irq_preinstall(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+ I915_WRITE16(I915REG_HWSTAM, 0xfffe);
+ I915_WRITE16(I915REG_INT_MASK_R, 0x0);
+ I915_WRITE16(I915REG_INT_ENABLE_R, 0x0);
+}
+
+void i915_driver_irq_postinstall(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+ I915_WRITE16(I915REG_INT_ENABLE_R, USER_INT_FLAG);
+ DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);
+}
+
+void i915_driver_irq_uninstall(drm_device_t * dev)
+{
+ drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ I915_WRITE16(I915REG_HWSTAM, 0xffff);
+ I915_WRITE16(I915REG_INT_MASK_R, 0xffff);
+ I915_WRITE16(I915REG_INT_ENABLE_R, 0x0);
+}
diff --git a/drivers/char/drm/i915_mem.c b/drivers/char/drm/i915_mem.c
new file mode 100644
index 000000000000..d54a3005946b
--- /dev/null
+++ b/drivers/char/drm/i915_mem.c
@@ -0,0 +1,346 @@
+/* i915_mem.c -- Simple agp/fb memory manager for i915 -*- linux-c -*-
+ */
+/**************************************************************************
+ *
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+/* This memory manager is integrated into the global/local lru
+ * mechanisms used by the clients. Specifically, it operates by
+ * setting the 'in_use' fields of the global LRU to indicate whether
+ * this region is privately allocated to a client.
+ *
+ * This does require the client to actually respect that field.
+ *
+ * Currently no effort is made to allocate 'private' memory in any
+ * clever way - the LRU information isn't used to determine which
+ * block to allocate, and the ring is drained prior to allocations --
+ * in other words allocation is expensive.
+ */
+static void mark_block(drm_device_t * dev, struct mem_block *p, int in_use)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_tex_region_t *list;
+ unsigned shift, nr;
+ unsigned start;
+ unsigned end;
+ unsigned i;
+ int age;
+
+ shift = dev_priv->tex_lru_log_granularity;
+ nr = I915_NR_TEX_REGIONS;
+
+ start = p->start >> shift;
+ end = (p->start + p->size - 1) >> shift;
+
+ age = ++sarea_priv->texAge;
+ list = sarea_priv->texList;
+
+ /* Mark the regions with the new flag and update their age. Move
+ * them to head of list to preserve LRU semantics.
+ */
+ for (i = start; i <= end; i++) {
+ list[i].in_use = in_use;
+ list[i].age = age;
+
+ /* remove_from_list(i)
+ */
+ list[(unsigned)list[i].next].prev = list[i].prev;
+ list[(unsigned)list[i].prev].next = list[i].next;
+
+ /* insert_at_head(list, i)
+ */
+ list[i].prev = nr;
+ list[i].next = list[nr].next;
+ list[(unsigned)list[nr].next].prev = i;
+ list[nr].next = i;
+ }
+}
+
+/* Very simple allocator for agp memory, working on a static range
+ * already mapped into each client's address space.
+ */
+
+static struct mem_block *split_block(struct mem_block *p, int start, int size,
+ DRMFILE filp)
+{
+ /* Maybe cut off the start of an existing block */
+ if (start > p->start) {
+ struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
+ if (!newblock)
+ goto out;
+ newblock->start = start;
+ newblock->size = p->size - (start - p->start);
+ newblock->filp = NULL;
+ newblock->next = p->next;
+ newblock->prev = p;
+ p->next->prev = newblock;
+ p->next = newblock;
+ p->size -= newblock->size;
+ p = newblock;
+ }
+
+ /* Maybe cut off the end of an existing block */
+ if (size < p->size) {
+ struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
+ if (!newblock)
+ goto out;
+ newblock->start = start + size;
+ newblock->size = p->size - size;
+ newblock->filp = NULL;
+ newblock->next = p->next;
+ newblock->prev = p;
+ p->next->prev = newblock;
+ p->next = newblock;
+ p->size = size;
+ }
+
+ out:
+ /* Our block is in the middle */
+ p->filp = filp;
+ return p;
+}
+
+static struct mem_block *alloc_block(struct mem_block *heap, int size,
+ int align2, DRMFILE filp)
+{
+ struct mem_block *p;
+ int mask = (1 << align2) - 1;
+
+ for (p = heap->next; p != heap; p = p->next) {
+ int start = (p->start + mask) & ~mask;
+ if (p->filp == NULL && start + size <= p->start + p->size)
+ return split_block(p, start, size, filp);
+ }
+
+ return NULL;
+}
+
+static struct mem_block *find_block(struct mem_block *heap, int start)
+{
+ struct mem_block *p;
+
+ for (p = heap->next; p != heap; p = p->next)
+ if (p->start == start)
+ return p;
+
+ return NULL;
+}
+
+static void free_block(struct mem_block *p)
+{
+ p->filp = NULL;
+
+ /* Assumes a single contiguous range. Needs a special filp in
+ * 'heap' to stop it being subsumed.
+ */
+ if (p->next->filp == NULL) {
+ struct mem_block *q = p->next;
+ p->size += q->size;
+ p->next = q->next;
+ p->next->prev = p;
+ drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+ }
+
+ if (p->prev->filp == NULL) {
+ struct mem_block *q = p->prev;
+ q->size += p->size;
+ q->next = p->next;
+ q->next->prev = q;
+ drm_free(p, sizeof(*q), DRM_MEM_BUFLISTS);
+ }
+}
+
+/* Initialize. How to check for an uninitialized heap?
+ */
+static int init_heap(struct mem_block **heap, int start, int size)
+{
+ struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFLISTS);
+
+ if (!blocks)
+ return -ENOMEM;
+
+ *heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFLISTS);
+ if (!*heap) {
+ drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFLISTS);
+ return -ENOMEM;
+ }
+
+ blocks->start = start;
+ blocks->size = size;
+ blocks->filp = NULL;
+ blocks->next = blocks->prev = *heap;
+
+ memset(*heap, 0, sizeof(**heap));
+ (*heap)->filp = (DRMFILE) - 1;
+ (*heap)->next = (*heap)->prev = blocks;
+ return 0;
+}
+
+/* Free all blocks associated with the releasing file.
+ */
+void i915_mem_release(drm_device_t * dev, DRMFILE filp, struct mem_block *heap)
+{
+ struct mem_block *p;
+
+ if (!heap || !heap->next)
+ return;
+
+ for (p = heap->next; p != heap; p = p->next) {
+ if (p->filp == filp) {
+ p->filp = NULL;
+ mark_block(dev, p, 0);
+ }
+ }
+
+ /* Assumes a single contiguous range. Needs a special filp in
+ * 'heap' to stop it being subsumed.
+ */
+ for (p = heap->next; p != heap; p = p->next) {
+ while (p->filp == NULL && p->next->filp == NULL) {
+ struct mem_block *q = p->next;
+ p->size += q->size;
+ p->next = q->next;
+ p->next->prev = p;
+ drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+ }
+ }
+}
+
+/* Shutdown.
+ */
+void i915_mem_takedown(struct mem_block **heap)
+{
+ struct mem_block *p;
+
+ if (!*heap)
+ return;
+
+ for (p = (*heap)->next; p != *heap;) {
+ struct mem_block *q = p;
+ p = p->next;
+ drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+ }
+
+ drm_free(*heap, sizeof(**heap), DRM_MEM_BUFLISTS);
+ *heap = NULL;
+}
+
+static struct mem_block **get_heap(drm_i915_private_t * dev_priv, int region)
+{
+ switch (region) {
+ case I915_MEM_REGION_AGP:
+ return &dev_priv->agp_heap;
+ default:
+ return NULL;
+ }
+}
+
+/* IOCTL HANDLERS */
+
+int i915_mem_alloc(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_mem_alloc_t alloc;
+ struct mem_block *block, **heap;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(alloc, (drm_i915_mem_alloc_t __user *) data,
+ sizeof(alloc));
+
+ heap = get_heap(dev_priv, alloc.region);
+ if (!heap || !*heap)
+ return DRM_ERR(EFAULT);
+
+ /* Make things easier on ourselves: all allocations at least
+ * 4k aligned.
+ */
+ if (alloc.alignment < 12)
+ alloc.alignment = 12;
+
+ block = alloc_block(*heap, alloc.size, alloc.alignment, filp);
+
+ if (!block)
+ return DRM_ERR(ENOMEM);
+
+ mark_block(dev, block, 1);
+
+ if (DRM_COPY_TO_USER(alloc.region_offset, &block->start, sizeof(int))) {
+ DRM_ERROR("copy_to_user\n");
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+int i915_mem_free(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_mem_free_t memfree;
+ struct mem_block *block, **heap;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(memfree, (drm_i915_mem_free_t __user *) data,
+ sizeof(memfree));
+
+ heap = get_heap(dev_priv, memfree.region);
+ if (!heap || !*heap)
+ return DRM_ERR(EFAULT);
+
+ block = find_block(*heap, memfree.region_offset);
+ if (!block)
+ return DRM_ERR(EFAULT);
+
+ if (block->filp != filp)
+ return DRM_ERR(EPERM);
+
+ mark_block(dev, block, 0);
+ free_block(block);
+ return 0;
+}
+
+int i915_mem_init_heap(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_i915_private_t *dev_priv = dev->dev_private;
+ drm_i915_mem_init_heap_t initheap;
+ struct mem_block **heap;
+
+ if (!dev_priv) {
+ DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(initheap,
+ (drm_i915_mem_init_heap_t __user *) data,
+ sizeof(initheap));
+
+ heap = get_heap(dev_priv, initheap.region);
+ if (!heap)
+ return DRM_ERR(EFAULT);
+
+ if (*heap) {
+ DRM_ERROR("heap already initialized?");
+ return DRM_ERR(EFAULT);
+ }
+
+ return init_heap(heap, initheap.start, initheap.size);
+}
diff --git a/drivers/char/drm/mga_dma.c b/drivers/char/drm/mga_dma.c
new file mode 100644
index 000000000000..832eaf8a5068
--- /dev/null
+++ b/drivers/char/drm/mga_dma.c
@@ -0,0 +1,754 @@
+/* mga_dma.c -- DMA support for mga g200/g400 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+#define MGA_DEFAULT_USEC_TIMEOUT 10000
+#define MGA_FREELIST_DEBUG 0
+
+static int mga_do_cleanup_dma( drm_device_t *dev );
+
+/* ================================================================
+ * Engine control
+ */
+
+int mga_do_wait_for_idle( drm_mga_private_t *dev_priv )
+{
+ u32 status = 0;
+ int i;
+ DRM_DEBUG( "\n" );
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+ if ( status == MGA_ENDPRDMASTS ) {
+ MGA_WRITE8( MGA_CRTC_INDEX, 0 );
+ return 0;
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if MGA_DMA_DEBUG
+ DRM_ERROR( "failed!\n" );
+ DRM_INFO( " status=0x%08x\n", status );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int mga_do_dma_reset( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+
+ DRM_DEBUG( "\n" );
+
+ /* The primary DMA stream should look like new right about now.
+ */
+ primary->tail = 0;
+ primary->space = primary->size;
+ primary->last_flush = 0;
+
+ sarea_priv->last_wrap = 0;
+
+ /* FIXME: Reset counters, buffer ages etc...
+ */
+
+ /* FIXME: What else do we need to reinitialize? WARP stuff?
+ */
+
+ return 0;
+}
+
+/* ================================================================
+ * Primary DMA stream
+ */
+
+void mga_do_dma_flush( drm_mga_private_t *dev_priv )
+{
+ drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+ u32 head, tail;
+ u32 status = 0;
+ int i;
+ DMA_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ /* We need to wait so that we can do an safe flush */
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+ if ( status == MGA_ENDPRDMASTS ) break;
+ DRM_UDELAY( 1 );
+ }
+
+ if ( primary->tail == primary->last_flush ) {
+ DRM_DEBUG( " bailing out...\n" );
+ return;
+ }
+
+ tail = primary->tail + dev_priv->primary->offset;
+
+ /* We need to pad the stream between flushes, as the card
+ * actually (partially?) reads the first of these commands.
+ * See page 4-16 in the G400 manual, middle of the page or so.
+ */
+ BEGIN_DMA( 1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ ADVANCE_DMA();
+
+ primary->last_flush = primary->tail;
+
+ head = MGA_READ( MGA_PRIMADDRESS );
+
+ if ( head <= tail ) {
+ primary->space = primary->size - primary->tail;
+ } else {
+ primary->space = head - tail;
+ }
+
+ DRM_DEBUG( " head = 0x%06lx\n", head - dev_priv->primary->offset );
+ DRM_DEBUG( " tail = 0x%06lx\n", tail - dev_priv->primary->offset );
+ DRM_DEBUG( " space = 0x%06x\n", primary->space );
+
+ mga_flush_write_combine();
+ MGA_WRITE( MGA_PRIMEND, tail | MGA_PAGPXFER );
+
+ DRM_DEBUG( "done.\n" );
+}
+
+void mga_do_dma_wrap_start( drm_mga_private_t *dev_priv )
+{
+ drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+ u32 head, tail;
+ DMA_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ BEGIN_DMA_WRAP();
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ ADVANCE_DMA();
+
+ tail = primary->tail + dev_priv->primary->offset;
+
+ primary->tail = 0;
+ primary->last_flush = 0;
+ primary->last_wrap++;
+
+ head = MGA_READ( MGA_PRIMADDRESS );
+
+ if ( head == dev_priv->primary->offset ) {
+ primary->space = primary->size;
+ } else {
+ primary->space = head - dev_priv->primary->offset;
+ }
+
+ DRM_DEBUG( " head = 0x%06lx\n",
+ head - dev_priv->primary->offset );
+ DRM_DEBUG( " tail = 0x%06x\n", primary->tail );
+ DRM_DEBUG( " wrap = %d\n", primary->last_wrap );
+ DRM_DEBUG( " space = 0x%06x\n", primary->space );
+
+ mga_flush_write_combine();
+ MGA_WRITE( MGA_PRIMEND, tail | MGA_PAGPXFER );
+
+ set_bit( 0, &primary->wrapped );
+ DRM_DEBUG( "done.\n" );
+}
+
+void mga_do_dma_wrap_end( drm_mga_private_t *dev_priv )
+{
+ drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ u32 head = dev_priv->primary->offset;
+ DRM_DEBUG( "\n" );
+
+ sarea_priv->last_wrap++;
+ DRM_DEBUG( " wrap = %d\n", sarea_priv->last_wrap );
+
+ mga_flush_write_combine();
+ MGA_WRITE( MGA_PRIMADDRESS, head | MGA_DMA_GENERAL );
+
+ clear_bit( 0, &primary->wrapped );
+ DRM_DEBUG( "done.\n" );
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+
+#define MGA_BUFFER_USED ~0
+#define MGA_BUFFER_FREE 0
+
+#if MGA_FREELIST_DEBUG
+static void mga_freelist_print( drm_device_t *dev )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_freelist_t *entry;
+
+ DRM_INFO( "\n" );
+ DRM_INFO( "current dispatch: last=0x%x done=0x%x\n",
+ dev_priv->sarea_priv->last_dispatch,
+ (unsigned int)(MGA_READ( MGA_PRIMADDRESS ) -
+ dev_priv->primary->offset) );
+ DRM_INFO( "current freelist:\n" );
+
+ for ( entry = dev_priv->head->next ; entry ; entry = entry->next ) {
+ DRM_INFO( " %p idx=%2d age=0x%x 0x%06lx\n",
+ entry, entry->buf->idx, entry->age.head,
+ entry->age.head - dev_priv->primary->offset );
+ }
+ DRM_INFO( "\n" );
+}
+#endif
+
+static int mga_freelist_init( drm_device_t *dev, drm_mga_private_t *dev_priv )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_mga_buf_priv_t *buf_priv;
+ drm_mga_freelist_t *entry;
+ int i;
+ DRM_DEBUG( "count=%d\n", dma->buf_count );
+
+ dev_priv->head = drm_alloc( sizeof(drm_mga_freelist_t),
+ DRM_MEM_DRIVER );
+ if ( dev_priv->head == NULL )
+ return DRM_ERR(ENOMEM);
+
+ memset( dev_priv->head, 0, sizeof(drm_mga_freelist_t) );
+ SET_AGE( &dev_priv->head->age, MGA_BUFFER_USED, 0 );
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+
+ entry = drm_alloc( sizeof(drm_mga_freelist_t),
+ DRM_MEM_DRIVER );
+ if ( entry == NULL )
+ return DRM_ERR(ENOMEM);
+
+ memset( entry, 0, sizeof(drm_mga_freelist_t) );
+
+ entry->next = dev_priv->head->next;
+ entry->prev = dev_priv->head;
+ SET_AGE( &entry->age, MGA_BUFFER_FREE, 0 );
+ entry->buf = buf;
+
+ if ( dev_priv->head->next != NULL )
+ dev_priv->head->next->prev = entry;
+ if ( entry->next == NULL )
+ dev_priv->tail = entry;
+
+ buf_priv->list_entry = entry;
+ buf_priv->discard = 0;
+ buf_priv->dispatched = 0;
+
+ dev_priv->head->next = entry;
+ }
+
+ return 0;
+}
+
+static void mga_freelist_cleanup( drm_device_t *dev )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_freelist_t *entry;
+ drm_mga_freelist_t *next;
+ DRM_DEBUG( "\n" );
+
+ entry = dev_priv->head;
+ while ( entry ) {
+ next = entry->next;
+ drm_free( entry, sizeof(drm_mga_freelist_t), DRM_MEM_DRIVER );
+ entry = next;
+ }
+
+ dev_priv->head = dev_priv->tail = NULL;
+}
+
+#if 0
+/* FIXME: Still needed?
+ */
+static void mga_freelist_reset( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_mga_buf_priv_t *buf_priv;
+ int i;
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+ SET_AGE( &buf_priv->list_entry->age,
+ MGA_BUFFER_FREE, 0 );
+ }
+}
+#endif
+
+static drm_buf_t *mga_freelist_get( drm_device_t *dev )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_freelist_t *next;
+ drm_mga_freelist_t *prev;
+ drm_mga_freelist_t *tail = dev_priv->tail;
+ u32 head, wrap;
+ DRM_DEBUG( "\n" );
+
+ head = MGA_READ( MGA_PRIMADDRESS );
+ wrap = dev_priv->sarea_priv->last_wrap;
+
+ DRM_DEBUG( " tail=0x%06lx %d\n",
+ tail->age.head ?
+ tail->age.head - dev_priv->primary->offset : 0,
+ tail->age.wrap );
+ DRM_DEBUG( " head=0x%06lx %d\n",
+ head - dev_priv->primary->offset, wrap );
+
+ if ( TEST_AGE( &tail->age, head, wrap ) ) {
+ prev = dev_priv->tail->prev;
+ next = dev_priv->tail;
+ prev->next = NULL;
+ next->prev = next->next = NULL;
+ dev_priv->tail = prev;
+ SET_AGE( &next->age, MGA_BUFFER_USED, 0 );
+ return next->buf;
+ }
+
+ DRM_DEBUG( "returning NULL!\n" );
+ return NULL;
+}
+
+int mga_freelist_put( drm_device_t *dev, drm_buf_t *buf )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+ drm_mga_freelist_t *head, *entry, *prev;
+
+ DRM_DEBUG( "age=0x%06lx wrap=%d\n",
+ buf_priv->list_entry->age.head -
+ dev_priv->primary->offset,
+ buf_priv->list_entry->age.wrap );
+
+ entry = buf_priv->list_entry;
+ head = dev_priv->head;
+
+ if ( buf_priv->list_entry->age.head == MGA_BUFFER_USED ) {
+ SET_AGE( &entry->age, MGA_BUFFER_FREE, 0 );
+ prev = dev_priv->tail;
+ prev->next = entry;
+ entry->prev = prev;
+ entry->next = NULL;
+ } else {
+ prev = head->next;
+ head->next = entry;
+ prev->prev = entry;
+ entry->prev = head;
+ entry->next = prev;
+ }
+
+ return 0;
+}
+
+
+/* ================================================================
+ * DMA initialization, cleanup
+ */
+
+static int mga_do_init_dma( drm_device_t *dev, drm_mga_init_t *init )
+{
+ drm_mga_private_t *dev_priv;
+ int ret;
+ DRM_DEBUG( "\n" );
+
+ dev_priv = drm_alloc( sizeof(drm_mga_private_t), DRM_MEM_DRIVER );
+ if ( !dev_priv )
+ return DRM_ERR(ENOMEM);
+
+ memset( dev_priv, 0, sizeof(drm_mga_private_t) );
+
+ dev_priv->chipset = init->chipset;
+
+ dev_priv->usec_timeout = MGA_DEFAULT_USEC_TIMEOUT;
+
+ if ( init->sgram ) {
+ dev_priv->clear_cmd = MGA_DWGCTL_CLEAR | MGA_ATYPE_BLK;
+ } else {
+ dev_priv->clear_cmd = MGA_DWGCTL_CLEAR | MGA_ATYPE_RSTR;
+ }
+ dev_priv->maccess = init->maccess;
+
+ dev_priv->fb_cpp = init->fb_cpp;
+ dev_priv->front_offset = init->front_offset;
+ dev_priv->front_pitch = init->front_pitch;
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->back_pitch = init->back_pitch;
+
+ dev_priv->depth_cpp = init->depth_cpp;
+ dev_priv->depth_offset = init->depth_offset;
+ dev_priv->depth_pitch = init->depth_pitch;
+
+ /* FIXME: Need to support AGP textures...
+ */
+ dev_priv->texture_offset = init->texture_offset[0];
+ dev_priv->texture_size = init->texture_size[0];
+
+ DRM_GETSAREA();
+
+ if(!dev_priv->sarea) {
+ DRM_ERROR( "failed to find sarea!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+ if(!dev_priv->mmio) {
+ DRM_ERROR( "failed to find mmio region!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->status = drm_core_findmap(dev, init->status_offset);
+ if(!dev_priv->status) {
+ DRM_ERROR( "failed to find status page!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->warp = drm_core_findmap(dev, init->warp_offset);
+ if(!dev_priv->warp) {
+ DRM_ERROR( "failed to find warp microcode region!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->primary = drm_core_findmap(dev, init->primary_offset);
+ if(!dev_priv->primary) {
+ DRM_ERROR( "failed to find primary dma region!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ if(!dev->agp_buffer_map) {
+ DRM_ERROR( "failed to find dma buffer region!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->sarea_priv =
+ (drm_mga_sarea_t *)((u8 *)dev_priv->sarea->handle +
+ init->sarea_priv_offset);
+
+ drm_core_ioremap( dev_priv->warp, dev );
+ drm_core_ioremap( dev_priv->primary, dev );
+ drm_core_ioremap( dev->agp_buffer_map, dev );
+
+ if(!dev_priv->warp->handle ||
+ !dev_priv->primary->handle ||
+ !dev->agp_buffer_map->handle ) {
+ DRM_ERROR( "failed to ioremap agp regions!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(ENOMEM);
+ }
+
+ ret = mga_warp_install_microcode( dev_priv );
+ if ( ret < 0 ) {
+ DRM_ERROR( "failed to install WARP ucode!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return ret;
+ }
+
+ ret = mga_warp_init( dev_priv );
+ if ( ret < 0 ) {
+ DRM_ERROR( "failed to init WARP engine!\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return ret;
+ }
+
+ dev_priv->prim.status = (u32 *)dev_priv->status->handle;
+
+ mga_do_wait_for_idle( dev_priv );
+
+ /* Init the primary DMA registers.
+ */
+ MGA_WRITE( MGA_PRIMADDRESS,
+ dev_priv->primary->offset | MGA_DMA_GENERAL );
+#if 0
+ MGA_WRITE( MGA_PRIMPTR,
+ virt_to_bus((void *)dev_priv->prim.status) |
+ MGA_PRIMPTREN0 | /* Soft trap, SECEND, SETUPEND */
+ MGA_PRIMPTREN1 ); /* DWGSYNC */
+#endif
+
+ dev_priv->prim.start = (u8 *)dev_priv->primary->handle;
+ dev_priv->prim.end = ((u8 *)dev_priv->primary->handle
+ + dev_priv->primary->size);
+ dev_priv->prim.size = dev_priv->primary->size;
+
+ dev_priv->prim.tail = 0;
+ dev_priv->prim.space = dev_priv->prim.size;
+ dev_priv->prim.wrapped = 0;
+
+ dev_priv->prim.last_flush = 0;
+ dev_priv->prim.last_wrap = 0;
+
+ dev_priv->prim.high_mark = 256 * DMA_BLOCK_SIZE;
+
+ dev_priv->prim.status[0] = dev_priv->primary->offset;
+ dev_priv->prim.status[1] = 0;
+
+ dev_priv->sarea_priv->last_wrap = 0;
+ dev_priv->sarea_priv->last_frame.head = 0;
+ dev_priv->sarea_priv->last_frame.wrap = 0;
+
+ if ( mga_freelist_init( dev, dev_priv ) < 0 ) {
+ DRM_ERROR( "could not initialize freelist\n" );
+ /* Assign dev_private so we can do cleanup. */
+ dev->dev_private = (void *)dev_priv;
+ mga_do_cleanup_dma( dev );
+ return DRM_ERR(ENOMEM);
+ }
+
+ /* Make dev_private visable to others. */
+ dev->dev_private = (void *)dev_priv;
+ return 0;
+}
+
+static int mga_do_cleanup_dma( drm_device_t *dev )
+{
+ DRM_DEBUG( "\n" );
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+ if ( dev->dev_private ) {
+ drm_mga_private_t *dev_priv = dev->dev_private;
+
+ if ( dev_priv->warp != NULL )
+ drm_core_ioremapfree( dev_priv->warp, dev );
+ if ( dev_priv->primary != NULL )
+ drm_core_ioremapfree( dev_priv->primary, dev );
+ if ( dev->agp_buffer_map != NULL )
+ drm_core_ioremapfree( dev->agp_buffer_map, dev );
+
+ if ( dev_priv->head != NULL ) {
+ mga_freelist_cleanup( dev );
+ }
+
+ drm_free( dev->dev_private, sizeof(drm_mga_private_t),
+ DRM_MEM_DRIVER );
+ dev->dev_private = NULL;
+ }
+
+ return 0;
+}
+
+int mga_dma_init( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_init_t init;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( init, (drm_mga_init_t __user *)data, sizeof(init) );
+
+ switch ( init.func ) {
+ case MGA_INIT_DMA:
+ return mga_do_init_dma( dev, &init );
+ case MGA_CLEANUP_DMA:
+ return mga_do_cleanup_dma( dev );
+ }
+
+ return DRM_ERR(EINVAL);
+}
+
+
+/* ================================================================
+ * Primary DMA stream management
+ */
+
+int mga_dma_flush( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+ drm_lock_t lock;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( lock, (drm_lock_t __user *)data, sizeof(lock) );
+
+ DRM_DEBUG( "%s%s%s\n",
+ (lock.flags & _DRM_LOCK_FLUSH) ? "flush, " : "",
+ (lock.flags & _DRM_LOCK_FLUSH_ALL) ? "flush all, " : "",
+ (lock.flags & _DRM_LOCK_QUIESCENT) ? "idle, " : "" );
+
+ WRAP_WAIT_WITH_RETURN( dev_priv );
+
+ if ( lock.flags & (_DRM_LOCK_FLUSH | _DRM_LOCK_FLUSH_ALL) ) {
+ mga_do_dma_flush( dev_priv );
+ }
+
+ if ( lock.flags & _DRM_LOCK_QUIESCENT ) {
+#if MGA_DMA_DEBUG
+ int ret = mga_do_wait_for_idle( dev_priv );
+ if ( ret < 0 )
+ DRM_INFO( "%s: -EBUSY\n", __FUNCTION__ );
+ return ret;
+#else
+ return mga_do_wait_for_idle( dev_priv );
+#endif
+ } else {
+ return 0;
+ }
+}
+
+int mga_dma_reset( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ return mga_do_dma_reset( dev_priv );
+}
+
+
+/* ================================================================
+ * DMA buffer management
+ */
+
+static int mga_dma_get_buffers( DRMFILE filp,
+ drm_device_t *dev, drm_dma_t *d )
+{
+ drm_buf_t *buf;
+ int i;
+
+ for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+ buf = mga_freelist_get( dev );
+ if ( !buf ) return DRM_ERR(EAGAIN);
+
+ buf->filp = filp;
+
+ if ( DRM_COPY_TO_USER( &d->request_indices[i],
+ &buf->idx, sizeof(buf->idx) ) )
+ return DRM_ERR(EFAULT);
+ if ( DRM_COPY_TO_USER( &d->request_sizes[i],
+ &buf->total, sizeof(buf->total) ) )
+ return DRM_ERR(EFAULT);
+
+ d->granted_count++;
+ }
+ return 0;
+}
+
+int mga_dma_buffers( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_device_dma_t *dma = dev->dma;
+ drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+ drm_dma_t __user *argp = (void __user *)data;
+ drm_dma_t d;
+ int ret = 0;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+ /* Please don't send us buffers.
+ */
+ if ( d.send_count != 0 ) {
+ DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+ DRM_CURRENTPID, d.send_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ /* We'll send you buffers.
+ */
+ if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+ DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+ DRM_CURRENTPID, d.request_count, dma->buf_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ d.granted_count = 0;
+
+ if ( d.request_count ) {
+ ret = mga_dma_get_buffers( filp, dev, &d );
+ }
+
+ DRM_COPY_TO_USER_IOCTL( argp, d, sizeof(d) );
+
+ return ret;
+}
+
+void mga_driver_pretakedown(drm_device_t *dev)
+{
+ mga_do_cleanup_dma( dev );
+}
+
+int mga_driver_dma_quiescent(drm_device_t *dev)
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ return mga_do_wait_for_idle( dev_priv );
+}
diff --git a/drivers/char/drm/mga_drm.h b/drivers/char/drm/mga_drm.h
new file mode 100644
index 000000000000..521d4451d012
--- /dev/null
+++ b/drivers/char/drm/mga_drm.h
@@ -0,0 +1,349 @@
+/* mga_drm.h -- Public header for the Matrox g200/g400 driver -*- linux-c -*-
+ * Created: Tue Jan 25 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __MGA_DRM_H__
+#define __MGA_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (mga_sarea.h)
+ */
+
+#ifndef __MGA_SAREA_DEFINES__
+#define __MGA_SAREA_DEFINES__
+
+/* WARP pipe flags
+ */
+#define MGA_F 0x1 /* fog */
+#define MGA_A 0x2 /* alpha */
+#define MGA_S 0x4 /* specular */
+#define MGA_T2 0x8 /* multitexture */
+
+#define MGA_WARP_TGZ 0
+#define MGA_WARP_TGZF (MGA_F)
+#define MGA_WARP_TGZA (MGA_A)
+#define MGA_WARP_TGZAF (MGA_F|MGA_A)
+#define MGA_WARP_TGZS (MGA_S)
+#define MGA_WARP_TGZSF (MGA_S|MGA_F)
+#define MGA_WARP_TGZSA (MGA_S|MGA_A)
+#define MGA_WARP_TGZSAF (MGA_S|MGA_F|MGA_A)
+#define MGA_WARP_T2GZ (MGA_T2)
+#define MGA_WARP_T2GZF (MGA_T2|MGA_F)
+#define MGA_WARP_T2GZA (MGA_T2|MGA_A)
+#define MGA_WARP_T2GZAF (MGA_T2|MGA_A|MGA_F)
+#define MGA_WARP_T2GZS (MGA_T2|MGA_S)
+#define MGA_WARP_T2GZSF (MGA_T2|MGA_S|MGA_F)
+#define MGA_WARP_T2GZSA (MGA_T2|MGA_S|MGA_A)
+#define MGA_WARP_T2GZSAF (MGA_T2|MGA_S|MGA_F|MGA_A)
+
+#define MGA_MAX_G200_PIPES 8 /* no multitex */
+#define MGA_MAX_G400_PIPES 16
+#define MGA_MAX_WARP_PIPES MGA_MAX_G400_PIPES
+#define MGA_WARP_UCODE_SIZE 32768 /* in bytes */
+
+#define MGA_CARD_TYPE_G200 1
+#define MGA_CARD_TYPE_G400 2
+
+
+#define MGA_FRONT 0x1
+#define MGA_BACK 0x2
+#define MGA_DEPTH 0x4
+
+/* What needs to be changed for the current vertex dma buffer?
+ */
+#define MGA_UPLOAD_CONTEXT 0x1
+#define MGA_UPLOAD_TEX0 0x2
+#define MGA_UPLOAD_TEX1 0x4
+#define MGA_UPLOAD_PIPE 0x8
+#define MGA_UPLOAD_TEX0IMAGE 0x10 /* handled client-side */
+#define MGA_UPLOAD_TEX1IMAGE 0x20 /* handled client-side */
+#define MGA_UPLOAD_2D 0x40
+#define MGA_WAIT_AGE 0x80 /* handled client-side */
+#define MGA_UPLOAD_CLIPRECTS 0x100 /* handled client-side */
+#if 0
+#define MGA_DMA_FLUSH 0x200 /* set when someone gets the lock
+ quiescent */
+#endif
+
+/* 32 buffers of 64k each, total 2 meg.
+ */
+#define MGA_BUFFER_SIZE (1 << 16)
+#define MGA_NUM_BUFFERS 128
+
+/* Keep these small for testing.
+ */
+#define MGA_NR_SAREA_CLIPRECTS 8
+
+/* 2 heaps (1 for card, 1 for agp), each divided into upto 128
+ * regions, subject to a minimum region size of (1<<16) == 64k.
+ *
+ * Clients may subdivide regions internally, but when sharing between
+ * clients, the region size is the minimum granularity.
+ */
+
+#define MGA_CARD_HEAP 0
+#define MGA_AGP_HEAP 1
+#define MGA_NR_TEX_HEAPS 2
+#define MGA_NR_TEX_REGIONS 16
+#define MGA_LOG_MIN_TEX_REGION_SIZE 16
+
+#define DRM_MGA_IDLE_RETRY 2048
+
+#endif /* __MGA_SAREA_DEFINES__ */
+
+
+/* Setup registers for 3D context
+ */
+typedef struct {
+ unsigned int dstorg;
+ unsigned int maccess;
+ unsigned int plnwt;
+ unsigned int dwgctl;
+ unsigned int alphactrl;
+ unsigned int fogcolor;
+ unsigned int wflag;
+ unsigned int tdualstage0;
+ unsigned int tdualstage1;
+ unsigned int fcol;
+ unsigned int stencil;
+ unsigned int stencilctl;
+} drm_mga_context_regs_t;
+
+/* Setup registers for 2D, X server
+ */
+typedef struct {
+ unsigned int pitch;
+} drm_mga_server_regs_t;
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+ unsigned int texctl;
+ unsigned int texctl2;
+ unsigned int texfilter;
+ unsigned int texbordercol;
+ unsigned int texorg;
+ unsigned int texwidth;
+ unsigned int texheight;
+ unsigned int texorg1;
+ unsigned int texorg2;
+ unsigned int texorg3;
+ unsigned int texorg4;
+} drm_mga_texture_regs_t;
+
+/* General aging mechanism
+ */
+typedef struct {
+ unsigned int head; /* Position of head pointer */
+ unsigned int wrap; /* Primary DMA wrap count */
+} drm_mga_age_t;
+
+typedef struct _drm_mga_sarea {
+ /* The channel for communication of state information to the kernel
+ * on firing a vertex dma buffer.
+ */
+ drm_mga_context_regs_t context_state;
+ drm_mga_server_regs_t server_state;
+ drm_mga_texture_regs_t tex_state[2];
+ unsigned int warp_pipe;
+ unsigned int dirty;
+ unsigned int vertsize;
+
+ /* The current cliprects, or a subset thereof.
+ */
+ drm_clip_rect_t boxes[MGA_NR_SAREA_CLIPRECTS];
+ unsigned int nbox;
+
+ /* Information about the most recently used 3d drawable. The
+ * client fills in the req_* fields, the server fills in the
+ * exported_ fields and puts the cliprects into boxes, above.
+ *
+ * The client clears the exported_drawable field before
+ * clobbering the boxes data.
+ */
+ unsigned int req_drawable; /* the X drawable id */
+ unsigned int req_draw_buffer; /* MGA_FRONT or MGA_BACK */
+
+ unsigned int exported_drawable;
+ unsigned int exported_index;
+ unsigned int exported_stamp;
+ unsigned int exported_buffers;
+ unsigned int exported_nfront;
+ unsigned int exported_nback;
+ int exported_back_x, exported_front_x, exported_w;
+ int exported_back_y, exported_front_y, exported_h;
+ drm_clip_rect_t exported_boxes[MGA_NR_SAREA_CLIPRECTS];
+
+ /* Counters for aging textures and for client-side throttling.
+ */
+ unsigned int status[4];
+ unsigned int last_wrap;
+
+ drm_mga_age_t last_frame;
+ unsigned int last_enqueue; /* last time a buffer was enqueued */
+ unsigned int last_dispatch; /* age of the most recently dispatched buffer */
+ unsigned int last_quiescent; /* */
+
+ /* LRU lists for texture memory in agp space and on the card.
+ */
+ drm_tex_region_t texList[MGA_NR_TEX_HEAPS][MGA_NR_TEX_REGIONS+1];
+ unsigned int texAge[MGA_NR_TEX_HEAPS];
+
+ /* Mechanism to validate card state.
+ */
+ int ctxOwner;
+} drm_mga_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmMga.h)
+ */
+
+/* MGA specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_MGA_INIT 0x00
+#define DRM_MGA_FLUSH 0x01
+#define DRM_MGA_RESET 0x02
+#define DRM_MGA_SWAP 0x03
+#define DRM_MGA_CLEAR 0x04
+#define DRM_MGA_VERTEX 0x05
+#define DRM_MGA_INDICES 0x06
+#define DRM_MGA_ILOAD 0x07
+#define DRM_MGA_BLIT 0x08
+#define DRM_MGA_GETPARAM 0x09
+
+#define DRM_IOCTL_MGA_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_INIT, drm_mga_init_t)
+#define DRM_IOCTL_MGA_FLUSH DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_FLUSH, drm_lock_t)
+#define DRM_IOCTL_MGA_RESET DRM_IO( DRM_COMMAND_BASE + DRM_MGA_RESET)
+#define DRM_IOCTL_MGA_SWAP DRM_IO( DRM_COMMAND_BASE + DRM_MGA_SWAP)
+#define DRM_IOCTL_MGA_CLEAR DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_CLEAR, drm_mga_clear_t)
+#define DRM_IOCTL_MGA_VERTEX DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_VERTEX, drm_mga_vertex_t)
+#define DRM_IOCTL_MGA_INDICES DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_INDICES, drm_mga_indices_t)
+#define DRM_IOCTL_MGA_ILOAD DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_ILOAD, drm_mga_iload_t)
+#define DRM_IOCTL_MGA_BLIT DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_BLIT, drm_mga_blit_t)
+#define DRM_IOCTL_MGA_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_MGA_GETPARAM, drm_mga_getparam_t)
+
+typedef struct _drm_mga_warp_index {
+ int installed;
+ unsigned long phys_addr;
+ int size;
+} drm_mga_warp_index_t;
+
+typedef struct drm_mga_init {
+ enum {
+ MGA_INIT_DMA = 0x01,
+ MGA_CLEANUP_DMA = 0x02
+ } func;
+
+ unsigned long sarea_priv_offset;
+
+ int chipset;
+ int sgram;
+
+ unsigned int maccess;
+
+ unsigned int fb_cpp;
+ unsigned int front_offset, front_pitch;
+ unsigned int back_offset, back_pitch;
+
+ unsigned int depth_cpp;
+ unsigned int depth_offset, depth_pitch;
+
+ unsigned int texture_offset[MGA_NR_TEX_HEAPS];
+ unsigned int texture_size[MGA_NR_TEX_HEAPS];
+
+ unsigned long fb_offset;
+ unsigned long mmio_offset;
+ unsigned long status_offset;
+ unsigned long warp_offset;
+ unsigned long primary_offset;
+ unsigned long buffers_offset;
+} drm_mga_init_t;
+
+typedef struct drm_mga_fullscreen {
+ enum {
+ MGA_INIT_FULLSCREEN = 0x01,
+ MGA_CLEANUP_FULLSCREEN = 0x02
+ } func;
+} drm_mga_fullscreen_t;
+
+typedef struct drm_mga_clear {
+ unsigned int flags;
+ unsigned int clear_color;
+ unsigned int clear_depth;
+ unsigned int color_mask;
+ unsigned int depth_mask;
+} drm_mga_clear_t;
+
+typedef struct drm_mga_vertex {
+ int idx; /* buffer to queue */
+ int used; /* bytes in use */
+ int discard; /* client finished with buffer? */
+} drm_mga_vertex_t;
+
+typedef struct drm_mga_indices {
+ int idx; /* buffer to queue */
+ unsigned int start;
+ unsigned int end;
+ int discard; /* client finished with buffer? */
+} drm_mga_indices_t;
+
+typedef struct drm_mga_iload {
+ int idx;
+ unsigned int dstorg;
+ unsigned int length;
+} drm_mga_iload_t;
+
+typedef struct _drm_mga_blit {
+ unsigned int planemask;
+ unsigned int srcorg;
+ unsigned int dstorg;
+ int src_pitch, dst_pitch;
+ int delta_sx, delta_sy;
+ int delta_dx, delta_dy;
+ int height, ydir; /* flip image vertically */
+ int source_pitch, dest_pitch;
+} drm_mga_blit_t;
+
+/* 3.1: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.
+ */
+#define MGA_PARAM_IRQ_NR 1
+
+typedef struct drm_mga_getparam {
+ int param;
+ void __user *value;
+} drm_mga_getparam_t;
+
+#endif
diff --git a/drivers/char/drm/mga_drv.c b/drivers/char/drm/mga_drv.c
new file mode 100644
index 000000000000..22dab3e9d92a
--- /dev/null
+++ b/drivers/char/drm/mga_drv.c
@@ -0,0 +1,127 @@
+/* mga_drv.c -- Matrox G200/G400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ dev->counters += 3;
+ dev->types[6] = _DRM_STAT_IRQ;
+ dev->types[7] = _DRM_STAT_PRIMARY;
+ dev->types[8] = _DRM_STAT_SECONDARY;
+
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ mga_PCI_IDS
+};
+
+extern drm_ioctl_desc_t mga_ioctls[];
+extern int mga_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+ .pretakedown = mga_driver_pretakedown,
+ .dma_quiescent = mga_driver_dma_quiescent,
+ .vblank_wait = mga_driver_vblank_wait,
+ .irq_preinstall = mga_driver_irq_preinstall,
+ .irq_postinstall = mga_driver_irq_postinstall,
+ .irq_uninstall = mga_driver_irq_uninstall,
+ .irq_handler = mga_driver_irq_handler,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = mga_ioctls,
+ .dma_ioctl = mga_dma_buffers,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init mga_init(void)
+{
+ driver.num_ioctls = mga_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit mga_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(mga_init);
+module_exit(mga_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/mga_drv.h b/drivers/char/drm/mga_drv.h
new file mode 100644
index 000000000000..1d84a1eb34db
--- /dev/null
+++ b/drivers/char/drm/mga_drv.h
@@ -0,0 +1,638 @@
+/* mga_drv.h -- Private header for the Matrox G200/G400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __MGA_DRV_H__
+#define __MGA_DRV_H__
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "Gareth Hughes, VA Linux Systems Inc."
+
+#define DRIVER_NAME "mga"
+#define DRIVER_DESC "Matrox G200/G400"
+#define DRIVER_DATE "20021029"
+
+#define DRIVER_MAJOR 3
+#define DRIVER_MINOR 1
+#define DRIVER_PATCHLEVEL 0
+
+typedef struct drm_mga_primary_buffer {
+ u8 *start;
+ u8 *end;
+ int size;
+
+ u32 tail;
+ int space;
+ volatile long wrapped;
+
+ volatile u32 *status;
+
+ u32 last_flush;
+ u32 last_wrap;
+
+ u32 high_mark;
+} drm_mga_primary_buffer_t;
+
+typedef struct drm_mga_freelist {
+ struct drm_mga_freelist *next;
+ struct drm_mga_freelist *prev;
+ drm_mga_age_t age;
+ drm_buf_t *buf;
+} drm_mga_freelist_t;
+
+typedef struct {
+ drm_mga_freelist_t *list_entry;
+ int discard;
+ int dispatched;
+} drm_mga_buf_priv_t;
+
+typedef struct drm_mga_private {
+ drm_mga_primary_buffer_t prim;
+ drm_mga_sarea_t *sarea_priv;
+
+ drm_mga_freelist_t *head;
+ drm_mga_freelist_t *tail;
+
+ unsigned int warp_pipe;
+ unsigned long warp_pipe_phys[MGA_MAX_WARP_PIPES];
+
+ int chipset;
+ int usec_timeout;
+
+ u32 clear_cmd;
+ u32 maccess;
+
+ unsigned int fb_cpp;
+ unsigned int front_offset;
+ unsigned int front_pitch;
+ unsigned int back_offset;
+ unsigned int back_pitch;
+
+ unsigned int depth_cpp;
+ unsigned int depth_offset;
+ unsigned int depth_pitch;
+
+ unsigned int texture_offset;
+ unsigned int texture_size;
+
+ drm_local_map_t *sarea;
+ drm_local_map_t *mmio;
+ drm_local_map_t *status;
+ drm_local_map_t *warp;
+ drm_local_map_t *primary;
+ drm_local_map_t *buffers;
+ drm_local_map_t *agp_textures;
+} drm_mga_private_t;
+
+ /* mga_dma.c */
+extern int mga_dma_init( DRM_IOCTL_ARGS );
+extern int mga_dma_flush( DRM_IOCTL_ARGS );
+extern int mga_dma_reset( DRM_IOCTL_ARGS );
+extern int mga_dma_buffers( DRM_IOCTL_ARGS );
+extern void mga_driver_pretakedown(drm_device_t *dev);
+extern int mga_driver_dma_quiescent(drm_device_t *dev);
+
+extern int mga_do_wait_for_idle( drm_mga_private_t *dev_priv );
+
+extern void mga_do_dma_flush( drm_mga_private_t *dev_priv );
+extern void mga_do_dma_wrap_start( drm_mga_private_t *dev_priv );
+extern void mga_do_dma_wrap_end( drm_mga_private_t *dev_priv );
+
+extern int mga_freelist_put( drm_device_t *dev, drm_buf_t *buf );
+
+ /* mga_warp.c */
+extern int mga_warp_install_microcode( drm_mga_private_t *dev_priv );
+extern int mga_warp_init( drm_mga_private_t *dev_priv );
+
+extern int mga_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+extern irqreturn_t mga_driver_irq_handler( DRM_IRQ_ARGS );
+extern void mga_driver_irq_preinstall( drm_device_t *dev );
+extern void mga_driver_irq_postinstall( drm_device_t *dev );
+extern void mga_driver_irq_uninstall( drm_device_t *dev );
+
+#define mga_flush_write_combine() DRM_WRITEMEMORYBARRIER()
+
+#if defined(__linux__) && defined(__alpha__)
+#define MGA_BASE( reg ) ((unsigned long)(dev_priv->mmio->handle))
+#define MGA_ADDR( reg ) (MGA_BASE(reg) + reg)
+
+#define MGA_DEREF( reg ) *(volatile u32 *)MGA_ADDR( reg )
+#define MGA_DEREF8( reg ) *(volatile u8 *)MGA_ADDR( reg )
+
+#define MGA_READ( reg ) (_MGA_READ((u32 *)MGA_ADDR(reg)))
+#define MGA_READ8( reg ) (_MGA_READ((u8 *)MGA_ADDR(reg)))
+#define MGA_WRITE( reg, val ) do { DRM_WRITEMEMORYBARRIER(); MGA_DEREF( reg ) = val; } while (0)
+#define MGA_WRITE8( reg, val ) do { DRM_WRITEMEMORYBARRIER(); MGA_DEREF8( reg ) = val; } while (0)
+
+static inline u32 _MGA_READ(u32 *addr)
+{
+ DRM_MEMORYBARRIER();
+ return *(volatile u32 *)addr;
+}
+#else
+#define MGA_READ8( reg ) DRM_READ8(dev_priv->mmio, (reg))
+#define MGA_READ( reg ) DRM_READ32(dev_priv->mmio, (reg))
+#define MGA_WRITE8( reg, val ) DRM_WRITE8(dev_priv->mmio, (reg), (val))
+#define MGA_WRITE( reg, val ) DRM_WRITE32(dev_priv->mmio, (reg), (val))
+#endif
+
+#define DWGREG0 0x1c00
+#define DWGREG0_END 0x1dff
+#define DWGREG1 0x2c00
+#define DWGREG1_END 0x2dff
+
+#define ISREG0(r) (r >= DWGREG0 && r <= DWGREG0_END)
+#define DMAREG0(r) (u8)((r - DWGREG0) >> 2)
+#define DMAREG1(r) (u8)(((r - DWGREG1) >> 2) | 0x80)
+#define DMAREG(r) (ISREG0(r) ? DMAREG0(r) : DMAREG1(r))
+
+
+
+/* ================================================================
+ * Helper macross...
+ */
+
+#define MGA_EMIT_STATE( dev_priv, dirty ) \
+do { \
+ if ( (dirty) & ~MGA_UPLOAD_CLIPRECTS ) { \
+ if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) { \
+ mga_g400_emit_state( dev_priv ); \
+ } else { \
+ mga_g200_emit_state( dev_priv ); \
+ } \
+ } \
+} while (0)
+
+#define WRAP_TEST_WITH_RETURN( dev_priv ) \
+do { \
+ if ( test_bit( 0, &dev_priv->prim.wrapped ) ) { \
+ if ( mga_is_idle( dev_priv ) ) { \
+ mga_do_dma_wrap_end( dev_priv ); \
+ } else if ( dev_priv->prim.space < \
+ dev_priv->prim.high_mark ) { \
+ if ( MGA_DMA_DEBUG ) \
+ DRM_INFO( "%s: wrap...\n", __FUNCTION__ ); \
+ return DRM_ERR(EBUSY); \
+ } \
+ } \
+} while (0)
+
+#define WRAP_WAIT_WITH_RETURN( dev_priv ) \
+do { \
+ if ( test_bit( 0, &dev_priv->prim.wrapped ) ) { \
+ if ( mga_do_wait_for_idle( dev_priv ) < 0 ) { \
+ if ( MGA_DMA_DEBUG ) \
+ DRM_INFO( "%s: wrap...\n", __FUNCTION__ ); \
+ return DRM_ERR(EBUSY); \
+ } \
+ mga_do_dma_wrap_end( dev_priv ); \
+ } \
+} while (0)
+
+
+/* ================================================================
+ * Primary DMA command stream
+ */
+
+#define MGA_VERBOSE 0
+
+#define DMA_LOCALS unsigned int write; volatile u8 *prim;
+
+#define DMA_BLOCK_SIZE (5 * sizeof(u32))
+
+#define BEGIN_DMA( n ) \
+do { \
+ if ( MGA_VERBOSE ) { \
+ DRM_INFO( "BEGIN_DMA( %d ) in %s\n", \
+ (n), __FUNCTION__ ); \
+ DRM_INFO( " space=0x%x req=0x%Zx\n", \
+ dev_priv->prim.space, (n) * DMA_BLOCK_SIZE ); \
+ } \
+ prim = dev_priv->prim.start; \
+ write = dev_priv->prim.tail; \
+} while (0)
+
+#define BEGIN_DMA_WRAP() \
+do { \
+ if ( MGA_VERBOSE ) { \
+ DRM_INFO( "BEGIN_DMA() in %s\n", __FUNCTION__ ); \
+ DRM_INFO( " space=0x%x\n", dev_priv->prim.space ); \
+ } \
+ prim = dev_priv->prim.start; \
+ write = dev_priv->prim.tail; \
+} while (0)
+
+#define ADVANCE_DMA() \
+do { \
+ dev_priv->prim.tail = write; \
+ if ( MGA_VERBOSE ) { \
+ DRM_INFO( "ADVANCE_DMA() tail=0x%05x sp=0x%x\n", \
+ write, dev_priv->prim.space ); \
+ } \
+} while (0)
+
+#define FLUSH_DMA() \
+do { \
+ if ( 0 ) { \
+ DRM_INFO( "%s:\n", __FUNCTION__ ); \
+ DRM_INFO( " tail=0x%06x head=0x%06lx\n", \
+ dev_priv->prim.tail, \
+ MGA_READ( MGA_PRIMADDRESS ) - \
+ dev_priv->primary->offset ); \
+ } \
+ if ( !test_bit( 0, &dev_priv->prim.wrapped ) ) { \
+ if ( dev_priv->prim.space < \
+ dev_priv->prim.high_mark ) { \
+ mga_do_dma_wrap_start( dev_priv ); \
+ } else { \
+ mga_do_dma_flush( dev_priv ); \
+ } \
+ } \
+} while (0)
+
+/* Never use this, always use DMA_BLOCK(...) for primary DMA output.
+ */
+#define DMA_WRITE( offset, val ) \
+do { \
+ if ( MGA_VERBOSE ) { \
+ DRM_INFO( " DMA_WRITE( 0x%08x ) at 0x%04Zx\n", \
+ (u32)(val), write + (offset) * sizeof(u32) ); \
+ } \
+ *(volatile u32 *)(prim + write + (offset) * sizeof(u32)) = val; \
+} while (0)
+
+#define DMA_BLOCK( reg0, val0, reg1, val1, reg2, val2, reg3, val3 ) \
+do { \
+ DMA_WRITE( 0, ((DMAREG( reg0 ) << 0) | \
+ (DMAREG( reg1 ) << 8) | \
+ (DMAREG( reg2 ) << 16) | \
+ (DMAREG( reg3 ) << 24)) ); \
+ DMA_WRITE( 1, val0 ); \
+ DMA_WRITE( 2, val1 ); \
+ DMA_WRITE( 3, val2 ); \
+ DMA_WRITE( 4, val3 ); \
+ write += DMA_BLOCK_SIZE; \
+} while (0)
+
+
+/* Buffer aging via primary DMA stream head pointer.
+ */
+
+#define SET_AGE( age, h, w ) \
+do { \
+ (age)->head = h; \
+ (age)->wrap = w; \
+} while (0)
+
+#define TEST_AGE( age, h, w ) ( (age)->wrap < w || \
+ ( (age)->wrap == w && \
+ (age)->head < h ) )
+
+#define AGE_BUFFER( buf_priv ) \
+do { \
+ drm_mga_freelist_t *entry = (buf_priv)->list_entry; \
+ if ( (buf_priv)->dispatched ) { \
+ entry->age.head = (dev_priv->prim.tail + \
+ dev_priv->primary->offset); \
+ entry->age.wrap = dev_priv->sarea_priv->last_wrap; \
+ } else { \
+ entry->age.head = 0; \
+ entry->age.wrap = 0; \
+ } \
+} while (0)
+
+
+#define MGA_ENGINE_IDLE_MASK (MGA_SOFTRAPEN | \
+ MGA_DWGENGSTS | \
+ MGA_ENDPRDMASTS)
+#define MGA_DMA_IDLE_MASK (MGA_SOFTRAPEN | \
+ MGA_ENDPRDMASTS)
+
+#define MGA_DMA_DEBUG 0
+
+
+
+/* A reduced set of the mga registers.
+ */
+#define MGA_CRTC_INDEX 0x1fd4
+#define MGA_CRTC_DATA 0x1fd5
+
+/* CRTC11 */
+#define MGA_VINTCLR (1 << 4)
+#define MGA_VINTEN (1 << 5)
+
+#define MGA_ALPHACTRL 0x2c7c
+#define MGA_AR0 0x1c60
+#define MGA_AR1 0x1c64
+#define MGA_AR2 0x1c68
+#define MGA_AR3 0x1c6c
+#define MGA_AR4 0x1c70
+#define MGA_AR5 0x1c74
+#define MGA_AR6 0x1c78
+
+#define MGA_CXBNDRY 0x1c80
+#define MGA_CXLEFT 0x1ca0
+#define MGA_CXRIGHT 0x1ca4
+
+#define MGA_DMAPAD 0x1c54
+#define MGA_DSTORG 0x2cb8
+#define MGA_DWGCTL 0x1c00
+# define MGA_OPCOD_MASK (15 << 0)
+# define MGA_OPCOD_TRAP (4 << 0)
+# define MGA_OPCOD_TEXTURE_TRAP (6 << 0)
+# define MGA_OPCOD_BITBLT (8 << 0)
+# define MGA_OPCOD_ILOAD (9 << 0)
+# define MGA_ATYPE_MASK (7 << 4)
+# define MGA_ATYPE_RPL (0 << 4)
+# define MGA_ATYPE_RSTR (1 << 4)
+# define MGA_ATYPE_ZI (3 << 4)
+# define MGA_ATYPE_BLK (4 << 4)
+# define MGA_ATYPE_I (7 << 4)
+# define MGA_LINEAR (1 << 7)
+# define MGA_ZMODE_MASK (7 << 8)
+# define MGA_ZMODE_NOZCMP (0 << 8)
+# define MGA_ZMODE_ZE (2 << 8)
+# define MGA_ZMODE_ZNE (3 << 8)
+# define MGA_ZMODE_ZLT (4 << 8)
+# define MGA_ZMODE_ZLTE (5 << 8)
+# define MGA_ZMODE_ZGT (6 << 8)
+# define MGA_ZMODE_ZGTE (7 << 8)
+# define MGA_SOLID (1 << 11)
+# define MGA_ARZERO (1 << 12)
+# define MGA_SGNZERO (1 << 13)
+# define MGA_SHIFTZERO (1 << 14)
+# define MGA_BOP_MASK (15 << 16)
+# define MGA_BOP_ZERO (0 << 16)
+# define MGA_BOP_DST (10 << 16)
+# define MGA_BOP_SRC (12 << 16)
+# define MGA_BOP_ONE (15 << 16)
+# define MGA_TRANS_SHIFT 20
+# define MGA_TRANS_MASK (15 << 20)
+# define MGA_BLTMOD_MASK (15 << 25)
+# define MGA_BLTMOD_BMONOLEF (0 << 25)
+# define MGA_BLTMOD_BMONOWF (4 << 25)
+# define MGA_BLTMOD_PLAN (1 << 25)
+# define MGA_BLTMOD_BFCOL (2 << 25)
+# define MGA_BLTMOD_BU32BGR (3 << 25)
+# define MGA_BLTMOD_BU32RGB (7 << 25)
+# define MGA_BLTMOD_BU24BGR (11 << 25)
+# define MGA_BLTMOD_BU24RGB (15 << 25)
+# define MGA_PATTERN (1 << 29)
+# define MGA_TRANSC (1 << 30)
+# define MGA_CLIPDIS (1 << 31)
+#define MGA_DWGSYNC 0x2c4c
+
+#define MGA_FCOL 0x1c24
+#define MGA_FIFOSTATUS 0x1e10
+#define MGA_FOGCOL 0x1cf4
+#define MGA_FXBNDRY 0x1c84
+#define MGA_FXLEFT 0x1ca8
+#define MGA_FXRIGHT 0x1cac
+
+#define MGA_ICLEAR 0x1e18
+# define MGA_SOFTRAPICLR (1 << 0)
+# define MGA_VLINEICLR (1 << 5)
+#define MGA_IEN 0x1e1c
+# define MGA_SOFTRAPIEN (1 << 0)
+# define MGA_VLINEIEN (1 << 5)
+
+#define MGA_LEN 0x1c5c
+
+#define MGA_MACCESS 0x1c04
+
+#define MGA_PITCH 0x1c8c
+#define MGA_PLNWT 0x1c1c
+#define MGA_PRIMADDRESS 0x1e58
+# define MGA_DMA_GENERAL (0 << 0)
+# define MGA_DMA_BLIT (1 << 0)
+# define MGA_DMA_VECTOR (2 << 0)
+# define MGA_DMA_VERTEX (3 << 0)
+#define MGA_PRIMEND 0x1e5c
+# define MGA_PRIMNOSTART (1 << 0)
+# define MGA_PAGPXFER (1 << 1)
+#define MGA_PRIMPTR 0x1e50
+# define MGA_PRIMPTREN0 (1 << 0)
+# define MGA_PRIMPTREN1 (1 << 1)
+
+#define MGA_RST 0x1e40
+# define MGA_SOFTRESET (1 << 0)
+# define MGA_SOFTEXTRST (1 << 1)
+
+#define MGA_SECADDRESS 0x2c40
+#define MGA_SECEND 0x2c44
+#define MGA_SETUPADDRESS 0x2cd0
+#define MGA_SETUPEND 0x2cd4
+#define MGA_SGN 0x1c58
+#define MGA_SOFTRAP 0x2c48
+#define MGA_SRCORG 0x2cb4
+# define MGA_SRMMAP_MASK (1 << 0)
+# define MGA_SRCMAP_FB (0 << 0)
+# define MGA_SRCMAP_SYSMEM (1 << 0)
+# define MGA_SRCACC_MASK (1 << 1)
+# define MGA_SRCACC_PCI (0 << 1)
+# define MGA_SRCACC_AGP (1 << 1)
+#define MGA_STATUS 0x1e14
+# define MGA_SOFTRAPEN (1 << 0)
+# define MGA_VSYNCPEN (1 << 4)
+# define MGA_VLINEPEN (1 << 5)
+# define MGA_DWGENGSTS (1 << 16)
+# define MGA_ENDPRDMASTS (1 << 17)
+#define MGA_STENCIL 0x2cc8
+#define MGA_STENCILCTL 0x2ccc
+
+#define MGA_TDUALSTAGE0 0x2cf8
+#define MGA_TDUALSTAGE1 0x2cfc
+#define MGA_TEXBORDERCOL 0x2c5c
+#define MGA_TEXCTL 0x2c30
+#define MGA_TEXCTL2 0x2c3c
+# define MGA_DUALTEX (1 << 7)
+# define MGA_G400_TC2_MAGIC (1 << 15)
+# define MGA_MAP1_ENABLE (1 << 31)
+#define MGA_TEXFILTER 0x2c58
+#define MGA_TEXHEIGHT 0x2c2c
+#define MGA_TEXORG 0x2c24
+# define MGA_TEXORGMAP_MASK (1 << 0)
+# define MGA_TEXORGMAP_FB (0 << 0)
+# define MGA_TEXORGMAP_SYSMEM (1 << 0)
+# define MGA_TEXORGACC_MASK (1 << 1)
+# define MGA_TEXORGACC_PCI (0 << 1)
+# define MGA_TEXORGACC_AGP (1 << 1)
+#define MGA_TEXORG1 0x2ca4
+#define MGA_TEXORG2 0x2ca8
+#define MGA_TEXORG3 0x2cac
+#define MGA_TEXORG4 0x2cb0
+#define MGA_TEXTRANS 0x2c34
+#define MGA_TEXTRANSHIGH 0x2c38
+#define MGA_TEXWIDTH 0x2c28
+
+#define MGA_WACCEPTSEQ 0x1dd4
+#define MGA_WCODEADDR 0x1e6c
+#define MGA_WFLAG 0x1dc4
+#define MGA_WFLAG1 0x1de0
+#define MGA_WFLAGNB 0x1e64
+#define MGA_WFLAGNB1 0x1e08
+#define MGA_WGETMSB 0x1dc8
+#define MGA_WIADDR 0x1dc0
+#define MGA_WIADDR2 0x1dd8
+# define MGA_WMODE_SUSPEND (0 << 0)
+# define MGA_WMODE_RESUME (1 << 0)
+# define MGA_WMODE_JUMP (2 << 0)
+# define MGA_WMODE_START (3 << 0)
+# define MGA_WAGP_ENABLE (1 << 2)
+#define MGA_WMISC 0x1e70
+# define MGA_WUCODECACHE_ENABLE (1 << 0)
+# define MGA_WMASTER_ENABLE (1 << 1)
+# define MGA_WCACHEFLUSH_ENABLE (1 << 3)
+#define MGA_WVRTXSZ 0x1dcc
+
+#define MGA_YBOT 0x1c9c
+#define MGA_YDST 0x1c90
+#define MGA_YDSTLEN 0x1c88
+#define MGA_YDSTORG 0x1c94
+#define MGA_YTOP 0x1c98
+
+#define MGA_ZORG 0x1c0c
+
+/* This finishes the current batch of commands
+ */
+#define MGA_EXEC 0x0100
+
+/* Warp registers
+ */
+#define MGA_WR0 0x2d00
+#define MGA_WR1 0x2d04
+#define MGA_WR2 0x2d08
+#define MGA_WR3 0x2d0c
+#define MGA_WR4 0x2d10
+#define MGA_WR5 0x2d14
+#define MGA_WR6 0x2d18
+#define MGA_WR7 0x2d1c
+#define MGA_WR8 0x2d20
+#define MGA_WR9 0x2d24
+#define MGA_WR10 0x2d28
+#define MGA_WR11 0x2d2c
+#define MGA_WR12 0x2d30
+#define MGA_WR13 0x2d34
+#define MGA_WR14 0x2d38
+#define MGA_WR15 0x2d3c
+#define MGA_WR16 0x2d40
+#define MGA_WR17 0x2d44
+#define MGA_WR18 0x2d48
+#define MGA_WR19 0x2d4c
+#define MGA_WR20 0x2d50
+#define MGA_WR21 0x2d54
+#define MGA_WR22 0x2d58
+#define MGA_WR23 0x2d5c
+#define MGA_WR24 0x2d60
+#define MGA_WR25 0x2d64
+#define MGA_WR26 0x2d68
+#define MGA_WR27 0x2d6c
+#define MGA_WR28 0x2d70
+#define MGA_WR29 0x2d74
+#define MGA_WR30 0x2d78
+#define MGA_WR31 0x2d7c
+#define MGA_WR32 0x2d80
+#define MGA_WR33 0x2d84
+#define MGA_WR34 0x2d88
+#define MGA_WR35 0x2d8c
+#define MGA_WR36 0x2d90
+#define MGA_WR37 0x2d94
+#define MGA_WR38 0x2d98
+#define MGA_WR39 0x2d9c
+#define MGA_WR40 0x2da0
+#define MGA_WR41 0x2da4
+#define MGA_WR42 0x2da8
+#define MGA_WR43 0x2dac
+#define MGA_WR44 0x2db0
+#define MGA_WR45 0x2db4
+#define MGA_WR46 0x2db8
+#define MGA_WR47 0x2dbc
+#define MGA_WR48 0x2dc0
+#define MGA_WR49 0x2dc4
+#define MGA_WR50 0x2dc8
+#define MGA_WR51 0x2dcc
+#define MGA_WR52 0x2dd0
+#define MGA_WR53 0x2dd4
+#define MGA_WR54 0x2dd8
+#define MGA_WR55 0x2ddc
+#define MGA_WR56 0x2de0
+#define MGA_WR57 0x2de4
+#define MGA_WR58 0x2de8
+#define MGA_WR59 0x2dec
+#define MGA_WR60 0x2df0
+#define MGA_WR61 0x2df4
+#define MGA_WR62 0x2df8
+#define MGA_WR63 0x2dfc
+# define MGA_G400_WR_MAGIC (1 << 6)
+# define MGA_G400_WR56_MAGIC 0x46480000 /* 12800.0f */
+
+
+#define MGA_ILOAD_ALIGN 64
+#define MGA_ILOAD_MASK (MGA_ILOAD_ALIGN - 1)
+
+#define MGA_DWGCTL_FLUSH (MGA_OPCOD_TEXTURE_TRAP | \
+ MGA_ATYPE_I | \
+ MGA_ZMODE_NOZCMP | \
+ MGA_ARZERO | \
+ MGA_SGNZERO | \
+ MGA_BOP_SRC | \
+ (15 << MGA_TRANS_SHIFT))
+
+#define MGA_DWGCTL_CLEAR (MGA_OPCOD_TRAP | \
+ MGA_ZMODE_NOZCMP | \
+ MGA_SOLID | \
+ MGA_ARZERO | \
+ MGA_SGNZERO | \
+ MGA_SHIFTZERO | \
+ MGA_BOP_SRC | \
+ (0 << MGA_TRANS_SHIFT) | \
+ MGA_BLTMOD_BMONOLEF | \
+ MGA_TRANSC | \
+ MGA_CLIPDIS)
+
+#define MGA_DWGCTL_COPY (MGA_OPCOD_BITBLT | \
+ MGA_ATYPE_RPL | \
+ MGA_SGNZERO | \
+ MGA_SHIFTZERO | \
+ MGA_BOP_SRC | \
+ (0 << MGA_TRANS_SHIFT) | \
+ MGA_BLTMOD_BFCOL | \
+ MGA_CLIPDIS)
+
+/* Simple idle test.
+ */
+static __inline__ int mga_is_idle( drm_mga_private_t *dev_priv )
+{
+ u32 status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+ return ( status == MGA_ENDPRDMASTS );
+}
+
+#endif
diff --git a/drivers/char/drm/mga_irq.c b/drivers/char/drm/mga_irq.c
new file mode 100644
index 000000000000..bc0b6b5d43a6
--- /dev/null
+++ b/drivers/char/drm/mga_irq.c
@@ -0,0 +1,102 @@
+/* mga_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved.
+ *
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Keith Whitwell <keith@tungstengraphics.com>
+ * Eric Anholt <anholt@FreeBSD.org>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+irqreturn_t mga_driver_irq_handler( DRM_IRQ_ARGS )
+{
+ drm_device_t *dev = (drm_device_t *) arg;
+ drm_mga_private_t *dev_priv =
+ (drm_mga_private_t *)dev->dev_private;
+ int status;
+
+ status = MGA_READ( MGA_STATUS );
+
+ /* VBLANK interrupt */
+ if ( status & MGA_VLINEPEN ) {
+ MGA_WRITE( MGA_ICLEAR, MGA_VLINEICLR );
+ atomic_inc(&dev->vbl_received);
+ DRM_WAKEUP(&dev->vbl_queue);
+ drm_vbl_send_signals( dev );
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+int mga_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+ unsigned int cur_vblank;
+ int ret = 0;
+
+ /* Assume that the user has missed the current sequence number
+ * by about a day rather than she wants to wait for years
+ * using vertical blanks...
+ */
+ DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ,
+ ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+ - *sequence ) <= (1<<23) ) );
+
+ *sequence = cur_vblank;
+
+ return ret;
+}
+
+void mga_driver_irq_preinstall( drm_device_t *dev ) {
+ drm_mga_private_t *dev_priv =
+ (drm_mga_private_t *)dev->dev_private;
+
+ /* Disable *all* interrupts */
+ MGA_WRITE( MGA_IEN, 0 );
+ /* Clear bits if they're already high */
+ MGA_WRITE( MGA_ICLEAR, ~0 );
+}
+
+void mga_driver_irq_postinstall( drm_device_t *dev ) {
+ drm_mga_private_t *dev_priv =
+ (drm_mga_private_t *)dev->dev_private;
+
+ /* Turn on VBL interrupt */
+ MGA_WRITE( MGA_IEN, MGA_VLINEIEN );
+}
+
+void mga_driver_irq_uninstall( drm_device_t *dev ) {
+ drm_mga_private_t *dev_priv =
+ (drm_mga_private_t *)dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ /* Disable *all* interrupts */
+ MGA_WRITE( MGA_IEN, 0 );
+}
diff --git a/drivers/char/drm/mga_state.c b/drivers/char/drm/mga_state.c
new file mode 100644
index 000000000000..3c7a8f5ba501
--- /dev/null
+++ b/drivers/char/drm/mga_state.c
@@ -0,0 +1,1123 @@
+/* mga_state.c -- State support for MGA G200/G400 -*- linux-c -*-
+ * Created: Thu Jan 27 02:53:43 2000 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Jeff Hartmann <jhartmann@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+/* ================================================================
+ * DMA hardware state programming functions
+ */
+
+static void mga_emit_clip_rect( drm_mga_private_t *dev_priv,
+ drm_clip_rect_t *box )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ unsigned int pitch = dev_priv->front_pitch;
+ DMA_LOCALS;
+
+ BEGIN_DMA( 2 );
+
+ /* Force reset of DWGCTL on G400 (eliminates clip disable bit).
+ */
+ if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) {
+ DMA_BLOCK( MGA_DWGCTL, ctx->dwgctl,
+ MGA_LEN + MGA_EXEC, 0x80000000,
+ MGA_DWGCTL, ctx->dwgctl,
+ MGA_LEN + MGA_EXEC, 0x80000000 );
+ }
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_CXBNDRY, (box->x2 << 16) | box->x1,
+ MGA_YTOP, box->y1 * pitch,
+ MGA_YBOT, box->y2 * pitch );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_context( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ DMA_LOCALS;
+
+ BEGIN_DMA( 3 );
+
+ DMA_BLOCK( MGA_DSTORG, ctx->dstorg,
+ MGA_MACCESS, ctx->maccess,
+ MGA_PLNWT, ctx->plnwt,
+ MGA_DWGCTL, ctx->dwgctl );
+
+ DMA_BLOCK( MGA_ALPHACTRL, ctx->alphactrl,
+ MGA_FOGCOL, ctx->fogcolor,
+ MGA_WFLAG, ctx->wflag,
+ MGA_ZORG, dev_priv->depth_offset );
+
+ DMA_BLOCK( MGA_FCOL, ctx->fcol,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_context( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ DMA_LOCALS;
+
+ BEGIN_DMA( 4 );
+
+ DMA_BLOCK( MGA_DSTORG, ctx->dstorg,
+ MGA_MACCESS, ctx->maccess,
+ MGA_PLNWT, ctx->plnwt,
+ MGA_DWGCTL, ctx->dwgctl );
+
+ DMA_BLOCK( MGA_ALPHACTRL, ctx->alphactrl,
+ MGA_FOGCOL, ctx->fogcolor,
+ MGA_WFLAG, ctx->wflag,
+ MGA_ZORG, dev_priv->depth_offset );
+
+ DMA_BLOCK( MGA_WFLAG1, ctx->wflag,
+ MGA_TDUALSTAGE0, ctx->tdualstage0,
+ MGA_TDUALSTAGE1, ctx->tdualstage1,
+ MGA_FCOL, ctx->fcol );
+
+ DMA_BLOCK( MGA_STENCIL, ctx->stencil,
+ MGA_STENCILCTL, ctx->stencilctl,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_tex0( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[0];
+ DMA_LOCALS;
+
+ BEGIN_DMA( 4 );
+
+ DMA_BLOCK( MGA_TEXCTL2, tex->texctl2,
+ MGA_TEXCTL, tex->texctl,
+ MGA_TEXFILTER, tex->texfilter,
+ MGA_TEXBORDERCOL, tex->texbordercol );
+
+ DMA_BLOCK( MGA_TEXORG, tex->texorg,
+ MGA_TEXORG1, tex->texorg1,
+ MGA_TEXORG2, tex->texorg2,
+ MGA_TEXORG3, tex->texorg3 );
+
+ DMA_BLOCK( MGA_TEXORG4, tex->texorg4,
+ MGA_TEXWIDTH, tex->texwidth,
+ MGA_TEXHEIGHT, tex->texheight,
+ MGA_WR24, tex->texwidth );
+
+ DMA_BLOCK( MGA_WR34, tex->texheight,
+ MGA_TEXTRANS, 0x0000ffff,
+ MGA_TEXTRANSHIGH, 0x0000ffff,
+ MGA_DMAPAD, 0x00000000 );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_tex0( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[0];
+ DMA_LOCALS;
+
+/* printk("mga_g400_emit_tex0 %x %x %x\n", tex->texorg, */
+/* tex->texctl, tex->texctl2); */
+
+ BEGIN_DMA( 6 );
+
+ DMA_BLOCK( MGA_TEXCTL2, tex->texctl2 | MGA_G400_TC2_MAGIC,
+ MGA_TEXCTL, tex->texctl,
+ MGA_TEXFILTER, tex->texfilter,
+ MGA_TEXBORDERCOL, tex->texbordercol );
+
+ DMA_BLOCK( MGA_TEXORG, tex->texorg,
+ MGA_TEXORG1, tex->texorg1,
+ MGA_TEXORG2, tex->texorg2,
+ MGA_TEXORG3, tex->texorg3 );
+
+ DMA_BLOCK( MGA_TEXORG4, tex->texorg4,
+ MGA_TEXWIDTH, tex->texwidth,
+ MGA_TEXHEIGHT, tex->texheight,
+ MGA_WR49, 0x00000000 );
+
+ DMA_BLOCK( MGA_WR57, 0x00000000,
+ MGA_WR53, 0x00000000,
+ MGA_WR61, 0x00000000,
+ MGA_WR52, MGA_G400_WR_MAGIC );
+
+ DMA_BLOCK( MGA_WR60, MGA_G400_WR_MAGIC,
+ MGA_WR54, tex->texwidth | MGA_G400_WR_MAGIC,
+ MGA_WR62, tex->texheight | MGA_G400_WR_MAGIC,
+ MGA_DMAPAD, 0x00000000 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_TEXTRANS, 0x0000ffff,
+ MGA_TEXTRANSHIGH, 0x0000ffff );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_tex1( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[1];
+ DMA_LOCALS;
+
+/* printk("mga_g400_emit_tex1 %x %x %x\n", tex->texorg, */
+/* tex->texctl, tex->texctl2); */
+
+ BEGIN_DMA( 5 );
+
+ DMA_BLOCK( MGA_TEXCTL2, (tex->texctl2 |
+ MGA_MAP1_ENABLE |
+ MGA_G400_TC2_MAGIC),
+ MGA_TEXCTL, tex->texctl,
+ MGA_TEXFILTER, tex->texfilter,
+ MGA_TEXBORDERCOL, tex->texbordercol );
+
+ DMA_BLOCK( MGA_TEXORG, tex->texorg,
+ MGA_TEXORG1, tex->texorg1,
+ MGA_TEXORG2, tex->texorg2,
+ MGA_TEXORG3, tex->texorg3 );
+
+ DMA_BLOCK( MGA_TEXORG4, tex->texorg4,
+ MGA_TEXWIDTH, tex->texwidth,
+ MGA_TEXHEIGHT, tex->texheight,
+ MGA_WR49, 0x00000000 );
+
+ DMA_BLOCK( MGA_WR57, 0x00000000,
+ MGA_WR53, 0x00000000,
+ MGA_WR61, 0x00000000,
+ MGA_WR52, tex->texwidth | MGA_G400_WR_MAGIC );
+
+ DMA_BLOCK( MGA_WR60, tex->texheight | MGA_G400_WR_MAGIC,
+ MGA_TEXTRANS, 0x0000ffff,
+ MGA_TEXTRANSHIGH, 0x0000ffff,
+ MGA_TEXCTL2, tex->texctl2 | MGA_G400_TC2_MAGIC );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_pipe( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int pipe = sarea_priv->warp_pipe;
+ DMA_LOCALS;
+
+ BEGIN_DMA( 3 );
+
+ DMA_BLOCK( MGA_WIADDR, MGA_WMODE_SUSPEND,
+ MGA_WVRTXSZ, 0x00000007,
+ MGA_WFLAG, 0x00000000,
+ MGA_WR24, 0x00000000 );
+
+ DMA_BLOCK( MGA_WR25, 0x00000100,
+ MGA_WR34, 0x00000000,
+ MGA_WR42, 0x0000ffff,
+ MGA_WR60, 0x0000ffff );
+
+ /* Padding required to to hardware bug.
+ */
+ DMA_BLOCK( MGA_DMAPAD, 0xffffffff,
+ MGA_DMAPAD, 0xffffffff,
+ MGA_DMAPAD, 0xffffffff,
+ MGA_WIADDR, (dev_priv->warp_pipe_phys[pipe] |
+ MGA_WMODE_START |
+ MGA_WAGP_ENABLE) );
+
+ ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_pipe( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int pipe = sarea_priv->warp_pipe;
+ DMA_LOCALS;
+
+/* printk("mga_g400_emit_pipe %x\n", pipe); */
+
+ BEGIN_DMA( 10 );
+
+ DMA_BLOCK( MGA_WIADDR2, MGA_WMODE_SUSPEND,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ if ( pipe & MGA_T2 ) {
+ DMA_BLOCK( MGA_WVRTXSZ, 0x00001e09,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ DMA_BLOCK( MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x1e000000 );
+ } else {
+ if ( dev_priv->warp_pipe & MGA_T2 ) {
+ /* Flush the WARP pipe */
+ DMA_BLOCK( MGA_YDST, 0x00000000,
+ MGA_FXLEFT, 0x00000000,
+ MGA_FXRIGHT, 0x00000001,
+ MGA_DWGCTL, MGA_DWGCTL_FLUSH );
+
+ DMA_BLOCK( MGA_LEN + MGA_EXEC, 0x00000001,
+ MGA_DWGSYNC, 0x00007000,
+ MGA_TEXCTL2, MGA_G400_TC2_MAGIC,
+ MGA_LEN + MGA_EXEC, 0x00000000 );
+
+ DMA_BLOCK( MGA_TEXCTL2, (MGA_DUALTEX |
+ MGA_G400_TC2_MAGIC),
+ MGA_LEN + MGA_EXEC, 0x00000000,
+ MGA_TEXCTL2, MGA_G400_TC2_MAGIC,
+ MGA_DMAPAD, 0x00000000 );
+ }
+
+ DMA_BLOCK( MGA_WVRTXSZ, 0x00001807,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000 );
+
+ DMA_BLOCK( MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x00000000,
+ MGA_WACCEPTSEQ, 0x18000000 );
+ }
+
+ DMA_BLOCK( MGA_WFLAG, 0x00000000,
+ MGA_WFLAG1, 0x00000000,
+ MGA_WR56, MGA_G400_WR56_MAGIC,
+ MGA_DMAPAD, 0x00000000 );
+
+ DMA_BLOCK( MGA_WR49, 0x00000000, /* tex0 */
+ MGA_WR57, 0x00000000, /* tex0 */
+ MGA_WR53, 0x00000000, /* tex1 */
+ MGA_WR61, 0x00000000 ); /* tex1 */
+
+ DMA_BLOCK( MGA_WR54, MGA_G400_WR_MAGIC, /* tex0 width */
+ MGA_WR62, MGA_G400_WR_MAGIC, /* tex0 height */
+ MGA_WR52, MGA_G400_WR_MAGIC, /* tex1 width */
+ MGA_WR60, MGA_G400_WR_MAGIC ); /* tex1 height */
+
+ /* Padding required to to hardware bug */
+ DMA_BLOCK( MGA_DMAPAD, 0xffffffff,
+ MGA_DMAPAD, 0xffffffff,
+ MGA_DMAPAD, 0xffffffff,
+ MGA_WIADDR2, (dev_priv->warp_pipe_phys[pipe] |
+ MGA_WMODE_START |
+ MGA_WAGP_ENABLE) );
+
+ ADVANCE_DMA();
+}
+
+static void mga_g200_emit_state( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+
+ if ( sarea_priv->warp_pipe != dev_priv->warp_pipe ) {
+ mga_g200_emit_pipe( dev_priv );
+ dev_priv->warp_pipe = sarea_priv->warp_pipe;
+ }
+
+ if ( dirty & MGA_UPLOAD_CONTEXT ) {
+ mga_g200_emit_context( dev_priv );
+ sarea_priv->dirty &= ~MGA_UPLOAD_CONTEXT;
+ }
+
+ if ( dirty & MGA_UPLOAD_TEX0 ) {
+ mga_g200_emit_tex0( dev_priv );
+ sarea_priv->dirty &= ~MGA_UPLOAD_TEX0;
+ }
+}
+
+static void mga_g400_emit_state( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+ int multitex = sarea_priv->warp_pipe & MGA_T2;
+
+ if ( sarea_priv->warp_pipe != dev_priv->warp_pipe ) {
+ mga_g400_emit_pipe( dev_priv );
+ dev_priv->warp_pipe = sarea_priv->warp_pipe;
+ }
+
+ if ( dirty & MGA_UPLOAD_CONTEXT ) {
+ mga_g400_emit_context( dev_priv );
+ sarea_priv->dirty &= ~MGA_UPLOAD_CONTEXT;
+ }
+
+ if ( dirty & MGA_UPLOAD_TEX0 ) {
+ mga_g400_emit_tex0( dev_priv );
+ sarea_priv->dirty &= ~MGA_UPLOAD_TEX0;
+ }
+
+ if ( (dirty & MGA_UPLOAD_TEX1) && multitex ) {
+ mga_g400_emit_tex1( dev_priv );
+ sarea_priv->dirty &= ~MGA_UPLOAD_TEX1;
+ }
+}
+
+
+/* ================================================================
+ * SAREA state verification
+ */
+
+/* Disallow all write destinations except the front and backbuffer.
+ */
+static int mga_verify_context( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+
+ if ( ctx->dstorg != dev_priv->front_offset &&
+ ctx->dstorg != dev_priv->back_offset ) {
+ DRM_ERROR( "*** bad DSTORG: %x (front %x, back %x)\n\n",
+ ctx->dstorg, dev_priv->front_offset,
+ dev_priv->back_offset );
+ ctx->dstorg = 0;
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
+
+/* Disallow texture reads from PCI space.
+ */
+static int mga_verify_tex( drm_mga_private_t *dev_priv, int unit )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[unit];
+ unsigned int org;
+
+ org = tex->texorg & (MGA_TEXORGMAP_MASK | MGA_TEXORGACC_MASK);
+
+ if ( org == (MGA_TEXORGMAP_SYSMEM | MGA_TEXORGACC_PCI) ) {
+ DRM_ERROR( "*** bad TEXORG: 0x%x, unit %d\n",
+ tex->texorg, unit );
+ tex->texorg = 0;
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
+
+static int mga_verify_state( drm_mga_private_t *dev_priv )
+{
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+ int ret = 0;
+
+ if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+ if ( dirty & MGA_UPLOAD_CONTEXT )
+ ret |= mga_verify_context( dev_priv );
+
+ if ( dirty & MGA_UPLOAD_TEX0 )
+ ret |= mga_verify_tex( dev_priv, 0 );
+
+ if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) {
+ if ( dirty & MGA_UPLOAD_TEX1 )
+ ret |= mga_verify_tex( dev_priv, 1 );
+
+ if ( dirty & MGA_UPLOAD_PIPE )
+ ret |= ( sarea_priv->warp_pipe > MGA_MAX_G400_PIPES );
+ } else {
+ if ( dirty & MGA_UPLOAD_PIPE )
+ ret |= ( sarea_priv->warp_pipe > MGA_MAX_G200_PIPES );
+ }
+
+ return ( ret == 0 );
+}
+
+static int mga_verify_iload( drm_mga_private_t *dev_priv,
+ unsigned int dstorg, unsigned int length )
+{
+ if ( dstorg < dev_priv->texture_offset ||
+ dstorg + length > (dev_priv->texture_offset +
+ dev_priv->texture_size) ) {
+ DRM_ERROR( "*** bad iload DSTORG: 0x%x\n", dstorg );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( length & MGA_ILOAD_MASK ) {
+ DRM_ERROR( "*** bad iload length: 0x%x\n",
+ length & MGA_ILOAD_MASK );
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
+
+static int mga_verify_blit( drm_mga_private_t *dev_priv,
+ unsigned int srcorg, unsigned int dstorg )
+{
+ if ( (srcorg & 0x3) == (MGA_SRCACC_PCI | MGA_SRCMAP_SYSMEM) ||
+ (dstorg & 0x3) == (MGA_SRCACC_PCI | MGA_SRCMAP_SYSMEM) ) {
+ DRM_ERROR( "*** bad blit: src=0x%x dst=0x%x\n",
+ srcorg, dstorg );
+ return DRM_ERR(EINVAL);
+ }
+ return 0;
+}
+
+
+/* ================================================================
+ *
+ */
+
+static void mga_dma_dispatch_clear( drm_device_t *dev,
+ drm_mga_clear_t *clear )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int nbox = sarea_priv->nbox;
+ int i;
+ DMA_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ BEGIN_DMA( 1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DWGSYNC, 0x00007100,
+ MGA_DWGSYNC, 0x00007000 );
+
+ ADVANCE_DMA();
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ drm_clip_rect_t *box = &pbox[i];
+ u32 height = box->y2 - box->y1;
+
+ DRM_DEBUG( " from=%d,%d to=%d,%d\n",
+ box->x1, box->y1, box->x2, box->y2 );
+
+ if ( clear->flags & MGA_FRONT ) {
+ BEGIN_DMA( 2 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, clear->color_mask,
+ MGA_YDSTLEN, (box->y1 << 16) | height,
+ MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_FCOL, clear->clear_color,
+ MGA_DSTORG, dev_priv->front_offset,
+ MGA_DWGCTL + MGA_EXEC,
+ dev_priv->clear_cmd );
+
+ ADVANCE_DMA();
+ }
+
+
+ if ( clear->flags & MGA_BACK ) {
+ BEGIN_DMA( 2 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, clear->color_mask,
+ MGA_YDSTLEN, (box->y1 << 16) | height,
+ MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_FCOL, clear->clear_color,
+ MGA_DSTORG, dev_priv->back_offset,
+ MGA_DWGCTL + MGA_EXEC,
+ dev_priv->clear_cmd );
+
+ ADVANCE_DMA();
+ }
+
+ if ( clear->flags & MGA_DEPTH ) {
+ BEGIN_DMA( 2 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, clear->depth_mask,
+ MGA_YDSTLEN, (box->y1 << 16) | height,
+ MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_FCOL, clear->clear_depth,
+ MGA_DSTORG, dev_priv->depth_offset,
+ MGA_DWGCTL + MGA_EXEC,
+ dev_priv->clear_cmd );
+
+ ADVANCE_DMA();
+ }
+
+ }
+
+ BEGIN_DMA( 1 );
+
+ /* Force reset of DWGCTL */
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, ctx->plnwt,
+ MGA_DWGCTL, ctx->dwgctl );
+
+ ADVANCE_DMA();
+
+ FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_swap( drm_device_t *dev )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int nbox = sarea_priv->nbox;
+ int i;
+ DMA_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ sarea_priv->last_frame.head = dev_priv->prim.tail;
+ sarea_priv->last_frame.wrap = dev_priv->prim.last_wrap;
+
+ BEGIN_DMA( 4 + nbox );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DWGSYNC, 0x00007100,
+ MGA_DWGSYNC, 0x00007000 );
+
+ DMA_BLOCK( MGA_DSTORG, dev_priv->front_offset,
+ MGA_MACCESS, dev_priv->maccess,
+ MGA_SRCORG, dev_priv->back_offset,
+ MGA_AR5, dev_priv->front_pitch );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, 0xffffffff,
+ MGA_DWGCTL, MGA_DWGCTL_COPY );
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ drm_clip_rect_t *box = &pbox[i];
+ u32 height = box->y2 - box->y1;
+ u32 start = box->y1 * dev_priv->front_pitch;
+
+ DRM_DEBUG( " from=%d,%d to=%d,%d\n",
+ box->x1, box->y1, box->x2, box->y2 );
+
+ DMA_BLOCK( MGA_AR0, start + box->x2 - 1,
+ MGA_AR3, start + box->x1,
+ MGA_FXBNDRY, ((box->x2 - 1) << 16) | box->x1,
+ MGA_YDSTLEN + MGA_EXEC,
+ (box->y1 << 16) | height );
+ }
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, ctx->plnwt,
+ MGA_SRCORG, dev_priv->front_offset,
+ MGA_DWGCTL, ctx->dwgctl );
+
+ ADVANCE_DMA();
+
+ FLUSH_DMA();
+
+ DRM_DEBUG( "%s... done.\n", __FUNCTION__ );
+}
+
+static void mga_dma_dispatch_vertex( drm_device_t *dev, drm_buf_t *buf )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ u32 address = (u32) buf->bus_address;
+ u32 length = (u32) buf->used;
+ int i = 0;
+ DMA_LOCALS;
+ DRM_DEBUG( "vertex: buf=%d used=%d\n", buf->idx, buf->used );
+
+ if ( buf->used ) {
+ buf_priv->dispatched = 1;
+
+ MGA_EMIT_STATE( dev_priv, sarea_priv->dirty );
+
+ do {
+ if ( i < sarea_priv->nbox ) {
+ mga_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+ }
+
+ BEGIN_DMA( 1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_SECADDRESS, (address |
+ MGA_DMA_VERTEX),
+ MGA_SECEND, ((address + length) |
+ MGA_PAGPXFER) );
+
+ ADVANCE_DMA();
+ } while ( ++i < sarea_priv->nbox );
+ }
+
+ if ( buf_priv->discard ) {
+ AGE_BUFFER( buf_priv );
+ buf->pending = 0;
+ buf->used = 0;
+ buf_priv->dispatched = 0;
+
+ mga_freelist_put( dev, buf );
+ }
+
+ FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_indices( drm_device_t *dev, drm_buf_t *buf,
+ unsigned int start, unsigned int end )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ u32 address = (u32) buf->bus_address;
+ int i = 0;
+ DMA_LOCALS;
+ DRM_DEBUG( "indices: buf=%d start=%d end=%d\n", buf->idx, start, end );
+
+ if ( start != end ) {
+ buf_priv->dispatched = 1;
+
+ MGA_EMIT_STATE( dev_priv, sarea_priv->dirty );
+
+ do {
+ if ( i < sarea_priv->nbox ) {
+ mga_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+ }
+
+ BEGIN_DMA( 1 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_SETUPADDRESS, address + start,
+ MGA_SETUPEND, ((address + end) |
+ MGA_PAGPXFER) );
+
+ ADVANCE_DMA();
+ } while ( ++i < sarea_priv->nbox );
+ }
+
+ if ( buf_priv->discard ) {
+ AGE_BUFFER( buf_priv );
+ buf->pending = 0;
+ buf->used = 0;
+ buf_priv->dispatched = 0;
+
+ mga_freelist_put( dev, buf );
+ }
+
+ FLUSH_DMA();
+}
+
+/* This copies a 64 byte aligned agp region to the frambuffer with a
+ * standard blit, the ioctl needs to do checking.
+ */
+static void mga_dma_dispatch_iload( drm_device_t *dev, drm_buf_t *buf,
+ unsigned int dstorg, unsigned int length )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+ drm_mga_context_regs_t *ctx = &dev_priv->sarea_priv->context_state;
+ u32 srcorg = buf->bus_address | MGA_SRCACC_AGP | MGA_SRCMAP_SYSMEM;
+ u32 y2;
+ DMA_LOCALS;
+ DRM_DEBUG( "buf=%d used=%d\n", buf->idx, buf->used );
+
+ y2 = length / 64;
+
+ BEGIN_DMA( 5 );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DWGSYNC, 0x00007100,
+ MGA_DWGSYNC, 0x00007000 );
+
+ DMA_BLOCK( MGA_DSTORG, dstorg,
+ MGA_MACCESS, 0x00000000,
+ MGA_SRCORG, srcorg,
+ MGA_AR5, 64 );
+
+ DMA_BLOCK( MGA_PITCH, 64,
+ MGA_PLNWT, 0xffffffff,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DWGCTL, MGA_DWGCTL_COPY );
+
+ DMA_BLOCK( MGA_AR0, 63,
+ MGA_AR3, 0,
+ MGA_FXBNDRY, (63 << 16) | 0,
+ MGA_YDSTLEN + MGA_EXEC, y2 );
+
+ DMA_BLOCK( MGA_PLNWT, ctx->plnwt,
+ MGA_SRCORG, dev_priv->front_offset,
+ MGA_PITCH, dev_priv->front_pitch,
+ MGA_DWGSYNC, 0x00007000 );
+
+ ADVANCE_DMA();
+
+ AGE_BUFFER( buf_priv );
+
+ buf->pending = 0;
+ buf->used = 0;
+ buf_priv->dispatched = 0;
+
+ mga_freelist_put( dev, buf );
+
+ FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_blit( drm_device_t *dev,
+ drm_mga_blit_t *blit )
+{
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int nbox = sarea_priv->nbox;
+ u32 scandir = 0, i;
+ DMA_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ BEGIN_DMA( 4 + nbox );
+
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_DMAPAD, 0x00000000,
+ MGA_DWGSYNC, 0x00007100,
+ MGA_DWGSYNC, 0x00007000 );
+
+ DMA_BLOCK( MGA_DWGCTL, MGA_DWGCTL_COPY,
+ MGA_PLNWT, blit->planemask,
+ MGA_SRCORG, blit->srcorg,
+ MGA_DSTORG, blit->dstorg );
+
+ DMA_BLOCK( MGA_SGN, scandir,
+ MGA_MACCESS, dev_priv->maccess,
+ MGA_AR5, blit->ydir * blit->src_pitch,
+ MGA_PITCH, blit->dst_pitch );
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ int srcx = pbox[i].x1 + blit->delta_sx;
+ int srcy = pbox[i].y1 + blit->delta_sy;
+ int dstx = pbox[i].x1 + blit->delta_dx;
+ int dsty = pbox[i].y1 + blit->delta_dy;
+ int h = pbox[i].y2 - pbox[i].y1;
+ int w = pbox[i].x2 - pbox[i].x1 - 1;
+ int start;
+
+ if ( blit->ydir == -1 ) {
+ srcy = blit->height - srcy - 1;
+ }
+
+ start = srcy * blit->src_pitch + srcx;
+
+ DMA_BLOCK( MGA_AR0, start + w,
+ MGA_AR3, start,
+ MGA_FXBNDRY, ((dstx + w) << 16) | (dstx & 0xffff),
+ MGA_YDSTLEN + MGA_EXEC, (dsty << 16) | h );
+ }
+
+ /* Do something to flush AGP?
+ */
+
+ /* Force reset of DWGCTL */
+ DMA_BLOCK( MGA_DMAPAD, 0x00000000,
+ MGA_PLNWT, ctx->plnwt,
+ MGA_PITCH, dev_priv->front_pitch,
+ MGA_DWGCTL, ctx->dwgctl );
+
+ ADVANCE_DMA();
+}
+
+
+/* ================================================================
+ *
+ */
+
+static int mga_dma_clear( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_clear_t clear;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( clear, (drm_mga_clear_t __user *)data, sizeof(clear) );
+
+ if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_clear( dev, &clear );
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+ return 0;
+}
+
+static int mga_dma_swap( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_swap( dev );
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+ return 0;
+}
+
+static int mga_dma_vertex( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_mga_buf_priv_t *buf_priv;
+ drm_mga_vertex_t vertex;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( vertex,
+ (drm_mga_vertex_t __user *)data,
+ sizeof(vertex) );
+
+ if(vertex.idx < 0 || vertex.idx > dma->buf_count) return DRM_ERR(EINVAL);
+ buf = dma->buflist[vertex.idx];
+ buf_priv = buf->dev_private;
+
+ buf->used = vertex.used;
+ buf_priv->discard = vertex.discard;
+
+ if ( !mga_verify_state( dev_priv ) ) {
+ if ( vertex.discard ) {
+ if ( buf_priv->dispatched == 1 )
+ AGE_BUFFER( buf_priv );
+ buf_priv->dispatched = 0;
+ mga_freelist_put( dev, buf );
+ }
+ return DRM_ERR(EINVAL);
+ }
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_vertex( dev, buf );
+
+ return 0;
+}
+
+static int mga_dma_indices( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_mga_buf_priv_t *buf_priv;
+ drm_mga_indices_t indices;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( indices,
+ (drm_mga_indices_t __user *)data,
+ sizeof(indices) );
+
+ if(indices.idx < 0 || indices.idx > dma->buf_count) return DRM_ERR(EINVAL);
+
+ buf = dma->buflist[indices.idx];
+ buf_priv = buf->dev_private;
+
+ buf_priv->discard = indices.discard;
+
+ if ( !mga_verify_state( dev_priv ) ) {
+ if ( indices.discard ) {
+ if ( buf_priv->dispatched == 1 )
+ AGE_BUFFER( buf_priv );
+ buf_priv->dispatched = 0;
+ mga_freelist_put( dev, buf );
+ }
+ return DRM_ERR(EINVAL);
+ }
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_indices( dev, buf, indices.start, indices.end );
+
+ return 0;
+}
+
+static int mga_dma_iload( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_device_dma_t *dma = dev->dma;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_buf_t *buf;
+ drm_mga_buf_priv_t *buf_priv;
+ drm_mga_iload_t iload;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( iload, (drm_mga_iload_t __user *)data, sizeof(iload) );
+
+#if 0
+ if ( mga_do_wait_for_idle( dev_priv ) < 0 ) {
+ if ( MGA_DMA_DEBUG )
+ DRM_INFO( "%s: -EBUSY\n", __FUNCTION__ );
+ return DRM_ERR(EBUSY);
+ }
+#endif
+ if(iload.idx < 0 || iload.idx > dma->buf_count) return DRM_ERR(EINVAL);
+
+ buf = dma->buflist[iload.idx];
+ buf_priv = buf->dev_private;
+
+ if ( mga_verify_iload( dev_priv, iload.dstorg, iload.length ) ) {
+ mga_freelist_put( dev, buf );
+ return DRM_ERR(EINVAL);
+ }
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_iload( dev, buf, iload.dstorg, iload.length );
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+ return 0;
+}
+
+static int mga_dma_blit( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_mga_blit_t blit;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( blit, (drm_mga_blit_t __user *)data, sizeof(blit) );
+
+ if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+ if ( mga_verify_blit( dev_priv, blit.srcorg, blit.dstorg ) )
+ return DRM_ERR(EINVAL);
+
+ WRAP_TEST_WITH_RETURN( dev_priv );
+
+ mga_dma_dispatch_blit( dev, &blit );
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+ return 0;
+}
+
+static int mga_getparam( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_mga_private_t *dev_priv = dev->dev_private;
+ drm_mga_getparam_t param;
+ int value;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( param, (drm_mga_getparam_t __user *)data,
+ sizeof(param) );
+
+ DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+ switch( param.param ) {
+ case MGA_PARAM_IRQ_NR:
+ value = dev->irq;
+ break;
+ default:
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+drm_ioctl_desc_t mga_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_MGA_INIT)] = { mga_dma_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_MGA_FLUSH)] = { mga_dma_flush, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_RESET)] = { mga_dma_reset, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_SWAP)] = { mga_dma_swap, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_CLEAR)] = { mga_dma_clear, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_VERTEX)] = { mga_dma_vertex, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_INDICES)] = { mga_dma_indices, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_ILOAD)] = { mga_dma_iload, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_BLIT)] = { mga_dma_blit, 1, 0 },
+ [DRM_IOCTL_NR(DRM_MGA_GETPARAM)]= { mga_getparam, 1, 0 },
+};
+
+int mga_max_ioctl = DRM_ARRAY_SIZE(mga_ioctls);
diff --git a/drivers/char/drm/mga_ucode.h b/drivers/char/drm/mga_ucode.h
new file mode 100644
index 000000000000..fa0f82ec9fa0
--- /dev/null
+++ b/drivers/char/drm/mga_ucode.h
@@ -0,0 +1,11645 @@
+/* mga_ucode.h -- Matrox G200/G400 WARP engine microcode -*- linux-c -*-
+ * Created: Thu Jan 11 21:20:43 2001 by gareth@valinux.com
+ *
+ * Copyright 1999 Matrox Graphics Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * MATROX GRAPHICS INC., OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Kernel-based WARP engine management:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * WARP pipes are named according to the functions they perform, where:
+ *
+ * - T stands for computation of texture stage 0
+ * - T2 stands for computation of both texture stage 0 and texture stage 1
+ * - G stands for computation of triangle intensity (Gouraud interpolation)
+ * - Z stands for computation of Z buffer interpolation
+ * - S stands for computation of specular highlight
+ * - A stands for computation of the alpha channel
+ * - F stands for computation of vertex fog interpolation
+ */
+
+static unsigned char warp_g200_tgz[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x72, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x60, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x03, 0x80, 0x0A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x57, 0x39, 0x20, 0xE9,
+0x28, 0x19, 0x60, 0xEC,
+
+0x2B, 0x32, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x16, 0x28, 0x20, 0xE9,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x2B, 0x20, 0xE9,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x85, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x84, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x82, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x7F, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgza[] = {
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x7D, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1F, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3F, 0x3D, 0x5D, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x00, 0x80, 0x00, 0xE8,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0x26, 0x1F, 0xDF,
+0x9D, 0x1F, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x9E, 0x3F, 0x4F, 0xE9,
+
+0x07, 0x07, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x9C, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x7A, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x79, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x77, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzaf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x83, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6F, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0x17, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x2D, 0x20,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x1F, 0x62, 0x57, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x3F, 0x3D, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x17, 0x26, 0x17, 0xDF,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x35, 0x17, 0x4F, 0xE9,
+
+0x1F, 0x26, 0x1F, 0xDF,
+0x9D, 0x1F, 0x4F, 0xE9,
+
+0x9E, 0x3F, 0x4F, 0xE9,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x07, 0x07, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x9C, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x73, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x71, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6E, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x7F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x17, 0x50, 0x56, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x00, 0x80, 0x00, 0xE8,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x17, 0x26, 0x17, 0xDF,
+0x35, 0x17, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x78, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x77, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x75, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x72, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzs[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8B, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x77, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x8F, 0x20,
+
+0xA5, 0x37, 0x4F, 0xE9,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x6C, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6B, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x69, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsa[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x7B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x44, 0x4C, 0xB6,
+0x05, 0x44, 0x54, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2F, 0xC0, 0x44, 0xC6,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0x9D, 0x17, 0x4F, 0xE9,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x9E, 0x37, 0x4F, 0xE9,
+0x2F, 0x17, 0x2F, 0xAF,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x9C, 0x80, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x68, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x67, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x65, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsaf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x94, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x80, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3E, 0x3D, 0x5D, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x2F, 0x20,
+0x00, 0xE0,
+0xA3, 0x0F, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0xA1, 0x1F, 0x4F, 0xE9,
+
+0x1E, 0x26, 0x1E, 0xDF,
+0x9D, 0x1E, 0x4F, 0xE9,
+
+0x35, 0x17, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x07, 0x07, 0x1E, 0xAF,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x9E, 0x3E, 0x4F, 0xE9,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x9C, 0x80, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x63, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x60, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x5D, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x7B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2F, 0x20,
+0x00, 0xE0,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0x35, 0x17, 0x4F, 0xE9,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x68, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x67, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x65, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g400_t2gz[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x78, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x69, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x25, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9F, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBE, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x7D, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gza[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x7C, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x6D, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x29, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x3D, 0xCF, 0x74, 0xC2,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9B, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBA, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x79, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzaf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x81, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x72, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x37, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x0F, 0xCF, 0x74, 0xC6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x00, 0x80, 0x00, 0xE8,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x55, 0xB6,
+0x02, 0x45, 0x65, 0xB6,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x96, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xB5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x7D, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x6E, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0F, 0xCF, 0x75, 0xC6,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x28, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x45, 0x55, 0xB6,
+0x1A, 0x45, 0x65, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9A, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBB, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x78, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzs[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x85, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x76, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x31, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x20,
+0x1A, 0x20,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA7, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x92, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xB2, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x70, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsa[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8A, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7B, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x36, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x8D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xAD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x6B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsaf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8E, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7F, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x3A, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x55, 0xB6,
+0x02, 0x45, 0x65, 0xB6,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x89, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xA9, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x67, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8A, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7B, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x36, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB6,
+0x1A, 0x45, 0x65, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x8D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xAD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x6B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgz[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x58, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4A, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x1D, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAF, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD6, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x9D, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgza[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x5C, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4E, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x3D, 0xCF, 0x74, 0xC2,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x20, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAB, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD3, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x99, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzaf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x61, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x53, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x37, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x26, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x27, 0xCF, 0x74, 0xC6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x00, 0x80, 0x00, 0xE8,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x4D, 0xB6,
+0x02, 0x45, 0x55, 0xB6,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xA6, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xCD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x94, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x5D, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4F, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x27, 0xCF, 0x75, 0xC6,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x20, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x45, 0x4D, 0xB6,
+0x1A, 0x45, 0x55, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAA, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD3, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x98, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzs[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x65, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x57, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x29, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA7, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xA2, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xCA, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x90, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsa[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6A, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x5C, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x9D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x8B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsaf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6E, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x60, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x32, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x4D, 0xB6,
+0x02, 0x45, 0x55, 0xB6,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x99, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC1, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x87, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6A, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x5C, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB6,
+0x1A, 0x45, 0x55, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x9D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x8B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
diff --git a/drivers/char/drm/mga_warp.c b/drivers/char/drm/mga_warp.c
new file mode 100644
index 000000000000..0a3a0cc700dc
--- /dev/null
+++ b/drivers/char/drm/mga_warp.c
@@ -0,0 +1,210 @@
+/* mga_warp.c -- Matrox G200/G400 WARP engine management -*- linux-c -*-
+ * Created: Thu Jan 11 21:29:32 2001 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+#include "mga_ucode.h"
+
+
+#define MGA_WARP_CODE_ALIGN 256 /* in bytes */
+
+#define WARP_UCODE_SIZE( which ) \
+ ((sizeof(which) / MGA_WARP_CODE_ALIGN + 1) * MGA_WARP_CODE_ALIGN)
+
+#define WARP_UCODE_INSTALL( which, where ) \
+do { \
+ DRM_DEBUG( " pcbase = 0x%08lx vcbase = %p\n", pcbase, vcbase );\
+ dev_priv->warp_pipe_phys[where] = pcbase; \
+ memcpy( vcbase, which, sizeof(which) ); \
+ pcbase += WARP_UCODE_SIZE( which ); \
+ vcbase += WARP_UCODE_SIZE( which ); \
+} while (0)
+
+
+static unsigned int mga_warp_g400_microcode_size( drm_mga_private_t *dev_priv )
+{
+ unsigned int size;
+
+ size = ( WARP_UCODE_SIZE( warp_g400_tgz ) +
+ WARP_UCODE_SIZE( warp_g400_tgza ) +
+ WARP_UCODE_SIZE( warp_g400_tgzaf ) +
+ WARP_UCODE_SIZE( warp_g400_tgzf ) +
+ WARP_UCODE_SIZE( warp_g400_tgzs ) +
+ WARP_UCODE_SIZE( warp_g400_tgzsa ) +
+ WARP_UCODE_SIZE( warp_g400_tgzsaf ) +
+ WARP_UCODE_SIZE( warp_g400_tgzsf ) +
+ WARP_UCODE_SIZE( warp_g400_t2gz ) +
+ WARP_UCODE_SIZE( warp_g400_t2gza ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzaf ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzf ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzs ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzsa ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzsaf ) +
+ WARP_UCODE_SIZE( warp_g400_t2gzsf ) );
+
+ size = PAGE_ALIGN( size );
+
+ DRM_DEBUG( "G400 ucode size = %d bytes\n", size );
+ return size;
+}
+
+static unsigned int mga_warp_g200_microcode_size( drm_mga_private_t *dev_priv )
+{
+ unsigned int size;
+
+ size = ( WARP_UCODE_SIZE( warp_g200_tgz ) +
+ WARP_UCODE_SIZE( warp_g200_tgza ) +
+ WARP_UCODE_SIZE( warp_g200_tgzaf ) +
+ WARP_UCODE_SIZE( warp_g200_tgzf ) +
+ WARP_UCODE_SIZE( warp_g200_tgzs ) +
+ WARP_UCODE_SIZE( warp_g200_tgzsa ) +
+ WARP_UCODE_SIZE( warp_g200_tgzsaf ) +
+ WARP_UCODE_SIZE( warp_g200_tgzsf ) );
+
+ size = PAGE_ALIGN( size );
+
+ DRM_DEBUG( "G200 ucode size = %d bytes\n", size );
+ return size;
+}
+
+static int mga_warp_install_g400_microcode( drm_mga_private_t *dev_priv )
+{
+ unsigned char *vcbase = dev_priv->warp->handle;
+ unsigned long pcbase = dev_priv->warp->offset;
+ unsigned int size;
+
+ size = mga_warp_g400_microcode_size( dev_priv );
+ if ( size > dev_priv->warp->size ) {
+ DRM_ERROR( "microcode too large! (%u > %lu)\n",
+ size, dev_priv->warp->size );
+ return DRM_ERR(ENOMEM);
+ }
+
+ memset( dev_priv->warp_pipe_phys, 0,
+ sizeof(dev_priv->warp_pipe_phys) );
+
+ WARP_UCODE_INSTALL( warp_g400_tgz, MGA_WARP_TGZ );
+ WARP_UCODE_INSTALL( warp_g400_tgzf, MGA_WARP_TGZF );
+ WARP_UCODE_INSTALL( warp_g400_tgza, MGA_WARP_TGZA );
+ WARP_UCODE_INSTALL( warp_g400_tgzaf, MGA_WARP_TGZAF );
+ WARP_UCODE_INSTALL( warp_g400_tgzs, MGA_WARP_TGZS );
+ WARP_UCODE_INSTALL( warp_g400_tgzsf, MGA_WARP_TGZSF );
+ WARP_UCODE_INSTALL( warp_g400_tgzsa, MGA_WARP_TGZSA );
+ WARP_UCODE_INSTALL( warp_g400_tgzsaf, MGA_WARP_TGZSAF );
+
+ WARP_UCODE_INSTALL( warp_g400_t2gz, MGA_WARP_T2GZ );
+ WARP_UCODE_INSTALL( warp_g400_t2gzf, MGA_WARP_T2GZF );
+ WARP_UCODE_INSTALL( warp_g400_t2gza, MGA_WARP_T2GZA );
+ WARP_UCODE_INSTALL( warp_g400_t2gzaf, MGA_WARP_T2GZAF );
+ WARP_UCODE_INSTALL( warp_g400_t2gzs, MGA_WARP_T2GZS );
+ WARP_UCODE_INSTALL( warp_g400_t2gzsf, MGA_WARP_T2GZSF );
+ WARP_UCODE_INSTALL( warp_g400_t2gzsa, MGA_WARP_T2GZSA );
+ WARP_UCODE_INSTALL( warp_g400_t2gzsaf, MGA_WARP_T2GZSAF );
+
+ return 0;
+}
+
+static int mga_warp_install_g200_microcode( drm_mga_private_t *dev_priv )
+{
+ unsigned char *vcbase = dev_priv->warp->handle;
+ unsigned long pcbase = dev_priv->warp->offset;
+ unsigned int size;
+
+ size = mga_warp_g200_microcode_size( dev_priv );
+ if ( size > dev_priv->warp->size ) {
+ DRM_ERROR( "microcode too large! (%u > %lu)\n",
+ size, dev_priv->warp->size );
+ return DRM_ERR(ENOMEM);
+ }
+
+ memset( dev_priv->warp_pipe_phys, 0,
+ sizeof(dev_priv->warp_pipe_phys) );
+
+ WARP_UCODE_INSTALL( warp_g200_tgz, MGA_WARP_TGZ );
+ WARP_UCODE_INSTALL( warp_g200_tgzf, MGA_WARP_TGZF );
+ WARP_UCODE_INSTALL( warp_g200_tgza, MGA_WARP_TGZA );
+ WARP_UCODE_INSTALL( warp_g200_tgzaf, MGA_WARP_TGZAF );
+ WARP_UCODE_INSTALL( warp_g200_tgzs, MGA_WARP_TGZS );
+ WARP_UCODE_INSTALL( warp_g200_tgzsf, MGA_WARP_TGZSF );
+ WARP_UCODE_INSTALL( warp_g200_tgzsa, MGA_WARP_TGZSA );
+ WARP_UCODE_INSTALL( warp_g200_tgzsaf, MGA_WARP_TGZSAF );
+
+ return 0;
+}
+
+int mga_warp_install_microcode( drm_mga_private_t *dev_priv )
+{
+ switch ( dev_priv->chipset ) {
+ case MGA_CARD_TYPE_G400:
+ return mga_warp_install_g400_microcode( dev_priv );
+ case MGA_CARD_TYPE_G200:
+ return mga_warp_install_g200_microcode( dev_priv );
+ default:
+ return DRM_ERR(EINVAL);
+ }
+}
+
+#define WMISC_EXPECTED (MGA_WUCODECACHE_ENABLE | MGA_WMASTER_ENABLE)
+
+int mga_warp_init( drm_mga_private_t *dev_priv )
+{
+ u32 wmisc;
+
+ /* FIXME: Get rid of these damned magic numbers...
+ */
+ switch ( dev_priv->chipset ) {
+ case MGA_CARD_TYPE_G400:
+ MGA_WRITE( MGA_WIADDR2, MGA_WMODE_SUSPEND );
+ MGA_WRITE( MGA_WGETMSB, 0x00000E00 );
+ MGA_WRITE( MGA_WVRTXSZ, 0x00001807 );
+ MGA_WRITE( MGA_WACCEPTSEQ, 0x18000000 );
+ break;
+ case MGA_CARD_TYPE_G200:
+ MGA_WRITE( MGA_WIADDR, MGA_WMODE_SUSPEND );
+ MGA_WRITE( MGA_WGETMSB, 0x1606 );
+ MGA_WRITE( MGA_WVRTXSZ, 7 );
+ break;
+ default:
+ return DRM_ERR(EINVAL);
+ }
+
+ MGA_WRITE( MGA_WMISC, (MGA_WUCODECACHE_ENABLE |
+ MGA_WMASTER_ENABLE |
+ MGA_WCACHEFLUSH_ENABLE) );
+ wmisc = MGA_READ( MGA_WMISC );
+ if ( wmisc != WMISC_EXPECTED ) {
+ DRM_ERROR( "WARP engine config failed! 0x%x != 0x%x\n",
+ wmisc, WMISC_EXPECTED );
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
diff --git a/drivers/char/drm/r128_cce.c b/drivers/char/drm/r128_cce.c
new file mode 100644
index 000000000000..08ed8d01d9d9
--- /dev/null
+++ b/drivers/char/drm/r128_cce.c
@@ -0,0 +1,943 @@
+/* r128_cce.c -- ATI Rage 128 driver -*- linux-c -*-
+ * Created: Wed Apr 5 19:24:19 2000 by kevin@precisioninsight.com
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+#define R128_FIFO_DEBUG 0
+
+/* CCE microcode (from ATI) */
+static u32 r128_cce_microcode[] = {
+ 0, 276838400, 0, 268449792, 2, 142, 2, 145, 0, 1076765731, 0,
+ 1617039951, 0, 774592877, 0, 1987540286, 0, 2307490946U, 0,
+ 599558925, 0, 589505315, 0, 596487092, 0, 589505315, 1,
+ 11544576, 1, 206848, 1, 311296, 1, 198656, 2, 912273422, 11,
+ 262144, 0, 0, 1, 33559837, 1, 7438, 1, 14809, 1, 6615, 12, 28,
+ 1, 6614, 12, 28, 2, 23, 11, 18874368, 0, 16790922, 1, 409600, 9,
+ 30, 1, 147854772, 16, 420483072, 3, 8192, 0, 10240, 1, 198656,
+ 1, 15630, 1, 51200, 10, 34858, 9, 42, 1, 33559823, 2, 10276, 1,
+ 15717, 1, 15718, 2, 43, 1, 15936948, 1, 570480831, 1, 14715071,
+ 12, 322123831, 1, 33953125, 12, 55, 1, 33559908, 1, 15718, 2,
+ 46, 4, 2099258, 1, 526336, 1, 442623, 4, 4194365, 1, 509952, 1,
+ 459007, 3, 0, 12, 92, 2, 46, 12, 176, 1, 15734, 1, 206848, 1,
+ 18432, 1, 133120, 1, 100670734, 1, 149504, 1, 165888, 1,
+ 15975928, 1, 1048576, 6, 3145806, 1, 15715, 16, 2150645232U, 2,
+ 268449859, 2, 10307, 12, 176, 1, 15734, 1, 15735, 1, 15630, 1,
+ 15631, 1, 5253120, 6, 3145810, 16, 2150645232U, 1, 15864, 2, 82,
+ 1, 343310, 1, 1064207, 2, 3145813, 1, 15728, 1, 7817, 1, 15729,
+ 3, 15730, 12, 92, 2, 98, 1, 16168, 1, 16167, 1, 16002, 1, 16008,
+ 1, 15974, 1, 15975, 1, 15990, 1, 15976, 1, 15977, 1, 15980, 0,
+ 15981, 1, 10240, 1, 5253120, 1, 15720, 1, 198656, 6, 110, 1,
+ 180224, 1, 103824738, 2, 112, 2, 3145839, 0, 536885440, 1,
+ 114880, 14, 125, 12, 206975, 1, 33559995, 12, 198784, 0,
+ 33570236, 1, 15803, 0, 15804, 3, 294912, 1, 294912, 3, 442370,
+ 1, 11544576, 0, 811612160, 1, 12593152, 1, 11536384, 1,
+ 14024704, 7, 310382726, 0, 10240, 1, 14796, 1, 14797, 1, 14793,
+ 1, 14794, 0, 14795, 1, 268679168, 1, 9437184, 1, 268449792, 1,
+ 198656, 1, 9452827, 1, 1075854602, 1, 1075854603, 1, 557056, 1,
+ 114880, 14, 159, 12, 198784, 1, 1109409213, 12, 198783, 1,
+ 1107312059, 12, 198784, 1, 1109409212, 2, 162, 1, 1075854781, 1,
+ 1073757627, 1, 1075854780, 1, 540672, 1, 10485760, 6, 3145894,
+ 16, 274741248, 9, 168, 3, 4194304, 3, 4209949, 0, 0, 0, 256, 14,
+ 174, 1, 114857, 1, 33560007, 12, 176, 0, 10240, 1, 114858, 1,
+ 33560018, 1, 114857, 3, 33560007, 1, 16008, 1, 114874, 1,
+ 33560360, 1, 114875, 1, 33560154, 0, 15963, 0, 256, 0, 4096, 1,
+ 409611, 9, 188, 0, 10240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int R128_READ_PLL(drm_device_t *dev, int addr)
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+
+ R128_WRITE8(R128_CLOCK_CNTL_INDEX, addr & 0x1f);
+ return R128_READ(R128_CLOCK_CNTL_DATA);
+}
+
+#if R128_FIFO_DEBUG
+static void r128_status( drm_r128_private_t *dev_priv )
+{
+ printk( "GUI_STAT = 0x%08x\n",
+ (unsigned int)R128_READ( R128_GUI_STAT ) );
+ printk( "PM4_STAT = 0x%08x\n",
+ (unsigned int)R128_READ( R128_PM4_STAT ) );
+ printk( "PM4_BUFFER_DL_WPTR = 0x%08x\n",
+ (unsigned int)R128_READ( R128_PM4_BUFFER_DL_WPTR ) );
+ printk( "PM4_BUFFER_DL_RPTR = 0x%08x\n",
+ (unsigned int)R128_READ( R128_PM4_BUFFER_DL_RPTR ) );
+ printk( "PM4_MICRO_CNTL = 0x%08x\n",
+ (unsigned int)R128_READ( R128_PM4_MICRO_CNTL ) );
+ printk( "PM4_BUFFER_CNTL = 0x%08x\n",
+ (unsigned int)R128_READ( R128_PM4_BUFFER_CNTL ) );
+}
+#endif
+
+
+/* ================================================================
+ * Engine, FIFO control
+ */
+
+static int r128_do_pixcache_flush( drm_r128_private_t *dev_priv )
+{
+ u32 tmp;
+ int i;
+
+ tmp = R128_READ( R128_PC_NGUI_CTLSTAT ) | R128_PC_FLUSH_ALL;
+ R128_WRITE( R128_PC_NGUI_CTLSTAT, tmp );
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ if ( !(R128_READ( R128_PC_NGUI_CTLSTAT ) & R128_PC_BUSY) ) {
+ return 0;
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if R128_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int r128_do_wait_for_fifo( drm_r128_private_t *dev_priv, int entries )
+{
+ int i;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ int slots = R128_READ( R128_GUI_STAT ) & R128_GUI_FIFOCNT_MASK;
+ if ( slots >= entries ) return 0;
+ DRM_UDELAY( 1 );
+ }
+
+#if R128_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int r128_do_wait_for_idle( drm_r128_private_t *dev_priv )
+{
+ int i, ret;
+
+ ret = r128_do_wait_for_fifo( dev_priv, 64 );
+ if ( ret ) return ret;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ if ( !(R128_READ( R128_GUI_STAT ) & R128_GUI_ACTIVE) ) {
+ r128_do_pixcache_flush( dev_priv );
+ return 0;
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if R128_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+
+/* ================================================================
+ * CCE control, initialization
+ */
+
+/* Load the microcode for the CCE */
+static void r128_cce_load_microcode( drm_r128_private_t *dev_priv )
+{
+ int i;
+
+ DRM_DEBUG( "\n" );
+
+ r128_do_wait_for_idle( dev_priv );
+
+ R128_WRITE( R128_PM4_MICROCODE_ADDR, 0 );
+ for ( i = 0 ; i < 256 ; i++ ) {
+ R128_WRITE( R128_PM4_MICROCODE_DATAH,
+ r128_cce_microcode[i * 2] );
+ R128_WRITE( R128_PM4_MICROCODE_DATAL,
+ r128_cce_microcode[i * 2 + 1] );
+ }
+}
+
+/* Flush any pending commands to the CCE. This should only be used just
+ * prior to a wait for idle, as it informs the engine that the command
+ * stream is ending.
+ */
+static void r128_do_cce_flush( drm_r128_private_t *dev_priv )
+{
+ u32 tmp;
+
+ tmp = R128_READ( R128_PM4_BUFFER_DL_WPTR ) | R128_PM4_BUFFER_DL_DONE;
+ R128_WRITE( R128_PM4_BUFFER_DL_WPTR, tmp );
+}
+
+/* Wait for the CCE to go idle.
+ */
+int r128_do_cce_idle( drm_r128_private_t *dev_priv )
+{
+ int i;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ if ( GET_RING_HEAD( dev_priv ) == dev_priv->ring.tail ) {
+ int pm4stat = R128_READ( R128_PM4_STAT );
+ if ( ( (pm4stat & R128_PM4_FIFOCNT_MASK) >=
+ dev_priv->cce_fifo_size ) &&
+ !(pm4stat & (R128_PM4_BUSY |
+ R128_PM4_GUI_ACTIVE)) ) {
+ return r128_do_pixcache_flush( dev_priv );
+ }
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if R128_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+ r128_status( dev_priv );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+/* Start the Concurrent Command Engine.
+ */
+static void r128_do_cce_start( drm_r128_private_t *dev_priv )
+{
+ r128_do_wait_for_idle( dev_priv );
+
+ R128_WRITE( R128_PM4_BUFFER_CNTL,
+ dev_priv->cce_mode | dev_priv->ring.size_l2qw
+ | R128_PM4_BUFFER_CNTL_NOUPDATE );
+ R128_READ( R128_PM4_BUFFER_ADDR ); /* as per the sample code */
+ R128_WRITE( R128_PM4_MICRO_CNTL, R128_PM4_MICRO_FREERUN );
+
+ dev_priv->cce_running = 1;
+}
+
+/* Reset the Concurrent Command Engine. This will not flush any pending
+ * commands, so you must wait for the CCE command stream to complete
+ * before calling this routine.
+ */
+static void r128_do_cce_reset( drm_r128_private_t *dev_priv )
+{
+ R128_WRITE( R128_PM4_BUFFER_DL_WPTR, 0 );
+ R128_WRITE( R128_PM4_BUFFER_DL_RPTR, 0 );
+ dev_priv->ring.tail = 0;
+}
+
+/* Stop the Concurrent Command Engine. This will not flush any pending
+ * commands, so you must flush the command stream and wait for the CCE
+ * to go idle before calling this routine.
+ */
+static void r128_do_cce_stop( drm_r128_private_t *dev_priv )
+{
+ R128_WRITE( R128_PM4_MICRO_CNTL, 0 );
+ R128_WRITE( R128_PM4_BUFFER_CNTL,
+ R128_PM4_NONPM4 | R128_PM4_BUFFER_CNTL_NOUPDATE );
+
+ dev_priv->cce_running = 0;
+}
+
+/* Reset the engine. This will stop the CCE if it is running.
+ */
+static int r128_do_engine_reset( drm_device_t *dev )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ u32 clock_cntl_index, mclk_cntl, gen_reset_cntl;
+
+ r128_do_pixcache_flush( dev_priv );
+
+ clock_cntl_index = R128_READ( R128_CLOCK_CNTL_INDEX );
+ mclk_cntl = R128_READ_PLL( dev, R128_MCLK_CNTL );
+
+ R128_WRITE_PLL( R128_MCLK_CNTL,
+ mclk_cntl | R128_FORCE_GCP | R128_FORCE_PIPE3D_CP );
+
+ gen_reset_cntl = R128_READ( R128_GEN_RESET_CNTL );
+
+ /* Taken from the sample code - do not change */
+ R128_WRITE( R128_GEN_RESET_CNTL,
+ gen_reset_cntl | R128_SOFT_RESET_GUI );
+ R128_READ( R128_GEN_RESET_CNTL );
+ R128_WRITE( R128_GEN_RESET_CNTL,
+ gen_reset_cntl & ~R128_SOFT_RESET_GUI );
+ R128_READ( R128_GEN_RESET_CNTL );
+
+ R128_WRITE_PLL( R128_MCLK_CNTL, mclk_cntl );
+ R128_WRITE( R128_CLOCK_CNTL_INDEX, clock_cntl_index );
+ R128_WRITE( R128_GEN_RESET_CNTL, gen_reset_cntl );
+
+ /* Reset the CCE ring */
+ r128_do_cce_reset( dev_priv );
+
+ /* The CCE is no longer running after an engine reset */
+ dev_priv->cce_running = 0;
+
+ /* Reset any pending vertex, indirect buffers */
+ r128_freelist_reset( dev );
+
+ return 0;
+}
+
+static void r128_cce_init_ring_buffer( drm_device_t *dev,
+ drm_r128_private_t *dev_priv )
+{
+ u32 ring_start;
+ u32 tmp;
+
+ DRM_DEBUG( "\n" );
+
+ /* The manual (p. 2) says this address is in "VM space". This
+ * means it's an offset from the start of AGP space.
+ */
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci )
+ ring_start = dev_priv->cce_ring->offset - dev->agp->base;
+ else
+#endif
+ ring_start = dev_priv->cce_ring->offset - dev->sg->handle;
+
+ R128_WRITE( R128_PM4_BUFFER_OFFSET, ring_start | R128_AGP_OFFSET );
+
+ R128_WRITE( R128_PM4_BUFFER_DL_WPTR, 0 );
+ R128_WRITE( R128_PM4_BUFFER_DL_RPTR, 0 );
+
+ /* Set watermark control */
+ R128_WRITE( R128_PM4_BUFFER_WM_CNTL,
+ ((R128_WATERMARK_L/4) << R128_WMA_SHIFT)
+ | ((R128_WATERMARK_M/4) << R128_WMB_SHIFT)
+ | ((R128_WATERMARK_N/4) << R128_WMC_SHIFT)
+ | ((R128_WATERMARK_K/64) << R128_WB_WM_SHIFT) );
+
+ /* Force read. Why? Because it's in the examples... */
+ R128_READ( R128_PM4_BUFFER_ADDR );
+
+ /* Turn on bus mastering */
+ tmp = R128_READ( R128_BUS_CNTL ) & ~R128_BUS_MASTER_DIS;
+ R128_WRITE( R128_BUS_CNTL, tmp );
+}
+
+static int r128_do_init_cce( drm_device_t *dev, drm_r128_init_t *init )
+{
+ drm_r128_private_t *dev_priv;
+
+ DRM_DEBUG( "\n" );
+
+ dev_priv = drm_alloc( sizeof(drm_r128_private_t), DRM_MEM_DRIVER );
+ if ( dev_priv == NULL )
+ return DRM_ERR(ENOMEM);
+
+ memset( dev_priv, 0, sizeof(drm_r128_private_t) );
+
+ dev_priv->is_pci = init->is_pci;
+
+ if ( dev_priv->is_pci && !dev->sg ) {
+ DRM_ERROR( "PCI GART memory not allocated!\n" );
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->usec_timeout = init->usec_timeout;
+ if ( dev_priv->usec_timeout < 1 ||
+ dev_priv->usec_timeout > R128_MAX_USEC_TIMEOUT ) {
+ DRM_DEBUG( "TIMEOUT problem!\n" );
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->cce_mode = init->cce_mode;
+
+ /* GH: Simple idle check.
+ */
+ atomic_set( &dev_priv->idle_count, 0 );
+
+ /* We don't support anything other than bus-mastering ring mode,
+ * but the ring can be in either AGP or PCI space for the ring
+ * read pointer.
+ */
+ if ( ( init->cce_mode != R128_PM4_192BM ) &&
+ ( init->cce_mode != R128_PM4_128BM_64INDBM ) &&
+ ( init->cce_mode != R128_PM4_64BM_128INDBM ) &&
+ ( init->cce_mode != R128_PM4_64BM_64VCBM_64INDBM ) ) {
+ DRM_DEBUG( "Bad cce_mode!\n" );
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ switch ( init->cce_mode ) {
+ case R128_PM4_NONPM4:
+ dev_priv->cce_fifo_size = 0;
+ break;
+ case R128_PM4_192PIO:
+ case R128_PM4_192BM:
+ dev_priv->cce_fifo_size = 192;
+ break;
+ case R128_PM4_128PIO_64INDBM:
+ case R128_PM4_128BM_64INDBM:
+ dev_priv->cce_fifo_size = 128;
+ break;
+ case R128_PM4_64PIO_128INDBM:
+ case R128_PM4_64BM_128INDBM:
+ case R128_PM4_64PIO_64VCBM_64INDBM:
+ case R128_PM4_64BM_64VCBM_64INDBM:
+ case R128_PM4_64PIO_64VCPIO_64INDPIO:
+ dev_priv->cce_fifo_size = 64;
+ break;
+ }
+
+ switch ( init->fb_bpp ) {
+ case 16:
+ dev_priv->color_fmt = R128_DATATYPE_RGB565;
+ break;
+ case 32:
+ default:
+ dev_priv->color_fmt = R128_DATATYPE_ARGB8888;
+ break;
+ }
+ dev_priv->front_offset = init->front_offset;
+ dev_priv->front_pitch = init->front_pitch;
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->back_pitch = init->back_pitch;
+
+ switch ( init->depth_bpp ) {
+ case 16:
+ dev_priv->depth_fmt = R128_DATATYPE_RGB565;
+ break;
+ case 24:
+ case 32:
+ default:
+ dev_priv->depth_fmt = R128_DATATYPE_ARGB8888;
+ break;
+ }
+ dev_priv->depth_offset = init->depth_offset;
+ dev_priv->depth_pitch = init->depth_pitch;
+ dev_priv->span_offset = init->span_offset;
+
+ dev_priv->front_pitch_offset_c = (((dev_priv->front_pitch/8) << 21) |
+ (dev_priv->front_offset >> 5));
+ dev_priv->back_pitch_offset_c = (((dev_priv->back_pitch/8) << 21) |
+ (dev_priv->back_offset >> 5));
+ dev_priv->depth_pitch_offset_c = (((dev_priv->depth_pitch/8) << 21) |
+ (dev_priv->depth_offset >> 5) |
+ R128_DST_TILE);
+ dev_priv->span_pitch_offset_c = (((dev_priv->depth_pitch/8) << 21) |
+ (dev_priv->span_offset >> 5));
+
+ DRM_GETSAREA();
+
+ if(!dev_priv->sarea) {
+ DRM_ERROR("could not find sarea!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+ if(!dev_priv->mmio) {
+ DRM_ERROR("could not find mmio region!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->cce_ring = drm_core_findmap(dev, init->ring_offset);
+ if(!dev_priv->cce_ring) {
+ DRM_ERROR("could not find cce ring region!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->ring_rptr = drm_core_findmap(dev, init->ring_rptr_offset);
+ if(!dev_priv->ring_rptr) {
+ DRM_ERROR("could not find ring read pointer!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ if(!dev->agp_buffer_map) {
+ DRM_ERROR("could not find dma buffer region!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( !dev_priv->is_pci ) {
+ dev_priv->agp_textures = drm_core_findmap(dev, init->agp_textures_offset);
+ if(!dev_priv->agp_textures) {
+ DRM_ERROR("could not find agp texture region!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(EINVAL);
+ }
+ }
+
+ dev_priv->sarea_priv =
+ (drm_r128_sarea_t *)((u8 *)dev_priv->sarea->handle +
+ init->sarea_priv_offset);
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ drm_core_ioremap( dev_priv->cce_ring, dev );
+ drm_core_ioremap( dev_priv->ring_rptr, dev );
+ drm_core_ioremap( dev->agp_buffer_map, dev );
+ if(!dev_priv->cce_ring->handle ||
+ !dev_priv->ring_rptr->handle ||
+ !dev->agp_buffer_map->handle) {
+ DRM_ERROR("Could not ioremap agp regions!\n");
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(ENOMEM);
+ }
+ } else
+#endif
+ {
+ dev_priv->cce_ring->handle =
+ (void *)dev_priv->cce_ring->offset;
+ dev_priv->ring_rptr->handle =
+ (void *)dev_priv->ring_rptr->offset;
+ dev->agp_buffer_map->handle = (void *)dev->agp_buffer_map->offset;
+ }
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci )
+ dev_priv->cce_buffers_offset = dev->agp->base;
+ else
+#endif
+ dev_priv->cce_buffers_offset = dev->sg->handle;
+
+ dev_priv->ring.start = (u32 *)dev_priv->cce_ring->handle;
+ dev_priv->ring.end = ((u32 *)dev_priv->cce_ring->handle
+ + init->ring_size / sizeof(u32));
+ dev_priv->ring.size = init->ring_size;
+ dev_priv->ring.size_l2qw = drm_order( init->ring_size / 8 );
+
+ dev_priv->ring.tail_mask =
+ (dev_priv->ring.size / sizeof(u32)) - 1;
+
+ dev_priv->ring.high_mark = 128;
+
+ dev_priv->sarea_priv->last_frame = 0;
+ R128_WRITE( R128_LAST_FRAME_REG, dev_priv->sarea_priv->last_frame );
+
+ dev_priv->sarea_priv->last_dispatch = 0;
+ R128_WRITE( R128_LAST_DISPATCH_REG,
+ dev_priv->sarea_priv->last_dispatch );
+
+#if __OS_HAS_AGP
+ if ( dev_priv->is_pci ) {
+#endif
+ if (!drm_ati_pcigart_init( dev, &dev_priv->phys_pci_gart,
+ &dev_priv->bus_pci_gart) ) {
+ DRM_ERROR( "failed to init PCI GART!\n" );
+ dev->dev_private = (void *)dev_priv;
+ r128_do_cleanup_cce( dev );
+ return DRM_ERR(ENOMEM);
+ }
+ R128_WRITE( R128_PCI_GART_PAGE, dev_priv->bus_pci_gart );
+#if __OS_HAS_AGP
+ }
+#endif
+
+ r128_cce_init_ring_buffer( dev, dev_priv );
+ r128_cce_load_microcode( dev_priv );
+
+ dev->dev_private = (void *)dev_priv;
+
+ r128_do_engine_reset( dev );
+
+ return 0;
+}
+
+int r128_do_cleanup_cce( drm_device_t *dev )
+{
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+ if ( dev->dev_private ) {
+ drm_r128_private_t *dev_priv = dev->dev_private;
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ if ( dev_priv->cce_ring != NULL )
+ drm_core_ioremapfree( dev_priv->cce_ring, dev );
+ if ( dev_priv->ring_rptr != NULL )
+ drm_core_ioremapfree( dev_priv->ring_rptr, dev );
+ if ( dev->agp_buffer_map != NULL )
+ drm_core_ioremapfree( dev->agp_buffer_map, dev );
+ } else
+#endif
+ {
+ if (!drm_ati_pcigart_cleanup( dev,
+ dev_priv->phys_pci_gart,
+ dev_priv->bus_pci_gart ))
+ DRM_ERROR( "failed to cleanup PCI GART!\n" );
+ }
+
+ drm_free( dev->dev_private, sizeof(drm_r128_private_t),
+ DRM_MEM_DRIVER );
+ dev->dev_private = NULL;
+ }
+
+ return 0;
+}
+
+int r128_cce_init( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_init_t init;
+
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( init, (drm_r128_init_t __user *)data, sizeof(init) );
+
+ switch ( init.func ) {
+ case R128_INIT_CCE:
+ return r128_do_init_cce( dev, &init );
+ case R128_CLEANUP_CCE:
+ return r128_do_cleanup_cce( dev );
+ }
+
+ return DRM_ERR(EINVAL);
+}
+
+int r128_cce_start( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( dev_priv->cce_running || dev_priv->cce_mode == R128_PM4_NONPM4 ) {
+ DRM_DEBUG( "%s while CCE running\n", __FUNCTION__ );
+ return 0;
+ }
+
+ r128_do_cce_start( dev_priv );
+
+ return 0;
+}
+
+/* Stop the CCE. The engine must have been idled before calling this
+ * routine.
+ */
+int r128_cce_stop( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_cce_stop_t stop;
+ int ret;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL(stop, (drm_r128_cce_stop_t __user *)data, sizeof(stop) );
+
+ /* Flush any pending CCE commands. This ensures any outstanding
+ * commands are exectuted by the engine before we turn it off.
+ */
+ if ( stop.flush ) {
+ r128_do_cce_flush( dev_priv );
+ }
+
+ /* If we fail to make the engine go idle, we return an error
+ * code so that the DRM ioctl wrapper can try again.
+ */
+ if ( stop.idle ) {
+ ret = r128_do_cce_idle( dev_priv );
+ if ( ret ) return ret;
+ }
+
+ /* Finally, we can turn off the CCE. If the engine isn't idle,
+ * we will get some dropped triangles as they won't be fully
+ * rendered before the CCE is shut down.
+ */
+ r128_do_cce_stop( dev_priv );
+
+ /* Reset the engine */
+ r128_do_engine_reset( dev );
+
+ return 0;
+}
+
+/* Just reset the CCE ring. Called as part of an X Server engine reset.
+ */
+int r128_cce_reset( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_DEBUG( "%s called before init done\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ r128_do_cce_reset( dev_priv );
+
+ /* The CCE is no longer running after an engine reset */
+ dev_priv->cce_running = 0;
+
+ return 0;
+}
+
+int r128_cce_idle( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( dev_priv->cce_running ) {
+ r128_do_cce_flush( dev_priv );
+ }
+
+ return r128_do_cce_idle( dev_priv );
+}
+
+int r128_engine_reset( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ return r128_do_engine_reset( dev );
+}
+
+int r128_fullscreen( DRM_IOCTL_ARGS )
+{
+ return DRM_ERR(EINVAL);
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+#define R128_BUFFER_USED 0xffffffff
+#define R128_BUFFER_FREE 0
+
+#if 0
+static int r128_freelist_init( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_buf_t *buf;
+ drm_r128_buf_priv_t *buf_priv;
+ drm_r128_freelist_t *entry;
+ int i;
+
+ dev_priv->head = drm_alloc( sizeof(drm_r128_freelist_t),
+ DRM_MEM_DRIVER );
+ if ( dev_priv->head == NULL )
+ return DRM_ERR(ENOMEM);
+
+ memset( dev_priv->head, 0, sizeof(drm_r128_freelist_t) );
+ dev_priv->head->age = R128_BUFFER_USED;
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+
+ entry = drm_alloc( sizeof(drm_r128_freelist_t),
+ DRM_MEM_DRIVER );
+ if ( !entry ) return DRM_ERR(ENOMEM);
+
+ entry->age = R128_BUFFER_FREE;
+ entry->buf = buf;
+ entry->prev = dev_priv->head;
+ entry->next = dev_priv->head->next;
+ if ( !entry->next )
+ dev_priv->tail = entry;
+
+ buf_priv->discard = 0;
+ buf_priv->dispatched = 0;
+ buf_priv->list_entry = entry;
+
+ dev_priv->head->next = entry;
+
+ if ( dev_priv->head->next )
+ dev_priv->head->next->prev = entry;
+ }
+
+ return 0;
+
+}
+#endif
+
+static drm_buf_t *r128_freelist_get( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_buf_priv_t *buf_priv;
+ drm_buf_t *buf;
+ int i, t;
+
+ /* FIXME: Optimize -- use freelist code */
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+ if ( buf->filp == 0 )
+ return buf;
+ }
+
+ for ( t = 0 ; t < dev_priv->usec_timeout ; t++ ) {
+ u32 done_age = R128_READ( R128_LAST_DISPATCH_REG );
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+ if ( buf->pending && buf_priv->age <= done_age ) {
+ /* The buffer has been processed, so it
+ * can now be used.
+ */
+ buf->pending = 0;
+ return buf;
+ }
+ }
+ DRM_UDELAY( 1 );
+ }
+
+ DRM_DEBUG( "returning NULL!\n" );
+ return NULL;
+}
+
+void r128_freelist_reset( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ int i;
+
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ drm_buf_t *buf = dma->buflist[i];
+ drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+ buf_priv->age = 0;
+ }
+}
+
+
+/* ================================================================
+ * CCE command submission
+ */
+
+int r128_wait_ring( drm_r128_private_t *dev_priv, int n )
+{
+ drm_r128_ring_buffer_t *ring = &dev_priv->ring;
+ int i;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ r128_update_ring_snapshot( dev_priv );
+ if ( ring->space >= n )
+ return 0;
+ DRM_UDELAY( 1 );
+ }
+
+ /* FIXME: This is being ignored... */
+ DRM_ERROR( "failed!\n" );
+ return DRM_ERR(EBUSY);
+}
+
+static int r128_cce_get_buffers( DRMFILE filp, drm_device_t *dev, drm_dma_t *d )
+{
+ int i;
+ drm_buf_t *buf;
+
+ for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+ buf = r128_freelist_get( dev );
+ if ( !buf ) return DRM_ERR(EAGAIN);
+
+ buf->filp = filp;
+
+ if ( DRM_COPY_TO_USER( &d->request_indices[i], &buf->idx,
+ sizeof(buf->idx) ) )
+ return DRM_ERR(EFAULT);
+ if ( DRM_COPY_TO_USER( &d->request_sizes[i], &buf->total,
+ sizeof(buf->total) ) )
+ return DRM_ERR(EFAULT);
+
+ d->granted_count++;
+ }
+ return 0;
+}
+
+int r128_cce_buffers( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_device_dma_t *dma = dev->dma;
+ int ret = 0;
+ drm_dma_t __user *argp = (void __user *)data;
+ drm_dma_t d;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+ /* Please don't send us buffers.
+ */
+ if ( d.send_count != 0 ) {
+ DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+ DRM_CURRENTPID, d.send_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ /* We'll send you buffers.
+ */
+ if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+ DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+ DRM_CURRENTPID, d.request_count, dma->buf_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ d.granted_count = 0;
+
+ if ( d.request_count ) {
+ ret = r128_cce_get_buffers( filp, dev, &d );
+ }
+
+ DRM_COPY_TO_USER_IOCTL(argp, d, sizeof(d) );
+
+ return ret;
+}
diff --git a/drivers/char/drm/r128_drm.h b/drivers/char/drm/r128_drm.h
new file mode 100644
index 000000000000..0cba17d1e0ff
--- /dev/null
+++ b/drivers/char/drm/r128_drm.h
@@ -0,0 +1,345 @@
+/* r128_drm.h -- Public header for the r128 driver -*- linux-c -*-
+ * Created: Wed Apr 5 19:24:19 2000 by kevin@precisioninsight.com
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ * Kevin E. Martin <martin@valinux.com>
+ */
+
+#ifndef __R128_DRM_H__
+#define __R128_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the X server file (r128_sarea.h)
+ */
+#ifndef __R128_SAREA_DEFINES__
+#define __R128_SAREA_DEFINES__
+
+/* What needs to be changed for the current vertex buffer?
+ */
+#define R128_UPLOAD_CONTEXT 0x001
+#define R128_UPLOAD_SETUP 0x002
+#define R128_UPLOAD_TEX0 0x004
+#define R128_UPLOAD_TEX1 0x008
+#define R128_UPLOAD_TEX0IMAGES 0x010
+#define R128_UPLOAD_TEX1IMAGES 0x020
+#define R128_UPLOAD_CORE 0x040
+#define R128_UPLOAD_MASKS 0x080
+#define R128_UPLOAD_WINDOW 0x100
+#define R128_UPLOAD_CLIPRECTS 0x200 /* handled client-side */
+#define R128_REQUIRE_QUIESCENCE 0x400
+#define R128_UPLOAD_ALL 0x7ff
+
+#define R128_FRONT 0x1
+#define R128_BACK 0x2
+#define R128_DEPTH 0x4
+
+/* Primitive types
+ */
+#define R128_POINTS 0x1
+#define R128_LINES 0x2
+#define R128_LINE_STRIP 0x3
+#define R128_TRIANGLES 0x4
+#define R128_TRIANGLE_FAN 0x5
+#define R128_TRIANGLE_STRIP 0x6
+
+/* Vertex/indirect buffer size
+ */
+#define R128_BUFFER_SIZE 16384
+
+/* Byte offsets for indirect buffer data
+ */
+#define R128_INDEX_PRIM_OFFSET 20
+#define R128_HOSTDATA_BLIT_OFFSET 32
+
+/* Keep these small for testing.
+ */
+#define R128_NR_SAREA_CLIPRECTS 12
+
+/* There are 2 heaps (local/AGP). Each region within a heap is a
+ * minimum of 64k, and there are at most 64 of them per heap.
+ */
+#define R128_LOCAL_TEX_HEAP 0
+#define R128_AGP_TEX_HEAP 1
+#define R128_NR_TEX_HEAPS 2
+#define R128_NR_TEX_REGIONS 64
+#define R128_LOG_TEX_GRANULARITY 16
+
+#define R128_NR_CONTEXT_REGS 12
+
+#define R128_MAX_TEXTURE_LEVELS 11
+#define R128_MAX_TEXTURE_UNITS 2
+
+#endif /* __R128_SAREA_DEFINES__ */
+
+typedef struct {
+ /* Context state - can be written in one large chunk */
+ unsigned int dst_pitch_offset_c;
+ unsigned int dp_gui_master_cntl_c;
+ unsigned int sc_top_left_c;
+ unsigned int sc_bottom_right_c;
+ unsigned int z_offset_c;
+ unsigned int z_pitch_c;
+ unsigned int z_sten_cntl_c;
+ unsigned int tex_cntl_c;
+ unsigned int misc_3d_state_cntl_reg;
+ unsigned int texture_clr_cmp_clr_c;
+ unsigned int texture_clr_cmp_msk_c;
+ unsigned int fog_color_c;
+
+ /* Texture state */
+ unsigned int tex_size_pitch_c;
+ unsigned int constant_color_c;
+
+ /* Setup state */
+ unsigned int pm4_vc_fpu_setup;
+ unsigned int setup_cntl;
+
+ /* Mask state */
+ unsigned int dp_write_mask;
+ unsigned int sten_ref_mask_c;
+ unsigned int plane_3d_mask_c;
+
+ /* Window state */
+ unsigned int window_xy_offset;
+
+ /* Core state */
+ unsigned int scale_3d_cntl;
+} drm_r128_context_regs_t;
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+ unsigned int tex_cntl;
+ unsigned int tex_combine_cntl;
+ unsigned int tex_size_pitch;
+ unsigned int tex_offset[R128_MAX_TEXTURE_LEVELS];
+ unsigned int tex_border_color;
+} drm_r128_texture_regs_t;
+
+
+typedef struct drm_r128_sarea {
+ /* The channel for communication of state information to the kernel
+ * on firing a vertex buffer.
+ */
+ drm_r128_context_regs_t context_state;
+ drm_r128_texture_regs_t tex_state[R128_MAX_TEXTURE_UNITS];
+ unsigned int dirty;
+ unsigned int vertsize;
+ unsigned int vc_format;
+
+ /* The current cliprects, or a subset thereof.
+ */
+ drm_clip_rect_t boxes[R128_NR_SAREA_CLIPRECTS];
+ unsigned int nbox;
+
+ /* Counters for client-side throttling of rendering clients.
+ */
+ unsigned int last_frame;
+ unsigned int last_dispatch;
+
+ drm_tex_region_t tex_list[R128_NR_TEX_HEAPS][R128_NR_TEX_REGIONS+1];
+ unsigned int tex_age[R128_NR_TEX_HEAPS];
+ int ctx_owner;
+ int pfAllowPageFlip; /* number of 3d windows (0,1,2 or more) */
+ int pfCurrentPage; /* which buffer is being displayed? */
+} drm_r128_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmR128.h)
+ */
+
+/* Rage 128 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_R128_INIT 0x00
+#define DRM_R128_CCE_START 0x01
+#define DRM_R128_CCE_STOP 0x02
+#define DRM_R128_CCE_RESET 0x03
+#define DRM_R128_CCE_IDLE 0x04
+/* 0x05 not used */
+#define DRM_R128_RESET 0x06
+#define DRM_R128_SWAP 0x07
+#define DRM_R128_CLEAR 0x08
+#define DRM_R128_VERTEX 0x09
+#define DRM_R128_INDICES 0x0a
+#define DRM_R128_BLIT 0x0b
+#define DRM_R128_DEPTH 0x0c
+#define DRM_R128_STIPPLE 0x0d
+/* 0x0e not used */
+#define DRM_R128_INDIRECT 0x0f
+#define DRM_R128_FULLSCREEN 0x10
+#define DRM_R128_CLEAR2 0x11
+#define DRM_R128_GETPARAM 0x12
+#define DRM_R128_FLIP 0x13
+
+#define DRM_IOCTL_R128_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_R128_INIT, drm_r128_init_t)
+#define DRM_IOCTL_R128_CCE_START DRM_IO( DRM_COMMAND_BASE + DRM_R128_CCE_START)
+#define DRM_IOCTL_R128_CCE_STOP DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CCE_STOP, drm_r128_cce_stop_t)
+#define DRM_IOCTL_R128_CCE_RESET DRM_IO( DRM_COMMAND_BASE + DRM_R128_CCE_RESET)
+#define DRM_IOCTL_R128_CCE_IDLE DRM_IO( DRM_COMMAND_BASE + DRM_R128_CCE_IDLE)
+/* 0x05 not used */
+#define DRM_IOCTL_R128_RESET DRM_IO( DRM_COMMAND_BASE + DRM_R128_RESET)
+#define DRM_IOCTL_R128_SWAP DRM_IO( DRM_COMMAND_BASE + DRM_R128_SWAP)
+#define DRM_IOCTL_R128_CLEAR DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CLEAR, drm_r128_clear_t)
+#define DRM_IOCTL_R128_VERTEX DRM_IOW( DRM_COMMAND_BASE + DRM_R128_VERTEX, drm_r128_vertex_t)
+#define DRM_IOCTL_R128_INDICES DRM_IOW( DRM_COMMAND_BASE + DRM_R128_INDICES, drm_r128_indices_t)
+#define DRM_IOCTL_R128_BLIT DRM_IOW( DRM_COMMAND_BASE + DRM_R128_BLIT, drm_r128_blit_t)
+#define DRM_IOCTL_R128_DEPTH DRM_IOW( DRM_COMMAND_BASE + DRM_R128_DEPTH, drm_r128_depth_t)
+#define DRM_IOCTL_R128_STIPPLE DRM_IOW( DRM_COMMAND_BASE + DRM_R128_STIPPLE, drm_r128_stipple_t)
+/* 0x0e not used */
+#define DRM_IOCTL_R128_INDIRECT DRM_IOWR(DRM_COMMAND_BASE + DRM_R128_INDIRECT, drm_r128_indirect_t)
+#define DRM_IOCTL_R128_FULLSCREEN DRM_IOW( DRM_COMMAND_BASE + DRM_R128_FULLSCREEN, drm_r128_fullscreen_t)
+#define DRM_IOCTL_R128_CLEAR2 DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CLEAR2, drm_r128_clear2_t)
+#define DRM_IOCTL_R128_GETPARAM DRM_IOW( DRM_COMMAND_BASE + DRM_R128_GETPARAM, drm_r128_getparam_t)
+#define DRM_IOCTL_R128_FLIP DRM_IO( DRM_COMMAND_BASE + DRM_R128_FLIP)
+
+typedef struct drm_r128_init {
+ enum {
+ R128_INIT_CCE = 0x01,
+ R128_CLEANUP_CCE = 0x02
+ } func;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+ int sarea_priv_offset;
+#else
+ unsigned long sarea_priv_offset;
+#endif
+ int is_pci;
+ int cce_mode;
+ int cce_secure;
+ int ring_size;
+ int usec_timeout;
+
+ unsigned int fb_bpp;
+ unsigned int front_offset, front_pitch;
+ unsigned int back_offset, back_pitch;
+ unsigned int depth_bpp;
+ unsigned int depth_offset, depth_pitch;
+ unsigned int span_offset;
+
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+ unsigned int fb_offset;
+ unsigned int mmio_offset;
+ unsigned int ring_offset;
+ unsigned int ring_rptr_offset;
+ unsigned int buffers_offset;
+ unsigned int agp_textures_offset;
+#else
+ unsigned long fb_offset;
+ unsigned long mmio_offset;
+ unsigned long ring_offset;
+ unsigned long ring_rptr_offset;
+ unsigned long buffers_offset;
+ unsigned long agp_textures_offset;
+#endif
+} drm_r128_init_t;
+
+typedef struct drm_r128_cce_stop {
+ int flush;
+ int idle;
+} drm_r128_cce_stop_t;
+
+typedef struct drm_r128_clear {
+ unsigned int flags;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+ int x, y, w, h;
+#endif
+ unsigned int clear_color;
+ unsigned int clear_depth;
+#if CONFIG_XFREE86_VERSION >= XFREE86_VERSION(4,1,0,0)
+ unsigned int color_mask;
+ unsigned int depth_mask;
+#endif
+} drm_r128_clear_t;
+
+typedef struct drm_r128_vertex {
+ int prim;
+ int idx; /* Index of vertex buffer */
+ int count; /* Number of vertices in buffer */
+ int discard; /* Client finished with buffer? */
+} drm_r128_vertex_t;
+
+typedef struct drm_r128_indices {
+ int prim;
+ int idx;
+ int start;
+ int end;
+ int discard; /* Client finished with buffer? */
+} drm_r128_indices_t;
+
+typedef struct drm_r128_blit {
+ int idx;
+ int pitch;
+ int offset;
+ int format;
+ unsigned short x, y;
+ unsigned short width, height;
+} drm_r128_blit_t;
+
+typedef struct drm_r128_depth {
+ enum {
+ R128_WRITE_SPAN = 0x01,
+ R128_WRITE_PIXELS = 0x02,
+ R128_READ_SPAN = 0x03,
+ R128_READ_PIXELS = 0x04
+ } func;
+ int n;
+ int __user *x;
+ int __user *y;
+ unsigned int __user *buffer;
+ unsigned char __user *mask;
+} drm_r128_depth_t;
+
+typedef struct drm_r128_stipple {
+ unsigned int __user *mask;
+} drm_r128_stipple_t;
+
+typedef struct drm_r128_indirect {
+ int idx;
+ int start;
+ int end;
+ int discard;
+} drm_r128_indirect_t;
+
+typedef struct drm_r128_fullscreen {
+ enum {
+ R128_INIT_FULLSCREEN = 0x01,
+ R128_CLEANUP_FULLSCREEN = 0x02
+ } func;
+} drm_r128_fullscreen_t;
+
+/* 2.3: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.
+ */
+#define R128_PARAM_IRQ_NR 1
+
+typedef struct drm_r128_getparam {
+ int param;
+ void __user *value;
+} drm_r128_getparam_t;
+
+#endif
diff --git a/drivers/char/drm/r128_drv.c b/drivers/char/drm/r128_drv.c
new file mode 100644
index 000000000000..ced63810237b
--- /dev/null
+++ b/drivers/char/drm/r128_drv.c
@@ -0,0 +1,122 @@
+/* r128_drv.c -- ATI Rage 128 driver -*- linux-c -*-
+ * Created: Mon Dec 13 09:47:27 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ r128_PCI_IDS
+};
+
+extern drm_ioctl_desc_t r128_ioctls[];
+extern int r128_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_SG | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+ .dev_priv_size = sizeof(drm_r128_buf_priv_t),
+ .prerelease = r128_driver_prerelease,
+ .pretakedown = r128_driver_pretakedown,
+ .vblank_wait = r128_driver_vblank_wait,
+ .irq_preinstall = r128_driver_irq_preinstall,
+ .irq_postinstall = r128_driver_irq_postinstall,
+ .irq_uninstall = r128_driver_irq_uninstall,
+ .irq_handler = r128_driver_irq_handler,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = r128_ioctls,
+ .dma_ioctl = r128_cce_buffers,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init r128_init(void)
+{
+ driver.num_ioctls = r128_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit r128_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(r128_init);
+module_exit(r128_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/r128_drv.h b/drivers/char/drm/r128_drv.h
new file mode 100644
index 000000000000..cf1aa5df459e
--- /dev/null
+++ b/drivers/char/drm/r128_drv.h
@@ -0,0 +1,521 @@
+/* r128_drv.h -- Private header for r128 driver -*- linux-c -*-
+ * Created: Mon Dec 13 09:51:11 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Kevin E. Martin <martin@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ * Michel D�zer <daenzerm@student.ethz.ch>
+ */
+
+#ifndef __R128_DRV_H__
+#define __R128_DRV_H__
+
+/* General customization:
+ */
+#define DRIVER_AUTHOR "Gareth Hughes, VA Linux Systems Inc."
+
+#define DRIVER_NAME "r128"
+#define DRIVER_DESC "ATI Rage 128"
+#define DRIVER_DATE "20030725"
+
+/* Interface history:
+ *
+ * ?? - ??
+ * 2.4 - Add support for ycbcr textures (no new ioctls)
+ * 2.5 - Add FLIP ioctl, disable FULLSCREEN.
+ */
+#define DRIVER_MAJOR 2
+#define DRIVER_MINOR 5
+#define DRIVER_PATCHLEVEL 0
+
+
+#define GET_RING_HEAD(dev_priv) R128_READ( R128_PM4_BUFFER_DL_RPTR )
+
+typedef struct drm_r128_freelist {
+ unsigned int age;
+ drm_buf_t *buf;
+ struct drm_r128_freelist *next;
+ struct drm_r128_freelist *prev;
+} drm_r128_freelist_t;
+
+typedef struct drm_r128_ring_buffer {
+ u32 *start;
+ u32 *end;
+ int size;
+ int size_l2qw;
+
+ u32 tail;
+ u32 tail_mask;
+ int space;
+
+ int high_mark;
+} drm_r128_ring_buffer_t;
+
+typedef struct drm_r128_private {
+ drm_r128_ring_buffer_t ring;
+ drm_r128_sarea_t *sarea_priv;
+
+ int cce_mode;
+ int cce_fifo_size;
+ int cce_running;
+
+ drm_r128_freelist_t *head;
+ drm_r128_freelist_t *tail;
+
+ int usec_timeout;
+ int is_pci;
+ unsigned long phys_pci_gart;
+ dma_addr_t bus_pci_gart;
+ unsigned long cce_buffers_offset;
+
+ atomic_t idle_count;
+
+ int page_flipping;
+ int current_page;
+ u32 crtc_offset;
+ u32 crtc_offset_cntl;
+
+ u32 color_fmt;
+ unsigned int front_offset;
+ unsigned int front_pitch;
+ unsigned int back_offset;
+ unsigned int back_pitch;
+
+ u32 depth_fmt;
+ unsigned int depth_offset;
+ unsigned int depth_pitch;
+ unsigned int span_offset;
+
+ u32 front_pitch_offset_c;
+ u32 back_pitch_offset_c;
+ u32 depth_pitch_offset_c;
+ u32 span_pitch_offset_c;
+
+ drm_local_map_t *sarea;
+ drm_local_map_t *mmio;
+ drm_local_map_t *cce_ring;
+ drm_local_map_t *ring_rptr;
+ drm_local_map_t *agp_textures;
+} drm_r128_private_t;
+
+typedef struct drm_r128_buf_priv {
+ u32 age;
+ int prim;
+ int discard;
+ int dispatched;
+ drm_r128_freelist_t *list_entry;
+} drm_r128_buf_priv_t;
+
+ /* r128_cce.c */
+extern int r128_cce_init( DRM_IOCTL_ARGS );
+extern int r128_cce_start( DRM_IOCTL_ARGS );
+extern int r128_cce_stop( DRM_IOCTL_ARGS );
+extern int r128_cce_reset( DRM_IOCTL_ARGS );
+extern int r128_cce_idle( DRM_IOCTL_ARGS );
+extern int r128_engine_reset( DRM_IOCTL_ARGS );
+extern int r128_fullscreen( DRM_IOCTL_ARGS );
+extern int r128_cce_buffers( DRM_IOCTL_ARGS );
+
+extern void r128_freelist_reset( drm_device_t *dev );
+
+extern int r128_wait_ring( drm_r128_private_t *dev_priv, int n );
+
+extern int r128_do_cce_idle( drm_r128_private_t *dev_priv );
+extern int r128_do_cleanup_cce( drm_device_t *dev );
+
+extern int r128_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+
+extern irqreturn_t r128_driver_irq_handler( DRM_IRQ_ARGS );
+extern void r128_driver_irq_preinstall( drm_device_t *dev );
+extern void r128_driver_irq_postinstall( drm_device_t *dev );
+extern void r128_driver_irq_uninstall( drm_device_t *dev );
+extern void r128_driver_pretakedown(drm_device_t *dev);
+extern void r128_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+/* Register definitions, register access macros and drmAddMap constants
+ * for Rage 128 kernel driver.
+ */
+
+#define R128_AUX_SC_CNTL 0x1660
+# define R128_AUX1_SC_EN (1 << 0)
+# define R128_AUX1_SC_MODE_OR (0 << 1)
+# define R128_AUX1_SC_MODE_NAND (1 << 1)
+# define R128_AUX2_SC_EN (1 << 2)
+# define R128_AUX2_SC_MODE_OR (0 << 3)
+# define R128_AUX2_SC_MODE_NAND (1 << 3)
+# define R128_AUX3_SC_EN (1 << 4)
+# define R128_AUX3_SC_MODE_OR (0 << 5)
+# define R128_AUX3_SC_MODE_NAND (1 << 5)
+#define R128_AUX1_SC_LEFT 0x1664
+#define R128_AUX1_SC_RIGHT 0x1668
+#define R128_AUX1_SC_TOP 0x166c
+#define R128_AUX1_SC_BOTTOM 0x1670
+#define R128_AUX2_SC_LEFT 0x1674
+#define R128_AUX2_SC_RIGHT 0x1678
+#define R128_AUX2_SC_TOP 0x167c
+#define R128_AUX2_SC_BOTTOM 0x1680
+#define R128_AUX3_SC_LEFT 0x1684
+#define R128_AUX3_SC_RIGHT 0x1688
+#define R128_AUX3_SC_TOP 0x168c
+#define R128_AUX3_SC_BOTTOM 0x1690
+
+#define R128_BRUSH_DATA0 0x1480
+#define R128_BUS_CNTL 0x0030
+# define R128_BUS_MASTER_DIS (1 << 6)
+
+#define R128_CLOCK_CNTL_INDEX 0x0008
+#define R128_CLOCK_CNTL_DATA 0x000c
+# define R128_PLL_WR_EN (1 << 7)
+#define R128_CONSTANT_COLOR_C 0x1d34
+#define R128_CRTC_OFFSET 0x0224
+#define R128_CRTC_OFFSET_CNTL 0x0228
+# define R128_CRTC_OFFSET_FLIP_CNTL (1 << 16)
+
+#define R128_DP_GUI_MASTER_CNTL 0x146c
+# define R128_GMC_SRC_PITCH_OFFSET_CNTL (1 << 0)
+# define R128_GMC_DST_PITCH_OFFSET_CNTL (1 << 1)
+# define R128_GMC_BRUSH_SOLID_COLOR (13 << 4)
+# define R128_GMC_BRUSH_NONE (15 << 4)
+# define R128_GMC_DST_16BPP (4 << 8)
+# define R128_GMC_DST_24BPP (5 << 8)
+# define R128_GMC_DST_32BPP (6 << 8)
+# define R128_GMC_DST_DATATYPE_SHIFT 8
+# define R128_GMC_SRC_DATATYPE_COLOR (3 << 12)
+# define R128_DP_SRC_SOURCE_MEMORY (2 << 24)
+# define R128_DP_SRC_SOURCE_HOST_DATA (3 << 24)
+# define R128_GMC_CLR_CMP_CNTL_DIS (1 << 28)
+# define R128_GMC_AUX_CLIP_DIS (1 << 29)
+# define R128_GMC_WR_MSK_DIS (1 << 30)
+# define R128_ROP3_S 0x00cc0000
+# define R128_ROP3_P 0x00f00000
+#define R128_DP_WRITE_MASK 0x16cc
+#define R128_DST_PITCH_OFFSET_C 0x1c80
+# define R128_DST_TILE (1 << 31)
+
+#define R128_GEN_INT_CNTL 0x0040
+# define R128_CRTC_VBLANK_INT_EN (1 << 0)
+#define R128_GEN_INT_STATUS 0x0044
+# define R128_CRTC_VBLANK_INT (1 << 0)
+# define R128_CRTC_VBLANK_INT_AK (1 << 0)
+#define R128_GEN_RESET_CNTL 0x00f0
+# define R128_SOFT_RESET_GUI (1 << 0)
+
+#define R128_GUI_SCRATCH_REG0 0x15e0
+#define R128_GUI_SCRATCH_REG1 0x15e4
+#define R128_GUI_SCRATCH_REG2 0x15e8
+#define R128_GUI_SCRATCH_REG3 0x15ec
+#define R128_GUI_SCRATCH_REG4 0x15f0
+#define R128_GUI_SCRATCH_REG5 0x15f4
+
+#define R128_GUI_STAT 0x1740
+# define R128_GUI_FIFOCNT_MASK 0x0fff
+# define R128_GUI_ACTIVE (1 << 31)
+
+#define R128_MCLK_CNTL 0x000f
+# define R128_FORCE_GCP (1 << 16)
+# define R128_FORCE_PIPE3D_CP (1 << 17)
+# define R128_FORCE_RCP (1 << 18)
+
+#define R128_PC_GUI_CTLSTAT 0x1748
+#define R128_PC_NGUI_CTLSTAT 0x0184
+# define R128_PC_FLUSH_GUI (3 << 0)
+# define R128_PC_RI_GUI (1 << 2)
+# define R128_PC_FLUSH_ALL 0x00ff
+# define R128_PC_BUSY (1 << 31)
+
+#define R128_PCI_GART_PAGE 0x017c
+#define R128_PRIM_TEX_CNTL_C 0x1cb0
+
+#define R128_SCALE_3D_CNTL 0x1a00
+#define R128_SEC_TEX_CNTL_C 0x1d00
+#define R128_SEC_TEXTURE_BORDER_COLOR_C 0x1d3c
+#define R128_SETUP_CNTL 0x1bc4
+#define R128_STEN_REF_MASK_C 0x1d40
+
+#define R128_TEX_CNTL_C 0x1c9c
+# define R128_TEX_CACHE_FLUSH (1 << 23)
+
+#define R128_WAIT_UNTIL 0x1720
+# define R128_EVENT_CRTC_OFFSET (1 << 0)
+#define R128_WINDOW_XY_OFFSET 0x1bcc
+
+
+/* CCE registers
+ */
+#define R128_PM4_BUFFER_OFFSET 0x0700
+#define R128_PM4_BUFFER_CNTL 0x0704
+# define R128_PM4_MASK (15 << 28)
+# define R128_PM4_NONPM4 (0 << 28)
+# define R128_PM4_192PIO (1 << 28)
+# define R128_PM4_192BM (2 << 28)
+# define R128_PM4_128PIO_64INDBM (3 << 28)
+# define R128_PM4_128BM_64INDBM (4 << 28)
+# define R128_PM4_64PIO_128INDBM (5 << 28)
+# define R128_PM4_64BM_128INDBM (6 << 28)
+# define R128_PM4_64PIO_64VCBM_64INDBM (7 << 28)
+# define R128_PM4_64BM_64VCBM_64INDBM (8 << 28)
+# define R128_PM4_64PIO_64VCPIO_64INDPIO (15 << 28)
+# define R128_PM4_BUFFER_CNTL_NOUPDATE (1 << 27)
+
+#define R128_PM4_BUFFER_WM_CNTL 0x0708
+# define R128_WMA_SHIFT 0
+# define R128_WMB_SHIFT 8
+# define R128_WMC_SHIFT 16
+# define R128_WB_WM_SHIFT 24
+
+#define R128_PM4_BUFFER_DL_RPTR_ADDR 0x070c
+#define R128_PM4_BUFFER_DL_RPTR 0x0710
+#define R128_PM4_BUFFER_DL_WPTR 0x0714
+# define R128_PM4_BUFFER_DL_DONE (1 << 31)
+
+#define R128_PM4_VC_FPU_SETUP 0x071c
+
+#define R128_PM4_IW_INDOFF 0x0738
+#define R128_PM4_IW_INDSIZE 0x073c
+
+#define R128_PM4_STAT 0x07b8
+# define R128_PM4_FIFOCNT_MASK 0x0fff
+# define R128_PM4_BUSY (1 << 16)
+# define R128_PM4_GUI_ACTIVE (1 << 31)
+
+#define R128_PM4_MICROCODE_ADDR 0x07d4
+#define R128_PM4_MICROCODE_RADDR 0x07d8
+#define R128_PM4_MICROCODE_DATAH 0x07dc
+#define R128_PM4_MICROCODE_DATAL 0x07e0
+
+#define R128_PM4_BUFFER_ADDR 0x07f0
+#define R128_PM4_MICRO_CNTL 0x07fc
+# define R128_PM4_MICRO_FREERUN (1 << 30)
+
+#define R128_PM4_FIFO_DATA_EVEN 0x1000
+#define R128_PM4_FIFO_DATA_ODD 0x1004
+
+
+/* CCE command packets
+ */
+#define R128_CCE_PACKET0 0x00000000
+#define R128_CCE_PACKET1 0x40000000
+#define R128_CCE_PACKET2 0x80000000
+#define R128_CCE_PACKET3 0xC0000000
+# define R128_CNTL_HOSTDATA_BLT 0x00009400
+# define R128_CNTL_PAINT_MULTI 0x00009A00
+# define R128_CNTL_BITBLT_MULTI 0x00009B00
+# define R128_3D_RNDR_GEN_INDX_PRIM 0x00002300
+
+#define R128_CCE_PACKET_MASK 0xC0000000
+#define R128_CCE_PACKET_COUNT_MASK 0x3fff0000
+#define R128_CCE_PACKET0_REG_MASK 0x000007ff
+#define R128_CCE_PACKET1_REG0_MASK 0x000007ff
+#define R128_CCE_PACKET1_REG1_MASK 0x003ff800
+
+#define R128_CCE_VC_CNTL_PRIM_TYPE_NONE 0x00000000
+#define R128_CCE_VC_CNTL_PRIM_TYPE_POINT 0x00000001
+#define R128_CCE_VC_CNTL_PRIM_TYPE_LINE 0x00000002
+#define R128_CCE_VC_CNTL_PRIM_TYPE_POLY_LINE 0x00000003
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_LIST 0x00000004
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_FAN 0x00000005
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_STRIP 0x00000006
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2 0x00000007
+#define R128_CCE_VC_CNTL_PRIM_WALK_IND 0x00000010
+#define R128_CCE_VC_CNTL_PRIM_WALK_LIST 0x00000020
+#define R128_CCE_VC_CNTL_PRIM_WALK_RING 0x00000030
+#define R128_CCE_VC_CNTL_NUM_SHIFT 16
+
+#define R128_DATATYPE_VQ 0
+#define R128_DATATYPE_CI4 1
+#define R128_DATATYPE_CI8 2
+#define R128_DATATYPE_ARGB1555 3
+#define R128_DATATYPE_RGB565 4
+#define R128_DATATYPE_RGB888 5
+#define R128_DATATYPE_ARGB8888 6
+#define R128_DATATYPE_RGB332 7
+#define R128_DATATYPE_Y8 8
+#define R128_DATATYPE_RGB8 9
+#define R128_DATATYPE_CI16 10
+#define R128_DATATYPE_YVYU422 11
+#define R128_DATATYPE_VYUY422 12
+#define R128_DATATYPE_AYUV444 14
+#define R128_DATATYPE_ARGB4444 15
+
+/* Constants */
+#define R128_AGP_OFFSET 0x02000000
+
+#define R128_WATERMARK_L 16
+#define R128_WATERMARK_M 8
+#define R128_WATERMARK_N 8
+#define R128_WATERMARK_K 128
+
+#define R128_MAX_USEC_TIMEOUT 100000 /* 100 ms */
+
+#define R128_LAST_FRAME_REG R128_GUI_SCRATCH_REG0
+#define R128_LAST_DISPATCH_REG R128_GUI_SCRATCH_REG1
+#define R128_MAX_VB_AGE 0x7fffffff
+#define R128_MAX_VB_VERTS (0xffff)
+
+#define R128_RING_HIGH_MARK 128
+
+#define R128_PERFORMANCE_BOXES 0
+
+#define R128_READ(reg) DRM_READ32( dev_priv->mmio, (reg) )
+#define R128_WRITE(reg,val) DRM_WRITE32( dev_priv->mmio, (reg), (val) )
+#define R128_READ8(reg) DRM_READ8( dev_priv->mmio, (reg) )
+#define R128_WRITE8(reg,val) DRM_WRITE8( dev_priv->mmio, (reg), (val) )
+
+#define R128_WRITE_PLL(addr,val) \
+do { \
+ R128_WRITE8(R128_CLOCK_CNTL_INDEX, \
+ ((addr) & 0x1f) | R128_PLL_WR_EN); \
+ R128_WRITE(R128_CLOCK_CNTL_DATA, (val)); \
+} while (0)
+
+
+#define CCE_PACKET0( reg, n ) (R128_CCE_PACKET0 | \
+ ((n) << 16) | ((reg) >> 2))
+#define CCE_PACKET1( reg0, reg1 ) (R128_CCE_PACKET1 | \
+ (((reg1) >> 2) << 11) | ((reg0) >> 2))
+#define CCE_PACKET2() (R128_CCE_PACKET2)
+#define CCE_PACKET3( pkt, n ) (R128_CCE_PACKET3 | \
+ (pkt) | ((n) << 16))
+
+
+static __inline__ void
+r128_update_ring_snapshot( drm_r128_private_t *dev_priv )
+{
+ drm_r128_ring_buffer_t *ring = &dev_priv->ring;
+ ring->space = (GET_RING_HEAD( dev_priv ) - ring->tail) * sizeof(u32);
+ if ( ring->space <= 0 )
+ ring->space += ring->size;
+}
+
+/* ================================================================
+ * Misc helper macros
+ */
+
+#define RING_SPACE_TEST_WITH_RETURN( dev_priv ) \
+do { \
+ drm_r128_ring_buffer_t *ring = &dev_priv->ring; int i; \
+ if ( ring->space < ring->high_mark ) { \
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) { \
+ r128_update_ring_snapshot( dev_priv ); \
+ if ( ring->space >= ring->high_mark ) \
+ goto __ring_space_done; \
+ DRM_UDELAY(1); \
+ } \
+ DRM_ERROR( "ring space check failed!\n" ); \
+ return DRM_ERR(EBUSY); \
+ } \
+ __ring_space_done: \
+ ; \
+} while (0)
+
+#define VB_AGE_TEST_WITH_RETURN( dev_priv ) \
+do { \
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv; \
+ if ( sarea_priv->last_dispatch >= R128_MAX_VB_AGE ) { \
+ int __ret = r128_do_cce_idle( dev_priv ); \
+ if ( __ret ) return __ret; \
+ sarea_priv->last_dispatch = 0; \
+ r128_freelist_reset( dev ); \
+ } \
+} while (0)
+
+#define R128_WAIT_UNTIL_PAGE_FLIPPED() do { \
+ OUT_RING( CCE_PACKET0( R128_WAIT_UNTIL, 0 ) ); \
+ OUT_RING( R128_EVENT_CRTC_OFFSET ); \
+} while (0)
+
+
+/* ================================================================
+ * Ring control
+ */
+
+#define R128_VERBOSE 0
+
+#define RING_LOCALS \
+ int write, _nr; unsigned int tail_mask; volatile u32 *ring;
+
+#define BEGIN_RING( n ) do { \
+ if ( R128_VERBOSE ) { \
+ DRM_INFO( "BEGIN_RING( %d ) in %s\n", \
+ (n), __FUNCTION__ ); \
+ } \
+ if ( dev_priv->ring.space <= (n) * sizeof(u32) ) { \
+ COMMIT_RING(); \
+ r128_wait_ring( dev_priv, (n) * sizeof(u32) ); \
+ } \
+ _nr = n; dev_priv->ring.space -= (n) * sizeof(u32); \
+ ring = dev_priv->ring.start; \
+ write = dev_priv->ring.tail; \
+ tail_mask = dev_priv->ring.tail_mask; \
+} while (0)
+
+/* You can set this to zero if you want. If the card locks up, you'll
+ * need to keep this set. It works around a bug in early revs of the
+ * Rage 128 chipset, where the CCE would read 32 dwords past the end of
+ * the ring buffer before wrapping around.
+ */
+#define R128_BROKEN_CCE 1
+
+#define ADVANCE_RING() do { \
+ if ( R128_VERBOSE ) { \
+ DRM_INFO( "ADVANCE_RING() wr=0x%06x tail=0x%06x\n", \
+ write, dev_priv->ring.tail ); \
+ } \
+ if ( R128_BROKEN_CCE && write < 32 ) { \
+ memcpy( dev_priv->ring.end, \
+ dev_priv->ring.start, \
+ write * sizeof(u32) ); \
+ } \
+ if (((dev_priv->ring.tail + _nr) & tail_mask) != write) { \
+ DRM_ERROR( \
+ "ADVANCE_RING(): mismatch: nr: %x write: %x line: %d\n", \
+ ((dev_priv->ring.tail + _nr) & tail_mask), \
+ write, __LINE__); \
+ } else \
+ dev_priv->ring.tail = write; \
+} while (0)
+
+#define COMMIT_RING() do { \
+ if ( R128_VERBOSE ) { \
+ DRM_INFO( "COMMIT_RING() tail=0x%06x\n", \
+ dev_priv->ring.tail ); \
+ } \
+ DRM_MEMORYBARRIER(); \
+ R128_WRITE( R128_PM4_BUFFER_DL_WPTR, dev_priv->ring.tail ); \
+ R128_READ( R128_PM4_BUFFER_DL_WPTR ); \
+} while (0)
+
+#define OUT_RING( x ) do { \
+ if ( R128_VERBOSE ) { \
+ DRM_INFO( " OUT_RING( 0x%08x ) at 0x%x\n", \
+ (unsigned int)(x), write ); \
+ } \
+ ring[write++] = cpu_to_le32( x ); \
+ write &= tail_mask; \
+} while (0)
+
+#endif /* __R128_DRV_H__ */
diff --git a/drivers/char/drm/r128_irq.c b/drivers/char/drm/r128_irq.c
new file mode 100644
index 000000000000..643a30785fe5
--- /dev/null
+++ b/drivers/char/drm/r128_irq.c
@@ -0,0 +1,102 @@
+/* r128_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved.
+ *
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Keith Whitwell <keith@tungstengraphics.com>
+ * Eric Anholt <anholt@FreeBSD.org>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+irqreturn_t r128_driver_irq_handler( DRM_IRQ_ARGS )
+{
+ drm_device_t *dev = (drm_device_t *) arg;
+ drm_r128_private_t *dev_priv =
+ (drm_r128_private_t *)dev->dev_private;
+ int status;
+
+ status = R128_READ( R128_GEN_INT_STATUS );
+
+ /* VBLANK interrupt */
+ if ( status & R128_CRTC_VBLANK_INT ) {
+ R128_WRITE( R128_GEN_INT_STATUS, R128_CRTC_VBLANK_INT_AK );
+ atomic_inc(&dev->vbl_received);
+ DRM_WAKEUP(&dev->vbl_queue);
+ drm_vbl_send_signals( dev );
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+int r128_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+ unsigned int cur_vblank;
+ int ret = 0;
+
+ /* Assume that the user has missed the current sequence number
+ * by about a day rather than she wants to wait for years
+ * using vertical blanks...
+ */
+ DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ,
+ ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+ - *sequence ) <= (1<<23) ) );
+
+ *sequence = cur_vblank;
+
+ return ret;
+}
+
+void r128_driver_irq_preinstall( drm_device_t *dev ) {
+ drm_r128_private_t *dev_priv =
+ (drm_r128_private_t *)dev->dev_private;
+
+ /* Disable *all* interrupts */
+ R128_WRITE( R128_GEN_INT_CNTL, 0 );
+ /* Clear vblank bit if it's already high */
+ R128_WRITE( R128_GEN_INT_STATUS, R128_CRTC_VBLANK_INT_AK );
+}
+
+void r128_driver_irq_postinstall( drm_device_t *dev ) {
+ drm_r128_private_t *dev_priv =
+ (drm_r128_private_t *)dev->dev_private;
+
+ /* Turn on VBL interrupt */
+ R128_WRITE( R128_GEN_INT_CNTL, R128_CRTC_VBLANK_INT_EN );
+}
+
+void r128_driver_irq_uninstall( drm_device_t *dev ) {
+ drm_r128_private_t *dev_priv =
+ (drm_r128_private_t *)dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ /* Disable *all* interrupts */
+ R128_WRITE( R128_GEN_INT_CNTL, 0 );
+}
diff --git a/drivers/char/drm/r128_state.c b/drivers/char/drm/r128_state.c
new file mode 100644
index 000000000000..38b3cbd0bbba
--- /dev/null
+++ b/drivers/char/drm/r128_state.c
@@ -0,0 +1,1732 @@
+/* r128_state.c -- State support for r128 -*- linux-c -*-
+ * Created: Thu Jan 27 02:53:43 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+
+/* ================================================================
+ * CCE hardware state programming functions
+ */
+
+static void r128_emit_clip_rects( drm_r128_private_t *dev_priv,
+ drm_clip_rect_t *boxes, int count )
+{
+ u32 aux_sc_cntl = 0x00000000;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( (count < 3? count: 3) * 5 + 2 );
+
+ if ( count >= 1 ) {
+ OUT_RING( CCE_PACKET0( R128_AUX1_SC_LEFT, 3 ) );
+ OUT_RING( boxes[0].x1 );
+ OUT_RING( boxes[0].x2 - 1 );
+ OUT_RING( boxes[0].y1 );
+ OUT_RING( boxes[0].y2 - 1 );
+
+ aux_sc_cntl |= (R128_AUX1_SC_EN | R128_AUX1_SC_MODE_OR);
+ }
+ if ( count >= 2 ) {
+ OUT_RING( CCE_PACKET0( R128_AUX2_SC_LEFT, 3 ) );
+ OUT_RING( boxes[1].x1 );
+ OUT_RING( boxes[1].x2 - 1 );
+ OUT_RING( boxes[1].y1 );
+ OUT_RING( boxes[1].y2 - 1 );
+
+ aux_sc_cntl |= (R128_AUX2_SC_EN | R128_AUX2_SC_MODE_OR);
+ }
+ if ( count >= 3 ) {
+ OUT_RING( CCE_PACKET0( R128_AUX3_SC_LEFT, 3 ) );
+ OUT_RING( boxes[2].x1 );
+ OUT_RING( boxes[2].x2 - 1 );
+ OUT_RING( boxes[2].y1 );
+ OUT_RING( boxes[2].y2 - 1 );
+
+ aux_sc_cntl |= (R128_AUX3_SC_EN | R128_AUX3_SC_MODE_OR);
+ }
+
+ OUT_RING( CCE_PACKET0( R128_AUX_SC_CNTL, 0 ) );
+ OUT_RING( aux_sc_cntl );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_core( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_SCALE_3D_CNTL, 0 ) );
+ OUT_RING( ctx->scale_3d_cntl );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_context( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 13 );
+
+ OUT_RING( CCE_PACKET0( R128_DST_PITCH_OFFSET_C, 11 ) );
+ OUT_RING( ctx->dst_pitch_offset_c );
+ OUT_RING( ctx->dp_gui_master_cntl_c );
+ OUT_RING( ctx->sc_top_left_c );
+ OUT_RING( ctx->sc_bottom_right_c );
+ OUT_RING( ctx->z_offset_c );
+ OUT_RING( ctx->z_pitch_c );
+ OUT_RING( ctx->z_sten_cntl_c );
+ OUT_RING( ctx->tex_cntl_c );
+ OUT_RING( ctx->misc_3d_state_cntl_reg );
+ OUT_RING( ctx->texture_clr_cmp_clr_c );
+ OUT_RING( ctx->texture_clr_cmp_msk_c );
+ OUT_RING( ctx->fog_color_c );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_setup( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 3 );
+
+ OUT_RING( CCE_PACKET1( R128_SETUP_CNTL, R128_PM4_VC_FPU_SETUP ) );
+ OUT_RING( ctx->setup_cntl );
+ OUT_RING( ctx->pm4_vc_fpu_setup );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_masks( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 5 );
+
+ OUT_RING( CCE_PACKET0( R128_DP_WRITE_MASK, 0 ) );
+ OUT_RING( ctx->dp_write_mask );
+
+ OUT_RING( CCE_PACKET0( R128_STEN_REF_MASK_C, 1 ) );
+ OUT_RING( ctx->sten_ref_mask_c );
+ OUT_RING( ctx->plane_3d_mask_c );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_window( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_WINDOW_XY_OFFSET, 0 ) );
+ OUT_RING( ctx->window_xy_offset );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_tex0( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+ drm_r128_texture_regs_t *tex = &sarea_priv->tex_state[0];
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 7 + R128_MAX_TEXTURE_LEVELS );
+
+ OUT_RING( CCE_PACKET0( R128_PRIM_TEX_CNTL_C,
+ 2 + R128_MAX_TEXTURE_LEVELS ) );
+ OUT_RING( tex->tex_cntl );
+ OUT_RING( tex->tex_combine_cntl );
+ OUT_RING( ctx->tex_size_pitch_c );
+ for ( i = 0 ; i < R128_MAX_TEXTURE_LEVELS ; i++ ) {
+ OUT_RING( tex->tex_offset[i] );
+ }
+
+ OUT_RING( CCE_PACKET0( R128_CONSTANT_COLOR_C, 1 ) );
+ OUT_RING( ctx->constant_color_c );
+ OUT_RING( tex->tex_border_color );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_tex1( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_texture_regs_t *tex = &sarea_priv->tex_state[1];
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( " %s\n", __FUNCTION__ );
+
+ BEGIN_RING( 5 + R128_MAX_TEXTURE_LEVELS );
+
+ OUT_RING( CCE_PACKET0( R128_SEC_TEX_CNTL_C,
+ 1 + R128_MAX_TEXTURE_LEVELS ) );
+ OUT_RING( tex->tex_cntl );
+ OUT_RING( tex->tex_combine_cntl );
+ for ( i = 0 ; i < R128_MAX_TEXTURE_LEVELS ; i++ ) {
+ OUT_RING( tex->tex_offset[i] );
+ }
+
+ OUT_RING( CCE_PACKET0( R128_SEC_TEXTURE_BORDER_COLOR_C, 0 ) );
+ OUT_RING( tex->tex_border_color );
+
+ ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_state( drm_r128_private_t *dev_priv )
+{
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ unsigned int dirty = sarea_priv->dirty;
+
+ DRM_DEBUG( "%s: dirty=0x%08x\n", __FUNCTION__, dirty );
+
+ if ( dirty & R128_UPLOAD_CORE ) {
+ r128_emit_core( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_CORE;
+ }
+
+ if ( dirty & R128_UPLOAD_CONTEXT ) {
+ r128_emit_context( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_CONTEXT;
+ }
+
+ if ( dirty & R128_UPLOAD_SETUP ) {
+ r128_emit_setup( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_SETUP;
+ }
+
+ if ( dirty & R128_UPLOAD_MASKS ) {
+ r128_emit_masks( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_MASKS;
+ }
+
+ if ( dirty & R128_UPLOAD_WINDOW ) {
+ r128_emit_window( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_WINDOW;
+ }
+
+ if ( dirty & R128_UPLOAD_TEX0 ) {
+ r128_emit_tex0( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_TEX0;
+ }
+
+ if ( dirty & R128_UPLOAD_TEX1 ) {
+ r128_emit_tex1( dev_priv );
+ sarea_priv->dirty &= ~R128_UPLOAD_TEX1;
+ }
+
+ /* Turn off the texture cache flushing */
+ sarea_priv->context_state.tex_cntl_c &= ~R128_TEX_CACHE_FLUSH;
+
+ sarea_priv->dirty &= ~R128_REQUIRE_QUIESCENCE;
+}
+
+
+#if R128_PERFORMANCE_BOXES
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void r128_clear_box( drm_r128_private_t *dev_priv,
+ int x, int y, int w, int h,
+ int r, int g, int b )
+{
+ u32 pitch, offset;
+ u32 fb_bpp, color;
+ RING_LOCALS;
+
+ switch ( dev_priv->fb_bpp ) {
+ case 16:
+ fb_bpp = R128_GMC_DST_16BPP;
+ color = (((r & 0xf8) << 8) |
+ ((g & 0xfc) << 3) |
+ ((b & 0xf8) >> 3));
+ break;
+ case 24:
+ fb_bpp = R128_GMC_DST_24BPP;
+ color = ((r << 16) | (g << 8) | b);
+ break;
+ case 32:
+ fb_bpp = R128_GMC_DST_32BPP;
+ color = (((0xff) << 24) | (r << 16) | (g << 8) | b);
+ break;
+ default:
+ return;
+ }
+
+ offset = dev_priv->back_offset;
+ pitch = dev_priv->back_pitch >> 3;
+
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ fb_bpp |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS );
+
+ OUT_RING( (pitch << 21) | (offset >> 5) );
+ OUT_RING( color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+}
+
+static void r128_cce_performance_boxes( drm_r128_private_t *dev_priv )
+{
+ if ( atomic_read( &dev_priv->idle_count ) == 0 ) {
+ r128_clear_box( dev_priv, 64, 4, 8, 8, 0, 255, 0 );
+ } else {
+ atomic_set( &dev_priv->idle_count, 0 );
+ }
+}
+
+#endif
+
+
+/* ================================================================
+ * CCE command dispatch functions
+ */
+
+static void r128_print_dirty( const char *msg, unsigned int flags )
+{
+ DRM_INFO( "%s: (0x%x) %s%s%s%s%s%s%s%s%s\n",
+ msg,
+ flags,
+ (flags & R128_UPLOAD_CORE) ? "core, " : "",
+ (flags & R128_UPLOAD_CONTEXT) ? "context, " : "",
+ (flags & R128_UPLOAD_SETUP) ? "setup, " : "",
+ (flags & R128_UPLOAD_TEX0) ? "tex0, " : "",
+ (flags & R128_UPLOAD_TEX1) ? "tex1, " : "",
+ (flags & R128_UPLOAD_MASKS) ? "masks, " : "",
+ (flags & R128_UPLOAD_WINDOW) ? "window, " : "",
+ (flags & R128_UPLOAD_CLIPRECTS) ? "cliprects, " : "",
+ (flags & R128_REQUIRE_QUIESCENCE) ? "quiescence, " : "" );
+}
+
+static void r128_cce_dispatch_clear( drm_device_t *dev,
+ drm_r128_clear_t *clear )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ unsigned int flags = clear->flags;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) {
+ unsigned int tmp = flags;
+
+ flags &= ~(R128_FRONT | R128_BACK);
+ if ( tmp & R128_FRONT ) flags |= R128_BACK;
+ if ( tmp & R128_BACK ) flags |= R128_FRONT;
+ }
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ int x = pbox[i].x1;
+ int y = pbox[i].y1;
+ int w = pbox[i].x2 - x;
+ int h = pbox[i].y2 - y;
+
+ DRM_DEBUG( "dispatch clear %d,%d-%d,%d flags 0x%x\n",
+ pbox[i].x1, pbox[i].y1, pbox[i].x2,
+ pbox[i].y2, flags );
+
+ if ( flags & (R128_FRONT | R128_BACK) ) {
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_DP_WRITE_MASK, 0 ) );
+ OUT_RING( clear->color_mask );
+
+ ADVANCE_RING();
+ }
+
+ if ( flags & R128_FRONT ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->color_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS );
+
+ OUT_RING( dev_priv->front_pitch_offset_c );
+ OUT_RING( clear->clear_color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+
+ if ( flags & R128_BACK ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->color_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS );
+
+ OUT_RING( dev_priv->back_pitch_offset_c );
+ OUT_RING( clear->clear_color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+
+ if ( flags & R128_DEPTH ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( clear->clear_depth );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+ }
+}
+
+static void r128_cce_dispatch_swap( drm_device_t *dev )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+#if R128_PERFORMANCE_BOXES
+ /* Do some trivial performance monitoring...
+ */
+ r128_cce_performance_boxes( dev_priv );
+#endif
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ int x = pbox[i].x1;
+ int y = pbox[i].y1;
+ int w = pbox[i].x2 - x;
+ int h = pbox[i].y2 - y;
+
+ BEGIN_RING( 7 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+ OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+ R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_NONE |
+ (dev_priv->color_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_S |
+ R128_DP_SRC_SOURCE_MEMORY |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ /* Make this work even if front & back are flipped:
+ */
+ if (dev_priv->current_page == 0) {
+ OUT_RING( dev_priv->back_pitch_offset_c );
+ OUT_RING( dev_priv->front_pitch_offset_c );
+ }
+ else {
+ OUT_RING( dev_priv->front_pitch_offset_c );
+ OUT_RING( dev_priv->back_pitch_offset_c );
+ }
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+
+ /* Increment the frame counter. The client-side 3D driver must
+ * throttle the framerate by waiting for this value before
+ * performing the swapbuffer ioctl.
+ */
+ dev_priv->sarea_priv->last_frame++;
+
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_LAST_FRAME_REG, 0 ) );
+ OUT_RING( dev_priv->sarea_priv->last_frame );
+
+ ADVANCE_RING();
+}
+
+static void r128_cce_dispatch_flip( drm_device_t *dev )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+ DRM_DEBUG("%s: page=%d pfCurrentPage=%d\n",
+ __FUNCTION__,
+ dev_priv->current_page,
+ dev_priv->sarea_priv->pfCurrentPage);
+
+#if R128_PERFORMANCE_BOXES
+ /* Do some trivial performance monitoring...
+ */
+ r128_cce_performance_boxes( dev_priv );
+#endif
+
+ BEGIN_RING( 4 );
+
+ R128_WAIT_UNTIL_PAGE_FLIPPED();
+ OUT_RING( CCE_PACKET0( R128_CRTC_OFFSET, 0 ) );
+
+ if ( dev_priv->current_page == 0 ) {
+ OUT_RING( dev_priv->back_offset );
+ } else {
+ OUT_RING( dev_priv->front_offset );
+ }
+
+ ADVANCE_RING();
+
+ /* Increment the frame counter. The client-side 3D driver must
+ * throttle the framerate by waiting for this value before
+ * performing the swapbuffer ioctl.
+ */
+ dev_priv->sarea_priv->last_frame++;
+ dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page =
+ 1 - dev_priv->current_page;
+
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_LAST_FRAME_REG, 0 ) );
+ OUT_RING( dev_priv->sarea_priv->last_frame );
+
+ ADVANCE_RING();
+}
+
+static void r128_cce_dispatch_vertex( drm_device_t *dev,
+ drm_buf_t *buf )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int format = sarea_priv->vc_format;
+ int offset = buf->bus_address;
+ int size = buf->used;
+ int prim = buf_priv->prim;
+ int i = 0;
+ RING_LOCALS;
+ DRM_DEBUG( "buf=%d nbox=%d\n", buf->idx, sarea_priv->nbox );
+
+ if ( 0 )
+ r128_print_dirty( "dispatch_vertex", sarea_priv->dirty );
+
+ if ( buf->used ) {
+ buf_priv->dispatched = 1;
+
+ if ( sarea_priv->dirty & ~R128_UPLOAD_CLIPRECTS ) {
+ r128_emit_state( dev_priv );
+ }
+
+ do {
+ /* Emit the next set of up to three cliprects */
+ if ( i < sarea_priv->nbox ) {
+ r128_emit_clip_rects( dev_priv,
+ &sarea_priv->boxes[i],
+ sarea_priv->nbox - i );
+ }
+
+ /* Emit the vertex buffer rendering commands */
+ BEGIN_RING( 5 );
+
+ OUT_RING( CCE_PACKET3( R128_3D_RNDR_GEN_INDX_PRIM, 3 ) );
+ OUT_RING( offset );
+ OUT_RING( size );
+ OUT_RING( format );
+ OUT_RING( prim | R128_CCE_VC_CNTL_PRIM_WALK_LIST |
+ (size << R128_CCE_VC_CNTL_NUM_SHIFT) );
+
+ ADVANCE_RING();
+
+ i += 3;
+ } while ( i < sarea_priv->nbox );
+ }
+
+ if ( buf_priv->discard ) {
+ buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+ /* Emit the vertex buffer age */
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+ OUT_RING( buf_priv->age );
+
+ ADVANCE_RING();
+
+ buf->pending = 1;
+ buf->used = 0;
+ /* FIXME: Check dispatched field */
+ buf_priv->dispatched = 0;
+ }
+
+ dev_priv->sarea_priv->last_dispatch++;
+
+ sarea_priv->dirty &= ~R128_UPLOAD_CLIPRECTS;
+ sarea_priv->nbox = 0;
+}
+
+static void r128_cce_dispatch_indirect( drm_device_t *dev,
+ drm_buf_t *buf,
+ int start, int end )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+ RING_LOCALS;
+ DRM_DEBUG( "indirect: buf=%d s=0x%x e=0x%x\n",
+ buf->idx, start, end );
+
+ if ( start != end ) {
+ int offset = buf->bus_address + start;
+ int dwords = (end - start + 3) / sizeof(u32);
+
+ /* Indirect buffer data must be an even number of
+ * dwords, so if we've been given an odd number we must
+ * pad the data with a Type-2 CCE packet.
+ */
+ if ( dwords & 1 ) {
+ u32 *data = (u32 *)
+ ((char *)dev->agp_buffer_map->handle
+ + buf->offset + start);
+ data[dwords++] = cpu_to_le32( R128_CCE_PACKET2 );
+ }
+
+ buf_priv->dispatched = 1;
+
+ /* Fire off the indirect buffer */
+ BEGIN_RING( 3 );
+
+ OUT_RING( CCE_PACKET0( R128_PM4_IW_INDOFF, 1 ) );
+ OUT_RING( offset );
+ OUT_RING( dwords );
+
+ ADVANCE_RING();
+ }
+
+ if ( buf_priv->discard ) {
+ buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+ /* Emit the indirect buffer age */
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+ OUT_RING( buf_priv->age );
+
+ ADVANCE_RING();
+
+ buf->pending = 1;
+ buf->used = 0;
+ /* FIXME: Check dispatched field */
+ buf_priv->dispatched = 0;
+ }
+
+ dev_priv->sarea_priv->last_dispatch++;
+}
+
+static void r128_cce_dispatch_indices( drm_device_t *dev,
+ drm_buf_t *buf,
+ int start, int end,
+ int count )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int format = sarea_priv->vc_format;
+ int offset = dev->agp_buffer_map->offset - dev_priv->cce_buffers_offset;
+ int prim = buf_priv->prim;
+ u32 *data;
+ int dwords;
+ int i = 0;
+ RING_LOCALS;
+ DRM_DEBUG( "indices: s=%d e=%d c=%d\n", start, end, count );
+
+ if ( 0 )
+ r128_print_dirty( "dispatch_indices", sarea_priv->dirty );
+
+ if ( start != end ) {
+ buf_priv->dispatched = 1;
+
+ if ( sarea_priv->dirty & ~R128_UPLOAD_CLIPRECTS ) {
+ r128_emit_state( dev_priv );
+ }
+
+ dwords = (end - start + 3) / sizeof(u32);
+
+ data = (u32 *)((char *)dev->agp_buffer_map->handle
+ + buf->offset + start);
+
+ data[0] = cpu_to_le32( CCE_PACKET3( R128_3D_RNDR_GEN_INDX_PRIM,
+ dwords-2 ) );
+
+ data[1] = cpu_to_le32( offset );
+ data[2] = cpu_to_le32( R128_MAX_VB_VERTS );
+ data[3] = cpu_to_le32( format );
+ data[4] = cpu_to_le32( (prim | R128_CCE_VC_CNTL_PRIM_WALK_IND |
+ (count << 16)) );
+
+ if ( count & 0x1 ) {
+#ifdef __LITTLE_ENDIAN
+ data[dwords-1] &= 0x0000ffff;
+#else
+ data[dwords-1] &= 0xffff0000;
+#endif
+ }
+
+ do {
+ /* Emit the next set of up to three cliprects */
+ if ( i < sarea_priv->nbox ) {
+ r128_emit_clip_rects( dev_priv,
+ &sarea_priv->boxes[i],
+ sarea_priv->nbox - i );
+ }
+
+ r128_cce_dispatch_indirect( dev, buf, start, end );
+
+ i += 3;
+ } while ( i < sarea_priv->nbox );
+ }
+
+ if ( buf_priv->discard ) {
+ buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+ /* Emit the vertex buffer age */
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+ OUT_RING( buf_priv->age );
+
+ ADVANCE_RING();
+
+ buf->pending = 1;
+ /* FIXME: Check dispatched field */
+ buf_priv->dispatched = 0;
+ }
+
+ dev_priv->sarea_priv->last_dispatch++;
+
+ sarea_priv->dirty &= ~R128_UPLOAD_CLIPRECTS;
+ sarea_priv->nbox = 0;
+}
+
+static int r128_cce_dispatch_blit( DRMFILE filp,
+ drm_device_t *dev,
+ drm_r128_blit_t *blit )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_r128_buf_priv_t *buf_priv;
+ u32 *data;
+ int dword_shift, dwords;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ /* The compiler won't optimize away a division by a variable,
+ * even if the only legal values are powers of two. Thus, we'll
+ * use a shift instead.
+ */
+ switch ( blit->format ) {
+ case R128_DATATYPE_ARGB8888:
+ dword_shift = 0;
+ break;
+ case R128_DATATYPE_ARGB1555:
+ case R128_DATATYPE_RGB565:
+ case R128_DATATYPE_ARGB4444:
+ case R128_DATATYPE_YVYU422:
+ case R128_DATATYPE_VYUY422:
+ dword_shift = 1;
+ break;
+ case R128_DATATYPE_CI8:
+ case R128_DATATYPE_RGB8:
+ dword_shift = 2;
+ break;
+ default:
+ DRM_ERROR( "invalid blit format %d\n", blit->format );
+ return DRM_ERR(EINVAL);
+ }
+
+ /* Flush the pixel cache, and mark the contents as Read Invalid.
+ * This ensures no pixel data gets mixed up with the texture
+ * data from the host data blit, otherwise part of the texture
+ * image may be corrupted.
+ */
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_PC_GUI_CTLSTAT, 0 ) );
+ OUT_RING( R128_PC_RI_GUI | R128_PC_FLUSH_GUI );
+
+ ADVANCE_RING();
+
+ /* Dispatch the indirect buffer.
+ */
+ buf = dma->buflist[blit->idx];
+ buf_priv = buf->dev_private;
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", blit->idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf_priv->discard = 1;
+
+ dwords = (blit->width * blit->height) >> dword_shift;
+
+ data = (u32 *)((char *)dev->agp_buffer_map->handle + buf->offset);
+
+ data[0] = cpu_to_le32( CCE_PACKET3( R128_CNTL_HOSTDATA_BLT, dwords + 6 ) );
+ data[1] = cpu_to_le32( (R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_NONE |
+ (blit->format << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_S |
+ R128_DP_SRC_SOURCE_HOST_DATA |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_AUX_CLIP_DIS |
+ R128_GMC_WR_MSK_DIS) );
+
+ data[2] = cpu_to_le32( (blit->pitch << 21) | (blit->offset >> 5) );
+ data[3] = cpu_to_le32( 0xffffffff );
+ data[4] = cpu_to_le32( 0xffffffff );
+ data[5] = cpu_to_le32( (blit->y << 16) | blit->x );
+ data[6] = cpu_to_le32( (blit->height << 16) | blit->width );
+ data[7] = cpu_to_le32( dwords );
+
+ buf->used = (dwords + 8) * sizeof(u32);
+
+ r128_cce_dispatch_indirect( dev, buf, 0, buf->used );
+
+ /* Flush the pixel cache after the blit completes. This ensures
+ * the texture data is written out to memory before rendering
+ * continues.
+ */
+ BEGIN_RING( 2 );
+
+ OUT_RING( CCE_PACKET0( R128_PC_GUI_CTLSTAT, 0 ) );
+ OUT_RING( R128_PC_FLUSH_GUI );
+
+ ADVANCE_RING();
+
+ return 0;
+}
+
+
+/* ================================================================
+ * Tiled depth buffer management
+ *
+ * FIXME: These should all set the destination write mask for when we
+ * have hardware stencil support.
+ */
+
+static int r128_cce_dispatch_write_span( drm_device_t *dev,
+ drm_r128_depth_t *depth )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ int count, x, y;
+ u32 *buffer;
+ u8 *mask;
+ int i, buffer_size, mask_size;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ count = depth->n;
+ if (count > 4096 || count <= 0)
+ return DRM_ERR(EMSGSIZE);
+
+ if ( DRM_COPY_FROM_USER( &x, depth->x, sizeof(x) ) ) {
+ return DRM_ERR(EFAULT);
+ }
+ if ( DRM_COPY_FROM_USER( &y, depth->y, sizeof(y) ) ) {
+ return DRM_ERR(EFAULT);
+ }
+
+ buffer_size = depth->n * sizeof(u32);
+ buffer = drm_alloc( buffer_size, DRM_MEM_BUFS );
+ if ( buffer == NULL )
+ return DRM_ERR(ENOMEM);
+ if ( DRM_COPY_FROM_USER( buffer, depth->buffer, buffer_size ) ) {
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS);
+ return DRM_ERR(EFAULT);
+ }
+
+ mask_size = depth->n * sizeof(u8);
+ if ( depth->mask ) {
+ mask = drm_alloc( mask_size, DRM_MEM_BUFS );
+ if ( mask == NULL ) {
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+ if ( DRM_COPY_FROM_USER( mask, depth->mask, mask_size ) ) {
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+ drm_free( mask, mask_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+
+ for ( i = 0 ; i < count ; i++, x++ ) {
+ if ( mask[i] ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( buffer[i] );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (1 << 16) | 1 );
+
+ ADVANCE_RING();
+ }
+ }
+
+ drm_free( mask, mask_size, DRM_MEM_BUFS );
+ } else {
+ for ( i = 0 ; i < count ; i++, x++ ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( buffer[i] );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (1 << 16) | 1 );
+
+ ADVANCE_RING();
+ }
+ }
+
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+
+ return 0;
+}
+
+static int r128_cce_dispatch_write_pixels( drm_device_t *dev,
+ drm_r128_depth_t *depth )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ int count, *x, *y;
+ u32 *buffer;
+ u8 *mask;
+ int i, xbuf_size, ybuf_size, buffer_size, mask_size;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ count = depth->n;
+ if (count > 4096 || count <= 0)
+ return DRM_ERR(EMSGSIZE);
+
+ xbuf_size = count * sizeof(*x);
+ ybuf_size = count * sizeof(*y);
+ x = drm_alloc( xbuf_size, DRM_MEM_BUFS );
+ if ( x == NULL ) {
+ return DRM_ERR(ENOMEM);
+ }
+ y = drm_alloc( ybuf_size, DRM_MEM_BUFS );
+ if ( y == NULL ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+ if ( DRM_COPY_FROM_USER( x, depth->x, xbuf_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+ if ( DRM_COPY_FROM_USER( y, depth->y, xbuf_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+
+ buffer_size = depth->n * sizeof(u32);
+ buffer = drm_alloc( buffer_size, DRM_MEM_BUFS );
+ if ( buffer == NULL ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+ if ( DRM_COPY_FROM_USER( buffer, depth->buffer, buffer_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+
+ if ( depth->mask ) {
+ mask_size = depth->n * sizeof(u8);
+ mask = drm_alloc( mask_size, DRM_MEM_BUFS );
+ if ( mask == NULL ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+ if ( DRM_COPY_FROM_USER( mask, depth->mask, mask_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+ drm_free( mask, mask_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+
+ for ( i = 0 ; i < count ; i++ ) {
+ if ( mask[i] ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( buffer[i] );
+
+ OUT_RING( (x[i] << 16) | y[i] );
+ OUT_RING( (1 << 16) | 1 );
+
+ ADVANCE_RING();
+ }
+ }
+
+ drm_free( mask, mask_size, DRM_MEM_BUFS );
+ } else {
+ for ( i = 0 ; i < count ; i++ ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_P |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( buffer[i] );
+
+ OUT_RING( (x[i] << 16) | y[i] );
+ OUT_RING( (1 << 16) | 1 );
+
+ ADVANCE_RING();
+ }
+ }
+
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+
+ return 0;
+}
+
+static int r128_cce_dispatch_read_span( drm_device_t *dev,
+ drm_r128_depth_t *depth )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ int count, x, y;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ count = depth->n;
+ if (count > 4096 || count <= 0)
+ return DRM_ERR(EMSGSIZE);
+
+ if ( DRM_COPY_FROM_USER( &x, depth->x, sizeof(x) ) ) {
+ return DRM_ERR(EFAULT);
+ }
+ if ( DRM_COPY_FROM_USER( &y, depth->y, sizeof(y) ) ) {
+ return DRM_ERR(EFAULT);
+ }
+
+ BEGIN_RING( 7 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+ OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+ R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_NONE |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_S |
+ R128_DP_SRC_SOURCE_MEMORY |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( dev_priv->span_pitch_offset_c );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (0 << 16) | 0 );
+ OUT_RING( (count << 16) | 1 );
+
+ ADVANCE_RING();
+
+ return 0;
+}
+
+static int r128_cce_dispatch_read_pixels( drm_device_t *dev,
+ drm_r128_depth_t *depth )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ int count, *x, *y;
+ int i, xbuf_size, ybuf_size;
+ RING_LOCALS;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ count = depth->n;
+ if (count > 4096 || count <= 0)
+ return DRM_ERR(EMSGSIZE);
+
+ if ( count > dev_priv->depth_pitch ) {
+ count = dev_priv->depth_pitch;
+ }
+
+ xbuf_size = count * sizeof(*x);
+ ybuf_size = count * sizeof(*y);
+ x = drm_alloc( xbuf_size, DRM_MEM_BUFS );
+ if ( x == NULL ) {
+ return DRM_ERR(ENOMEM);
+ }
+ y = drm_alloc( ybuf_size, DRM_MEM_BUFS );
+ if ( y == NULL ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+ if ( DRM_COPY_FROM_USER( x, depth->x, xbuf_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+ if ( DRM_COPY_FROM_USER( y, depth->y, ybuf_size ) ) {
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+ return DRM_ERR(EFAULT);
+ }
+
+ for ( i = 0 ; i < count ; i++ ) {
+ BEGIN_RING( 7 );
+
+ OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+ OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+ R128_GMC_DST_PITCH_OFFSET_CNTL |
+ R128_GMC_BRUSH_NONE |
+ (dev_priv->depth_fmt << 8) |
+ R128_GMC_SRC_DATATYPE_COLOR |
+ R128_ROP3_S |
+ R128_DP_SRC_SOURCE_MEMORY |
+ R128_GMC_CLR_CMP_CNTL_DIS |
+ R128_GMC_WR_MSK_DIS );
+
+ OUT_RING( dev_priv->depth_pitch_offset_c );
+ OUT_RING( dev_priv->span_pitch_offset_c );
+
+ OUT_RING( (x[i] << 16) | y[i] );
+ OUT_RING( (i << 16) | 0 );
+ OUT_RING( (1 << 16) | 1 );
+
+ ADVANCE_RING();
+ }
+
+ drm_free( x, xbuf_size, DRM_MEM_BUFS );
+ drm_free( y, ybuf_size, DRM_MEM_BUFS );
+
+ return 0;
+}
+
+
+/* ================================================================
+ * Polygon stipple
+ */
+
+static void r128_cce_dispatch_stipple( drm_device_t *dev, u32 *stipple )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ BEGIN_RING( 33 );
+
+ OUT_RING( CCE_PACKET0( R128_BRUSH_DATA0, 31 ) );
+ for ( i = 0 ; i < 32 ; i++ ) {
+ OUT_RING( stipple[i] );
+ }
+
+ ADVANCE_RING();
+}
+
+
+/* ================================================================
+ * IOCTL functions
+ */
+
+static int r128_cce_clear( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_r128_clear_t clear;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( clear, (drm_r128_clear_t __user *) data,
+ sizeof(clear) );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if ( sarea_priv->nbox > R128_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = R128_NR_SAREA_CLIPRECTS;
+
+ r128_cce_dispatch_clear( dev, &clear );
+ COMMIT_RING();
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->dirty |= R128_UPLOAD_CONTEXT | R128_UPLOAD_MASKS;
+
+ return 0;
+}
+
+static int r128_do_init_pageflip( drm_device_t *dev )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ dev_priv->crtc_offset = R128_READ( R128_CRTC_OFFSET );
+ dev_priv->crtc_offset_cntl = R128_READ( R128_CRTC_OFFSET_CNTL );
+
+ R128_WRITE( R128_CRTC_OFFSET, dev_priv->front_offset );
+ R128_WRITE( R128_CRTC_OFFSET_CNTL,
+ dev_priv->crtc_offset_cntl | R128_CRTC_OFFSET_FLIP_CNTL );
+
+ dev_priv->page_flipping = 1;
+ dev_priv->current_page = 0;
+ dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page;
+
+ return 0;
+}
+
+int r128_do_cleanup_pageflip( drm_device_t *dev )
+{
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ R128_WRITE( R128_CRTC_OFFSET, dev_priv->crtc_offset );
+ R128_WRITE( R128_CRTC_OFFSET_CNTL, dev_priv->crtc_offset_cntl );
+
+ if (dev_priv->current_page != 0) {
+ r128_cce_dispatch_flip( dev );
+ COMMIT_RING();
+ }
+
+ dev_priv->page_flipping = 0;
+ return 0;
+}
+
+/* Swapping and flipping are different operations, need different ioctls.
+ * They can & should be intermixed to support multiple 3d windows.
+ */
+
+static int r128_cce_flip( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if (!dev_priv->page_flipping)
+ r128_do_init_pageflip( dev );
+
+ r128_cce_dispatch_flip( dev );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_cce_swap( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if ( sarea_priv->nbox > R128_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = R128_NR_SAREA_CLIPRECTS;
+
+ r128_cce_dispatch_swap( dev );
+ dev_priv->sarea_priv->dirty |= (R128_UPLOAD_CONTEXT |
+ R128_UPLOAD_MASKS);
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_cce_vertex( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_r128_buf_priv_t *buf_priv;
+ drm_r128_vertex_t vertex;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( vertex, (drm_r128_vertex_t __user *) data,
+ sizeof(vertex) );
+
+ DRM_DEBUG( "pid=%d index=%d count=%d discard=%d\n",
+ DRM_CURRENTPID,
+ vertex.idx, vertex.count, vertex.discard );
+
+ if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ vertex.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+ if ( vertex.prim < 0 ||
+ vertex.prim > R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2 ) {
+ DRM_ERROR( "buffer prim %d\n", vertex.prim );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf = dma->buflist[vertex.idx];
+ buf_priv = buf->dev_private;
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf->used = vertex.count;
+ buf_priv->prim = vertex.prim;
+ buf_priv->discard = vertex.discard;
+
+ r128_cce_dispatch_vertex( dev, buf );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_cce_indices( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_r128_buf_priv_t *buf_priv;
+ drm_r128_indices_t elts;
+ int count;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( elts, (drm_r128_indices_t __user *) data,
+ sizeof(elts) );
+
+ DRM_DEBUG( "pid=%d buf=%d s=%d e=%d d=%d\n", DRM_CURRENTPID,
+ elts.idx, elts.start, elts.end, elts.discard );
+
+ if ( elts.idx < 0 || elts.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ elts.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+ if ( elts.prim < 0 ||
+ elts.prim > R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2 ) {
+ DRM_ERROR( "buffer prim %d\n", elts.prim );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf = dma->buflist[elts.idx];
+ buf_priv = buf->dev_private;
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", elts.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ count = (elts.end - elts.start) / sizeof(u16);
+ elts.start -= R128_INDEX_PRIM_OFFSET;
+
+ if ( elts.start & 0x7 ) {
+ DRM_ERROR( "misaligned buffer 0x%x\n", elts.start );
+ return DRM_ERR(EINVAL);
+ }
+ if ( elts.start < buf->used ) {
+ DRM_ERROR( "no header 0x%x - 0x%x\n", elts.start, buf->used );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf->used = elts.end;
+ buf_priv->prim = elts.prim;
+ buf_priv->discard = elts.discard;
+
+ r128_cce_dispatch_indices( dev, buf, elts.start, elts.end, count );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_cce_blit( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_device_dma_t *dma = dev->dma;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_blit_t blit;
+ int ret;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( blit, (drm_r128_blit_t __user *) data,
+ sizeof(blit) );
+
+ DRM_DEBUG( "pid=%d index=%d\n", DRM_CURRENTPID, blit.idx );
+
+ if ( blit.idx < 0 || blit.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ blit.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ ret = r128_cce_dispatch_blit( filp, dev, &blit );
+
+ COMMIT_RING();
+ return ret;
+}
+
+static int r128_cce_depth( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_depth_t depth;
+ int ret;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( depth, (drm_r128_depth_t __user *) data,
+ sizeof(depth) );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ ret = DRM_ERR(EINVAL);
+ switch ( depth.func ) {
+ case R128_WRITE_SPAN:
+ ret = r128_cce_dispatch_write_span( dev, &depth );
+ case R128_WRITE_PIXELS:
+ ret = r128_cce_dispatch_write_pixels( dev, &depth );
+ case R128_READ_SPAN:
+ ret = r128_cce_dispatch_read_span( dev, &depth );
+ case R128_READ_PIXELS:
+ ret = r128_cce_dispatch_read_pixels( dev, &depth );
+ }
+
+ COMMIT_RING();
+ return ret;
+}
+
+static int r128_cce_stipple( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_stipple_t stipple;
+ u32 mask[32];
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( stipple, (drm_r128_stipple_t __user *) data,
+ sizeof(stipple) );
+
+ if ( DRM_COPY_FROM_USER( &mask, stipple.mask,
+ 32 * sizeof(u32) ) )
+ return DRM_ERR( EFAULT );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ r128_cce_dispatch_stipple( dev, mask );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_cce_indirect( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_r128_buf_priv_t *buf_priv;
+ drm_r128_indirect_t indirect;
+#if 0
+ RING_LOCALS;
+#endif
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( indirect, (drm_r128_indirect_t __user *) data,
+ sizeof(indirect) );
+
+ DRM_DEBUG( "indirect: idx=%d s=%d e=%d d=%d\n",
+ indirect.idx, indirect.start,
+ indirect.end, indirect.discard );
+
+ if ( indirect.idx < 0 || indirect.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ indirect.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf = dma->buflist[indirect.idx];
+ buf_priv = buf->dev_private;
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", indirect.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( indirect.start < buf->used ) {
+ DRM_ERROR( "reusing indirect: start=0x%x actual=0x%x\n",
+ indirect.start, buf->used );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf->used = indirect.end;
+ buf_priv->discard = indirect.discard;
+
+#if 0
+ /* Wait for the 3D stream to idle before the indirect buffer
+ * containing 2D acceleration commands is processed.
+ */
+ BEGIN_RING( 2 );
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ ADVANCE_RING();
+#endif
+
+ /* Dispatch the indirect buffer full of commands from the
+ * X server. This is insecure and is thus only available to
+ * privileged clients.
+ */
+ r128_cce_dispatch_indirect( dev, buf, indirect.start, indirect.end );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int r128_getparam( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ drm_r128_getparam_t param;
+ int value;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( param, (drm_r128_getparam_t __user *)data,
+ sizeof(param) );
+
+ DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+ switch( param.param ) {
+ case R128_PARAM_IRQ_NR:
+ value = dev->irq;
+ break;
+ default:
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+void r128_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+ if ( dev->dev_private ) {
+ drm_r128_private_t *dev_priv = dev->dev_private;
+ if ( dev_priv->page_flipping ) {
+ r128_do_cleanup_pageflip( dev );
+ }
+ }
+}
+
+void r128_driver_pretakedown(drm_device_t *dev)
+{
+ r128_do_cleanup_cce( dev );
+}
+
+drm_ioctl_desc_t r128_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_R128_INIT)] = { r128_cce_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_R128_CCE_START)] = { r128_cce_start, 1, 1 },
+ [DRM_IOCTL_NR(DRM_R128_CCE_STOP)] = { r128_cce_stop, 1, 1 },
+ [DRM_IOCTL_NR(DRM_R128_CCE_RESET)] = { r128_cce_reset, 1, 1 },
+ [DRM_IOCTL_NR(DRM_R128_CCE_IDLE)] = { r128_cce_idle, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_RESET)] = { r128_engine_reset, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_FULLSCREEN)] = { r128_fullscreen, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_SWAP)] = { r128_cce_swap, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_FLIP)] = { r128_cce_flip, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_CLEAR)] = { r128_cce_clear, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_VERTEX)] = { r128_cce_vertex, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_INDICES)] = { r128_cce_indices, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_BLIT)] = { r128_cce_blit, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_DEPTH)] = { r128_cce_depth, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_STIPPLE)] = { r128_cce_stipple, 1, 0 },
+ [DRM_IOCTL_NR(DRM_R128_INDIRECT)] = { r128_cce_indirect, 1, 1 },
+ [DRM_IOCTL_NR(DRM_R128_GETPARAM)] = { r128_getparam, 1, 0 },
+};
+
+int r128_max_ioctl = DRM_ARRAY_SIZE(r128_ioctls);
diff --git a/drivers/char/drm/radeon_cp.c b/drivers/char/drm/radeon_cp.c
new file mode 100644
index 000000000000..20bcf872b348
--- /dev/null
+++ b/drivers/char/drm/radeon_cp.c
@@ -0,0 +1,2061 @@
+/* radeon_cp.c -- CP support for Radeon -*- linux-c -*-
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Kevin E. Martin <martin@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+#define RADEON_FIFO_DEBUG 0
+
+static int radeon_do_cleanup_cp( drm_device_t *dev );
+
+/* CP microcode (from ATI) */
+static u32 R200_cp_microcode[][2] = {
+ { 0x21007000, 0000000000 },
+ { 0x20007000, 0000000000 },
+ { 0x000000ab, 0x00000004 },
+ { 0x000000af, 0x00000004 },
+ { 0x66544a49, 0000000000 },
+ { 0x49494174, 0000000000 },
+ { 0x54517d83, 0000000000 },
+ { 0x498d8b64, 0000000000 },
+ { 0x49494949, 0000000000 },
+ { 0x49da493c, 0000000000 },
+ { 0x49989898, 0000000000 },
+ { 0xd34949d5, 0000000000 },
+ { 0x9dc90e11, 0000000000 },
+ { 0xce9b9b9b, 0000000000 },
+ { 0x000f0000, 0x00000016 },
+ { 0x352e232c, 0000000000 },
+ { 0x00000013, 0x00000004 },
+ { 0x000f0000, 0x00000016 },
+ { 0x352e272c, 0000000000 },
+ { 0x000f0001, 0x00000016 },
+ { 0x3239362f, 0000000000 },
+ { 0x000077ef, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x00000020, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00061000, 0x00000002 },
+ { 0x00000020, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00061000, 0x00000002 },
+ { 0x00000020, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00000016, 0x00000004 },
+ { 0x0003802a, 0x00000002 },
+ { 0x040067e0, 0x00000002 },
+ { 0x00000016, 0x00000004 },
+ { 0x000077e0, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x000037e1, 0x00000002 },
+ { 0x040067e1, 0x00000006 },
+ { 0x000077e0, 0x00000002 },
+ { 0x000077e1, 0x00000002 },
+ { 0x000077e1, 0x00000006 },
+ { 0xffffffff, 0000000000 },
+ { 0x10000000, 0000000000 },
+ { 0x0003802a, 0x00000002 },
+ { 0x040067e0, 0x00000006 },
+ { 0x00007675, 0x00000002 },
+ { 0x00007676, 0x00000002 },
+ { 0x00007677, 0x00000002 },
+ { 0x00007678, 0x00000006 },
+ { 0x0003802b, 0x00000002 },
+ { 0x04002676, 0x00000002 },
+ { 0x00007677, 0x00000002 },
+ { 0x00007678, 0x00000006 },
+ { 0x0000002e, 0x00000018 },
+ { 0x0000002e, 0x00000018 },
+ { 0000000000, 0x00000006 },
+ { 0x0000002f, 0x00000018 },
+ { 0x0000002f, 0x00000018 },
+ { 0000000000, 0x00000006 },
+ { 0x01605000, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x00098000, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x64c0603d, 0x00000004 },
+ { 0x00080000, 0x00000016 },
+ { 0000000000, 0000000000 },
+ { 0x0400251d, 0x00000002 },
+ { 0x00007580, 0x00000002 },
+ { 0x00067581, 0x00000002 },
+ { 0x04002580, 0x00000002 },
+ { 0x00067581, 0x00000002 },
+ { 0x00000046, 0x00000004 },
+ { 0x00005000, 0000000000 },
+ { 0x00061000, 0x00000002 },
+ { 0x0000750e, 0x00000002 },
+ { 0x00019000, 0x00000002 },
+ { 0x00011055, 0x00000014 },
+ { 0x00000055, 0x00000012 },
+ { 0x0400250f, 0x00000002 },
+ { 0x0000504a, 0x00000004 },
+ { 0x00007565, 0x00000002 },
+ { 0x00007566, 0x00000002 },
+ { 0x00000051, 0x00000004 },
+ { 0x01e655b4, 0x00000002 },
+ { 0x4401b0dc, 0x00000002 },
+ { 0x01c110dc, 0x00000002 },
+ { 0x2666705d, 0x00000018 },
+ { 0x040c2565, 0x00000002 },
+ { 0x0000005d, 0x00000018 },
+ { 0x04002564, 0x00000002 },
+ { 0x00007566, 0x00000002 },
+ { 0x00000054, 0x00000004 },
+ { 0x00401060, 0x00000008 },
+ { 0x00101000, 0x00000002 },
+ { 0x000d80ff, 0x00000002 },
+ { 0x00800063, 0x00000008 },
+ { 0x000f9000, 0x00000002 },
+ { 0x000e00ff, 0x00000002 },
+ { 0000000000, 0x00000006 },
+ { 0x00000080, 0x00000018 },
+ { 0x00000054, 0x00000004 },
+ { 0x00007576, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x00009000, 0x00000002 },
+ { 0x00041000, 0x00000002 },
+ { 0x0c00350e, 0x00000002 },
+ { 0x00049000, 0x00000002 },
+ { 0x00051000, 0x00000002 },
+ { 0x01e785f8, 0x00000002 },
+ { 0x00200000, 0x00000002 },
+ { 0x00600073, 0x0000000c },
+ { 0x00007563, 0x00000002 },
+ { 0x006075f0, 0x00000021 },
+ { 0x20007068, 0x00000004 },
+ { 0x00005068, 0x00000004 },
+ { 0x00007576, 0x00000002 },
+ { 0x00007577, 0x00000002 },
+ { 0x0000750e, 0x00000002 },
+ { 0x0000750f, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00600076, 0x0000000c },
+ { 0x006075f0, 0x00000021 },
+ { 0x000075f8, 0x00000002 },
+ { 0x00000076, 0x00000004 },
+ { 0x000a750e, 0x00000002 },
+ { 0x0020750f, 0x00000002 },
+ { 0x00600079, 0x00000004 },
+ { 0x00007570, 0x00000002 },
+ { 0x00007571, 0x00000002 },
+ { 0x00007572, 0x00000006 },
+ { 0x00005000, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00007568, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x00000084, 0x0000000c },
+ { 0x00058000, 0x00000002 },
+ { 0x0c607562, 0x00000002 },
+ { 0x00000086, 0x00000004 },
+ { 0x00600085, 0x00000004 },
+ { 0x400070dd, 0000000000 },
+ { 0x000380dd, 0x00000002 },
+ { 0x00000093, 0x0000001c },
+ { 0x00065095, 0x00000018 },
+ { 0x040025bb, 0x00000002 },
+ { 0x00061096, 0x00000018 },
+ { 0x040075bc, 0000000000 },
+ { 0x000075bb, 0x00000002 },
+ { 0x000075bc, 0000000000 },
+ { 0x00090000, 0x00000006 },
+ { 0x00090000, 0x00000002 },
+ { 0x000d8002, 0x00000006 },
+ { 0x00005000, 0x00000002 },
+ { 0x00007821, 0x00000002 },
+ { 0x00007800, 0000000000 },
+ { 0x00007821, 0x00000002 },
+ { 0x00007800, 0000000000 },
+ { 0x01665000, 0x00000002 },
+ { 0x000a0000, 0x00000002 },
+ { 0x000671cc, 0x00000002 },
+ { 0x0286f1cd, 0x00000002 },
+ { 0x000000a3, 0x00000010 },
+ { 0x21007000, 0000000000 },
+ { 0x000000aa, 0x0000001c },
+ { 0x00065000, 0x00000002 },
+ { 0x000a0000, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x000b0000, 0x00000002 },
+ { 0x38067000, 0x00000002 },
+ { 0x000a00a6, 0x00000004 },
+ { 0x20007000, 0000000000 },
+ { 0x01200000, 0x00000002 },
+ { 0x20077000, 0x00000002 },
+ { 0x01200000, 0x00000002 },
+ { 0x20007000, 0000000000 },
+ { 0x00061000, 0x00000002 },
+ { 0x0120751b, 0x00000002 },
+ { 0x8040750a, 0x00000002 },
+ { 0x8040750b, 0x00000002 },
+ { 0x00110000, 0x00000002 },
+ { 0x000380dd, 0x00000002 },
+ { 0x000000bd, 0x0000001c },
+ { 0x00061096, 0x00000018 },
+ { 0x844075bd, 0x00000002 },
+ { 0x00061095, 0x00000018 },
+ { 0x840075bb, 0x00000002 },
+ { 0x00061096, 0x00000018 },
+ { 0x844075bc, 0x00000002 },
+ { 0x000000c0, 0x00000004 },
+ { 0x804075bd, 0x00000002 },
+ { 0x800075bb, 0x00000002 },
+ { 0x804075bc, 0x00000002 },
+ { 0x00108000, 0x00000002 },
+ { 0x01400000, 0x00000002 },
+ { 0x006000c4, 0x0000000c },
+ { 0x20c07000, 0x00000020 },
+ { 0x000000c6, 0x00000012 },
+ { 0x00800000, 0x00000006 },
+ { 0x0080751d, 0x00000006 },
+ { 0x000025bb, 0x00000002 },
+ { 0x000040c0, 0x00000004 },
+ { 0x0000775c, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00661000, 0x00000002 },
+ { 0x0460275d, 0x00000020 },
+ { 0x00004000, 0000000000 },
+ { 0x00007999, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00661000, 0x00000002 },
+ { 0x0460299b, 0x00000020 },
+ { 0x00004000, 0000000000 },
+ { 0x01e00830, 0x00000002 },
+ { 0x21007000, 0000000000 },
+ { 0x00005000, 0x00000002 },
+ { 0x00038042, 0x00000002 },
+ { 0x040025e0, 0x00000002 },
+ { 0x000075e1, 0000000000 },
+ { 0x00000001, 0000000000 },
+ { 0x000380d9, 0x00000002 },
+ { 0x04007394, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+};
+
+
+static u32 radeon_cp_microcode[][2] = {
+ { 0x21007000, 0000000000 },
+ { 0x20007000, 0000000000 },
+ { 0x000000b4, 0x00000004 },
+ { 0x000000b8, 0x00000004 },
+ { 0x6f5b4d4c, 0000000000 },
+ { 0x4c4c427f, 0000000000 },
+ { 0x5b568a92, 0000000000 },
+ { 0x4ca09c6d, 0000000000 },
+ { 0xad4c4c4c, 0000000000 },
+ { 0x4ce1af3d, 0000000000 },
+ { 0xd8afafaf, 0000000000 },
+ { 0xd64c4cdc, 0000000000 },
+ { 0x4cd10d10, 0000000000 },
+ { 0x000f0000, 0x00000016 },
+ { 0x362f242d, 0000000000 },
+ { 0x00000012, 0x00000004 },
+ { 0x000f0000, 0x00000016 },
+ { 0x362f282d, 0000000000 },
+ { 0x000380e7, 0x00000002 },
+ { 0x04002c97, 0x00000002 },
+ { 0x000f0001, 0x00000016 },
+ { 0x333a3730, 0000000000 },
+ { 0x000077ef, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x00000021, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00061000, 0x00000002 },
+ { 0x00000021, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00061000, 0x00000002 },
+ { 0x00000021, 0x0000001a },
+ { 0x00004000, 0x0000001e },
+ { 0x00000017, 0x00000004 },
+ { 0x0003802b, 0x00000002 },
+ { 0x040067e0, 0x00000002 },
+ { 0x00000017, 0x00000004 },
+ { 0x000077e0, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x000037e1, 0x00000002 },
+ { 0x040067e1, 0x00000006 },
+ { 0x000077e0, 0x00000002 },
+ { 0x000077e1, 0x00000002 },
+ { 0x000077e1, 0x00000006 },
+ { 0xffffffff, 0000000000 },
+ { 0x10000000, 0000000000 },
+ { 0x0003802b, 0x00000002 },
+ { 0x040067e0, 0x00000006 },
+ { 0x00007675, 0x00000002 },
+ { 0x00007676, 0x00000002 },
+ { 0x00007677, 0x00000002 },
+ { 0x00007678, 0x00000006 },
+ { 0x0003802c, 0x00000002 },
+ { 0x04002676, 0x00000002 },
+ { 0x00007677, 0x00000002 },
+ { 0x00007678, 0x00000006 },
+ { 0x0000002f, 0x00000018 },
+ { 0x0000002f, 0x00000018 },
+ { 0000000000, 0x00000006 },
+ { 0x00000030, 0x00000018 },
+ { 0x00000030, 0x00000018 },
+ { 0000000000, 0x00000006 },
+ { 0x01605000, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x00098000, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x64c0603e, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00080000, 0x00000016 },
+ { 0000000000, 0000000000 },
+ { 0x0400251d, 0x00000002 },
+ { 0x00007580, 0x00000002 },
+ { 0x00067581, 0x00000002 },
+ { 0x04002580, 0x00000002 },
+ { 0x00067581, 0x00000002 },
+ { 0x00000049, 0x00000004 },
+ { 0x00005000, 0000000000 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x0000750e, 0x00000002 },
+ { 0x00019000, 0x00000002 },
+ { 0x00011055, 0x00000014 },
+ { 0x00000055, 0x00000012 },
+ { 0x0400250f, 0x00000002 },
+ { 0x0000504f, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00007565, 0x00000002 },
+ { 0x00007566, 0x00000002 },
+ { 0x00000058, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x01e655b4, 0x00000002 },
+ { 0x4401b0e4, 0x00000002 },
+ { 0x01c110e4, 0x00000002 },
+ { 0x26667066, 0x00000018 },
+ { 0x040c2565, 0x00000002 },
+ { 0x00000066, 0x00000018 },
+ { 0x04002564, 0x00000002 },
+ { 0x00007566, 0x00000002 },
+ { 0x0000005d, 0x00000004 },
+ { 0x00401069, 0x00000008 },
+ { 0x00101000, 0x00000002 },
+ { 0x000d80ff, 0x00000002 },
+ { 0x0080006c, 0x00000008 },
+ { 0x000f9000, 0x00000002 },
+ { 0x000e00ff, 0x00000002 },
+ { 0000000000, 0x00000006 },
+ { 0x0000008f, 0x00000018 },
+ { 0x0000005b, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00007576, 0x00000002 },
+ { 0x00065000, 0x00000002 },
+ { 0x00009000, 0x00000002 },
+ { 0x00041000, 0x00000002 },
+ { 0x0c00350e, 0x00000002 },
+ { 0x00049000, 0x00000002 },
+ { 0x00051000, 0x00000002 },
+ { 0x01e785f8, 0x00000002 },
+ { 0x00200000, 0x00000002 },
+ { 0x0060007e, 0x0000000c },
+ { 0x00007563, 0x00000002 },
+ { 0x006075f0, 0x00000021 },
+ { 0x20007073, 0x00000004 },
+ { 0x00005073, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00007576, 0x00000002 },
+ { 0x00007577, 0x00000002 },
+ { 0x0000750e, 0x00000002 },
+ { 0x0000750f, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00600083, 0x0000000c },
+ { 0x006075f0, 0x00000021 },
+ { 0x000075f8, 0x00000002 },
+ { 0x00000083, 0x00000004 },
+ { 0x000a750e, 0x00000002 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x0020750f, 0x00000002 },
+ { 0x00600086, 0x00000004 },
+ { 0x00007570, 0x00000002 },
+ { 0x00007571, 0x00000002 },
+ { 0x00007572, 0x00000006 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00005000, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00007568, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x00000095, 0x0000000c },
+ { 0x00058000, 0x00000002 },
+ { 0x0c607562, 0x00000002 },
+ { 0x00000097, 0x00000004 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x00600096, 0x00000004 },
+ { 0x400070e5, 0000000000 },
+ { 0x000380e6, 0x00000002 },
+ { 0x040025c5, 0x00000002 },
+ { 0x000380e5, 0x00000002 },
+ { 0x000000a8, 0x0000001c },
+ { 0x000650aa, 0x00000018 },
+ { 0x040025bb, 0x00000002 },
+ { 0x000610ab, 0x00000018 },
+ { 0x040075bc, 0000000000 },
+ { 0x000075bb, 0x00000002 },
+ { 0x000075bc, 0000000000 },
+ { 0x00090000, 0x00000006 },
+ { 0x00090000, 0x00000002 },
+ { 0x000d8002, 0x00000006 },
+ { 0x00007832, 0x00000002 },
+ { 0x00005000, 0x00000002 },
+ { 0x000380e7, 0x00000002 },
+ { 0x04002c97, 0x00000002 },
+ { 0x00007820, 0x00000002 },
+ { 0x00007821, 0x00000002 },
+ { 0x00007800, 0000000000 },
+ { 0x01200000, 0x00000002 },
+ { 0x20077000, 0x00000002 },
+ { 0x01200000, 0x00000002 },
+ { 0x20007000, 0x00000002 },
+ { 0x00061000, 0x00000002 },
+ { 0x0120751b, 0x00000002 },
+ { 0x8040750a, 0x00000002 },
+ { 0x8040750b, 0x00000002 },
+ { 0x00110000, 0x00000002 },
+ { 0x000380e5, 0x00000002 },
+ { 0x000000c6, 0x0000001c },
+ { 0x000610ab, 0x00000018 },
+ { 0x844075bd, 0x00000002 },
+ { 0x000610aa, 0x00000018 },
+ { 0x840075bb, 0x00000002 },
+ { 0x000610ab, 0x00000018 },
+ { 0x844075bc, 0x00000002 },
+ { 0x000000c9, 0x00000004 },
+ { 0x804075bd, 0x00000002 },
+ { 0x800075bb, 0x00000002 },
+ { 0x804075bc, 0x00000002 },
+ { 0x00108000, 0x00000002 },
+ { 0x01400000, 0x00000002 },
+ { 0x006000cd, 0x0000000c },
+ { 0x20c07000, 0x00000020 },
+ { 0x000000cf, 0x00000012 },
+ { 0x00800000, 0x00000006 },
+ { 0x0080751d, 0x00000006 },
+ { 0000000000, 0000000000 },
+ { 0x0000775c, 0x00000002 },
+ { 0x00a05000, 0x00000002 },
+ { 0x00661000, 0x00000002 },
+ { 0x0460275d, 0x00000020 },
+ { 0x00004000, 0000000000 },
+ { 0x01e00830, 0x00000002 },
+ { 0x21007000, 0000000000 },
+ { 0x6464614d, 0000000000 },
+ { 0x69687420, 0000000000 },
+ { 0x00000073, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0x00005000, 0x00000002 },
+ { 0x000380d0, 0x00000002 },
+ { 0x040025e0, 0x00000002 },
+ { 0x000075e1, 0000000000 },
+ { 0x00000001, 0000000000 },
+ { 0x000380e0, 0x00000002 },
+ { 0x04002394, 0x00000002 },
+ { 0x00005000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0x00000008, 0000000000 },
+ { 0x00000004, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+};
+
+static u32 R300_cp_microcode[][2] = {
+ { 0x4200e000, 0000000000 },
+ { 0x4000e000, 0000000000 },
+ { 0x000000af, 0x00000008 },
+ { 0x000000b3, 0x00000008 },
+ { 0x6c5a504f, 0000000000 },
+ { 0x4f4f497a, 0000000000 },
+ { 0x5a578288, 0000000000 },
+ { 0x4f91906a, 0000000000 },
+ { 0x4f4f4f4f, 0000000000 },
+ { 0x4fe24f44, 0000000000 },
+ { 0x4f9c9c9c, 0000000000 },
+ { 0xdc4f4fde, 0000000000 },
+ { 0xa1cd4f4f, 0000000000 },
+ { 0xd29d9d9d, 0000000000 },
+ { 0x4f0f9fd7, 0000000000 },
+ { 0x000ca000, 0x00000004 },
+ { 0x000d0012, 0x00000038 },
+ { 0x0000e8b4, 0x00000004 },
+ { 0x000d0014, 0x00000038 },
+ { 0x0000e8b6, 0x00000004 },
+ { 0x000d0016, 0x00000038 },
+ { 0x0000e854, 0x00000004 },
+ { 0x000d0018, 0x00000038 },
+ { 0x0000e855, 0x00000004 },
+ { 0x000d001a, 0x00000038 },
+ { 0x0000e856, 0x00000004 },
+ { 0x000d001c, 0x00000038 },
+ { 0x0000e857, 0x00000004 },
+ { 0x000d001e, 0x00000038 },
+ { 0x0000e824, 0x00000004 },
+ { 0x000d0020, 0x00000038 },
+ { 0x0000e825, 0x00000004 },
+ { 0x000d0022, 0x00000038 },
+ { 0x0000e830, 0x00000004 },
+ { 0x000d0024, 0x00000038 },
+ { 0x0000f0c0, 0x00000004 },
+ { 0x000d0026, 0x00000038 },
+ { 0x0000f0c1, 0x00000004 },
+ { 0x000d0028, 0x00000038 },
+ { 0x0000f041, 0x00000004 },
+ { 0x000d002a, 0x00000038 },
+ { 0x0000f184, 0x00000004 },
+ { 0x000d002c, 0x00000038 },
+ { 0x0000f185, 0x00000004 },
+ { 0x000d002e, 0x00000038 },
+ { 0x0000f186, 0x00000004 },
+ { 0x000d0030, 0x00000038 },
+ { 0x0000f187, 0x00000004 },
+ { 0x000d0032, 0x00000038 },
+ { 0x0000f180, 0x00000004 },
+ { 0x000d0034, 0x00000038 },
+ { 0x0000f393, 0x00000004 },
+ { 0x000d0036, 0x00000038 },
+ { 0x0000f38a, 0x00000004 },
+ { 0x000d0038, 0x00000038 },
+ { 0x0000f38e, 0x00000004 },
+ { 0x0000e821, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x00000043, 0x00000018 },
+ { 0x00cce800, 0x00000004 },
+ { 0x001b0001, 0x00000004 },
+ { 0x08004800, 0x00000004 },
+ { 0x001b0001, 0x00000004 },
+ { 0x08004800, 0x00000004 },
+ { 0x001b0001, 0x00000004 },
+ { 0x08004800, 0x00000004 },
+ { 0x0000003a, 0x00000008 },
+ { 0x0000a000, 0000000000 },
+ { 0x02c0a000, 0x00000004 },
+ { 0x000ca000, 0x00000004 },
+ { 0x00130000, 0x00000004 },
+ { 0x000c2000, 0x00000004 },
+ { 0xc980c045, 0x00000008 },
+ { 0x2000451d, 0x00000004 },
+ { 0x0000e580, 0x00000004 },
+ { 0x000ce581, 0x00000004 },
+ { 0x08004580, 0x00000004 },
+ { 0x000ce581, 0x00000004 },
+ { 0x0000004c, 0x00000008 },
+ { 0x0000a000, 0000000000 },
+ { 0x000c2000, 0x00000004 },
+ { 0x0000e50e, 0x00000004 },
+ { 0x00032000, 0x00000004 },
+ { 0x00022056, 0x00000028 },
+ { 0x00000056, 0x00000024 },
+ { 0x0800450f, 0x00000004 },
+ { 0x0000a050, 0x00000008 },
+ { 0x0000e565, 0x00000004 },
+ { 0x0000e566, 0x00000004 },
+ { 0x00000057, 0x00000008 },
+ { 0x03cca5b4, 0x00000004 },
+ { 0x05432000, 0x00000004 },
+ { 0x00022000, 0x00000004 },
+ { 0x4ccce063, 0x00000030 },
+ { 0x08274565, 0x00000004 },
+ { 0x00000063, 0x00000030 },
+ { 0x08004564, 0x00000004 },
+ { 0x0000e566, 0x00000004 },
+ { 0x0000005a, 0x00000008 },
+ { 0x00802066, 0x00000010 },
+ { 0x00202000, 0x00000004 },
+ { 0x001b00ff, 0x00000004 },
+ { 0x01000069, 0x00000010 },
+ { 0x001f2000, 0x00000004 },
+ { 0x001c00ff, 0x00000004 },
+ { 0000000000, 0x0000000c },
+ { 0x00000085, 0x00000030 },
+ { 0x0000005a, 0x00000008 },
+ { 0x0000e576, 0x00000004 },
+ { 0x000ca000, 0x00000004 },
+ { 0x00012000, 0x00000004 },
+ { 0x00082000, 0x00000004 },
+ { 0x1800650e, 0x00000004 },
+ { 0x00092000, 0x00000004 },
+ { 0x000a2000, 0x00000004 },
+ { 0x000f0000, 0x00000004 },
+ { 0x00400000, 0x00000004 },
+ { 0x00000079, 0x00000018 },
+ { 0x0000e563, 0x00000004 },
+ { 0x00c0e5f9, 0x000000c2 },
+ { 0x0000006e, 0x00000008 },
+ { 0x0000a06e, 0x00000008 },
+ { 0x0000e576, 0x00000004 },
+ { 0x0000e577, 0x00000004 },
+ { 0x0000e50e, 0x00000004 },
+ { 0x0000e50f, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x0000007c, 0x00000018 },
+ { 0x00c0e5f9, 0x000000c2 },
+ { 0x0000007c, 0x00000008 },
+ { 0x0014e50e, 0x00000004 },
+ { 0x0040e50f, 0x00000004 },
+ { 0x00c0007f, 0x00000008 },
+ { 0x0000e570, 0x00000004 },
+ { 0x0000e571, 0x00000004 },
+ { 0x0000e572, 0x0000000c },
+ { 0x0000a000, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x0000e568, 0x00000004 },
+ { 0x000c2000, 0x00000004 },
+ { 0x00000089, 0x00000018 },
+ { 0x000b0000, 0x00000004 },
+ { 0x18c0e562, 0x00000004 },
+ { 0x0000008b, 0x00000008 },
+ { 0x00c0008a, 0x00000008 },
+ { 0x000700e4, 0x00000004 },
+ { 0x00000097, 0x00000038 },
+ { 0x000ca099, 0x00000030 },
+ { 0x080045bb, 0x00000004 },
+ { 0x000c209a, 0x00000030 },
+ { 0x0800e5bc, 0000000000 },
+ { 0x0000e5bb, 0x00000004 },
+ { 0x0000e5bc, 0000000000 },
+ { 0x00120000, 0x0000000c },
+ { 0x00120000, 0x00000004 },
+ { 0x001b0002, 0x0000000c },
+ { 0x0000a000, 0x00000004 },
+ { 0x0000e821, 0x00000004 },
+ { 0x0000e800, 0000000000 },
+ { 0x0000e821, 0x00000004 },
+ { 0x0000e82e, 0000000000 },
+ { 0x02cca000, 0x00000004 },
+ { 0x00140000, 0x00000004 },
+ { 0x000ce1cc, 0x00000004 },
+ { 0x050de1cd, 0x00000004 },
+ { 0x000000a7, 0x00000020 },
+ { 0x4200e000, 0000000000 },
+ { 0x000000ae, 0x00000038 },
+ { 0x000ca000, 0x00000004 },
+ { 0x00140000, 0x00000004 },
+ { 0x000c2000, 0x00000004 },
+ { 0x00160000, 0x00000004 },
+ { 0x700ce000, 0x00000004 },
+ { 0x001400aa, 0x00000008 },
+ { 0x4000e000, 0000000000 },
+ { 0x02400000, 0x00000004 },
+ { 0x400ee000, 0x00000004 },
+ { 0x02400000, 0x00000004 },
+ { 0x4000e000, 0000000000 },
+ { 0x000c2000, 0x00000004 },
+ { 0x0240e51b, 0x00000004 },
+ { 0x0080e50a, 0x00000005 },
+ { 0x0080e50b, 0x00000005 },
+ { 0x00220000, 0x00000004 },
+ { 0x000700e4, 0x00000004 },
+ { 0x000000c1, 0x00000038 },
+ { 0x000c209a, 0x00000030 },
+ { 0x0880e5bd, 0x00000005 },
+ { 0x000c2099, 0x00000030 },
+ { 0x0800e5bb, 0x00000005 },
+ { 0x000c209a, 0x00000030 },
+ { 0x0880e5bc, 0x00000005 },
+ { 0x000000c4, 0x00000008 },
+ { 0x0080e5bd, 0x00000005 },
+ { 0x0000e5bb, 0x00000005 },
+ { 0x0080e5bc, 0x00000005 },
+ { 0x00210000, 0x00000004 },
+ { 0x02800000, 0x00000004 },
+ { 0x00c000c8, 0x00000018 },
+ { 0x4180e000, 0x00000040 },
+ { 0x000000ca, 0x00000024 },
+ { 0x01000000, 0x0000000c },
+ { 0x0100e51d, 0x0000000c },
+ { 0x000045bb, 0x00000004 },
+ { 0x000080c4, 0x00000008 },
+ { 0x0000f3ce, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x00cc2000, 0x00000004 },
+ { 0x08c053cf, 0x00000040 },
+ { 0x00008000, 0000000000 },
+ { 0x0000f3d2, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x00cc2000, 0x00000004 },
+ { 0x08c053d3, 0x00000040 },
+ { 0x00008000, 0000000000 },
+ { 0x0000f39d, 0x00000004 },
+ { 0x0140a000, 0x00000004 },
+ { 0x00cc2000, 0x00000004 },
+ { 0x08c0539e, 0x00000040 },
+ { 0x00008000, 0000000000 },
+ { 0x03c00830, 0x00000004 },
+ { 0x4200e000, 0000000000 },
+ { 0x0000a000, 0x00000004 },
+ { 0x200045e0, 0x00000004 },
+ { 0x0000e5e1, 0000000000 },
+ { 0x00000001, 0000000000 },
+ { 0x000700e1, 0x00000004 },
+ { 0x0800e394, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+ { 0000000000, 0000000000 },
+};
+
+static int RADEON_READ_PLL(drm_device_t *dev, int addr)
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+
+ RADEON_WRITE8(RADEON_CLOCK_CNTL_INDEX, addr & 0x1f);
+ return RADEON_READ(RADEON_CLOCK_CNTL_DATA);
+}
+
+#if RADEON_FIFO_DEBUG
+static void radeon_status( drm_radeon_private_t *dev_priv )
+{
+ printk( "%s:\n", __FUNCTION__ );
+ printk( "RBBM_STATUS = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_RBBM_STATUS ) );
+ printk( "CP_RB_RTPR = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_CP_RB_RPTR ) );
+ printk( "CP_RB_WTPR = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_CP_RB_WPTR ) );
+ printk( "AIC_CNTL = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_AIC_CNTL ) );
+ printk( "AIC_STAT = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_AIC_STAT ) );
+ printk( "AIC_PT_BASE = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_AIC_PT_BASE ) );
+ printk( "TLB_ADDR = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_AIC_TLB_ADDR ) );
+ printk( "TLB_DATA = 0x%08x\n",
+ (unsigned int)RADEON_READ( RADEON_AIC_TLB_DATA ) );
+}
+#endif
+
+
+/* ================================================================
+ * Engine, FIFO control
+ */
+
+static int radeon_do_pixcache_flush( drm_radeon_private_t *dev_priv )
+{
+ u32 tmp;
+ int i;
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ tmp = RADEON_READ( RADEON_RB2D_DSTCACHE_CTLSTAT );
+ tmp |= RADEON_RB2D_DC_FLUSH_ALL;
+ RADEON_WRITE( RADEON_RB2D_DSTCACHE_CTLSTAT, tmp );
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ if ( !(RADEON_READ( RADEON_RB2D_DSTCACHE_CTLSTAT )
+ & RADEON_RB2D_DC_BUSY) ) {
+ return 0;
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if RADEON_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+ radeon_status( dev_priv );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int radeon_do_wait_for_fifo( drm_radeon_private_t *dev_priv,
+ int entries )
+{
+ int i;
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ int slots = ( RADEON_READ( RADEON_RBBM_STATUS )
+ & RADEON_RBBM_FIFOCNT_MASK );
+ if ( slots >= entries ) return 0;
+ DRM_UDELAY( 1 );
+ }
+
+#if RADEON_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+ radeon_status( dev_priv );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int radeon_do_wait_for_idle( drm_radeon_private_t *dev_priv )
+{
+ int i, ret;
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ ret = radeon_do_wait_for_fifo( dev_priv, 64 );
+ if ( ret ) return ret;
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ if ( !(RADEON_READ( RADEON_RBBM_STATUS )
+ & RADEON_RBBM_ACTIVE) ) {
+ radeon_do_pixcache_flush( dev_priv );
+ return 0;
+ }
+ DRM_UDELAY( 1 );
+ }
+
+#if RADEON_FIFO_DEBUG
+ DRM_ERROR( "failed!\n" );
+ radeon_status( dev_priv );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+
+/* ================================================================
+ * CP control, initialization
+ */
+
+/* Load the microcode for the CP */
+static void radeon_cp_load_microcode( drm_radeon_private_t *dev_priv )
+{
+ int i;
+ DRM_DEBUG( "\n" );
+
+ radeon_do_wait_for_idle( dev_priv );
+
+ RADEON_WRITE( RADEON_CP_ME_RAM_ADDR, 0 );
+
+ if (dev_priv->microcode_version==UCODE_R200) {
+ DRM_INFO("Loading R200 Microcode\n");
+ for ( i = 0 ; i < 256 ; i++ )
+ {
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+ R200_cp_microcode[i][1] );
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+ R200_cp_microcode[i][0] );
+ }
+ } else if (dev_priv->microcode_version==UCODE_R300) {
+ DRM_INFO("Loading R300 Microcode\n");
+ for ( i = 0 ; i < 256 ; i++ )
+ {
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+ R300_cp_microcode[i][1] );
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+ R300_cp_microcode[i][0] );
+ }
+ } else {
+ for ( i = 0 ; i < 256 ; i++ ) {
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+ radeon_cp_microcode[i][1] );
+ RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+ radeon_cp_microcode[i][0] );
+ }
+ }
+}
+
+/* Flush any pending commands to the CP. This should only be used just
+ * prior to a wait for idle, as it informs the engine that the command
+ * stream is ending.
+ */
+static void radeon_do_cp_flush( drm_radeon_private_t *dev_priv )
+{
+ DRM_DEBUG( "\n" );
+#if 0
+ u32 tmp;
+
+ tmp = RADEON_READ( RADEON_CP_RB_WPTR ) | (1 << 31);
+ RADEON_WRITE( RADEON_CP_RB_WPTR, tmp );
+#endif
+}
+
+/* Wait for the CP to go idle.
+ */
+int radeon_do_cp_idle( drm_radeon_private_t *dev_priv )
+{
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ BEGIN_RING( 6 );
+
+ RADEON_PURGE_CACHE();
+ RADEON_PURGE_ZCACHE();
+ RADEON_WAIT_UNTIL_IDLE();
+
+ ADVANCE_RING();
+ COMMIT_RING();
+
+ return radeon_do_wait_for_idle( dev_priv );
+}
+
+/* Start the Command Processor.
+ */
+static void radeon_do_cp_start( drm_radeon_private_t *dev_priv )
+{
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ radeon_do_wait_for_idle( dev_priv );
+
+ RADEON_WRITE( RADEON_CP_CSQ_CNTL, dev_priv->cp_mode );
+
+ dev_priv->cp_running = 1;
+
+ BEGIN_RING( 6 );
+
+ RADEON_PURGE_CACHE();
+ RADEON_PURGE_ZCACHE();
+ RADEON_WAIT_UNTIL_IDLE();
+
+ ADVANCE_RING();
+ COMMIT_RING();
+}
+
+/* Reset the Command Processor. This will not flush any pending
+ * commands, so you must wait for the CP command stream to complete
+ * before calling this routine.
+ */
+static void radeon_do_cp_reset( drm_radeon_private_t *dev_priv )
+{
+ u32 cur_read_ptr;
+ DRM_DEBUG( "\n" );
+
+ cur_read_ptr = RADEON_READ( RADEON_CP_RB_RPTR );
+ RADEON_WRITE( RADEON_CP_RB_WPTR, cur_read_ptr );
+ SET_RING_HEAD( dev_priv, cur_read_ptr );
+ dev_priv->ring.tail = cur_read_ptr;
+}
+
+/* Stop the Command Processor. This will not flush any pending
+ * commands, so you must flush the command stream and wait for the CP
+ * to go idle before calling this routine.
+ */
+static void radeon_do_cp_stop( drm_radeon_private_t *dev_priv )
+{
+ DRM_DEBUG( "\n" );
+
+ RADEON_WRITE( RADEON_CP_CSQ_CNTL, RADEON_CSQ_PRIDIS_INDDIS );
+
+ dev_priv->cp_running = 0;
+}
+
+/* Reset the engine. This will stop the CP if it is running.
+ */
+static int radeon_do_engine_reset( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ u32 clock_cntl_index, mclk_cntl, rbbm_soft_reset;
+ DRM_DEBUG( "\n" );
+
+ radeon_do_pixcache_flush( dev_priv );
+
+ clock_cntl_index = RADEON_READ( RADEON_CLOCK_CNTL_INDEX );
+ mclk_cntl = RADEON_READ_PLL( dev, RADEON_MCLK_CNTL );
+
+ RADEON_WRITE_PLL( RADEON_MCLK_CNTL, ( mclk_cntl |
+ RADEON_FORCEON_MCLKA |
+ RADEON_FORCEON_MCLKB |
+ RADEON_FORCEON_YCLKA |
+ RADEON_FORCEON_YCLKB |
+ RADEON_FORCEON_MC |
+ RADEON_FORCEON_AIC ) );
+
+ rbbm_soft_reset = RADEON_READ( RADEON_RBBM_SOFT_RESET );
+
+ RADEON_WRITE( RADEON_RBBM_SOFT_RESET, ( rbbm_soft_reset |
+ RADEON_SOFT_RESET_CP |
+ RADEON_SOFT_RESET_HI |
+ RADEON_SOFT_RESET_SE |
+ RADEON_SOFT_RESET_RE |
+ RADEON_SOFT_RESET_PP |
+ RADEON_SOFT_RESET_E2 |
+ RADEON_SOFT_RESET_RB ) );
+ RADEON_READ( RADEON_RBBM_SOFT_RESET );
+ RADEON_WRITE( RADEON_RBBM_SOFT_RESET, ( rbbm_soft_reset &
+ ~( RADEON_SOFT_RESET_CP |
+ RADEON_SOFT_RESET_HI |
+ RADEON_SOFT_RESET_SE |
+ RADEON_SOFT_RESET_RE |
+ RADEON_SOFT_RESET_PP |
+ RADEON_SOFT_RESET_E2 |
+ RADEON_SOFT_RESET_RB ) ) );
+ RADEON_READ( RADEON_RBBM_SOFT_RESET );
+
+
+ RADEON_WRITE_PLL( RADEON_MCLK_CNTL, mclk_cntl );
+ RADEON_WRITE( RADEON_CLOCK_CNTL_INDEX, clock_cntl_index );
+ RADEON_WRITE( RADEON_RBBM_SOFT_RESET, rbbm_soft_reset );
+
+ /* Reset the CP ring */
+ radeon_do_cp_reset( dev_priv );
+
+ /* The CP is no longer running after an engine reset */
+ dev_priv->cp_running = 0;
+
+ /* Reset any pending vertex, indirect buffers */
+ radeon_freelist_reset( dev );
+
+ return 0;
+}
+
+static void radeon_cp_init_ring_buffer( drm_device_t *dev,
+ drm_radeon_private_t *dev_priv )
+{
+ u32 ring_start, cur_read_ptr;
+ u32 tmp;
+
+ /* Initialize the memory controller */
+ RADEON_WRITE( RADEON_MC_FB_LOCATION,
+ ( ( dev_priv->gart_vm_start - 1 ) & 0xffff0000 )
+ | ( dev_priv->fb_location >> 16 ) );
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ RADEON_WRITE( RADEON_MC_AGP_LOCATION,
+ (((dev_priv->gart_vm_start - 1 +
+ dev_priv->gart_size) & 0xffff0000) |
+ (dev_priv->gart_vm_start >> 16)) );
+
+ ring_start = (dev_priv->cp_ring->offset
+ - dev->agp->base
+ + dev_priv->gart_vm_start);
+ } else
+#endif
+ ring_start = (dev_priv->cp_ring->offset
+ - dev->sg->handle
+ + dev_priv->gart_vm_start);
+
+ RADEON_WRITE( RADEON_CP_RB_BASE, ring_start );
+
+ /* Set the write pointer delay */
+ RADEON_WRITE( RADEON_CP_RB_WPTR_DELAY, 0 );
+
+ /* Initialize the ring buffer's read and write pointers */
+ cur_read_ptr = RADEON_READ( RADEON_CP_RB_RPTR );
+ RADEON_WRITE( RADEON_CP_RB_WPTR, cur_read_ptr );
+ SET_RING_HEAD( dev_priv, cur_read_ptr );
+ dev_priv->ring.tail = cur_read_ptr;
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ RADEON_WRITE( RADEON_CP_RB_RPTR_ADDR,
+ dev_priv->ring_rptr->offset
+ - dev->agp->base
+ + dev_priv->gart_vm_start);
+ } else
+#endif
+ {
+ drm_sg_mem_t *entry = dev->sg;
+ unsigned long tmp_ofs, page_ofs;
+
+ tmp_ofs = dev_priv->ring_rptr->offset - dev->sg->handle;
+ page_ofs = tmp_ofs >> PAGE_SHIFT;
+
+ RADEON_WRITE( RADEON_CP_RB_RPTR_ADDR,
+ entry->busaddr[page_ofs]);
+ DRM_DEBUG( "ring rptr: offset=0x%08lx handle=0x%08lx\n",
+ (unsigned long) entry->busaddr[page_ofs],
+ entry->handle + tmp_ofs );
+ }
+
+ /* Initialize the scratch register pointer. This will cause
+ * the scratch register values to be written out to memory
+ * whenever they are updated.
+ *
+ * We simply put this behind the ring read pointer, this works
+ * with PCI GART as well as (whatever kind of) AGP GART
+ */
+ RADEON_WRITE( RADEON_SCRATCH_ADDR, RADEON_READ( RADEON_CP_RB_RPTR_ADDR )
+ + RADEON_SCRATCH_REG_OFFSET );
+
+ dev_priv->scratch = ((__volatile__ u32 *)
+ dev_priv->ring_rptr->handle +
+ (RADEON_SCRATCH_REG_OFFSET / sizeof(u32)));
+
+ RADEON_WRITE( RADEON_SCRATCH_UMSK, 0x7 );
+
+ /* Writeback doesn't seem to work everywhere, test it first */
+ DRM_WRITE32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(1), 0 );
+ RADEON_WRITE( RADEON_SCRATCH_REG1, 0xdeadbeef );
+
+ for ( tmp = 0 ; tmp < dev_priv->usec_timeout ; tmp++ ) {
+ if ( DRM_READ32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(1) ) == 0xdeadbeef )
+ break;
+ DRM_UDELAY( 1 );
+ }
+
+ if ( tmp < dev_priv->usec_timeout ) {
+ dev_priv->writeback_works = 1;
+ DRM_DEBUG( "writeback test succeeded, tmp=%d\n", tmp );
+ } else {
+ dev_priv->writeback_works = 0;
+ DRM_DEBUG( "writeback test failed\n" );
+ }
+
+ dev_priv->sarea_priv->last_frame = dev_priv->scratch[0] = 0;
+ RADEON_WRITE( RADEON_LAST_FRAME_REG,
+ dev_priv->sarea_priv->last_frame );
+
+ dev_priv->sarea_priv->last_dispatch = dev_priv->scratch[1] = 0;
+ RADEON_WRITE( RADEON_LAST_DISPATCH_REG,
+ dev_priv->sarea_priv->last_dispatch );
+
+ dev_priv->sarea_priv->last_clear = dev_priv->scratch[2] = 0;
+ RADEON_WRITE( RADEON_LAST_CLEAR_REG,
+ dev_priv->sarea_priv->last_clear );
+
+ /* Set ring buffer size */
+#ifdef __BIG_ENDIAN
+ RADEON_WRITE( RADEON_CP_RB_CNTL, dev_priv->ring.size_l2qw | RADEON_BUF_SWAP_32BIT );
+#else
+ RADEON_WRITE( RADEON_CP_RB_CNTL, dev_priv->ring.size_l2qw );
+#endif
+
+ radeon_do_wait_for_idle( dev_priv );
+
+ /* Turn on bus mastering */
+ tmp = RADEON_READ( RADEON_BUS_CNTL ) & ~RADEON_BUS_MASTER_DIS;
+ RADEON_WRITE( RADEON_BUS_CNTL, tmp );
+
+ /* Sync everything up */
+ RADEON_WRITE( RADEON_ISYNC_CNTL,
+ (RADEON_ISYNC_ANY2D_IDLE3D |
+ RADEON_ISYNC_ANY3D_IDLE2D |
+ RADEON_ISYNC_WAIT_IDLEGUI |
+ RADEON_ISYNC_CPSCRATCH_IDLEGUI) );
+}
+
+/* Enable or disable PCI GART on the chip */
+static void radeon_set_pcigart( drm_radeon_private_t *dev_priv, int on )
+{
+ u32 tmp = RADEON_READ( RADEON_AIC_CNTL );
+
+ if ( on ) {
+ RADEON_WRITE( RADEON_AIC_CNTL, tmp | RADEON_PCIGART_TRANSLATE_EN );
+
+ /* set PCI GART page-table base address
+ */
+ RADEON_WRITE( RADEON_AIC_PT_BASE, dev_priv->bus_pci_gart );
+
+ /* set address range for PCI address translate
+ */
+ RADEON_WRITE( RADEON_AIC_LO_ADDR, dev_priv->gart_vm_start );
+ RADEON_WRITE( RADEON_AIC_HI_ADDR, dev_priv->gart_vm_start
+ + dev_priv->gart_size - 1);
+
+ /* Turn off AGP aperture -- is this required for PCI GART?
+ */
+ RADEON_WRITE( RADEON_MC_AGP_LOCATION, 0xffffffc0 ); /* ?? */
+ RADEON_WRITE( RADEON_AGP_COMMAND, 0 ); /* clear AGP_COMMAND */
+ } else {
+ RADEON_WRITE( RADEON_AIC_CNTL, tmp & ~RADEON_PCIGART_TRANSLATE_EN );
+ }
+}
+
+static int radeon_do_init_cp( drm_device_t *dev, drm_radeon_init_t *init )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;;
+ DRM_DEBUG( "\n" );
+
+ dev_priv->is_pci = init->is_pci;
+
+ if ( dev_priv->is_pci && !dev->sg ) {
+ DRM_ERROR( "PCI GART memory not allocated!\n" );
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->usec_timeout = init->usec_timeout;
+ if ( dev_priv->usec_timeout < 1 ||
+ dev_priv->usec_timeout > RADEON_MAX_USEC_TIMEOUT ) {
+ DRM_DEBUG( "TIMEOUT problem!\n" );
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ switch(init->func) {
+ case RADEON_INIT_R200_CP:
+ dev_priv->microcode_version=UCODE_R200;
+ break;
+ case RADEON_INIT_R300_CP:
+ dev_priv->microcode_version=UCODE_R300;
+ break;
+ default:
+ dev_priv->microcode_version=UCODE_R100;
+ }
+
+ dev_priv->do_boxes = 0;
+ dev_priv->cp_mode = init->cp_mode;
+
+ /* We don't support anything other than bus-mastering ring mode,
+ * but the ring can be in either AGP or PCI space for the ring
+ * read pointer.
+ */
+ if ( ( init->cp_mode != RADEON_CSQ_PRIBM_INDDIS ) &&
+ ( init->cp_mode != RADEON_CSQ_PRIBM_INDBM ) ) {
+ DRM_DEBUG( "BAD cp_mode (%x)!\n", init->cp_mode );
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ switch ( init->fb_bpp ) {
+ case 16:
+ dev_priv->color_fmt = RADEON_COLOR_FORMAT_RGB565;
+ break;
+ case 32:
+ default:
+ dev_priv->color_fmt = RADEON_COLOR_FORMAT_ARGB8888;
+ break;
+ }
+ dev_priv->front_offset = init->front_offset;
+ dev_priv->front_pitch = init->front_pitch;
+ dev_priv->back_offset = init->back_offset;
+ dev_priv->back_pitch = init->back_pitch;
+
+ switch ( init->depth_bpp ) {
+ case 16:
+ dev_priv->depth_fmt = RADEON_DEPTH_FORMAT_16BIT_INT_Z;
+ break;
+ case 32:
+ default:
+ dev_priv->depth_fmt = RADEON_DEPTH_FORMAT_24BIT_INT_Z;
+ break;
+ }
+ dev_priv->depth_offset = init->depth_offset;
+ dev_priv->depth_pitch = init->depth_pitch;
+
+ /* Hardware state for depth clears. Remove this if/when we no
+ * longer clear the depth buffer with a 3D rectangle. Hard-code
+ * all values to prevent unwanted 3D state from slipping through
+ * and screwing with the clear operation.
+ */
+ dev_priv->depth_clear.rb3d_cntl = (RADEON_PLANE_MASK_ENABLE |
+ (dev_priv->color_fmt << 10) |
+ (dev_priv->microcode_version == UCODE_R100 ? RADEON_ZBLOCK16 : 0));
+
+ dev_priv->depth_clear.rb3d_zstencilcntl =
+ (dev_priv->depth_fmt |
+ RADEON_Z_TEST_ALWAYS |
+ RADEON_STENCIL_TEST_ALWAYS |
+ RADEON_STENCIL_S_FAIL_REPLACE |
+ RADEON_STENCIL_ZPASS_REPLACE |
+ RADEON_STENCIL_ZFAIL_REPLACE |
+ RADEON_Z_WRITE_ENABLE);
+
+ dev_priv->depth_clear.se_cntl = (RADEON_FFACE_CULL_CW |
+ RADEON_BFACE_SOLID |
+ RADEON_FFACE_SOLID |
+ RADEON_FLAT_SHADE_VTX_LAST |
+ RADEON_DIFFUSE_SHADE_FLAT |
+ RADEON_ALPHA_SHADE_FLAT |
+ RADEON_SPECULAR_SHADE_FLAT |
+ RADEON_FOG_SHADE_FLAT |
+ RADEON_VTX_PIX_CENTER_OGL |
+ RADEON_ROUND_MODE_TRUNC |
+ RADEON_ROUND_PREC_8TH_PIX);
+
+ DRM_GETSAREA();
+
+ dev_priv->fb_offset = init->fb_offset;
+ dev_priv->mmio_offset = init->mmio_offset;
+ dev_priv->ring_offset = init->ring_offset;
+ dev_priv->ring_rptr_offset = init->ring_rptr_offset;
+ dev_priv->buffers_offset = init->buffers_offset;
+ dev_priv->gart_textures_offset = init->gart_textures_offset;
+
+ if(!dev_priv->sarea) {
+ DRM_ERROR("could not find sarea!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+ if(!dev_priv->mmio) {
+ DRM_ERROR("could not find mmio region!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->cp_ring = drm_core_findmap(dev, init->ring_offset);
+ if(!dev_priv->cp_ring) {
+ DRM_ERROR("could not find cp ring region!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+ dev_priv->ring_rptr = drm_core_findmap(dev, init->ring_rptr_offset);
+ if(!dev_priv->ring_rptr) {
+ DRM_ERROR("could not find ring read pointer!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+ dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+ if(!dev->agp_buffer_map) {
+ DRM_ERROR("could not find dma buffer region!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( init->gart_textures_offset ) {
+ dev_priv->gart_textures = drm_core_findmap(dev, init->gart_textures_offset);
+ if ( !dev_priv->gart_textures ) {
+ DRM_ERROR("could not find GART texture region!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+ }
+
+ dev_priv->sarea_priv =
+ (drm_radeon_sarea_t *)((u8 *)dev_priv->sarea->handle +
+ init->sarea_priv_offset);
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ drm_core_ioremap( dev_priv->cp_ring, dev );
+ drm_core_ioremap( dev_priv->ring_rptr, dev );
+ drm_core_ioremap( dev->agp_buffer_map, dev );
+ if(!dev_priv->cp_ring->handle ||
+ !dev_priv->ring_rptr->handle ||
+ !dev->agp_buffer_map->handle) {
+ DRM_ERROR("could not find ioremap agp regions!\n");
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(EINVAL);
+ }
+ } else
+#endif
+ {
+ dev_priv->cp_ring->handle =
+ (void *)dev_priv->cp_ring->offset;
+ dev_priv->ring_rptr->handle =
+ (void *)dev_priv->ring_rptr->offset;
+ dev->agp_buffer_map->handle = (void *)dev->agp_buffer_map->offset;
+
+ DRM_DEBUG( "dev_priv->cp_ring->handle %p\n",
+ dev_priv->cp_ring->handle );
+ DRM_DEBUG( "dev_priv->ring_rptr->handle %p\n",
+ dev_priv->ring_rptr->handle );
+ DRM_DEBUG( "dev->agp_buffer_map->handle %p\n",
+ dev->agp_buffer_map->handle );
+ }
+
+ dev_priv->fb_location = ( RADEON_READ( RADEON_MC_FB_LOCATION )
+ & 0xffff ) << 16;
+
+ dev_priv->front_pitch_offset = (((dev_priv->front_pitch/64) << 22) |
+ ( ( dev_priv->front_offset
+ + dev_priv->fb_location ) >> 10 ) );
+
+ dev_priv->back_pitch_offset = (((dev_priv->back_pitch/64) << 22) |
+ ( ( dev_priv->back_offset
+ + dev_priv->fb_location ) >> 10 ) );
+
+ dev_priv->depth_pitch_offset = (((dev_priv->depth_pitch/64) << 22) |
+ ( ( dev_priv->depth_offset
+ + dev_priv->fb_location ) >> 10 ) );
+
+
+ dev_priv->gart_size = init->gart_size;
+ dev_priv->gart_vm_start = dev_priv->fb_location
+ + RADEON_READ( RADEON_CONFIG_APER_SIZE );
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci )
+ dev_priv->gart_buffers_offset = (dev->agp_buffer_map->offset
+ - dev->agp->base
+ + dev_priv->gart_vm_start);
+ else
+#endif
+ dev_priv->gart_buffers_offset = (dev->agp_buffer_map->offset
+ - dev->sg->handle
+ + dev_priv->gart_vm_start);
+
+ DRM_DEBUG( "dev_priv->gart_size %d\n",
+ dev_priv->gart_size );
+ DRM_DEBUG( "dev_priv->gart_vm_start 0x%x\n",
+ dev_priv->gart_vm_start );
+ DRM_DEBUG( "dev_priv->gart_buffers_offset 0x%lx\n",
+ dev_priv->gart_buffers_offset );
+
+ dev_priv->ring.start = (u32 *)dev_priv->cp_ring->handle;
+ dev_priv->ring.end = ((u32 *)dev_priv->cp_ring->handle
+ + init->ring_size / sizeof(u32));
+ dev_priv->ring.size = init->ring_size;
+ dev_priv->ring.size_l2qw = drm_order( init->ring_size / 8 );
+
+ dev_priv->ring.tail_mask =
+ (dev_priv->ring.size / sizeof(u32)) - 1;
+
+ dev_priv->ring.high_mark = RADEON_RING_HIGH_MARK;
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ /* Turn off PCI GART */
+ radeon_set_pcigart( dev_priv, 0 );
+ } else
+#endif
+ {
+ if (!drm_ati_pcigart_init( dev, &dev_priv->phys_pci_gart,
+ &dev_priv->bus_pci_gart)) {
+ DRM_ERROR( "failed to init PCI GART!\n" );
+ dev->dev_private = (void *)dev_priv;
+ radeon_do_cleanup_cp(dev);
+ return DRM_ERR(ENOMEM);
+ }
+
+ /* Turn on PCI GART */
+ radeon_set_pcigart( dev_priv, 1 );
+ }
+
+ radeon_cp_load_microcode( dev_priv );
+ radeon_cp_init_ring_buffer( dev, dev_priv );
+
+ dev_priv->last_buf = 0;
+
+ dev->dev_private = (void *)dev_priv;
+
+ radeon_do_engine_reset( dev );
+
+ return 0;
+}
+
+static int radeon_do_cleanup_cp( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ /* Make sure interrupts are disabled here because the uninstall ioctl
+ * may not have been called from userspace and after dev_private
+ * is freed, it's too late.
+ */
+ if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ if ( dev_priv->cp_ring != NULL )
+ drm_core_ioremapfree( dev_priv->cp_ring, dev );
+ if ( dev_priv->ring_rptr != NULL )
+ drm_core_ioremapfree( dev_priv->ring_rptr, dev );
+ if ( dev->agp_buffer_map != NULL )
+ {
+ drm_core_ioremapfree( dev->agp_buffer_map, dev );
+ dev->agp_buffer_map = NULL;
+ }
+ } else
+#endif
+ {
+ if (!drm_ati_pcigart_cleanup( dev,
+ dev_priv->phys_pci_gart,
+ dev_priv->bus_pci_gart ))
+ DRM_ERROR( "failed to cleanup PCI GART!\n" );
+ }
+
+ /* only clear to the start of flags */
+ memset(dev_priv, 0, offsetof(drm_radeon_private_t, flags));
+
+ return 0;
+}
+
+/* This code will reinit the Radeon CP hardware after a resume from disc.
+ * AFAIK, it would be very difficult to pickle the state at suspend time, so
+ * here we make sure that all Radeon hardware initialisation is re-done without
+ * affecting running applications.
+ *
+ * Charl P. Botha <http://cpbotha.net>
+ */
+static int radeon_do_resume_cp( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "Called with no initialization\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ DRM_DEBUG("Starting radeon_do_resume_cp()\n");
+
+#if __OS_HAS_AGP
+ if ( !dev_priv->is_pci ) {
+ /* Turn off PCI GART */
+ radeon_set_pcigart( dev_priv, 0 );
+ } else
+#endif
+ {
+ /* Turn on PCI GART */
+ radeon_set_pcigart( dev_priv, 1 );
+ }
+
+ radeon_cp_load_microcode( dev_priv );
+ radeon_cp_init_ring_buffer( dev, dev_priv );
+
+ radeon_do_engine_reset( dev );
+
+ DRM_DEBUG("radeon_do_resume_cp() complete\n");
+
+ return 0;
+}
+
+
+int radeon_cp_init( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_init_t init;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( init, (drm_radeon_init_t __user *)data, sizeof(init) );
+
+ switch ( init.func ) {
+ case RADEON_INIT_CP:
+ case RADEON_INIT_R200_CP:
+ case RADEON_INIT_R300_CP:
+ return radeon_do_init_cp( dev, &init );
+ case RADEON_CLEANUP_CP:
+ return radeon_do_cleanup_cp( dev );
+ }
+
+ return DRM_ERR(EINVAL);
+}
+
+int radeon_cp_start( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( dev_priv->cp_running ) {
+ DRM_DEBUG( "%s while CP running\n", __FUNCTION__ );
+ return 0;
+ }
+ if ( dev_priv->cp_mode == RADEON_CSQ_PRIDIS_INDDIS ) {
+ DRM_DEBUG( "%s called with bogus CP mode (%d)\n",
+ __FUNCTION__, dev_priv->cp_mode );
+ return 0;
+ }
+
+ radeon_do_cp_start( dev_priv );
+
+ return 0;
+}
+
+/* Stop the CP. The engine must have been idled before calling this
+ * routine.
+ */
+int radeon_cp_stop( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_cp_stop_t stop;
+ int ret;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( stop, (drm_radeon_cp_stop_t __user *)data, sizeof(stop) );
+
+ if (!dev_priv->cp_running)
+ return 0;
+
+ /* Flush any pending CP commands. This ensures any outstanding
+ * commands are exectuted by the engine before we turn it off.
+ */
+ if ( stop.flush ) {
+ radeon_do_cp_flush( dev_priv );
+ }
+
+ /* If we fail to make the engine go idle, we return an error
+ * code so that the DRM ioctl wrapper can try again.
+ */
+ if ( stop.idle ) {
+ ret = radeon_do_cp_idle( dev_priv );
+ if ( ret ) return ret;
+ }
+
+ /* Finally, we can turn off the CP. If the engine isn't idle,
+ * we will get some dropped triangles as they won't be fully
+ * rendered before the CP is shut down.
+ */
+ radeon_do_cp_stop( dev_priv );
+
+ /* Reset the engine */
+ radeon_do_engine_reset( dev );
+
+ return 0;
+}
+
+
+void radeon_do_release( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ int i, ret;
+
+ if (dev_priv) {
+ if (dev_priv->cp_running) {
+ /* Stop the cp */
+ while ((ret = radeon_do_cp_idle( dev_priv )) != 0) {
+ DRM_DEBUG("radeon_do_cp_idle %d\n", ret);
+#ifdef __linux__
+ schedule();
+#else
+ tsleep(&ret, PZERO, "rdnrel", 1);
+#endif
+ }
+ radeon_do_cp_stop( dev_priv );
+ radeon_do_engine_reset( dev );
+ }
+
+ /* Disable *all* interrupts */
+ if (dev_priv->mmio) /* remove this after permanent addmaps */
+ RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+
+ if (dev_priv->mmio) {/* remove all surfaces */
+ for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+ RADEON_WRITE(RADEON_SURFACE0_INFO + 16*i, 0);
+ RADEON_WRITE(RADEON_SURFACE0_LOWER_BOUND + 16*i, 0);
+ RADEON_WRITE(RADEON_SURFACE0_UPPER_BOUND + 16*i, 0);
+ }
+ }
+
+ /* Free memory heap structures */
+ radeon_mem_takedown( &(dev_priv->gart_heap) );
+ radeon_mem_takedown( &(dev_priv->fb_heap) );
+
+ /* deallocate kernel resources */
+ radeon_do_cleanup_cp( dev );
+ }
+}
+
+/* Just reset the CP ring. Called as part of an X Server engine reset.
+ */
+int radeon_cp_reset( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_DEBUG( "%s called before init done\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ radeon_do_cp_reset( dev_priv );
+
+ /* The CP is no longer running after an engine reset */
+ dev_priv->cp_running = 0;
+
+ return 0;
+}
+
+int radeon_cp_idle( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ return radeon_do_cp_idle( dev_priv );
+}
+
+/* Added by Charl P. Botha to call radeon_do_resume_cp().
+ */
+int radeon_cp_resume( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+
+ return radeon_do_resume_cp(dev);
+}
+
+
+int radeon_engine_reset( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ return radeon_do_engine_reset( dev );
+}
+
+
+/* ================================================================
+ * Fullscreen mode
+ */
+
+/* KW: Deprecated to say the least:
+ */
+int radeon_fullscreen( DRM_IOCTL_ARGS )
+{
+ return 0;
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+
+/* Original comment: FIXME: ROTATE_BUFS is a hack to cycle through
+ * bufs until freelist code is used. Note this hides a problem with
+ * the scratch register * (used to keep track of last buffer
+ * completed) being written to before * the last buffer has actually
+ * completed rendering.
+ *
+ * KW: It's also a good way to find free buffers quickly.
+ *
+ * KW: Ideally this loop wouldn't exist, and freelist_get wouldn't
+ * sleep. However, bugs in older versions of radeon_accel.c mean that
+ * we essentially have to do this, else old clients will break.
+ *
+ * However, it does leave open a potential deadlock where all the
+ * buffers are held by other clients, which can't release them because
+ * they can't get the lock.
+ */
+
+drm_buf_t *radeon_freelist_get( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_buf_priv_t *buf_priv;
+ drm_buf_t *buf;
+ int i, t;
+ int start;
+
+ if ( ++dev_priv->last_buf >= dma->buf_count )
+ dev_priv->last_buf = 0;
+
+ start = dev_priv->last_buf;
+
+ for ( t = 0 ; t < dev_priv->usec_timeout ; t++ ) {
+ u32 done_age = GET_SCRATCH( 1 );
+ DRM_DEBUG("done_age = %d\n",done_age);
+ for ( i = start ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+ if ( buf->filp == 0 || (buf->pending &&
+ buf_priv->age <= done_age) ) {
+ dev_priv->stats.requested_bufs++;
+ buf->pending = 0;
+ return buf;
+ }
+ start = 0;
+ }
+
+ if (t) {
+ DRM_UDELAY( 1 );
+ dev_priv->stats.freelist_loops++;
+ }
+ }
+
+ DRM_DEBUG( "returning NULL!\n" );
+ return NULL;
+}
+#if 0
+drm_buf_t *radeon_freelist_get( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_buf_priv_t *buf_priv;
+ drm_buf_t *buf;
+ int i, t;
+ int start;
+ u32 done_age = DRM_READ32(dev_priv->ring_rptr, RADEON_SCRATCHOFF(1));
+
+ if ( ++dev_priv->last_buf >= dma->buf_count )
+ dev_priv->last_buf = 0;
+
+ start = dev_priv->last_buf;
+ dev_priv->stats.freelist_loops++;
+
+ for ( t = 0 ; t < 2 ; t++ ) {
+ for ( i = start ; i < dma->buf_count ; i++ ) {
+ buf = dma->buflist[i];
+ buf_priv = buf->dev_private;
+ if ( buf->filp == 0 || (buf->pending &&
+ buf_priv->age <= done_age) ) {
+ dev_priv->stats.requested_bufs++;
+ buf->pending = 0;
+ return buf;
+ }
+ }
+ start = 0;
+ }
+
+ return NULL;
+}
+#endif
+
+void radeon_freelist_reset( drm_device_t *dev )
+{
+ drm_device_dma_t *dma = dev->dma;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ int i;
+
+ dev_priv->last_buf = 0;
+ for ( i = 0 ; i < dma->buf_count ; i++ ) {
+ drm_buf_t *buf = dma->buflist[i];
+ drm_radeon_buf_priv_t *buf_priv = buf->dev_private;
+ buf_priv->age = 0;
+ }
+}
+
+
+/* ================================================================
+ * CP command submission
+ */
+
+int radeon_wait_ring( drm_radeon_private_t *dev_priv, int n )
+{
+ drm_radeon_ring_buffer_t *ring = &dev_priv->ring;
+ int i;
+ u32 last_head = GET_RING_HEAD( dev_priv );
+
+ for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+ u32 head = GET_RING_HEAD( dev_priv );
+
+ ring->space = (head - ring->tail) * sizeof(u32);
+ if ( ring->space <= 0 )
+ ring->space += ring->size;
+ if ( ring->space > n )
+ return 0;
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ if (head != last_head)
+ i = 0;
+ last_head = head;
+
+ DRM_UDELAY( 1 );
+ }
+
+ /* FIXME: This return value is ignored in the BEGIN_RING macro! */
+#if RADEON_FIFO_DEBUG
+ radeon_status( dev_priv );
+ DRM_ERROR( "failed!\n" );
+#endif
+ return DRM_ERR(EBUSY);
+}
+
+static int radeon_cp_get_buffers( DRMFILE filp, drm_device_t *dev, drm_dma_t *d )
+{
+ int i;
+ drm_buf_t *buf;
+
+ for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+ buf = radeon_freelist_get( dev );
+ if ( !buf ) return DRM_ERR(EBUSY); /* NOTE: broken client */
+
+ buf->filp = filp;
+
+ if ( DRM_COPY_TO_USER( &d->request_indices[i], &buf->idx,
+ sizeof(buf->idx) ) )
+ return DRM_ERR(EFAULT);
+ if ( DRM_COPY_TO_USER( &d->request_sizes[i], &buf->total,
+ sizeof(buf->total) ) )
+ return DRM_ERR(EFAULT);
+
+ d->granted_count++;
+ }
+ return 0;
+}
+
+int radeon_cp_buffers( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_device_dma_t *dma = dev->dma;
+ int ret = 0;
+ drm_dma_t __user *argp = (void __user *)data;
+ drm_dma_t d;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+ /* Please don't send us buffers.
+ */
+ if ( d.send_count != 0 ) {
+ DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+ DRM_CURRENTPID, d.send_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ /* We'll send you buffers.
+ */
+ if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+ DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+ DRM_CURRENTPID, d.request_count, dma->buf_count );
+ return DRM_ERR(EINVAL);
+ }
+
+ d.granted_count = 0;
+
+ if ( d.request_count ) {
+ ret = radeon_cp_get_buffers( filp, dev, &d );
+ }
+
+ DRM_COPY_TO_USER_IOCTL( argp, d, sizeof(d) );
+
+ return ret;
+}
+
+int radeon_driver_preinit(struct drm_device *dev, unsigned long flags)
+{
+ drm_radeon_private_t *dev_priv;
+ int ret = 0;
+
+ dev_priv = drm_alloc(sizeof(drm_radeon_private_t), DRM_MEM_DRIVER);
+ if (dev_priv == NULL)
+ return DRM_ERR(ENOMEM);
+
+ memset(dev_priv, 0, sizeof(drm_radeon_private_t));
+ dev->dev_private = (void *)dev_priv;
+ dev_priv->flags = flags;
+
+ switch (flags & CHIP_FAMILY_MASK) {
+ case CHIP_R100:
+ case CHIP_RV200:
+ case CHIP_R200:
+ case CHIP_R300:
+ dev_priv->flags |= CHIP_HAS_HIERZ;
+ break;
+ default:
+ /* all other chips have no hierarchical z buffer */
+ break;
+ }
+ return ret;
+}
+
+int radeon_driver_postcleanup(struct drm_device *dev)
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+
+ DRM_DEBUG("\n");
+
+ drm_free(dev_priv, sizeof(*dev_priv), DRM_MEM_DRIVER);
+
+ dev->dev_private = NULL;
+ return 0;
+}
diff --git a/drivers/char/drm/radeon_drm.h b/drivers/char/drm/radeon_drm.h
new file mode 100644
index 000000000000..c1e62d047989
--- /dev/null
+++ b/drivers/char/drm/radeon_drm.h
@@ -0,0 +1,659 @@
+/* radeon_drm.h -- Public header for the radeon driver -*- linux-c -*-
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * Copyright 2002 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Kevin E. Martin <martin@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ * Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#ifndef __RADEON_DRM_H__
+#define __RADEON_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the X server file (radeon_sarea.h)
+ */
+#ifndef __RADEON_SAREA_DEFINES__
+#define __RADEON_SAREA_DEFINES__
+
+/* Old style state flags, required for sarea interface (1.1 and 1.2
+ * clears) and 1.2 drm_vertex2 ioctl.
+ */
+#define RADEON_UPLOAD_CONTEXT 0x00000001
+#define RADEON_UPLOAD_VERTFMT 0x00000002
+#define RADEON_UPLOAD_LINE 0x00000004
+#define RADEON_UPLOAD_BUMPMAP 0x00000008
+#define RADEON_UPLOAD_MASKS 0x00000010
+#define RADEON_UPLOAD_VIEWPORT 0x00000020
+#define RADEON_UPLOAD_SETUP 0x00000040
+#define RADEON_UPLOAD_TCL 0x00000080
+#define RADEON_UPLOAD_MISC 0x00000100
+#define RADEON_UPLOAD_TEX0 0x00000200
+#define RADEON_UPLOAD_TEX1 0x00000400
+#define RADEON_UPLOAD_TEX2 0x00000800
+#define RADEON_UPLOAD_TEX0IMAGES 0x00001000
+#define RADEON_UPLOAD_TEX1IMAGES 0x00002000
+#define RADEON_UPLOAD_TEX2IMAGES 0x00004000
+#define RADEON_UPLOAD_CLIPRECTS 0x00008000 /* handled client-side */
+#define RADEON_REQUIRE_QUIESCENCE 0x00010000
+#define RADEON_UPLOAD_ZBIAS 0x00020000 /* version 1.2 and newer */
+#define RADEON_UPLOAD_ALL 0x003effff
+#define RADEON_UPLOAD_CONTEXT_ALL 0x003e01ff
+
+
+/* New style per-packet identifiers for use in cmd_buffer ioctl with
+ * the RADEON_EMIT_PACKET command. Comments relate new packets to old
+ * state bits and the packet size:
+ */
+#define RADEON_EMIT_PP_MISC 0 /* context/7 */
+#define RADEON_EMIT_PP_CNTL 1 /* context/3 */
+#define RADEON_EMIT_RB3D_COLORPITCH 2 /* context/1 */
+#define RADEON_EMIT_RE_LINE_PATTERN 3 /* line/2 */
+#define RADEON_EMIT_SE_LINE_WIDTH 4 /* line/1 */
+#define RADEON_EMIT_PP_LUM_MATRIX 5 /* bumpmap/1 */
+#define RADEON_EMIT_PP_ROT_MATRIX_0 6 /* bumpmap/2 */
+#define RADEON_EMIT_RB3D_STENCILREFMASK 7 /* masks/3 */
+#define RADEON_EMIT_SE_VPORT_XSCALE 8 /* viewport/6 */
+#define RADEON_EMIT_SE_CNTL 9 /* setup/2 */
+#define RADEON_EMIT_SE_CNTL_STATUS 10 /* setup/1 */
+#define RADEON_EMIT_RE_MISC 11 /* misc/1 */
+#define RADEON_EMIT_PP_TXFILTER_0 12 /* tex0/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_0 13 /* tex0/1 */
+#define RADEON_EMIT_PP_TXFILTER_1 14 /* tex1/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_1 15 /* tex1/1 */
+#define RADEON_EMIT_PP_TXFILTER_2 16 /* tex2/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_2 17 /* tex2/1 */
+#define RADEON_EMIT_SE_ZBIAS_FACTOR 18 /* zbias/2 */
+#define RADEON_EMIT_SE_TCL_OUTPUT_VTX_FMT 19 /* tcl/11 */
+#define RADEON_EMIT_SE_TCL_MATERIAL_EMMISSIVE_RED 20 /* material/17 */
+#define R200_EMIT_PP_TXCBLEND_0 21 /* tex0/4 */
+#define R200_EMIT_PP_TXCBLEND_1 22 /* tex1/4 */
+#define R200_EMIT_PP_TXCBLEND_2 23 /* tex2/4 */
+#define R200_EMIT_PP_TXCBLEND_3 24 /* tex3/4 */
+#define R200_EMIT_PP_TXCBLEND_4 25 /* tex4/4 */
+#define R200_EMIT_PP_TXCBLEND_5 26 /* tex5/4 */
+#define R200_EMIT_PP_TXCBLEND_6 27 /* /4 */
+#define R200_EMIT_PP_TXCBLEND_7 28 /* /4 */
+#define R200_EMIT_TCL_LIGHT_MODEL_CTL_0 29 /* tcl/7 */
+#define R200_EMIT_TFACTOR_0 30 /* tf/7 */
+#define R200_EMIT_VTX_FMT_0 31 /* vtx/5 */
+#define R200_EMIT_VAP_CTL 32 /* vap/1 */
+#define R200_EMIT_MATRIX_SELECT_0 33 /* msl/5 */
+#define R200_EMIT_TEX_PROC_CTL_2 34 /* tcg/5 */
+#define R200_EMIT_TCL_UCP_VERT_BLEND_CTL 35 /* tcl/1 */
+#define R200_EMIT_PP_TXFILTER_0 36 /* tex0/6 */
+#define R200_EMIT_PP_TXFILTER_1 37 /* tex1/6 */
+#define R200_EMIT_PP_TXFILTER_2 38 /* tex2/6 */
+#define R200_EMIT_PP_TXFILTER_3 39 /* tex3/6 */
+#define R200_EMIT_PP_TXFILTER_4 40 /* tex4/6 */
+#define R200_EMIT_PP_TXFILTER_5 41 /* tex5/6 */
+#define R200_EMIT_PP_TXOFFSET_0 42 /* tex0/1 */
+#define R200_EMIT_PP_TXOFFSET_1 43 /* tex1/1 */
+#define R200_EMIT_PP_TXOFFSET_2 44 /* tex2/1 */
+#define R200_EMIT_PP_TXOFFSET_3 45 /* tex3/1 */
+#define R200_EMIT_PP_TXOFFSET_4 46 /* tex4/1 */
+#define R200_EMIT_PP_TXOFFSET_5 47 /* tex5/1 */
+#define R200_EMIT_VTE_CNTL 48 /* vte/1 */
+#define R200_EMIT_OUTPUT_VTX_COMP_SEL 49 /* vtx/1 */
+#define R200_EMIT_PP_TAM_DEBUG3 50 /* tam/1 */
+#define R200_EMIT_PP_CNTL_X 51 /* cst/1 */
+#define R200_EMIT_RB3D_DEPTHXY_OFFSET 52 /* cst/1 */
+#define R200_EMIT_RE_AUX_SCISSOR_CNTL 53 /* cst/1 */
+#define R200_EMIT_RE_SCISSOR_TL_0 54 /* cst/2 */
+#define R200_EMIT_RE_SCISSOR_TL_1 55 /* cst/2 */
+#define R200_EMIT_RE_SCISSOR_TL_2 56 /* cst/2 */
+#define R200_EMIT_SE_VAP_CNTL_STATUS 57 /* cst/1 */
+#define R200_EMIT_SE_VTX_STATE_CNTL 58 /* cst/1 */
+#define R200_EMIT_RE_POINTSIZE 59 /* cst/1 */
+#define R200_EMIT_TCL_INPUT_VTX_VECTOR_ADDR_0 60 /* cst/4 */
+#define R200_EMIT_PP_CUBIC_FACES_0 61
+#define R200_EMIT_PP_CUBIC_OFFSETS_0 62
+#define R200_EMIT_PP_CUBIC_FACES_1 63
+#define R200_EMIT_PP_CUBIC_OFFSETS_1 64
+#define R200_EMIT_PP_CUBIC_FACES_2 65
+#define R200_EMIT_PP_CUBIC_OFFSETS_2 66
+#define R200_EMIT_PP_CUBIC_FACES_3 67
+#define R200_EMIT_PP_CUBIC_OFFSETS_3 68
+#define R200_EMIT_PP_CUBIC_FACES_4 69
+#define R200_EMIT_PP_CUBIC_OFFSETS_4 70
+#define R200_EMIT_PP_CUBIC_FACES_5 71
+#define R200_EMIT_PP_CUBIC_OFFSETS_5 72
+#define RADEON_EMIT_PP_TEX_SIZE_0 73
+#define RADEON_EMIT_PP_TEX_SIZE_1 74
+#define RADEON_EMIT_PP_TEX_SIZE_2 75
+#define R200_EMIT_RB3D_BLENDCOLOR 76
+#define R200_EMIT_TCL_POINT_SPRITE_CNTL 77
+#define RADEON_EMIT_PP_CUBIC_FACES_0 78
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T0 79
+#define RADEON_EMIT_PP_CUBIC_FACES_1 80
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T1 81
+#define RADEON_EMIT_PP_CUBIC_FACES_2 82
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T2 83
+#define R200_EMIT_PP_TRI_PERF_CNTL 84
+#define RADEON_MAX_STATE_PACKETS 85
+
+/* Commands understood by cmd_buffer ioctl. More can be added but
+ * obviously these can't be removed or changed:
+ */
+#define RADEON_CMD_PACKET 1 /* emit one of the register packets above */
+#define RADEON_CMD_SCALARS 2 /* emit scalar data */
+#define RADEON_CMD_VECTORS 3 /* emit vector data */
+#define RADEON_CMD_DMA_DISCARD 4 /* discard current dma buf */
+#define RADEON_CMD_PACKET3 5 /* emit hw packet */
+#define RADEON_CMD_PACKET3_CLIP 6 /* emit hw packet wrapped in cliprects */
+#define RADEON_CMD_SCALARS2 7 /* r200 stopgap */
+#define RADEON_CMD_WAIT 8 /* emit hw wait commands -- note:
+ * doesn't make the cpu wait, just
+ * the graphics hardware */
+
+
+typedef union {
+ int i;
+ struct {
+ unsigned char cmd_type, pad0, pad1, pad2;
+ } header;
+ struct {
+ unsigned char cmd_type, packet_id, pad0, pad1;
+ } packet;
+ struct {
+ unsigned char cmd_type, offset, stride, count;
+ } scalars;
+ struct {
+ unsigned char cmd_type, offset, stride, count;
+ } vectors;
+ struct {
+ unsigned char cmd_type, buf_idx, pad0, pad1;
+ } dma;
+ struct {
+ unsigned char cmd_type, flags, pad0, pad1;
+ } wait;
+} drm_radeon_cmd_header_t;
+
+#define RADEON_WAIT_2D 0x1
+#define RADEON_WAIT_3D 0x2
+
+
+#define RADEON_FRONT 0x1
+#define RADEON_BACK 0x2
+#define RADEON_DEPTH 0x4
+#define RADEON_STENCIL 0x8
+#define RADEON_CLEAR_FASTZ 0x80000000
+#define RADEON_USE_HIERZ 0x40000000
+#define RADEON_USE_COMP_ZBUF 0x20000000
+
+/* Primitive types
+ */
+#define RADEON_POINTS 0x1
+#define RADEON_LINES 0x2
+#define RADEON_LINE_STRIP 0x3
+#define RADEON_TRIANGLES 0x4
+#define RADEON_TRIANGLE_FAN 0x5
+#define RADEON_TRIANGLE_STRIP 0x6
+
+/* Vertex/indirect buffer size
+ */
+#define RADEON_BUFFER_SIZE 65536
+
+/* Byte offsets for indirect buffer data
+ */
+#define RADEON_INDEX_PRIM_OFFSET 20
+
+#define RADEON_SCRATCH_REG_OFFSET 32
+
+#define RADEON_NR_SAREA_CLIPRECTS 12
+
+/* There are 2 heaps (local/GART). Each region within a heap is a
+ * minimum of 64k, and there are at most 64 of them per heap.
+ */
+#define RADEON_LOCAL_TEX_HEAP 0
+#define RADEON_GART_TEX_HEAP 1
+#define RADEON_NR_TEX_HEAPS 2
+#define RADEON_NR_TEX_REGIONS 64
+#define RADEON_LOG_TEX_GRANULARITY 16
+
+#define RADEON_MAX_TEXTURE_LEVELS 12
+#define RADEON_MAX_TEXTURE_UNITS 3
+
+#define RADEON_MAX_SURFACES 8
+
+/* Blits have strict offset rules. All blit offset must be aligned on
+ * a 1K-byte boundary.
+ */
+#define RADEON_OFFSET_SHIFT 10
+#define RADEON_OFFSET_ALIGN (1 << RADEON_OFFSET_SHIFT)
+#define RADEON_OFFSET_MASK (RADEON_OFFSET_ALIGN - 1)
+
+#endif /* __RADEON_SAREA_DEFINES__ */
+
+typedef struct {
+ unsigned int red;
+ unsigned int green;
+ unsigned int blue;
+ unsigned int alpha;
+} radeon_color_regs_t;
+
+typedef struct {
+ /* Context state */
+ unsigned int pp_misc; /* 0x1c14 */
+ unsigned int pp_fog_color;
+ unsigned int re_solid_color;
+ unsigned int rb3d_blendcntl;
+ unsigned int rb3d_depthoffset;
+ unsigned int rb3d_depthpitch;
+ unsigned int rb3d_zstencilcntl;
+
+ unsigned int pp_cntl; /* 0x1c38 */
+ unsigned int rb3d_cntl;
+ unsigned int rb3d_coloroffset;
+ unsigned int re_width_height;
+ unsigned int rb3d_colorpitch;
+ unsigned int se_cntl;
+
+ /* Vertex format state */
+ unsigned int se_coord_fmt; /* 0x1c50 */
+
+ /* Line state */
+ unsigned int re_line_pattern; /* 0x1cd0 */
+ unsigned int re_line_state;
+
+ unsigned int se_line_width; /* 0x1db8 */
+
+ /* Bumpmap state */
+ unsigned int pp_lum_matrix; /* 0x1d00 */
+
+ unsigned int pp_rot_matrix_0; /* 0x1d58 */
+ unsigned int pp_rot_matrix_1;
+
+ /* Mask state */
+ unsigned int rb3d_stencilrefmask; /* 0x1d7c */
+ unsigned int rb3d_ropcntl;
+ unsigned int rb3d_planemask;
+
+ /* Viewport state */
+ unsigned int se_vport_xscale; /* 0x1d98 */
+ unsigned int se_vport_xoffset;
+ unsigned int se_vport_yscale;
+ unsigned int se_vport_yoffset;
+ unsigned int se_vport_zscale;
+ unsigned int se_vport_zoffset;
+
+ /* Setup state */
+ unsigned int se_cntl_status; /* 0x2140 */
+
+ /* Misc state */
+ unsigned int re_top_left; /* 0x26c0 */
+ unsigned int re_misc;
+} drm_radeon_context_regs_t;
+
+typedef struct {
+ /* Zbias state */
+ unsigned int se_zbias_factor; /* 0x1dac */
+ unsigned int se_zbias_constant;
+} drm_radeon_context2_regs_t;
+
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+ unsigned int pp_txfilter;
+ unsigned int pp_txformat;
+ unsigned int pp_txoffset;
+ unsigned int pp_txcblend;
+ unsigned int pp_txablend;
+ unsigned int pp_tfactor;
+ unsigned int pp_border_color;
+} drm_radeon_texture_regs_t;
+
+typedef struct {
+ unsigned int start;
+ unsigned int finish;
+ unsigned int prim:8;
+ unsigned int stateidx:8;
+ unsigned int numverts:16; /* overloaded as offset/64 for elt prims */
+ unsigned int vc_format; /* vertex format */
+} drm_radeon_prim_t;
+
+
+typedef struct {
+ drm_radeon_context_regs_t context;
+ drm_radeon_texture_regs_t tex[RADEON_MAX_TEXTURE_UNITS];
+ drm_radeon_context2_regs_t context2;
+ unsigned int dirty;
+} drm_radeon_state_t;
+
+
+typedef struct {
+ /* The channel for communication of state information to the
+ * kernel on firing a vertex buffer with either of the
+ * obsoleted vertex/index ioctls.
+ */
+ drm_radeon_context_regs_t context_state;
+ drm_radeon_texture_regs_t tex_state[RADEON_MAX_TEXTURE_UNITS];
+ unsigned int dirty;
+ unsigned int vertsize;
+ unsigned int vc_format;
+
+ /* The current cliprects, or a subset thereof.
+ */
+ drm_clip_rect_t boxes[RADEON_NR_SAREA_CLIPRECTS];
+ unsigned int nbox;
+
+ /* Counters for client-side throttling of rendering clients.
+ */
+ unsigned int last_frame;
+ unsigned int last_dispatch;
+ unsigned int last_clear;
+
+ drm_tex_region_t tex_list[RADEON_NR_TEX_HEAPS][RADEON_NR_TEX_REGIONS+1];
+ unsigned int tex_age[RADEON_NR_TEX_HEAPS];
+ int ctx_owner;
+ int pfState; /* number of 3d windows (0,1,2ormore) */
+ int pfCurrentPage; /* which buffer is being displayed? */
+ int crtc2_base; /* CRTC2 frame offset */
+ int tiling_enabled; /* set by drm, read by 2d + 3d clients */
+} drm_radeon_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmRadeon.h)
+ *
+ * KW: actually it's illegal to change any of this (backwards compatibility).
+ */
+
+/* Radeon specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_RADEON_CP_INIT 0x00
+#define DRM_RADEON_CP_START 0x01
+#define DRM_RADEON_CP_STOP 0x02
+#define DRM_RADEON_CP_RESET 0x03
+#define DRM_RADEON_CP_IDLE 0x04
+#define DRM_RADEON_RESET 0x05
+#define DRM_RADEON_FULLSCREEN 0x06
+#define DRM_RADEON_SWAP 0x07
+#define DRM_RADEON_CLEAR 0x08
+#define DRM_RADEON_VERTEX 0x09
+#define DRM_RADEON_INDICES 0x0A
+#define DRM_RADEON_NOT_USED
+#define DRM_RADEON_STIPPLE 0x0C
+#define DRM_RADEON_INDIRECT 0x0D
+#define DRM_RADEON_TEXTURE 0x0E
+#define DRM_RADEON_VERTEX2 0x0F
+#define DRM_RADEON_CMDBUF 0x10
+#define DRM_RADEON_GETPARAM 0x11
+#define DRM_RADEON_FLIP 0x12
+#define DRM_RADEON_ALLOC 0x13
+#define DRM_RADEON_FREE 0x14
+#define DRM_RADEON_INIT_HEAP 0x15
+#define DRM_RADEON_IRQ_EMIT 0x16
+#define DRM_RADEON_IRQ_WAIT 0x17
+#define DRM_RADEON_CP_RESUME 0x18
+#define DRM_RADEON_SETPARAM 0x19
+#define DRM_RADEON_SURF_ALLOC 0x1a
+#define DRM_RADEON_SURF_FREE 0x1b
+
+#define DRM_IOCTL_RADEON_CP_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CP_INIT, drm_radeon_init_t)
+#define DRM_IOCTL_RADEON_CP_START DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_CP_START)
+#define DRM_IOCTL_RADEON_CP_STOP DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CP_STOP, drm_radeon_cp_stop_t)
+#define DRM_IOCTL_RADEON_CP_RESET DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_CP_RESET)
+#define DRM_IOCTL_RADEON_CP_IDLE DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_CP_IDLE)
+#define DRM_IOCTL_RADEON_RESET DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_RESET)
+#define DRM_IOCTL_RADEON_FULLSCREEN DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_FULLSCREEN, drm_radeon_fullscreen_t)
+#define DRM_IOCTL_RADEON_SWAP DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_SWAP)
+#define DRM_IOCTL_RADEON_CLEAR DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CLEAR, drm_radeon_clear_t)
+#define DRM_IOCTL_RADEON_VERTEX DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_VERTEX, drm_radeon_vertex_t)
+#define DRM_IOCTL_RADEON_INDICES DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_INDICES, drm_radeon_indices_t)
+#define DRM_IOCTL_RADEON_STIPPLE DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_STIPPLE, drm_radeon_stipple_t)
+#define DRM_IOCTL_RADEON_INDIRECT DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_INDIRECT, drm_radeon_indirect_t)
+#define DRM_IOCTL_RADEON_TEXTURE DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_TEXTURE, drm_radeon_texture_t)
+#define DRM_IOCTL_RADEON_VERTEX2 DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_VERTEX2, drm_radeon_vertex2_t)
+#define DRM_IOCTL_RADEON_CMDBUF DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CMDBUF, drm_radeon_cmd_buffer_t)
+#define DRM_IOCTL_RADEON_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_GETPARAM, drm_radeon_getparam_t)
+#define DRM_IOCTL_RADEON_FLIP DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_FLIP)
+#define DRM_IOCTL_RADEON_ALLOC DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_ALLOC, drm_radeon_mem_alloc_t)
+#define DRM_IOCTL_RADEON_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_FREE, drm_radeon_mem_free_t)
+#define DRM_IOCTL_RADEON_INIT_HEAP DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_INIT_HEAP, drm_radeon_mem_init_heap_t)
+#define DRM_IOCTL_RADEON_IRQ_EMIT DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_IRQ_EMIT, drm_radeon_irq_emit_t)
+#define DRM_IOCTL_RADEON_IRQ_WAIT DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_IRQ_WAIT, drm_radeon_irq_wait_t)
+#define DRM_IOCTL_RADEON_CP_RESUME DRM_IO( DRM_COMMAND_BASE + DRM_RADEON_CP_RESUME)
+#define DRM_IOCTL_RADEON_SETPARAM DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SETPARAM, drm_radeon_setparam_t)
+#define DRM_IOCTL_RADEON_SURF_ALLOC DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_ALLOC, drm_radeon_surface_alloc_t)
+#define DRM_IOCTL_RADEON_SURF_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_FREE, drm_radeon_surface_free_t)
+
+typedef struct drm_radeon_init {
+ enum {
+ RADEON_INIT_CP = 0x01,
+ RADEON_CLEANUP_CP = 0x02,
+ RADEON_INIT_R200_CP = 0x03,
+ RADEON_INIT_R300_CP = 0x04
+ } func;
+ unsigned long sarea_priv_offset;
+ int is_pci;
+ int cp_mode;
+ int gart_size;
+ int ring_size;
+ int usec_timeout;
+
+ unsigned int fb_bpp;
+ unsigned int front_offset, front_pitch;
+ unsigned int back_offset, back_pitch;
+ unsigned int depth_bpp;
+ unsigned int depth_offset, depth_pitch;
+
+ unsigned long fb_offset;
+ unsigned long mmio_offset;
+ unsigned long ring_offset;
+ unsigned long ring_rptr_offset;
+ unsigned long buffers_offset;
+ unsigned long gart_textures_offset;
+} drm_radeon_init_t;
+
+typedef struct drm_radeon_cp_stop {
+ int flush;
+ int idle;
+} drm_radeon_cp_stop_t;
+
+typedef struct drm_radeon_fullscreen {
+ enum {
+ RADEON_INIT_FULLSCREEN = 0x01,
+ RADEON_CLEANUP_FULLSCREEN = 0x02
+ } func;
+} drm_radeon_fullscreen_t;
+
+#define CLEAR_X1 0
+#define CLEAR_Y1 1
+#define CLEAR_X2 2
+#define CLEAR_Y2 3
+#define CLEAR_DEPTH 4
+
+typedef union drm_radeon_clear_rect {
+ float f[5];
+ unsigned int ui[5];
+} drm_radeon_clear_rect_t;
+
+typedef struct drm_radeon_clear {
+ unsigned int flags;
+ unsigned int clear_color;
+ unsigned int clear_depth;
+ unsigned int color_mask;
+ unsigned int depth_mask; /* misnamed field: should be stencil */
+ drm_radeon_clear_rect_t __user *depth_boxes;
+} drm_radeon_clear_t;
+
+typedef struct drm_radeon_vertex {
+ int prim;
+ int idx; /* Index of vertex buffer */
+ int count; /* Number of vertices in buffer */
+ int discard; /* Client finished with buffer? */
+} drm_radeon_vertex_t;
+
+typedef struct drm_radeon_indices {
+ int prim;
+ int idx;
+ int start;
+ int end;
+ int discard; /* Client finished with buffer? */
+} drm_radeon_indices_t;
+
+/* v1.2 - obsoletes drm_radeon_vertex and drm_radeon_indices
+ * - allows multiple primitives and state changes in a single ioctl
+ * - supports driver change to emit native primitives
+ */
+typedef struct drm_radeon_vertex2 {
+ int idx; /* Index of vertex buffer */
+ int discard; /* Client finished with buffer? */
+ int nr_states;
+ drm_radeon_state_t __user *state;
+ int nr_prims;
+ drm_radeon_prim_t __user *prim;
+} drm_radeon_vertex2_t;
+
+/* v1.3 - obsoletes drm_radeon_vertex2
+ * - allows arbitarily large cliprect list
+ * - allows updating of tcl packet, vector and scalar state
+ * - allows memory-efficient description of state updates
+ * - allows state to be emitted without a primitive
+ * (for clears, ctx switches)
+ * - allows more than one dma buffer to be referenced per ioctl
+ * - supports tcl driver
+ * - may be extended in future versions with new cmd types, packets
+ */
+typedef struct drm_radeon_cmd_buffer {
+ int bufsz;
+ char __user *buf;
+ int nbox;
+ drm_clip_rect_t __user *boxes;
+} drm_radeon_cmd_buffer_t;
+
+typedef struct drm_radeon_tex_image {
+ unsigned int x, y; /* Blit coordinates */
+ unsigned int width, height;
+ const void __user *data;
+} drm_radeon_tex_image_t;
+
+typedef struct drm_radeon_texture {
+ unsigned int offset;
+ int pitch;
+ int format;
+ int width; /* Texture image coordinates */
+ int height;
+ drm_radeon_tex_image_t __user *image;
+} drm_radeon_texture_t;
+
+typedef struct drm_radeon_stipple {
+ unsigned int __user *mask;
+} drm_radeon_stipple_t;
+
+typedef struct drm_radeon_indirect {
+ int idx;
+ int start;
+ int end;
+ int discard;
+} drm_radeon_indirect_t;
+
+
+/* 1.3: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.
+ */
+#define RADEON_PARAM_GART_BUFFER_OFFSET 1 /* card offset of 1st GART buffer */
+#define RADEON_PARAM_LAST_FRAME 2
+#define RADEON_PARAM_LAST_DISPATCH 3
+#define RADEON_PARAM_LAST_CLEAR 4
+/* Added with DRM version 1.6. */
+#define RADEON_PARAM_IRQ_NR 5
+#define RADEON_PARAM_GART_BASE 6 /* card offset of GART base */
+/* Added with DRM version 1.8. */
+#define RADEON_PARAM_REGISTER_HANDLE 7 /* for drmMap() */
+#define RADEON_PARAM_STATUS_HANDLE 8
+#define RADEON_PARAM_SAREA_HANDLE 9
+#define RADEON_PARAM_GART_TEX_HANDLE 10
+#define RADEON_PARAM_SCRATCH_OFFSET 11
+
+typedef struct drm_radeon_getparam {
+ int param;
+ void __user *value;
+} drm_radeon_getparam_t;
+
+/* 1.6: Set up a memory manager for regions of shared memory:
+ */
+#define RADEON_MEM_REGION_GART 1
+#define RADEON_MEM_REGION_FB 2
+
+typedef struct drm_radeon_mem_alloc {
+ int region;
+ int alignment;
+ int size;
+ int __user *region_offset; /* offset from start of fb or GART */
+} drm_radeon_mem_alloc_t;
+
+typedef struct drm_radeon_mem_free {
+ int region;
+ int region_offset;
+} drm_radeon_mem_free_t;
+
+typedef struct drm_radeon_mem_init_heap {
+ int region;
+ int size;
+ int start;
+} drm_radeon_mem_init_heap_t;
+
+
+/* 1.6: Userspace can request & wait on irq's:
+ */
+typedef struct drm_radeon_irq_emit {
+ int __user *irq_seq;
+} drm_radeon_irq_emit_t;
+
+typedef struct drm_radeon_irq_wait {
+ int irq_seq;
+} drm_radeon_irq_wait_t;
+
+
+/* 1.10: Clients tell the DRM where they think the framebuffer is located in
+ * the card's address space, via a new generic ioctl to set parameters
+ */
+
+typedef struct drm_radeon_setparam {
+ unsigned int param;
+ int64_t value;
+} drm_radeon_setparam_t;
+
+#define RADEON_SETPARAM_FB_LOCATION 1 /* determined framebuffer location */
+#define RADEON_SETPARAM_SWITCH_TILING 2 /* enable/disable color tiling */
+
+/* 1.14: Clients can allocate/free a surface
+ */
+typedef struct drm_radeon_surface_alloc {
+ unsigned int address;
+ unsigned int size;
+ unsigned int flags;
+} drm_radeon_surface_alloc_t;
+
+typedef struct drm_radeon_surface_free {
+ unsigned int address;
+} drm_radeon_surface_free_t;
+
+#endif
diff --git a/drivers/char/drm/radeon_drv.c b/drivers/char/drm/radeon_drv.c
new file mode 100644
index 000000000000..7b983d96e53b
--- /dev/null
+++ b/drivers/char/drm/radeon_drv.c
@@ -0,0 +1,127 @@
+/**
+ * \file radeon_drv.c
+ * ATI Radeon driver
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ radeon_PCI_IDS
+};
+
+extern drm_ioctl_desc_t radeon_ioctls[];
+extern int radeon_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_SG | DRIVER_HAVE_IRQ | DRIVER_HAVE_DMA | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+ .dev_priv_size = sizeof(drm_radeon_buf_priv_t),
+ .preinit = radeon_driver_preinit,
+ .postcleanup = radeon_driver_postcleanup,
+ .prerelease = radeon_driver_prerelease,
+ .pretakedown = radeon_driver_pretakedown,
+ .open_helper = radeon_driver_open_helper,
+ .vblank_wait = radeon_driver_vblank_wait,
+ .irq_preinstall = radeon_driver_irq_preinstall,
+ .irq_postinstall = radeon_driver_irq_postinstall,
+ .irq_uninstall = radeon_driver_irq_uninstall,
+ .irq_handler = radeon_driver_irq_handler,
+ .free_filp_priv = radeon_driver_free_filp_priv,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = radeon_ioctls,
+ .dma_ioctl = radeon_cp_buffers,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init radeon_init(void)
+{
+ driver.num_ioctls = radeon_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit radeon_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(radeon_init);
+module_exit(radeon_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/radeon_drv.h b/drivers/char/drm/radeon_drv.h
new file mode 100644
index 000000000000..5837098afae8
--- /dev/null
+++ b/drivers/char/drm/radeon_drv.h
@@ -0,0 +1,1044 @@
+/* radeon_drv.h -- Private header for radeon driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Kevin E. Martin <martin@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __RADEON_DRV_H__
+#define __RADEON_DRV_H__
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "Gareth Hughes, Keith Whitwell, others."
+
+#define DRIVER_NAME "radeon"
+#define DRIVER_DESC "ATI Radeon"
+#define DRIVER_DATE "20050311"
+
+/* Interface history:
+ *
+ * 1.1 - ??
+ * 1.2 - Add vertex2 ioctl (keith)
+ * - Add stencil capability to clear ioctl (gareth, keith)
+ * - Increase MAX_TEXTURE_LEVELS (brian)
+ * 1.3 - Add cmdbuf ioctl (keith)
+ * - Add support for new radeon packets (keith)
+ * - Add getparam ioctl (keith)
+ * - Add flip-buffers ioctl, deprecate fullscreen foo (keith).
+ * 1.4 - Add scratch registers to get_param ioctl.
+ * 1.5 - Add r200 packets to cmdbuf ioctl
+ * - Add r200 function to init ioctl
+ * - Add 'scalar2' instruction to cmdbuf
+ * 1.6 - Add static GART memory manager
+ * Add irq handler (won't be turned on unless X server knows to)
+ * Add irq ioctls and irq_active getparam.
+ * Add wait command for cmdbuf ioctl
+ * Add GART offset query for getparam
+ * 1.7 - Add support for cube map registers: R200_PP_CUBIC_FACES_[0..5]
+ * and R200_PP_CUBIC_OFFSET_F1_[0..5].
+ * Added packets R200_EMIT_PP_CUBIC_FACES_[0..5] and
+ * R200_EMIT_PP_CUBIC_OFFSETS_[0..5]. (brian)
+ * 1.8 - Remove need to call cleanup ioctls on last client exit (keith)
+ * Add 'GET' queries for starting additional clients on different VT's.
+ * 1.9 - Add DRM_IOCTL_RADEON_CP_RESUME ioctl.
+ * Add texture rectangle support for r100.
+ * 1.10- Add SETPARAM ioctl; first parameter to set is FB_LOCATION, which
+ * clients use to tell the DRM where they think the framebuffer is
+ * located in the card's address space
+ * 1.11- Add packet R200_EMIT_RB3D_BLENDCOLOR to support GL_EXT_blend_color
+ * and GL_EXT_blend_[func|equation]_separate on r200
+ * 1.12- Add R300 CP microcode support - this just loads the CP on r300
+ * (No 3D support yet - just microcode loading)
+ * 1.13- Add packet R200_EMIT_TCL_POINT_SPRITE_CNTL for ARB_point_parameters
+ * - Add hyperz support, add hyperz flags to clear ioctl.
+ * 1.14- Add support for color tiling
+ * - Add R100/R200 surface allocation/free support
+ * 1.15- Add support for texture micro tiling
+ * - Add support for r100 cube maps
+ * 1.16- Add R200_EMIT_PP_TRI_PERF_CNTL packet to support brilinear
+ * texture filtering on r200
+ */
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 16
+#define DRIVER_PATCHLEVEL 0
+
+#define GET_RING_HEAD(dev_priv) DRM_READ32( (dev_priv)->ring_rptr, 0 )
+#define SET_RING_HEAD(dev_priv,val) DRM_WRITE32( (dev_priv)->ring_rptr, 0, (val) )
+
+/*
+ * Radeon chip families
+ */
+enum radeon_family {
+ CHIP_R100,
+ CHIP_RS100,
+ CHIP_RV100,
+ CHIP_R200,
+ CHIP_RV200,
+ CHIP_RS200,
+ CHIP_R250,
+ CHIP_RS250,
+ CHIP_RV250,
+ CHIP_RV280,
+ CHIP_R300,
+ CHIP_RS300,
+ CHIP_RV350,
+ CHIP_LAST,
+};
+
+enum radeon_cp_microcode_version {
+ UCODE_R100,
+ UCODE_R200,
+ UCODE_R300,
+};
+
+/*
+ * Chip flags
+ */
+enum radeon_chip_flags {
+ CHIP_FAMILY_MASK = 0x0000ffffUL,
+ CHIP_FLAGS_MASK = 0xffff0000UL,
+ CHIP_IS_MOBILITY = 0x00010000UL,
+ CHIP_IS_IGP = 0x00020000UL,
+ CHIP_SINGLE_CRTC = 0x00040000UL,
+ CHIP_IS_AGP = 0x00080000UL,
+ CHIP_HAS_HIERZ = 0x00100000UL,
+};
+
+typedef struct drm_radeon_freelist {
+ unsigned int age;
+ drm_buf_t *buf;
+ struct drm_radeon_freelist *next;
+ struct drm_radeon_freelist *prev;
+} drm_radeon_freelist_t;
+
+typedef struct drm_radeon_ring_buffer {
+ u32 *start;
+ u32 *end;
+ int size;
+ int size_l2qw;
+
+ u32 tail;
+ u32 tail_mask;
+ int space;
+
+ int high_mark;
+} drm_radeon_ring_buffer_t;
+
+typedef struct drm_radeon_depth_clear_t {
+ u32 rb3d_cntl;
+ u32 rb3d_zstencilcntl;
+ u32 se_cntl;
+} drm_radeon_depth_clear_t;
+
+struct drm_radeon_driver_file_fields {
+ int64_t radeon_fb_delta;
+};
+
+struct mem_block {
+ struct mem_block *next;
+ struct mem_block *prev;
+ int start;
+ int size;
+ DRMFILE filp; /* 0: free, -1: heap, other: real files */
+};
+
+struct radeon_surface {
+ int refcount;
+ u32 lower;
+ u32 upper;
+ u32 flags;
+};
+
+struct radeon_virt_surface {
+ int surface_index;
+ u32 lower;
+ u32 upper;
+ u32 flags;
+ DRMFILE filp;
+};
+
+typedef struct drm_radeon_private {
+ drm_radeon_ring_buffer_t ring;
+ drm_radeon_sarea_t *sarea_priv;
+
+ u32 fb_location;
+
+ int gart_size;
+ u32 gart_vm_start;
+ unsigned long gart_buffers_offset;
+
+ int cp_mode;
+ int cp_running;
+
+ drm_radeon_freelist_t *head;
+ drm_radeon_freelist_t *tail;
+ int last_buf;
+ volatile u32 *scratch;
+ int writeback_works;
+
+ int usec_timeout;
+
+ int microcode_version;
+
+ int is_pci;
+ unsigned long phys_pci_gart;
+ dma_addr_t bus_pci_gart;
+
+ struct {
+ u32 boxes;
+ int freelist_timeouts;
+ int freelist_loops;
+ int requested_bufs;
+ int last_frame_reads;
+ int last_clear_reads;
+ int clears;
+ int texture_uploads;
+ } stats;
+
+ int do_boxes;
+ int page_flipping;
+ int current_page;
+
+ u32 color_fmt;
+ unsigned int front_offset;
+ unsigned int front_pitch;
+ unsigned int back_offset;
+ unsigned int back_pitch;
+
+ u32 depth_fmt;
+ unsigned int depth_offset;
+ unsigned int depth_pitch;
+
+ u32 front_pitch_offset;
+ u32 back_pitch_offset;
+ u32 depth_pitch_offset;
+
+ drm_radeon_depth_clear_t depth_clear;
+
+ unsigned long fb_offset;
+ unsigned long mmio_offset;
+ unsigned long ring_offset;
+ unsigned long ring_rptr_offset;
+ unsigned long buffers_offset;
+ unsigned long gart_textures_offset;
+
+ drm_local_map_t *sarea;
+ drm_local_map_t *mmio;
+ drm_local_map_t *cp_ring;
+ drm_local_map_t *ring_rptr;
+ drm_local_map_t *gart_textures;
+
+ struct mem_block *gart_heap;
+ struct mem_block *fb_heap;
+
+ /* SW interrupt */
+ wait_queue_head_t swi_queue;
+ atomic_t swi_emitted;
+
+ struct radeon_surface surfaces[RADEON_MAX_SURFACES];
+ struct radeon_virt_surface virt_surfaces[2*RADEON_MAX_SURFACES];
+
+ /* starting from here on, data is preserved accross an open */
+ uint32_t flags; /* see radeon_chip_flags */
+} drm_radeon_private_t;
+
+typedef struct drm_radeon_buf_priv {
+ u32 age;
+} drm_radeon_buf_priv_t;
+
+ /* radeon_cp.c */
+extern int radeon_cp_init( DRM_IOCTL_ARGS );
+extern int radeon_cp_start( DRM_IOCTL_ARGS );
+extern int radeon_cp_stop( DRM_IOCTL_ARGS );
+extern int radeon_cp_reset( DRM_IOCTL_ARGS );
+extern int radeon_cp_idle( DRM_IOCTL_ARGS );
+extern int radeon_cp_resume( DRM_IOCTL_ARGS );
+extern int radeon_engine_reset( DRM_IOCTL_ARGS );
+extern int radeon_fullscreen( DRM_IOCTL_ARGS );
+extern int radeon_cp_buffers( DRM_IOCTL_ARGS );
+
+extern void radeon_freelist_reset( drm_device_t *dev );
+extern drm_buf_t *radeon_freelist_get( drm_device_t *dev );
+
+extern int radeon_wait_ring( drm_radeon_private_t *dev_priv, int n );
+
+extern int radeon_do_cp_idle( drm_radeon_private_t *dev_priv );
+
+extern int radeon_driver_preinit(struct drm_device *dev, unsigned long flags);
+extern int radeon_driver_postcleanup(struct drm_device *dev);
+
+extern int radeon_mem_alloc( DRM_IOCTL_ARGS );
+extern int radeon_mem_free( DRM_IOCTL_ARGS );
+extern int radeon_mem_init_heap( DRM_IOCTL_ARGS );
+extern void radeon_mem_takedown( struct mem_block **heap );
+extern void radeon_mem_release( DRMFILE filp, struct mem_block *heap );
+
+ /* radeon_irq.c */
+extern int radeon_irq_emit( DRM_IOCTL_ARGS );
+extern int radeon_irq_wait( DRM_IOCTL_ARGS );
+
+extern void radeon_do_release(drm_device_t *dev);
+extern int radeon_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+extern irqreturn_t radeon_driver_irq_handler( DRM_IRQ_ARGS );
+extern void radeon_driver_irq_preinstall( drm_device_t *dev );
+extern void radeon_driver_irq_postinstall( drm_device_t *dev );
+extern void radeon_driver_irq_uninstall( drm_device_t *dev );
+extern void radeon_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+extern void radeon_driver_pretakedown(drm_device_t *dev);
+extern int radeon_driver_open_helper(drm_device_t *dev, drm_file_t *filp_priv);
+extern void radeon_driver_free_filp_priv(drm_device_t *dev, drm_file_t *filp_priv);
+
+extern int radeon_preinit( struct drm_device *dev, unsigned long flags );
+extern int radeon_postinit( struct drm_device *dev, unsigned long flags );
+extern int radeon_postcleanup( struct drm_device *dev );
+
+/* Flags for stats.boxes
+ */
+#define RADEON_BOX_DMA_IDLE 0x1
+#define RADEON_BOX_RING_FULL 0x2
+#define RADEON_BOX_FLIP 0x4
+#define RADEON_BOX_WAIT_IDLE 0x8
+#define RADEON_BOX_TEXTURE_LOAD 0x10
+
+
+
+/* Register definitions, register access macros and drmAddMap constants
+ * for Radeon kernel driver.
+ */
+
+#define RADEON_AGP_COMMAND 0x0f60
+#define RADEON_AUX_SCISSOR_CNTL 0x26f0
+# define RADEON_EXCLUSIVE_SCISSOR_0 (1 << 24)
+# define RADEON_EXCLUSIVE_SCISSOR_1 (1 << 25)
+# define RADEON_EXCLUSIVE_SCISSOR_2 (1 << 26)
+# define RADEON_SCISSOR_0_ENABLE (1 << 28)
+# define RADEON_SCISSOR_1_ENABLE (1 << 29)
+# define RADEON_SCISSOR_2_ENABLE (1 << 30)
+
+#define RADEON_BUS_CNTL 0x0030
+# define RADEON_BUS_MASTER_DIS (1 << 6)
+
+#define RADEON_CLOCK_CNTL_DATA 0x000c
+# define RADEON_PLL_WR_EN (1 << 7)
+#define RADEON_CLOCK_CNTL_INDEX 0x0008
+#define RADEON_CONFIG_APER_SIZE 0x0108
+#define RADEON_CRTC_OFFSET 0x0224
+#define RADEON_CRTC_OFFSET_CNTL 0x0228
+# define RADEON_CRTC_TILE_EN (1 << 15)
+# define RADEON_CRTC_OFFSET_FLIP_CNTL (1 << 16)
+#define RADEON_CRTC2_OFFSET 0x0324
+#define RADEON_CRTC2_OFFSET_CNTL 0x0328
+
+#define RADEON_RB3D_COLOROFFSET 0x1c40
+#define RADEON_RB3D_COLORPITCH 0x1c48
+
+#define RADEON_DP_GUI_MASTER_CNTL 0x146c
+# define RADEON_GMC_SRC_PITCH_OFFSET_CNTL (1 << 0)
+# define RADEON_GMC_DST_PITCH_OFFSET_CNTL (1 << 1)
+# define RADEON_GMC_BRUSH_SOLID_COLOR (13 << 4)
+# define RADEON_GMC_BRUSH_NONE (15 << 4)
+# define RADEON_GMC_DST_16BPP (4 << 8)
+# define RADEON_GMC_DST_24BPP (5 << 8)
+# define RADEON_GMC_DST_32BPP (6 << 8)
+# define RADEON_GMC_DST_DATATYPE_SHIFT 8
+# define RADEON_GMC_SRC_DATATYPE_COLOR (3 << 12)
+# define RADEON_DP_SRC_SOURCE_MEMORY (2 << 24)
+# define RADEON_DP_SRC_SOURCE_HOST_DATA (3 << 24)
+# define RADEON_GMC_CLR_CMP_CNTL_DIS (1 << 28)
+# define RADEON_GMC_WR_MSK_DIS (1 << 30)
+# define RADEON_ROP3_S 0x00cc0000
+# define RADEON_ROP3_P 0x00f00000
+#define RADEON_DP_WRITE_MASK 0x16cc
+#define RADEON_DST_PITCH_OFFSET 0x142c
+#define RADEON_DST_PITCH_OFFSET_C 0x1c80
+# define RADEON_DST_TILE_LINEAR (0 << 30)
+# define RADEON_DST_TILE_MACRO (1 << 30)
+# define RADEON_DST_TILE_MICRO (2 << 30)
+# define RADEON_DST_TILE_BOTH (3 << 30)
+
+#define RADEON_SCRATCH_REG0 0x15e0
+#define RADEON_SCRATCH_REG1 0x15e4
+#define RADEON_SCRATCH_REG2 0x15e8
+#define RADEON_SCRATCH_REG3 0x15ec
+#define RADEON_SCRATCH_REG4 0x15f0
+#define RADEON_SCRATCH_REG5 0x15f4
+#define RADEON_SCRATCH_UMSK 0x0770
+#define RADEON_SCRATCH_ADDR 0x0774
+
+#define RADEON_SCRATCHOFF( x ) (RADEON_SCRATCH_REG_OFFSET + 4*(x))
+
+#define GET_SCRATCH( x ) (dev_priv->writeback_works \
+ ? DRM_READ32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(x) ) \
+ : RADEON_READ( RADEON_SCRATCH_REG0 + 4*(x) ) )
+
+
+#define RADEON_GEN_INT_CNTL 0x0040
+# define RADEON_CRTC_VBLANK_MASK (1 << 0)
+# define RADEON_GUI_IDLE_INT_ENABLE (1 << 19)
+# define RADEON_SW_INT_ENABLE (1 << 25)
+
+#define RADEON_GEN_INT_STATUS 0x0044
+# define RADEON_CRTC_VBLANK_STAT (1 << 0)
+# define RADEON_CRTC_VBLANK_STAT_ACK (1 << 0)
+# define RADEON_GUI_IDLE_INT_TEST_ACK (1 << 19)
+# define RADEON_SW_INT_TEST (1 << 25)
+# define RADEON_SW_INT_TEST_ACK (1 << 25)
+# define RADEON_SW_INT_FIRE (1 << 26)
+
+#define RADEON_HOST_PATH_CNTL 0x0130
+# define RADEON_HDP_SOFT_RESET (1 << 26)
+# define RADEON_HDP_WC_TIMEOUT_MASK (7 << 28)
+# define RADEON_HDP_WC_TIMEOUT_28BCLK (7 << 28)
+
+#define RADEON_ISYNC_CNTL 0x1724
+# define RADEON_ISYNC_ANY2D_IDLE3D (1 << 0)
+# define RADEON_ISYNC_ANY3D_IDLE2D (1 << 1)
+# define RADEON_ISYNC_TRIG2D_IDLE3D (1 << 2)
+# define RADEON_ISYNC_TRIG3D_IDLE2D (1 << 3)
+# define RADEON_ISYNC_WAIT_IDLEGUI (1 << 4)
+# define RADEON_ISYNC_CPSCRATCH_IDLEGUI (1 << 5)
+
+#define RADEON_RBBM_GUICNTL 0x172c
+# define RADEON_HOST_DATA_SWAP_NONE (0 << 0)
+# define RADEON_HOST_DATA_SWAP_16BIT (1 << 0)
+# define RADEON_HOST_DATA_SWAP_32BIT (2 << 0)
+# define RADEON_HOST_DATA_SWAP_HDW (3 << 0)
+
+#define RADEON_MC_AGP_LOCATION 0x014c
+#define RADEON_MC_FB_LOCATION 0x0148
+#define RADEON_MCLK_CNTL 0x0012
+# define RADEON_FORCEON_MCLKA (1 << 16)
+# define RADEON_FORCEON_MCLKB (1 << 17)
+# define RADEON_FORCEON_YCLKA (1 << 18)
+# define RADEON_FORCEON_YCLKB (1 << 19)
+# define RADEON_FORCEON_MC (1 << 20)
+# define RADEON_FORCEON_AIC (1 << 21)
+
+#define RADEON_PP_BORDER_COLOR_0 0x1d40
+#define RADEON_PP_BORDER_COLOR_1 0x1d44
+#define RADEON_PP_BORDER_COLOR_2 0x1d48
+#define RADEON_PP_CNTL 0x1c38
+# define RADEON_SCISSOR_ENABLE (1 << 1)
+#define RADEON_PP_LUM_MATRIX 0x1d00
+#define RADEON_PP_MISC 0x1c14
+#define RADEON_PP_ROT_MATRIX_0 0x1d58
+#define RADEON_PP_TXFILTER_0 0x1c54
+#define RADEON_PP_TXOFFSET_0 0x1c5c
+#define RADEON_PP_TXFILTER_1 0x1c6c
+#define RADEON_PP_TXFILTER_2 0x1c84
+
+#define RADEON_RB2D_DSTCACHE_CTLSTAT 0x342c
+# define RADEON_RB2D_DC_FLUSH (3 << 0)
+# define RADEON_RB2D_DC_FREE (3 << 2)
+# define RADEON_RB2D_DC_FLUSH_ALL 0xf
+# define RADEON_RB2D_DC_BUSY (1 << 31)
+#define RADEON_RB3D_CNTL 0x1c3c
+# define RADEON_ALPHA_BLEND_ENABLE (1 << 0)
+# define RADEON_PLANE_MASK_ENABLE (1 << 1)
+# define RADEON_DITHER_ENABLE (1 << 2)
+# define RADEON_ROUND_ENABLE (1 << 3)
+# define RADEON_SCALE_DITHER_ENABLE (1 << 4)
+# define RADEON_DITHER_INIT (1 << 5)
+# define RADEON_ROP_ENABLE (1 << 6)
+# define RADEON_STENCIL_ENABLE (1 << 7)
+# define RADEON_Z_ENABLE (1 << 8)
+# define RADEON_ZBLOCK16 (1 << 15)
+#define RADEON_RB3D_DEPTHOFFSET 0x1c24
+#define RADEON_RB3D_DEPTHCLEARVALUE 0x3230
+#define RADEON_RB3D_DEPTHPITCH 0x1c28
+#define RADEON_RB3D_PLANEMASK 0x1d84
+#define RADEON_RB3D_STENCILREFMASK 0x1d7c
+#define RADEON_RB3D_ZCACHE_MODE 0x3250
+#define RADEON_RB3D_ZCACHE_CTLSTAT 0x3254
+# define RADEON_RB3D_ZC_FLUSH (1 << 0)
+# define RADEON_RB3D_ZC_FREE (1 << 2)
+# define RADEON_RB3D_ZC_FLUSH_ALL 0x5
+# define RADEON_RB3D_ZC_BUSY (1 << 31)
+#define RADEON_RB3D_ZSTENCILCNTL 0x1c2c
+# define RADEON_Z_TEST_MASK (7 << 4)
+# define RADEON_Z_TEST_ALWAYS (7 << 4)
+# define RADEON_Z_HIERARCHY_ENABLE (1 << 8)
+# define RADEON_STENCIL_TEST_ALWAYS (7 << 12)
+# define RADEON_STENCIL_S_FAIL_REPLACE (2 << 16)
+# define RADEON_STENCIL_ZPASS_REPLACE (2 << 20)
+# define RADEON_STENCIL_ZFAIL_REPLACE (2 << 24)
+# define RADEON_Z_COMPRESSION_ENABLE (1 << 28)
+# define RADEON_FORCE_Z_DIRTY (1 << 29)
+# define RADEON_Z_WRITE_ENABLE (1 << 30)
+# define RADEON_Z_DECOMPRESSION_ENABLE (1 << 31)
+#define RADEON_RBBM_SOFT_RESET 0x00f0
+# define RADEON_SOFT_RESET_CP (1 << 0)
+# define RADEON_SOFT_RESET_HI (1 << 1)
+# define RADEON_SOFT_RESET_SE (1 << 2)
+# define RADEON_SOFT_RESET_RE (1 << 3)
+# define RADEON_SOFT_RESET_PP (1 << 4)
+# define RADEON_SOFT_RESET_E2 (1 << 5)
+# define RADEON_SOFT_RESET_RB (1 << 6)
+# define RADEON_SOFT_RESET_HDP (1 << 7)
+#define RADEON_RBBM_STATUS 0x0e40
+# define RADEON_RBBM_FIFOCNT_MASK 0x007f
+# define RADEON_RBBM_ACTIVE (1 << 31)
+#define RADEON_RE_LINE_PATTERN 0x1cd0
+#define RADEON_RE_MISC 0x26c4
+#define RADEON_RE_TOP_LEFT 0x26c0
+#define RADEON_RE_WIDTH_HEIGHT 0x1c44
+#define RADEON_RE_STIPPLE_ADDR 0x1cc8
+#define RADEON_RE_STIPPLE_DATA 0x1ccc
+
+#define RADEON_SCISSOR_TL_0 0x1cd8
+#define RADEON_SCISSOR_BR_0 0x1cdc
+#define RADEON_SCISSOR_TL_1 0x1ce0
+#define RADEON_SCISSOR_BR_1 0x1ce4
+#define RADEON_SCISSOR_TL_2 0x1ce8
+#define RADEON_SCISSOR_BR_2 0x1cec
+#define RADEON_SE_COORD_FMT 0x1c50
+#define RADEON_SE_CNTL 0x1c4c
+# define RADEON_FFACE_CULL_CW (0 << 0)
+# define RADEON_BFACE_SOLID (3 << 1)
+# define RADEON_FFACE_SOLID (3 << 3)
+# define RADEON_FLAT_SHADE_VTX_LAST (3 << 6)
+# define RADEON_DIFFUSE_SHADE_FLAT (1 << 8)
+# define RADEON_DIFFUSE_SHADE_GOURAUD (2 << 8)
+# define RADEON_ALPHA_SHADE_FLAT (1 << 10)
+# define RADEON_ALPHA_SHADE_GOURAUD (2 << 10)
+# define RADEON_SPECULAR_SHADE_FLAT (1 << 12)
+# define RADEON_SPECULAR_SHADE_GOURAUD (2 << 12)
+# define RADEON_FOG_SHADE_FLAT (1 << 14)
+# define RADEON_FOG_SHADE_GOURAUD (2 << 14)
+# define RADEON_VPORT_XY_XFORM_ENABLE (1 << 24)
+# define RADEON_VPORT_Z_XFORM_ENABLE (1 << 25)
+# define RADEON_VTX_PIX_CENTER_OGL (1 << 27)
+# define RADEON_ROUND_MODE_TRUNC (0 << 28)
+# define RADEON_ROUND_PREC_8TH_PIX (1 << 30)
+#define RADEON_SE_CNTL_STATUS 0x2140
+#define RADEON_SE_LINE_WIDTH 0x1db8
+#define RADEON_SE_VPORT_XSCALE 0x1d98
+#define RADEON_SE_ZBIAS_FACTOR 0x1db0
+#define RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED 0x2210
+#define RADEON_SE_TCL_OUTPUT_VTX_FMT 0x2254
+#define RADEON_SE_TCL_VECTOR_INDX_REG 0x2200
+# define RADEON_VEC_INDX_OCTWORD_STRIDE_SHIFT 16
+# define RADEON_VEC_INDX_DWORD_COUNT_SHIFT 28
+#define RADEON_SE_TCL_VECTOR_DATA_REG 0x2204
+#define RADEON_SE_TCL_SCALAR_INDX_REG 0x2208
+# define RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT 16
+#define RADEON_SE_TCL_SCALAR_DATA_REG 0x220C
+#define RADEON_SURFACE_ACCESS_FLAGS 0x0bf8
+#define RADEON_SURFACE_ACCESS_CLR 0x0bfc
+#define RADEON_SURFACE_CNTL 0x0b00
+# define RADEON_SURF_TRANSLATION_DIS (1 << 8)
+# define RADEON_NONSURF_AP0_SWP_MASK (3 << 20)
+# define RADEON_NONSURF_AP0_SWP_LITTLE (0 << 20)
+# define RADEON_NONSURF_AP0_SWP_BIG16 (1 << 20)
+# define RADEON_NONSURF_AP0_SWP_BIG32 (2 << 20)
+# define RADEON_NONSURF_AP1_SWP_MASK (3 << 22)
+# define RADEON_NONSURF_AP1_SWP_LITTLE (0 << 22)
+# define RADEON_NONSURF_AP1_SWP_BIG16 (1 << 22)
+# define RADEON_NONSURF_AP1_SWP_BIG32 (2 << 22)
+#define RADEON_SURFACE0_INFO 0x0b0c
+# define RADEON_SURF_PITCHSEL_MASK (0x1ff << 0)
+# define RADEON_SURF_TILE_MODE_MASK (3 << 16)
+# define RADEON_SURF_TILE_MODE_MACRO (0 << 16)
+# define RADEON_SURF_TILE_MODE_MICRO (1 << 16)
+# define RADEON_SURF_TILE_MODE_32BIT_Z (2 << 16)
+# define RADEON_SURF_TILE_MODE_16BIT_Z (3 << 16)
+#define RADEON_SURFACE0_LOWER_BOUND 0x0b04
+#define RADEON_SURFACE0_UPPER_BOUND 0x0b08
+# define RADEON_SURF_ADDRESS_FIXED_MASK (0x3ff << 0)
+#define RADEON_SURFACE1_INFO 0x0b1c
+#define RADEON_SURFACE1_LOWER_BOUND 0x0b14
+#define RADEON_SURFACE1_UPPER_BOUND 0x0b18
+#define RADEON_SURFACE2_INFO 0x0b2c
+#define RADEON_SURFACE2_LOWER_BOUND 0x0b24
+#define RADEON_SURFACE2_UPPER_BOUND 0x0b28
+#define RADEON_SURFACE3_INFO 0x0b3c
+#define RADEON_SURFACE3_LOWER_BOUND 0x0b34
+#define RADEON_SURFACE3_UPPER_BOUND 0x0b38
+#define RADEON_SURFACE4_INFO 0x0b4c
+#define RADEON_SURFACE4_LOWER_BOUND 0x0b44
+#define RADEON_SURFACE4_UPPER_BOUND 0x0b48
+#define RADEON_SURFACE5_INFO 0x0b5c
+#define RADEON_SURFACE5_LOWER_BOUND 0x0b54
+#define RADEON_SURFACE5_UPPER_BOUND 0x0b58
+#define RADEON_SURFACE6_INFO 0x0b6c
+#define RADEON_SURFACE6_LOWER_BOUND 0x0b64
+#define RADEON_SURFACE6_UPPER_BOUND 0x0b68
+#define RADEON_SURFACE7_INFO 0x0b7c
+#define RADEON_SURFACE7_LOWER_BOUND 0x0b74
+#define RADEON_SURFACE7_UPPER_BOUND 0x0b78
+#define RADEON_SW_SEMAPHORE 0x013c
+
+#define RADEON_WAIT_UNTIL 0x1720
+# define RADEON_WAIT_CRTC_PFLIP (1 << 0)
+# define RADEON_WAIT_2D_IDLECLEAN (1 << 16)
+# define RADEON_WAIT_3D_IDLECLEAN (1 << 17)
+# define RADEON_WAIT_HOST_IDLECLEAN (1 << 18)
+
+#define RADEON_RB3D_ZMASKOFFSET 0x3234
+#define RADEON_RB3D_ZSTENCILCNTL 0x1c2c
+# define RADEON_DEPTH_FORMAT_16BIT_INT_Z (0 << 0)
+# define RADEON_DEPTH_FORMAT_24BIT_INT_Z (2 << 0)
+
+
+/* CP registers */
+#define RADEON_CP_ME_RAM_ADDR 0x07d4
+#define RADEON_CP_ME_RAM_RADDR 0x07d8
+#define RADEON_CP_ME_RAM_DATAH 0x07dc
+#define RADEON_CP_ME_RAM_DATAL 0x07e0
+
+#define RADEON_CP_RB_BASE 0x0700
+#define RADEON_CP_RB_CNTL 0x0704
+# define RADEON_BUF_SWAP_32BIT (2 << 16)
+#define RADEON_CP_RB_RPTR_ADDR 0x070c
+#define RADEON_CP_RB_RPTR 0x0710
+#define RADEON_CP_RB_WPTR 0x0714
+
+#define RADEON_CP_RB_WPTR_DELAY 0x0718
+# define RADEON_PRE_WRITE_TIMER_SHIFT 0
+# define RADEON_PRE_WRITE_LIMIT_SHIFT 23
+
+#define RADEON_CP_IB_BASE 0x0738
+
+#define RADEON_CP_CSQ_CNTL 0x0740
+# define RADEON_CSQ_CNT_PRIMARY_MASK (0xff << 0)
+# define RADEON_CSQ_PRIDIS_INDDIS (0 << 28)
+# define RADEON_CSQ_PRIPIO_INDDIS (1 << 28)
+# define RADEON_CSQ_PRIBM_INDDIS (2 << 28)
+# define RADEON_CSQ_PRIPIO_INDBM (3 << 28)
+# define RADEON_CSQ_PRIBM_INDBM (4 << 28)
+# define RADEON_CSQ_PRIPIO_INDPIO (15 << 28)
+
+#define RADEON_AIC_CNTL 0x01d0
+# define RADEON_PCIGART_TRANSLATE_EN (1 << 0)
+#define RADEON_AIC_STAT 0x01d4
+#define RADEON_AIC_PT_BASE 0x01d8
+#define RADEON_AIC_LO_ADDR 0x01dc
+#define RADEON_AIC_HI_ADDR 0x01e0
+#define RADEON_AIC_TLB_ADDR 0x01e4
+#define RADEON_AIC_TLB_DATA 0x01e8
+
+/* CP command packets */
+#define RADEON_CP_PACKET0 0x00000000
+# define RADEON_ONE_REG_WR (1 << 15)
+#define RADEON_CP_PACKET1 0x40000000
+#define RADEON_CP_PACKET2 0x80000000
+#define RADEON_CP_PACKET3 0xC0000000
+# define RADEON_3D_RNDR_GEN_INDX_PRIM 0x00002300
+# define RADEON_WAIT_FOR_IDLE 0x00002600
+# define RADEON_3D_DRAW_VBUF 0x00002800
+# define RADEON_3D_DRAW_IMMD 0x00002900
+# define RADEON_3D_DRAW_INDX 0x00002A00
+# define RADEON_3D_LOAD_VBPNTR 0x00002F00
+# define RADEON_MPEG_IDCT_MACROBLOCK 0x00003000
+# define RADEON_MPEG_IDCT_MACROBLOCK_REV 0x00003100
+# define RADEON_3D_CLEAR_ZMASK 0x00003200
+# define RADEON_3D_CLEAR_HIZ 0x00003700
+# define RADEON_CNTL_HOSTDATA_BLT 0x00009400
+# define RADEON_CNTL_PAINT_MULTI 0x00009A00
+# define RADEON_CNTL_BITBLT_MULTI 0x00009B00
+# define RADEON_CNTL_SET_SCISSORS 0xC0001E00
+
+#define RADEON_CP_PACKET_MASK 0xC0000000
+#define RADEON_CP_PACKET_COUNT_MASK 0x3fff0000
+#define RADEON_CP_PACKET0_REG_MASK 0x000007ff
+#define RADEON_CP_PACKET1_REG0_MASK 0x000007ff
+#define RADEON_CP_PACKET1_REG1_MASK 0x003ff800
+
+#define RADEON_VTX_Z_PRESENT (1 << 31)
+#define RADEON_VTX_PKCOLOR_PRESENT (1 << 3)
+
+#define RADEON_PRIM_TYPE_NONE (0 << 0)
+#define RADEON_PRIM_TYPE_POINT (1 << 0)
+#define RADEON_PRIM_TYPE_LINE (2 << 0)
+#define RADEON_PRIM_TYPE_LINE_STRIP (3 << 0)
+#define RADEON_PRIM_TYPE_TRI_LIST (4 << 0)
+#define RADEON_PRIM_TYPE_TRI_FAN (5 << 0)
+#define RADEON_PRIM_TYPE_TRI_STRIP (6 << 0)
+#define RADEON_PRIM_TYPE_TRI_TYPE2 (7 << 0)
+#define RADEON_PRIM_TYPE_RECT_LIST (8 << 0)
+#define RADEON_PRIM_TYPE_3VRT_POINT_LIST (9 << 0)
+#define RADEON_PRIM_TYPE_3VRT_LINE_LIST (10 << 0)
+#define RADEON_PRIM_TYPE_MASK 0xf
+#define RADEON_PRIM_WALK_IND (1 << 4)
+#define RADEON_PRIM_WALK_LIST (2 << 4)
+#define RADEON_PRIM_WALK_RING (3 << 4)
+#define RADEON_COLOR_ORDER_BGRA (0 << 6)
+#define RADEON_COLOR_ORDER_RGBA (1 << 6)
+#define RADEON_MAOS_ENABLE (1 << 7)
+#define RADEON_VTX_FMT_R128_MODE (0 << 8)
+#define RADEON_VTX_FMT_RADEON_MODE (1 << 8)
+#define RADEON_NUM_VERTICES_SHIFT 16
+
+#define RADEON_COLOR_FORMAT_CI8 2
+#define RADEON_COLOR_FORMAT_ARGB1555 3
+#define RADEON_COLOR_FORMAT_RGB565 4
+#define RADEON_COLOR_FORMAT_ARGB8888 6
+#define RADEON_COLOR_FORMAT_RGB332 7
+#define RADEON_COLOR_FORMAT_RGB8 9
+#define RADEON_COLOR_FORMAT_ARGB4444 15
+
+#define RADEON_TXFORMAT_I8 0
+#define RADEON_TXFORMAT_AI88 1
+#define RADEON_TXFORMAT_RGB332 2
+#define RADEON_TXFORMAT_ARGB1555 3
+#define RADEON_TXFORMAT_RGB565 4
+#define RADEON_TXFORMAT_ARGB4444 5
+#define RADEON_TXFORMAT_ARGB8888 6
+#define RADEON_TXFORMAT_RGBA8888 7
+#define RADEON_TXFORMAT_Y8 8
+#define RADEON_TXFORMAT_VYUY422 10
+#define RADEON_TXFORMAT_YVYU422 11
+#define RADEON_TXFORMAT_DXT1 12
+#define RADEON_TXFORMAT_DXT23 14
+#define RADEON_TXFORMAT_DXT45 15
+
+#define R200_PP_TXCBLEND_0 0x2f00
+#define R200_PP_TXCBLEND_1 0x2f10
+#define R200_PP_TXCBLEND_2 0x2f20
+#define R200_PP_TXCBLEND_3 0x2f30
+#define R200_PP_TXCBLEND_4 0x2f40
+#define R200_PP_TXCBLEND_5 0x2f50
+#define R200_PP_TXCBLEND_6 0x2f60
+#define R200_PP_TXCBLEND_7 0x2f70
+#define R200_SE_TCL_LIGHT_MODEL_CTL_0 0x2268
+#define R200_PP_TFACTOR_0 0x2ee0
+#define R200_SE_VTX_FMT_0 0x2088
+#define R200_SE_VAP_CNTL 0x2080
+#define R200_SE_TCL_MATRIX_SEL_0 0x2230
+#define R200_SE_TCL_TEX_PROC_CTL_2 0x22a8
+#define R200_SE_TCL_UCP_VERT_BLEND_CTL 0x22c0
+#define R200_PP_TXFILTER_5 0x2ca0
+#define R200_PP_TXFILTER_4 0x2c80
+#define R200_PP_TXFILTER_3 0x2c60
+#define R200_PP_TXFILTER_2 0x2c40
+#define R200_PP_TXFILTER_1 0x2c20
+#define R200_PP_TXFILTER_0 0x2c00
+#define R200_PP_TXOFFSET_5 0x2d78
+#define R200_PP_TXOFFSET_4 0x2d60
+#define R200_PP_TXOFFSET_3 0x2d48
+#define R200_PP_TXOFFSET_2 0x2d30
+#define R200_PP_TXOFFSET_1 0x2d18
+#define R200_PP_TXOFFSET_0 0x2d00
+
+#define R200_PP_CUBIC_FACES_0 0x2c18
+#define R200_PP_CUBIC_FACES_1 0x2c38
+#define R200_PP_CUBIC_FACES_2 0x2c58
+#define R200_PP_CUBIC_FACES_3 0x2c78
+#define R200_PP_CUBIC_FACES_4 0x2c98
+#define R200_PP_CUBIC_FACES_5 0x2cb8
+#define R200_PP_CUBIC_OFFSET_F1_0 0x2d04
+#define R200_PP_CUBIC_OFFSET_F2_0 0x2d08
+#define R200_PP_CUBIC_OFFSET_F3_0 0x2d0c
+#define R200_PP_CUBIC_OFFSET_F4_0 0x2d10
+#define R200_PP_CUBIC_OFFSET_F5_0 0x2d14
+#define R200_PP_CUBIC_OFFSET_F1_1 0x2d1c
+#define R200_PP_CUBIC_OFFSET_F2_1 0x2d20
+#define R200_PP_CUBIC_OFFSET_F3_1 0x2d24
+#define R200_PP_CUBIC_OFFSET_F4_1 0x2d28
+#define R200_PP_CUBIC_OFFSET_F5_1 0x2d2c
+#define R200_PP_CUBIC_OFFSET_F1_2 0x2d34
+#define R200_PP_CUBIC_OFFSET_F2_2 0x2d38
+#define R200_PP_CUBIC_OFFSET_F3_2 0x2d3c
+#define R200_PP_CUBIC_OFFSET_F4_2 0x2d40
+#define R200_PP_CUBIC_OFFSET_F5_2 0x2d44
+#define R200_PP_CUBIC_OFFSET_F1_3 0x2d4c
+#define R200_PP_CUBIC_OFFSET_F2_3 0x2d50
+#define R200_PP_CUBIC_OFFSET_F3_3 0x2d54
+#define R200_PP_CUBIC_OFFSET_F4_3 0x2d58
+#define R200_PP_CUBIC_OFFSET_F5_3 0x2d5c
+#define R200_PP_CUBIC_OFFSET_F1_4 0x2d64
+#define R200_PP_CUBIC_OFFSET_F2_4 0x2d68
+#define R200_PP_CUBIC_OFFSET_F3_4 0x2d6c
+#define R200_PP_CUBIC_OFFSET_F4_4 0x2d70
+#define R200_PP_CUBIC_OFFSET_F5_4 0x2d74
+#define R200_PP_CUBIC_OFFSET_F1_5 0x2d7c
+#define R200_PP_CUBIC_OFFSET_F2_5 0x2d80
+#define R200_PP_CUBIC_OFFSET_F3_5 0x2d84
+#define R200_PP_CUBIC_OFFSET_F4_5 0x2d88
+#define R200_PP_CUBIC_OFFSET_F5_5 0x2d8c
+
+#define R200_RE_AUX_SCISSOR_CNTL 0x26f0
+#define R200_SE_VTE_CNTL 0x20b0
+#define R200_SE_TCL_OUTPUT_VTX_COMP_SEL 0x2250
+#define R200_PP_TAM_DEBUG3 0x2d9c
+#define R200_PP_CNTL_X 0x2cc4
+#define R200_SE_VAP_CNTL_STATUS 0x2140
+#define R200_RE_SCISSOR_TL_0 0x1cd8
+#define R200_RE_SCISSOR_TL_1 0x1ce0
+#define R200_RE_SCISSOR_TL_2 0x1ce8
+#define R200_RB3D_DEPTHXY_OFFSET 0x1d60
+#define R200_RE_AUX_SCISSOR_CNTL 0x26f0
+#define R200_SE_VTX_STATE_CNTL 0x2180
+#define R200_RE_POINTSIZE 0x2648
+#define R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0 0x2254
+
+#define RADEON_PP_TEX_SIZE_0 0x1d04 /* NPOT */
+#define RADEON_PP_TEX_SIZE_1 0x1d0c
+#define RADEON_PP_TEX_SIZE_2 0x1d14
+
+#define RADEON_PP_CUBIC_FACES_0 0x1d24
+#define RADEON_PP_CUBIC_FACES_1 0x1d28
+#define RADEON_PP_CUBIC_FACES_2 0x1d2c
+#define RADEON_PP_CUBIC_OFFSET_T0_0 0x1dd0 /* bits [31:5] */
+#define RADEON_PP_CUBIC_OFFSET_T1_0 0x1e00
+#define RADEON_PP_CUBIC_OFFSET_T2_0 0x1e14
+
+#define SE_VAP_CNTL__TCL_ENA_MASK 0x00000001
+#define SE_VAP_CNTL__FORCE_W_TO_ONE_MASK 0x00010000
+#define SE_VAP_CNTL__VF_MAX_VTX_NUM__SHIFT 0x00000012
+#define SE_VTE_CNTL__VTX_XY_FMT_MASK 0x00000100
+#define SE_VTE_CNTL__VTX_Z_FMT_MASK 0x00000200
+#define SE_VTX_FMT_0__VTX_Z0_PRESENT_MASK 0x00000001
+#define SE_VTX_FMT_0__VTX_W0_PRESENT_MASK 0x00000002
+#define SE_VTX_FMT_0__VTX_COLOR_0_FMT__SHIFT 0x0000000b
+#define R200_3D_DRAW_IMMD_2 0xC0003500
+#define R200_SE_VTX_FMT_1 0x208c
+#define R200_RE_CNTL 0x1c50
+
+#define R200_RB3D_BLENDCOLOR 0x3218
+
+#define R200_SE_TCL_POINT_SPRITE_CNTL 0x22c4
+
+#define R200_PP_TRI_PERF 0x2cf8
+
+/* Constants */
+#define RADEON_MAX_USEC_TIMEOUT 100000 /* 100 ms */
+
+#define RADEON_LAST_FRAME_REG RADEON_SCRATCH_REG0
+#define RADEON_LAST_DISPATCH_REG RADEON_SCRATCH_REG1
+#define RADEON_LAST_CLEAR_REG RADEON_SCRATCH_REG2
+#define RADEON_LAST_SWI_REG RADEON_SCRATCH_REG3
+#define RADEON_LAST_DISPATCH 1
+
+#define RADEON_MAX_VB_AGE 0x7fffffff
+#define RADEON_MAX_VB_VERTS (0xffff)
+
+#define RADEON_RING_HIGH_MARK 128
+
+#define RADEON_READ(reg) DRM_READ32( dev_priv->mmio, (reg) )
+#define RADEON_WRITE(reg,val) DRM_WRITE32( dev_priv->mmio, (reg), (val) )
+#define RADEON_READ8(reg) DRM_READ8( dev_priv->mmio, (reg) )
+#define RADEON_WRITE8(reg,val) DRM_WRITE8( dev_priv->mmio, (reg), (val) )
+
+#define RADEON_WRITE_PLL( addr, val ) \
+do { \
+ RADEON_WRITE8( RADEON_CLOCK_CNTL_INDEX, \
+ ((addr) & 0x1f) | RADEON_PLL_WR_EN ); \
+ RADEON_WRITE( RADEON_CLOCK_CNTL_DATA, (val) ); \
+} while (0)
+
+#define CP_PACKET0( reg, n ) \
+ (RADEON_CP_PACKET0 | ((n) << 16) | ((reg) >> 2))
+#define CP_PACKET0_TABLE( reg, n ) \
+ (RADEON_CP_PACKET0 | RADEON_ONE_REG_WR | ((n) << 16) | ((reg) >> 2))
+#define CP_PACKET1( reg0, reg1 ) \
+ (RADEON_CP_PACKET1 | (((reg1) >> 2) << 15) | ((reg0) >> 2))
+#define CP_PACKET2() \
+ (RADEON_CP_PACKET2)
+#define CP_PACKET3( pkt, n ) \
+ (RADEON_CP_PACKET3 | (pkt) | ((n) << 16))
+
+
+/* ================================================================
+ * Engine control helper macros
+ */
+
+#define RADEON_WAIT_UNTIL_2D_IDLE() do { \
+ OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) ); \
+ OUT_RING( (RADEON_WAIT_2D_IDLECLEAN | \
+ RADEON_WAIT_HOST_IDLECLEAN) ); \
+} while (0)
+
+#define RADEON_WAIT_UNTIL_3D_IDLE() do { \
+ OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) ); \
+ OUT_RING( (RADEON_WAIT_3D_IDLECLEAN | \
+ RADEON_WAIT_HOST_IDLECLEAN) ); \
+} while (0)
+
+#define RADEON_WAIT_UNTIL_IDLE() do { \
+ OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) ); \
+ OUT_RING( (RADEON_WAIT_2D_IDLECLEAN | \
+ RADEON_WAIT_3D_IDLECLEAN | \
+ RADEON_WAIT_HOST_IDLECLEAN) ); \
+} while (0)
+
+#define RADEON_WAIT_UNTIL_PAGE_FLIPPED() do { \
+ OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) ); \
+ OUT_RING( RADEON_WAIT_CRTC_PFLIP ); \
+} while (0)
+
+#define RADEON_FLUSH_CACHE() do { \
+ OUT_RING( CP_PACKET0( RADEON_RB2D_DSTCACHE_CTLSTAT, 0 ) ); \
+ OUT_RING( RADEON_RB2D_DC_FLUSH ); \
+} while (0)
+
+#define RADEON_PURGE_CACHE() do { \
+ OUT_RING( CP_PACKET0( RADEON_RB2D_DSTCACHE_CTLSTAT, 0 ) ); \
+ OUT_RING( RADEON_RB2D_DC_FLUSH_ALL ); \
+} while (0)
+
+#define RADEON_FLUSH_ZCACHE() do { \
+ OUT_RING( CP_PACKET0( RADEON_RB3D_ZCACHE_CTLSTAT, 0 ) ); \
+ OUT_RING( RADEON_RB3D_ZC_FLUSH ); \
+} while (0)
+
+#define RADEON_PURGE_ZCACHE() do { \
+ OUT_RING( CP_PACKET0( RADEON_RB3D_ZCACHE_CTLSTAT, 0 ) ); \
+ OUT_RING( RADEON_RB3D_ZC_FLUSH_ALL ); \
+} while (0)
+
+
+/* ================================================================
+ * Misc helper macros
+ */
+
+/* Perfbox functionality only.
+ */
+#define RING_SPACE_TEST_WITH_RETURN( dev_priv ) \
+do { \
+ if (!(dev_priv->stats.boxes & RADEON_BOX_DMA_IDLE)) { \
+ u32 head = GET_RING_HEAD( dev_priv ); \
+ if (head == dev_priv->ring.tail) \
+ dev_priv->stats.boxes |= RADEON_BOX_DMA_IDLE; \
+ } \
+} while (0)
+
+#define VB_AGE_TEST_WITH_RETURN( dev_priv ) \
+do { \
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv; \
+ if ( sarea_priv->last_dispatch >= RADEON_MAX_VB_AGE ) { \
+ int __ret = radeon_do_cp_idle( dev_priv ); \
+ if ( __ret ) return __ret; \
+ sarea_priv->last_dispatch = 0; \
+ radeon_freelist_reset( dev ); \
+ } \
+} while (0)
+
+#define RADEON_DISPATCH_AGE( age ) do { \
+ OUT_RING( CP_PACKET0( RADEON_LAST_DISPATCH_REG, 0 ) ); \
+ OUT_RING( age ); \
+} while (0)
+
+#define RADEON_FRAME_AGE( age ) do { \
+ OUT_RING( CP_PACKET0( RADEON_LAST_FRAME_REG, 0 ) ); \
+ OUT_RING( age ); \
+} while (0)
+
+#define RADEON_CLEAR_AGE( age ) do { \
+ OUT_RING( CP_PACKET0( RADEON_LAST_CLEAR_REG, 0 ) ); \
+ OUT_RING( age ); \
+} while (0)
+
+
+/* ================================================================
+ * Ring control
+ */
+
+#define RADEON_VERBOSE 0
+
+#define RING_LOCALS int write, _nr; unsigned int mask; u32 *ring;
+
+#define BEGIN_RING( n ) do { \
+ if ( RADEON_VERBOSE ) { \
+ DRM_INFO( "BEGIN_RING( %d ) in %s\n", \
+ n, __FUNCTION__ ); \
+ } \
+ if ( dev_priv->ring.space <= (n) * sizeof(u32) ) { \
+ COMMIT_RING(); \
+ radeon_wait_ring( dev_priv, (n) * sizeof(u32) ); \
+ } \
+ _nr = n; dev_priv->ring.space -= (n) * sizeof(u32); \
+ ring = dev_priv->ring.start; \
+ write = dev_priv->ring.tail; \
+ mask = dev_priv->ring.tail_mask; \
+} while (0)
+
+#define ADVANCE_RING() do { \
+ if ( RADEON_VERBOSE ) { \
+ DRM_INFO( "ADVANCE_RING() wr=0x%06x tail=0x%06x\n", \
+ write, dev_priv->ring.tail ); \
+ } \
+ if (((dev_priv->ring.tail + _nr) & mask) != write) { \
+ DRM_ERROR( \
+ "ADVANCE_RING(): mismatch: nr: %x write: %x line: %d\n", \
+ ((dev_priv->ring.tail + _nr) & mask), \
+ write, __LINE__); \
+ } else \
+ dev_priv->ring.tail = write; \
+} while (0)
+
+#define COMMIT_RING() do { \
+ /* Flush writes to ring */ \
+ DRM_MEMORYBARRIER(); \
+ GET_RING_HEAD( dev_priv ); \
+ RADEON_WRITE( RADEON_CP_RB_WPTR, dev_priv->ring.tail ); \
+ /* read from PCI bus to ensure correct posting */ \
+ RADEON_READ( RADEON_CP_RB_RPTR ); \
+} while (0)
+
+#define OUT_RING( x ) do { \
+ if ( RADEON_VERBOSE ) { \
+ DRM_INFO( " OUT_RING( 0x%08x ) at 0x%x\n", \
+ (unsigned int)(x), write ); \
+ } \
+ ring[write++] = (x); \
+ write &= mask; \
+} while (0)
+
+#define OUT_RING_REG( reg, val ) do { \
+ OUT_RING( CP_PACKET0( reg, 0 ) ); \
+ OUT_RING( val ); \
+} while (0)
+
+
+#define OUT_RING_TABLE( tab, sz ) do { \
+ int _size = (sz); \
+ int *_tab = (int *)(tab); \
+ \
+ if (write + _size > mask) { \
+ int _i = (mask+1) - write; \
+ _size -= _i; \
+ while (_i > 0 ) { \
+ *(int *)(ring + write) = *_tab++; \
+ write++; \
+ _i--; \
+ } \
+ write = 0; \
+ _tab += _i; \
+ } \
+ \
+ while (_size > 0) { \
+ *(ring + write) = *_tab++; \
+ write++; \
+ _size--; \
+ } \
+ write &= mask; \
+} while (0)
+
+
+#endif /* __RADEON_DRV_H__ */
diff --git a/drivers/char/drm/radeon_irq.c b/drivers/char/drm/radeon_irq.c
new file mode 100644
index 000000000000..5b18bee6492e
--- /dev/null
+++ b/drivers/char/drm/radeon_irq.c
@@ -0,0 +1,251 @@
+/* radeon_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved.
+ *
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Keith Whitwell <keith@tungstengraphics.com>
+ * Michel D�zer <michel@daenzer.net>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* Interrupts - Used for device synchronization and flushing in the
+ * following circumstances:
+ *
+ * - Exclusive FB access with hw idle:
+ * - Wait for GUI Idle (?) interrupt, then do normal flush.
+ *
+ * - Frame throttling, NV_fence:
+ * - Drop marker irq's into command stream ahead of time.
+ * - Wait on irq's with lock *not held*
+ * - Check each for termination condition
+ *
+ * - Internally in cp_getbuffer, etc:
+ * - as above, but wait with lock held???
+ *
+ * NOTE: These functions are misleadingly named -- the irq's aren't
+ * tied to dma at all, this is just a hangover from dri prehistory.
+ */
+
+irqreturn_t radeon_driver_irq_handler( DRM_IRQ_ARGS )
+{
+ drm_device_t *dev = (drm_device_t *) arg;
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+ u32 stat;
+
+ /* Only consider the bits we're interested in - others could be used
+ * outside the DRM
+ */
+ stat = RADEON_READ(RADEON_GEN_INT_STATUS)
+ & (RADEON_SW_INT_TEST | RADEON_CRTC_VBLANK_STAT);
+ if (!stat)
+ return IRQ_NONE;
+
+ /* SW interrupt */
+ if (stat & RADEON_SW_INT_TEST) {
+ DRM_WAKEUP( &dev_priv->swi_queue );
+ }
+
+ /* VBLANK interrupt */
+ if (stat & RADEON_CRTC_VBLANK_STAT) {
+ atomic_inc(&dev->vbl_received);
+ DRM_WAKEUP(&dev->vbl_queue);
+ drm_vbl_send_signals( dev );
+ }
+
+ /* Acknowledge interrupts we handle */
+ RADEON_WRITE(RADEON_GEN_INT_STATUS, stat);
+ return IRQ_HANDLED;
+}
+
+static __inline__ void radeon_acknowledge_irqs(drm_radeon_private_t *dev_priv)
+{
+ u32 tmp = RADEON_READ( RADEON_GEN_INT_STATUS )
+ & (RADEON_SW_INT_TEST_ACK | RADEON_CRTC_VBLANK_STAT);
+ if (tmp)
+ RADEON_WRITE( RADEON_GEN_INT_STATUS, tmp );
+}
+
+static int radeon_emit_irq(drm_device_t *dev)
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ unsigned int ret;
+ RING_LOCALS;
+
+ atomic_inc(&dev_priv->swi_emitted);
+ ret = atomic_read(&dev_priv->swi_emitted);
+
+ BEGIN_RING( 4 );
+ OUT_RING_REG( RADEON_LAST_SWI_REG, ret );
+ OUT_RING_REG( RADEON_GEN_INT_STATUS, RADEON_SW_INT_FIRE );
+ ADVANCE_RING();
+ COMMIT_RING();
+
+ return ret;
+}
+
+
+static int radeon_wait_irq(drm_device_t *dev, int swi_nr)
+{
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+ int ret = 0;
+
+ if (RADEON_READ( RADEON_LAST_SWI_REG ) >= swi_nr)
+ return 0;
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ /* This is a hack to work around mysterious freezes on certain
+ * systems:
+ */
+ radeon_acknowledge_irqs( dev_priv );
+
+ DRM_WAIT_ON( ret, dev_priv->swi_queue, 3 * DRM_HZ,
+ RADEON_READ( RADEON_LAST_SWI_REG ) >= swi_nr );
+
+ return ret;
+}
+
+int radeon_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+ unsigned int cur_vblank;
+ int ret = 0;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ radeon_acknowledge_irqs( dev_priv );
+
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+ /* Assume that the user has missed the current sequence number
+ * by about a day rather than she wants to wait for years
+ * using vertical blanks...
+ */
+ DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ,
+ ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+ - *sequence ) <= (1<<23) ) );
+
+ *sequence = cur_vblank;
+
+ return ret;
+}
+
+
+/* Needs the lock as it touches the ring.
+ */
+int radeon_irq_emit( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_irq_emit_t emit;
+ int result;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( emit, (drm_radeon_irq_emit_t __user *)data,
+ sizeof(emit) );
+
+ result = radeon_emit_irq( dev );
+
+ if ( DRM_COPY_TO_USER( emit.irq_seq, &result, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+
+/* Doesn't need the hardware lock.
+ */
+int radeon_irq_wait( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_irq_wait_t irqwait;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( irqwait, (drm_radeon_irq_wait_t __user*)data,
+ sizeof(irqwait) );
+
+ return radeon_wait_irq( dev, irqwait.irq_seq );
+}
+
+
+/* drm_dma.h hooks
+*/
+void radeon_driver_irq_preinstall( drm_device_t *dev ) {
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+
+ /* Disable *all* interrupts */
+ RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+
+ /* Clear bits if they're already high */
+ radeon_acknowledge_irqs( dev_priv );
+}
+
+void radeon_driver_irq_postinstall( drm_device_t *dev ) {
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+
+ atomic_set(&dev_priv->swi_emitted, 0);
+ DRM_INIT_WAITQUEUE( &dev_priv->swi_queue );
+
+ /* Turn on SW and VBL ints */
+ RADEON_WRITE( RADEON_GEN_INT_CNTL,
+ RADEON_CRTC_VBLANK_MASK |
+ RADEON_SW_INT_ENABLE );
+}
+
+void radeon_driver_irq_uninstall( drm_device_t *dev ) {
+ drm_radeon_private_t *dev_priv =
+ (drm_radeon_private_t *)dev->dev_private;
+ if (!dev_priv)
+ return;
+
+ /* Disable *all* interrupts */
+ RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+}
diff --git a/drivers/char/drm/radeon_mem.c b/drivers/char/drm/radeon_mem.c
new file mode 100644
index 000000000000..134f894e6e4b
--- /dev/null
+++ b/drivers/char/drm/radeon_mem.c
@@ -0,0 +1,322 @@
+/* radeon_mem.c -- Simple GART/fb memory manager for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc. 2002. All Rights Reserved.
+ *
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* Very simple allocator for GART memory, working on a static range
+ * already mapped into each client's address space.
+ */
+
+static struct mem_block *split_block(struct mem_block *p, int start, int size,
+ DRMFILE filp )
+{
+ /* Maybe cut off the start of an existing block */
+ if (start > p->start) {
+ struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFS );
+ if (!newblock)
+ goto out;
+ newblock->start = start;
+ newblock->size = p->size - (start - p->start);
+ newblock->filp = NULL;
+ newblock->next = p->next;
+ newblock->prev = p;
+ p->next->prev = newblock;
+ p->next = newblock;
+ p->size -= newblock->size;
+ p = newblock;
+ }
+
+ /* Maybe cut off the end of an existing block */
+ if (size < p->size) {
+ struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFS );
+ if (!newblock)
+ goto out;
+ newblock->start = start + size;
+ newblock->size = p->size - size;
+ newblock->filp = NULL;
+ newblock->next = p->next;
+ newblock->prev = p;
+ p->next->prev = newblock;
+ p->next = newblock;
+ p->size = size;
+ }
+
+ out:
+ /* Our block is in the middle */
+ p->filp = filp;
+ return p;
+}
+
+static struct mem_block *alloc_block( struct mem_block *heap, int size,
+ int align2, DRMFILE filp )
+{
+ struct mem_block *p;
+ int mask = (1 << align2)-1;
+
+ list_for_each(p, heap) {
+ int start = (p->start + mask) & ~mask;
+ if (p->filp == 0 && start + size <= p->start + p->size)
+ return split_block( p, start, size, filp );
+ }
+
+ return NULL;
+}
+
+static struct mem_block *find_block( struct mem_block *heap, int start )
+{
+ struct mem_block *p;
+
+ list_for_each(p, heap)
+ if (p->start == start)
+ return p;
+
+ return NULL;
+}
+
+
+static void free_block( struct mem_block *p )
+{
+ p->filp = NULL;
+
+ /* Assumes a single contiguous range. Needs a special filp in
+ * 'heap' to stop it being subsumed.
+ */
+ if (p->next->filp == 0) {
+ struct mem_block *q = p->next;
+ p->size += q->size;
+ p->next = q->next;
+ p->next->prev = p;
+ drm_free(q, sizeof(*q), DRM_MEM_BUFS );
+ }
+
+ if (p->prev->filp == 0) {
+ struct mem_block *q = p->prev;
+ q->size += p->size;
+ q->next = p->next;
+ q->next->prev = q;
+ drm_free(p, sizeof(*q), DRM_MEM_BUFS );
+ }
+}
+
+/* Initialize. How to check for an uninitialized heap?
+ */
+static int init_heap(struct mem_block **heap, int start, int size)
+{
+ struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFS );
+
+ if (!blocks)
+ return DRM_ERR(ENOMEM);
+
+ *heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFS );
+ if (!*heap) {
+ drm_free( blocks, sizeof(*blocks), DRM_MEM_BUFS );
+ return DRM_ERR(ENOMEM);
+ }
+
+ blocks->start = start;
+ blocks->size = size;
+ blocks->filp = NULL;
+ blocks->next = blocks->prev = *heap;
+
+ memset( *heap, 0, sizeof(**heap) );
+ (*heap)->filp = (DRMFILE) -1;
+ (*heap)->next = (*heap)->prev = blocks;
+ return 0;
+}
+
+
+/* Free all blocks associated with the releasing file.
+ */
+void radeon_mem_release( DRMFILE filp, struct mem_block *heap )
+{
+ struct mem_block *p;
+
+ if (!heap || !heap->next)
+ return;
+
+ list_for_each(p, heap) {
+ if (p->filp == filp)
+ p->filp = NULL;
+ }
+
+ /* Assumes a single contiguous range. Needs a special filp in
+ * 'heap' to stop it being subsumed.
+ */
+ list_for_each(p, heap) {
+ while (p->filp == 0 && p->next->filp == 0) {
+ struct mem_block *q = p->next;
+ p->size += q->size;
+ p->next = q->next;
+ p->next->prev = p;
+ drm_free(q, sizeof(*q),DRM_MEM_DRIVER);
+ }
+ }
+}
+
+/* Shutdown.
+ */
+void radeon_mem_takedown( struct mem_block **heap )
+{
+ struct mem_block *p;
+
+ if (!*heap)
+ return;
+
+ for (p = (*heap)->next ; p != *heap ; ) {
+ struct mem_block *q = p;
+ p = p->next;
+ drm_free(q, sizeof(*q),DRM_MEM_DRIVER);
+ }
+
+ drm_free( *heap, sizeof(**heap),DRM_MEM_DRIVER );
+ *heap = NULL;
+}
+
+
+
+/* IOCTL HANDLERS */
+
+static struct mem_block **get_heap( drm_radeon_private_t *dev_priv,
+ int region )
+{
+ switch( region ) {
+ case RADEON_MEM_REGION_GART:
+ return &dev_priv->gart_heap;
+ case RADEON_MEM_REGION_FB:
+ return &dev_priv->fb_heap;
+ default:
+ return NULL;
+ }
+}
+
+int radeon_mem_alloc( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_mem_alloc_t alloc;
+ struct mem_block *block, **heap;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( alloc, (drm_radeon_mem_alloc_t __user *)data,
+ sizeof(alloc) );
+
+ heap = get_heap( dev_priv, alloc.region );
+ if (!heap || !*heap)
+ return DRM_ERR(EFAULT);
+
+ /* Make things easier on ourselves: all allocations at least
+ * 4k aligned.
+ */
+ if (alloc.alignment < 12)
+ alloc.alignment = 12;
+
+ block = alloc_block( *heap, alloc.size, alloc.alignment,
+ filp );
+
+ if (!block)
+ return DRM_ERR(ENOMEM);
+
+ if ( DRM_COPY_TO_USER( alloc.region_offset, &block->start,
+ sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+
+
+int radeon_mem_free( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_mem_free_t memfree;
+ struct mem_block *block, **heap;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( memfree, (drm_radeon_mem_free_t __user *)data,
+ sizeof(memfree) );
+
+ heap = get_heap( dev_priv, memfree.region );
+ if (!heap || !*heap)
+ return DRM_ERR(EFAULT);
+
+ block = find_block( *heap, memfree.region_offset );
+ if (!block)
+ return DRM_ERR(EFAULT);
+
+ if (block->filp != filp)
+ return DRM_ERR(EPERM);
+
+ free_block( block );
+ return 0;
+}
+
+int radeon_mem_init_heap( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_mem_init_heap_t initheap;
+ struct mem_block **heap;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( initheap, (drm_radeon_mem_init_heap_t __user *)data,
+ sizeof(initheap) );
+
+ heap = get_heap( dev_priv, initheap.region );
+ if (!heap)
+ return DRM_ERR(EFAULT);
+
+ if (*heap) {
+ DRM_ERROR("heap already initialized?");
+ return DRM_ERR(EFAULT);
+ }
+
+ return init_heap( heap, initheap.start, initheap.size );
+}
+
+
diff --git a/drivers/char/drm/radeon_state.c b/drivers/char/drm/radeon_state.c
new file mode 100644
index 000000000000..1f79e249146c
--- /dev/null
+++ b/drivers/char/drm/radeon_state.c
@@ -0,0 +1,3102 @@
+/* radeon_state.c -- State support for Radeon -*- linux-c -*-
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ * Kevin E. Martin <martin@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "drm_sarea.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* ================================================================
+ * Helper functions for client state checking and fixup
+ */
+
+static __inline__ int radeon_check_and_fixup_offset( drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ u32 *offset ) {
+ u32 off = *offset;
+ struct drm_radeon_driver_file_fields *radeon_priv;
+
+ if ( off >= dev_priv->fb_location &&
+ off < ( dev_priv->gart_vm_start + dev_priv->gart_size ) )
+ return 0;
+
+ radeon_priv = filp_priv->driver_priv;
+ off += radeon_priv->radeon_fb_delta;
+
+ DRM_DEBUG( "offset fixed up to 0x%x\n", off );
+
+ if ( off < dev_priv->fb_location ||
+ off >= ( dev_priv->gart_vm_start + dev_priv->gart_size ) )
+ return DRM_ERR( EINVAL );
+
+ *offset = off;
+
+ return 0;
+}
+
+static __inline__ int radeon_check_and_fixup_packets( drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ int id,
+ u32 __user *data ) {
+ switch ( id ) {
+
+ case RADEON_EMIT_PP_MISC:
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &data[( RADEON_RB3D_DEPTHOFFSET
+ - RADEON_PP_MISC ) / 4] ) ) {
+ DRM_ERROR( "Invalid depth buffer offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ break;
+
+ case RADEON_EMIT_PP_CNTL:
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &data[( RADEON_RB3D_COLOROFFSET
+ - RADEON_PP_CNTL ) / 4] ) ) {
+ DRM_ERROR( "Invalid colour buffer offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ break;
+
+ case R200_EMIT_PP_TXOFFSET_0:
+ case R200_EMIT_PP_TXOFFSET_1:
+ case R200_EMIT_PP_TXOFFSET_2:
+ case R200_EMIT_PP_TXOFFSET_3:
+ case R200_EMIT_PP_TXOFFSET_4:
+ case R200_EMIT_PP_TXOFFSET_5:
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &data[0] ) ) {
+ DRM_ERROR( "Invalid R200 texture offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ break;
+
+ case RADEON_EMIT_PP_TXFILTER_0:
+ case RADEON_EMIT_PP_TXFILTER_1:
+ case RADEON_EMIT_PP_TXFILTER_2:
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &data[( RADEON_PP_TXOFFSET_0
+ - RADEON_PP_TXFILTER_0 ) / 4] ) ) {
+ DRM_ERROR( "Invalid R100 texture offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ break;
+
+ case R200_EMIT_PP_CUBIC_OFFSETS_0:
+ case R200_EMIT_PP_CUBIC_OFFSETS_1:
+ case R200_EMIT_PP_CUBIC_OFFSETS_2:
+ case R200_EMIT_PP_CUBIC_OFFSETS_3:
+ case R200_EMIT_PP_CUBIC_OFFSETS_4:
+ case R200_EMIT_PP_CUBIC_OFFSETS_5: {
+ int i;
+ for ( i = 0; i < 5; i++ ) {
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &data[i] ) ) {
+ DRM_ERROR( "Invalid R200 cubic texture offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ }
+ break;
+ }
+
+ case RADEON_EMIT_PP_CUBIC_OFFSETS_T0:
+ case RADEON_EMIT_PP_CUBIC_OFFSETS_T1:
+ case RADEON_EMIT_PP_CUBIC_OFFSETS_T2:{
+ int i;
+ for (i = 0; i < 5; i++) {
+ if (radeon_check_and_fixup_offset(dev_priv,
+ filp_priv,
+ &data[i])) {
+ DRM_ERROR
+ ("Invalid R100 cubic texture offset\n");
+ return DRM_ERR(EINVAL);
+ }
+ }
+ }
+ break;
+
+ case RADEON_EMIT_RB3D_COLORPITCH:
+ case RADEON_EMIT_RE_LINE_PATTERN:
+ case RADEON_EMIT_SE_LINE_WIDTH:
+ case RADEON_EMIT_PP_LUM_MATRIX:
+ case RADEON_EMIT_PP_ROT_MATRIX_0:
+ case RADEON_EMIT_RB3D_STENCILREFMASK:
+ case RADEON_EMIT_SE_VPORT_XSCALE:
+ case RADEON_EMIT_SE_CNTL:
+ case RADEON_EMIT_SE_CNTL_STATUS:
+ case RADEON_EMIT_RE_MISC:
+ case RADEON_EMIT_PP_BORDER_COLOR_0:
+ case RADEON_EMIT_PP_BORDER_COLOR_1:
+ case RADEON_EMIT_PP_BORDER_COLOR_2:
+ case RADEON_EMIT_SE_ZBIAS_FACTOR:
+ case RADEON_EMIT_SE_TCL_OUTPUT_VTX_FMT:
+ case RADEON_EMIT_SE_TCL_MATERIAL_EMMISSIVE_RED:
+ case R200_EMIT_PP_TXCBLEND_0:
+ case R200_EMIT_PP_TXCBLEND_1:
+ case R200_EMIT_PP_TXCBLEND_2:
+ case R200_EMIT_PP_TXCBLEND_3:
+ case R200_EMIT_PP_TXCBLEND_4:
+ case R200_EMIT_PP_TXCBLEND_5:
+ case R200_EMIT_PP_TXCBLEND_6:
+ case R200_EMIT_PP_TXCBLEND_7:
+ case R200_EMIT_TCL_LIGHT_MODEL_CTL_0:
+ case R200_EMIT_TFACTOR_0:
+ case R200_EMIT_VTX_FMT_0:
+ case R200_EMIT_VAP_CTL:
+ case R200_EMIT_MATRIX_SELECT_0:
+ case R200_EMIT_TEX_PROC_CTL_2:
+ case R200_EMIT_TCL_UCP_VERT_BLEND_CTL:
+ case R200_EMIT_PP_TXFILTER_0:
+ case R200_EMIT_PP_TXFILTER_1:
+ case R200_EMIT_PP_TXFILTER_2:
+ case R200_EMIT_PP_TXFILTER_3:
+ case R200_EMIT_PP_TXFILTER_4:
+ case R200_EMIT_PP_TXFILTER_5:
+ case R200_EMIT_VTE_CNTL:
+ case R200_EMIT_OUTPUT_VTX_COMP_SEL:
+ case R200_EMIT_PP_TAM_DEBUG3:
+ case R200_EMIT_PP_CNTL_X:
+ case R200_EMIT_RB3D_DEPTHXY_OFFSET:
+ case R200_EMIT_RE_AUX_SCISSOR_CNTL:
+ case R200_EMIT_RE_SCISSOR_TL_0:
+ case R200_EMIT_RE_SCISSOR_TL_1:
+ case R200_EMIT_RE_SCISSOR_TL_2:
+ case R200_EMIT_SE_VAP_CNTL_STATUS:
+ case R200_EMIT_SE_VTX_STATE_CNTL:
+ case R200_EMIT_RE_POINTSIZE:
+ case R200_EMIT_TCL_INPUT_VTX_VECTOR_ADDR_0:
+ case R200_EMIT_PP_CUBIC_FACES_0:
+ case R200_EMIT_PP_CUBIC_FACES_1:
+ case R200_EMIT_PP_CUBIC_FACES_2:
+ case R200_EMIT_PP_CUBIC_FACES_3:
+ case R200_EMIT_PP_CUBIC_FACES_4:
+ case R200_EMIT_PP_CUBIC_FACES_5:
+ case RADEON_EMIT_PP_TEX_SIZE_0:
+ case RADEON_EMIT_PP_TEX_SIZE_1:
+ case RADEON_EMIT_PP_TEX_SIZE_2:
+ case R200_EMIT_RB3D_BLENDCOLOR:
+ case R200_EMIT_TCL_POINT_SPRITE_CNTL:
+ case RADEON_EMIT_PP_CUBIC_FACES_0:
+ case RADEON_EMIT_PP_CUBIC_FACES_1:
+ case RADEON_EMIT_PP_CUBIC_FACES_2:
+ case R200_EMIT_PP_TRI_PERF_CNTL:
+ /* These packets don't contain memory offsets */
+ break;
+
+ default:
+ DRM_ERROR( "Unknown state packet ID %d\n", id );
+ return DRM_ERR( EINVAL );
+ }
+
+ return 0;
+}
+
+static __inline__ int radeon_check_and_fixup_packet3( drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ drm_radeon_cmd_buffer_t *cmdbuf,
+ unsigned int *cmdsz ) {
+ u32 *cmd = (u32 *) cmdbuf->buf;
+
+ *cmdsz = 2 + ( ( cmd[0] & RADEON_CP_PACKET_COUNT_MASK ) >> 16 );
+
+ if ( ( cmd[0] & 0xc0000000 ) != RADEON_CP_PACKET3 ) {
+ DRM_ERROR( "Not a type 3 packet\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ if ( 4 * *cmdsz > cmdbuf->bufsz ) {
+ DRM_ERROR( "Packet size larger than size of data provided\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ /* Check client state and fix it up if necessary */
+ if ( cmd[0] & 0x8000 ) { /* MSB of opcode: next DWORD GUI_CNTL */
+ u32 offset;
+
+ if ( cmd[1] & ( RADEON_GMC_SRC_PITCH_OFFSET_CNTL
+ | RADEON_GMC_DST_PITCH_OFFSET_CNTL ) ) {
+ offset = cmd[2] << 10;
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &offset ) ) {
+ DRM_ERROR( "Invalid first packet offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ cmd[2] = ( cmd[2] & 0xffc00000 ) | offset >> 10;
+ }
+
+ if ( ( cmd[1] & RADEON_GMC_SRC_PITCH_OFFSET_CNTL ) &&
+ ( cmd[1] & RADEON_GMC_DST_PITCH_OFFSET_CNTL ) ) {
+ offset = cmd[3] << 10;
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &offset ) ) {
+ DRM_ERROR( "Invalid second packet offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+ cmd[3] = ( cmd[3] & 0xffc00000 ) | offset >> 10;
+ }
+ }
+
+ return 0;
+}
+
+
+/* ================================================================
+ * CP hardware state programming functions
+ */
+
+static __inline__ void radeon_emit_clip_rect( drm_radeon_private_t *dev_priv,
+ drm_clip_rect_t *box )
+{
+ RING_LOCALS;
+
+ DRM_DEBUG( " box: x1=%d y1=%d x2=%d y2=%d\n",
+ box->x1, box->y1, box->x2, box->y2 );
+
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET0( RADEON_RE_TOP_LEFT, 0 ) );
+ OUT_RING( (box->y1 << 16) | box->x1 );
+ OUT_RING( CP_PACKET0( RADEON_RE_WIDTH_HEIGHT, 0 ) );
+ OUT_RING( ((box->y2 - 1) << 16) | (box->x2 - 1) );
+ ADVANCE_RING();
+}
+
+/* Emit 1.1 state
+ */
+static int radeon_emit_state( drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ drm_radeon_context_regs_t *ctx,
+ drm_radeon_texture_regs_t *tex,
+ unsigned int dirty )
+{
+ RING_LOCALS;
+ DRM_DEBUG( "dirty=0x%08x\n", dirty );
+
+ if ( dirty & RADEON_UPLOAD_CONTEXT ) {
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &ctx->rb3d_depthoffset ) ) {
+ DRM_ERROR( "Invalid depth buffer offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &ctx->rb3d_coloroffset ) ) {
+ DRM_ERROR( "Invalid depth buffer offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ BEGIN_RING( 14 );
+ OUT_RING( CP_PACKET0( RADEON_PP_MISC, 6 ) );
+ OUT_RING( ctx->pp_misc );
+ OUT_RING( ctx->pp_fog_color );
+ OUT_RING( ctx->re_solid_color );
+ OUT_RING( ctx->rb3d_blendcntl );
+ OUT_RING( ctx->rb3d_depthoffset );
+ OUT_RING( ctx->rb3d_depthpitch );
+ OUT_RING( ctx->rb3d_zstencilcntl );
+ OUT_RING( CP_PACKET0( RADEON_PP_CNTL, 2 ) );
+ OUT_RING( ctx->pp_cntl );
+ OUT_RING( ctx->rb3d_cntl );
+ OUT_RING( ctx->rb3d_coloroffset );
+ OUT_RING( CP_PACKET0( RADEON_RB3D_COLORPITCH, 0 ) );
+ OUT_RING( ctx->rb3d_colorpitch );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_VERTFMT ) {
+ BEGIN_RING( 2 );
+ OUT_RING( CP_PACKET0( RADEON_SE_COORD_FMT, 0 ) );
+ OUT_RING( ctx->se_coord_fmt );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_LINE ) {
+ BEGIN_RING( 5 );
+ OUT_RING( CP_PACKET0( RADEON_RE_LINE_PATTERN, 1 ) );
+ OUT_RING( ctx->re_line_pattern );
+ OUT_RING( ctx->re_line_state );
+ OUT_RING( CP_PACKET0( RADEON_SE_LINE_WIDTH, 0 ) );
+ OUT_RING( ctx->se_line_width );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_BUMPMAP ) {
+ BEGIN_RING( 5 );
+ OUT_RING( CP_PACKET0( RADEON_PP_LUM_MATRIX, 0 ) );
+ OUT_RING( ctx->pp_lum_matrix );
+ OUT_RING( CP_PACKET0( RADEON_PP_ROT_MATRIX_0, 1 ) );
+ OUT_RING( ctx->pp_rot_matrix_0 );
+ OUT_RING( ctx->pp_rot_matrix_1 );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_MASKS ) {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET0( RADEON_RB3D_STENCILREFMASK, 2 ) );
+ OUT_RING( ctx->rb3d_stencilrefmask );
+ OUT_RING( ctx->rb3d_ropcntl );
+ OUT_RING( ctx->rb3d_planemask );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_VIEWPORT ) {
+ BEGIN_RING( 7 );
+ OUT_RING( CP_PACKET0( RADEON_SE_VPORT_XSCALE, 5 ) );
+ OUT_RING( ctx->se_vport_xscale );
+ OUT_RING( ctx->se_vport_xoffset );
+ OUT_RING( ctx->se_vport_yscale );
+ OUT_RING( ctx->se_vport_yoffset );
+ OUT_RING( ctx->se_vport_zscale );
+ OUT_RING( ctx->se_vport_zoffset );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_SETUP ) {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET0( RADEON_SE_CNTL, 0 ) );
+ OUT_RING( ctx->se_cntl );
+ OUT_RING( CP_PACKET0( RADEON_SE_CNTL_STATUS, 0 ) );
+ OUT_RING( ctx->se_cntl_status );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_MISC ) {
+ BEGIN_RING( 2 );
+ OUT_RING( CP_PACKET0( RADEON_RE_MISC, 0 ) );
+ OUT_RING( ctx->re_misc );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_TEX0 ) {
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &tex[0].pp_txoffset ) ) {
+ DRM_ERROR( "Invalid texture offset for unit 0\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ BEGIN_RING( 9 );
+ OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_0, 5 ) );
+ OUT_RING( tex[0].pp_txfilter );
+ OUT_RING( tex[0].pp_txformat );
+ OUT_RING( tex[0].pp_txoffset );
+ OUT_RING( tex[0].pp_txcblend );
+ OUT_RING( tex[0].pp_txablend );
+ OUT_RING( tex[0].pp_tfactor );
+ OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_0, 0 ) );
+ OUT_RING( tex[0].pp_border_color );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_TEX1 ) {
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &tex[1].pp_txoffset ) ) {
+ DRM_ERROR( "Invalid texture offset for unit 1\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ BEGIN_RING( 9 );
+ OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_1, 5 ) );
+ OUT_RING( tex[1].pp_txfilter );
+ OUT_RING( tex[1].pp_txformat );
+ OUT_RING( tex[1].pp_txoffset );
+ OUT_RING( tex[1].pp_txcblend );
+ OUT_RING( tex[1].pp_txablend );
+ OUT_RING( tex[1].pp_tfactor );
+ OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_1, 0 ) );
+ OUT_RING( tex[1].pp_border_color );
+ ADVANCE_RING();
+ }
+
+ if ( dirty & RADEON_UPLOAD_TEX2 ) {
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+ &tex[2].pp_txoffset ) ) {
+ DRM_ERROR( "Invalid texture offset for unit 2\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ BEGIN_RING( 9 );
+ OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_2, 5 ) );
+ OUT_RING( tex[2].pp_txfilter );
+ OUT_RING( tex[2].pp_txformat );
+ OUT_RING( tex[2].pp_txoffset );
+ OUT_RING( tex[2].pp_txcblend );
+ OUT_RING( tex[2].pp_txablend );
+ OUT_RING( tex[2].pp_tfactor );
+ OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_2, 0 ) );
+ OUT_RING( tex[2].pp_border_color );
+ ADVANCE_RING();
+ }
+
+ return 0;
+}
+
+/* Emit 1.2 state
+ */
+static int radeon_emit_state2( drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ drm_radeon_state_t *state )
+{
+ RING_LOCALS;
+
+ if (state->dirty & RADEON_UPLOAD_ZBIAS) {
+ BEGIN_RING( 3 );
+ OUT_RING( CP_PACKET0( RADEON_SE_ZBIAS_FACTOR, 1 ) );
+ OUT_RING( state->context2.se_zbias_factor );
+ OUT_RING( state->context2.se_zbias_constant );
+ ADVANCE_RING();
+ }
+
+ return radeon_emit_state( dev_priv, filp_priv, &state->context,
+ state->tex, state->dirty );
+}
+
+/* New (1.3) state mechanism. 3 commands (packet, scalar, vector) in
+ * 1.3 cmdbuffers allow all previous state to be updated as well as
+ * the tcl scalar and vector areas.
+ */
+static struct {
+ int start;
+ int len;
+ const char *name;
+} packet[RADEON_MAX_STATE_PACKETS] = {
+ { RADEON_PP_MISC,7,"RADEON_PP_MISC" },
+ { RADEON_PP_CNTL,3,"RADEON_PP_CNTL" },
+ { RADEON_RB3D_COLORPITCH,1,"RADEON_RB3D_COLORPITCH" },
+ { RADEON_RE_LINE_PATTERN,2,"RADEON_RE_LINE_PATTERN" },
+ { RADEON_SE_LINE_WIDTH,1,"RADEON_SE_LINE_WIDTH" },
+ { RADEON_PP_LUM_MATRIX,1,"RADEON_PP_LUM_MATRIX" },
+ { RADEON_PP_ROT_MATRIX_0,2,"RADEON_PP_ROT_MATRIX_0" },
+ { RADEON_RB3D_STENCILREFMASK,3,"RADEON_RB3D_STENCILREFMASK" },
+ { RADEON_SE_VPORT_XSCALE,6,"RADEON_SE_VPORT_XSCALE" },
+ { RADEON_SE_CNTL,2,"RADEON_SE_CNTL" },
+ { RADEON_SE_CNTL_STATUS,1,"RADEON_SE_CNTL_STATUS" },
+ { RADEON_RE_MISC,1,"RADEON_RE_MISC" },
+ { RADEON_PP_TXFILTER_0,6,"RADEON_PP_TXFILTER_0" },
+ { RADEON_PP_BORDER_COLOR_0,1,"RADEON_PP_BORDER_COLOR_0" },
+ { RADEON_PP_TXFILTER_1,6,"RADEON_PP_TXFILTER_1" },
+ { RADEON_PP_BORDER_COLOR_1,1,"RADEON_PP_BORDER_COLOR_1" },
+ { RADEON_PP_TXFILTER_2,6,"RADEON_PP_TXFILTER_2" },
+ { RADEON_PP_BORDER_COLOR_2,1,"RADEON_PP_BORDER_COLOR_2" },
+ { RADEON_SE_ZBIAS_FACTOR,2,"RADEON_SE_ZBIAS_FACTOR" },
+ { RADEON_SE_TCL_OUTPUT_VTX_FMT,11,"RADEON_SE_TCL_OUTPUT_VTX_FMT" },
+ { RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED,17,"RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED" },
+ { R200_PP_TXCBLEND_0, 4, "R200_PP_TXCBLEND_0" },
+ { R200_PP_TXCBLEND_1, 4, "R200_PP_TXCBLEND_1" },
+ { R200_PP_TXCBLEND_2, 4, "R200_PP_TXCBLEND_2" },
+ { R200_PP_TXCBLEND_3, 4, "R200_PP_TXCBLEND_3" },
+ { R200_PP_TXCBLEND_4, 4, "R200_PP_TXCBLEND_4" },
+ { R200_PP_TXCBLEND_5, 4, "R200_PP_TXCBLEND_5" },
+ { R200_PP_TXCBLEND_6, 4, "R200_PP_TXCBLEND_6" },
+ { R200_PP_TXCBLEND_7, 4, "R200_PP_TXCBLEND_7" },
+ { R200_SE_TCL_LIGHT_MODEL_CTL_0, 6, "R200_SE_TCL_LIGHT_MODEL_CTL_0" },
+ { R200_PP_TFACTOR_0, 6, "R200_PP_TFACTOR_0" },
+ { R200_SE_VTX_FMT_0, 4, "R200_SE_VTX_FMT_0" },
+ { R200_SE_VAP_CNTL, 1, "R200_SE_VAP_CNTL" },
+ { R200_SE_TCL_MATRIX_SEL_0, 5, "R200_SE_TCL_MATRIX_SEL_0" },
+ { R200_SE_TCL_TEX_PROC_CTL_2, 5, "R200_SE_TCL_TEX_PROC_CTL_2" },
+ { R200_SE_TCL_UCP_VERT_BLEND_CTL, 1, "R200_SE_TCL_UCP_VERT_BLEND_CTL" },
+ { R200_PP_TXFILTER_0, 6, "R200_PP_TXFILTER_0" },
+ { R200_PP_TXFILTER_1, 6, "R200_PP_TXFILTER_1" },
+ { R200_PP_TXFILTER_2, 6, "R200_PP_TXFILTER_2" },
+ { R200_PP_TXFILTER_3, 6, "R200_PP_TXFILTER_3" },
+ { R200_PP_TXFILTER_4, 6, "R200_PP_TXFILTER_4" },
+ { R200_PP_TXFILTER_5, 6, "R200_PP_TXFILTER_5" },
+ { R200_PP_TXOFFSET_0, 1, "R200_PP_TXOFFSET_0" },
+ { R200_PP_TXOFFSET_1, 1, "R200_PP_TXOFFSET_1" },
+ { R200_PP_TXOFFSET_2, 1, "R200_PP_TXOFFSET_2" },
+ { R200_PP_TXOFFSET_3, 1, "R200_PP_TXOFFSET_3" },
+ { R200_PP_TXOFFSET_4, 1, "R200_PP_TXOFFSET_4" },
+ { R200_PP_TXOFFSET_5, 1, "R200_PP_TXOFFSET_5" },
+ { R200_SE_VTE_CNTL, 1, "R200_SE_VTE_CNTL" },
+ { R200_SE_TCL_OUTPUT_VTX_COMP_SEL, 1, "R200_SE_TCL_OUTPUT_VTX_COMP_SEL" },
+ { R200_PP_TAM_DEBUG3, 1, "R200_PP_TAM_DEBUG3" },
+ { R200_PP_CNTL_X, 1, "R200_PP_CNTL_X" },
+ { R200_RB3D_DEPTHXY_OFFSET, 1, "R200_RB3D_DEPTHXY_OFFSET" },
+ { R200_RE_AUX_SCISSOR_CNTL, 1, "R200_RE_AUX_SCISSOR_CNTL" },
+ { R200_RE_SCISSOR_TL_0, 2, "R200_RE_SCISSOR_TL_0" },
+ { R200_RE_SCISSOR_TL_1, 2, "R200_RE_SCISSOR_TL_1" },
+ { R200_RE_SCISSOR_TL_2, 2, "R200_RE_SCISSOR_TL_2" },
+ { R200_SE_VAP_CNTL_STATUS, 1, "R200_SE_VAP_CNTL_STATUS" },
+ { R200_SE_VTX_STATE_CNTL, 1, "R200_SE_VTX_STATE_CNTL" },
+ { R200_RE_POINTSIZE, 1, "R200_RE_POINTSIZE" },
+ { R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0, 4, "R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0" },
+ { R200_PP_CUBIC_FACES_0, 1, "R200_PP_CUBIC_FACES_0" }, /* 61 */
+ { R200_PP_CUBIC_OFFSET_F1_0, 5, "R200_PP_CUBIC_OFFSET_F1_0" }, /* 62 */
+ { R200_PP_CUBIC_FACES_1, 1, "R200_PP_CUBIC_FACES_1" },
+ { R200_PP_CUBIC_OFFSET_F1_1, 5, "R200_PP_CUBIC_OFFSET_F1_1" },
+ { R200_PP_CUBIC_FACES_2, 1, "R200_PP_CUBIC_FACES_2" },
+ { R200_PP_CUBIC_OFFSET_F1_2, 5, "R200_PP_CUBIC_OFFSET_F1_2" },
+ { R200_PP_CUBIC_FACES_3, 1, "R200_PP_CUBIC_FACES_3" },
+ { R200_PP_CUBIC_OFFSET_F1_3, 5, "R200_PP_CUBIC_OFFSET_F1_3" },
+ { R200_PP_CUBIC_FACES_4, 1, "R200_PP_CUBIC_FACES_4" },
+ { R200_PP_CUBIC_OFFSET_F1_4, 5, "R200_PP_CUBIC_OFFSET_F1_4" },
+ { R200_PP_CUBIC_FACES_5, 1, "R200_PP_CUBIC_FACES_5" },
+ { R200_PP_CUBIC_OFFSET_F1_5, 5, "R200_PP_CUBIC_OFFSET_F1_5" },
+ { RADEON_PP_TEX_SIZE_0, 2, "RADEON_PP_TEX_SIZE_0" },
+ { RADEON_PP_TEX_SIZE_1, 2, "RADEON_PP_TEX_SIZE_1" },
+ { RADEON_PP_TEX_SIZE_2, 2, "RADEON_PP_TEX_SIZE_2" },
+ { R200_RB3D_BLENDCOLOR, 3, "R200_RB3D_BLENDCOLOR" },
+ { R200_SE_TCL_POINT_SPRITE_CNTL, 1, "R200_SE_TCL_POINT_SPRITE_CNTL" },
+ { RADEON_PP_CUBIC_FACES_0, 1, "RADEON_PP_CUBIC_FACES_0"},
+ { RADEON_PP_CUBIC_OFFSET_T0_0, 5, "RADEON_PP_CUBIC_OFFSET_T0_0"},
+ { RADEON_PP_CUBIC_FACES_1, 1, "RADEON_PP_CUBIC_FACES_1"},
+ { RADEON_PP_CUBIC_OFFSET_T1_0, 5, "RADEON_PP_CUBIC_OFFSET_T1_0"},
+ { RADEON_PP_CUBIC_FACES_2, 1, "RADEON_PP_CUBIC_FACES_2"},
+ { RADEON_PP_CUBIC_OFFSET_T2_0, 5, "RADEON_PP_CUBIC_OFFSET_T2_0"},
+ { R200_PP_TRI_PERF, 2, "R200_PP_TRI_PERF"},
+};
+
+
+
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void radeon_clear_box( drm_radeon_private_t *dev_priv,
+ int x, int y, int w, int h,
+ int r, int g, int b )
+{
+ u32 color;
+ RING_LOCALS;
+
+ x += dev_priv->sarea_priv->boxes[0].x1;
+ y += dev_priv->sarea_priv->boxes[0].y1;
+
+ switch ( dev_priv->color_fmt ) {
+ case RADEON_COLOR_FORMAT_RGB565:
+ color = (((r & 0xf8) << 8) |
+ ((g & 0xfc) << 3) |
+ ((b & 0xf8) >> 3));
+ break;
+ case RADEON_COLOR_FORMAT_ARGB8888:
+ default:
+ color = (((0xff) << 24) | (r << 16) | (g << 8) | b);
+ break;
+ }
+
+ BEGIN_RING( 4 );
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ OUT_RING( CP_PACKET0( RADEON_DP_WRITE_MASK, 0 ) );
+ OUT_RING( 0xffffffff );
+ ADVANCE_RING();
+
+ BEGIN_RING( 6 );
+
+ OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+ RADEON_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->color_fmt << 8) |
+ RADEON_GMC_SRC_DATATYPE_COLOR |
+ RADEON_ROP3_P |
+ RADEON_GMC_CLR_CMP_CNTL_DIS );
+
+ if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) {
+ OUT_RING( dev_priv->front_pitch_offset );
+ } else {
+ OUT_RING( dev_priv->back_pitch_offset );
+ }
+
+ OUT_RING( color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+}
+
+static void radeon_cp_performance_boxes( drm_radeon_private_t *dev_priv )
+{
+ /* Collapse various things into a wait flag -- trying to
+ * guess if userspase slept -- better just to have them tell us.
+ */
+ if (dev_priv->stats.last_frame_reads > 1 ||
+ dev_priv->stats.last_clear_reads > dev_priv->stats.clears) {
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+ }
+
+ if (dev_priv->stats.freelist_loops) {
+ dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+ }
+
+ /* Purple box for page flipping
+ */
+ if ( dev_priv->stats.boxes & RADEON_BOX_FLIP )
+ radeon_clear_box( dev_priv, 4, 4, 8, 8, 255, 0, 255 );
+
+ /* Red box if we have to wait for idle at any point
+ */
+ if ( dev_priv->stats.boxes & RADEON_BOX_WAIT_IDLE )
+ radeon_clear_box( dev_priv, 16, 4, 8, 8, 255, 0, 0 );
+
+ /* Blue box: lost context?
+ */
+
+ /* Yellow box for texture swaps
+ */
+ if ( dev_priv->stats.boxes & RADEON_BOX_TEXTURE_LOAD )
+ radeon_clear_box( dev_priv, 40, 4, 8, 8, 255, 255, 0 );
+
+ /* Green box if hardware never idles (as far as we can tell)
+ */
+ if ( !(dev_priv->stats.boxes & RADEON_BOX_DMA_IDLE) )
+ radeon_clear_box( dev_priv, 64, 4, 8, 8, 0, 255, 0 );
+
+
+ /* Draw bars indicating number of buffers allocated
+ * (not a great measure, easily confused)
+ */
+ if (dev_priv->stats.requested_bufs) {
+ if (dev_priv->stats.requested_bufs > 100)
+ dev_priv->stats.requested_bufs = 100;
+
+ radeon_clear_box( dev_priv, 4, 16,
+ dev_priv->stats.requested_bufs, 4,
+ 196, 128, 128 );
+ }
+
+ memset( &dev_priv->stats, 0, sizeof(dev_priv->stats) );
+
+}
+/* ================================================================
+ * CP command dispatch functions
+ */
+
+static void radeon_cp_dispatch_clear( drm_device_t *dev,
+ drm_radeon_clear_t *clear,
+ drm_radeon_clear_rect_t *depth_boxes )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_radeon_depth_clear_t *depth_clear = &dev_priv->depth_clear;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ unsigned int flags = clear->flags;
+ u32 rb3d_cntl = 0, rb3d_stencilrefmask= 0;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "flags = 0x%x\n", flags );
+
+ dev_priv->stats.clears++;
+
+ if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) {
+ unsigned int tmp = flags;
+
+ flags &= ~(RADEON_FRONT | RADEON_BACK);
+ if ( tmp & RADEON_FRONT ) flags |= RADEON_BACK;
+ if ( tmp & RADEON_BACK ) flags |= RADEON_FRONT;
+ }
+
+ if ( flags & (RADEON_FRONT | RADEON_BACK) ) {
+
+ BEGIN_RING( 4 );
+
+ /* Ensure the 3D stream is idle before doing a
+ * 2D fill to clear the front or back buffer.
+ */
+ RADEON_WAIT_UNTIL_3D_IDLE();
+
+ OUT_RING( CP_PACKET0( RADEON_DP_WRITE_MASK, 0 ) );
+ OUT_RING( clear->color_mask );
+
+ ADVANCE_RING();
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->ctx_owner = 0;
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ int x = pbox[i].x1;
+ int y = pbox[i].y1;
+ int w = pbox[i].x2 - x;
+ int h = pbox[i].y2 - y;
+
+ DRM_DEBUG( "dispatch clear %d,%d-%d,%d flags 0x%x\n",
+ x, y, w, h, flags );
+
+ if ( flags & RADEON_FRONT ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+ RADEON_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->color_fmt << 8) |
+ RADEON_GMC_SRC_DATATYPE_COLOR |
+ RADEON_ROP3_P |
+ RADEON_GMC_CLR_CMP_CNTL_DIS );
+
+ OUT_RING( dev_priv->front_pitch_offset );
+ OUT_RING( clear->clear_color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+
+ if ( flags & RADEON_BACK ) {
+ BEGIN_RING( 6 );
+
+ OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+ OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+ RADEON_GMC_BRUSH_SOLID_COLOR |
+ (dev_priv->color_fmt << 8) |
+ RADEON_GMC_SRC_DATATYPE_COLOR |
+ RADEON_ROP3_P |
+ RADEON_GMC_CLR_CMP_CNTL_DIS );
+
+ OUT_RING( dev_priv->back_pitch_offset );
+ OUT_RING( clear->clear_color );
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+ }
+ }
+
+ /* hyper z clear */
+ /* no docs available, based on reverse engeneering by Stephane Marchesin */
+ if ((flags & (RADEON_DEPTH | RADEON_STENCIL)) && (flags & RADEON_CLEAR_FASTZ)) {
+
+ int i;
+ int depthpixperline = dev_priv->depth_fmt==RADEON_DEPTH_FORMAT_16BIT_INT_Z?
+ (dev_priv->depth_pitch / 2): (dev_priv->depth_pitch / 4);
+
+ u32 clearmask;
+
+ u32 tempRB3D_DEPTHCLEARVALUE = clear->clear_depth |
+ ((clear->depth_mask & 0xff) << 24);
+
+
+ /* Make sure we restore the 3D state next time.
+ * we haven't touched any "normal" state - still need this?
+ */
+ dev_priv->sarea_priv->ctx_owner = 0;
+
+ if ((dev_priv->flags & CHIP_HAS_HIERZ) && (flags & RADEON_USE_HIERZ)) {
+ /* FIXME : reverse engineer that for Rx00 cards */
+ /* FIXME : the mask supposedly contains low-res z values. So can't set
+ just to the max (0xff? or actually 0x3fff?), need to take z clear
+ value into account? */
+ /* pattern seems to work for r100, though get slight
+ rendering errors with glxgears. If hierz is not enabled for r100,
+ only 4 bits which indicate clear (15,16,31,32, all zero) matter, the
+ other ones are ignored, and the same clear mask can be used. That's
+ very different behaviour than R200 which needs different clear mask
+ and different number of tiles to clear if hierz is enabled or not !?!
+ */
+ clearmask = (0xff<<22)|(0xff<<6)| 0x003f003f;
+ }
+ else {
+ /* clear mask : chooses the clearing pattern.
+ rv250: could be used to clear only parts of macrotiles
+ (but that would get really complicated...)?
+ bit 0 and 1 (either or both of them ?!?!) are used to
+ not clear tile (or maybe one of the bits indicates if the tile is
+ compressed or not), bit 2 and 3 to not clear tile 1,...,.
+ Pattern is as follows:
+ | 0,1 | 4,5 | 8,9 |12,13|16,17|20,21|24,25|28,29|
+ bits -------------------------------------------------
+ | 2,3 | 6,7 |10,11|14,15|18,19|22,23|26,27|30,31|
+ rv100: clearmask covers 2x8 4x1 tiles, but one clear still
+ covers 256 pixels ?!?
+ */
+ clearmask = 0x0;
+ }
+
+ BEGIN_RING( 8 );
+ RADEON_WAIT_UNTIL_2D_IDLE();
+ OUT_RING_REG( RADEON_RB3D_DEPTHCLEARVALUE,
+ tempRB3D_DEPTHCLEARVALUE);
+ /* what offset is this exactly ? */
+ OUT_RING_REG( RADEON_RB3D_ZMASKOFFSET, 0 );
+ /* need ctlstat, otherwise get some strange black flickering */
+ OUT_RING_REG( RADEON_RB3D_ZCACHE_CTLSTAT, RADEON_RB3D_ZC_FLUSH_ALL );
+ ADVANCE_RING();
+
+ for (i = 0; i < nbox; i++) {
+ int tileoffset, nrtilesx, nrtilesy, j;
+ /* it looks like r200 needs rv-style clears, at least if hierz is not enabled? */
+ if ((dev_priv->flags&CHIP_HAS_HIERZ) && !(dev_priv->microcode_version==UCODE_R200)) {
+ /* FIXME : figure this out for r200 (when hierz is enabled). Or
+ maybe r200 actually doesn't need to put the low-res z value into
+ the tile cache like r100, but just needs to clear the hi-level z-buffer?
+ Works for R100, both with hierz and without.
+ R100 seems to operate on 2x1 8x8 tiles, but...
+ odd: offset/nrtiles need to be 64 pix (4 block) aligned? Potentially
+ problematic with resolutions which are not 64 pix aligned? */
+ tileoffset = ((pbox[i].y1 >> 3) * depthpixperline + pbox[i].x1) >> 6;
+ nrtilesx = ((pbox[i].x2 & ~63) - (pbox[i].x1 & ~63)) >> 4;
+ nrtilesy = (pbox[i].y2 >> 3) - (pbox[i].y1 >> 3);
+ for (j = 0; j <= nrtilesy; j++) {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+ /* first tile */
+ OUT_RING( tileoffset * 8 );
+ /* the number of tiles to clear */
+ OUT_RING( nrtilesx + 4 );
+ /* clear mask : chooses the clearing pattern. */
+ OUT_RING( clearmask );
+ ADVANCE_RING();
+ tileoffset += depthpixperline >> 6;
+ }
+ }
+ else if (dev_priv->microcode_version==UCODE_R200) {
+ /* works for rv250. */
+ /* find first macro tile (8x2 4x4 z-pixels on rv250) */
+ tileoffset = ((pbox[i].y1 >> 3) * depthpixperline + pbox[i].x1) >> 5;
+ nrtilesx = (pbox[i].x2 >> 5) - (pbox[i].x1 >> 5);
+ nrtilesy = (pbox[i].y2 >> 3) - (pbox[i].y1 >> 3);
+ for (j = 0; j <= nrtilesy; j++) {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+ /* first tile */
+ /* judging by the first tile offset needed, could possibly
+ directly address/clear 4x4 tiles instead of 8x2 * 4x4
+ macro tiles, though would still need clear mask for
+ right/bottom if truely 4x4 granularity is desired ? */
+ OUT_RING( tileoffset * 16 );
+ /* the number of tiles to clear */
+ OUT_RING( nrtilesx + 1 );
+ /* clear mask : chooses the clearing pattern. */
+ OUT_RING( clearmask );
+ ADVANCE_RING();
+ tileoffset += depthpixperline >> 5;
+ }
+ }
+ else { /* rv 100 */
+ /* rv100 might not need 64 pix alignment, who knows */
+ /* offsets are, hmm, weird */
+ tileoffset = ((pbox[i].y1 >> 4) * depthpixperline + pbox[i].x1) >> 6;
+ nrtilesx = ((pbox[i].x2 & ~63) - (pbox[i].x1 & ~63)) >> 4;
+ nrtilesy = (pbox[i].y2 >> 4) - (pbox[i].y1 >> 4);
+ for (j = 0; j <= nrtilesy; j++) {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+ OUT_RING( tileoffset * 128 );
+ /* the number of tiles to clear */
+ OUT_RING( nrtilesx + 4 );
+ /* clear mask : chooses the clearing pattern. */
+ OUT_RING( clearmask );
+ ADVANCE_RING();
+ tileoffset += depthpixperline >> 6;
+ }
+ }
+ }
+
+ /* TODO don't always clear all hi-level z tiles */
+ if ((dev_priv->flags & CHIP_HAS_HIERZ) && (dev_priv->microcode_version==UCODE_R200)
+ && (flags & RADEON_USE_HIERZ))
+ /* r100 and cards without hierarchical z-buffer have no high-level z-buffer */
+ /* FIXME : the mask supposedly contains low-res z values. So can't set
+ just to the max (0xff? or actually 0x3fff?), need to take z clear
+ value into account? */
+ {
+ BEGIN_RING( 4 );
+ OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_HIZ, 2 ) );
+ OUT_RING( 0x0 ); /* First tile */
+ OUT_RING( 0x3cc0 );
+ OUT_RING( (0xff<<22)|(0xff<<6)| 0x003f003f);
+ ADVANCE_RING();
+ }
+ }
+
+ /* We have to clear the depth and/or stencil buffers by
+ * rendering a quad into just those buffers. Thus, we have to
+ * make sure the 3D engine is configured correctly.
+ */
+ if ((dev_priv->microcode_version == UCODE_R200) &&
+ (flags & (RADEON_DEPTH | RADEON_STENCIL))) {
+
+ int tempPP_CNTL;
+ int tempRE_CNTL;
+ int tempRB3D_CNTL;
+ int tempRB3D_ZSTENCILCNTL;
+ int tempRB3D_STENCILREFMASK;
+ int tempRB3D_PLANEMASK;
+ int tempSE_CNTL;
+ int tempSE_VTE_CNTL;
+ int tempSE_VTX_FMT_0;
+ int tempSE_VTX_FMT_1;
+ int tempSE_VAP_CNTL;
+ int tempRE_AUX_SCISSOR_CNTL;
+
+ tempPP_CNTL = 0;
+ tempRE_CNTL = 0;
+
+ tempRB3D_CNTL = depth_clear->rb3d_cntl;
+
+ tempRB3D_ZSTENCILCNTL = depth_clear->rb3d_zstencilcntl;
+ tempRB3D_STENCILREFMASK = 0x0;
+
+ tempSE_CNTL = depth_clear->se_cntl;
+
+
+
+ /* Disable TCL */
+
+ tempSE_VAP_CNTL = (/* SE_VAP_CNTL__FORCE_W_TO_ONE_MASK | */
+ (0x9 << SE_VAP_CNTL__VF_MAX_VTX_NUM__SHIFT));
+
+ tempRB3D_PLANEMASK = 0x0;
+
+ tempRE_AUX_SCISSOR_CNTL = 0x0;
+
+ tempSE_VTE_CNTL =
+ SE_VTE_CNTL__VTX_XY_FMT_MASK |
+ SE_VTE_CNTL__VTX_Z_FMT_MASK;
+
+ /* Vertex format (X, Y, Z, W)*/
+ tempSE_VTX_FMT_0 =
+ SE_VTX_FMT_0__VTX_Z0_PRESENT_MASK |
+ SE_VTX_FMT_0__VTX_W0_PRESENT_MASK;
+ tempSE_VTX_FMT_1 = 0x0;
+
+
+ /*
+ * Depth buffer specific enables
+ */
+ if (flags & RADEON_DEPTH) {
+ /* Enable depth buffer */
+ tempRB3D_CNTL |= RADEON_Z_ENABLE;
+ } else {
+ /* Disable depth buffer */
+ tempRB3D_CNTL &= ~RADEON_Z_ENABLE;
+ }
+
+ /*
+ * Stencil buffer specific enables
+ */
+ if ( flags & RADEON_STENCIL ) {
+ tempRB3D_CNTL |= RADEON_STENCIL_ENABLE;
+ tempRB3D_STENCILREFMASK = clear->depth_mask;
+ } else {
+ tempRB3D_CNTL &= ~RADEON_STENCIL_ENABLE;
+ tempRB3D_STENCILREFMASK = 0x00000000;
+ }
+
+ if (flags & RADEON_USE_COMP_ZBUF) {
+ tempRB3D_ZSTENCILCNTL |= RADEON_Z_COMPRESSION_ENABLE |
+ RADEON_Z_DECOMPRESSION_ENABLE;
+ }
+ if (flags & RADEON_USE_HIERZ) {
+ tempRB3D_ZSTENCILCNTL |= RADEON_Z_HIERARCHY_ENABLE;
+ }
+
+ BEGIN_RING( 26 );
+ RADEON_WAIT_UNTIL_2D_IDLE();
+
+ OUT_RING_REG( RADEON_PP_CNTL, tempPP_CNTL );
+ OUT_RING_REG( R200_RE_CNTL, tempRE_CNTL );
+ OUT_RING_REG( RADEON_RB3D_CNTL, tempRB3D_CNTL );
+ OUT_RING_REG( RADEON_RB3D_ZSTENCILCNTL,
+ tempRB3D_ZSTENCILCNTL );
+ OUT_RING_REG( RADEON_RB3D_STENCILREFMASK,
+ tempRB3D_STENCILREFMASK );
+ OUT_RING_REG( RADEON_RB3D_PLANEMASK, tempRB3D_PLANEMASK );
+ OUT_RING_REG( RADEON_SE_CNTL, tempSE_CNTL );
+ OUT_RING_REG( R200_SE_VTE_CNTL, tempSE_VTE_CNTL );
+ OUT_RING_REG( R200_SE_VTX_FMT_0, tempSE_VTX_FMT_0 );
+ OUT_RING_REG( R200_SE_VTX_FMT_1, tempSE_VTX_FMT_1 );
+ OUT_RING_REG( R200_SE_VAP_CNTL, tempSE_VAP_CNTL );
+ OUT_RING_REG( R200_RE_AUX_SCISSOR_CNTL,
+ tempRE_AUX_SCISSOR_CNTL );
+ ADVANCE_RING();
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->ctx_owner = 0;
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+
+ /* Funny that this should be required --
+ * sets top-left?
+ */
+ radeon_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+
+ BEGIN_RING( 14 );
+ OUT_RING( CP_PACKET3( R200_3D_DRAW_IMMD_2, 12 ) );
+ OUT_RING( (RADEON_PRIM_TYPE_RECT_LIST |
+ RADEON_PRIM_WALK_RING |
+ (3 << RADEON_NUM_VERTICES_SHIFT)) );
+ OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x3f800000 );
+ OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x3f800000 );
+ OUT_RING( depth_boxes[i].ui[CLEAR_X2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x3f800000 );
+ ADVANCE_RING();
+ }
+ }
+ else if ( (flags & (RADEON_DEPTH | RADEON_STENCIL)) ) {
+
+ int tempRB3D_ZSTENCILCNTL = depth_clear->rb3d_zstencilcntl;
+
+ rb3d_cntl = depth_clear->rb3d_cntl;
+
+ if ( flags & RADEON_DEPTH ) {
+ rb3d_cntl |= RADEON_Z_ENABLE;
+ } else {
+ rb3d_cntl &= ~RADEON_Z_ENABLE;
+ }
+
+ if ( flags & RADEON_STENCIL ) {
+ rb3d_cntl |= RADEON_STENCIL_ENABLE;
+ rb3d_stencilrefmask = clear->depth_mask; /* misnamed field */
+ } else {
+ rb3d_cntl &= ~RADEON_STENCIL_ENABLE;
+ rb3d_stencilrefmask = 0x00000000;
+ }
+
+ if (flags & RADEON_USE_COMP_ZBUF) {
+ tempRB3D_ZSTENCILCNTL |= RADEON_Z_COMPRESSION_ENABLE |
+ RADEON_Z_DECOMPRESSION_ENABLE;
+ }
+ if (flags & RADEON_USE_HIERZ) {
+ tempRB3D_ZSTENCILCNTL |= RADEON_Z_HIERARCHY_ENABLE;
+ }
+
+ BEGIN_RING( 13 );
+ RADEON_WAIT_UNTIL_2D_IDLE();
+
+ OUT_RING( CP_PACKET0( RADEON_PP_CNTL, 1 ) );
+ OUT_RING( 0x00000000 );
+ OUT_RING( rb3d_cntl );
+
+ OUT_RING_REG( RADEON_RB3D_ZSTENCILCNTL, tempRB3D_ZSTENCILCNTL );
+ OUT_RING_REG( RADEON_RB3D_STENCILREFMASK,
+ rb3d_stencilrefmask );
+ OUT_RING_REG( RADEON_RB3D_PLANEMASK,
+ 0x00000000 );
+ OUT_RING_REG( RADEON_SE_CNTL,
+ depth_clear->se_cntl );
+ ADVANCE_RING();
+
+ /* Make sure we restore the 3D state next time.
+ */
+ dev_priv->sarea_priv->ctx_owner = 0;
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+
+ /* Funny that this should be required --
+ * sets top-left?
+ */
+ radeon_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+
+ BEGIN_RING( 15 );
+
+ OUT_RING( CP_PACKET3( RADEON_3D_DRAW_IMMD, 13 ) );
+ OUT_RING( RADEON_VTX_Z_PRESENT |
+ RADEON_VTX_PKCOLOR_PRESENT);
+ OUT_RING( (RADEON_PRIM_TYPE_RECT_LIST |
+ RADEON_PRIM_WALK_RING |
+ RADEON_MAOS_ENABLE |
+ RADEON_VTX_FMT_RADEON_MODE |
+ (3 << RADEON_NUM_VERTICES_SHIFT)) );
+
+
+ OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x0 );
+
+ OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x0 );
+
+ OUT_RING( depth_boxes[i].ui[CLEAR_X2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+ OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+ OUT_RING( 0x0 );
+
+ ADVANCE_RING();
+ }
+ }
+
+ /* Increment the clear counter. The client-side 3D driver must
+ * wait on this value before performing the clear ioctl. We
+ * need this because the card's so damned fast...
+ */
+ dev_priv->sarea_priv->last_clear++;
+
+ BEGIN_RING( 4 );
+
+ RADEON_CLEAR_AGE( dev_priv->sarea_priv->last_clear );
+ RADEON_WAIT_UNTIL_IDLE();
+
+ ADVANCE_RING();
+}
+
+static void radeon_cp_dispatch_swap( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int nbox = sarea_priv->nbox;
+ drm_clip_rect_t *pbox = sarea_priv->boxes;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ /* Do some trivial performance monitoring...
+ */
+ if (dev_priv->do_boxes)
+ radeon_cp_performance_boxes( dev_priv );
+
+
+ /* Wait for the 3D stream to idle before dispatching the bitblt.
+ * This will prevent data corruption between the two streams.
+ */
+ BEGIN_RING( 2 );
+
+ RADEON_WAIT_UNTIL_3D_IDLE();
+
+ ADVANCE_RING();
+
+ for ( i = 0 ; i < nbox ; i++ ) {
+ int x = pbox[i].x1;
+ int y = pbox[i].y1;
+ int w = pbox[i].x2 - x;
+ int h = pbox[i].y2 - y;
+
+ DRM_DEBUG( "dispatch swap %d,%d-%d,%d\n",
+ x, y, w, h );
+
+ BEGIN_RING( 7 );
+
+ OUT_RING( CP_PACKET3( RADEON_CNTL_BITBLT_MULTI, 5 ) );
+ OUT_RING( RADEON_GMC_SRC_PITCH_OFFSET_CNTL |
+ RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+ RADEON_GMC_BRUSH_NONE |
+ (dev_priv->color_fmt << 8) |
+ RADEON_GMC_SRC_DATATYPE_COLOR |
+ RADEON_ROP3_S |
+ RADEON_DP_SRC_SOURCE_MEMORY |
+ RADEON_GMC_CLR_CMP_CNTL_DIS |
+ RADEON_GMC_WR_MSK_DIS );
+
+ /* Make this work even if front & back are flipped:
+ */
+ if (dev_priv->current_page == 0) {
+ OUT_RING( dev_priv->back_pitch_offset );
+ OUT_RING( dev_priv->front_pitch_offset );
+ }
+ else {
+ OUT_RING( dev_priv->front_pitch_offset );
+ OUT_RING( dev_priv->back_pitch_offset );
+ }
+
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (x << 16) | y );
+ OUT_RING( (w << 16) | h );
+
+ ADVANCE_RING();
+ }
+
+ /* Increment the frame counter. The client-side 3D driver must
+ * throttle the framerate by waiting for this value before
+ * performing the swapbuffer ioctl.
+ */
+ dev_priv->sarea_priv->last_frame++;
+
+ BEGIN_RING( 4 );
+
+ RADEON_FRAME_AGE( dev_priv->sarea_priv->last_frame );
+ RADEON_WAIT_UNTIL_2D_IDLE();
+
+ ADVANCE_RING();
+}
+
+static void radeon_cp_dispatch_flip( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_sarea_t *sarea = (drm_sarea_t *)dev_priv->sarea->handle;
+ int offset = (dev_priv->current_page == 1)
+ ? dev_priv->front_offset : dev_priv->back_offset;
+ RING_LOCALS;
+ DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n",
+ __FUNCTION__,
+ dev_priv->current_page,
+ dev_priv->sarea_priv->pfCurrentPage);
+
+ /* Do some trivial performance monitoring...
+ */
+ if (dev_priv->do_boxes) {
+ dev_priv->stats.boxes |= RADEON_BOX_FLIP;
+ radeon_cp_performance_boxes( dev_priv );
+ }
+
+ /* Update the frame offsets for both CRTCs
+ */
+ BEGIN_RING( 6 );
+
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ OUT_RING_REG( RADEON_CRTC_OFFSET, ( ( sarea->frame.y * dev_priv->front_pitch
+ + sarea->frame.x
+ * ( dev_priv->color_fmt - 2 ) ) & ~7 )
+ + offset );
+ OUT_RING_REG( RADEON_CRTC2_OFFSET, dev_priv->sarea_priv->crtc2_base
+ + offset );
+
+ ADVANCE_RING();
+
+ /* Increment the frame counter. The client-side 3D driver must
+ * throttle the framerate by waiting for this value before
+ * performing the swapbuffer ioctl.
+ */
+ dev_priv->sarea_priv->last_frame++;
+ dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page =
+ 1 - dev_priv->current_page;
+
+ BEGIN_RING( 2 );
+
+ RADEON_FRAME_AGE( dev_priv->sarea_priv->last_frame );
+
+ ADVANCE_RING();
+}
+
+static int bad_prim_vertex_nr( int primitive, int nr )
+{
+ switch (primitive & RADEON_PRIM_TYPE_MASK) {
+ case RADEON_PRIM_TYPE_NONE:
+ case RADEON_PRIM_TYPE_POINT:
+ return nr < 1;
+ case RADEON_PRIM_TYPE_LINE:
+ return (nr & 1) || nr == 0;
+ case RADEON_PRIM_TYPE_LINE_STRIP:
+ return nr < 2;
+ case RADEON_PRIM_TYPE_TRI_LIST:
+ case RADEON_PRIM_TYPE_3VRT_POINT_LIST:
+ case RADEON_PRIM_TYPE_3VRT_LINE_LIST:
+ case RADEON_PRIM_TYPE_RECT_LIST:
+ return nr % 3 || nr == 0;
+ case RADEON_PRIM_TYPE_TRI_FAN:
+ case RADEON_PRIM_TYPE_TRI_STRIP:
+ return nr < 3;
+ default:
+ return 1;
+ }
+}
+
+
+
+typedef struct {
+ unsigned int start;
+ unsigned int finish;
+ unsigned int prim;
+ unsigned int numverts;
+ unsigned int offset;
+ unsigned int vc_format;
+} drm_radeon_tcl_prim_t;
+
+static void radeon_cp_dispatch_vertex( drm_device_t *dev,
+ drm_buf_t *buf,
+ drm_radeon_tcl_prim_t *prim )
+
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int offset = dev_priv->gart_buffers_offset + buf->offset + prim->start;
+ int numverts = (int)prim->numverts;
+ int nbox = sarea_priv->nbox;
+ int i = 0;
+ RING_LOCALS;
+
+ DRM_DEBUG("hwprim 0x%x vfmt 0x%x %d..%d %d verts\n",
+ prim->prim,
+ prim->vc_format,
+ prim->start,
+ prim->finish,
+ prim->numverts);
+
+ if (bad_prim_vertex_nr( prim->prim, prim->numverts )) {
+ DRM_ERROR( "bad prim %x numverts %d\n",
+ prim->prim, prim->numverts );
+ return;
+ }
+
+ do {
+ /* Emit the next cliprect */
+ if ( i < nbox ) {
+ radeon_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+ }
+
+ /* Emit the vertex buffer rendering commands */
+ BEGIN_RING( 5 );
+
+ OUT_RING( CP_PACKET3( RADEON_3D_RNDR_GEN_INDX_PRIM, 3 ) );
+ OUT_RING( offset );
+ OUT_RING( numverts );
+ OUT_RING( prim->vc_format );
+ OUT_RING( prim->prim | RADEON_PRIM_WALK_LIST |
+ RADEON_COLOR_ORDER_RGBA |
+ RADEON_VTX_FMT_RADEON_MODE |
+ (numverts << RADEON_NUM_VERTICES_SHIFT) );
+
+ ADVANCE_RING();
+
+ i++;
+ } while ( i < nbox );
+}
+
+
+
+static void radeon_cp_discard_buffer( drm_device_t *dev, drm_buf_t *buf )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_buf_priv_t *buf_priv = buf->dev_private;
+ RING_LOCALS;
+
+ buf_priv->age = ++dev_priv->sarea_priv->last_dispatch;
+
+ /* Emit the vertex buffer age */
+ BEGIN_RING( 2 );
+ RADEON_DISPATCH_AGE( buf_priv->age );
+ ADVANCE_RING();
+
+ buf->pending = 1;
+ buf->used = 0;
+}
+
+static void radeon_cp_dispatch_indirect( drm_device_t *dev,
+ drm_buf_t *buf,
+ int start, int end )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+ DRM_DEBUG( "indirect: buf=%d s=0x%x e=0x%x\n",
+ buf->idx, start, end );
+
+ if ( start != end ) {
+ int offset = (dev_priv->gart_buffers_offset
+ + buf->offset + start);
+ int dwords = (end - start + 3) / sizeof(u32);
+
+ /* Indirect buffer data must be an even number of
+ * dwords, so if we've been given an odd number we must
+ * pad the data with a Type-2 CP packet.
+ */
+ if ( dwords & 1 ) {
+ u32 *data = (u32 *)
+ ((char *)dev->agp_buffer_map->handle
+ + buf->offset + start);
+ data[dwords++] = RADEON_CP_PACKET2;
+ }
+
+ /* Fire off the indirect buffer */
+ BEGIN_RING( 3 );
+
+ OUT_RING( CP_PACKET0( RADEON_CP_IB_BASE, 1 ) );
+ OUT_RING( offset );
+ OUT_RING( dwords );
+
+ ADVANCE_RING();
+ }
+}
+
+
+static void radeon_cp_dispatch_indices( drm_device_t *dev,
+ drm_buf_t *elt_buf,
+ drm_radeon_tcl_prim_t *prim )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ int offset = dev_priv->gart_buffers_offset + prim->offset;
+ u32 *data;
+ int dwords;
+ int i = 0;
+ int start = prim->start + RADEON_INDEX_PRIM_OFFSET;
+ int count = (prim->finish - start) / sizeof(u16);
+ int nbox = sarea_priv->nbox;
+
+ DRM_DEBUG("hwprim 0x%x vfmt 0x%x %d..%d offset: %x nr %d\n",
+ prim->prim,
+ prim->vc_format,
+ prim->start,
+ prim->finish,
+ prim->offset,
+ prim->numverts);
+
+ if (bad_prim_vertex_nr( prim->prim, count )) {
+ DRM_ERROR( "bad prim %x count %d\n",
+ prim->prim, count );
+ return;
+ }
+
+
+ if ( start >= prim->finish ||
+ (prim->start & 0x7) ) {
+ DRM_ERROR( "buffer prim %d\n", prim->prim );
+ return;
+ }
+
+ dwords = (prim->finish - prim->start + 3) / sizeof(u32);
+
+ data = (u32 *)((char *)dev->agp_buffer_map->handle +
+ elt_buf->offset + prim->start);
+
+ data[0] = CP_PACKET3( RADEON_3D_RNDR_GEN_INDX_PRIM, dwords-2 );
+ data[1] = offset;
+ data[2] = prim->numverts;
+ data[3] = prim->vc_format;
+ data[4] = (prim->prim |
+ RADEON_PRIM_WALK_IND |
+ RADEON_COLOR_ORDER_RGBA |
+ RADEON_VTX_FMT_RADEON_MODE |
+ (count << RADEON_NUM_VERTICES_SHIFT) );
+
+ do {
+ if ( i < nbox )
+ radeon_emit_clip_rect( dev_priv,
+ &sarea_priv->boxes[i] );
+
+ radeon_cp_dispatch_indirect( dev, elt_buf,
+ prim->start,
+ prim->finish );
+
+ i++;
+ } while ( i < nbox );
+
+}
+
+#define RADEON_MAX_TEXTURE_SIZE (RADEON_BUFFER_SIZE - 8 * sizeof(u32))
+
+static int radeon_cp_dispatch_texture( DRMFILE filp,
+ drm_device_t *dev,
+ drm_radeon_texture_t *tex,
+ drm_radeon_tex_image_t *image )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_buf_t *buf;
+ u32 format;
+ u32 *buffer;
+ const u8 __user *data;
+ int size, dwords, tex_width, blit_width;
+ u32 height;
+ int i;
+ u32 texpitch, microtile;
+ RING_LOCALS;
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &tex->offset ) ) {
+ DRM_ERROR( "Invalid destination offset\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ dev_priv->stats.boxes |= RADEON_BOX_TEXTURE_LOAD;
+
+ /* Flush the pixel cache. This ensures no pixel data gets mixed
+ * up with the texture data from the host data blit, otherwise
+ * part of the texture image may be corrupted.
+ */
+ BEGIN_RING( 4 );
+ RADEON_FLUSH_CACHE();
+ RADEON_WAIT_UNTIL_IDLE();
+ ADVANCE_RING();
+
+#ifdef __BIG_ENDIAN
+ /* The Mesa texture functions provide the data in little endian as the
+ * chip wants it, but we need to compensate for the fact that the CP
+ * ring gets byte-swapped
+ */
+ BEGIN_RING( 2 );
+ OUT_RING_REG( RADEON_RBBM_GUICNTL, RADEON_HOST_DATA_SWAP_32BIT );
+ ADVANCE_RING();
+#endif
+
+
+ /* The compiler won't optimize away a division by a variable,
+ * even if the only legal values are powers of two. Thus, we'll
+ * use a shift instead.
+ */
+ switch ( tex->format ) {
+ case RADEON_TXFORMAT_ARGB8888:
+ case RADEON_TXFORMAT_RGBA8888:
+ format = RADEON_COLOR_FORMAT_ARGB8888;
+ tex_width = tex->width * 4;
+ blit_width = image->width * 4;
+ break;
+ case RADEON_TXFORMAT_AI88:
+ case RADEON_TXFORMAT_ARGB1555:
+ case RADEON_TXFORMAT_RGB565:
+ case RADEON_TXFORMAT_ARGB4444:
+ case RADEON_TXFORMAT_VYUY422:
+ case RADEON_TXFORMAT_YVYU422:
+ format = RADEON_COLOR_FORMAT_RGB565;
+ tex_width = tex->width * 2;
+ blit_width = image->width * 2;
+ break;
+ case RADEON_TXFORMAT_I8:
+ case RADEON_TXFORMAT_RGB332:
+ format = RADEON_COLOR_FORMAT_CI8;
+ tex_width = tex->width * 1;
+ blit_width = image->width * 1;
+ break;
+ default:
+ DRM_ERROR( "invalid texture format %d\n", tex->format );
+ return DRM_ERR(EINVAL);
+ }
+ texpitch = tex->pitch;
+ if ((texpitch << 22) & RADEON_DST_TILE_MICRO) {
+ microtile = 1;
+ if (tex_width < 64) {
+ texpitch &= ~(RADEON_DST_TILE_MICRO >> 22);
+ /* we got tiled coordinates, untile them */
+ image->x *= 2;
+ }
+ }
+ else microtile = 0;
+
+ DRM_DEBUG("tex=%dx%d blit=%d\n", tex_width, tex->height, blit_width );
+
+ do {
+ DRM_DEBUG( "tex: ofs=0x%x p=%d f=%d x=%hd y=%hd w=%hd h=%hd\n",
+ tex->offset >> 10, tex->pitch, tex->format,
+ image->x, image->y, image->width, image->height );
+
+ /* Make a copy of some parameters in case we have to
+ * update them for a multi-pass texture blit.
+ */
+ height = image->height;
+ data = (const u8 __user *)image->data;
+
+ size = height * blit_width;
+
+ if ( size > RADEON_MAX_TEXTURE_SIZE ) {
+ height = RADEON_MAX_TEXTURE_SIZE / blit_width;
+ size = height * blit_width;
+ } else if ( size < 4 && size > 0 ) {
+ size = 4;
+ } else if ( size == 0 ) {
+ return 0;
+ }
+
+ buf = radeon_freelist_get( dev );
+ if ( 0 && !buf ) {
+ radeon_do_cp_idle( dev_priv );
+ buf = radeon_freelist_get( dev );
+ }
+ if ( !buf ) {
+ DRM_DEBUG("radeon_cp_dispatch_texture: EAGAIN\n");
+ if (DRM_COPY_TO_USER( tex->image, image, sizeof(*image) ))
+ return DRM_ERR(EFAULT);
+ return DRM_ERR(EAGAIN);
+ }
+
+
+ /* Dispatch the indirect buffer.
+ */
+ buffer = (u32*)((char*)dev->agp_buffer_map->handle + buf->offset);
+ dwords = size / 4;
+ buffer[0] = CP_PACKET3( RADEON_CNTL_HOSTDATA_BLT, dwords + 6 );
+ buffer[1] = (RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+ RADEON_GMC_BRUSH_NONE |
+ (format << 8) |
+ RADEON_GMC_SRC_DATATYPE_COLOR |
+ RADEON_ROP3_S |
+ RADEON_DP_SRC_SOURCE_HOST_DATA |
+ RADEON_GMC_CLR_CMP_CNTL_DIS |
+ RADEON_GMC_WR_MSK_DIS);
+
+ buffer[2] = (texpitch << 22) | (tex->offset >> 10);
+ buffer[3] = 0xffffffff;
+ buffer[4] = 0xffffffff;
+ buffer[5] = (image->y << 16) | image->x;
+ buffer[6] = (height << 16) | image->width;
+ buffer[7] = dwords;
+ buffer += 8;
+
+
+
+ if (microtile) {
+ /* texture micro tiling in use, minimum texture width is thus 16 bytes.
+ however, we cannot use blitter directly for texture width < 64 bytes,
+ since minimum tex pitch is 64 bytes and we need this to match
+ the texture width, otherwise the blitter will tile it wrong.
+ Thus, tiling manually in this case. Additionally, need to special
+ case tex height = 1, since our actual image will have height 2
+ and we need to ensure we don't read beyond the texture size
+ from user space. */
+ if (tex->height == 1) {
+ if (tex_width >= 64 || tex_width <= 16) {
+ if (DRM_COPY_FROM_USER(buffer, data,
+ tex_width * sizeof(u32))) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ } else if (tex_width == 32) {
+ if (DRM_COPY_FROM_USER(buffer, data, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ if (DRM_COPY_FROM_USER(buffer + 8, data + 16, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ }
+ } else if (tex_width >= 64 || tex_width == 16) {
+ if (DRM_COPY_FROM_USER(buffer, data,
+ dwords * sizeof(u32))) {
+ DRM_ERROR("EFAULT on data, %d dwords\n",
+ dwords);
+ return DRM_ERR(EFAULT);
+ }
+ } else if (tex_width < 16) {
+ for (i = 0; i < tex->height; i++) {
+ if (DRM_COPY_FROM_USER(buffer, data, tex_width)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ buffer += 4;
+ data += tex_width;
+ }
+ } else if (tex_width == 32) {
+ /* TODO: make sure this works when not fitting in one buffer
+ (i.e. 32bytes x 2048...) */
+ for (i = 0; i < tex->height; i += 2) {
+ if (DRM_COPY_FROM_USER(buffer, data, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ data += 16;
+ if (DRM_COPY_FROM_USER(buffer + 8, data, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ data += 16;
+ if (DRM_COPY_FROM_USER(buffer + 4, data, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ data += 16;
+ if (DRM_COPY_FROM_USER(buffer + 12, data, 16)) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n",
+ tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ data += 16;
+ buffer += 16;
+ }
+ }
+ }
+ else {
+ if (tex_width >= 32) {
+ /* Texture image width is larger than the minimum, so we
+ * can upload it directly.
+ */
+ if (DRM_COPY_FROM_USER(buffer, data,
+ dwords * sizeof(u32))) {
+ DRM_ERROR("EFAULT on data, %d dwords\n",
+ dwords);
+ return DRM_ERR(EFAULT);
+ }
+ } else {
+ /* Texture image width is less than the minimum, so we
+ * need to pad out each image scanline to the minimum
+ * width.
+ */
+ for (i = 0 ; i < tex->height ; i++) {
+ if (DRM_COPY_FROM_USER(buffer, data, tex_width )) {
+ DRM_ERROR("EFAULT on pad, %d bytes\n", tex_width);
+ return DRM_ERR(EFAULT);
+ }
+ buffer += 8;
+ data += tex_width;
+ }
+ }
+ }
+
+ buf->filp = filp;
+ buf->used = (dwords + 8) * sizeof(u32);
+ radeon_cp_dispatch_indirect( dev, buf, 0, buf->used );
+ radeon_cp_discard_buffer( dev, buf );
+
+ /* Update the input parameters for next time */
+ image->y += height;
+ image->height -= height;
+ image->data = (const u8 __user *)image->data + size;
+ } while (image->height > 0);
+
+ /* Flush the pixel cache after the blit completes. This ensures
+ * the texture data is written out to memory before rendering
+ * continues.
+ */
+ BEGIN_RING( 4 );
+ RADEON_FLUSH_CACHE();
+ RADEON_WAIT_UNTIL_2D_IDLE();
+ ADVANCE_RING();
+ return 0;
+}
+
+
+static void radeon_cp_dispatch_stipple( drm_device_t *dev, u32 *stipple )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ int i;
+ RING_LOCALS;
+ DRM_DEBUG( "\n" );
+
+ BEGIN_RING( 35 );
+
+ OUT_RING( CP_PACKET0( RADEON_RE_STIPPLE_ADDR, 0 ) );
+ OUT_RING( 0x00000000 );
+
+ OUT_RING( CP_PACKET0_TABLE( RADEON_RE_STIPPLE_DATA, 31 ) );
+ for ( i = 0 ; i < 32 ; i++ ) {
+ OUT_RING( stipple[i] );
+ }
+
+ ADVANCE_RING();
+}
+
+static void radeon_apply_surface_regs(int surf_index, drm_radeon_private_t *dev_priv)
+{
+ if (!dev_priv->mmio)
+ return;
+
+ radeon_do_cp_idle(dev_priv);
+
+ RADEON_WRITE(RADEON_SURFACE0_INFO + 16*surf_index,
+ dev_priv->surfaces[surf_index].flags);
+ RADEON_WRITE(RADEON_SURFACE0_LOWER_BOUND + 16*surf_index,
+ dev_priv->surfaces[surf_index].lower);
+ RADEON_WRITE(RADEON_SURFACE0_UPPER_BOUND + 16*surf_index,
+ dev_priv->surfaces[surf_index].upper);
+}
+
+
+/* Allocates a virtual surface
+ * doesn't always allocate a real surface, will stretch an existing
+ * surface when possible.
+ *
+ * Note that refcount can be at most 2, since during a free refcount=3
+ * might mean we have to allocate a new surface which might not always
+ * be available.
+ * For example : we allocate three contigous surfaces ABC. If B is
+ * freed, we suddenly need two surfaces to store A and C, which might
+ * not always be available.
+ */
+static int alloc_surface(drm_radeon_surface_alloc_t* new, drm_radeon_private_t *dev_priv, DRMFILE filp)
+{
+ struct radeon_virt_surface *s;
+ int i;
+ int virt_surface_index;
+ uint32_t new_upper, new_lower;
+
+ new_lower = new->address;
+ new_upper = new_lower + new->size - 1;
+
+ /* sanity check */
+ if ((new_lower >= new_upper) || (new->flags == 0) || (new->size == 0) ||
+ ((new_upper & RADEON_SURF_ADDRESS_FIXED_MASK) != RADEON_SURF_ADDRESS_FIXED_MASK) ||
+ ((new_lower & RADEON_SURF_ADDRESS_FIXED_MASK) != 0))
+ return -1;
+
+ /* make sure there is no overlap with existing surfaces */
+ for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+ if ((dev_priv->surfaces[i].refcount != 0) &&
+ (( (new_lower >= dev_priv->surfaces[i].lower) &&
+ (new_lower < dev_priv->surfaces[i].upper) ) ||
+ ( (new_lower < dev_priv->surfaces[i].lower) &&
+ (new_upper > dev_priv->surfaces[i].lower) )) ){
+ return -1;}
+ }
+
+ /* find a virtual surface */
+ for (i = 0; i < 2*RADEON_MAX_SURFACES; i++)
+ if (dev_priv->virt_surfaces[i].filp == 0)
+ break;
+ if (i == 2*RADEON_MAX_SURFACES) {
+ return -1;}
+ virt_surface_index = i;
+
+ /* try to reuse an existing surface */
+ for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+ /* extend before */
+ if ((dev_priv->surfaces[i].refcount == 1) &&
+ (new->flags == dev_priv->surfaces[i].flags) &&
+ (new_upper + 1 == dev_priv->surfaces[i].lower)) {
+ s = &(dev_priv->virt_surfaces[virt_surface_index]);
+ s->surface_index = i;
+ s->lower = new_lower;
+ s->upper = new_upper;
+ s->flags = new->flags;
+ s->filp = filp;
+ dev_priv->surfaces[i].refcount++;
+ dev_priv->surfaces[i].lower = s->lower;
+ radeon_apply_surface_regs(s->surface_index, dev_priv);
+ return virt_surface_index;
+ }
+
+ /* extend after */
+ if ((dev_priv->surfaces[i].refcount == 1) &&
+ (new->flags == dev_priv->surfaces[i].flags) &&
+ (new_lower == dev_priv->surfaces[i].upper + 1)) {
+ s = &(dev_priv->virt_surfaces[virt_surface_index]);
+ s->surface_index = i;
+ s->lower = new_lower;
+ s->upper = new_upper;
+ s->flags = new->flags;
+ s->filp = filp;
+ dev_priv->surfaces[i].refcount++;
+ dev_priv->surfaces[i].upper = s->upper;
+ radeon_apply_surface_regs(s->surface_index, dev_priv);
+ return virt_surface_index;
+ }
+ }
+
+ /* okay, we need a new one */
+ for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+ if (dev_priv->surfaces[i].refcount == 0) {
+ s = &(dev_priv->virt_surfaces[virt_surface_index]);
+ s->surface_index = i;
+ s->lower = new_lower;
+ s->upper = new_upper;
+ s->flags = new->flags;
+ s->filp = filp;
+ dev_priv->surfaces[i].refcount = 1;
+ dev_priv->surfaces[i].lower = s->lower;
+ dev_priv->surfaces[i].upper = s->upper;
+ dev_priv->surfaces[i].flags = s->flags;
+ radeon_apply_surface_regs(s->surface_index, dev_priv);
+ return virt_surface_index;
+ }
+ }
+
+ /* we didn't find anything */
+ return -1;
+}
+
+static int free_surface(DRMFILE filp, drm_radeon_private_t *dev_priv, int lower)
+{
+ struct radeon_virt_surface *s;
+ int i;
+ /* find the virtual surface */
+ for(i = 0; i < 2*RADEON_MAX_SURFACES; i++) {
+ s = &(dev_priv->virt_surfaces[i]);
+ if (s->filp) {
+ if ((lower == s->lower) && (filp == s->filp)) {
+ if (dev_priv->surfaces[s->surface_index].lower == s->lower)
+ dev_priv->surfaces[s->surface_index].lower = s->upper;
+
+ if (dev_priv->surfaces[s->surface_index].upper == s->upper)
+ dev_priv->surfaces[s->surface_index].upper = s->lower;
+
+ dev_priv->surfaces[s->surface_index].refcount--;
+ if (dev_priv->surfaces[s->surface_index].refcount == 0)
+ dev_priv->surfaces[s->surface_index].flags = 0;
+ s->filp = NULL;
+ radeon_apply_surface_regs(s->surface_index, dev_priv);
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+static void radeon_surfaces_release(DRMFILE filp, drm_radeon_private_t *dev_priv)
+{
+ int i;
+ for( i = 0; i < 2*RADEON_MAX_SURFACES; i++)
+ {
+ if (dev_priv->virt_surfaces[i].filp == filp)
+ free_surface(filp, dev_priv, dev_priv->virt_surfaces[i].lower);
+ }
+}
+
+/* ================================================================
+ * IOCTL functions
+ */
+static int radeon_surface_alloc(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_surface_alloc_t alloc;
+
+ if (!dev_priv) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(alloc, (drm_radeon_surface_alloc_t __user *)data,
+ sizeof(alloc));
+
+ if (alloc_surface(&alloc, dev_priv, filp) == -1)
+ return DRM_ERR(EINVAL);
+ else
+ return 0;
+}
+
+static int radeon_surface_free(DRM_IOCTL_ARGS)
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_surface_free_t memfree;
+
+ if (!dev_priv) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL(memfree, (drm_radeon_mem_free_t __user *)data,
+ sizeof(memfree) );
+
+ if (free_surface(filp, dev_priv, memfree.address))
+ return DRM_ERR(EINVAL);
+ else
+ return 0;
+}
+
+static int radeon_cp_clear( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_radeon_clear_t clear;
+ drm_radeon_clear_rect_t depth_boxes[RADEON_NR_SAREA_CLIPRECTS];
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( clear, (drm_radeon_clear_t __user *)data,
+ sizeof(clear) );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if ( sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = RADEON_NR_SAREA_CLIPRECTS;
+
+ if ( DRM_COPY_FROM_USER( &depth_boxes, clear.depth_boxes,
+ sarea_priv->nbox * sizeof(depth_boxes[0]) ) )
+ return DRM_ERR(EFAULT);
+
+ radeon_cp_dispatch_clear( dev, &clear, depth_boxes );
+
+ COMMIT_RING();
+ return 0;
+}
+
+
+/* Not sure why this isn't set all the time:
+ */
+static int radeon_do_init_pageflip( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ DRM_DEBUG( "\n" );
+
+ BEGIN_RING( 6 );
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ OUT_RING( CP_PACKET0( RADEON_CRTC_OFFSET_CNTL, 0 ) );
+ OUT_RING( RADEON_READ( RADEON_CRTC_OFFSET_CNTL ) | RADEON_CRTC_OFFSET_FLIP_CNTL );
+ OUT_RING( CP_PACKET0( RADEON_CRTC2_OFFSET_CNTL, 0 ) );
+ OUT_RING( RADEON_READ( RADEON_CRTC2_OFFSET_CNTL ) | RADEON_CRTC_OFFSET_FLIP_CNTL );
+ ADVANCE_RING();
+
+ dev_priv->page_flipping = 1;
+ dev_priv->current_page = 0;
+ dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page;
+
+ return 0;
+}
+
+/* Called whenever a client dies, from drm_release.
+ * NOTE: Lock isn't necessarily held when this is called!
+ */
+static int radeon_do_cleanup_pageflip( drm_device_t *dev )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ if (dev_priv->current_page != 0)
+ radeon_cp_dispatch_flip( dev );
+
+ dev_priv->page_flipping = 0;
+ return 0;
+}
+
+/* Swapping and flipping are different operations, need different ioctls.
+ * They can & should be intermixed to support multiple 3d windows.
+ */
+static int radeon_cp_flip( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if (!dev_priv->page_flipping)
+ radeon_do_init_pageflip( dev );
+
+ radeon_cp_dispatch_flip( dev );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_swap( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ DRM_DEBUG( "\n" );
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ if ( sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS )
+ sarea_priv->nbox = RADEON_NR_SAREA_CLIPRECTS;
+
+ radeon_cp_dispatch_swap( dev );
+ dev_priv->sarea_priv->ctx_owner = 0;
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_vertex( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_radeon_vertex_t vertex;
+ drm_radeon_tcl_prim_t prim;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( vertex, (drm_radeon_vertex_t __user *)data,
+ sizeof(vertex) );
+
+ DRM_DEBUG( "pid=%d index=%d count=%d discard=%d\n",
+ DRM_CURRENTPID,
+ vertex.idx, vertex.count, vertex.discard );
+
+ if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ vertex.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+ if ( vertex.prim < 0 ||
+ vertex.prim > RADEON_PRIM_TYPE_3VRT_LINE_LIST ) {
+ DRM_ERROR( "buffer prim %d\n", vertex.prim );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf = dma->buflist[vertex.idx];
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ /* Build up a prim_t record:
+ */
+ if (vertex.count) {
+ buf->used = vertex.count; /* not used? */
+
+ if ( sarea_priv->dirty & ~RADEON_UPLOAD_CLIPRECTS ) {
+ if ( radeon_emit_state( dev_priv, filp_priv,
+ &sarea_priv->context_state,
+ sarea_priv->tex_state,
+ sarea_priv->dirty ) ) {
+ DRM_ERROR( "radeon_emit_state failed\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ sarea_priv->dirty &= ~(RADEON_UPLOAD_TEX0IMAGES |
+ RADEON_UPLOAD_TEX1IMAGES |
+ RADEON_UPLOAD_TEX2IMAGES |
+ RADEON_REQUIRE_QUIESCENCE);
+ }
+
+ prim.start = 0;
+ prim.finish = vertex.count; /* unused */
+ prim.prim = vertex.prim;
+ prim.numverts = vertex.count;
+ prim.vc_format = dev_priv->sarea_priv->vc_format;
+
+ radeon_cp_dispatch_vertex( dev, buf, &prim );
+ }
+
+ if (vertex.discard) {
+ radeon_cp_discard_buffer( dev, buf );
+ }
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_indices( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_radeon_indices_t elts;
+ drm_radeon_tcl_prim_t prim;
+ int count;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( elts, (drm_radeon_indices_t __user *)data,
+ sizeof(elts) );
+
+ DRM_DEBUG( "pid=%d index=%d start=%d end=%d discard=%d\n",
+ DRM_CURRENTPID,
+ elts.idx, elts.start, elts.end, elts.discard );
+
+ if ( elts.idx < 0 || elts.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ elts.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+ if ( elts.prim < 0 ||
+ elts.prim > RADEON_PRIM_TYPE_3VRT_LINE_LIST ) {
+ DRM_ERROR( "buffer prim %d\n", elts.prim );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf = dma->buflist[elts.idx];
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", elts.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ count = (elts.end - elts.start) / sizeof(u16);
+ elts.start -= RADEON_INDEX_PRIM_OFFSET;
+
+ if ( elts.start & 0x7 ) {
+ DRM_ERROR( "misaligned buffer 0x%x\n", elts.start );
+ return DRM_ERR(EINVAL);
+ }
+ if ( elts.start < buf->used ) {
+ DRM_ERROR( "no header 0x%x - 0x%x\n", elts.start, buf->used );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf->used = elts.end;
+
+ if ( sarea_priv->dirty & ~RADEON_UPLOAD_CLIPRECTS ) {
+ if ( radeon_emit_state( dev_priv, filp_priv,
+ &sarea_priv->context_state,
+ sarea_priv->tex_state,
+ sarea_priv->dirty ) ) {
+ DRM_ERROR( "radeon_emit_state failed\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ sarea_priv->dirty &= ~(RADEON_UPLOAD_TEX0IMAGES |
+ RADEON_UPLOAD_TEX1IMAGES |
+ RADEON_UPLOAD_TEX2IMAGES |
+ RADEON_REQUIRE_QUIESCENCE);
+ }
+
+
+ /* Build up a prim_t record:
+ */
+ prim.start = elts.start;
+ prim.finish = elts.end;
+ prim.prim = elts.prim;
+ prim.offset = 0; /* offset from start of dma buffers */
+ prim.numverts = RADEON_MAX_VB_VERTS; /* duh */
+ prim.vc_format = dev_priv->sarea_priv->vc_format;
+
+ radeon_cp_dispatch_indices( dev, buf, &prim );
+ if (elts.discard) {
+ radeon_cp_discard_buffer( dev, buf );
+ }
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_texture( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_texture_t tex;
+ drm_radeon_tex_image_t image;
+ int ret;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( tex, (drm_radeon_texture_t __user *)data, sizeof(tex) );
+
+ if ( tex.image == NULL ) {
+ DRM_ERROR( "null texture image!\n" );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( DRM_COPY_FROM_USER( &image,
+ (drm_radeon_tex_image_t __user *)tex.image,
+ sizeof(image) ) )
+ return DRM_ERR(EFAULT);
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ ret = radeon_cp_dispatch_texture( filp, dev, &tex, &image );
+
+ COMMIT_RING();
+ return ret;
+}
+
+static int radeon_cp_stipple( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_stipple_t stipple;
+ u32 mask[32];
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( stipple, (drm_radeon_stipple_t __user *)data,
+ sizeof(stipple) );
+
+ if ( DRM_COPY_FROM_USER( &mask, stipple.mask, 32 * sizeof(u32) ) )
+ return DRM_ERR(EFAULT);
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+ radeon_cp_dispatch_stipple( dev, mask );
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_indirect( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_radeon_indirect_t indirect;
+ RING_LOCALS;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( indirect, (drm_radeon_indirect_t __user *)data,
+ sizeof(indirect) );
+
+ DRM_DEBUG( "indirect: idx=%d s=%d e=%d d=%d\n",
+ indirect.idx, indirect.start,
+ indirect.end, indirect.discard );
+
+ if ( indirect.idx < 0 || indirect.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ indirect.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+
+ buf = dma->buflist[indirect.idx];
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", indirect.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( indirect.start < buf->used ) {
+ DRM_ERROR( "reusing indirect: start=0x%x actual=0x%x\n",
+ indirect.start, buf->used );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf->used = indirect.end;
+
+ /* Wait for the 3D stream to idle before the indirect buffer
+ * containing 2D acceleration commands is processed.
+ */
+ BEGIN_RING( 2 );
+
+ RADEON_WAIT_UNTIL_3D_IDLE();
+
+ ADVANCE_RING();
+
+ /* Dispatch the indirect buffer full of commands from the
+ * X server. This is insecure and is thus only available to
+ * privileged clients.
+ */
+ radeon_cp_dispatch_indirect( dev, buf, indirect.start, indirect.end );
+ if (indirect.discard) {
+ radeon_cp_discard_buffer( dev, buf );
+ }
+
+
+ COMMIT_RING();
+ return 0;
+}
+
+static int radeon_cp_vertex2( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf;
+ drm_radeon_vertex2_t vertex;
+ int i;
+ unsigned char laststate;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( vertex, (drm_radeon_vertex2_t __user *)data,
+ sizeof(vertex) );
+
+ DRM_DEBUG( "pid=%d index=%d discard=%d\n",
+ DRM_CURRENTPID,
+ vertex.idx, vertex.discard );
+
+ if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ vertex.idx, dma->buf_count - 1 );
+ return DRM_ERR(EINVAL);
+ }
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ buf = dma->buflist[vertex.idx];
+
+ if ( buf->filp != filp ) {
+ DRM_ERROR( "process %d using buffer owned by %p\n",
+ DRM_CURRENTPID, buf->filp );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( buf->pending ) {
+ DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+ return DRM_ERR(EINVAL);
+ }
+
+ if (sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS)
+ return DRM_ERR(EINVAL);
+
+ for (laststate = 0xff, i = 0 ; i < vertex.nr_prims ; i++) {
+ drm_radeon_prim_t prim;
+ drm_radeon_tcl_prim_t tclprim;
+
+ if ( DRM_COPY_FROM_USER( &prim, &vertex.prim[i], sizeof(prim) ) )
+ return DRM_ERR(EFAULT);
+
+ if ( prim.stateidx != laststate ) {
+ drm_radeon_state_t state;
+
+ if ( DRM_COPY_FROM_USER( &state,
+ &vertex.state[prim.stateidx],
+ sizeof(state) ) )
+ return DRM_ERR(EFAULT);
+
+ if ( radeon_emit_state2( dev_priv, filp_priv, &state ) ) {
+ DRM_ERROR( "radeon_emit_state2 failed\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ laststate = prim.stateidx;
+ }
+
+ tclprim.start = prim.start;
+ tclprim.finish = prim.finish;
+ tclprim.prim = prim.prim;
+ tclprim.vc_format = prim.vc_format;
+
+ if ( prim.prim & RADEON_PRIM_WALK_IND ) {
+ tclprim.offset = prim.numverts * 64;
+ tclprim.numverts = RADEON_MAX_VB_VERTS; /* duh */
+
+ radeon_cp_dispatch_indices( dev, buf, &tclprim );
+ } else {
+ tclprim.numverts = prim.numverts;
+ tclprim.offset = 0; /* not used */
+
+ radeon_cp_dispatch_vertex( dev, buf, &tclprim );
+ }
+
+ if (sarea_priv->nbox == 1)
+ sarea_priv->nbox = 0;
+ }
+
+ if ( vertex.discard ) {
+ radeon_cp_discard_buffer( dev, buf );
+ }
+
+ COMMIT_RING();
+ return 0;
+}
+
+
+static int radeon_emit_packets(
+ drm_radeon_private_t *dev_priv,
+ drm_file_t *filp_priv,
+ drm_radeon_cmd_header_t header,
+ drm_radeon_cmd_buffer_t *cmdbuf )
+{
+ int id = (int)header.packet.packet_id;
+ int sz, reg;
+ int *data = (int *)cmdbuf->buf;
+ RING_LOCALS;
+
+ if (id >= RADEON_MAX_STATE_PACKETS)
+ return DRM_ERR(EINVAL);
+
+ sz = packet[id].len;
+ reg = packet[id].start;
+
+ if (sz * sizeof(int) > cmdbuf->bufsz) {
+ DRM_ERROR( "Packet size provided larger than data provided\n" );
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( radeon_check_and_fixup_packets( dev_priv, filp_priv, id, data ) ) {
+ DRM_ERROR( "Packet verification failed\n" );
+ return DRM_ERR( EINVAL );
+ }
+
+ BEGIN_RING(sz+1);
+ OUT_RING( CP_PACKET0( reg, (sz-1) ) );
+ OUT_RING_TABLE( data, sz );
+ ADVANCE_RING();
+
+ cmdbuf->buf += sz * sizeof(int);
+ cmdbuf->bufsz -= sz * sizeof(int);
+ return 0;
+}
+
+static __inline__ int radeon_emit_scalars(
+ drm_radeon_private_t *dev_priv,
+ drm_radeon_cmd_header_t header,
+ drm_radeon_cmd_buffer_t *cmdbuf )
+{
+ int sz = header.scalars.count;
+ int start = header.scalars.offset;
+ int stride = header.scalars.stride;
+ RING_LOCALS;
+
+ BEGIN_RING( 3+sz );
+ OUT_RING( CP_PACKET0( RADEON_SE_TCL_SCALAR_INDX_REG, 0 ) );
+ OUT_RING( start | (stride << RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT));
+ OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_SCALAR_DATA_REG, sz-1 ) );
+ OUT_RING_TABLE( cmdbuf->buf, sz );
+ ADVANCE_RING();
+ cmdbuf->buf += sz * sizeof(int);
+ cmdbuf->bufsz -= sz * sizeof(int);
+ return 0;
+}
+
+/* God this is ugly
+ */
+static __inline__ int radeon_emit_scalars2(
+ drm_radeon_private_t *dev_priv,
+ drm_radeon_cmd_header_t header,
+ drm_radeon_cmd_buffer_t *cmdbuf )
+{
+ int sz = header.scalars.count;
+ int start = ((unsigned int)header.scalars.offset) + 0x100;
+ int stride = header.scalars.stride;
+ RING_LOCALS;
+
+ BEGIN_RING( 3+sz );
+ OUT_RING( CP_PACKET0( RADEON_SE_TCL_SCALAR_INDX_REG, 0 ) );
+ OUT_RING( start | (stride << RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT));
+ OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_SCALAR_DATA_REG, sz-1 ) );
+ OUT_RING_TABLE( cmdbuf->buf, sz );
+ ADVANCE_RING();
+ cmdbuf->buf += sz * sizeof(int);
+ cmdbuf->bufsz -= sz * sizeof(int);
+ return 0;
+}
+
+static __inline__ int radeon_emit_vectors(
+ drm_radeon_private_t *dev_priv,
+ drm_radeon_cmd_header_t header,
+ drm_radeon_cmd_buffer_t *cmdbuf )
+{
+ int sz = header.vectors.count;
+ int start = header.vectors.offset;
+ int stride = header.vectors.stride;
+ RING_LOCALS;
+
+ BEGIN_RING( 3+sz );
+ OUT_RING( CP_PACKET0( RADEON_SE_TCL_VECTOR_INDX_REG, 0 ) );
+ OUT_RING( start | (stride << RADEON_VEC_INDX_OCTWORD_STRIDE_SHIFT));
+ OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_VECTOR_DATA_REG, (sz-1) ) );
+ OUT_RING_TABLE( cmdbuf->buf, sz );
+ ADVANCE_RING();
+
+ cmdbuf->buf += sz * sizeof(int);
+ cmdbuf->bufsz -= sz * sizeof(int);
+ return 0;
+}
+
+
+static int radeon_emit_packet3( drm_device_t *dev,
+ drm_file_t *filp_priv,
+ drm_radeon_cmd_buffer_t *cmdbuf )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ unsigned int cmdsz;
+ int ret;
+ RING_LOCALS;
+
+ DRM_DEBUG("\n");
+
+ if ( ( ret = radeon_check_and_fixup_packet3( dev_priv, filp_priv,
+ cmdbuf, &cmdsz ) ) ) {
+ DRM_ERROR( "Packet verification failed\n" );
+ return ret;
+ }
+
+ BEGIN_RING( cmdsz );
+ OUT_RING_TABLE( cmdbuf->buf, cmdsz );
+ ADVANCE_RING();
+
+ cmdbuf->buf += cmdsz * 4;
+ cmdbuf->bufsz -= cmdsz * 4;
+ return 0;
+}
+
+
+static int radeon_emit_packet3_cliprect( drm_device_t *dev,
+ drm_file_t *filp_priv,
+ drm_radeon_cmd_buffer_t *cmdbuf,
+ int orig_nbox )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_clip_rect_t box;
+ unsigned int cmdsz;
+ int ret;
+ drm_clip_rect_t __user *boxes = cmdbuf->boxes;
+ int i = 0;
+ RING_LOCALS;
+
+ DRM_DEBUG("\n");
+
+ if ( ( ret = radeon_check_and_fixup_packet3( dev_priv, filp_priv,
+ cmdbuf, &cmdsz ) ) ) {
+ DRM_ERROR( "Packet verification failed\n" );
+ return ret;
+ }
+
+ if (!orig_nbox)
+ goto out;
+
+ do {
+ if ( i < cmdbuf->nbox ) {
+ if (DRM_COPY_FROM_USER( &box, &boxes[i], sizeof(box) ))
+ return DRM_ERR(EFAULT);
+ /* FIXME The second and subsequent times round
+ * this loop, send a WAIT_UNTIL_3D_IDLE before
+ * calling emit_clip_rect(). This fixes a
+ * lockup on fast machines when sending
+ * several cliprects with a cmdbuf, as when
+ * waving a 2D window over a 3D
+ * window. Something in the commands from user
+ * space seems to hang the card when they're
+ * sent several times in a row. That would be
+ * the correct place to fix it but this works
+ * around it until I can figure that out - Tim
+ * Smith */
+ if ( i ) {
+ BEGIN_RING( 2 );
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ ADVANCE_RING();
+ }
+ radeon_emit_clip_rect( dev_priv, &box );
+ }
+
+ BEGIN_RING( cmdsz );
+ OUT_RING_TABLE( cmdbuf->buf, cmdsz );
+ ADVANCE_RING();
+
+ } while ( ++i < cmdbuf->nbox );
+ if (cmdbuf->nbox == 1)
+ cmdbuf->nbox = 0;
+
+ out:
+ cmdbuf->buf += cmdsz * 4;
+ cmdbuf->bufsz -= cmdsz * 4;
+ return 0;
+}
+
+
+static int radeon_emit_wait( drm_device_t *dev, int flags )
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ RING_LOCALS;
+
+ DRM_DEBUG("%s: %x\n", __FUNCTION__, flags);
+ switch (flags) {
+ case RADEON_WAIT_2D:
+ BEGIN_RING( 2 );
+ RADEON_WAIT_UNTIL_2D_IDLE();
+ ADVANCE_RING();
+ break;
+ case RADEON_WAIT_3D:
+ BEGIN_RING( 2 );
+ RADEON_WAIT_UNTIL_3D_IDLE();
+ ADVANCE_RING();
+ break;
+ case RADEON_WAIT_2D|RADEON_WAIT_3D:
+ BEGIN_RING( 2 );
+ RADEON_WAIT_UNTIL_IDLE();
+ ADVANCE_RING();
+ break;
+ default:
+ return DRM_ERR(EINVAL);
+ }
+
+ return 0;
+}
+
+static int radeon_cp_cmdbuf( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_device_dma_t *dma = dev->dma;
+ drm_buf_t *buf = NULL;
+ int idx;
+ drm_radeon_cmd_buffer_t cmdbuf;
+ drm_radeon_cmd_header_t header;
+ int orig_nbox, orig_bufsz;
+ char *kbuf=NULL;
+
+ LOCK_TEST_WITH_RETURN( dev, filp );
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( cmdbuf, (drm_radeon_cmd_buffer_t __user *)data,
+ sizeof(cmdbuf) );
+
+ RING_SPACE_TEST_WITH_RETURN( dev_priv );
+ VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+ if (cmdbuf.bufsz > 64*1024 || cmdbuf.bufsz<0) {
+ return DRM_ERR(EINVAL);
+ }
+
+ /* Allocate an in-kernel area and copy in the cmdbuf. Do this to avoid
+ * races between checking values and using those values in other code,
+ * and simply to avoid a lot of function calls to copy in data.
+ */
+ orig_bufsz = cmdbuf.bufsz;
+ if (orig_bufsz != 0) {
+ kbuf = drm_alloc(cmdbuf.bufsz, DRM_MEM_DRIVER);
+ if (kbuf == NULL)
+ return DRM_ERR(ENOMEM);
+ if (DRM_COPY_FROM_USER(kbuf, cmdbuf.buf, cmdbuf.bufsz)) {
+ drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+ return DRM_ERR(EFAULT);
+ }
+ cmdbuf.buf = kbuf;
+ }
+
+ orig_nbox = cmdbuf.nbox;
+
+ while ( cmdbuf.bufsz >= sizeof(header) ) {
+
+ header.i = *(int *)cmdbuf.buf;
+ cmdbuf.buf += sizeof(header);
+ cmdbuf.bufsz -= sizeof(header);
+
+ switch (header.header.cmd_type) {
+ case RADEON_CMD_PACKET:
+ DRM_DEBUG("RADEON_CMD_PACKET\n");
+ if (radeon_emit_packets( dev_priv, filp_priv, header, &cmdbuf )) {
+ DRM_ERROR("radeon_emit_packets failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_SCALARS:
+ DRM_DEBUG("RADEON_CMD_SCALARS\n");
+ if (radeon_emit_scalars( dev_priv, header, &cmdbuf )) {
+ DRM_ERROR("radeon_emit_scalars failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_VECTORS:
+ DRM_DEBUG("RADEON_CMD_VECTORS\n");
+ if (radeon_emit_vectors( dev_priv, header, &cmdbuf )) {
+ DRM_ERROR("radeon_emit_vectors failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_DMA_DISCARD:
+ DRM_DEBUG("RADEON_CMD_DMA_DISCARD\n");
+ idx = header.dma.buf_idx;
+ if ( idx < 0 || idx >= dma->buf_count ) {
+ DRM_ERROR( "buffer index %d (of %d max)\n",
+ idx, dma->buf_count - 1 );
+ goto err;
+ }
+
+ buf = dma->buflist[idx];
+ if ( buf->filp != filp || buf->pending ) {
+ DRM_ERROR( "bad buffer %p %p %d\n",
+ buf->filp, filp, buf->pending);
+ goto err;
+ }
+
+ radeon_cp_discard_buffer( dev, buf );
+ break;
+
+ case RADEON_CMD_PACKET3:
+ DRM_DEBUG("RADEON_CMD_PACKET3\n");
+ if (radeon_emit_packet3( dev, filp_priv, &cmdbuf )) {
+ DRM_ERROR("radeon_emit_packet3 failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_PACKET3_CLIP:
+ DRM_DEBUG("RADEON_CMD_PACKET3_CLIP\n");
+ if (radeon_emit_packet3_cliprect( dev, filp_priv, &cmdbuf, orig_nbox )) {
+ DRM_ERROR("radeon_emit_packet3_clip failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_SCALARS2:
+ DRM_DEBUG("RADEON_CMD_SCALARS2\n");
+ if (radeon_emit_scalars2( dev_priv, header, &cmdbuf )) {
+ DRM_ERROR("radeon_emit_scalars2 failed\n");
+ goto err;
+ }
+ break;
+
+ case RADEON_CMD_WAIT:
+ DRM_DEBUG("RADEON_CMD_WAIT\n");
+ if (radeon_emit_wait( dev, header.wait.flags )) {
+ DRM_ERROR("radeon_emit_wait failed\n");
+ goto err;
+ }
+ break;
+ default:
+ DRM_ERROR("bad cmd_type %d at %p\n",
+ header.header.cmd_type,
+ cmdbuf.buf - sizeof(header));
+ goto err;
+ }
+ }
+
+ if (orig_bufsz != 0)
+ drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+
+ DRM_DEBUG("DONE\n");
+ COMMIT_RING();
+ return 0;
+
+err:
+ if (orig_bufsz != 0)
+ drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+ return DRM_ERR(EINVAL);
+}
+
+
+
+static int radeon_cp_getparam( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_radeon_getparam_t param;
+ int value;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR(EINVAL);
+ }
+
+ DRM_COPY_FROM_USER_IOCTL( param, (drm_radeon_getparam_t __user *)data,
+ sizeof(param) );
+
+ DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+ switch( param.param ) {
+ case RADEON_PARAM_GART_BUFFER_OFFSET:
+ value = dev_priv->gart_buffers_offset;
+ break;
+ case RADEON_PARAM_LAST_FRAME:
+ dev_priv->stats.last_frame_reads++;
+ value = GET_SCRATCH( 0 );
+ break;
+ case RADEON_PARAM_LAST_DISPATCH:
+ value = GET_SCRATCH( 1 );
+ break;
+ case RADEON_PARAM_LAST_CLEAR:
+ dev_priv->stats.last_clear_reads++;
+ value = GET_SCRATCH( 2 );
+ break;
+ case RADEON_PARAM_IRQ_NR:
+ value = dev->irq;
+ break;
+ case RADEON_PARAM_GART_BASE:
+ value = dev_priv->gart_vm_start;
+ break;
+ case RADEON_PARAM_REGISTER_HANDLE:
+ value = dev_priv->mmio_offset;
+ break;
+ case RADEON_PARAM_STATUS_HANDLE:
+ value = dev_priv->ring_rptr_offset;
+ break;
+#if BITS_PER_LONG == 32
+ /*
+ * This ioctl() doesn't work on 64-bit platforms because hw_lock is a
+ * pointer which can't fit into an int-sized variable. According to
+ * Michel Dänzer, the ioctl() is only used on embedded platforms, so
+ * not supporting it shouldn't be a problem. If the same functionality
+ * is needed on 64-bit platforms, a new ioctl() would have to be added,
+ * so backwards-compatibility for the embedded platforms can be
+ * maintained. --davidm 4-Feb-2004.
+ */
+ case RADEON_PARAM_SAREA_HANDLE:
+ /* The lock is the first dword in the sarea. */
+ value = (long)dev->lock.hw_lock;
+ break;
+#endif
+ case RADEON_PARAM_GART_TEX_HANDLE:
+ value = dev_priv->gart_textures_offset;
+ break;
+ default:
+ return DRM_ERR(EINVAL);
+ }
+
+ if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+ DRM_ERROR( "copy_to_user\n" );
+ return DRM_ERR(EFAULT);
+ }
+
+ return 0;
+}
+
+static int radeon_cp_setparam( DRM_IOCTL_ARGS ) {
+ DRM_DEVICE;
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ drm_file_t *filp_priv;
+ drm_radeon_setparam_t sp;
+ struct drm_radeon_driver_file_fields *radeon_priv;
+
+ if ( !dev_priv ) {
+ DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+ return DRM_ERR( EINVAL );
+ }
+
+ DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+ DRM_COPY_FROM_USER_IOCTL( sp, ( drm_radeon_setparam_t __user * )data,
+ sizeof( sp ) );
+
+ switch( sp.param ) {
+ case RADEON_SETPARAM_FB_LOCATION:
+ radeon_priv = filp_priv->driver_priv;
+ radeon_priv->radeon_fb_delta = dev_priv->fb_location - sp.value;
+ break;
+ case RADEON_SETPARAM_SWITCH_TILING:
+ if (sp.value == 0) {
+ DRM_DEBUG( "color tiling disabled\n" );
+ dev_priv->front_pitch_offset &= ~RADEON_DST_TILE_MACRO;
+ dev_priv->back_pitch_offset &= ~RADEON_DST_TILE_MACRO;
+ dev_priv->sarea_priv->tiling_enabled = 0;
+ }
+ else if (sp.value == 1) {
+ DRM_DEBUG( "color tiling enabled\n" );
+ dev_priv->front_pitch_offset |= RADEON_DST_TILE_MACRO;
+ dev_priv->back_pitch_offset |= RADEON_DST_TILE_MACRO;
+ dev_priv->sarea_priv->tiling_enabled = 1;
+ }
+ break;
+ default:
+ DRM_DEBUG( "Invalid parameter %d\n", sp.param );
+ return DRM_ERR( EINVAL );
+ }
+
+ return 0;
+}
+
+/* When a client dies:
+ * - Check for and clean up flipped page state
+ * - Free any alloced GART memory.
+ *
+ * DRM infrastructure takes care of reclaiming dma buffers.
+ */
+void radeon_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+ if ( dev->dev_private ) {
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ if ( dev_priv->page_flipping ) {
+ radeon_do_cleanup_pageflip( dev );
+ }
+ radeon_mem_release( filp, dev_priv->gart_heap );
+ radeon_mem_release( filp, dev_priv->fb_heap );
+ radeon_surfaces_release(filp, dev_priv);
+ }
+}
+
+void radeon_driver_pretakedown(drm_device_t *dev)
+{
+ radeon_do_release(dev);
+}
+
+int radeon_driver_open_helper(drm_device_t *dev, drm_file_t *filp_priv)
+{
+ drm_radeon_private_t *dev_priv = dev->dev_private;
+ struct drm_radeon_driver_file_fields *radeon_priv;
+
+ radeon_priv = (struct drm_radeon_driver_file_fields *)drm_alloc(sizeof(*radeon_priv), DRM_MEM_FILES);
+
+ if (!radeon_priv)
+ return -ENOMEM;
+
+ filp_priv->driver_priv = radeon_priv;
+ if ( dev_priv )
+ radeon_priv->radeon_fb_delta = dev_priv->fb_location;
+ else
+ radeon_priv->radeon_fb_delta = 0;
+ return 0;
+}
+
+
+void radeon_driver_free_filp_priv(drm_device_t *dev, drm_file_t *filp_priv)
+{
+ struct drm_radeon_driver_file_fields *radeon_priv = filp_priv->driver_priv;
+
+ drm_free(radeon_priv, sizeof(*radeon_priv), DRM_MEM_FILES);
+}
+
+drm_ioctl_desc_t radeon_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_RADEON_CP_INIT)] = { radeon_cp_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_CP_START)] = { radeon_cp_start, 1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_CP_STOP)] = { radeon_cp_stop, 1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_CP_RESET)] = { radeon_cp_reset, 1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_CP_IDLE)] = { radeon_cp_idle, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_CP_RESUME)] = { radeon_cp_resume, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_RESET)] = { radeon_engine_reset, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_FULLSCREEN)] = { radeon_fullscreen, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_SWAP)] = { radeon_cp_swap, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_CLEAR)] = { radeon_cp_clear, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_VERTEX)] = { radeon_cp_vertex, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_INDICES)] = { radeon_cp_indices, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_TEXTURE)] = { radeon_cp_texture, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_STIPPLE)] = { radeon_cp_stipple, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_INDIRECT)] = { radeon_cp_indirect, 1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_VERTEX2)] = { radeon_cp_vertex2, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_CMDBUF)] = { radeon_cp_cmdbuf, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_GETPARAM)] = { radeon_cp_getparam, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_FLIP)] = { radeon_cp_flip, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_ALLOC)] = { radeon_mem_alloc, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_FREE)] = { radeon_mem_free, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_INIT_HEAP)] = { radeon_mem_init_heap,1, 1 },
+ [DRM_IOCTL_NR(DRM_RADEON_IRQ_EMIT)] = { radeon_irq_emit, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_IRQ_WAIT)] = { radeon_irq_wait, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_SETPARAM)] = { radeon_cp_setparam, 1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_SURF_ALLOC)] = { radeon_surface_alloc,1, 0 },
+ [DRM_IOCTL_NR(DRM_RADEON_SURF_FREE)] = { radeon_surface_free, 1, 0 }
+};
+
+int radeon_max_ioctl = DRM_ARRAY_SIZE(radeon_ioctls);
diff --git a/drivers/char/drm/sis_drm.h b/drivers/char/drm/sis_drm.h
new file mode 100644
index 000000000000..e99c3a43abbc
--- /dev/null
+++ b/drivers/char/drm/sis_drm.h
@@ -0,0 +1,42 @@
+
+#ifndef __SIS_DRM_H__
+#define __SIS_DRM_H__
+
+/* SiS specific ioctls */
+#define NOT_USED_0_3
+#define DRM_SIS_FB_ALLOC 0x04
+#define DRM_SIS_FB_FREE 0x05
+#define NOT_USED_6_12
+#define DRM_SIS_AGP_INIT 0x13
+#define DRM_SIS_AGP_ALLOC 0x14
+#define DRM_SIS_AGP_FREE 0x15
+#define DRM_SIS_FB_INIT 0x16
+
+#define DRM_IOCTL_SIS_FB_ALLOC DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_FB_ALLOC, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_FB_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_FB_FREE, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_AGP_INIT DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_AGP_INIT, drm_sis_agp_t)
+#define DRM_IOCTL_SIS_AGP_ALLOC DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_AGP_ALLOC, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_AGP_FREE DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_AGP_FREE, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_FB_INIT DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_FB_INIT, drm_sis_fb_t)
+/*
+#define DRM_IOCTL_SIS_FLIP DRM_IOW( 0x48, drm_sis_flip_t)
+#define DRM_IOCTL_SIS_FLIP_INIT DRM_IO( 0x49)
+#define DRM_IOCTL_SIS_FLIP_FINAL DRM_IO( 0x50)
+*/
+
+typedef struct {
+ int context;
+ unsigned int offset;
+ unsigned int size;
+ unsigned long free;
+} drm_sis_mem_t;
+
+typedef struct {
+ unsigned int offset, size;
+} drm_sis_agp_t;
+
+typedef struct {
+ unsigned int offset, size;
+} drm_sis_fb_t;
+
+#endif /* __SIS_DRM_H__ */
diff --git a/drivers/char/drm/sis_drv.c b/drivers/char/drm/sis_drv.c
new file mode 100644
index 000000000000..f441714faae3
--- /dev/null
+++ b/drivers/char/drm/sis_drv.c
@@ -0,0 +1,110 @@
+/* sis.c -- sis driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "sis_drm.h"
+#include "sis_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ sisdrv_PCI_IDS
+};
+
+extern drm_ioctl_desc_t sis_ioctls[];
+extern int sis_max_ioctl;
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR,
+ .context_ctor = sis_init_context,
+ .context_dtor = sis_final_context,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .ioctls = sis_ioctls,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init sis_init(void)
+{
+ driver.num_ioctls = sis_max_ioctl;
+ return drm_init(&driver);
+}
+
+static void __exit sis_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(sis_init);
+module_exit(sis_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/sis_drv.h b/drivers/char/drm/sis_drv.h
new file mode 100644
index 000000000000..5be36b5caec9
--- /dev/null
+++ b/drivers/char/drm/sis_drv.h
@@ -0,0 +1,52 @@
+/* sis_drv.h -- Private header for sis driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef _SIS_DRV_H_
+#define _SIS_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "SIS"
+#define DRIVER_NAME "sis"
+#define DRIVER_DESC "SIS 300/630/540"
+#define DRIVER_DATE "20030826"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 1
+#define DRIVER_PATCHLEVEL 0
+
+#include "sis_ds.h"
+
+typedef struct drm_sis_private {
+ memHeap_t *AGPHeap;
+ memHeap_t *FBHeap;
+} drm_sis_private_t;
+
+extern int sis_init_context(drm_device_t *dev, int context);
+extern int sis_final_context(drm_device_t *dev, int context);
+
+#endif
diff --git a/drivers/char/drm/sis_ds.c b/drivers/char/drm/sis_ds.c
new file mode 100644
index 000000000000..e37ed8ce48df
--- /dev/null
+++ b/drivers/char/drm/sis_ds.c
@@ -0,0 +1,301 @@
+/* sis_ds.c -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan 4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Sung-Ching Lin <sclin@sis.com.tw>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "sis_ds.h"
+
+/* Set Data Structure, not check repeated value
+ * temporarily used
+ */
+
+set_t *setInit(void)
+{
+ int i;
+ set_t *set;
+
+ set = (set_t *)drm_alloc(sizeof(set_t), DRM_MEM_DRIVER);
+ if (set != NULL) {
+ for (i = 0; i < SET_SIZE; i++) {
+ set->list[i].free_next = i + 1;
+ set->list[i].alloc_next = -1;
+ }
+ set->list[SET_SIZE-1].free_next = -1;
+ set->free = 0;
+ set->alloc = -1;
+ set->trace = -1;
+ }
+ return set;
+}
+
+int setAdd(set_t *set, ITEM_TYPE item)
+{
+ int free = set->free;
+
+ if (free != -1) {
+ set->list[free].val = item;
+ set->free = set->list[free].free_next;
+ } else {
+ return 0;
+ }
+
+ set->list[free].alloc_next = set->alloc;
+ set->alloc = free;
+ set->list[free].free_next = -1;
+
+ return 1;
+}
+
+int setDel(set_t *set, ITEM_TYPE item)
+{
+ int alloc = set->alloc;
+ int prev = -1;
+
+ while (alloc != -1) {
+ if (set->list[alloc].val == item) {
+ if (prev != -1)
+ set->list[prev].alloc_next =
+ set->list[alloc].alloc_next;
+ else
+ set->alloc = set->list[alloc].alloc_next;
+ break;
+ }
+ prev = alloc;
+ alloc = set->list[alloc].alloc_next;
+ }
+
+ if (alloc == -1)
+ return 0;
+
+ set->list[alloc].free_next = set->free;
+ set->free = alloc;
+ set->list[alloc].alloc_next = -1;
+
+ return 1;
+}
+
+/* setFirst -> setAdd -> setNext is wrong */
+
+int setFirst(set_t *set, ITEM_TYPE *item)
+{
+ if (set->alloc == -1)
+ return 0;
+
+ *item = set->list[set->alloc].val;
+ set->trace = set->list[set->alloc].alloc_next;
+
+ return 1;
+}
+
+int setNext(set_t *set, ITEM_TYPE *item)
+{
+ if (set->trace == -1)
+ return 0;
+
+ *item = set->list[set->trace].val;
+ set->trace = set->list[set->trace].alloc_next;
+
+ return 1;
+}
+
+int setDestroy(set_t *set)
+{
+ drm_free(set, sizeof(set_t), DRM_MEM_DRIVER);
+
+ return 1;
+}
+
+/*
+ * GLX Hardware Device Driver common code
+ * Copyright (C) 1999 Wittawat Yamwong
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * WITTAWAT YAMWONG, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#define ISFREE(bptr) ((bptr)->free)
+
+memHeap_t *mmInit(int ofs,
+ int size)
+{
+ PMemBlock blocks;
+
+ if (size <= 0)
+ return NULL;
+
+ blocks = (TMemBlock *)drm_calloc(1, sizeof(TMemBlock), DRM_MEM_DRIVER);
+ if (blocks != NULL) {
+ blocks->ofs = ofs;
+ blocks->size = size;
+ blocks->free = 1;
+ return (memHeap_t *)blocks;
+ } else
+ return NULL;
+}
+
+/* Checks if a pointer 'b' is part of the heap 'heap' */
+int mmBlockInHeap(memHeap_t *heap, PMemBlock b)
+{
+ TMemBlock *p;
+
+ if (heap == NULL || b == NULL)
+ return 0;
+
+ p = heap;
+ while (p != NULL && p != b) {
+ p = p->next;
+ }
+ if (p == b)
+ return 1;
+ else
+ return 0;
+}
+
+static TMemBlock* SliceBlock(TMemBlock *p,
+ int startofs, int size,
+ int reserved, int alignment)
+{
+ TMemBlock *newblock;
+
+ /* break left */
+ if (startofs > p->ofs) {
+ newblock = (TMemBlock*) drm_calloc(1, sizeof(TMemBlock),
+ DRM_MEM_DRIVER);
+ newblock->ofs = startofs;
+ newblock->size = p->size - (startofs - p->ofs);
+ newblock->free = 1;
+ newblock->next = p->next;
+ p->size -= newblock->size;
+ p->next = newblock;
+ p = newblock;
+ }
+
+ /* break right */
+ if (size < p->size) {
+ newblock = (TMemBlock*) drm_calloc(1, sizeof(TMemBlock),
+ DRM_MEM_DRIVER);
+ newblock->ofs = startofs + size;
+ newblock->size = p->size - size;
+ newblock->free = 1;
+ newblock->next = p->next;
+ p->size = size;
+ p->next = newblock;
+ }
+
+ /* p = middle block */
+ p->align = alignment;
+ p->free = 0;
+ p->reserved = reserved;
+ return p;
+}
+
+PMemBlock mmAllocMem( memHeap_t *heap, int size, int align2, int startSearch)
+{
+ int mask,startofs, endofs;
+ TMemBlock *p;
+
+ if (heap == NULL || align2 < 0 || size <= 0)
+ return NULL;
+
+ mask = (1 << align2)-1;
+ startofs = 0;
+ p = (TMemBlock *)heap;
+ while (p != NULL) {
+ if (ISFREE(p)) {
+ startofs = (p->ofs + mask) & ~mask;
+ if ( startofs < startSearch ) {
+ startofs = startSearch;
+ }
+ endofs = startofs+size;
+ if (endofs <= (p->ofs+p->size))
+ break;
+ }
+ p = p->next;
+ }
+ if (p == NULL)
+ return NULL;
+ p = SliceBlock(p,startofs,size,0,mask+1);
+ p->heap = heap;
+ return p;
+}
+
+static __inline__ int Join2Blocks(TMemBlock *p)
+{
+ if (p->free && p->next && p->next->free) {
+ TMemBlock *q = p->next;
+ p->size += q->size;
+ p->next = q->next;
+ drm_free(q, sizeof(TMemBlock), DRM_MEM_DRIVER);
+ return 1;
+ }
+ return 0;
+}
+
+int mmFreeMem(PMemBlock b)
+{
+ TMemBlock *p, *prev;
+
+ if (b == NULL)
+ return 0;
+ if (b->heap == NULL)
+ return -1;
+
+ p = b->heap;
+ prev = NULL;
+ while (p != NULL && p != b) {
+ prev = p;
+ p = p->next;
+ }
+ if (p == NULL || p->free || p->reserved)
+ return -1;
+
+ p->free = 1;
+ Join2Blocks(p);
+ if (prev)
+ Join2Blocks(prev);
+ return 0;
+}
+
diff --git a/drivers/char/drm/sis_ds.h b/drivers/char/drm/sis_ds.h
new file mode 100644
index 000000000000..171ee75afa57
--- /dev/null
+++ b/drivers/char/drm/sis_ds.h
@@ -0,0 +1,145 @@
+/* sis_ds.h -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan 4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Sung-Ching Lin <sclin@sis.com.tw>
+ *
+ */
+
+#ifndef __SIS_DS_H__
+#define __SIS_DS_H__
+
+/* Set Data Structure */
+
+#define SET_SIZE 5000
+
+typedef unsigned int ITEM_TYPE;
+
+typedef struct {
+ ITEM_TYPE val;
+ int alloc_next, free_next;
+} list_item_t;
+
+typedef struct {
+ int alloc;
+ int free;
+ int trace;
+ list_item_t list[SET_SIZE];
+} set_t;
+
+set_t *setInit(void);
+int setAdd(set_t *set, ITEM_TYPE item);
+int setDel(set_t *set, ITEM_TYPE item);
+int setFirst(set_t *set, ITEM_TYPE *item);
+int setNext(set_t *set, ITEM_TYPE *item);
+int setDestroy(set_t *set);
+
+/*
+ * GLX Hardware Device Driver common code
+ * Copyright (C) 1999 Wittawat Yamwong
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * WITTAWAT YAMWONG, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+struct mem_block_t {
+ struct mem_block_t *next;
+ struct mem_block_t *heap;
+ int ofs,size;
+ int align;
+ unsigned int free:1;
+ unsigned int reserved:1;
+};
+typedef struct mem_block_t TMemBlock;
+typedef struct mem_block_t *PMemBlock;
+
+/* a heap is just the first block in a chain */
+typedef struct mem_block_t memHeap_t;
+
+static __inline__ int mmBlockSize(PMemBlock b)
+{
+ return b->size;
+}
+
+static __inline__ int mmOffset(PMemBlock b)
+{
+ return b->ofs;
+}
+
+static __inline__ void mmMarkReserved(PMemBlock b)
+{
+ b->reserved = 1;
+}
+
+/*
+ * input: total size in bytes
+ * return: a heap pointer if OK, NULL if error
+ */
+memHeap_t *mmInit( int ofs, int size );
+
+/*
+ * Allocate 'size' bytes with 2^align2 bytes alignment,
+ * restrict the search to free memory after 'startSearch'
+ * depth and back buffers should be in different 4mb banks
+ * to get better page hits if possible
+ * input: size = size of block
+ * align2 = 2^align2 bytes alignment
+ * startSearch = linear offset from start of heap to begin search
+ * return: pointer to the allocated block, 0 if error
+ */
+PMemBlock mmAllocMem( memHeap_t *heap, int size, int align2, int startSearch );
+
+/*
+ * Returns 1 if the block 'b' is part of the heap 'heap'
+ */
+int mmBlockInHeap( PMemBlock heap, PMemBlock b );
+
+/*
+ * Free block starts at offset
+ * input: pointer to a block
+ * return: 0 if OK, -1 if error
+ */
+int mmFreeMem( PMemBlock b );
+
+/* For debuging purpose. */
+void mmDumpMemInfo( memHeap_t *mmInit );
+
+#endif /* __SIS_DS_H__ */
diff --git a/drivers/char/drm/sis_mm.c b/drivers/char/drm/sis_mm.c
new file mode 100644
index 000000000000..6610c5576d22
--- /dev/null
+++ b/drivers/char/drm/sis_mm.c
@@ -0,0 +1,417 @@
+/* sis_mm.c -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan 4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Sung-Ching Lin <sclin@sis.com.tw>
+ *
+ */
+
+#include "drmP.h"
+#include "sis_drm.h"
+#include "sis_drv.h"
+#include "sis_ds.h"
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+#include <video/sisfb.h>
+#endif
+
+#define MAX_CONTEXT 100
+#define VIDEO_TYPE 0
+#define AGP_TYPE 1
+
+typedef struct {
+ int used;
+ int context;
+ set_t *sets[2]; /* 0 for video, 1 for AGP */
+} sis_context_t;
+
+static sis_context_t global_ppriv[MAX_CONTEXT];
+
+
+static int add_alloc_set(int context, int type, unsigned int val)
+{
+ int i, retval = 0;
+
+ for (i = 0; i < MAX_CONTEXT; i++) {
+ if (global_ppriv[i].used && global_ppriv[i].context == context)
+ {
+ retval = setAdd(global_ppriv[i].sets[type], val);
+ break;
+ }
+ }
+ return retval;
+}
+
+static int del_alloc_set(int context, int type, unsigned int val)
+{
+ int i, retval = 0;
+
+ for (i = 0; i < MAX_CONTEXT; i++) {
+ if (global_ppriv[i].used && global_ppriv[i].context == context)
+ {
+ retval = setDel(global_ppriv[i].sets[type], val);
+ break;
+ }
+ }
+ return retval;
+}
+
+/* fb management via fb device */
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+
+static int sis_fb_init( DRM_IOCTL_ARGS )
+{
+ return 0;
+}
+
+static int sis_fb_alloc( DRM_IOCTL_ARGS )
+{
+ drm_sis_mem_t fb;
+ struct sis_memreq req;
+ drm_sis_mem_t __user *argp = (void __user *)data;
+ int retval = 0;
+
+ DRM_COPY_FROM_USER_IOCTL(fb, argp, sizeof(fb));
+
+ req.size = fb.size;
+ sis_malloc(&req);
+ if (req.offset) {
+ /* TODO */
+ fb.offset = req.offset;
+ fb.free = req.offset;
+ if (!add_alloc_set(fb.context, VIDEO_TYPE, fb.free)) {
+ DRM_DEBUG("adding to allocation set fails\n");
+ sis_free(req.offset);
+ retval = DRM_ERR(EINVAL);
+ }
+ } else {
+ fb.offset = 0;
+ fb.size = 0;
+ fb.free = 0;
+ }
+
+ DRM_COPY_TO_USER_IOCTL(argp, fb, sizeof(fb));
+
+ DRM_DEBUG("alloc fb, size = %d, offset = %d\n", fb.size, req.offset);
+
+ return retval;
+}
+
+static int sis_fb_free( DRM_IOCTL_ARGS )
+{
+ drm_sis_mem_t fb;
+ int retval = 0;
+
+ DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_mem_t __user *)data, sizeof(fb));
+
+ if (!fb.free)
+ return DRM_ERR(EINVAL);
+
+ if (!del_alloc_set(fb.context, VIDEO_TYPE, fb.free))
+ retval = DRM_ERR(EINVAL);
+ sis_free((u32)fb.free);
+
+ DRM_DEBUG("free fb, offset = %lu\n", fb.free);
+
+ return retval;
+}
+
+#else
+
+/* Called by the X Server to initialize the FB heap. Allocations will fail
+ * unless this is called. Offset is the beginning of the heap from the
+ * framebuffer offset (MaxXFBMem in XFree86).
+ *
+ * Memory layout according to Thomas Winischofer:
+ * |------------------|DDDDDDDDDDDDDDDDDDDDDDDDDDDDD|HHHH|CCCCCCCCCCC|
+ *
+ * X driver/sisfb HW- Command-
+ * framebuffer memory DRI heap Cursor queue
+ */
+static int sis_fb_init( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_fb_t fb;
+
+ DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_fb_t __user *)data, sizeof(fb));
+
+ if (dev_priv == NULL) {
+ dev->dev_private = drm_calloc(1, sizeof(drm_sis_private_t),
+ DRM_MEM_DRIVER);
+ dev_priv = dev->dev_private;
+ if (dev_priv == NULL)
+ return ENOMEM;
+ }
+
+ if (dev_priv->FBHeap != NULL)
+ return DRM_ERR(EINVAL);
+
+ dev_priv->FBHeap = mmInit(fb.offset, fb.size);
+
+ DRM_DEBUG("offset = %u, size = %u", fb.offset, fb.size);
+
+ return 0;
+}
+
+static int sis_fb_alloc( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_mem_t __user *argp = (void __user *)data;
+ drm_sis_mem_t fb;
+ PMemBlock block;
+ int retval = 0;
+
+ if (dev_priv == NULL || dev_priv->FBHeap == NULL)
+ return DRM_ERR(EINVAL);
+
+ DRM_COPY_FROM_USER_IOCTL(fb, argp, sizeof(fb));
+
+ block = mmAllocMem(dev_priv->FBHeap, fb.size, 0, 0);
+ if (block) {
+ /* TODO */
+ fb.offset = block->ofs;
+ fb.free = (unsigned long)block;
+ if (!add_alloc_set(fb.context, VIDEO_TYPE, fb.free)) {
+ DRM_DEBUG("adding to allocation set fails\n");
+ mmFreeMem((PMemBlock)fb.free);
+ retval = DRM_ERR(EINVAL);
+ }
+ } else {
+ fb.offset = 0;
+ fb.size = 0;
+ fb.free = 0;
+ }
+
+ DRM_COPY_TO_USER_IOCTL(argp, fb, sizeof(fb));
+
+ DRM_DEBUG("alloc fb, size = %d, offset = %d\n", fb.size, fb.offset);
+
+ return retval;
+}
+
+static int sis_fb_free( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_mem_t fb;
+
+ if (dev_priv == NULL || dev_priv->FBHeap == NULL)
+ return DRM_ERR(EINVAL);
+
+ DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_mem_t __user *)data, sizeof(fb));
+
+ if (!mmBlockInHeap(dev_priv->FBHeap, (PMemBlock)fb.free))
+ return DRM_ERR(EINVAL);
+
+ if (!del_alloc_set(fb.context, VIDEO_TYPE, fb.free))
+ return DRM_ERR(EINVAL);
+ mmFreeMem((PMemBlock)fb.free);
+
+ DRM_DEBUG("free fb, free = 0x%lx\n", fb.free);
+
+ return 0;
+}
+
+#endif
+
+/* agp memory management */
+
+static int sis_ioctl_agp_init( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_agp_t agp;
+
+ if (dev_priv == NULL) {
+ dev->dev_private = drm_calloc(1, sizeof(drm_sis_private_t),
+ DRM_MEM_DRIVER);
+ dev_priv = dev->dev_private;
+ if (dev_priv == NULL)
+ return ENOMEM;
+ }
+
+ if (dev_priv->AGPHeap != NULL)
+ return DRM_ERR(EINVAL);
+
+ DRM_COPY_FROM_USER_IOCTL(agp, (drm_sis_agp_t __user *)data, sizeof(agp));
+
+ dev_priv->AGPHeap = mmInit(agp.offset, agp.size);
+
+ DRM_DEBUG("offset = %u, size = %u", agp.offset, agp.size);
+
+ return 0;
+}
+
+static int sis_ioctl_agp_alloc( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_mem_t __user *argp = (void __user *)data;
+ drm_sis_mem_t agp;
+ PMemBlock block;
+ int retval = 0;
+
+ if (dev_priv == NULL || dev_priv->AGPHeap == NULL)
+ return DRM_ERR(EINVAL);
+
+ DRM_COPY_FROM_USER_IOCTL(agp, argp, sizeof(agp));
+
+ block = mmAllocMem(dev_priv->AGPHeap, agp.size, 0, 0);
+ if (block) {
+ /* TODO */
+ agp.offset = block->ofs;
+ agp.free = (unsigned long)block;
+ if (!add_alloc_set(agp.context, AGP_TYPE, agp.free)) {
+ DRM_DEBUG("adding to allocation set fails\n");
+ mmFreeMem((PMemBlock)agp.free);
+ retval = -1;
+ }
+ } else {
+ agp.offset = 0;
+ agp.size = 0;
+ agp.free = 0;
+ }
+
+ DRM_COPY_TO_USER_IOCTL(argp, agp, sizeof(agp));
+
+ DRM_DEBUG("alloc agp, size = %d, offset = %d\n", agp.size, agp.offset);
+
+ return retval;
+}
+
+static int sis_ioctl_agp_free( DRM_IOCTL_ARGS )
+{
+ DRM_DEVICE;
+ drm_sis_private_t *dev_priv = dev->dev_private;
+ drm_sis_mem_t agp;
+
+ if (dev_priv == NULL || dev_priv->AGPHeap == NULL)
+ return DRM_ERR(EINVAL);
+
+ DRM_COPY_FROM_USER_IOCTL(agp, (drm_sis_mem_t __user *)data, sizeof(agp));
+
+ if (!mmBlockInHeap(dev_priv->AGPHeap, (PMemBlock)agp.free))
+ return DRM_ERR(EINVAL);
+
+ mmFreeMem((PMemBlock)agp.free);
+ if (!del_alloc_set(agp.context, AGP_TYPE, agp.free))
+ return DRM_ERR(EINVAL);
+
+ DRM_DEBUG("free agp, free = 0x%lx\n", agp.free);
+
+ return 0;
+}
+
+int sis_init_context(struct drm_device *dev, int context)
+{
+ int i;
+
+ for (i = 0; i < MAX_CONTEXT ; i++) {
+ if (global_ppriv[i].used &&
+ (global_ppriv[i].context == context))
+ break;
+ }
+
+ if (i >= MAX_CONTEXT) {
+ for (i = 0; i < MAX_CONTEXT ; i++) {
+ if (!global_ppriv[i].used) {
+ global_ppriv[i].context = context;
+ global_ppriv[i].used = 1;
+ global_ppriv[i].sets[0] = setInit();
+ global_ppriv[i].sets[1] = setInit();
+ DRM_DEBUG("init allocation set, socket=%d, "
+ "context = %d\n", i, context);
+ break;
+ }
+ }
+ if ((i >= MAX_CONTEXT) || (global_ppriv[i].sets[0] == NULL) ||
+ (global_ppriv[i].sets[1] == NULL))
+ {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int sis_final_context(struct drm_device *dev, int context)
+{
+ int i;
+
+ for (i=0; i<MAX_CONTEXT; i++) {
+ if (global_ppriv[i].used &&
+ (global_ppriv[i].context == context))
+ break;
+ }
+
+ if (i < MAX_CONTEXT) {
+ set_t *set;
+ unsigned int item;
+ int retval;
+
+ DRM_DEBUG("find socket %d, context = %d\n", i, context);
+
+ /* Video Memory */
+ set = global_ppriv[i].sets[0];
+ retval = setFirst(set, &item);
+ while (retval) {
+ DRM_DEBUG("free video memory 0x%x\n", item);
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+ sis_free(item);
+#else
+ mmFreeMem((PMemBlock)item);
+#endif
+ retval = setNext(set, &item);
+ }
+ setDestroy(set);
+
+ /* AGP Memory */
+ set = global_ppriv[i].sets[1];
+ retval = setFirst(set, &item);
+ while (retval) {
+ DRM_DEBUG("free agp memory 0x%x\n", item);
+ mmFreeMem((PMemBlock)item);
+ retval = setNext(set, &item);
+ }
+ setDestroy(set);
+
+ global_ppriv[i].used = 0;
+ }
+
+ return 1;
+}
+
+drm_ioctl_desc_t sis_ioctls[] = {
+ [DRM_IOCTL_NR(DRM_SIS_FB_ALLOC)] = { sis_fb_alloc, 1, 0 },
+ [DRM_IOCTL_NR(DRM_SIS_FB_FREE)] = { sis_fb_free, 1, 0 },
+ [DRM_IOCTL_NR(DRM_SIS_AGP_INIT)] = { sis_ioctl_agp_init, 1, 1 },
+ [DRM_IOCTL_NR(DRM_SIS_AGP_ALLOC)] = { sis_ioctl_agp_alloc, 1, 0 },
+ [DRM_IOCTL_NR(DRM_SIS_AGP_FREE)] = { sis_ioctl_agp_free, 1, 0 },
+ [DRM_IOCTL_NR(DRM_SIS_FB_INIT)] = { sis_fb_init, 1, 1 }
+};
+
+int sis_max_ioctl = DRM_ARRAY_SIZE(sis_ioctls);
+
diff --git a/drivers/char/drm/tdfx_drv.c b/drivers/char/drm/tdfx_drv.c
new file mode 100644
index 000000000000..0e7943e6efea
--- /dev/null
+++ b/drivers/char/drm/tdfx_drv.c
@@ -0,0 +1,107 @@
+/* tdfx_drv.c -- tdfx driver -*- linux-c -*-
+ * Created: Thu Oct 7 10:38:32 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Rickard E. (Rik) Faith <faith@valinux.com>
+ * Daryll Strauss <daryll@valinux.com>
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "tdfx_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+ DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+ DRIVER_NAME,
+ DRIVER_MAJOR,
+ DRIVER_MINOR,
+ DRIVER_PATCHLEVEL,
+ DRIVER_DATE,
+ dev->primary.minor,
+ pci_pretty_name(dev->pdev)
+ );
+ return 0;
+}
+
+static int version( drm_version_t *version )
+{
+ int len;
+
+ version->version_major = DRIVER_MAJOR;
+ version->version_minor = DRIVER_MINOR;
+ version->version_patchlevel = DRIVER_PATCHLEVEL;
+ DRM_COPY( version->name, DRIVER_NAME );
+ DRM_COPY( version->date, DRIVER_DATE );
+ DRM_COPY( version->desc, DRIVER_DESC );
+ return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+ tdfx_PCI_IDS
+};
+
+static struct drm_driver driver = {
+ .driver_features = DRIVER_USE_MTRR,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .get_map_ofs = drm_core_get_map_ofs,
+ .get_reg_ofs = drm_core_get_reg_ofs,
+ .postinit = postinit,
+ .version = version,
+ .fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .ioctl = drm_ioctl,
+ .mmap = drm_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ },
+ .pci_driver = {
+ .name = DRIVER_NAME,
+ .id_table = pciidlist,
+ }
+};
+
+static int __init tdfx_init(void)
+{
+ return drm_init(&driver);
+}
+
+static void __exit tdfx_exit(void)
+{
+ drm_exit(&driver);
+}
+
+module_init(tdfx_init);
+module_exit(tdfx_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/tdfx_drv.h b/drivers/char/drm/tdfx_drv.h
new file mode 100644
index 000000000000..a582a3db4c75
--- /dev/null
+++ b/drivers/char/drm/tdfx_drv.h
@@ -0,0 +1,50 @@
+/* tdfx.h -- 3dfx DRM template customization -*- linux-c -*-
+ * Created: Wed Feb 14 12:32:32 2001 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __TDFX_H__
+#define __TDFX_H__
+
+/* This remains constant for all DRM template files.
+ */
+#define DRM(x) tdfx_##x
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR "VA Linux Systems Inc."
+
+#define DRIVER_NAME "tdfx"
+#define DRIVER_DESC "3dfx Banshee/Voodoo3+"
+#define DRIVER_DATE "20010216"
+
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+#endif
diff --git a/drivers/char/ds1286.c b/drivers/char/ds1286.c
new file mode 100644
index 000000000000..d755cac14bc1
--- /dev/null
+++ b/drivers/char/ds1286.c
@@ -0,0 +1,578 @@
+/*
+ * DS1286 Real Time Clock interface for Linux
+ *
+ * Copyright (C) 1998, 1999, 2000 Ralf Baechle
+ *
+ * Based on code written by Paul Gortmaker.
+ *
+ * This driver allows use of the real time clock (built into nearly all
+ * computers) from user space. It exports the /dev/rtc interface supporting
+ * various ioctl() and also the /proc/rtc pseudo-file for status
+ * information.
+ *
+ * The ioctls can be used to set the interrupt behaviour and generation rate
+ * from the RTC via IRQ 8. Then the /dev/rtc interface can be used to make
+ * use of these timer interrupts, be they interval or alarm based.
+ *
+ * The /dev/rtc interface will block on reads until an interrupt has been
+ * received. If a RTC interrupt has already happened, it will output an
+ * unsigned long and then block. The output value contains the interrupt
+ * status in the low byte and the number of interrupts since the last read
+ * in the remaining high bytes. The /dev/rtc interface can also be used with
+ * the select(2) call.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/ds1286.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/rtc.h>
+#include <linux/spinlock.h>
+#include <linux/bcd.h>
+#include <linux/proc_fs.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define DS1286_VERSION "1.0"
+
+/*
+ * We sponge a minor off of the misc major. No need slurping
+ * up another valuable major dev number for this. If you add
+ * an ioctl, make sure you don't conflict with SPARC's RTC
+ * ioctls.
+ */
+
+static DECLARE_WAIT_QUEUE_HEAD(ds1286_wait);
+
+static ssize_t ds1286_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos);
+
+static int ds1286_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static unsigned int ds1286_poll(struct file *file, poll_table *wait);
+
+static void ds1286_get_alm_time (struct rtc_time *alm_tm);
+static void ds1286_get_time(struct rtc_time *rtc_tm);
+static int ds1286_set_time(struct rtc_time *rtc_tm);
+
+static inline unsigned char ds1286_is_updating(void);
+
+static DEFINE_SPINLOCK(ds1286_lock);
+
+static int ds1286_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+
+/*
+ * Bits in rtc_status. (7 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
+#define RTC_TIMER_ON 0x02 /* missed irq timer active */
+
+static unsigned char ds1286_status; /* bitmapped status byte. */
+
+static unsigned char days_in_mo[] = {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ * Now all the various file operations that we export.
+ */
+
+static ssize_t ds1286_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ return -EIO;
+}
+
+static int ds1286_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct rtc_time wtime;
+
+ switch (cmd) {
+ case RTC_AIE_OFF: /* Mask alarm int. enab. bit */
+ {
+ unsigned int flags;
+ unsigned char val;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ spin_lock_irqsave(&ds1286_lock, flags);
+ val = rtc_read(RTC_CMD);
+ val |= RTC_TDM;
+ rtc_write(val, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ return 0;
+ }
+ case RTC_AIE_ON: /* Allow alarm interrupts. */
+ {
+ unsigned int flags;
+ unsigned char val;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ spin_lock_irqsave(&ds1286_lock, flags);
+ val = rtc_read(RTC_CMD);
+ val &= ~RTC_TDM;
+ rtc_write(val, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ return 0;
+ }
+ case RTC_WIE_OFF: /* Mask watchdog int. enab. bit */
+ {
+ unsigned int flags;
+ unsigned char val;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ spin_lock_irqsave(&ds1286_lock, flags);
+ val = rtc_read(RTC_CMD);
+ val |= RTC_WAM;
+ rtc_write(val, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ return 0;
+ }
+ case RTC_WIE_ON: /* Allow watchdog interrupts. */
+ {
+ unsigned int flags;
+ unsigned char val;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ spin_lock_irqsave(&ds1286_lock, flags);
+ val = rtc_read(RTC_CMD);
+ val &= ~RTC_WAM;
+ rtc_write(val, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ return 0;
+ }
+ case RTC_ALM_READ: /* Read the present alarm time */
+ {
+ /*
+ * This returns a struct rtc_time. Reading >= 0xc0
+ * means "don't care" or "match all". Only the tm_hour,
+ * tm_min, and tm_sec values are filled in.
+ */
+
+ memset(&wtime, 0, sizeof(wtime));
+ ds1286_get_alm_time(&wtime);
+ break;
+ }
+ case RTC_ALM_SET: /* Store a time into the alarm */
+ {
+ /*
+ * This expects a struct rtc_time. Writing 0xff means
+ * "don't care" or "match all". Only the tm_hour,
+ * tm_min and tm_sec are used.
+ */
+ unsigned char hrs, min, sec;
+ struct rtc_time alm_tm;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&alm_tm, (struct rtc_time*)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ hrs = alm_tm.tm_hour;
+ min = alm_tm.tm_min;
+
+ if (hrs >= 24)
+ hrs = 0xff;
+
+ if (min >= 60)
+ min = 0xff;
+
+ BIN_TO_BCD(sec);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(hrs);
+
+ spin_lock(&ds1286_lock);
+ rtc_write(hrs, RTC_HOURS_ALARM);
+ rtc_write(min, RTC_MINUTES_ALARM);
+ spin_unlock(&ds1286_lock);
+
+ return 0;
+ }
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ {
+ memset(&wtime, 0, sizeof(wtime));
+ ds1286_get_time(&wtime);
+ break;
+ }
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ struct rtc_time rtc_tm;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ return ds1286_set_time(&rtc_tm);
+ }
+ default:
+ return -EINVAL;
+ }
+ return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+static int ds1286_open(struct inode *inode, struct file *file)
+{
+ spin_lock_irq(&ds1286_lock);
+
+ if (ds1286_status & RTC_IS_OPEN)
+ goto out_busy;
+
+ ds1286_status |= RTC_IS_OPEN;
+
+ spin_unlock_irq(&ds1286_lock);
+ return 0;
+
+out_busy:
+ spin_lock_irq(&ds1286_lock);
+ return -EBUSY;
+}
+
+static int ds1286_release(struct inode *inode, struct file *file)
+{
+ ds1286_status &= ~RTC_IS_OPEN;
+
+ return 0;
+}
+
+static unsigned int ds1286_poll(struct file *file, poll_table *wait)
+{
+ poll_wait(file, &ds1286_wait, wait);
+
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations ds1286_fops = {
+ .llseek = no_llseek,
+ .read = ds1286_read,
+ .poll = ds1286_poll,
+ .ioctl = ds1286_ioctl,
+ .open = ds1286_open,
+ .release = ds1286_release,
+};
+
+static struct miscdevice ds1286_dev=
+{
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &ds1286_fops,
+};
+
+static int __init ds1286_init(void)
+{
+ int err;
+
+ printk(KERN_INFO "DS1286 Real Time Clock Driver v%s\n", DS1286_VERSION);
+
+ err = misc_register(&ds1286_dev);
+ if (err)
+ goto out;
+
+ if (!create_proc_read_entry("driver/rtc", 0, 0, ds1286_read_proc, NULL)) {
+ err = -ENOMEM;
+
+ goto out_deregister;
+ }
+
+ return 0;
+
+out_deregister:
+ misc_deregister(&ds1286_dev);
+
+out:
+ return err;
+}
+
+static void __exit ds1286_exit(void)
+{
+ remove_proc_entry("driver/rtc", NULL);
+ misc_deregister(&ds1286_dev);
+}
+
+static char *days[] = {
+ "***", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/*
+ * Info exported via "/proc/rtc".
+ */
+static int ds1286_proc_output(char *buf)
+{
+ char *p, *s;
+ struct rtc_time tm;
+ unsigned char hundredth, month, cmd, amode;
+
+ p = buf;
+
+ ds1286_get_time(&tm);
+ hundredth = rtc_read(RTC_HUNDREDTH_SECOND);
+ BCD_TO_BIN(hundredth);
+
+ p += sprintf(p,
+ "rtc_time\t: %02d:%02d:%02d.%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec, hundredth,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+
+ /*
+ * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will
+ * match any value for that particular field. Values that are
+ * greater than a valid time, but less than 0xc0 shouldn't appear.
+ */
+ ds1286_get_alm_time(&tm);
+ p += sprintf(p, "alarm\t\t: %s ", days[tm.tm_wday]);
+ if (tm.tm_hour <= 24)
+ p += sprintf(p, "%02d:", tm.tm_hour);
+ else
+ p += sprintf(p, "**:");
+
+ if (tm.tm_min <= 59)
+ p += sprintf(p, "%02d\n", tm.tm_min);
+ else
+ p += sprintf(p, "**\n");
+
+ month = rtc_read(RTC_MONTH);
+ p += sprintf(p,
+ "oscillator\t: %s\n"
+ "square_wave\t: %s\n",
+ (month & RTC_EOSC) ? "disabled" : "enabled",
+ (month & RTC_ESQW) ? "disabled" : "enabled");
+
+ amode = ((rtc_read(RTC_MINUTES_ALARM) & 0x80) >> 5) |
+ ((rtc_read(RTC_HOURS_ALARM) & 0x80) >> 6) |
+ ((rtc_read(RTC_DAY_ALARM) & 0x80) >> 7);
+ if (amode == 7) s = "each minute";
+ else if (amode == 3) s = "minutes match";
+ else if (amode == 1) s = "hours and minutes match";
+ else if (amode == 0) s = "days, hours and minutes match";
+ else s = "invalid";
+ p += sprintf(p, "alarm_mode\t: %s\n", s);
+
+ cmd = rtc_read(RTC_CMD);
+ p += sprintf(p,
+ "alarm_enable\t: %s\n"
+ "wdog_alarm\t: %s\n"
+ "alarm_mask\t: %s\n"
+ "wdog_alarm_mask\t: %s\n"
+ "interrupt_mode\t: %s\n"
+ "INTB_mode\t: %s_active\n"
+ "interrupt_pins\t: %s\n",
+ (cmd & RTC_TDF) ? "yes" : "no",
+ (cmd & RTC_WAF) ? "yes" : "no",
+ (cmd & RTC_TDM) ? "disabled" : "enabled",
+ (cmd & RTC_WAM) ? "disabled" : "enabled",
+ (cmd & RTC_PU_LVL) ? "pulse" : "level",
+ (cmd & RTC_IBH_LO) ? "low" : "high",
+ (cmd & RTC_IPSW) ? "unswapped" : "swapped");
+
+ return p - buf;
+}
+
+static int ds1286_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = ds1286_proc_output (page);
+ if (len <= off+count) *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len>count)
+ len = count;
+ if (len<0)
+ len = 0;
+
+ return len;
+}
+
+/*
+ * Returns true if a clock update is in progress
+ */
+static inline unsigned char ds1286_is_updating(void)
+{
+ return rtc_read(RTC_CMD) & RTC_TE;
+}
+
+
+static void ds1286_get_time(struct rtc_time *rtc_tm)
+{
+ unsigned char save_control;
+ unsigned int flags;
+ unsigned long uip_watchdog = jiffies;
+
+ /*
+ * read RTC once any update in progress is done. The update
+ * can take just over 2ms. We wait 10 to 20ms. There is no need to
+ * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP.
+ * If you need to know *exactly* when a second has started, enable
+ * periodic update complete interrupts, (via ioctl) and then
+ * immediately read /dev/rtc which will block until you get the IRQ.
+ * Once the read clears, read the RTC time (again via ioctl). Easy.
+ */
+
+ if (ds1286_is_updating() != 0)
+ while (jiffies - uip_watchdog < 2*HZ/100)
+ barrier();
+
+ /*
+ * Only the values that we read from the RTC are set. We leave
+ * tm_wday, tm_yday and tm_isdst untouched. Even though the
+ * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+ * by the RTC when initially set to a non-zero value.
+ */
+ spin_lock_irqsave(&ds1286_lock, flags);
+ save_control = rtc_read(RTC_CMD);
+ rtc_write((save_control|RTC_TE), RTC_CMD);
+
+ rtc_tm->tm_sec = rtc_read(RTC_SECONDS);
+ rtc_tm->tm_min = rtc_read(RTC_MINUTES);
+ rtc_tm->tm_hour = rtc_read(RTC_HOURS) & 0x3f;
+ rtc_tm->tm_mday = rtc_read(RTC_DATE);
+ rtc_tm->tm_mon = rtc_read(RTC_MONTH) & 0x1f;
+ rtc_tm->tm_year = rtc_read(RTC_YEAR);
+
+ rtc_write(save_control, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ BCD_TO_BIN(rtc_tm->tm_sec);
+ BCD_TO_BIN(rtc_tm->tm_min);
+ BCD_TO_BIN(rtc_tm->tm_hour);
+ BCD_TO_BIN(rtc_tm->tm_mday);
+ BCD_TO_BIN(rtc_tm->tm_mon);
+ BCD_TO_BIN(rtc_tm->tm_year);
+
+ /*
+ * Account for differences between how the RTC uses the values
+ * and how they are defined in a struct rtc_time;
+ */
+ if (rtc_tm->tm_year < 45)
+ rtc_tm->tm_year += 30;
+ if ((rtc_tm->tm_year += 40) < 70)
+ rtc_tm->tm_year += 100;
+
+ rtc_tm->tm_mon--;
+}
+
+static int ds1286_set_time(struct rtc_time *rtc_tm)
+{
+ unsigned char mon, day, hrs, min, sec, leap_yr;
+ unsigned char save_control;
+ unsigned int yrs, flags;
+
+
+ yrs = rtc_tm->tm_year + 1900;
+ mon = rtc_tm->tm_mon + 1; /* tm_mon starts at zero */
+ day = rtc_tm->tm_mday;
+ hrs = rtc_tm->tm_hour;
+ min = rtc_tm->tm_min;
+ sec = rtc_tm->tm_sec;
+
+ if (yrs < 1970)
+ return -EINVAL;
+
+ leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+ if ((mon > 12) || (day == 0))
+ return -EINVAL;
+
+ if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+ return -EINVAL;
+
+ if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+ return -EINVAL;
+
+ if ((yrs -= 1940) > 255) /* They are unsigned */
+ return -EINVAL;
+
+ if (yrs >= 100)
+ yrs -= 100;
+
+ BIN_TO_BCD(sec);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(hrs);
+ BIN_TO_BCD(day);
+ BIN_TO_BCD(mon);
+ BIN_TO_BCD(yrs);
+
+ spin_lock_irqsave(&ds1286_lock, flags);
+ save_control = rtc_read(RTC_CMD);
+ rtc_write((save_control|RTC_TE), RTC_CMD);
+
+ rtc_write(yrs, RTC_YEAR);
+ rtc_write(mon, RTC_MONTH);
+ rtc_write(day, RTC_DATE);
+ rtc_write(hrs, RTC_HOURS);
+ rtc_write(min, RTC_MINUTES);
+ rtc_write(sec, RTC_SECONDS);
+ rtc_write(0, RTC_HUNDREDTH_SECOND);
+
+ rtc_write(save_control, RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ return 0;
+}
+
+static void ds1286_get_alm_time(struct rtc_time *alm_tm)
+{
+ unsigned char cmd;
+ unsigned int flags;
+
+ /*
+ * Only the values that we read from the RTC are set. That
+ * means only tm_wday, tm_hour, tm_min.
+ */
+ spin_lock_irqsave(&ds1286_lock, flags);
+ alm_tm->tm_min = rtc_read(RTC_MINUTES_ALARM) & 0x7f;
+ alm_tm->tm_hour = rtc_read(RTC_HOURS_ALARM) & 0x1f;
+ alm_tm->tm_wday = rtc_read(RTC_DAY_ALARM) & 0x07;
+ cmd = rtc_read(RTC_CMD);
+ spin_unlock_irqrestore(&ds1286_lock, flags);
+
+ BCD_TO_BIN(alm_tm->tm_min);
+ BCD_TO_BIN(alm_tm->tm_hour);
+ alm_tm->tm_sec = 0;
+}
+
+module_init(ds1286_init);
+module_exit(ds1286_exit);
+
+MODULE_AUTHOR("Ralf Baechle");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/ds1302.c b/drivers/char/ds1302.c
new file mode 100644
index 000000000000..a75e8609be01
--- /dev/null
+++ b/drivers/char/ds1302.c
@@ -0,0 +1,354 @@
+/*!***************************************************************************
+*!
+*! FILE NAME : ds1302.c
+*!
+*! DESCRIPTION: Implements an interface for the DS1302 RTC
+*!
+*! Functions exported: ds1302_readreg, ds1302_writereg, ds1302_init, get_rtc_status
+*!
+*! ---------------------------------------------------------------------------
+*!
+*! (C) Copyright 1999, 2000, 2001 Axis Communications AB, LUND, SWEDEN
+*!
+*!***************************************************************************/
+
+#include <linux/config.h>
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <linux/bcd.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/rtc.h>
+#if defined(CONFIG_M32R)
+#include <asm/m32r.h>
+#endif
+
+#define RTC_MAJOR_NR 121 /* local major, change later */
+
+static const char ds1302_name[] = "ds1302";
+
+/* Send 8 bits. */
+static void
+out_byte_rtc(unsigned int reg_addr, unsigned char x)
+{
+ //RST H
+ outw(0x0001,(unsigned long)PLD_RTCRSTODT);
+ //write data
+ outw(((x<<8)|(reg_addr&0xff)),(unsigned long)PLD_RTCWRDATA);
+ //WE
+ outw(0x0002,(unsigned long)PLD_RTCCR);
+ //wait
+ while(inw((unsigned long)PLD_RTCCR));
+
+ //RST L
+ outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+
+}
+
+static unsigned char
+in_byte_rtc(unsigned int reg_addr)
+{
+ unsigned char retval;
+
+ //RST H
+ outw(0x0001,(unsigned long)PLD_RTCRSTODT);
+ //write data
+ outw((reg_addr&0xff),(unsigned long)PLD_RTCRDDATA);
+ //RE
+ outw(0x0001,(unsigned long)PLD_RTCCR);
+ //wait
+ while(inw((unsigned long)PLD_RTCCR));
+
+ //read data
+ retval=(inw((unsigned long)PLD_RTCRDDATA) & 0xff00)>>8;
+
+ //RST L
+ outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+
+ return retval;
+}
+
+/* Enable writing. */
+
+static void
+ds1302_wenable(void)
+{
+ out_byte_rtc(0x8e,0x00);
+}
+
+/* Disable writing. */
+
+static void
+ds1302_wdisable(void)
+{
+ out_byte_rtc(0x8e,0x80);
+}
+
+
+
+/* Read a byte from the selected register in the DS1302. */
+
+unsigned char
+ds1302_readreg(int reg)
+{
+ unsigned char x;
+
+ x=in_byte_rtc((0x81 | (reg << 1))); /* read register */
+
+ return x;
+}
+
+/* Write a byte to the selected register. */
+
+void
+ds1302_writereg(int reg, unsigned char val)
+{
+ ds1302_wenable();
+ out_byte_rtc((0x80 | (reg << 1)),val);
+ ds1302_wdisable();
+}
+
+void
+get_rtc_time(struct rtc_time *rtc_tm)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+ local_irq_disable();
+
+ rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
+ rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
+ rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
+ rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
+ rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
+ rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
+
+ local_irq_restore(flags);
+
+ BCD_TO_BIN(rtc_tm->tm_sec);
+ BCD_TO_BIN(rtc_tm->tm_min);
+ BCD_TO_BIN(rtc_tm->tm_hour);
+ BCD_TO_BIN(rtc_tm->tm_mday);
+ BCD_TO_BIN(rtc_tm->tm_mon);
+ BCD_TO_BIN(rtc_tm->tm_year);
+
+ /*
+ * Account for differences between how the RTC uses the values
+ * and how they are defined in a struct rtc_time;
+ */
+
+ if (rtc_tm->tm_year <= 69)
+ rtc_tm->tm_year += 100;
+
+ rtc_tm->tm_mon--;
+}
+
+static unsigned char days_in_mo[] =
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/* ioctl that supports RTC_RD_TIME and RTC_SET_TIME (read and set time/date). */
+
+static int
+rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned long flags;
+
+ switch(cmd) {
+ case RTC_RD_TIME: /* read the time/date from RTC */
+ {
+ struct rtc_time rtc_tm;
+
+ memset(&rtc_tm, 0, sizeof (struct rtc_time));
+ get_rtc_time(&rtc_tm);
+ if (copy_to_user((struct rtc_time*)arg, &rtc_tm, sizeof(struct rtc_time)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case RTC_SET_TIME: /* set the RTC */
+ {
+ struct rtc_time rtc_tm;
+ unsigned char mon, day, hrs, min, sec, leap_yr;
+ unsigned int yrs;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+
+ if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ yrs = rtc_tm.tm_year + 1900;
+ mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */
+ day = rtc_tm.tm_mday;
+ hrs = rtc_tm.tm_hour;
+ min = rtc_tm.tm_min;
+ sec = rtc_tm.tm_sec;
+
+
+ if ((yrs < 1970) || (yrs > 2069))
+ return -EINVAL;
+
+ leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+ if ((mon > 12) || (day == 0))
+ return -EINVAL;
+
+ if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+ return -EINVAL;
+
+ if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+ return -EINVAL;
+
+ if (yrs >= 2000)
+ yrs -= 2000; /* RTC (0, 1, ... 69) */
+ else
+ yrs -= 1900; /* RTC (70, 71, ... 99) */
+
+ BIN_TO_BCD(sec);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(hrs);
+ BIN_TO_BCD(day);
+ BIN_TO_BCD(mon);
+ BIN_TO_BCD(yrs);
+
+ local_irq_save(flags);
+ local_irq_disable();
+ CMOS_WRITE(yrs, RTC_YEAR);
+ CMOS_WRITE(mon, RTC_MONTH);
+ CMOS_WRITE(day, RTC_DAY_OF_MONTH);
+ CMOS_WRITE(hrs, RTC_HOURS);
+ CMOS_WRITE(min, RTC_MINUTES);
+ CMOS_WRITE(sec, RTC_SECONDS);
+ local_irq_restore(flags);
+
+ /* Notice that at this point, the RTC is updated but
+ * the kernel is still running with the old time.
+ * You need to set that separately with settimeofday
+ * or adjtimex.
+ */
+ return 0;
+ }
+
+ case RTC_SET_CHARGE: /* set the RTC TRICKLE CHARGE register */
+ {
+ int tcs_val;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EPERM;
+
+ if(copy_from_user(&tcs_val, (int*)arg, sizeof(int)))
+ return -EFAULT;
+
+ tcs_val = RTC_TCR_PATTERN | (tcs_val & 0x0F);
+ ds1302_writereg(RTC_TRICKLECHARGER, tcs_val);
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+int
+get_rtc_status(char *buf)
+{
+ char *p;
+ struct rtc_time tm;
+
+ p = buf;
+
+ get_rtc_time(&tm);
+
+ /*
+ * There is no way to tell if the luser has the RTC set for local
+ * time or for Universal Standard Time (GMT). Probably local though.
+ */
+
+ p += sprintf(p,
+ "rtc_time\t: %02d:%02d:%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+
+ return p - buf;
+}
+
+
+/* The various file operations we support. */
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = rtc_ioctl,
+};
+
+/* Probe for the chip by writing something to its RAM and try reading it back. */
+
+#define MAGIC_PATTERN 0x42
+
+static int __init
+ds1302_probe(void)
+{
+ int retval, res, baur;
+
+ baur=(boot_cpu_data.bus_clock/(2*1000*1000));
+
+ printk("%s: Set PLD_RTCBAUR = %d\n", ds1302_name,baur);
+
+ outw(0x0000,(unsigned long)PLD_RTCCR);
+ outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+ outw(baur,(unsigned long)PLD_RTCBAUR);
+
+ /* Try to talk to timekeeper. */
+
+ ds1302_wenable();
+ /* write RAM byte 0 */
+ /* write something magic */
+ out_byte_rtc(0xc0,MAGIC_PATTERN);
+
+ /* read RAM byte 0 */
+ if((res = in_byte_rtc(0xc1)) == MAGIC_PATTERN) {
+ char buf[100];
+ ds1302_wdisable();
+ printk("%s: RTC found.\n", ds1302_name);
+ get_rtc_status(buf);
+ printk(buf);
+ retval = 1;
+ } else {
+ printk("%s: RTC not found.\n", ds1302_name);
+ retval = 0;
+ }
+
+ return retval;
+}
+
+
+/* Just probe for the RTC and register the device to handle the ioctl needed. */
+
+int __init
+ds1302_init(void)
+{
+ if (!ds1302_probe()) {
+ return -1;
+ }
+ return 0;
+}
+
+static int __init ds1302_register(void)
+{
+ ds1302_init();
+ if (register_chrdev(RTC_MAJOR_NR, ds1302_name, &rtc_fops)) {
+ printk(KERN_INFO "%s: unable to get major %d for rtc\n",
+ ds1302_name, RTC_MAJOR_NR);
+ return -1;
+ }
+ return 0;
+}
+
+module_init(ds1302_register);
diff --git a/drivers/char/ds1620.c b/drivers/char/ds1620.c
new file mode 100644
index 000000000000..7def6ad51798
--- /dev/null
+++ b/drivers/char/ds1620.c
@@ -0,0 +1,416 @@
+/*
+ * linux/drivers/char/ds1620.c: Dallas Semiconductors DS1620
+ * thermometer driver (as used in the Rebel.com NetWinder)
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/capability.h>
+#include <linux/init.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/uaccess.h>
+#include <asm/therm.h>
+
+#ifdef CONFIG_PROC_FS
+/* define for /proc interface */
+#define THERM_USE_PROC
+#endif
+
+/* Definitions for DS1620 chip */
+#define THERM_START_CONVERT 0xee
+#define THERM_RESET 0xaf
+#define THERM_READ_CONFIG 0xac
+#define THERM_READ_TEMP 0xaa
+#define THERM_READ_TL 0xa2
+#define THERM_READ_TH 0xa1
+#define THERM_WRITE_CONFIG 0x0c
+#define THERM_WRITE_TL 0x02
+#define THERM_WRITE_TH 0x01
+
+#define CFG_CPU 2
+#define CFG_1SHOT 1
+
+static const char *fan_state[] = { "off", "on", "on (hardwired)" };
+
+/*
+ * Start of NetWinder specifics
+ * Note! We have to hold the gpio lock with IRQs disabled over the
+ * whole of our transaction to the Dallas chip, since there is a
+ * chance that the WaveArtist driver could touch these bits to
+ * enable or disable the speaker.
+ */
+extern spinlock_t gpio_lock;
+extern unsigned int system_rev;
+
+static inline void netwinder_ds1620_set_clk(int clk)
+{
+ gpio_modify_op(GPIO_DSCLK, clk ? GPIO_DSCLK : 0);
+}
+
+static inline void netwinder_ds1620_set_data(int dat)
+{
+ gpio_modify_op(GPIO_DATA, dat ? GPIO_DATA : 0);
+}
+
+static inline int netwinder_ds1620_get_data(void)
+{
+ return gpio_read() & GPIO_DATA;
+}
+
+static inline void netwinder_ds1620_set_data_dir(int dir)
+{
+ gpio_modify_io(GPIO_DATA, dir ? GPIO_DATA : 0);
+}
+
+static inline void netwinder_ds1620_reset(void)
+{
+ cpld_modify(CPLD_DS_ENABLE, 0);
+ cpld_modify(CPLD_DS_ENABLE, CPLD_DS_ENABLE);
+}
+
+static inline void netwinder_lock(unsigned long *flags)
+{
+ spin_lock_irqsave(&gpio_lock, *flags);
+}
+
+static inline void netwinder_unlock(unsigned long *flags)
+{
+ spin_unlock_irqrestore(&gpio_lock, *flags);
+}
+
+static inline void netwinder_set_fan(int i)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ gpio_modify_op(GPIO_FAN, i ? GPIO_FAN : 0);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+}
+
+static inline int netwinder_get_fan(void)
+{
+ if ((system_rev & 0xf000) == 0x4000)
+ return FAN_ALWAYS_ON;
+
+ return (gpio_read() & GPIO_FAN) ? FAN_ON : FAN_OFF;
+}
+
+/*
+ * End of NetWinder specifics
+ */
+
+static void ds1620_send_bits(int nr, int value)
+{
+ int i;
+
+ for (i = 0; i < nr; i++) {
+ netwinder_ds1620_set_data(value & 1);
+ netwinder_ds1620_set_clk(0);
+ udelay(1);
+ netwinder_ds1620_set_clk(1);
+ udelay(1);
+
+ value >>= 1;
+ }
+}
+
+static unsigned int ds1620_recv_bits(int nr)
+{
+ unsigned int value = 0, mask = 1;
+ int i;
+
+ netwinder_ds1620_set_data(0);
+
+ for (i = 0; i < nr; i++) {
+ netwinder_ds1620_set_clk(0);
+ udelay(1);
+
+ if (netwinder_ds1620_get_data())
+ value |= mask;
+
+ mask <<= 1;
+
+ netwinder_ds1620_set_clk(1);
+ udelay(1);
+ }
+
+ return value;
+}
+
+static void ds1620_out(int cmd, int bits, int value)
+{
+ unsigned long flags;
+
+ netwinder_lock(&flags);
+ netwinder_ds1620_set_clk(1);
+ netwinder_ds1620_set_data_dir(0);
+ netwinder_ds1620_reset();
+
+ udelay(1);
+
+ ds1620_send_bits(8, cmd);
+ if (bits)
+ ds1620_send_bits(bits, value);
+
+ udelay(1);
+
+ netwinder_ds1620_reset();
+ netwinder_unlock(&flags);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(2);
+}
+
+static unsigned int ds1620_in(int cmd, int bits)
+{
+ unsigned long flags;
+ unsigned int value;
+
+ netwinder_lock(&flags);
+ netwinder_ds1620_set_clk(1);
+ netwinder_ds1620_set_data_dir(0);
+ netwinder_ds1620_reset();
+
+ udelay(1);
+
+ ds1620_send_bits(8, cmd);
+
+ netwinder_ds1620_set_data_dir(1);
+ value = ds1620_recv_bits(bits);
+
+ netwinder_ds1620_reset();
+ netwinder_unlock(&flags);
+
+ return value;
+}
+
+static int cvt_9_to_int(unsigned int val)
+{
+ if (val & 0x100)
+ val |= 0xfffffe00;
+
+ return val;
+}
+
+static void ds1620_write_state(struct therm *therm)
+{
+ ds1620_out(THERM_WRITE_CONFIG, 8, CFG_CPU);
+ ds1620_out(THERM_WRITE_TL, 9, therm->lo);
+ ds1620_out(THERM_WRITE_TH, 9, therm->hi);
+ ds1620_out(THERM_START_CONVERT, 0, 0);
+}
+
+static void ds1620_read_state(struct therm *therm)
+{
+ therm->lo = cvt_9_to_int(ds1620_in(THERM_READ_TL, 9));
+ therm->hi = cvt_9_to_int(ds1620_in(THERM_READ_TH, 9));
+}
+
+static ssize_t
+ds1620_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+ signed int cur_temp;
+ signed char cur_temp_degF;
+
+ cur_temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;
+
+ /* convert to Fahrenheit, as per wdt.c */
+ cur_temp_degF = (cur_temp * 9) / 5 + 32;
+
+ if (copy_to_user(buf, &cur_temp_degF, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int
+ds1620_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct therm therm;
+ union {
+ struct therm __user *therm;
+ int __user *i;
+ } uarg;
+ int i;
+
+ uarg.i = (int __user *)arg;
+
+ switch(cmd) {
+ case CMD_SET_THERMOSTATE:
+ case CMD_SET_THERMOSTATE2:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (cmd == CMD_SET_THERMOSTATE) {
+ if (get_user(therm.hi, uarg.i))
+ return -EFAULT;
+ therm.lo = therm.hi - 3;
+ } else {
+ if (copy_from_user(&therm, uarg.therm, sizeof(therm)))
+ return -EFAULT;
+ }
+
+ therm.lo <<= 1;
+ therm.hi <<= 1;
+
+ ds1620_write_state(&therm);
+ break;
+
+ case CMD_GET_THERMOSTATE:
+ case CMD_GET_THERMOSTATE2:
+ ds1620_read_state(&therm);
+
+ therm.lo >>= 1;
+ therm.hi >>= 1;
+
+ if (cmd == CMD_GET_THERMOSTATE) {
+ if (put_user(therm.hi, uarg.i))
+ return -EFAULT;
+ } else {
+ if (copy_to_user(uarg.therm, &therm, sizeof(therm)))
+ return -EFAULT;
+ }
+ break;
+
+ case CMD_GET_TEMPERATURE:
+ case CMD_GET_TEMPERATURE2:
+ i = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+ if (cmd == CMD_GET_TEMPERATURE)
+ i >>= 1;
+
+ return put_user(i, uarg.i) ? -EFAULT : 0;
+
+ case CMD_GET_STATUS:
+ i = ds1620_in(THERM_READ_CONFIG, 8) & 0xe3;
+
+ return put_user(i, uarg.i) ? -EFAULT : 0;
+
+ case CMD_GET_FAN:
+ i = netwinder_get_fan();
+
+ return put_user(i, uarg.i) ? -EFAULT : 0;
+
+ case CMD_SET_FAN:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (get_user(i, uarg.i))
+ return -EFAULT;
+
+ netwinder_set_fan(i);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+#ifdef THERM_USE_PROC
+static int
+proc_therm_ds1620_read(char *buf, char **start, off_t offset,
+ int len, int *eof, void *unused)
+{
+ struct therm th;
+ int temp;
+
+ ds1620_read_state(&th);
+ temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+ len = sprintf(buf, "Thermostat: HI %i.%i, LOW %i.%i; "
+ "temperature: %i.%i C, fan %s\n",
+ th.hi >> 1, th.hi & 1 ? 5 : 0,
+ th.lo >> 1, th.lo & 1 ? 5 : 0,
+ temp >> 1, temp & 1 ? 5 : 0,
+ fan_state[netwinder_get_fan()]);
+
+ return len;
+}
+
+static struct proc_dir_entry *proc_therm_ds1620;
+#endif
+
+static struct file_operations ds1620_fops = {
+ .owner = THIS_MODULE,
+ .open = nonseekable_open,
+ .read = ds1620_read,
+ .ioctl = ds1620_ioctl,
+};
+
+static struct miscdevice ds1620_miscdev = {
+ TEMP_MINOR,
+ "temp",
+ &ds1620_fops
+};
+
+static int __init ds1620_init(void)
+{
+ int ret;
+ struct therm th, th_start;
+
+ if (!machine_is_netwinder())
+ return -ENODEV;
+
+ ds1620_out(THERM_RESET, 0, 0);
+ ds1620_out(THERM_WRITE_CONFIG, 8, CFG_CPU);
+ ds1620_out(THERM_START_CONVERT, 0, 0);
+
+ /*
+ * Trigger the fan to start by setting
+ * temperature high point low. This kicks
+ * the fan into action.
+ */
+ ds1620_read_state(&th);
+ th_start.lo = 0;
+ th_start.hi = 1;
+ ds1620_write_state(&th_start);
+
+ msleep(2000);
+
+ ds1620_write_state(&th);
+
+ ret = misc_register(&ds1620_miscdev);
+ if (ret < 0)
+ return ret;
+
+#ifdef THERM_USE_PROC
+ proc_therm_ds1620 = create_proc_entry("therm", 0, NULL);
+ if (proc_therm_ds1620)
+ proc_therm_ds1620->read_proc = proc_therm_ds1620_read;
+ else
+ printk(KERN_ERR "therm: unable to register /proc/therm\n");
+#endif
+
+ ds1620_read_state(&th);
+ ret = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+ printk(KERN_INFO "Thermostat: high %i.%i, low %i.%i, "
+ "current %i.%i C, fan %s.\n",
+ th.hi >> 1, th.hi & 1 ? 5 : 0,
+ th.lo >> 1, th.lo & 1 ? 5 : 0,
+ ret >> 1, ret & 1 ? 5 : 0,
+ fan_state[netwinder_get_fan()]);
+
+ return 0;
+}
+
+static void __exit ds1620_exit(void)
+{
+#ifdef THERM_USE_PROC
+ remove_proc_entry("therm", NULL);
+#endif
+ misc_deregister(&ds1620_miscdev);
+}
+
+module_init(ds1620_init);
+module_exit(ds1620_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/dsp56k.c b/drivers/char/dsp56k.c
new file mode 100644
index 000000000000..37d6649011ad
--- /dev/null
+++ b/drivers/char/dsp56k.c
@@ -0,0 +1,547 @@
+/*
+ * The DSP56001 Device Driver, saviour of the Free World(tm)
+ *
+ * Authors: Fredrik Noring <noring@nocrew.org>
+ * lars brinkhoff <lars@nocrew.org>
+ * Tomas Berndtsson <tomas@nocrew.org>
+ *
+ * First version May 1996
+ *
+ * History:
+ * 97-01-29 Tomas Berndtsson,
+ * Integrated with Linux 2.1.21 kernel sources.
+ * 97-02-15 Tomas Berndtsson,
+ * Fixed for kernel 2.1.26
+ *
+ * BUGS:
+ * Hmm... there must be something here :)
+ *
+ * Copyright (C) 1996,1997 Fredrik Noring, lars brinkhoff & Tomas Berndtsson
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h> /* for kmalloc() and kfree() */
+#include <linux/sched.h> /* for struct wait_queue etc */
+#include <linux/major.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/delay.h> /* guess what */
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#include <asm/atarihw.h>
+#include <asm/traps.h>
+#include <asm/uaccess.h> /* For put_user and get_user */
+
+#include <asm/dsp56k.h>
+
+/* minor devices */
+#define DSP56K_DEV_56001 0 /* The only device so far */
+
+#define TIMEOUT 10 /* Host port timeout in number of tries */
+#define MAXIO 2048 /* Maximum number of words before sleep */
+#define DSP56K_MAX_BINARY_LENGTH (3*64*1024)
+
+#define DSP56K_TX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_TREQ
+#define DSP56K_RX_INT_ON dsp56k_host_interface.icr |= DSP56K_ICR_RREQ
+#define DSP56K_TX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_TREQ
+#define DSP56K_RX_INT_OFF dsp56k_host_interface.icr &= ~DSP56K_ICR_RREQ
+
+#define DSP56K_TRANSMIT (dsp56k_host_interface.isr & DSP56K_ISR_TXDE)
+#define DSP56K_RECEIVE (dsp56k_host_interface.isr & DSP56K_ISR_RXDF)
+
+#define handshake(count, maxio, timeout, ENABLE, f) \
+{ \
+ long i, t, m; \
+ while (count > 0) { \
+ m = min_t(unsigned long, count, maxio); \
+ for (i = 0; i < m; i++) { \
+ for (t = 0; t < timeout && !ENABLE; t++) \
+ msleep(20); \
+ if(!ENABLE) \
+ return -EIO; \
+ f; \
+ } \
+ count -= m; \
+ if (m == maxio) msleep(20); \
+ } \
+}
+
+#define tx_wait(n) \
+{ \
+ int t; \
+ for(t = 0; t < n && !DSP56K_TRANSMIT; t++) \
+ msleep(10); \
+ if(!DSP56K_TRANSMIT) { \
+ return -EIO; \
+ } \
+}
+
+#define rx_wait(n) \
+{ \
+ int t; \
+ for(t = 0; t < n && !DSP56K_RECEIVE; t++) \
+ msleep(10); \
+ if(!DSP56K_RECEIVE) { \
+ return -EIO; \
+ } \
+}
+
+/* DSP56001 bootstrap code */
+static char bootstrap[] = {
+ 0x0c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0xf4, 0x00, 0x00, 0x00, 0x4f, 0x61, 0xf4,
+ 0x00, 0x00, 0x7e, 0xa9, 0x06, 0x2e, 0x80, 0x00, 0x00, 0x47,
+ 0x07, 0xd8, 0x84, 0x07, 0x59, 0x84, 0x08, 0xf4, 0xa8, 0x00,
+ 0x00, 0x04, 0x08, 0xf4, 0xbf, 0x00, 0x0c, 0x00, 0x00, 0xfe,
+ 0xb8, 0x0a, 0xf0, 0x80, 0x00, 0x7e, 0xa9, 0x08, 0xf4, 0xa0,
+ 0x00, 0x00, 0x01, 0x08, 0xf4, 0xbe, 0x00, 0x00, 0x00, 0x0a,
+ 0xa9, 0x80, 0x00, 0x7e, 0xad, 0x08, 0x4e, 0x2b, 0x44, 0xf4,
+ 0x00, 0x00, 0x00, 0x03, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x01,
+ 0x0e, 0xa0, 0x00, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb5, 0x08,
+ 0x50, 0x2b, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb8, 0x08, 0x46,
+ 0x2b, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x02, 0x0a, 0xf0, 0xaa,
+ 0x00, 0x7e, 0xc9, 0x20, 0x00, 0x45, 0x0a, 0xf0, 0xaa, 0x00,
+ 0x7e, 0xd0, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xc6, 0x0a, 0xa9,
+ 0x80, 0x00, 0x7e, 0xc4, 0x08, 0x58, 0x6b, 0x0a, 0xf0, 0x80,
+ 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xcd, 0x0a,
+ 0xa9, 0x80, 0x00, 0x7e, 0xcb, 0x08, 0x58, 0xab, 0x0a, 0xf0,
+ 0x80, 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xd4,
+ 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xd2, 0x08, 0x58, 0xeb, 0x0a,
+ 0xf0, 0x80, 0x00, 0x7e, 0xad};
+static int sizeof_bootstrap = 375;
+
+
+static struct dsp56k_device {
+ long in_use;
+ long maxio, timeout;
+ int tx_wsize, rx_wsize;
+} dsp56k;
+
+static struct class_simple *dsp56k_class;
+
+static int dsp56k_reset(void)
+{
+ u_char status;
+
+ /* Power down the DSP */
+ sound_ym.rd_data_reg_sel = 14;
+ status = sound_ym.rd_data_reg_sel & 0xef;
+ sound_ym.wd_data = status;
+ sound_ym.wd_data = status | 0x10;
+
+ udelay(10);
+
+ /* Power up the DSP */
+ sound_ym.rd_data_reg_sel = 14;
+ sound_ym.wd_data = sound_ym.rd_data_reg_sel & 0xef;
+
+ return 0;
+}
+
+static int dsp56k_upload(u_char *bin, int len)
+{
+ int i;
+ u_char *p;
+
+ dsp56k_reset();
+
+ p = bootstrap;
+ for (i = 0; i < sizeof_bootstrap/3; i++) {
+ /* tx_wait(10); */
+ dsp56k_host_interface.data.b[1] = *p++;
+ dsp56k_host_interface.data.b[2] = *p++;
+ dsp56k_host_interface.data.b[3] = *p++;
+ }
+ for (; i < 512; i++) {
+ /* tx_wait(10); */
+ dsp56k_host_interface.data.b[1] = 0;
+ dsp56k_host_interface.data.b[2] = 0;
+ dsp56k_host_interface.data.b[3] = 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ tx_wait(10);
+ get_user(dsp56k_host_interface.data.b[1], bin++);
+ get_user(dsp56k_host_interface.data.b[2], bin++);
+ get_user(dsp56k_host_interface.data.b[3], bin++);
+ }
+
+ tx_wait(10);
+ dsp56k_host_interface.data.l = 3; /* Magic execute */
+
+ return 0;
+}
+
+static ssize_t dsp56k_read(struct file *file, char *buf, size_t count,
+ loff_t *ppos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ int dev = iminor(inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+ {
+
+ long n;
+
+ /* Don't do anything if nothing is to be done */
+ if (!count) return 0;
+
+ n = 0;
+ switch (dsp56k.rx_wsize) {
+ case 1: /* 8 bit */
+ {
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+ put_user(dsp56k_host_interface.data.b[3], buf+n++));
+ return n;
+ }
+ case 2: /* 16 bit */
+ {
+ short *data;
+
+ count /= 2;
+ data = (short*) buf;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+ put_user(dsp56k_host_interface.data.w[1], data+n++));
+ return 2*n;
+ }
+ case 3: /* 24 bit */
+ {
+ count /= 3;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+ put_user(dsp56k_host_interface.data.b[1], buf+n++);
+ put_user(dsp56k_host_interface.data.b[2], buf+n++);
+ put_user(dsp56k_host_interface.data.b[3], buf+n++));
+ return 3*n;
+ }
+ case 4: /* 32 bit */
+ {
+ long *data;
+
+ count /= 4;
+ data = (long*) buf;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+ put_user(dsp56k_host_interface.data.l, data+n++));
+ return 4*n;
+ }
+ }
+ return -EFAULT;
+ }
+
+ default:
+ printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+ return -ENXIO;
+ }
+}
+
+static ssize_t dsp56k_write(struct file *file, const char *buf, size_t count,
+ loff_t *ppos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ int dev = iminor(inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+ {
+ long n;
+
+ /* Don't do anything if nothing is to be done */
+ if (!count) return 0;
+
+ n = 0;
+ switch (dsp56k.tx_wsize) {
+ case 1: /* 8 bit */
+ {
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+ get_user(dsp56k_host_interface.data.b[3], buf+n++));
+ return n;
+ }
+ case 2: /* 16 bit */
+ {
+ const short *data;
+
+ count /= 2;
+ data = (const short *)buf;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+ get_user(dsp56k_host_interface.data.w[1], data+n++));
+ return 2*n;
+ }
+ case 3: /* 24 bit */
+ {
+ count /= 3;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+ get_user(dsp56k_host_interface.data.b[1], buf+n++);
+ get_user(dsp56k_host_interface.data.b[2], buf+n++);
+ get_user(dsp56k_host_interface.data.b[3], buf+n++));
+ return 3*n;
+ }
+ case 4: /* 32 bit */
+ {
+ const long *data;
+
+ count /= 4;
+ data = (const long *)buf;
+ handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+ get_user(dsp56k_host_interface.data.l, data+n++));
+ return 4*n;
+ }
+ }
+
+ return -EFAULT;
+ }
+ default:
+ printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+ return -ENXIO;
+ }
+}
+
+static int dsp56k_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int dev = iminor(inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+
+ switch(cmd) {
+ case DSP56K_UPLOAD:
+ {
+ char *bin;
+ int r, len;
+ struct dsp56k_upload *binary = (struct dsp56k_upload *) arg;
+
+ if(get_user(len, &binary->len) < 0)
+ return -EFAULT;
+ if(get_user(bin, &binary->bin) < 0)
+ return -EFAULT;
+
+ if (len == 0) {
+ return -EINVAL; /* nothing to upload?!? */
+ }
+ if (len > DSP56K_MAX_BINARY_LENGTH) {
+ return -EINVAL;
+ }
+
+ r = dsp56k_upload(bin, len);
+ if (r < 0) {
+ return r;
+ }
+
+ break;
+ }
+ case DSP56K_SET_TX_WSIZE:
+ if (arg > 4 || arg < 1)
+ return -EINVAL;
+ dsp56k.tx_wsize = (int) arg;
+ break;
+ case DSP56K_SET_RX_WSIZE:
+ if (arg > 4 || arg < 1)
+ return -EINVAL;
+ dsp56k.rx_wsize = (int) arg;
+ break;
+ case DSP56K_HOST_FLAGS:
+ {
+ int dir, out, status;
+ struct dsp56k_host_flags *hf = (struct dsp56k_host_flags*) arg;
+
+ if(get_user(dir, &hf->dir) < 0)
+ return -EFAULT;
+ if(get_user(out, &hf->out) < 0)
+ return -EFAULT;
+
+ if ((dir & 0x1) && (out & 0x1))
+ dsp56k_host_interface.icr |= DSP56K_ICR_HF0;
+ else if (dir & 0x1)
+ dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
+ if ((dir & 0x2) && (out & 0x2))
+ dsp56k_host_interface.icr |= DSP56K_ICR_HF1;
+ else if (dir & 0x2)
+ dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
+
+ status = 0;
+ if (dsp56k_host_interface.icr & DSP56K_ICR_HF0) status |= 0x1;
+ if (dsp56k_host_interface.icr & DSP56K_ICR_HF1) status |= 0x2;
+ if (dsp56k_host_interface.isr & DSP56K_ISR_HF2) status |= 0x4;
+ if (dsp56k_host_interface.isr & DSP56K_ISR_HF3) status |= 0x8;
+
+ return put_user(status, &hf->status);
+ }
+ case DSP56K_HOST_CMD:
+ if (arg > 31 || arg < 0)
+ return -EINVAL;
+ dsp56k_host_interface.cvr = (u_char)((arg & DSP56K_CVR_HV_MASK) |
+ DSP56K_CVR_HC);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+ default:
+ printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+ return -ENXIO;
+ }
+}
+
+/* As of 2.1.26 this should be dsp56k_poll,
+ * but how do I then check device minor number?
+ * Do I need this function at all???
+ */
+#if 0
+static unsigned int dsp56k_poll(struct file *file, poll_table *wait)
+{
+ int dev = iminor(file->f_dentry->d_inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+ /* poll_wait(file, ???, wait); */
+ return POLLIN | POLLRDNORM | POLLOUT;
+
+ default:
+ printk("DSP56k driver: Unknown minor device: %d\n", dev);
+ return 0;
+ }
+}
+#endif
+
+static int dsp56k_open(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+
+ if (test_and_set_bit(0, &dsp56k.in_use))
+ return -EBUSY;
+
+ dsp56k.timeout = TIMEOUT;
+ dsp56k.maxio = MAXIO;
+ dsp56k.rx_wsize = dsp56k.tx_wsize = 4;
+
+ DSP56K_TX_INT_OFF;
+ DSP56K_RX_INT_OFF;
+
+ /* Zero host flags */
+ dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
+ dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
+
+ break;
+
+ default:
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int dsp56k_release(struct inode *inode, struct file *file)
+{
+ int dev = iminor(inode) & 0x0f;
+
+ switch(dev)
+ {
+ case DSP56K_DEV_56001:
+ clear_bit(0, &dsp56k.in_use);
+ break;
+ default:
+ printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static struct file_operations dsp56k_fops = {
+ .owner = THIS_MODULE,
+ .read = dsp56k_read,
+ .write = dsp56k_write,
+ .ioctl = dsp56k_ioctl,
+ .open = dsp56k_open,
+ .release = dsp56k_release,
+};
+
+
+/****** Init and module functions ******/
+
+static char banner[] __initdata = KERN_INFO "DSP56k driver installed\n";
+
+static int __init dsp56k_init_driver(void)
+{
+ int err = 0;
+
+ if(!MACH_IS_ATARI || !ATARIHW_PRESENT(DSP56K)) {
+ printk("DSP56k driver: Hardware not present\n");
+ return -ENODEV;
+ }
+
+ if(register_chrdev(DSP56K_MAJOR, "dsp56k", &dsp56k_fops)) {
+ printk("DSP56k driver: Unable to register driver\n");
+ return -ENODEV;
+ }
+ dsp56k_class = class_simple_create(THIS_MODULE, "dsp56k");
+ if (IS_ERR(dsp56k_class)) {
+ err = PTR_ERR(dsp56k_class);
+ goto out_chrdev;
+ }
+ class_simple_device_add(dsp56k_class, MKDEV(DSP56K_MAJOR, 0), NULL, "dsp56k");
+
+ err = devfs_mk_cdev(MKDEV(DSP56K_MAJOR, 0),
+ S_IFCHR | S_IRUSR | S_IWUSR, "dsp56k");
+ if(err)
+ goto out_class;
+
+ printk(banner);
+ goto out;
+
+out_class:
+ class_simple_device_remove(MKDEV(DSP56K_MAJOR, 0));
+ class_simple_destroy(dsp56k_class);
+out_chrdev:
+ unregister_chrdev(DSP56K_MAJOR, "dsp56k");
+out:
+ return err;
+}
+module_init(dsp56k_init_driver);
+
+static void __exit dsp56k_cleanup_driver(void)
+{
+ class_simple_device_remove(MKDEV(DSP56K_MAJOR, 0));
+ class_simple_destroy(dsp56k_class);
+ unregister_chrdev(DSP56K_MAJOR, "dsp56k");
+ devfs_remove("dsp56k");
+}
+module_exit(dsp56k_cleanup_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/dtlk.c b/drivers/char/dtlk.c
new file mode 100644
index 000000000000..903e4c3cc209
--- /dev/null
+++ b/drivers/char/dtlk.c
@@ -0,0 +1,659 @@
+/* -*- linux-c -*-
+ * dtlk.c - DoubleTalk PC driver for Linux
+ *
+ * Original author: Chris Pallotta <chris@allmedia.com>
+ * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
+ *
+ * 2000-03-18 Jim Van Zandt: Fix polling.
+ * Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
+ * function. Don't restart timer in dtlk_timer_tick. Restart timer
+ * in dtlk_poll after every poll. dtlk_poll returns mask (duh).
+ * Eliminate unused function dtlk_write_byte. Misc. code cleanups.
+ */
+
+/* This driver is for the DoubleTalk PC, a speech synthesizer
+ manufactured by RC Systems (http://www.rcsys.com/). It was written
+ based on documentation in their User's Manual file and Developer's
+ Tools disk.
+
+ The DoubleTalk PC contains four voice synthesizers: text-to-speech
+ (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD. It
+ also has a tone generator. Output data for LPC are written to the
+ LPC port, and output data for the other modes are written to the
+ TTS port.
+
+ Two kinds of data can be read from the DoubleTalk: status
+ information (in response to the "\001?" interrogation command) is
+ read from the TTS port, and index markers (which mark the progress
+ of the speech) are read from the LPC port. Not all models of the
+ DoubleTalk PC implement index markers. Both the TTS and LPC ports
+ can also display status flags.
+
+ The DoubleTalk PC generates no interrupts.
+
+ These characteristics are mapped into the Unix stream I/O model as
+ follows:
+
+ "write" sends bytes to the TTS port. It is the responsibility of
+ the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
+ This driver was written for use with the text-to-speech
+ synthesizer. If LPC output is needed some day, other minor device
+ numbers can be used to select among output modes.
+
+ "read" gets index markers from the LPC port. If the device does
+ not implement index markers, the read will fail with error EINVAL.
+
+ Status information is available using the DTLK_INTERROGATE ioctl.
+
+ */
+
+#include <linux/module.h>
+
+#define KERNEL
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/mm.h> /* for verify_area */
+#include <linux/errno.h> /* for -EBUSY */
+#include <linux/ioport.h> /* for request_region */
+#include <linux/delay.h> /* for loops_per_jiffy */
+#include <asm/io.h> /* for inb_p, outb_p, inb, outb, etc. */
+#include <asm/uaccess.h> /* for get_user, etc. */
+#include <linux/wait.h> /* for wait_queue */
+#include <linux/init.h> /* for __init, module_{init,exit} */
+#include <linux/poll.h> /* for POLLIN, etc. */
+#include <linux/dtlk.h> /* local header file for DoubleTalk values */
+#include <linux/devfs_fs_kernel.h>
+#include <linux/smp_lock.h>
+
+#ifdef TRACING
+#define TRACE_TEXT(str) printk(str);
+#define TRACE_RET printk(")")
+#else /* !TRACING */
+#define TRACE_TEXT(str) ((void) 0)
+#define TRACE_RET ((void) 0)
+#endif /* TRACING */
+
+
+static int dtlk_major;
+static int dtlk_port_lpc;
+static int dtlk_port_tts;
+static int dtlk_busy;
+static int dtlk_has_indexing;
+static unsigned int dtlk_portlist[] =
+{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
+static wait_queue_head_t dtlk_process_list;
+static struct timer_list dtlk_timer;
+
+/* prototypes for file_operations struct */
+static ssize_t dtlk_read(struct file *, char __user *,
+ size_t nbytes, loff_t * ppos);
+static ssize_t dtlk_write(struct file *, const char __user *,
+ size_t nbytes, loff_t * ppos);
+static unsigned int dtlk_poll(struct file *, poll_table *);
+static int dtlk_open(struct inode *, struct file *);
+static int dtlk_release(struct inode *, struct file *);
+static int dtlk_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static struct file_operations dtlk_fops =
+{
+ .owner = THIS_MODULE,
+ .read = dtlk_read,
+ .write = dtlk_write,
+ .poll = dtlk_poll,
+ .ioctl = dtlk_ioctl,
+ .open = dtlk_open,
+ .release = dtlk_release,
+};
+
+/* local prototypes */
+static int dtlk_dev_probe(void);
+static struct dtlk_settings *dtlk_interrogate(void);
+static int dtlk_readable(void);
+static char dtlk_read_lpc(void);
+static char dtlk_read_tts(void);
+static int dtlk_writeable(void);
+static char dtlk_write_bytes(const char *buf, int n);
+static char dtlk_write_tts(char);
+/*
+ static void dtlk_handle_error(char, char, unsigned int);
+ */
+static void dtlk_timer_tick(unsigned long data);
+
+static ssize_t dtlk_read(struct file *file, char __user *buf,
+ size_t count, loff_t * ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode);
+ char ch;
+ int i = 0, retries;
+
+ TRACE_TEXT("(dtlk_read");
+ /* printk("DoubleTalk PC - dtlk_read()\n"); */
+
+ if (minor != DTLK_MINOR || !dtlk_has_indexing)
+ return -EINVAL;
+
+ for (retries = 0; retries < loops_per_jiffy; retries++) {
+ while (i < count && dtlk_readable()) {
+ ch = dtlk_read_lpc();
+ /* printk("dtlk_read() reads 0x%02x\n", ch); */
+ if (put_user(ch, buf++))
+ return -EFAULT;
+ i++;
+ }
+ if (i)
+ return i;
+ if (file->f_flags & O_NONBLOCK)
+ break;
+ msleep_interruptible(100);
+ }
+ if (retries == loops_per_jiffy)
+ printk(KERN_ERR "dtlk_read times out\n");
+ TRACE_RET;
+ return -EAGAIN;
+}
+
+static ssize_t dtlk_write(struct file *file, const char __user *buf,
+ size_t count, loff_t * ppos)
+{
+ int i = 0, retries = 0, ch;
+
+ TRACE_TEXT("(dtlk_write");
+#ifdef TRACING
+ printk(" \"");
+ {
+ int i, ch;
+ for (i = 0; i < count; i++) {
+ if (get_user(ch, buf + i))
+ return -EFAULT;
+ if (' ' <= ch && ch <= '~')
+ printk("%c", ch);
+ else
+ printk("\\%03o", ch);
+ }
+ printk("\"");
+ }
+#endif
+
+ if (iminor(file->f_dentry->d_inode) != DTLK_MINOR)
+ return -EINVAL;
+
+ while (1) {
+ while (i < count && !get_user(ch, buf) &&
+ (ch == DTLK_CLEAR || dtlk_writeable())) {
+ dtlk_write_tts(ch);
+ buf++;
+ i++;
+ if (i % 5 == 0)
+ /* We yield our time until scheduled
+ again. This reduces the transfer
+ rate to 500 bytes/sec, but that's
+ still enough to keep up with the
+ speech synthesizer. */
+ msleep_interruptible(1);
+ else {
+ /* the RDY bit goes zero 2-3 usec
+ after writing, and goes 1 again
+ 180-190 usec later. Here, we wait
+ up to 250 usec for the RDY bit to
+ go nonzero. */
+ for (retries = 0;
+ retries < loops_per_jiffy / (4000/HZ);
+ retries++)
+ if (inb_p(dtlk_port_tts) &
+ TTS_WRITABLE)
+ break;
+ }
+ retries = 0;
+ }
+ if (i == count)
+ return i;
+ if (file->f_flags & O_NONBLOCK)
+ break;
+
+ msleep_interruptible(1);
+
+ if (++retries > 10 * HZ) { /* wait no more than 10 sec
+ from last write */
+ printk("dtlk: write timeout. "
+ "inb_p(dtlk_port_tts) = 0x%02x\n",
+ inb_p(dtlk_port_tts));
+ TRACE_RET;
+ return -EBUSY;
+ }
+ }
+ TRACE_RET;
+ return -EAGAIN;
+}
+
+static unsigned int dtlk_poll(struct file *file, poll_table * wait)
+{
+ int mask = 0;
+ unsigned long expires;
+
+ TRACE_TEXT(" dtlk_poll");
+ /*
+ static long int j;
+ printk(".");
+ printk("<%ld>", jiffies-j);
+ j=jiffies;
+ */
+ poll_wait(file, &dtlk_process_list, wait);
+
+ if (dtlk_has_indexing && dtlk_readable()) {
+ del_timer(&dtlk_timer);
+ mask = POLLIN | POLLRDNORM;
+ }
+ if (dtlk_writeable()) {
+ del_timer(&dtlk_timer);
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ /* there are no exception conditions */
+
+ /* There won't be any interrupts, so we set a timer instead. */
+ expires = jiffies + 3*HZ / 100;
+ mod_timer(&dtlk_timer, expires);
+
+ return mask;
+}
+
+static void dtlk_timer_tick(unsigned long data)
+{
+ TRACE_TEXT(" dtlk_timer_tick");
+ wake_up_interruptible(&dtlk_process_list);
+}
+
+static int dtlk_ioctl(struct inode *inode,
+ struct file *file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ char __user *argp = (char __user *)arg;
+ struct dtlk_settings *sp;
+ char portval;
+ TRACE_TEXT(" dtlk_ioctl");
+
+ switch (cmd) {
+
+ case DTLK_INTERROGATE:
+ sp = dtlk_interrogate();
+ if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
+ return -EINVAL;
+ return 0;
+
+ case DTLK_STATUS:
+ portval = inb_p(dtlk_port_tts);
+ return put_user(portval, argp);
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int dtlk_open(struct inode *inode, struct file *file)
+{
+ TRACE_TEXT("(dtlk_open");
+
+ nonseekable_open(inode, file);
+ switch (iminor(inode)) {
+ case DTLK_MINOR:
+ if (dtlk_busy)
+ return -EBUSY;
+ return nonseekable_open(inode, file);
+
+ default:
+ return -ENXIO;
+ }
+}
+
+static int dtlk_release(struct inode *inode, struct file *file)
+{
+ TRACE_TEXT("(dtlk_release");
+
+ switch (iminor(inode)) {
+ case DTLK_MINOR:
+ break;
+
+ default:
+ break;
+ }
+ TRACE_RET;
+
+ del_timer(&dtlk_timer);
+
+ return 0;
+}
+
+static int __init dtlk_init(void)
+{
+ dtlk_port_lpc = 0;
+ dtlk_port_tts = 0;
+ dtlk_busy = 0;
+ dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
+ if (dtlk_major == 0) {
+ printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
+ return 0;
+ }
+ if (dtlk_dev_probe() == 0)
+ printk(", MAJOR %d\n", dtlk_major);
+
+ devfs_mk_cdev(MKDEV(dtlk_major, DTLK_MINOR),
+ S_IFCHR | S_IRUSR | S_IWUSR, "dtlk");
+
+ init_timer(&dtlk_timer);
+ dtlk_timer.function = dtlk_timer_tick;
+ init_waitqueue_head(&dtlk_process_list);
+
+ return 0;
+}
+
+static void __exit dtlk_cleanup (void)
+{
+ dtlk_write_bytes("goodbye", 8);
+ msleep_interruptible(500); /* nap 0.50 sec but
+ could be awakened
+ earlier by
+ signals... */
+
+ dtlk_write_tts(DTLK_CLEAR);
+ unregister_chrdev(dtlk_major, "dtlk");
+ devfs_remove("dtlk");
+ release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
+}
+
+module_init(dtlk_init);
+module_exit(dtlk_cleanup);
+
+/* ------------------------------------------------------------------------ */
+
+static int dtlk_readable(void)
+{
+#ifdef TRACING
+ printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
+#endif
+ return inb_p(dtlk_port_lpc) != 0x7f;
+}
+
+static int dtlk_writeable(void)
+{
+ /* TRACE_TEXT(" dtlk_writeable"); */
+#ifdef TRACINGMORE
+ printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
+#endif
+ return inb_p(dtlk_port_tts) & TTS_WRITABLE;
+}
+
+static int __init dtlk_dev_probe(void)
+{
+ unsigned int testval = 0;
+ int i = 0;
+ struct dtlk_settings *sp;
+
+ if (dtlk_port_lpc | dtlk_port_tts)
+ return -EBUSY;
+
+ for (i = 0; dtlk_portlist[i]; i++) {
+#if 0
+ printk("DoubleTalk PC - Port %03x = %04x\n",
+ dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
+#endif
+
+ if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT,
+ "dtlk"))
+ continue;
+ testval = inw_p(dtlk_portlist[i]);
+ if ((testval &= 0xfbff) == 0x107f) {
+ dtlk_port_lpc = dtlk_portlist[i];
+ dtlk_port_tts = dtlk_port_lpc + 1;
+
+ sp = dtlk_interrogate();
+ printk("DoubleTalk PC at %03x-%03x, "
+ "ROM version %s, serial number %u",
+ dtlk_portlist[i], dtlk_portlist[i] +
+ DTLK_IO_EXTENT - 1,
+ sp->rom_version, sp->serial_number);
+
+ /* put LPC port into known state, so
+ dtlk_readable() gives valid result */
+ outb_p(0xff, dtlk_port_lpc);
+
+ /* INIT string and index marker */
+ dtlk_write_bytes("\036\1@\0\0012I\r", 8);
+ /* posting an index takes 18 msec. Here, we
+ wait up to 100 msec to see whether it
+ appears. */
+ msleep_interruptible(100);
+ dtlk_has_indexing = dtlk_readable();
+#ifdef TRACING
+ printk(", indexing %d\n", dtlk_has_indexing);
+#endif
+#ifdef INSCOPE
+ {
+/* This macro records ten samples read from the LPC port, for later display */
+#define LOOK \
+for (i = 0; i < 10; i++) \
+ { \
+ buffer[b++] = inb_p(dtlk_port_lpc); \
+ __delay(loops_per_jiffy/(1000000/HZ)); \
+ }
+ char buffer[1000];
+ int b = 0, i, j;
+
+ LOOK
+ outb_p(0xff, dtlk_port_lpc);
+ buffer[b++] = 0;
+ LOOK
+ dtlk_write_bytes("\0012I\r", 4);
+ buffer[b++] = 0;
+ __delay(50 * loops_per_jiffy / (1000/HZ));
+ outb_p(0xff, dtlk_port_lpc);
+ buffer[b++] = 0;
+ LOOK
+
+ printk("\n");
+ for (j = 0; j < b; j++)
+ printk(" %02x", buffer[j]);
+ printk("\n");
+ }
+#endif /* INSCOPE */
+
+#ifdef OUTSCOPE
+ {
+/* This macro records ten samples read from the TTS port, for later display */
+#define LOOK \
+for (i = 0; i < 10; i++) \
+ { \
+ buffer[b++] = inb_p(dtlk_port_tts); \
+ __delay(loops_per_jiffy/(1000000/HZ)); /* 1 us */ \
+ }
+ char buffer[1000];
+ int b = 0, i, j;
+
+ mdelay(10); /* 10 ms */
+ LOOK
+ outb_p(0x03, dtlk_port_tts);
+ buffer[b++] = 0;
+ LOOK
+ LOOK
+
+ printk("\n");
+ for (j = 0; j < b; j++)
+ printk(" %02x", buffer[j]);
+ printk("\n");
+ }
+#endif /* OUTSCOPE */
+
+ dtlk_write_bytes("Double Talk found", 18);
+
+ return 0;
+ }
+ release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
+ }
+
+ printk(KERN_INFO "\nDoubleTalk PC - not found\n");
+ return -ENODEV;
+}
+
+/*
+ static void dtlk_handle_error(char op, char rc, unsigned int minor)
+ {
+ printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n",
+ minor, op, rc);
+ return;
+ }
+ */
+
+/* interrogate the DoubleTalk PC and return its settings */
+static struct dtlk_settings *dtlk_interrogate(void)
+{
+ unsigned char *t;
+ static char buf[sizeof(struct dtlk_settings) + 1];
+ int total, i;
+ static struct dtlk_settings status;
+ TRACE_TEXT("(dtlk_interrogate");
+ dtlk_write_bytes("\030\001?", 3);
+ for (total = 0, i = 0; i < 50; i++) {
+ buf[total] = dtlk_read_tts();
+ if (total > 2 && buf[total] == 0x7f)
+ break;
+ if (total < sizeof(struct dtlk_settings))
+ total++;
+ }
+ /*
+ if (i==50) printk("interrogate() read overrun\n");
+ for (i=0; i<sizeof(buf); i++)
+ printk(" %02x", buf[i]);
+ printk("\n");
+ */
+ t = buf;
+ status.serial_number = t[0] + t[1] * 256; /* serial number is
+ little endian */
+ t += 2;
+
+ i = 0;
+ while (*t != '\r') {
+ status.rom_version[i] = *t;
+ if (i < sizeof(status.rom_version) - 1)
+ i++;
+ t++;
+ }
+ status.rom_version[i] = 0;
+ t++;
+
+ status.mode = *t++;
+ status.punc_level = *t++;
+ status.formant_freq = *t++;
+ status.pitch = *t++;
+ status.speed = *t++;
+ status.volume = *t++;
+ status.tone = *t++;
+ status.expression = *t++;
+ status.ext_dict_loaded = *t++;
+ status.ext_dict_status = *t++;
+ status.free_ram = *t++;
+ status.articulation = *t++;
+ status.reverb = *t++;
+ status.eob = *t++;
+ status.has_indexing = dtlk_has_indexing;
+ TRACE_RET;
+ return &status;
+}
+
+static char dtlk_read_tts(void)
+{
+ int portval, retries = 0;
+ char ch;
+ TRACE_TEXT("(dtlk_read_tts");
+
+ /* verify DT is ready, read char, wait for ACK */
+ do {
+ portval = inb_p(dtlk_port_tts);
+ } while ((portval & TTS_READABLE) == 0 &&
+ retries++ < DTLK_MAX_RETRIES);
+ if (retries == DTLK_MAX_RETRIES)
+ printk(KERN_ERR "dtlk_read_tts() timeout\n");
+
+ ch = inb_p(dtlk_port_tts); /* input from TTS port */
+ ch &= 0x7f;
+ outb_p(ch, dtlk_port_tts);
+
+ retries = 0;
+ do {
+ portval = inb_p(dtlk_port_tts);
+ } while ((portval & TTS_READABLE) != 0 &&
+ retries++ < DTLK_MAX_RETRIES);
+ if (retries == DTLK_MAX_RETRIES)
+ printk(KERN_ERR "dtlk_read_tts() timeout\n");
+
+ TRACE_RET;
+ return ch;
+}
+
+static char dtlk_read_lpc(void)
+{
+ int retries = 0;
+ char ch;
+ TRACE_TEXT("(dtlk_read_lpc");
+
+ /* no need to test -- this is only called when the port is readable */
+
+ ch = inb_p(dtlk_port_lpc); /* input from LPC port */
+
+ outb_p(0xff, dtlk_port_lpc);
+
+ /* acknowledging a read takes 3-4
+ usec. Here, we wait up to 20 usec
+ for the acknowledgement */
+ retries = (loops_per_jiffy * 20) / (1000000/HZ);
+ while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
+ if (retries == 0)
+ printk(KERN_ERR "dtlk_read_lpc() timeout\n");
+
+ TRACE_RET;
+ return ch;
+}
+
+/* write n bytes to tts port */
+static char dtlk_write_bytes(const char *buf, int n)
+{
+ char val = 0;
+ /* printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
+ TRACE_TEXT("(dtlk_write_bytes");
+ while (n-- > 0)
+ val = dtlk_write_tts(*buf++);
+ TRACE_RET;
+ return val;
+}
+
+static char dtlk_write_tts(char ch)
+{
+ int retries = 0;
+#ifdef TRACINGMORE
+ printk(" dtlk_write_tts(");
+ if (' ' <= ch && ch <= '~')
+ printk("'%c'", ch);
+ else
+ printk("0x%02x", ch);
+#endif
+ if (ch != DTLK_CLEAR) /* no flow control for CLEAR command */
+ while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
+ retries++ < DTLK_MAX_RETRIES) /* DT ready? */
+ ;
+ if (retries == DTLK_MAX_RETRIES)
+ printk(KERN_ERR "dtlk_write_tts() timeout\n");
+
+ outb_p(ch, dtlk_port_tts); /* output to TTS port */
+ /* the RDY bit goes zero 2-3 usec after writing, and goes
+ 1 again 180-190 usec later. Here, we wait up to 10
+ usec for the RDY bit to go zero. */
+ for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
+ if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
+ break;
+
+#ifdef TRACINGMORE
+ printk(")\n");
+#endif
+ return 0;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ec3104_keyb.c b/drivers/char/ec3104_keyb.c
new file mode 100644
index 000000000000..4aed66968821
--- /dev/null
+++ b/drivers/char/ec3104_keyb.c
@@ -0,0 +1,459 @@
+/*
+ * linux/drivers/char/ec3104_keyb.c
+ *
+ * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org>
+ *
+ * based on linux/drivers/char/pc_keyb.c, which had the following comments:
+ *
+ * Separation of the PC low-level part by Geert Uytterhoeven, May 1997
+ * See keyboard.c for the whole history.
+ *
+ * Major cleanup by Martin Mares, May 1997
+ *
+ * Combined the keyboard and PS/2 mouse handling into one file,
+ * because they share the same hardware.
+ * Johan Myreen <jem@iki.fi> 1998-10-08.
+ *
+ * Code fixes to handle mouse ACKs properly.
+ * C. Scott Ananian <cananian@alumni.princeton.edu> 1999-01-29.
+ */
+/* EC3104 note:
+ * This code was written without any documentation about the EC3104 chip. While
+ * I hope I got most of the basic functionality right, the register names I use
+ * are most likely completely different from those in the chip documentation.
+ *
+ * If you have any further information about the EC3104, please tell me
+ * (prumpf@tux.org).
+ */
+
+#include <linux/config.h>
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/smp_lock.h>
+#include <linux/bitops.h>
+
+#include <asm/keyboard.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/ec3104.h>
+
+#include <asm/io.h>
+
+/* Some configuration switches are present in the include file... */
+
+#include <linux/pc_keyb.h>
+
+#define MSR_CTS 0x10
+#define MCR_RTS 0x02
+#define LSR_DR 0x01
+#define LSR_BOTH_EMPTY 0x60
+
+static struct e5_struct {
+ u8 packet[8];
+ int pos;
+ int length;
+
+ u8 cached_mcr;
+ u8 last_msr;
+} ec3104_keyb;
+
+/* Simple translation table for the SysRq keys */
+
+
+#ifdef CONFIG_MAGIC_SYSRQ
+unsigned char ec3104_kbd_sysrq_xlate[128] =
+ "\000\0331234567890-=\177\t" /* 0x00 - 0x0f */
+ "qwertyuiop[]\r\000as" /* 0x10 - 0x1f */
+ "dfghjkl;'`\000\\zxcv" /* 0x20 - 0x2f */
+ "bnm,./\000*\000 \000\201\202\203\204\205" /* 0x30 - 0x3f */
+ "\206\207\210\211\212\000\000789-456+1" /* 0x40 - 0x4f */
+ "230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */
+ "\r\000/"; /* 0x60 - 0x6f */
+#endif
+
+static void kbd_write_command_w(int data);
+static void kbd_write_output_w(int data);
+#ifdef CONFIG_PSMOUSE
+static void aux_write_ack(int val);
+static void __aux_write_ack(int val);
+#endif
+
+static DEFINE_SPINLOCK(kbd_controller_lock);
+static unsigned char handle_kbd_event(void);
+
+/* used only by send_data - set by keyboard_interrupt */
+static volatile unsigned char reply_expected;
+static volatile unsigned char acknowledge;
+static volatile unsigned char resend;
+
+
+int ec3104_kbd_setkeycode(unsigned int scancode, unsigned int keycode)
+{
+ return 0;
+}
+
+int ec3104_kbd_getkeycode(unsigned int scancode)
+{
+ return 0;
+}
+
+
+/* yes, it probably would be faster to use an array. I don't care. */
+
+static inline unsigned char ec3104_scan2key(unsigned char scancode)
+{
+ switch (scancode) {
+ case 1: /* '`' */
+ return 41;
+
+ case 2 ... 27:
+ return scancode;
+
+ case 28: /* '\\' */
+ return 43;
+
+ case 29 ... 39:
+ return scancode + 1;
+
+ case 40: /* '\r' */
+ return 28;
+
+ case 41 ... 50:
+ return scancode + 3;
+
+ case 51: /* ' ' */
+ return 57;
+
+ case 52: /* escape */
+ return 1;
+
+ case 54: /* insert/delete (labelled delete) */
+ /* this should arguably be 110, but I'd like to have ctrl-alt-del
+ * working with a standard keymap */
+ return 111;
+
+ case 55: /* left */
+ return 105;
+ case 56: /* home */
+ return 102;
+ case 57: /* end */
+ return 107;
+ case 58: /* up */
+ return 103;
+ case 59: /* down */
+ return 108;
+ case 60: /* pgup */
+ return 104;
+ case 61: /* pgdown */
+ return 109;
+ case 62: /* right */
+ return 106;
+
+ case 79 ... 88: /* f1 - f10 */
+ return scancode - 20;
+
+ case 89 ... 90: /* f11 - f12 */
+ return scancode - 2;
+
+ case 91: /* left shift */
+ return 42;
+
+ case 92: /* right shift */
+ return 54;
+
+ case 93: /* left alt */
+ return 56;
+ case 94: /* right alt */
+ return 100;
+ case 95: /* left ctrl */
+ return 29;
+ case 96: /* right ctrl */
+ return 97;
+
+ case 97: /* caps lock */
+ return 58;
+ case 102: /* left windows */
+ return 125;
+ case 103: /* right windows */
+ return 126;
+
+ case 106: /* Fn */
+ /* this is wrong. */
+ return 84;
+
+ default:
+ return 0;
+ }
+}
+
+int ec3104_kbd_translate(unsigned char scancode, unsigned char *keycode,
+ char raw_mode)
+{
+ scancode &= 0x7f;
+
+ *keycode = ec3104_scan2key(scancode);
+
+ return 1;
+}
+
+char ec3104_kbd_unexpected_up(unsigned char keycode)
+{
+ return 0200;
+}
+
+static inline void handle_keyboard_event(unsigned char scancode)
+{
+#ifdef CONFIG_VT
+ handle_scancode(scancode, !(scancode & 0x80));
+#endif
+ tasklet_schedule(&keyboard_tasklet);
+}
+
+void ec3104_kbd_leds(unsigned char leds)
+{
+}
+
+static u8 e5_checksum(u8 *packet, int count)
+{
+ int i;
+ u8 sum = 0;
+
+ for (i=0; i<count; i++)
+ sum ^= packet[i];
+
+ if (sum & 0x80)
+ sum ^= 0xc0;
+
+ return sum;
+}
+
+static void e5_wait_for_cts(struct e5_struct *k)
+{
+ u8 msr;
+
+ do {
+ msr = ctrl_inb(EC3104_SER4_MSR);
+ } while (!(msr & MSR_CTS));
+}
+
+
+static void e5_send_byte(u8 byte, struct e5_struct *k)
+{
+ u8 status;
+
+ do {
+ status = ctrl_inb(EC3104_SER4_LSR);
+ } while ((status & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY);
+
+ printk("<%02x>", byte);
+
+ ctrl_outb(byte, EC3104_SER4_DATA);
+
+ do {
+ status = ctrl_inb(EC3104_SER4_LSR);
+ } while ((status & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY);
+
+}
+
+static int e5_send_packet(u8 *packet, int count, struct e5_struct *k)
+{
+ int i;
+
+ disable_irq(EC3104_IRQ_SER4);
+
+ if (k->cached_mcr & MCR_RTS) {
+ printk("e5_send_packet: too slow\n");
+ enable_irq(EC3104_IRQ_SER4);
+ return -EAGAIN;
+ }
+
+ k->cached_mcr |= MCR_RTS;
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+
+ e5_wait_for_cts(k);
+
+ printk("p: ");
+
+ for(i=0; i<count; i++)
+ e5_send_byte(packet[i], k);
+
+ e5_send_byte(e5_checksum(packet, count), k);
+
+ printk("\n");
+
+ udelay(1500);
+
+ k->cached_mcr &= ~MCR_RTS;
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+
+
+
+ enable_irq(EC3104_IRQ_SER4);
+
+
+
+ return 0;
+}
+
+/*
+ * E5 packets we know about:
+ * E5->host 0x80 0x05 <checksum> - resend packet
+ * host->E5 0x83 0x43 <contrast> - set LCD contrast
+ * host->E5 0x85 0x41 0x02 <brightness> 0x02 - set LCD backlight
+ * E5->host 0x87 <ps2 packet> 0x00 <checksum> - external PS2
+ * E5->host 0x88 <scancode> <checksum> - key press
+ */
+
+static void e5_receive(struct e5_struct *k)
+{
+ k->packet[k->pos++] = ctrl_inb(EC3104_SER4_DATA);
+
+ if (k->pos == 1) {
+ switch(k->packet[0]) {
+ case 0x80:
+ k->length = 3;
+ break;
+
+ case 0x87: /* PS2 ext */
+ k->length = 6;
+ break;
+
+ case 0x88: /* keyboard */
+ k->length = 3;
+ break;
+
+ default:
+ k->length = 1;
+ printk(KERN_WARNING "unknown E5 packet %02x\n",
+ k->packet[0]);
+ }
+ }
+
+ if (k->pos == k->length) {
+ int i;
+
+ if (e5_checksum(k->packet, k->length) != 0)
+ printk(KERN_WARNING "E5: wrong checksum\n");
+
+#if 0
+ printk("E5 packet [");
+ for(i=0; i<k->length; i++) {
+ printk("%02x ", k->packet[i]);
+ }
+
+ printk("(%02x)]\n", e5_checksum(k->packet, k->length-1));
+#endif
+
+ switch(k->packet[0]) {
+ case 0x80:
+ case 0x88:
+ handle_keyboard_event(k->packet[1]);
+ break;
+ }
+
+ k->pos = k->length = 0;
+ }
+}
+
+static void ec3104_keyb_interrupt(int irq, void *data, struct pt_regs *regs)
+{
+ struct e5_struct *k = &ec3104_keyb;
+ u8 msr, lsr;
+
+ msr = ctrl_inb(EC3104_SER4_MSR);
+
+ if ((msr & MSR_CTS) && !(k->last_msr & MSR_CTS)) {
+ if (k->cached_mcr & MCR_RTS)
+ printk("confused: RTS already high\n");
+ /* CTS went high. Send RTS. */
+ k->cached_mcr |= MCR_RTS;
+
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+ } else if ((!(msr & MSR_CTS)) && (k->last_msr & MSR_CTS)) {
+ /* CTS went low. */
+ if (!(k->cached_mcr & MCR_RTS))
+ printk("confused: RTS already low\n");
+
+ k->cached_mcr &= ~MCR_RTS;
+
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+ }
+
+ k->last_msr = msr;
+
+ lsr = ctrl_inb(EC3104_SER4_LSR);
+
+ if (lsr & LSR_DR)
+ e5_receive(k);
+}
+
+static void ec3104_keyb_clear_state(void)
+{
+ struct e5_struct *k = &ec3104_keyb;
+ u8 msr, lsr;
+
+ /* we want CTS to be low */
+ k->last_msr = 0;
+
+ for (;;) {
+ msleep(100);
+
+ msr = ctrl_inb(EC3104_SER4_MSR);
+
+ lsr = ctrl_inb(EC3104_SER4_LSR);
+
+ if (lsr & LSR_DR) {
+ e5_receive(k);
+ continue;
+ }
+
+ if ((msr & MSR_CTS) && !(k->last_msr & MSR_CTS)) {
+ if (k->cached_mcr & MCR_RTS)
+ printk("confused: RTS already high\n");
+ /* CTS went high. Send RTS. */
+ k->cached_mcr |= MCR_RTS;
+
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+ } else if ((!(msr & MSR_CTS)) && (k->last_msr & MSR_CTS)) {
+ /* CTS went low. */
+ if (!(k->cached_mcr & MCR_RTS))
+ printk("confused: RTS already low\n");
+
+ k->cached_mcr &= ~MCR_RTS;
+
+ ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+ } else
+ break;
+
+ k->last_msr = msr;
+
+ continue;
+ }
+}
+
+void __init ec3104_kbd_init_hw(void)
+{
+ ec3104_keyb.last_msr = ctrl_inb(EC3104_SER4_MSR);
+ ec3104_keyb.cached_mcr = ctrl_inb(EC3104_SER4_MCR);
+
+ ec3104_keyb_clear_state();
+
+ /* Ok, finally allocate the IRQ, and off we go.. */
+ request_irq(EC3104_IRQ_SER4, ec3104_keyb_interrupt, 0, "keyboard", NULL);
+}
diff --git a/drivers/char/efirtc.c b/drivers/char/efirtc.c
new file mode 100644
index 000000000000..0090e7a4fcd3
--- /dev/null
+++ b/drivers/char/efirtc.c
@@ -0,0 +1,417 @@
+/*
+ * EFI Time Services Driver for Linux
+ *
+ * Copyright (C) 1999 Hewlett-Packard Co
+ * Copyright (C) 1999 Stephane Eranian <eranian@hpl.hp.com>
+ *
+ * Based on skeleton from the drivers/char/rtc.c driver by P. Gortmaker
+ *
+ * This code provides an architected & portable interface to the real time
+ * clock by using EFI instead of direct bit fiddling. The functionalities are
+ * quite different from the rtc.c driver. The only way to talk to the device
+ * is by using ioctl(). There is a /proc interface which provides the raw
+ * information.
+ *
+ * Please note that we have kept the API as close as possible to the
+ * legacy RTC. The standard /sbin/hwclock program should work normally
+ * when used to get/set the time.
+ *
+ * NOTES:
+ * - Locking is required for safe execution of EFI calls with regards
+ * to interrrupts and SMP.
+ *
+ * TODO (December 1999):
+ * - provide the API to set/get the WakeUp Alarm (different from the
+ * rtc.c alarm).
+ * - SMP testing
+ * - Add module support
+ */
+
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/proc_fs.h>
+#include <linux/efi.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define EFI_RTC_VERSION "0.4"
+
+#define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
+/*
+ * EFI Epoch is 1/1/1998
+ */
+#define EFI_RTC_EPOCH 1998
+
+static DEFINE_SPINLOCK(efi_rtc_lock);
+
+static int efi_rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+#define is_leap(year) \
+ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
+
+static const unsigned short int __mon_yday[2][13] =
+{
+ /* Normal years. */
+ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+ /* Leap years. */
+ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
+
+/*
+ * returns day of the year [0-365]
+ */
+static inline int
+compute_yday(efi_time_t *eft)
+{
+ /* efi_time_t.month is in the [1-12] so, we need -1 */
+ return __mon_yday[is_leap(eft->year)][eft->month-1]+ eft->day -1;
+}
+/*
+ * returns day of the week [0-6] 0=Sunday
+ *
+ * Don't try to provide a year that's before 1998, please !
+ */
+static int
+compute_wday(efi_time_t *eft)
+{
+ int y;
+ int ndays = 0;
+
+ if ( eft->year < 1998 ) {
+ printk(KERN_ERR "efirtc: EFI year < 1998, invalid date\n");
+ return -1;
+ }
+
+ for(y=EFI_RTC_EPOCH; y < eft->year; y++ ) {
+ ndays += 365 + (is_leap(y) ? 1 : 0);
+ }
+ ndays += compute_yday(eft);
+
+ /*
+ * 4=1/1/1998 was a Thursday
+ */
+ return (ndays + 4) % 7;
+}
+
+static void
+convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft)
+{
+
+ eft->year = wtime->tm_year + 1900;
+ eft->month = wtime->tm_mon + 1;
+ eft->day = wtime->tm_mday;
+ eft->hour = wtime->tm_hour;
+ eft->minute = wtime->tm_min;
+ eft->second = wtime->tm_sec;
+ eft->nanosecond = 0;
+ eft->daylight = wtime->tm_isdst ? EFI_ISDST: 0;
+ eft->timezone = EFI_UNSPECIFIED_TIMEZONE;
+}
+
+static void
+convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)
+{
+ memset(wtime, 0, sizeof(*wtime));
+ wtime->tm_sec = eft->second;
+ wtime->tm_min = eft->minute;
+ wtime->tm_hour = eft->hour;
+ wtime->tm_mday = eft->day;
+ wtime->tm_mon = eft->month - 1;
+ wtime->tm_year = eft->year - 1900;
+
+ /* day of the week [0-6], Sunday=0 */
+ wtime->tm_wday = compute_wday(eft);
+
+ /* day in the year [1-365]*/
+ wtime->tm_yday = compute_yday(eft);
+
+
+ switch (eft->daylight & EFI_ISDST) {
+ case EFI_ISDST:
+ wtime->tm_isdst = 1;
+ break;
+ case EFI_TIME_ADJUST_DAYLIGHT:
+ wtime->tm_isdst = 0;
+ break;
+ default:
+ wtime->tm_isdst = -1;
+ }
+}
+
+static int
+efi_rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+
+ efi_status_t status;
+ unsigned long flags;
+ efi_time_t eft;
+ efi_time_cap_t cap;
+ struct rtc_time wtime;
+ struct rtc_wkalrm __user *ewp;
+ unsigned char enabled, pending;
+
+ switch (cmd) {
+ case RTC_UIE_ON:
+ case RTC_UIE_OFF:
+ case RTC_PIE_ON:
+ case RTC_PIE_OFF:
+ case RTC_AIE_ON:
+ case RTC_AIE_OFF:
+ case RTC_ALM_SET:
+ case RTC_ALM_READ:
+ case RTC_IRQP_READ:
+ case RTC_IRQP_SET:
+ case RTC_EPOCH_READ:
+ case RTC_EPOCH_SET:
+ return -EINVAL;
+
+ case RTC_RD_TIME:
+
+ spin_lock_irqsave(&efi_rtc_lock, flags);
+
+ status = efi.get_time(&eft, &cap);
+
+ spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+ if (status != EFI_SUCCESS) {
+ /* should never happen */
+ printk(KERN_ERR "efitime: can't read time\n");
+ return -EINVAL;
+ }
+
+ convert_from_efi_time(&eft, &wtime);
+
+ return copy_to_user((void __user *)arg, &wtime,
+ sizeof (struct rtc_time)) ? - EFAULT : 0;
+
+ case RTC_SET_TIME:
+
+ if (!capable(CAP_SYS_TIME)) return -EACCES;
+
+ if (copy_from_user(&wtime, (struct rtc_time __user *)arg,
+ sizeof(struct rtc_time)) )
+ return -EFAULT;
+
+ convert_to_efi_time(&wtime, &eft);
+
+ spin_lock_irqsave(&efi_rtc_lock, flags);
+
+ status = efi.set_time(&eft);
+
+ spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+ return status == EFI_SUCCESS ? 0 : -EINVAL;
+
+ case RTC_WKALM_SET:
+
+ if (!capable(CAP_SYS_TIME)) return -EACCES;
+
+ ewp = (struct rtc_wkalrm __user *)arg;
+
+ if ( get_user(enabled, &ewp->enabled)
+ || copy_from_user(&wtime, &ewp->time, sizeof(struct rtc_time)) )
+ return -EFAULT;
+
+ convert_to_efi_time(&wtime, &eft);
+
+ spin_lock_irqsave(&efi_rtc_lock, flags);
+ /*
+ * XXX Fixme:
+ * As of EFI 0.92 with the firmware I have on my
+ * machine this call does not seem to work quite
+ * right
+ */
+ status = efi.set_wakeup_time((efi_bool_t)enabled, &eft);
+
+ spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+ return status == EFI_SUCCESS ? 0 : -EINVAL;
+
+ case RTC_WKALM_RD:
+
+ spin_lock_irqsave(&efi_rtc_lock, flags);
+
+ status = efi.get_wakeup_time((efi_bool_t *)&enabled, (efi_bool_t *)&pending, &eft);
+
+ spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+ if (status != EFI_SUCCESS) return -EINVAL;
+
+ ewp = (struct rtc_wkalrm __user *)arg;
+
+ if ( put_user(enabled, &ewp->enabled)
+ || put_user(pending, &ewp->pending)) return -EFAULT;
+
+ convert_from_efi_time(&eft, &wtime);
+
+ return copy_to_user(&ewp->time, &wtime,
+ sizeof(struct rtc_time)) ? -EFAULT : 0;
+ }
+ return -EINVAL;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+static int
+efi_rtc_open(struct inode *inode, struct file *file)
+{
+ /*
+ * nothing special to do here
+ * We do accept multiple open files at the same time as we
+ * synchronize on the per call operation.
+ */
+ return 0;
+}
+
+static int
+efi_rtc_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations efi_rtc_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = efi_rtc_ioctl,
+ .open = efi_rtc_open,
+ .release = efi_rtc_close,
+};
+
+static struct miscdevice efi_rtc_dev=
+{
+ EFI_RTC_MINOR,
+ "efirtc",
+ &efi_rtc_fops
+};
+
+/*
+ * We export RAW EFI information to /proc/driver/efirtc
+ */
+static int
+efi_rtc_get_status(char *buf)
+{
+ efi_time_t eft, alm;
+ efi_time_cap_t cap;
+ char *p = buf;
+ efi_bool_t enabled, pending;
+ unsigned long flags;
+
+ memset(&eft, 0, sizeof(eft));
+ memset(&alm, 0, sizeof(alm));
+ memset(&cap, 0, sizeof(cap));
+
+ spin_lock_irqsave(&efi_rtc_lock, flags);
+
+ efi.get_time(&eft, &cap);
+ efi.get_wakeup_time(&enabled, &pending, &alm);
+
+ spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+ p += sprintf(p,
+ "Time : %u:%u:%u.%09u\n"
+ "Date : %u-%u-%u\n"
+ "Daylight : %u\n",
+ eft.hour, eft.minute, eft.second, eft.nanosecond,
+ eft.year, eft.month, eft.day,
+ eft.daylight);
+
+ if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
+ p += sprintf(p, "Timezone : unspecified\n");
+ else
+ /* XXX fixme: convert to string? */
+ p += sprintf(p, "Timezone : %u\n", eft.timezone);
+
+
+ p += sprintf(p,
+ "Alarm Time : %u:%u:%u.%09u\n"
+ "Alarm Date : %u-%u-%u\n"
+ "Alarm Daylight : %u\n"
+ "Enabled : %s\n"
+ "Pending : %s\n",
+ alm.hour, alm.minute, alm.second, alm.nanosecond,
+ alm.year, alm.month, alm.day,
+ alm.daylight,
+ enabled == 1 ? "yes" : "no",
+ pending == 1 ? "yes" : "no");
+
+ if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
+ p += sprintf(p, "Timezone : unspecified\n");
+ else
+ /* XXX fixme: convert to string? */
+ p += sprintf(p, "Timezone : %u\n", alm.timezone);
+
+ /*
+ * now prints the capabilities
+ */
+ p += sprintf(p,
+ "Resolution : %u\n"
+ "Accuracy : %u\n"
+ "SetstoZero : %u\n",
+ cap.resolution, cap.accuracy, cap.sets_to_zero);
+
+ return p - buf;
+}
+
+static int
+efi_rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = efi_rtc_get_status(page);
+ if (len <= off+count) *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len>count) len = count;
+ if (len<0) len = 0;
+ return len;
+}
+
+static int __init
+efi_rtc_init(void)
+{
+ int ret;
+ struct proc_dir_entry *dir;
+
+ printk(KERN_INFO "EFI Time Services Driver v%s\n", EFI_RTC_VERSION);
+
+ ret = misc_register(&efi_rtc_dev);
+ if (ret) {
+ printk(KERN_ERR "efirtc: can't misc_register on minor=%d\n",
+ EFI_RTC_MINOR);
+ return ret;
+ }
+
+ dir = create_proc_read_entry ("driver/efirtc", 0, NULL,
+ efi_rtc_read_proc, NULL);
+ if (dir == NULL) {
+ printk(KERN_ERR "efirtc: can't create /proc/driver/efirtc.\n");
+ misc_deregister(&efi_rtc_dev);
+ return -1;
+ }
+ return 0;
+}
+
+static void __exit
+efi_rtc_exit(void)
+{
+ /* not yet used */
+}
+
+module_init(efi_rtc_init);
+module_exit(efi_rtc_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/epca.c b/drivers/char/epca.c
new file mode 100644
index 000000000000..6025e1866c7e
--- /dev/null
+++ b/drivers/char/epca.c
@@ -0,0 +1,3789 @@
+/*
+
+
+ Copyright (C) 1996 Digi International.
+
+ For technical support please email digiLinux@dgii.com or
+ call Digi tech support at (612) 912-3456
+
+ Much of this design and code came from epca.c which was
+ copyright (C) 1994, 1995 Troy De Jongh, and subsquently
+ modified by David Nugent, Christoph Lameter, Mike McLagan.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+--------------------------------------------------------------------------- */
+/* See README.epca for change history --DAT*/
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/serial.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_PCI
+#define ENABLE_PCI
+#endif /* CONFIG_PCI */
+
+#define putUser(arg1, arg2) put_user(arg1, (unsigned long __user *)arg2)
+#define getUser(arg1, arg2) get_user(arg1, (unsigned __user *)arg2)
+
+#ifdef ENABLE_PCI
+#include <linux/pci.h>
+#include "digiPCI.h"
+#endif /* ENABLE_PCI */
+
+#include "digi1.h"
+#include "digiFep1.h"
+#include "epca.h"
+#include "epcaconfig.h"
+
+#if BITS_PER_LONG != 32
+# error FIXME: this driver only works on 32-bit platforms
+#endif
+
+/* ---------------------- Begin defines ------------------------ */
+
+#define VERSION "1.3.0.1-LK"
+
+/* This major needs to be submitted to Linux to join the majors list */
+
+#define DIGIINFOMAJOR 35 /* For Digi specific ioctl */
+
+
+#define MAXCARDS 7
+#define epcaassert(x, msg) if (!(x)) epca_error(__LINE__, msg)
+
+#define PFX "epca: "
+
+/* ----------------- Begin global definitions ------------------- */
+
+static char mesg[100];
+static int nbdevs, num_cards, liloconfig;
+static int digi_poller_inhibited = 1 ;
+
+static int setup_error_code;
+static int invalid_lilo_config;
+
+/* -----------------------------------------------------------------------
+ MAXBOARDS is typically 12, but ISA and EISA cards are restricted to
+ 7 below.
+--------------------------------------------------------------------------*/
+static struct board_info boards[MAXBOARDS];
+
+
+/* ------------- Begin structures used for driver registeration ---------- */
+
+static struct tty_driver *pc_driver;
+static struct tty_driver *pc_info;
+
+/* ------------------ Begin Digi specific structures -------------------- */
+
+/* ------------------------------------------------------------------------
+ digi_channels represents an array of structures that keep track of
+ each channel of the Digi product. Information such as transmit and
+ receive pointers, termio data, and signal definitions (DTR, CTS, etc ...)
+ are stored here. This structure is NOT used to overlay the cards
+ physical channel structure.
+-------------------------------------------------------------------------- */
+
+static struct channel digi_channels[MAX_ALLOC];
+
+/* ------------------------------------------------------------------------
+ card_ptr is an array used to hold the address of the
+ first channel structure of each card. This array will hold
+ the addresses of various channels located in digi_channels.
+-------------------------------------------------------------------------- */
+static struct channel *card_ptr[MAXCARDS];
+
+static struct timer_list epca_timer;
+
+/* ---------------------- Begin function prototypes --------------------- */
+
+/* ----------------------------------------------------------------------
+ Begin generic memory functions. These functions will be alias
+ (point at) more specific functions dependent on the board being
+ configured.
+----------------------------------------------------------------------- */
+
+static inline void memwinon(struct board_info *b, unsigned int win);
+static inline void memwinoff(struct board_info *b, unsigned int win);
+static inline void globalwinon(struct channel *ch);
+static inline void rxwinon(struct channel *ch);
+static inline void txwinon(struct channel *ch);
+static inline void memoff(struct channel *ch);
+static inline void assertgwinon(struct channel *ch);
+static inline void assertmemoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for cx_like products --- */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxem_globalwinon(struct channel *ch);
+static inline void pcxem_rxwinon(struct channel *ch);
+static inline void pcxem_txwinon(struct channel *ch);
+static inline void pcxem_memoff(struct channel *ch);
+
+/* ------ Begin more 'specific' memory functions for the pcxe ------- */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxe_globalwinon(struct channel *ch);
+static inline void pcxe_rxwinon(struct channel *ch);
+static inline void pcxe_txwinon(struct channel *ch);
+static inline void pcxe_memoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for the pc64xe and pcxi ---- */
+/* Note : pc64xe and pcxi share the same windowing routines */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxi_globalwinon(struct channel *ch);
+static inline void pcxi_rxwinon(struct channel *ch);
+static inline void pcxi_txwinon(struct channel *ch);
+static inline void pcxi_memoff(struct channel *ch);
+
+/* - Begin 'specific' do nothing memory functions needed for some cards - */
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win);
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win);
+static inline void dummy_globalwinon(struct channel *ch);
+static inline void dummy_rxwinon(struct channel *ch);
+static inline void dummy_txwinon(struct channel *ch);
+static inline void dummy_memoff(struct channel *ch);
+static inline void dummy_assertgwinon(struct channel *ch);
+static inline void dummy_assertmemoff(struct channel *ch);
+
+/* ------------------- Begin declare functions ----------------------- */
+
+static inline struct channel *verifyChannel(register struct tty_struct *);
+static inline void pc_sched_event(struct channel *, int);
+static void epca_error(int, char *);
+static void pc_close(struct tty_struct *, struct file *);
+static void shutdown(struct channel *);
+static void pc_hangup(struct tty_struct *);
+static void pc_put_char(struct tty_struct *, unsigned char);
+static int pc_write_room(struct tty_struct *);
+static int pc_chars_in_buffer(struct tty_struct *);
+static void pc_flush_buffer(struct tty_struct *);
+static void pc_flush_chars(struct tty_struct *);
+static int block_til_ready(struct tty_struct *, struct file *,
+ struct channel *);
+static int pc_open(struct tty_struct *, struct file *);
+static void post_fep_init(unsigned int crd);
+static void epcapoll(unsigned long);
+static void doevent(int);
+static void fepcmd(struct channel *, int, int, int, int, int);
+static unsigned termios2digi_h(struct channel *ch, unsigned);
+static unsigned termios2digi_i(struct channel *ch, unsigned);
+static unsigned termios2digi_c(struct channel *ch, unsigned);
+static void epcaparam(struct tty_struct *, struct channel *);
+static void receive_data(struct channel *);
+static int pc_ioctl(struct tty_struct *, struct file *,
+ unsigned int, unsigned long);
+static int info_ioctl(struct tty_struct *, struct file *,
+ unsigned int, unsigned long);
+static void pc_set_termios(struct tty_struct *, struct termios *);
+static void do_softint(void *);
+static void pc_stop(struct tty_struct *);
+static void pc_start(struct tty_struct *);
+static void pc_throttle(struct tty_struct * tty);
+static void pc_unthrottle(struct tty_struct *tty);
+static void digi_send_break(struct channel *ch, int msec);
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch);
+void epca_setup(char *, int *);
+void console_print(const char *);
+
+static int get_termio(struct tty_struct *, struct termio __user *);
+static int pc_write(struct tty_struct *, const unsigned char *, int);
+int pc_init(void);
+
+#ifdef ENABLE_PCI
+static int init_PCI(void);
+#endif /* ENABLE_PCI */
+
+
+/* ------------------------------------------------------------------
+ Table of functions for each board to handle memory. Mantaining
+ parallelism is a *very* good idea here. The idea is for the
+ runtime code to blindly call these functions, not knowing/caring
+ about the underlying hardware. This stuff should contain no
+ conditionals; if more functionality is needed a different entry
+ should be established. These calls are the interface calls and
+ are the only functions that should be accessed. Anyone caught
+ making direct calls deserves what they get.
+-------------------------------------------------------------------- */
+
+static inline void memwinon(struct board_info *b, unsigned int win)
+{
+ (b->memwinon)(b, win);
+}
+
+static inline void memwinoff(struct board_info *b, unsigned int win)
+{
+ (b->memwinoff)(b, win);
+}
+
+static inline void globalwinon(struct channel *ch)
+{
+ (ch->board->globalwinon)(ch);
+}
+
+static inline void rxwinon(struct channel *ch)
+{
+ (ch->board->rxwinon)(ch);
+}
+
+static inline void txwinon(struct channel *ch)
+{
+ (ch->board->txwinon)(ch);
+}
+
+static inline void memoff(struct channel *ch)
+{
+ (ch->board->memoff)(ch);
+}
+static inline void assertgwinon(struct channel *ch)
+{
+ (ch->board->assertgwinon)(ch);
+}
+
+static inline void assertmemoff(struct channel *ch)
+{
+ (ch->board->assertmemoff)(ch);
+}
+
+/* ---------------------------------------------------------
+ PCXEM windowing is the same as that used in the PCXR
+ and CX series cards.
+------------------------------------------------------------ */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(FEPWIN|win, (int)b->port + 1);
+}
+
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxem_globalwinon(struct channel *ch)
+{
+ outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_rxwinon(struct channel *ch)
+{
+ outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_txwinon(struct channel *ch)
+{
+ outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ----------------- Begin pcxe memory window stuff ------------------ */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(FEPWIN | win, (int)b->port + 1);
+}
+
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) & ~FEPMEM,
+ (int)b->port + 1);
+ outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxe_globalwinon(struct channel *ch)
+{
+ outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_rxwinon(struct channel *ch)
+{
+ outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_txwinon(struct channel *ch)
+{
+ outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port);
+ outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ------------- Begin pc64xe and pcxi memory window stuff -------------- */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) | FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win)
+{
+ outb_p(inb((int)b->port) & ~FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_globalwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_rxwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_txwinon(struct channel *ch)
+{
+ outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_memoff(struct channel *ch)
+{
+ outb_p(0, (int)ch->board->port);
+}
+
+static inline void pcxi_assertgwinon(struct channel *ch)
+{
+ epcaassert(inb((int)ch->board->port) & FEPMEM, "Global memory off");
+}
+
+static inline void pcxi_assertmemoff(struct channel *ch)
+{
+ epcaassert(!(inb((int)ch->board->port) & FEPMEM), "Memory on");
+}
+
+
+/* ----------------------------------------------------------------------
+ Not all of the cards need specific memory windowing routines. Some
+ cards (Such as PCI) needs no windowing routines at all. We provide
+ these do nothing routines so that the same code base can be used.
+ The driver will ALWAYS call a windowing routine if it thinks it needs
+ to; regardless of the card. However, dependent on the card the routine
+ may or may not do anything.
+---------------------------------------------------------------------------*/
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_globalwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_rxwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_txwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_memoff(struct channel *ch)
+{
+}
+
+static inline void dummy_assertgwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_assertmemoff(struct channel *ch)
+{
+}
+
+/* ----------------- Begin verifyChannel function ----------------------- */
+static inline struct channel *verifyChannel(register struct tty_struct *tty)
+{ /* Begin verifyChannel */
+
+ /* --------------------------------------------------------------------
+ This routine basically provides a sanity check. It insures that
+ the channel returned is within the proper range of addresses as
+ well as properly initialized. If some bogus info gets passed in
+ through tty->driver_data this should catch it.
+ --------------------------------------------------------------------- */
+
+ if (tty)
+ { /* Begin if tty */
+
+ register struct channel *ch = (struct channel *)tty->driver_data;
+
+ if ((ch >= &digi_channels[0]) && (ch < &digi_channels[nbdevs]))
+ {
+ if (ch->magic == EPCA_MAGIC)
+ return ch;
+ }
+
+ } /* End if tty */
+
+ /* Else return a NULL for invalid */
+ return NULL;
+
+} /* End verifyChannel */
+
+/* ------------------ Begin pc_sched_event ------------------------- */
+
+static inline void pc_sched_event(struct channel *ch, int event)
+{ /* Begin pc_sched_event */
+
+
+ /* ----------------------------------------------------------------------
+ We call this to schedule interrupt processing on some event. The
+ kernel sees our request and calls the related routine in OUR driver.
+ -------------------------------------------------------------------------*/
+
+ ch->event |= 1 << event;
+ schedule_work(&ch->tqueue);
+
+
+} /* End pc_sched_event */
+
+/* ------------------ Begin epca_error ------------------------- */
+
+static void epca_error(int line, char *msg)
+{ /* Begin epca_error */
+
+ printk(KERN_ERR "epca_error (Digi): line = %d %s\n",line,msg);
+ return;
+
+} /* End epca_error */
+
+/* ------------------ Begin pc_close ------------------------- */
+static void pc_close(struct tty_struct * tty, struct file * filp)
+{ /* Begin pc_close */
+
+ struct channel *ch;
+ unsigned long flags;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if ch != NULL */
+
+ save_flags(flags);
+ cli();
+
+ if (tty_hung_up_p(filp))
+ {
+ restore_flags(flags);
+ return;
+ }
+
+ /* Check to see if the channel is open more than once */
+ if (ch->count-- > 1)
+ { /* Begin channel is open more than once */
+
+ /* -------------------------------------------------------------
+ Return without doing anything. Someone might still be using
+ the channel.
+ ---------------------------------------------------------------- */
+
+ restore_flags(flags);
+ return;
+ } /* End channel is open more than once */
+
+ /* Port open only once go ahead with shutdown & reset */
+
+ if (ch->count < 0)
+ {
+ ch->count = 0;
+ }
+
+ /* ---------------------------------------------------------------
+ Let the rest of the driver know the channel is being closed.
+ This becomes important if an open is attempted before close
+ is finished.
+ ------------------------------------------------------------------ */
+
+ ch->asyncflags |= ASYNC_CLOSING;
+
+ tty->closing = 1;
+
+ if (ch->asyncflags & ASYNC_INITIALIZED)
+ {
+ /* Setup an event to indicate when the transmit buffer empties */
+ setup_empty_event(tty, ch);
+ tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+ }
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+ shutdown(ch);
+ tty->closing = 0;
+ ch->event = 0;
+ ch->tty = NULL;
+
+ if (ch->blocked_open)
+ { /* Begin if blocked_open */
+
+ if (ch->close_delay)
+ {
+ msleep_interruptible(jiffies_to_msecs(ch->close_delay));
+ }
+
+ wake_up_interruptible(&ch->open_wait);
+
+ } /* End if blocked_open */
+
+ ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED |
+ ASYNC_CLOSING);
+ wake_up_interruptible(&ch->close_wait);
+
+
+ restore_flags(flags);
+
+ } /* End if ch != NULL */
+
+} /* End pc_close */
+
+/* ------------------ Begin shutdown ------------------------- */
+
+static void shutdown(struct channel *ch)
+{ /* Begin shutdown */
+
+ unsigned long flags;
+ struct tty_struct *tty;
+ volatile struct board_chan *bc;
+
+ if (!(ch->asyncflags & ASYNC_INITIALIZED))
+ return;
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+
+ /* ------------------------------------------------------------------
+ In order for an event to be generated on the receipt of data the
+ idata flag must be set. Since we are shutting down, this is not
+ necessary clear this flag.
+ --------------------------------------------------------------------- */
+
+ if (bc)
+ bc->idata = 0;
+
+ tty = ch->tty;
+
+ /* ----------------------------------------------------------------
+ If we're a modem control device and HUPCL is on, drop RTS & DTR.
+ ------------------------------------------------------------------ */
+
+ if (tty->termios->c_cflag & HUPCL)
+ {
+ ch->omodem &= ~(ch->m_rts | ch->m_dtr);
+ fepcmd(ch, SETMODEM, 0, ch->m_dtr | ch->m_rts, 10, 1);
+ }
+
+ memoff(ch);
+
+ /* ------------------------------------------------------------------
+ The channel has officialy been closed. The next time it is opened
+ it will have to reinitialized. Set a flag to indicate this.
+ ---------------------------------------------------------------------- */
+
+ /* Prevent future Digi programmed interrupts from coming active */
+
+ ch->asyncflags &= ~ASYNC_INITIALIZED;
+ restore_flags(flags);
+
+} /* End shutdown */
+
+/* ------------------ Begin pc_hangup ------------------------- */
+
+static void pc_hangup(struct tty_struct *tty)
+{ /* Begin pc_hangup */
+
+ struct channel *ch;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if ch != NULL */
+
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ shutdown(ch);
+
+ ch->tty = NULL;
+ ch->event = 0;
+ ch->count = 0;
+ restore_flags(flags);
+ ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED);
+ wake_up_interruptible(&ch->open_wait);
+
+ } /* End if ch != NULL */
+
+} /* End pc_hangup */
+
+/* ------------------ Begin pc_write ------------------------- */
+
+static int pc_write(struct tty_struct * tty,
+ const unsigned char *buf, int bytesAvailable)
+{ /* Begin pc_write */
+
+ register unsigned int head, tail;
+ register int dataLen;
+ register int size;
+ register int amountCopied;
+
+
+ struct channel *ch;
+ unsigned long flags;
+ int remain;
+ volatile struct board_chan *bc;
+
+
+ /* ----------------------------------------------------------------
+ pc_write is primarily called directly by the kernel routine
+ tty_write (Though it can also be called by put_char) found in
+ tty_io.c. pc_write is passed a line discipline buffer where
+ the data to be written out is stored. The line discipline
+ implementation itself is done at the kernel level and is not
+ brought into the driver.
+ ------------------------------------------------------------------- */
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) == NULL)
+ return 0;
+
+ /* Make a pointer to the channel data structure found on the board. */
+
+ bc = ch->brdchan;
+ size = ch->txbufsize;
+
+ amountCopied = 0;
+ save_flags(flags);
+ cli();
+
+ globalwinon(ch);
+
+ head = bc->tin & (size - 1);
+ tail = bc->tout;
+
+ if (tail != bc->tout)
+ tail = bc->tout;
+ tail &= (size - 1);
+
+ /* If head >= tail, head has not wrapped around. */
+ if (head >= tail)
+ { /* Begin head has not wrapped */
+
+ /* ---------------------------------------------------------------
+ remain (much like dataLen above) represents the total amount of
+ space available on the card for data. Here dataLen represents
+ the space existing between the head pointer and the end of
+ buffer. This is important because a memcpy cannot be told to
+ automatically wrap around when it hits the buffer end.
+ ------------------------------------------------------------------ */
+
+ dataLen = size - head;
+ remain = size - (head - tail) - 1;
+
+ } /* End head has not wrapped */
+ else
+ { /* Begin head has wrapped around */
+
+ remain = tail - head - 1;
+ dataLen = remain;
+
+ } /* End head has wrapped around */
+
+ /* -------------------------------------------------------------------
+ Check the space on the card. If we have more data than
+ space; reduce the amount of data to fit the space.
+ ---------------------------------------------------------------------- */
+
+ bytesAvailable = min(remain, bytesAvailable);
+
+ txwinon(ch);
+ while (bytesAvailable > 0)
+ { /* Begin while there is data to copy onto card */
+
+ /* -----------------------------------------------------------------
+ If head is not wrapped, the below will make sure the first
+ data copy fills to the end of card buffer.
+ ------------------------------------------------------------------- */
+
+ dataLen = min(bytesAvailable, dataLen);
+ memcpy(ch->txptr + head, buf, dataLen);
+ buf += dataLen;
+ head += dataLen;
+ amountCopied += dataLen;
+ bytesAvailable -= dataLen;
+
+ if (head >= size)
+ {
+ head = 0;
+ dataLen = tail;
+ }
+
+ } /* End while there is data to copy onto card */
+
+ ch->statusflags |= TXBUSY;
+ globalwinon(ch);
+ bc->tin = head;
+
+ if ((ch->statusflags & LOWWAIT) == 0)
+ {
+ ch->statusflags |= LOWWAIT;
+ bc->ilow = 1;
+ }
+ memoff(ch);
+ restore_flags(flags);
+
+ return(amountCopied);
+
+} /* End pc_write */
+
+/* ------------------ Begin pc_put_char ------------------------- */
+
+static void pc_put_char(struct tty_struct *tty, unsigned char c)
+{ /* Begin pc_put_char */
+
+
+ pc_write(tty, &c, 1);
+ return;
+
+} /* End pc_put_char */
+
+/* ------------------ Begin pc_write_room ------------------------- */
+
+static int pc_write_room(struct tty_struct *tty)
+{ /* Begin pc_write_room */
+
+ int remain;
+ struct channel *ch;
+ unsigned long flags;
+ unsigned int head, tail;
+ volatile struct board_chan *bc;
+
+ remain = 0;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ {
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+ head = bc->tin & (ch->txbufsize - 1);
+ tail = bc->tout;
+
+ if (tail != bc->tout)
+ tail = bc->tout;
+ /* Wrap tail if necessary */
+ tail &= (ch->txbufsize - 1);
+
+ if ((remain = tail - head - 1) < 0 )
+ remain += ch->txbufsize;
+
+ if (remain && (ch->statusflags & LOWWAIT) == 0)
+ {
+ ch->statusflags |= LOWWAIT;
+ bc->ilow = 1;
+ }
+ memoff(ch);
+ restore_flags(flags);
+ }
+
+ /* Return how much room is left on card */
+ return remain;
+
+} /* End pc_write_room */
+
+/* ------------------ Begin pc_chars_in_buffer ---------------------- */
+
+static int pc_chars_in_buffer(struct tty_struct *tty)
+{ /* Begin pc_chars_in_buffer */
+
+ int chars;
+ unsigned int ctail, head, tail;
+ int remain;
+ unsigned long flags;
+ struct channel *ch;
+ volatile struct board_chan *bc;
+
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) == NULL)
+ return(0);
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+ tail = bc->tout;
+ head = bc->tin;
+ ctail = ch->mailbox->cout;
+
+ if (tail == head && ch->mailbox->cin == ctail && bc->tbusy == 0)
+ chars = 0;
+ else
+ { /* Begin if some space on the card has been used */
+
+ head = bc->tin & (ch->txbufsize - 1);
+ tail &= (ch->txbufsize - 1);
+
+ /* --------------------------------------------------------------
+ The logic here is basically opposite of the above pc_write_room
+ here we are finding the amount of bytes in the buffer filled.
+ Not the amount of bytes empty.
+ ------------------------------------------------------------------- */
+
+ if ((remain = tail - head - 1) < 0 )
+ remain += ch->txbufsize;
+
+ chars = (int)(ch->txbufsize - remain);
+
+ /* -------------------------------------------------------------
+ Make it possible to wakeup anything waiting for output
+ in tty_ioctl.c, etc.
+
+ If not already set. Setup an event to indicate when the
+ transmit buffer empties
+ ----------------------------------------------------------------- */
+
+ if (!(ch->statusflags & EMPTYWAIT))
+ setup_empty_event(tty,ch);
+
+ } /* End if some space on the card has been used */
+
+ memoff(ch);
+ restore_flags(flags);
+
+ /* Return number of characters residing on card. */
+ return(chars);
+
+} /* End pc_chars_in_buffer */
+
+/* ------------------ Begin pc_flush_buffer ---------------------- */
+
+static void pc_flush_buffer(struct tty_struct *tty)
+{ /* Begin pc_flush_buffer */
+
+ unsigned int tail;
+ unsigned long flags;
+ struct channel *ch;
+ volatile struct board_chan *bc;
+
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) == NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+
+ globalwinon(ch);
+
+ bc = ch->brdchan;
+ tail = bc->tout;
+
+ /* Have FEP move tout pointer; effectively flushing transmit buffer */
+
+ fepcmd(ch, STOUT, (unsigned) tail, 0, 0, 0);
+
+ memoff(ch);
+ restore_flags(flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+
+} /* End pc_flush_buffer */
+
+/* ------------------ Begin pc_flush_chars ---------------------- */
+
+static void pc_flush_chars(struct tty_struct *tty)
+{ /* Begin pc_flush_chars */
+
+ struct channel * ch;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ {
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ /* ----------------------------------------------------------------
+ If not already set and the transmitter is busy setup an event
+ to indicate when the transmit empties.
+ ------------------------------------------------------------------- */
+
+ if ((ch->statusflags & TXBUSY) && !(ch->statusflags & EMPTYWAIT))
+ setup_empty_event(tty,ch);
+
+ restore_flags(flags);
+ }
+
+} /* End pc_flush_chars */
+
+/* ------------------ Begin block_til_ready ---------------------- */
+
+static int block_til_ready(struct tty_struct *tty,
+ struct file *filp, struct channel *ch)
+{ /* Begin block_til_ready */
+
+ DECLARE_WAITQUEUE(wait,current);
+ int retval, do_clocal = 0;
+ unsigned long flags;
+
+
+ if (tty_hung_up_p(filp))
+ {
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ return(retval);
+ }
+
+ /* -----------------------------------------------------------------
+ If the device is in the middle of being closed, then block
+ until it's done, and then try again.
+ -------------------------------------------------------------------- */
+ if (ch->asyncflags & ASYNC_CLOSING)
+ {
+ interruptible_sleep_on(&ch->close_wait);
+
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ if (filp->f_flags & O_NONBLOCK)
+ {
+ /* -----------------------------------------------------------------
+ If non-blocking mode is set, then make the check up front
+ and then exit.
+ -------------------------------------------------------------------- */
+
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+ return 0;
+ }
+
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /* Block waiting for the carrier detect and the line to become free */
+
+ retval = 0;
+ add_wait_queue(&ch->open_wait, &wait);
+ save_flags(flags);
+ cli();
+
+
+ /* We dec count so that pc_close will know when to free things */
+ if (!tty_hung_up_p(filp))
+ ch->count--;
+
+ restore_flags(flags);
+
+ ch->blocked_open++;
+
+ while(1)
+ { /* Begin forever while */
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (tty_hung_up_p(filp) ||
+ !(ch->asyncflags & ASYNC_INITIALIZED))
+ {
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ if (!(ch->asyncflags & ASYNC_CLOSING) &&
+ (do_clocal || (ch->imodem & ch->dcd)))
+ break;
+
+ if (signal_pending(current))
+ {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ /* ---------------------------------------------------------------
+ Allow someone else to be scheduled. We will occasionally go
+ through this loop until one of the above conditions change.
+ The below schedule call will allow other processes to enter and
+ prevent this loop from hogging the cpu.
+ ------------------------------------------------------------------ */
+ schedule();
+
+ } /* End forever while */
+
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&ch->open_wait, &wait);
+ cli();
+ if (!tty_hung_up_p(filp))
+ ch->count++;
+ restore_flags(flags);
+
+ ch->blocked_open--;
+
+ if (retval)
+ return retval;
+
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+ return 0;
+
+} /* End block_til_ready */
+
+/* ------------------ Begin pc_open ---------------------- */
+
+static int pc_open(struct tty_struct *tty, struct file * filp)
+{ /* Begin pc_open */
+
+ struct channel *ch;
+ unsigned long flags;
+ int line, retval, boardnum;
+ volatile struct board_chan *bc;
+ volatile unsigned int head;
+
+ line = tty->index;
+ if (line < 0 || line >= nbdevs)
+ {
+ printk(KERN_ERR "<Error> - pc_open : line out of range in pc_open\n");
+ tty->driver_data = NULL;
+ return(-ENODEV);
+ }
+
+
+ ch = &digi_channels[line];
+ boardnum = ch->boardnum;
+
+ /* Check status of board configured in system. */
+
+ /* -----------------------------------------------------------------
+ I check to see if the epca_setup routine detected an user error.
+ It might be better to put this in pc_init, but for the moment it
+ goes here.
+ ---------------------------------------------------------------------- */
+
+ if (invalid_lilo_config)
+ {
+ if (setup_error_code & INVALID_BOARD_TYPE)
+ printk(KERN_ERR "<Error> - pc_open: Invalid board type specified in LILO command\n");
+
+ if (setup_error_code & INVALID_NUM_PORTS)
+ printk(KERN_ERR "<Error> - pc_open: Invalid number of ports specified in LILO command\n");
+
+ if (setup_error_code & INVALID_MEM_BASE)
+ printk(KERN_ERR "<Error> - pc_open: Invalid board memory address specified in LILO command\n");
+
+ if (setup_error_code & INVALID_PORT_BASE)
+ printk(KERN_ERR "<Error> - pc_open: Invalid board port address specified in LILO command\n");
+
+ if (setup_error_code & INVALID_BOARD_STATUS)
+ printk(KERN_ERR "<Error> - pc_open: Invalid board status specified in LILO command\n");
+
+ if (setup_error_code & INVALID_ALTPIN)
+ printk(KERN_ERR "<Error> - pc_open: Invalid board altpin specified in LILO command\n");
+
+ tty->driver_data = NULL; /* Mark this device as 'down' */
+ return(-ENODEV);
+ }
+
+ if ((boardnum >= num_cards) || (boards[boardnum].status == DISABLED))
+ {
+ tty->driver_data = NULL; /* Mark this device as 'down' */
+ return(-ENODEV);
+ }
+
+ if (( bc = ch->brdchan) == 0)
+ {
+ tty->driver_data = NULL;
+ return(-ENODEV);
+ }
+
+ /* ------------------------------------------------------------------
+ Every time a channel is opened, increment a counter. This is
+ necessary because we do not wish to flush and shutdown the channel
+ until the last app holding the channel open, closes it.
+ --------------------------------------------------------------------- */
+
+ ch->count++;
+
+ /* ----------------------------------------------------------------
+ Set a kernel structures pointer to our local channel
+ structure. This way we can get to it when passed only
+ a tty struct.
+ ------------------------------------------------------------------ */
+
+ tty->driver_data = ch;
+
+ /* ----------------------------------------------------------------
+ If this is the first time the channel has been opened, initialize
+ the tty->termios struct otherwise let pc_close handle it.
+ -------------------------------------------------------------------- */
+
+ save_flags(flags);
+ cli();
+
+ globalwinon(ch);
+ ch->statusflags = 0;
+
+ /* Save boards current modem status */
+ ch->imodem = bc->mstat;
+
+ /* ----------------------------------------------------------------
+ Set receive head and tail ptrs to each other. This indicates
+ no data available to read.
+ ----------------------------------------------------------------- */
+ head = bc->rin;
+ bc->rout = head;
+
+ /* Set the channels associated tty structure */
+ ch->tty = tty;
+
+ /* -----------------------------------------------------------------
+ The below routine generally sets up parity, baud, flow control
+ issues, etc.... It effect both control flags and input flags.
+ -------------------------------------------------------------------- */
+ epcaparam(tty,ch);
+
+ ch->asyncflags |= ASYNC_INITIALIZED;
+ memoff(ch);
+
+ restore_flags(flags);
+
+ retval = block_til_ready(tty, filp, ch);
+ if (retval)
+ {
+ return retval;
+ }
+
+ /* -------------------------------------------------------------
+ Set this again in case a hangup set it to zero while this
+ open() was waiting for the line...
+ --------------------------------------------------------------- */
+ ch->tty = tty;
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ /* Enable Digi Data events */
+ bc->idata = 1;
+
+ memoff(ch);
+ restore_flags(flags);
+
+ return 0;
+
+} /* End pc_open */
+
+#ifdef MODULE
+static int __init epca_module_init(void)
+{ /* Begin init_module */
+
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ pc_init();
+
+ restore_flags(flags);
+
+ return(0);
+}
+
+module_init(epca_module_init);
+#endif
+
+#ifdef ENABLE_PCI
+static struct pci_driver epca_driver;
+#endif
+
+#ifdef MODULE
+/* -------------------- Begin cleanup_module ---------------------- */
+
+static void __exit epca_module_exit(void)
+{
+
+ int count, crd;
+ struct board_info *bd;
+ struct channel *ch;
+ unsigned long flags;
+
+ del_timer_sync(&epca_timer);
+
+ save_flags(flags);
+ cli();
+
+ if ((tty_unregister_driver(pc_driver)) ||
+ (tty_unregister_driver(pc_info)))
+ {
+ printk(KERN_WARNING "<Error> - DIGI : cleanup_module failed to un-register tty driver\n");
+ restore_flags(flags);
+ return;
+ }
+ put_tty_driver(pc_driver);
+ put_tty_driver(pc_info);
+
+ for (crd = 0; crd < num_cards; crd++)
+ { /* Begin for each card */
+
+ bd = &boards[crd];
+
+ if (!bd)
+ { /* Begin sanity check */
+ printk(KERN_ERR "<Error> - Digi : cleanup_module failed\n");
+ return;
+ } /* End sanity check */
+
+ ch = card_ptr[crd];
+
+ for (count = 0; count < bd->numports; count++, ch++)
+ { /* Begin for each port */
+
+ if (ch)
+ {
+ if (ch->tty)
+ tty_hangup(ch->tty);
+ kfree(ch->tmp_buf);
+ }
+
+ } /* End for each port */
+ } /* End for each card */
+
+#ifdef ENABLE_PCI
+ pci_unregister_driver (&epca_driver);
+#endif
+
+ restore_flags(flags);
+
+}
+module_exit(epca_module_exit);
+#endif /* MODULE */
+
+static struct tty_operations pc_ops = {
+ .open = pc_open,
+ .close = pc_close,
+ .write = pc_write,
+ .write_room = pc_write_room,
+ .flush_buffer = pc_flush_buffer,
+ .chars_in_buffer = pc_chars_in_buffer,
+ .flush_chars = pc_flush_chars,
+ .put_char = pc_put_char,
+ .ioctl = pc_ioctl,
+ .set_termios = pc_set_termios,
+ .stop = pc_stop,
+ .start = pc_start,
+ .throttle = pc_throttle,
+ .unthrottle = pc_unthrottle,
+ .hangup = pc_hangup,
+};
+
+static int info_open(struct tty_struct *tty, struct file * filp)
+{
+ return 0;
+}
+
+static struct tty_operations info_ops = {
+ .open = info_open,
+ .ioctl = info_ioctl,
+};
+
+/* ------------------ Begin pc_init ---------------------- */
+
+int __init pc_init(void)
+{ /* Begin pc_init */
+
+ /* ----------------------------------------------------------------
+ pc_init is called by the operating system during boot up prior to
+ any open calls being made. In the older versions of Linux (Prior
+ to 2.0.0) an entry is made into tty_io.c. A pointer to the last
+ memory location (from kernel space) used (kmem_start) is passed
+ to pc_init. It is pc_inits responsibility to modify this value
+ for any memory that the Digi driver might need and then return
+ this value to the operating system. For example if the driver
+ wishes to allocate 1K of kernel memory, pc_init would return
+ (kmem_start + 1024). This memory (Between kmem_start and kmem_start
+ + 1024) would then be available for use exclusively by the driver.
+ In this case our driver does not allocate any of this kernel
+ memory.
+ ------------------------------------------------------------------*/
+
+ ulong flags;
+ int crd;
+ struct board_info *bd;
+ unsigned char board_id = 0;
+
+#ifdef ENABLE_PCI
+ int pci_boards_found, pci_count;
+
+ pci_count = 0;
+#endif /* ENABLE_PCI */
+
+ pc_driver = alloc_tty_driver(MAX_ALLOC);
+ if (!pc_driver)
+ return -ENOMEM;
+
+ pc_info = alloc_tty_driver(MAX_ALLOC);
+ if (!pc_info) {
+ put_tty_driver(pc_driver);
+ return -ENOMEM;
+ }
+
+ /* -----------------------------------------------------------------------
+ If epca_setup has not been ran by LILO set num_cards to defaults; copy
+ board structure defined by digiConfig into drivers board structure.
+ Note : If LILO has ran epca_setup then epca_setup will handle defining
+ num_cards as well as copying the data into the board structure.
+ -------------------------------------------------------------------------- */
+ if (!liloconfig)
+ { /* Begin driver has been configured via. epcaconfig */
+
+ nbdevs = NBDEVS;
+ num_cards = NUMCARDS;
+ memcpy((void *)&boards, (void *)&static_boards,
+ (sizeof(struct board_info) * NUMCARDS));
+ } /* End driver has been configured via. epcaconfig */
+
+ /* -----------------------------------------------------------------
+ Note : If lilo was used to configure the driver and the
+ ignore epcaconfig option was choosen (digiepca=2) then
+ nbdevs and num_cards will equal 0 at this point. This is
+ okay; PCI cards will still be picked up if detected.
+ --------------------------------------------------------------------- */
+
+ /* -----------------------------------------------------------
+ Set up interrupt, we will worry about memory allocation in
+ post_fep_init.
+ --------------------------------------------------------------- */
+
+
+ printk(KERN_INFO "DIGI epca driver version %s loaded.\n",VERSION);
+
+#ifdef ENABLE_PCI
+
+ /* ------------------------------------------------------------------
+ NOTE : This code assumes that the number of ports found in
+ the boards array is correct. This could be wrong if
+ the card in question is PCI (And therefore has no ports
+ entry in the boards structure.) The rest of the
+ information will be valid for PCI because the beginning
+ of pc_init scans for PCI and determines i/o and base
+ memory addresses. I am not sure if it is possible to
+ read the number of ports supported by the card prior to
+ it being booted (Since that is the state it is in when
+ pc_init is run). Because it is not possible to query the
+ number of supported ports until after the card has booted;
+ we are required to calculate the card_ptrs as the card is
+ is initialized (Inside post_fep_init). The negative thing
+ about this approach is that digiDload's call to GET_INFO
+ will have a bad port value. (Since this is called prior
+ to post_fep_init.)
+
+ --------------------------------------------------------------------- */
+
+ pci_boards_found = 0;
+ if(num_cards < MAXBOARDS)
+ pci_boards_found += init_PCI();
+ num_cards += pci_boards_found;
+
+#endif /* ENABLE_PCI */
+
+ pc_driver->owner = THIS_MODULE;
+ pc_driver->name = "ttyD";
+ pc_driver->devfs_name = "tts/D";
+ pc_driver->major = DIGI_MAJOR;
+ pc_driver->minor_start = 0;
+ pc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ pc_driver->subtype = SERIAL_TYPE_NORMAL;
+ pc_driver->init_termios = tty_std_termios;
+ pc_driver->init_termios.c_iflag = 0;
+ pc_driver->init_termios.c_oflag = 0;
+ pc_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+ pc_driver->init_termios.c_lflag = 0;
+ pc_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(pc_driver, &pc_ops);
+
+ pc_info->owner = THIS_MODULE;
+ pc_info->name = "digi_ctl";
+ pc_info->major = DIGIINFOMAJOR;
+ pc_info->minor_start = 0;
+ pc_info->type = TTY_DRIVER_TYPE_SERIAL;
+ pc_info->subtype = SERIAL_TYPE_INFO;
+ pc_info->init_termios = tty_std_termios;
+ pc_info->init_termios.c_iflag = 0;
+ pc_info->init_termios.c_oflag = 0;
+ pc_info->init_termios.c_lflag = 0;
+ pc_info->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+ pc_info->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(pc_info, &info_ops);
+
+
+ save_flags(flags);
+ cli();
+
+ for (crd = 0; crd < num_cards; crd++)
+ { /* Begin for each card */
+
+ /* ------------------------------------------------------------------
+ This is where the appropriate memory handlers for the hardware is
+ set. Everything at runtime blindly jumps through these vectors.
+ ---------------------------------------------------------------------- */
+
+ /* defined in epcaconfig.h */
+ bd = &boards[crd];
+
+ switch (bd->type)
+ { /* Begin switch on bd->type {board type} */
+ case PCXEM:
+ case EISAXEM:
+ bd->memwinon = pcxem_memwinon ;
+ bd->memwinoff = pcxem_memwinoff ;
+ bd->globalwinon = pcxem_globalwinon ;
+ bd->txwinon = pcxem_txwinon ;
+ bd->rxwinon = pcxem_rxwinon ;
+ bd->memoff = pcxem_memoff ;
+ bd->assertgwinon = dummy_assertgwinon;
+ bd->assertmemoff = dummy_assertmemoff;
+ break;
+
+ case PCIXEM:
+ case PCIXRJ:
+ case PCIXR:
+ bd->memwinon = dummy_memwinon;
+ bd->memwinoff = dummy_memwinoff;
+ bd->globalwinon = dummy_globalwinon;
+ bd->txwinon = dummy_txwinon;
+ bd->rxwinon = dummy_rxwinon;
+ bd->memoff = dummy_memoff;
+ bd->assertgwinon = dummy_assertgwinon;
+ bd->assertmemoff = dummy_assertmemoff;
+ break;
+
+ case PCXE:
+ case PCXEVE:
+
+ bd->memwinon = pcxe_memwinon;
+ bd->memwinoff = pcxe_memwinoff;
+ bd->globalwinon = pcxe_globalwinon;
+ bd->txwinon = pcxe_txwinon;
+ bd->rxwinon = pcxe_rxwinon;
+ bd->memoff = pcxe_memoff;
+ bd->assertgwinon = dummy_assertgwinon;
+ bd->assertmemoff = dummy_assertmemoff;
+ break;
+
+ case PCXI:
+ case PC64XE:
+
+ bd->memwinon = pcxi_memwinon;
+ bd->memwinoff = pcxi_memwinoff;
+ bd->globalwinon = pcxi_globalwinon;
+ bd->txwinon = pcxi_txwinon;
+ bd->rxwinon = pcxi_rxwinon;
+ bd->memoff = pcxi_memoff;
+ bd->assertgwinon = pcxi_assertgwinon;
+ bd->assertmemoff = pcxi_assertmemoff;
+ break;
+
+ default:
+ break;
+
+ } /* End switch on bd->type */
+
+ /* ---------------------------------------------------------------
+ Some cards need a memory segment to be defined for use in
+ transmit and receive windowing operations. These boards
+ are listed in the below switch. In the case of the XI the
+ amount of memory on the board is variable so the memory_seg
+ is also variable. This code determines what they segment
+ should be.
+ ----------------------------------------------------------------- */
+
+ switch (bd->type)
+ { /* Begin switch on bd->type {board type} */
+
+ case PCXE:
+ case PCXEVE:
+ case PC64XE:
+ bd->memory_seg = 0xf000;
+ break;
+
+ case PCXI:
+ board_id = inb((int)bd->port);
+ if ((board_id & 0x1) == 0x1)
+ { /* Begin it's an XI card */
+
+ /* Is it a 64K board */
+ if ((board_id & 0x30) == 0)
+ bd->memory_seg = 0xf000;
+
+ /* Is it a 128K board */
+ if ((board_id & 0x30) == 0x10)
+ bd->memory_seg = 0xe000;
+
+ /* Is is a 256K board */
+ if ((board_id & 0x30) == 0x20)
+ bd->memory_seg = 0xc000;
+
+ /* Is it a 512K board */
+ if ((board_id & 0x30) == 0x30)
+ bd->memory_seg = 0x8000;
+
+ } /* End it is an XI card */
+ else
+ {
+ printk(KERN_ERR "<Error> - Board at 0x%x doesn't appear to be an XI\n",(int)bd->port);
+ }
+ break;
+
+ } /* End switch on bd->type */
+
+ } /* End for each card */
+
+ if (tty_register_driver(pc_driver))
+ panic("Couldn't register Digi PC/ driver");
+
+ if (tty_register_driver(pc_info))
+ panic("Couldn't register Digi PC/ info ");
+
+ /* -------------------------------------------------------------------
+ Start up the poller to check for events on all enabled boards
+ ---------------------------------------------------------------------- */
+
+ init_timer(&epca_timer);
+ epca_timer.function = epcapoll;
+ mod_timer(&epca_timer, jiffies + HZ/25);
+
+ restore_flags(flags);
+
+ return 0;
+
+} /* End pc_init */
+
+/* ------------------ Begin post_fep_init ---------------------- */
+
+static void post_fep_init(unsigned int crd)
+{ /* Begin post_fep_init */
+
+ int i;
+ unchar *memaddr;
+ volatile struct global_data *gd;
+ struct board_info *bd;
+ volatile struct board_chan *bc;
+ struct channel *ch;
+ int shrinkmem = 0, lowwater ;
+
+ /* -------------------------------------------------------------
+ This call is made by the user via. the ioctl call DIGI_INIT.
+ It is responsible for setting up all the card specific stuff.
+ ---------------------------------------------------------------- */
+ bd = &boards[crd];
+
+ /* -----------------------------------------------------------------
+ If this is a PCI board, get the port info. Remember PCI cards
+ do not have entries into the epcaconfig.h file, so we can't get
+ the number of ports from it. Unfortunetly, this means that anyone
+ doing a DIGI_GETINFO before the board has booted will get an invalid
+ number of ports returned (It should return 0). Calls to DIGI_GETINFO
+ after DIGI_INIT has been called will return the proper values.
+ ------------------------------------------------------------------- */
+
+ if (bd->type >= PCIXEM) /* If the board in question is PCI */
+ { /* Begin get PCI number of ports */
+
+ /* --------------------------------------------------------------------
+ Below we use XEMPORTS as a memory offset regardless of which PCI
+ card it is. This is because all of the supported PCI cards have
+ the same memory offset for the channel data. This will have to be
+ changed if we ever develop a PCI/XE card. NOTE : The FEP manual
+ states that the port offset is 0xC22 as opposed to 0xC02. This is
+ only true for PC/XE, and PC/XI cards; not for the XEM, or CX series.
+ On the PCI cards the number of ports is determined by reading a
+ ID PROM located in the box attached to the card. The card can then
+ determine the index the id to determine the number of ports available.
+ (FYI - The id should be located at 0x1ac (And may use up to 4 bytes
+ if the box in question is a XEM or CX)).
+ ------------------------------------------------------------------------ */
+
+ bd->numports = (unsigned short)*(unsigned char *)bus_to_virt((unsigned long)
+ (bd->re_map_membase + XEMPORTS));
+
+
+ epcaassert(bd->numports <= 64,"PCI returned a invalid number of ports");
+ nbdevs += (bd->numports);
+
+ } /* End get PCI number of ports */
+
+ if (crd != 0)
+ card_ptr[crd] = card_ptr[crd-1] + boards[crd-1].numports;
+ else
+ card_ptr[crd] = &digi_channels[crd]; /* <- For card 0 only */
+
+ ch = card_ptr[crd];
+
+
+ epcaassert(ch <= &digi_channels[nbdevs - 1], "ch out of range");
+
+ memaddr = (unchar *)bd->re_map_membase;
+
+ /*
+ The below command is necessary because newer kernels (2.1.x and
+ up) do not have a 1:1 virtual to physical mapping. The below
+ call adjust for that.
+ */
+
+ memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+ /* -----------------------------------------------------------------
+ The below assignment will set bc to point at the BEGINING of
+ the cards channel structures. For 1 card there will be between
+ 8 and 64 of these structures.
+ -------------------------------------------------------------------- */
+
+ bc = (volatile struct board_chan *)((ulong)memaddr + CHANSTRUCT);
+
+ /* -------------------------------------------------------------------
+ The below assignment will set gd to point at the BEGINING of
+ global memory address 0xc00. The first data in that global
+ memory actually starts at address 0xc1a. The command in
+ pointer begins at 0xd10.
+ ---------------------------------------------------------------------- */
+
+ gd = (volatile struct global_data *)((ulong)memaddr + GLOBAL);
+
+ /* --------------------------------------------------------------------
+ XEPORTS (address 0xc22) points at the number of channels the
+ card supports. (For 64XE, XI, XEM, and XR use 0xc02)
+ ----------------------------------------------------------------------- */
+
+ if (((bd->type == PCXEVE) | (bd->type == PCXE)) &&
+ (*(ushort *)((ulong)memaddr + XEPORTS) < 3))
+ shrinkmem = 1;
+ if (bd->type < PCIXEM)
+ if (!request_region((int)bd->port, 4, board_desc[bd->type]))
+ return;
+
+ memwinon(bd, 0);
+
+ /* --------------------------------------------------------------------
+ Remember ch is the main drivers channels structure, while bc is
+ the cards channel structure.
+ ------------------------------------------------------------------------ */
+
+ /* For every port on the card do ..... */
+
+ for (i = 0; i < bd->numports; i++, ch++, bc++)
+ { /* Begin for each port */
+
+ ch->brdchan = bc;
+ ch->mailbox = gd;
+ INIT_WORK(&ch->tqueue, do_softint, ch);
+ ch->board = &boards[crd];
+
+ switch (bd->type)
+ { /* Begin switch bd->type */
+
+ /* ----------------------------------------------------------------
+ Since some of the boards use different bitmaps for their
+ control signals we cannot hard code these values and retain
+ portability. We virtualize this data here.
+ ------------------------------------------------------------------- */
+ case EISAXEM:
+ case PCXEM:
+ case PCIXEM:
+ case PCIXRJ:
+ case PCIXR:
+ ch->m_rts = 0x02 ;
+ ch->m_dcd = 0x80 ;
+ ch->m_dsr = 0x20 ;
+ ch->m_cts = 0x10 ;
+ ch->m_ri = 0x40 ;
+ ch->m_dtr = 0x01 ;
+ break;
+
+ case PCXE:
+ case PCXEVE:
+ case PCXI:
+ case PC64XE:
+ ch->m_rts = 0x02 ;
+ ch->m_dcd = 0x08 ;
+ ch->m_dsr = 0x10 ;
+ ch->m_cts = 0x20 ;
+ ch->m_ri = 0x40 ;
+ ch->m_dtr = 0x80 ;
+ break;
+
+ } /* End switch bd->type */
+
+ if (boards[crd].altpin)
+ {
+ ch->dsr = ch->m_dcd;
+ ch->dcd = ch->m_dsr;
+ ch->digiext.digi_flags |= DIGI_ALTPIN;
+ }
+ else
+ {
+ ch->dcd = ch->m_dcd;
+ ch->dsr = ch->m_dsr;
+ }
+
+ ch->boardnum = crd;
+ ch->channelnum = i;
+ ch->magic = EPCA_MAGIC;
+ ch->tty = NULL;
+
+ if (shrinkmem)
+ {
+ fepcmd(ch, SETBUFFER, 32, 0, 0, 0);
+ shrinkmem = 0;
+ }
+
+ switch (bd->type)
+ { /* Begin switch bd->type */
+
+ case PCIXEM:
+ case PCIXRJ:
+ case PCIXR:
+ /* Cover all the 2MEG cards */
+ ch->txptr = memaddr + (((bc->tseg) << 4) & 0x1fffff);
+ ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x1fffff);
+ ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+ ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+ break;
+
+ case PCXEM:
+ case EISAXEM:
+ /* Cover all the 32K windowed cards */
+ /* Mask equal to window size - 1 */
+ ch->txptr = memaddr + (((bc->tseg) << 4) & 0x7fff);
+ ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x7fff);
+ ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+ ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+ break;
+
+ case PCXEVE:
+ case PCXE:
+ ch->txptr = memaddr + (((bc->tseg - bd->memory_seg) << 4) & 0x1fff);
+ ch->txwin = FEPWIN | ((bc->tseg - bd->memory_seg) >> 9);
+ ch->rxptr = memaddr + (((bc->rseg - bd->memory_seg) << 4) & 0x1fff);
+ ch->rxwin = FEPWIN | ((bc->rseg - bd->memory_seg) >>9 );
+ break;
+
+ case PCXI:
+ case PC64XE:
+ ch->txptr = memaddr + ((bc->tseg - bd->memory_seg) << 4);
+ ch->rxptr = memaddr + ((bc->rseg - bd->memory_seg) << 4);
+ ch->txwin = ch->rxwin = 0;
+ break;
+
+ } /* End switch bd->type */
+
+ ch->txbufhead = 0;
+ ch->txbufsize = bc->tmax + 1;
+
+ ch->rxbufhead = 0;
+ ch->rxbufsize = bc->rmax + 1;
+
+ lowwater = ch->txbufsize >= 2000 ? 1024 : (ch->txbufsize / 2);
+
+ /* Set transmitter low water mark */
+ fepcmd(ch, STXLWATER, lowwater, 0, 10, 0);
+
+ /* Set receiver low water mark */
+
+ fepcmd(ch, SRXLWATER, (ch->rxbufsize / 4), 0, 10, 0);
+
+ /* Set receiver high water mark */
+
+ fepcmd(ch, SRXHWATER, (3 * ch->rxbufsize / 4), 0, 10, 0);
+
+ bc->edelay = 100;
+ bc->idata = 1;
+
+ ch->startc = bc->startc;
+ ch->stopc = bc->stopc;
+ ch->startca = bc->startca;
+ ch->stopca = bc->stopca;
+
+ ch->fepcflag = 0;
+ ch->fepiflag = 0;
+ ch->fepoflag = 0;
+ ch->fepstartc = 0;
+ ch->fepstopc = 0;
+ ch->fepstartca = 0;
+ ch->fepstopca = 0;
+
+ ch->close_delay = 50;
+ ch->count = 0;
+ ch->blocked_open = 0;
+ init_waitqueue_head(&ch->open_wait);
+ init_waitqueue_head(&ch->close_wait);
+ ch->tmp_buf = kmalloc(ch->txbufsize,GFP_KERNEL);
+ if (!(ch->tmp_buf))
+ {
+ printk(KERN_ERR "POST FEP INIT : kmalloc failed for port 0x%x\n",i);
+ release_region((int)bd->port, 4);
+ while(i-- > 0)
+ kfree((ch--)->tmp_buf);
+ return;
+ }
+ else
+ memset((void *)ch->tmp_buf,0,ch->txbufsize);
+ } /* End for each port */
+
+ printk(KERN_INFO
+ "Digi PC/Xx Driver V%s: %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n",
+ VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+ sprintf(mesg,
+ "Digi PC/Xx Driver V%s: %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n",
+ VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+ console_print(mesg);
+
+ memwinoff(bd, 0);
+
+} /* End post_fep_init */
+
+/* --------------------- Begin epcapoll ------------------------ */
+
+static void epcapoll(unsigned long ignored)
+{ /* Begin epcapoll */
+
+ unsigned long flags;
+ int crd;
+ volatile unsigned int head, tail;
+ struct channel *ch;
+ struct board_info *bd;
+
+ /* -------------------------------------------------------------------
+ This routine is called upon every timer interrupt. Even though
+ the Digi series cards are capable of generating interrupts this
+ method of non-looping polling is more efficient. This routine
+ checks for card generated events (Such as receive data, are transmit
+ buffer empty) and acts on those events.
+ ----------------------------------------------------------------------- */
+
+ save_flags(flags);
+ cli();
+
+ for (crd = 0; crd < num_cards; crd++)
+ { /* Begin for each card */
+
+ bd = &boards[crd];
+ ch = card_ptr[crd];
+
+ if ((bd->status == DISABLED) || digi_poller_inhibited)
+ continue; /* Begin loop next interation */
+
+ /* -----------------------------------------------------------
+ assertmemoff is not needed here; indeed it is an empty subroutine.
+ It is being kept because future boards may need this as well as
+ some legacy boards.
+ ---------------------------------------------------------------- */
+
+ assertmemoff(ch);
+
+ globalwinon(ch);
+
+ /* ---------------------------------------------------------------
+ In this case head and tail actually refer to the event queue not
+ the transmit or receive queue.
+ ------------------------------------------------------------------- */
+
+ head = ch->mailbox->ein;
+ tail = ch->mailbox->eout;
+
+ /* If head isn't equal to tail we have an event */
+
+ if (head != tail)
+ doevent(crd);
+
+ memoff(ch);
+
+ } /* End for each card */
+
+ mod_timer(&epca_timer, jiffies + (HZ / 25));
+
+ restore_flags(flags);
+} /* End epcapoll */
+
+/* --------------------- Begin doevent ------------------------ */
+
+static void doevent(int crd)
+{ /* Begin doevent */
+
+ volatile unchar *eventbuf;
+ struct channel *ch, *chan0;
+ static struct tty_struct *tty;
+ volatile struct board_info *bd;
+ volatile struct board_chan *bc;
+ register volatile unsigned int tail, head;
+ register int event, channel;
+ register int mstat, lstat;
+
+ /* -------------------------------------------------------------------
+ This subroutine is called by epcapoll when an event is detected
+ in the event queue. This routine responds to those events.
+ --------------------------------------------------------------------- */
+
+ bd = &boards[crd];
+
+ chan0 = card_ptr[crd];
+ epcaassert(chan0 <= &digi_channels[nbdevs - 1], "ch out of range");
+
+ assertgwinon(chan0);
+
+ while ((tail = chan0->mailbox->eout) != (head = chan0->mailbox->ein))
+ { /* Begin while something in event queue */
+
+ assertgwinon(chan0);
+
+ eventbuf = (volatile unchar *)bus_to_virt((ulong)(bd->re_map_membase + tail + ISTART));
+
+ /* Get the channel the event occurred on */
+ channel = eventbuf[0];
+
+ /* Get the actual event code that occurred */
+ event = eventbuf[1];
+
+ /* ----------------------------------------------------------------
+ The two assignments below get the current modem status (mstat)
+ and the previous modem status (lstat). These are useful becuase
+ an event could signal a change in modem signals itself.
+ ------------------------------------------------------------------- */
+
+ mstat = eventbuf[2];
+ lstat = eventbuf[3];
+
+ ch = chan0 + channel;
+
+ if ((unsigned)channel >= bd->numports || !ch)
+ {
+ if (channel >= bd->numports)
+ ch = chan0;
+ bc = ch->brdchan;
+ goto next;
+ }
+
+ if ((bc = ch->brdchan) == NULL)
+ goto next;
+
+ if (event & DATA_IND)
+ { /* Begin DATA_IND */
+
+ receive_data(ch);
+ assertgwinon(ch);
+
+ } /* End DATA_IND */
+ /* else *//* Fix for DCD transition missed bug */
+ if (event & MODEMCHG_IND)
+ { /* Begin MODEMCHG_IND */
+
+ /* A modem signal change has been indicated */
+
+ ch->imodem = mstat;
+
+ if (ch->asyncflags & ASYNC_CHECK_CD)
+ {
+ if (mstat & ch->dcd) /* We are now receiving dcd */
+ wake_up_interruptible(&ch->open_wait);
+ else
+ pc_sched_event(ch, EPCA_EVENT_HANGUP); /* No dcd; hangup */
+ }
+
+ } /* End MODEMCHG_IND */
+
+ tty = ch->tty;
+ if (tty)
+ { /* Begin if valid tty */
+
+ if (event & BREAK_IND)
+ { /* Begin if BREAK_IND */
+
+ /* A break has been indicated */
+
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+
+ *tty->flip.char_buf_ptr++ = 0;
+
+ tty_schedule_flip(tty);
+
+ } /* End if BREAK_IND */
+ else
+ if (event & LOWTX_IND)
+ { /* Begin LOWTX_IND */
+
+ if (ch->statusflags & LOWWAIT)
+ { /* Begin if LOWWAIT */
+
+ ch->statusflags &= ~LOWWAIT;
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+
+ } /* End if LOWWAIT */
+
+ } /* End LOWTX_IND */
+ else
+ if (event & EMPTYTX_IND)
+ { /* Begin EMPTYTX_IND */
+
+ /* This event is generated by setup_empty_event */
+
+ ch->statusflags &= ~TXBUSY;
+ if (ch->statusflags & EMPTYWAIT)
+ { /* Begin if EMPTYWAIT */
+
+ ch->statusflags &= ~EMPTYWAIT;
+ tty_wakeup(tty);
+
+ wake_up_interruptible(&tty->write_wait);
+
+ } /* End if EMPTYWAIT */
+
+ } /* End EMPTYTX_IND */
+
+ } /* End if valid tty */
+
+
+ next:
+ globalwinon(ch);
+
+ if (!bc)
+ printk(KERN_ERR "<Error> - bc == NULL in doevent!\n");
+ else
+ bc->idata = 1;
+
+ chan0->mailbox->eout = (tail + 4) & (IMAX - ISTART - 4);
+ globalwinon(chan0);
+
+ } /* End while something in event queue */
+
+} /* End doevent */
+
+/* --------------------- Begin fepcmd ------------------------ */
+
+static void fepcmd(struct channel *ch, int cmd, int word_or_byte,
+ int byte2, int ncmds, int bytecmd)
+{ /* Begin fepcmd */
+
+ unchar *memaddr;
+ unsigned int head, cmdTail, cmdStart, cmdMax;
+ long count;
+ int n;
+
+ /* This is the routine in which commands may be passed to the card. */
+
+ if (ch->board->status == DISABLED)
+ {
+ return;
+ }
+
+ assertgwinon(ch);
+
+ /* Remember head (As well as max) is just an offset not a base addr */
+ head = ch->mailbox->cin;
+
+ /* cmdStart is a base address */
+ cmdStart = ch->mailbox->cstart;
+
+ /* ------------------------------------------------------------------
+ We do the addition below because we do not want a max pointer
+ relative to cmdStart. We want a max pointer that points at the
+ physical end of the command queue.
+ -------------------------------------------------------------------- */
+
+ cmdMax = (cmdStart + 4 + (ch->mailbox->cmax));
+
+ memaddr = ch->board->re_map_membase;
+
+ /*
+ The below command is necessary because newer kernels (2.1.x and
+ up) do not have a 1:1 virtual to physical mapping. The below
+ call adjust for that.
+ */
+
+ memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+ if (head >= (cmdMax - cmdStart) || (head & 03))
+ {
+ printk(KERN_ERR "line %d: Out of range, cmd = %x, head = %x\n", __LINE__,
+ cmd, head);
+ printk(KERN_ERR "line %d: Out of range, cmdMax = %x, cmdStart = %x\n", __LINE__,
+ cmdMax, cmdStart);
+ return;
+ }
+
+ if (bytecmd)
+ {
+ *(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+
+ *(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+ /* Below word_or_byte is bits to set */
+ *(volatile unchar *)(memaddr + head + cmdStart + 2) = (unchar)word_or_byte;
+ /* Below byte2 is bits to reset */
+ *(volatile unchar *)(memaddr + head + cmdStart + 3) = (unchar)byte2;
+
+ }
+ else
+ {
+ *(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+ *(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+ *(volatile ushort*)(memaddr + head + cmdStart + 2) = (ushort)word_or_byte;
+ }
+
+ head = (head + 4) & (cmdMax - cmdStart - 4);
+ ch->mailbox->cin = head;
+
+ count = FEPTIMEOUT;
+
+ for (;;)
+ { /* Begin forever loop */
+
+ count--;
+ if (count == 0)
+ {
+ printk(KERN_ERR "<Error> - Fep not responding in fepcmd()\n");
+ return;
+ }
+
+ head = ch->mailbox->cin;
+ cmdTail = ch->mailbox->cout;
+
+ n = (head - cmdTail) & (cmdMax - cmdStart - 4);
+
+ /* ----------------------------------------------------------
+ Basically this will break when the FEP acknowledges the
+ command by incrementing cmdTail (Making it equal to head).
+ ------------------------------------------------------------- */
+
+ if (n <= ncmds * (sizeof(short) * 4))
+ break; /* Well nearly forever :-) */
+
+ } /* End forever loop */
+
+} /* End fepcmd */
+
+/* ---------------------------------------------------------------------
+ Digi products use fields in their channels structures that are very
+ similar to the c_cflag and c_iflag fields typically found in UNIX
+ termios structures. The below three routines allow mappings
+ between these hardware "flags" and their respective Linux flags.
+------------------------------------------------------------------------- */
+
+/* --------------------- Begin termios2digi_h -------------------- */
+
+static unsigned termios2digi_h(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_h */
+
+ unsigned res = 0;
+
+ if (cflag & CRTSCTS)
+ {
+ ch->digiext.digi_flags |= (RTSPACE | CTSPACE);
+ res |= ((ch->m_cts) | (ch->m_rts));
+ }
+
+ if (ch->digiext.digi_flags & RTSPACE)
+ res |= ch->m_rts;
+
+ if (ch->digiext.digi_flags & DTRPACE)
+ res |= ch->m_dtr;
+
+ if (ch->digiext.digi_flags & CTSPACE)
+ res |= ch->m_cts;
+
+ if (ch->digiext.digi_flags & DSRPACE)
+ res |= ch->dsr;
+
+ if (ch->digiext.digi_flags & DCDPACE)
+ res |= ch->dcd;
+
+ if (res & (ch->m_rts))
+ ch->digiext.digi_flags |= RTSPACE;
+
+ if (res & (ch->m_cts))
+ ch->digiext.digi_flags |= CTSPACE;
+
+ return res;
+
+} /* End termios2digi_h */
+
+/* --------------------- Begin termios2digi_i -------------------- */
+static unsigned termios2digi_i(struct channel *ch, unsigned iflag)
+{ /* Begin termios2digi_i */
+
+ unsigned res = iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK |
+ INPCK | ISTRIP|IXON|IXANY|IXOFF);
+
+ if (ch->digiext.digi_flags & DIGI_AIXON)
+ res |= IAIXON;
+ return res;
+
+} /* End termios2digi_i */
+
+/* --------------------- Begin termios2digi_c -------------------- */
+
+static unsigned termios2digi_c(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_c */
+
+ unsigned res = 0;
+
+#ifdef SPEED_HACK
+ /* CL: HACK to force 115200 at 38400 and 57600 at 19200 Baud */
+ if ((cflag & CBAUD)== B38400) cflag=cflag - B38400 + B115200;
+ if ((cflag & CBAUD)== B19200) cflag=cflag - B19200 + B57600;
+#endif /* SPEED_HACK */
+
+ if (cflag & CBAUDEX)
+ { /* Begin detected CBAUDEX */
+
+ ch->digiext.digi_flags |= DIGI_FAST;
+
+ /* -------------------------------------------------------------
+ HUPCL bit is used by FEP to indicate fast baud
+ table is to be used.
+ ----------------------------------------------------------------- */
+
+ res |= FEP_HUPCL;
+
+ } /* End detected CBAUDEX */
+ else ch->digiext.digi_flags &= ~DIGI_FAST;
+
+ /* -------------------------------------------------------------------
+ CBAUD has bit position 0x1000 set these days to indicate Linux
+ baud rate remap. Digi hardware can't handle the bit assignment.
+ (We use a different bit assignment for high speed.). Clear this
+ bit out.
+ ---------------------------------------------------------------------- */
+ res |= cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB | CSTOPB | CSIZE);
+
+ /* -------------------------------------------------------------
+ This gets a little confusing. The Digi cards have their own
+ representation of c_cflags controling baud rate. For the most
+ part this is identical to the Linux implementation. However;
+ Digi supports one rate (76800) that Linux doesn't. This means
+ that the c_cflag entry that would normally mean 76800 for Digi
+ actually means 115200 under Linux. Without the below mapping,
+ a stty 115200 would only drive the board at 76800. Since
+ the rate 230400 is also found after 76800, the same problem afflicts
+ us when we choose a rate of 230400. Without the below modificiation
+ stty 230400 would actually give us 115200.
+
+ There are two additional differences. The Linux value for CLOCAL
+ (0x800; 0004000) has no meaning to the Digi hardware. Also in
+ later releases of Linux; the CBAUD define has CBAUDEX (0x1000;
+ 0010000) ored into it (CBAUD = 0x100f as opposed to 0xf). CBAUDEX
+ should be checked for a screened out prior to termios2digi_c
+ returning. Since CLOCAL isn't used by the board this can be
+ ignored as long as the returned value is used only by Digi hardware.
+ ----------------------------------------------------------------- */
+
+ if (cflag & CBAUDEX)
+ {
+ /* -------------------------------------------------------------
+ The below code is trying to guarantee that only baud rates
+ 115200 and 230400 are remapped. We use exclusive or because
+ the various baud rates share common bit positions and therefore
+ can't be tested for easily.
+ ----------------------------------------------------------------- */
+
+
+ if ((!((cflag & 0x7) ^ (B115200 & ~CBAUDEX))) ||
+ (!((cflag & 0x7) ^ (B230400 & ~CBAUDEX))))
+ {
+ res += 1;
+ }
+ }
+
+ return res;
+
+} /* End termios2digi_c */
+
+/* --------------------- Begin epcaparam ----------------------- */
+
+static void epcaparam(struct tty_struct *tty, struct channel *ch)
+{ /* Begin epcaparam */
+
+ unsigned int cmdHead;
+ struct termios *ts;
+ volatile struct board_chan *bc;
+ unsigned mval, hflow, cflag, iflag;
+
+ bc = ch->brdchan;
+ epcaassert(bc !=0, "bc out of range");
+
+ assertgwinon(ch);
+
+ ts = tty->termios;
+
+ if ((ts->c_cflag & CBAUD) == 0)
+ { /* Begin CBAUD detected */
+
+ cmdHead = bc->rin;
+ bc->rout = cmdHead;
+ cmdHead = bc->tin;
+
+ /* Changing baud in mid-stream transmission can be wonderful */
+ /* ---------------------------------------------------------------
+ Flush current transmit buffer by setting cmdTail pointer (tout)
+ to cmdHead pointer (tin). Hopefully the transmit buffer is empty.
+ ----------------------------------------------------------------- */
+
+ fepcmd(ch, STOUT, (unsigned) cmdHead, 0, 0, 0);
+ mval = 0;
+
+ } /* End CBAUD detected */
+ else
+ { /* Begin CBAUD not detected */
+
+ /* -------------------------------------------------------------------
+ c_cflags have changed but that change had nothing to do with BAUD.
+ Propagate the change to the card.
+ ---------------------------------------------------------------------- */
+
+ cflag = termios2digi_c(ch, ts->c_cflag);
+
+ if (cflag != ch->fepcflag)
+ {
+ ch->fepcflag = cflag;
+ /* Set baud rate, char size, stop bits, parity */
+ fepcmd(ch, SETCTRLFLAGS, (unsigned) cflag, 0, 0, 0);
+ }
+
+
+ /* ----------------------------------------------------------------
+ If the user has not forced CLOCAL and if the device is not a
+ CALLOUT device (Which is always CLOCAL) we set flags such that
+ the driver will wait on carrier detect.
+ ------------------------------------------------------------------- */
+
+ if (ts->c_cflag & CLOCAL)
+ { /* Begin it is a cud device or a ttyD device with CLOCAL on */
+ ch->asyncflags &= ~ASYNC_CHECK_CD;
+ } /* End it is a cud device or a ttyD device with CLOCAL on */
+ else
+ { /* Begin it is a ttyD device */
+ ch->asyncflags |= ASYNC_CHECK_CD;
+ } /* End it is a ttyD device */
+
+ mval = ch->m_dtr | ch->m_rts;
+
+ } /* End CBAUD not detected */
+
+ iflag = termios2digi_i(ch, ts->c_iflag);
+
+ /* Check input mode flags */
+
+ if (iflag != ch->fepiflag)
+ {
+ ch->fepiflag = iflag;
+
+ /* ---------------------------------------------------------------
+ Command sets channels iflag structure on the board. Such things
+ as input soft flow control, handling of parity errors, and
+ break handling are all set here.
+ ------------------------------------------------------------------- */
+
+ /* break handling, parity handling, input stripping, flow control chars */
+ fepcmd(ch, SETIFLAGS, (unsigned int) ch->fepiflag, 0, 0, 0);
+ }
+
+ /* ---------------------------------------------------------------
+ Set the board mint value for this channel. This will cause hardware
+ events to be generated each time the DCD signal (Described in mint)
+ changes.
+ ------------------------------------------------------------------- */
+ bc->mint = ch->dcd;
+
+ if ((ts->c_cflag & CLOCAL) || (ch->digiext.digi_flags & DIGI_FORCEDCD))
+ if (ch->digiext.digi_flags & DIGI_FORCEDCD)
+ bc->mint = 0;
+
+ ch->imodem = bc->mstat;
+
+ hflow = termios2digi_h(ch, ts->c_cflag);
+
+ if (hflow != ch->hflow)
+ {
+ ch->hflow = hflow;
+
+ /* --------------------------------------------------------------
+ Hard flow control has been selected but the board is not
+ using it. Activate hard flow control now.
+ ----------------------------------------------------------------- */
+
+ fepcmd(ch, SETHFLOW, hflow, 0xff, 0, 1);
+ }
+
+
+ mval ^= ch->modemfake & (mval ^ ch->modem);
+
+ if (ch->omodem ^ mval)
+ {
+ ch->omodem = mval;
+
+ /* --------------------------------------------------------------
+ The below command sets the DTR and RTS mstat structure. If
+ hard flow control is NOT active these changes will drive the
+ output of the actual DTR and RTS lines. If hard flow control
+ is active, the changes will be saved in the mstat structure and
+ only asserted when hard flow control is turned off.
+ ----------------------------------------------------------------- */
+
+ /* First reset DTR & RTS; then set them */
+ fepcmd(ch, SETMODEM, 0, ((ch->m_dtr)|(ch->m_rts)), 0, 1);
+ fepcmd(ch, SETMODEM, mval, 0, 0, 1);
+
+ }
+
+ if (ch->startc != ch->fepstartc || ch->stopc != ch->fepstopc)
+ {
+ ch->fepstartc = ch->startc;
+ ch->fepstopc = ch->stopc;
+
+ /* ------------------------------------------------------------
+ The XON / XOFF characters have changed; propagate these
+ changes to the card.
+ --------------------------------------------------------------- */
+
+ fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+ }
+
+ if (ch->startca != ch->fepstartca || ch->stopca != ch->fepstopca)
+ {
+ ch->fepstartca = ch->startca;
+ ch->fepstopca = ch->stopca;
+
+ /* ---------------------------------------------------------------
+ Similar to the above, this time the auxilarly XON / XOFF
+ characters have changed; propagate these changes to the card.
+ ------------------------------------------------------------------ */
+
+ fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+ }
+
+} /* End epcaparam */
+
+/* --------------------- Begin receive_data ----------------------- */
+
+static void receive_data(struct channel *ch)
+{ /* Begin receive_data */
+
+ unchar *rptr;
+ struct termios *ts = NULL;
+ struct tty_struct *tty;
+ volatile struct board_chan *bc;
+ register int dataToRead, wrapgap, bytesAvailable;
+ register unsigned int tail, head;
+ unsigned int wrapmask;
+ int rc;
+
+
+ /* ---------------------------------------------------------------
+ This routine is called by doint when a receive data event
+ has taken place.
+ ------------------------------------------------------------------- */
+
+ globalwinon(ch);
+
+ if (ch->statusflags & RXSTOPPED)
+ return;
+
+ tty = ch->tty;
+ if (tty)
+ ts = tty->termios;
+
+ bc = ch->brdchan;
+
+ if (!bc)
+ {
+ printk(KERN_ERR "<Error> - bc is NULL in receive_data!\n");
+ return;
+ }
+
+ wrapmask = ch->rxbufsize - 1;
+
+ /* ---------------------------------------------------------------------
+ Get the head and tail pointers to the receiver queue. Wrap the
+ head pointer if it has reached the end of the buffer.
+ ------------------------------------------------------------------------ */
+
+ head = bc->rin;
+ head &= wrapmask;
+ tail = bc->rout & wrapmask;
+
+ bytesAvailable = (head - tail) & wrapmask;
+
+ if (bytesAvailable == 0)
+ return;
+
+ /* ------------------------------------------------------------------
+ If CREAD bit is off or device not open, set TX tail to head
+ --------------------------------------------------------------------- */
+
+ if (!tty || !ts || !(ts->c_cflag & CREAD))
+ {
+ bc->rout = head;
+ return;
+ }
+
+ if (tty->flip.count == TTY_FLIPBUF_SIZE)
+ return;
+
+ if (bc->orun)
+ {
+ bc->orun = 0;
+ printk(KERN_WARNING "overrun! DigiBoard device %s\n",tty->name);
+ }
+
+ rxwinon(ch);
+ rptr = tty->flip.char_buf_ptr;
+ rc = tty->flip.count;
+
+ while (bytesAvailable > 0)
+ { /* Begin while there is data on the card */
+
+ wrapgap = (head >= tail) ? head - tail : ch->rxbufsize - tail;
+
+ /* ---------------------------------------------------------------
+ Even if head has wrapped around only report the amount of
+ data to be equal to the size - tail. Remember memcpy can't
+ automaticly wrap around the receive buffer.
+ ----------------------------------------------------------------- */
+
+ dataToRead = (wrapgap < bytesAvailable) ? wrapgap : bytesAvailable;
+
+ /* --------------------------------------------------------------
+ Make sure we don't overflow the buffer
+ ----------------------------------------------------------------- */
+
+ if ((rc + dataToRead) > TTY_FLIPBUF_SIZE)
+ dataToRead = TTY_FLIPBUF_SIZE - rc;
+
+ if (dataToRead == 0)
+ break;
+
+ /* ---------------------------------------------------------------
+ Move data read from our card into the line disciplines buffer
+ for translation if necessary.
+ ------------------------------------------------------------------ */
+
+ if ((memcpy(rptr, ch->rxptr + tail, dataToRead)) != rptr)
+ printk(KERN_ERR "<Error> - receive_data : memcpy failed\n");
+
+ rc += dataToRead;
+ rptr += dataToRead;
+ tail = (tail + dataToRead) & wrapmask;
+ bytesAvailable -= dataToRead;
+
+ } /* End while there is data on the card */
+
+
+ tty->flip.count = rc;
+ tty->flip.char_buf_ptr = rptr;
+ globalwinon(ch);
+ bc->rout = tail;
+
+ /* Must be called with global data */
+ tty_schedule_flip(ch->tty);
+ return;
+
+} /* End receive_data */
+
+static int info_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd)
+ { /* Begin switch cmd */
+
+ case DIGI_GETINFO:
+ { /* Begin case DIGI_GETINFO */
+
+ struct digi_info di ;
+ int brd;
+
+ getUser(brd, (unsigned int __user *)arg);
+
+ if ((brd < 0) || (brd >= num_cards) || (num_cards == 0))
+ return (-ENODEV);
+
+ memset(&di, 0, sizeof(di));
+
+ di.board = brd ;
+ di.status = boards[brd].status;
+ di.type = boards[brd].type ;
+ di.numports = boards[brd].numports ;
+ di.port = boards[brd].port ;
+ di.membase = boards[brd].membase ;
+
+ if (copy_to_user((void __user *)arg, &di, sizeof (di)))
+ return -EFAULT;
+ break;
+
+ } /* End case DIGI_GETINFO */
+
+ case DIGI_POLLER:
+ { /* Begin case DIGI_POLLER */
+
+ int brd = arg & 0xff000000 >> 16 ;
+ unsigned char state = arg & 0xff ;
+
+ if ((brd < 0) || (brd >= num_cards))
+ {
+ printk(KERN_ERR "<Error> - DIGI POLLER : brd not valid!\n");
+ return (-ENODEV);
+ }
+
+ digi_poller_inhibited = state ;
+ break ;
+
+ } /* End case DIGI_POLLER */
+
+ case DIGI_INIT:
+ { /* Begin case DIGI_INIT */
+
+ /* ------------------------------------------------------------
+ This call is made by the apps to complete the initilization
+ of the board(s). This routine is responsible for setting
+ the card to its initial state and setting the drivers control
+ fields to the sutianle settings for the card in question.
+ ---------------------------------------------------------------- */
+
+ int crd ;
+ for (crd = 0; crd < num_cards; crd++)
+ post_fep_init (crd);
+
+ break ;
+
+ } /* End case DIGI_INIT */
+
+
+ default:
+ return -ENOIOCTLCMD;
+
+ } /* End switch cmd */
+ return (0) ;
+}
+/* --------------------- Begin pc_ioctl ----------------------- */
+
+static int pc_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct channel *ch = (struct channel *) tty->driver_data;
+ volatile struct board_chan *bc;
+ unsigned int mstat, mflag = 0;
+ unsigned long flags;
+
+ if (ch)
+ bc = ch->brdchan;
+ else
+ {
+ printk(KERN_ERR "<Error> - ch is NULL in pc_tiocmget!\n");
+ return(-EINVAL);
+ }
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+ mstat = bc->mstat;
+ memoff(ch);
+ restore_flags(flags);
+
+ if (mstat & ch->m_dtr)
+ mflag |= TIOCM_DTR;
+
+ if (mstat & ch->m_rts)
+ mflag |= TIOCM_RTS;
+
+ if (mstat & ch->m_cts)
+ mflag |= TIOCM_CTS;
+
+ if (mstat & ch->dsr)
+ mflag |= TIOCM_DSR;
+
+ if (mstat & ch->m_ri)
+ mflag |= TIOCM_RI;
+
+ if (mstat & ch->dcd)
+ mflag |= TIOCM_CD;
+
+ return mflag;
+}
+
+static int pc_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct channel *ch = (struct channel *) tty->driver_data;
+ unsigned long flags;
+
+ if (!ch) {
+ printk(KERN_ERR "<Error> - ch is NULL in pc_tiocmset!\n");
+ return(-EINVAL);
+ }
+
+ save_flags(flags);
+ cli();
+ /*
+ * I think this modemfake stuff is broken. It doesn't
+ * correctly reflect the behaviour desired by the TIOCM*
+ * ioctls. Therefore this is probably broken.
+ */
+ if (set & TIOCM_RTS) {
+ ch->modemfake |= ch->m_rts;
+ ch->modem |= ch->m_rts;
+ }
+ if (set & TIOCM_DTR) {
+ ch->modemfake |= ch->m_dtr;
+ ch->modem |= ch->m_dtr;
+ }
+ if (clear & TIOCM_RTS) {
+ ch->modemfake |= ch->m_rts;
+ ch->modem &= ~ch->m_rts;
+ }
+ if (clear & TIOCM_DTR) {
+ ch->modemfake |= ch->m_dtr;
+ ch->modem &= ~ch->m_dtr;
+ }
+
+ globalwinon(ch);
+
+ /* --------------------------------------------------------------
+ The below routine generally sets up parity, baud, flow control
+ issues, etc.... It effect both control flags and input flags.
+ ------------------------------------------------------------------ */
+
+ epcaparam(tty,ch);
+ memoff(ch);
+ restore_flags(flags);
+ return 0;
+}
+
+static int pc_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{ /* Begin pc_ioctl */
+
+ digiflow_t dflow;
+ int retval;
+ unsigned long flags;
+ unsigned int mflag, mstat;
+ unsigned char startc, stopc;
+ volatile struct board_chan *bc;
+ struct channel *ch = (struct channel *) tty->driver_data;
+ void __user *argp = (void __user *)arg;
+
+ if (ch)
+ bc = ch->brdchan;
+ else
+ {
+ printk(KERN_ERR "<Error> - ch is NULL in pc_ioctl!\n");
+ return(-EINVAL);
+ }
+
+ save_flags(flags);
+
+ /* -------------------------------------------------------------------
+ For POSIX compliance we need to add more ioctls. See tty_ioctl.c
+ in /usr/src/linux/drivers/char for a good example. In particular
+ think about adding TCSETAF, TCSETAW, TCSETA, TCSETSF, TCSETSW, TCSETS.
+ ---------------------------------------------------------------------- */
+
+ switch (cmd)
+ { /* Begin switch cmd */
+
+ case TCGETS:
+ if (copy_to_user(argp,
+ tty->termios, sizeof(struct termios)))
+ return -EFAULT;
+ return(0);
+
+ case TCGETA:
+ return get_termio(tty, argp);
+
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+
+ /* Setup an event to indicate when the transmit buffer empties */
+
+ setup_empty_event(tty,ch);
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ digi_send_break(ch, HZ/4); /* 1/4 second */
+ return 0;
+
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+
+ /* Setup an event to indicate when the transmit buffer empties */
+
+ setup_empty_event(tty,ch);
+ tty_wait_until_sent(tty, 0);
+ digi_send_break(ch, arg ? arg*(HZ/10) : HZ/4);
+ return 0;
+
+ case TIOCGSOFTCAR:
+ if (put_user(C_CLOCAL(tty)?1:0, (unsigned long __user *)arg))
+ return -EFAULT;
+ return 0;
+
+ case TIOCSSOFTCAR:
+ {
+ unsigned int value;
+
+ if (get_user(value, (unsigned __user *)argp))
+ return -EFAULT;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (value ? CLOCAL : 0));
+ return 0;
+ }
+
+ case TIOCMODG:
+ mflag = pc_tiocmget(tty, file);
+ if (put_user(mflag, (unsigned long __user *)argp))
+ return -EFAULT;
+ break;
+
+ case TIOCMODS:
+ if (get_user(mstat, (unsigned __user *)argp))
+ return -EFAULT;
+ return pc_tiocmset(tty, file, mstat, ~mstat);
+
+ case TIOCSDTR:
+ ch->omodem |= ch->m_dtr;
+ cli();
+ globalwinon(ch);
+ fepcmd(ch, SETMODEM, ch->m_dtr, 0, 10, 1);
+ memoff(ch);
+ restore_flags(flags);
+ break;
+
+ case TIOCCDTR:
+ ch->omodem &= ~ch->m_dtr;
+ cli();
+ globalwinon(ch);
+ fepcmd(ch, SETMODEM, 0, ch->m_dtr, 10, 1);
+ memoff(ch);
+ restore_flags(flags);
+ break;
+
+ case DIGI_GETA:
+ if (copy_to_user(argp, &ch->digiext, sizeof(digi_t)))
+ return -EFAULT;
+ break;
+
+ case DIGI_SETAW:
+ case DIGI_SETAF:
+ if ((cmd) == (DIGI_SETAW))
+ {
+ /* Setup an event to indicate when the transmit buffer empties */
+
+ setup_empty_event(tty,ch);
+ tty_wait_until_sent(tty, 0);
+ }
+ else
+ {
+ /* ldisc lock already held in ioctl */
+ if (tty->ldisc.flush_buffer)
+ tty->ldisc.flush_buffer(tty);
+ }
+
+ /* Fall Thru */
+
+ case DIGI_SETA:
+ if (copy_from_user(&ch->digiext, argp, sizeof(digi_t)))
+ return -EFAULT;
+
+ if (ch->digiext.digi_flags & DIGI_ALTPIN)
+ {
+ ch->dcd = ch->m_dsr;
+ ch->dsr = ch->m_dcd;
+ }
+ else
+ {
+ ch->dcd = ch->m_dcd;
+ ch->dsr = ch->m_dsr;
+ }
+
+ cli();
+ globalwinon(ch);
+
+ /* -----------------------------------------------------------------
+ The below routine generally sets up parity, baud, flow control
+ issues, etc.... It effect both control flags and input flags.
+ ------------------------------------------------------------------- */
+
+ epcaparam(tty,ch);
+ memoff(ch);
+ restore_flags(flags);
+ break;
+
+ case DIGI_GETFLOW:
+ case DIGI_GETAFLOW:
+ cli();
+ globalwinon(ch);
+ if ((cmd) == (DIGI_GETFLOW))
+ {
+ dflow.startc = bc->startc;
+ dflow.stopc = bc->stopc;
+ }
+ else
+ {
+ dflow.startc = bc->startca;
+ dflow.stopc = bc->stopca;
+ }
+ memoff(ch);
+ restore_flags(flags);
+
+ if (copy_to_user(argp, &dflow, sizeof(dflow)))
+ return -EFAULT;
+ break;
+
+ case DIGI_SETAFLOW:
+ case DIGI_SETFLOW:
+ if ((cmd) == (DIGI_SETFLOW))
+ {
+ startc = ch->startc;
+ stopc = ch->stopc;
+ }
+ else
+ {
+ startc = ch->startca;
+ stopc = ch->stopca;
+ }
+
+ if (copy_from_user(&dflow, argp, sizeof(dflow)))
+ return -EFAULT;
+
+ if (dflow.startc != startc || dflow.stopc != stopc)
+ { /* Begin if setflow toggled */
+ cli();
+ globalwinon(ch);
+
+ if ((cmd) == (DIGI_SETFLOW))
+ {
+ ch->fepstartc = ch->startc = dflow.startc;
+ ch->fepstopc = ch->stopc = dflow.stopc;
+ fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+ }
+ else
+ {
+ ch->fepstartca = ch->startca = dflow.startc;
+ ch->fepstopca = ch->stopca = dflow.stopc;
+ fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+ }
+
+ if (ch->statusflags & TXSTOPPED)
+ pc_start(tty);
+
+ memoff(ch);
+ restore_flags(flags);
+
+ } /* End if setflow toggled */
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+
+ } /* End switch cmd */
+
+ return 0;
+
+} /* End pc_ioctl */
+
+/* --------------------- Begin pc_set_termios ----------------------- */
+
+static void pc_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{ /* Begin pc_set_termios */
+
+ struct channel *ch;
+ unsigned long flags;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if channel valid */
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+ epcaparam(tty, ch);
+ memoff(ch);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ ((tty->termios->c_cflag & CRTSCTS) == 0))
+ tty->hw_stopped = 0;
+
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&ch->open_wait);
+
+ restore_flags(flags);
+
+ } /* End if channel valid */
+
+} /* End pc_set_termios */
+
+/* --------------------- Begin do_softint ----------------------- */
+
+static void do_softint(void *private_)
+{ /* Begin do_softint */
+
+ struct channel *ch = (struct channel *) private_;
+
+
+ /* Called in response to a modem change event */
+
+ if (ch && ch->magic == EPCA_MAGIC)
+ { /* Begin EPCA_MAGIC */
+
+ struct tty_struct *tty = ch->tty;
+
+ if (tty && tty->driver_data)
+ {
+ if (test_and_clear_bit(EPCA_EVENT_HANGUP, &ch->event))
+ { /* Begin if clear_bit */
+
+ tty_hangup(tty); /* FIXME: module removal race here - AKPM */
+ wake_up_interruptible(&ch->open_wait);
+ ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+
+ } /* End if clear_bit */
+ }
+
+ } /* End EPCA_MAGIC */
+} /* End do_softint */
+
+/* ------------------------------------------------------------
+ pc_stop and pc_start provide software flow control to the
+ routine and the pc_ioctl routine.
+---------------------------------------------------------------- */
+
+/* --------------------- Begin pc_stop ----------------------- */
+
+static void pc_stop(struct tty_struct *tty)
+{ /* Begin pc_stop */
+
+ struct channel *ch;
+ unsigned long flags;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if valid channel */
+
+ save_flags(flags);
+ cli();
+
+ if ((ch->statusflags & TXSTOPPED) == 0)
+ { /* Begin if transmit stop requested */
+
+ globalwinon(ch);
+
+ /* STOP transmitting now !! */
+
+ fepcmd(ch, PAUSETX, 0, 0, 0, 0);
+
+ ch->statusflags |= TXSTOPPED;
+ memoff(ch);
+
+ } /* End if transmit stop requested */
+
+ restore_flags(flags);
+
+ } /* End if valid channel */
+
+} /* End pc_stop */
+
+/* --------------------- Begin pc_start ----------------------- */
+
+static void pc_start(struct tty_struct *tty)
+{ /* Begin pc_start */
+
+ struct channel *ch;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if channel valid */
+
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ /* Just in case output was resumed because of a change in Digi-flow */
+ if (ch->statusflags & TXSTOPPED)
+ { /* Begin transmit resume requested */
+
+ volatile struct board_chan *bc;
+
+ globalwinon(ch);
+ bc = ch->brdchan;
+ if (ch->statusflags & LOWWAIT)
+ bc->ilow = 1;
+
+ /* Okay, you can start transmitting again... */
+
+ fepcmd(ch, RESUMETX, 0, 0, 0, 0);
+
+ ch->statusflags &= ~TXSTOPPED;
+ memoff(ch);
+
+ } /* End transmit resume requested */
+
+ restore_flags(flags);
+
+ } /* End if channel valid */
+
+} /* End pc_start */
+
+/* ------------------------------------------------------------------
+ The below routines pc_throttle and pc_unthrottle are used
+ to slow (And resume) the receipt of data into the kernels
+ receive buffers. The exact occurrence of this depends on the
+ size of the kernels receive buffer and what the 'watermarks'
+ are set to for that buffer. See the n_ttys.c file for more
+ details.
+______________________________________________________________________ */
+/* --------------------- Begin throttle ----------------------- */
+
+static void pc_throttle(struct tty_struct * tty)
+{ /* Begin pc_throttle */
+
+ struct channel *ch;
+ unsigned long flags;
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if channel valid */
+
+
+ save_flags(flags);
+ cli();
+
+ if ((ch->statusflags & RXSTOPPED) == 0)
+ {
+ globalwinon(ch);
+ fepcmd(ch, PAUSERX, 0, 0, 0, 0);
+
+ ch->statusflags |= RXSTOPPED;
+ memoff(ch);
+ }
+ restore_flags(flags);
+
+ } /* End if channel valid */
+
+} /* End pc_throttle */
+
+/* --------------------- Begin unthrottle ----------------------- */
+
+static void pc_unthrottle(struct tty_struct *tty)
+{ /* Begin pc_unthrottle */
+
+ struct channel *ch;
+ unsigned long flags;
+ volatile struct board_chan *bc;
+
+
+ /* ---------------------------------------------------------
+ verifyChannel returns the channel from the tty struct
+ if it is valid. This serves as a sanity check.
+ ------------------------------------------------------------- */
+
+ if ((ch = verifyChannel(tty)) != NULL)
+ { /* Begin if channel valid */
+
+
+ /* Just in case output was resumed because of a change in Digi-flow */
+ save_flags(flags);
+ cli();
+
+ if (ch->statusflags & RXSTOPPED)
+ {
+
+ globalwinon(ch);
+ bc = ch->brdchan;
+ fepcmd(ch, RESUMERX, 0, 0, 0, 0);
+
+ ch->statusflags &= ~RXSTOPPED;
+ memoff(ch);
+ }
+ restore_flags(flags);
+
+ } /* End if channel valid */
+
+} /* End pc_unthrottle */
+
+/* --------------------- Begin digi_send_break ----------------------- */
+
+void digi_send_break(struct channel *ch, int msec)
+{ /* Begin digi_send_break */
+
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+
+ /* --------------------------------------------------------------------
+ Maybe I should send an infinite break here, schedule() for
+ msec amount of time, and then stop the break. This way,
+ the user can't screw up the FEP by causing digi_send_break()
+ to be called (i.e. via an ioctl()) more than once in msec amount
+ of time. Try this for now...
+ ------------------------------------------------------------------------ */
+
+ fepcmd(ch, SENDBREAK, msec, 0, 10, 0);
+ memoff(ch);
+
+ restore_flags(flags);
+
+} /* End digi_send_break */
+
+/* --------------------- Begin setup_empty_event ----------------------- */
+
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch)
+{ /* Begin setup_empty_event */
+
+ volatile struct board_chan *bc = ch->brdchan;
+ unsigned long int flags;
+
+ save_flags(flags);
+ cli();
+ globalwinon(ch);
+ ch->statusflags |= EMPTYWAIT;
+
+ /* ------------------------------------------------------------------
+ When set the iempty flag request a event to be generated when the
+ transmit buffer is empty (If there is no BREAK in progress).
+ --------------------------------------------------------------------- */
+
+ bc->iempty = 1;
+ memoff(ch);
+ restore_flags(flags);
+
+} /* End setup_empty_event */
+
+/* --------------------- Begin get_termio ----------------------- */
+
+static int get_termio(struct tty_struct * tty, struct termio __user * termio)
+{ /* Begin get_termio */
+ return kernel_termios_to_user_termio(termio, tty->termios);
+} /* End get_termio */
+/* ---------------------- Begin epca_setup -------------------------- */
+void epca_setup(char *str, int *ints)
+{ /* Begin epca_setup */
+
+ struct board_info board;
+ int index, loop, last;
+ char *temp, *t2;
+ unsigned len;
+
+ /* ----------------------------------------------------------------------
+ If this routine looks a little strange it is because it is only called
+ if a LILO append command is given to boot the kernel with parameters.
+ In this way, we can provide the user a method of changing his board
+ configuration without rebuilding the kernel.
+ ----------------------------------------------------------------------- */
+ if (!liloconfig)
+ liloconfig = 1;
+
+ memset(&board, 0, sizeof(board));
+
+ /* Assume the data is int first, later we can change it */
+ /* I think that array position 0 of ints holds the number of args */
+ for (last = 0, index = 1; index <= ints[0]; index++)
+ switch(index)
+ { /* Begin parse switch */
+
+ case 1:
+ board.status = ints[index];
+
+ /* ---------------------------------------------------------
+ We check for 2 (As opposed to 1; because 2 is a flag
+ instructing the driver to ignore epcaconfig.) For this
+ reason we check for 2.
+ ------------------------------------------------------------ */
+ if (board.status == 2)
+ { /* Begin ignore epcaconfig as well as lilo cmd line */
+ nbdevs = 0;
+ num_cards = 0;
+ return;
+ } /* End ignore epcaconfig as well as lilo cmd line */
+
+ if (board.status > 2)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid board status 0x%x\n", board.status);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_BOARD_STATUS;
+ return;
+ }
+ last = index;
+ break;
+
+ case 2:
+ board.type = ints[index];
+ if (board.type >= PCIXEM)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid board type 0x%x\n", board.type);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_BOARD_TYPE;
+ return;
+ }
+ last = index;
+ break;
+
+ case 3:
+ board.altpin = ints[index];
+ if (board.altpin > 1)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid board altpin 0x%x\n", board.altpin);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_ALTPIN;
+ return;
+ }
+ last = index;
+ break;
+
+ case 4:
+ board.numports = ints[index];
+ if ((board.numports < 2) || (board.numports > 256))
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid board numports 0x%x\n", board.numports);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_NUM_PORTS;
+ return;
+ }
+ nbdevs += board.numports;
+ last = index;
+ break;
+
+ case 5:
+ board.port = (unsigned char *)ints[index];
+ if (ints[index] <= 0)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid io port 0x%x\n", (unsigned int)board.port);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_PORT_BASE;
+ return;
+ }
+ last = index;
+ break;
+
+ case 6:
+ board.membase = (unsigned char *)ints[index];
+ if (ints[index] <= 0)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid memory base 0x%x\n",(unsigned int)board.membase);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_MEM_BASE;
+ return;
+ }
+ last = index;
+ break;
+
+ default:
+ printk(KERN_ERR "<Error> - epca_setup: Too many integer parms\n");
+ return;
+
+ } /* End parse switch */
+
+ while (str && *str)
+ { /* Begin while there is a string arg */
+
+ /* find the next comma or terminator */
+ temp = str;
+
+ /* While string is not null, and a comma hasn't been found */
+ while (*temp && (*temp != ','))
+ temp++;
+
+ if (!*temp)
+ temp = NULL;
+ else
+ *temp++ = 0;
+
+ /* Set index to the number of args + 1 */
+ index = last + 1;
+
+ switch(index)
+ {
+ case 1:
+ len = strlen(str);
+ if (strncmp("Disable", str, len) == 0)
+ board.status = 0;
+ else
+ if (strncmp("Enable", str, len) == 0)
+ board.status = 1;
+ else
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid status %s\n", str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_BOARD_STATUS;
+ return;
+ }
+ last = index;
+ break;
+
+ case 2:
+
+ for(loop = 0; loop < EPCA_NUM_TYPES; loop++)
+ if (strcmp(board_desc[loop], str) == 0)
+ break;
+
+
+ /* ---------------------------------------------------------------
+ If the index incremented above refers to a legitamate board
+ type set it here.
+ ------------------------------------------------------------------*/
+
+ if (index < EPCA_NUM_TYPES)
+ board.type = loop;
+ else
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid board type: %s\n", str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_BOARD_TYPE;
+ return;
+ }
+ last = index;
+ break;
+
+ case 3:
+ len = strlen(str);
+ if (strncmp("Disable", str, len) == 0)
+ board.altpin = 0;
+ else
+ if (strncmp("Enable", str, len) == 0)
+ board.altpin = 1;
+ else
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid altpin %s\n", str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_ALTPIN;
+ return;
+ }
+ last = index;
+ break;
+
+ case 4:
+ t2 = str;
+ while (isdigit(*t2))
+ t2++;
+
+ if (*t2)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid port count %s\n", str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_NUM_PORTS;
+ return;
+ }
+
+ /* ------------------------------------------------------------
+ There is not a man page for simple_strtoul but the code can be
+ found in vsprintf.c. The first argument is the string to
+ translate (To an unsigned long obviously), the second argument
+ can be the address of any character variable or a NULL. If a
+ variable is given, the end pointer of the string will be stored
+ in that variable; if a NULL is given the end pointer will
+ not be returned. The last argument is the base to use. If
+ a 0 is indicated, the routine will attempt to determine the
+ proper base by looking at the values prefix (A '0' for octal,
+ a 'x' for hex, etc ... If a value is given it will use that
+ value as the base.
+ ---------------------------------------------------------------- */
+ board.numports = simple_strtoul(str, NULL, 0);
+ nbdevs += board.numports;
+ last = index;
+ break;
+
+ case 5:
+ t2 = str;
+ while (isxdigit(*t2))
+ t2++;
+
+ if (*t2)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid i/o address %s\n", str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_PORT_BASE;
+ return;
+ }
+
+ board.port = (unsigned char *)simple_strtoul(str, NULL, 16);
+ last = index;
+ break;
+
+ case 6:
+ t2 = str;
+ while (isxdigit(*t2))
+ t2++;
+
+ if (*t2)
+ {
+ printk(KERN_ERR "<Error> - epca_setup: Invalid memory base %s\n",str);
+ invalid_lilo_config = 1;
+ setup_error_code |= INVALID_MEM_BASE;
+ return;
+ }
+
+ board.membase = (unsigned char *)simple_strtoul(str, NULL, 16);
+ last = index;
+ break;
+
+ default:
+ printk(KERN_ERR "PC/Xx: Too many string parms\n");
+ return;
+ }
+ str = temp;
+
+ } /* End while there is a string arg */
+
+
+ if (last < 6)
+ {
+ printk(KERN_ERR "PC/Xx: Insufficient parms specified\n");
+ return;
+ }
+
+ /* I should REALLY validate the stuff here */
+
+ /* Copies our local copy of board into boards */
+ memcpy((void *)&boards[num_cards],(void *)&board, sizeof(board));
+
+
+ /* Does this get called once per lilo arg are what ? */
+
+ printk(KERN_INFO "PC/Xx: Added board %i, %s %i ports at 0x%4.4X base 0x%6.6X\n",
+ num_cards, board_desc[board.type],
+ board.numports, (int)board.port, (unsigned int) board.membase);
+
+ num_cards++;
+
+} /* End epca_setup */
+
+
+
+#ifdef ENABLE_PCI
+/* ------------------------ Begin init_PCI --------------------------- */
+
+enum epic_board_types {
+ brd_xr = 0,
+ brd_xem,
+ brd_cx,
+ brd_xrj,
+};
+
+
+/* indexed directly by epic_board_types enum */
+static struct {
+ unsigned char board_type;
+ unsigned bar_idx; /* PCI base address region */
+} epca_info_tbl[] = {
+ { PCIXR, 0, },
+ { PCIXEM, 0, },
+ { PCICX, 0, },
+ { PCIXRJ, 2, },
+};
+
+
+static int __devinit epca_init_one (struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ static int board_num = -1;
+ int board_idx, info_idx = ent->driver_data;
+ unsigned long addr;
+
+ if (pci_enable_device(pdev))
+ return -EIO;
+
+ board_num++;
+ board_idx = board_num + num_cards;
+ if (board_idx >= MAXBOARDS)
+ goto err_out;
+
+ addr = pci_resource_start (pdev, epca_info_tbl[info_idx].bar_idx);
+ if (!addr) {
+ printk (KERN_ERR PFX "PCI region #%d not available (size 0)\n",
+ epca_info_tbl[info_idx].bar_idx);
+ goto err_out;
+ }
+
+ boards[board_idx].status = ENABLED;
+ boards[board_idx].type = epca_info_tbl[info_idx].board_type;
+ boards[board_idx].numports = 0x0;
+ boards[board_idx].port =
+ (unsigned char *)((char *) addr + PCI_IO_OFFSET);
+ boards[board_idx].membase =
+ (unsigned char *)((char *) addr);
+
+ if (!request_mem_region (addr + PCI_IO_OFFSET, 0x200000, "epca")) {
+ printk (KERN_ERR PFX "resource 0x%x @ 0x%lx unavailable\n",
+ 0x200000, addr + PCI_IO_OFFSET);
+ goto err_out;
+ }
+
+ boards[board_idx].re_map_port = ioremap(addr + PCI_IO_OFFSET, 0x200000);
+ if (!boards[board_idx].re_map_port) {
+ printk (KERN_ERR PFX "cannot map 0x%x @ 0x%lx\n",
+ 0x200000, addr + PCI_IO_OFFSET);
+ goto err_out_free_pciio;
+ }
+
+ if (!request_mem_region (addr, 0x200000, "epca")) {
+ printk (KERN_ERR PFX "resource 0x%x @ 0x%lx unavailable\n",
+ 0x200000, addr);
+ goto err_out_free_iounmap;
+ }
+
+ boards[board_idx].re_map_membase = ioremap(addr, 0x200000);
+ if (!boards[board_idx].re_map_membase) {
+ printk (KERN_ERR PFX "cannot map 0x%x @ 0x%lx\n",
+ 0x200000, addr + PCI_IO_OFFSET);
+ goto err_out_free_memregion;
+ }
+
+ /* --------------------------------------------------------------
+ I don't know what the below does, but the hardware guys say
+ its required on everything except PLX (In this case XRJ).
+ ---------------------------------------------------------------- */
+ if (info_idx != brd_xrj) {
+ pci_write_config_byte(pdev, 0x40, 0);
+ pci_write_config_byte(pdev, 0x46, 0);
+ }
+
+ return 0;
+
+err_out_free_memregion:
+ release_mem_region (addr, 0x200000);
+err_out_free_iounmap:
+ iounmap (boards[board_idx].re_map_port);
+err_out_free_pciio:
+ release_mem_region (addr + PCI_IO_OFFSET, 0x200000);
+err_out:
+ return -ENODEV;
+}
+
+
+static struct pci_device_id epca_pci_tbl[] = {
+ { PCI_VENDOR_DIGI, PCI_DEVICE_XR, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xr },
+ { PCI_VENDOR_DIGI, PCI_DEVICE_XEM, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xem },
+ { PCI_VENDOR_DIGI, PCI_DEVICE_CX, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_cx },
+ { PCI_VENDOR_DIGI, PCI_DEVICE_XRJ, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xrj },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, epca_pci_tbl);
+
+int __init init_PCI (void)
+{ /* Begin init_PCI */
+ memset (&epca_driver, 0, sizeof (epca_driver));
+ epca_driver.name = "epca";
+ epca_driver.id_table = epca_pci_tbl;
+ epca_driver.probe = epca_init_one;
+
+ return pci_register_driver(&epca_driver);
+} /* End init_PCI */
+
+#endif /* ENABLE_PCI */
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/epca.h b/drivers/char/epca.h
new file mode 100644
index 000000000000..52205ef71314
--- /dev/null
+++ b/drivers/char/epca.h
@@ -0,0 +1,165 @@
+#define XEMPORTS 0xC02
+#define XEPORTS 0xC22
+
+#define MAX_ALLOC 0x100
+
+#define MAXBOARDS 12
+#define FEPCODESEG 0x0200L
+#define FEPCODE 0x2000L
+#define BIOSCODE 0xf800L
+
+#define MISCGLOBAL 0x0C00L
+#define NPORT 0x0C22L
+#define MBOX 0x0C40L
+#define PORTBASE 0x0C90L
+
+/* Begin code defines used for epca_setup */
+
+#define INVALID_BOARD_TYPE 0x1
+#define INVALID_NUM_PORTS 0x2
+#define INVALID_MEM_BASE 0x4
+#define INVALID_PORT_BASE 0x8
+#define INVALID_BOARD_STATUS 0x10
+#define INVALID_ALTPIN 0x20
+
+/* End code defines used for epca_setup */
+
+
+#define FEPCLR 0x00
+#define FEPMEM 0x02
+#define FEPRST 0x04
+#define FEPINT 0x08
+#define FEPMASK 0x0e
+#define FEPWIN 0x80
+
+#define PCXE 0
+#define PCXEVE 1
+#define PCXEM 2
+#define EISAXEM 3
+#define PC64XE 4
+#define PCXI 5
+#define PCIXEM 7
+#define PCICX 8
+#define PCIXR 9
+#define PCIXRJ 10
+#define EPCA_NUM_TYPES 6
+
+
+static char *board_desc[] =
+{
+ "PC/Xe",
+ "PC/Xeve",
+ "PC/Xem",
+ "EISA/Xem",
+ "PC/64Xe",
+ "PC/Xi",
+ "unknown",
+ "PCI/Xem",
+ "PCI/CX",
+ "PCI/Xr",
+ "PCI/Xrj",
+};
+
+#define STARTC 021
+#define STOPC 023
+#define IAIXON 0x2000
+
+
+#define TXSTOPPED 0x1
+#define LOWWAIT 0x2
+#define EMPTYWAIT 0x4
+#define RXSTOPPED 0x8
+#define TXBUSY 0x10
+
+#define DISABLED 0
+#define ENABLED 1
+#define OFF 0
+#define ON 1
+
+#define FEPTIMEOUT 200000
+#define SERIAL_TYPE_NORMAL 1
+#define SERIAL_TYPE_INFO 3
+#define EPCA_EVENT_HANGUP 1
+#define EPCA_MAGIC 0x5c6df104L
+
+struct channel
+{
+ long magic;
+ unchar boardnum;
+ unchar channelnum;
+ unchar omodem; /* FEP output modem status */
+ unchar imodem; /* FEP input modem status */
+ unchar modemfake; /* Modem values to be forced */
+ unchar modem; /* Force values */
+ unchar hflow;
+ unchar dsr;
+ unchar dcd;
+ unchar m_rts ; /* The bits used in whatever FEP */
+ unchar m_dcd ; /* is indiginous to this board to */
+ unchar m_dsr ; /* represent each of the physical */
+ unchar m_cts ; /* handshake lines */
+ unchar m_ri ;
+ unchar m_dtr ;
+ unchar stopc;
+ unchar startc;
+ unchar stopca;
+ unchar startca;
+ unchar fepstopc;
+ unchar fepstartc;
+ unchar fepstopca;
+ unchar fepstartca;
+ unchar txwin;
+ unchar rxwin;
+ ushort fepiflag;
+ ushort fepcflag;
+ ushort fepoflag;
+ ushort txbufhead;
+ ushort txbufsize;
+ ushort rxbufhead;
+ ushort rxbufsize;
+ int close_delay;
+ int count;
+ int blocked_open;
+ ulong event;
+ int asyncflags;
+ uint dev;
+ ulong statusflags;
+ ulong c_iflag;
+ ulong c_cflag;
+ ulong c_lflag;
+ ulong c_oflag;
+ unchar *txptr;
+ unchar *rxptr;
+ unchar *tmp_buf;
+ struct board_info *board;
+ volatile struct board_chan *brdchan;
+ struct digi_struct digiext;
+ struct tty_struct *tty;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct work_struct tqueue;
+ volatile struct global_data *mailbox;
+};
+
+struct board_info
+{
+ unchar status;
+ unchar type;
+ unchar altpin;
+ ushort numports;
+ unchar *port;
+ unchar *membase;
+ unchar __iomem *re_map_port;
+ unchar *re_map_membase;
+ ulong memory_seg;
+ void ( * memwinon ) (struct board_info *, unsigned int) ;
+ void ( * memwinoff ) (struct board_info *, unsigned int) ;
+ void ( * globalwinon ) (struct channel *) ;
+ void ( * txwinon ) (struct channel *) ;
+ void ( * rxwinon ) (struct channel *) ;
+ void ( * memoff ) (struct channel *) ;
+ void ( * assertgwinon ) (struct channel *) ;
+ void ( * assertmemoff ) (struct channel *) ;
+ unchar poller_inhibited ;
+};
+
diff --git a/drivers/char/epcaconfig.h b/drivers/char/epcaconfig.h
new file mode 100644
index 000000000000..55dec067078e
--- /dev/null
+++ b/drivers/char/epcaconfig.h
@@ -0,0 +1,7 @@
+#define NUMCARDS 0
+#define NBDEVS 0
+
+struct board_info static_boards[NUMCARDS]={
+};
+
+/* DO NOT HAND EDIT THIS FILE! */
diff --git a/drivers/char/esp.c b/drivers/char/esp.c
new file mode 100644
index 000000000000..9f53d2fcc360
--- /dev/null
+++ b/drivers/char/esp.c
@@ -0,0 +1,2630 @@
+/*
+ * esp.c - driver for Hayes ESP serial cards
+ *
+ * --- Notices from serial.c, upon which this driver is based ---
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92. Now
+ * much more extensible to support other serial cards based on the
+ * 16450/16550A UART's. Added support for the AST FourPort and the
+ * Accent Async board.
+ *
+ * set_serial_info fixed to set the flags, custom divisor, and uart
+ * type fields. Fix suggested by Michael K. Johnson 12/12/92.
+ *
+ * 11/95: TIOCMIWAIT, TIOCGICOUNT by Angelo Haritsis <ah@doc.ic.ac.uk>
+ *
+ * 03/96: Modularised by Angelo Haritsis <ah@doc.ic.ac.uk>
+ *
+ * rs_set_termios fixed to look also for changes of the input
+ * flags INPCK, BRKINT, PARMRK, IGNPAR and IGNBRK.
+ * Bernd Anh�pl 05/17/96.
+ *
+ * --- End of notices from serial.c ---
+ *
+ * Support for the ESP serial card by Andrew J. Robinson
+ * <arobinso@nyx.net> (Card detection routine taken from a patch
+ * by Dennis J. Boylan). Patches to allow use with 2.1.x contributed
+ * by Chris Faylor.
+ *
+ * Most recent changes: (Andrew J. Robinson)
+ * Support for PIO mode. This allows the driver to work properly with
+ * multiport cards.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> -
+ * several cleanups, use module_init/module_exit, etc
+ *
+ * This module exports the following rs232 io functions:
+ *
+ * int espserial_init(void);
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/serial_reg.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+
+#include <linux/hayesesp.h>
+
+#define NR_PORTS 64 /* maximum number of ports */
+#define NR_PRIMARY 8 /* maximum number of primary ports */
+#define REGION_SIZE 8 /* size of io region to request */
+
+/* The following variables can be set by giving module options */
+static int irq[NR_PRIMARY]; /* IRQ for each base port */
+static unsigned int divisor[NR_PRIMARY]; /* custom divisor for each port */
+static unsigned int dma = ESP_DMA_CHANNEL; /* DMA channel */
+static unsigned int rx_trigger = ESP_RX_TRIGGER;
+static unsigned int tx_trigger = ESP_TX_TRIGGER;
+static unsigned int flow_off = ESP_FLOW_OFF;
+static unsigned int flow_on = ESP_FLOW_ON;
+static unsigned int rx_timeout = ESP_RX_TMOUT;
+static unsigned int pio_threshold = ESP_PIO_THRESHOLD;
+
+MODULE_LICENSE("GPL");
+
+module_param_array(irq, int, NULL, 0);
+module_param_array(divisor, uint, NULL, 0);
+module_param(dma, uint, 0);
+module_param(rx_trigger, uint, 0);
+module_param(tx_trigger, uint, 0);
+module_param(flow_off, uint, 0);
+module_param(flow_on, uint, 0);
+module_param(rx_timeout, uint, 0);
+module_param(pio_threshold, uint, 0);
+
+/* END */
+
+static char *dma_buffer;
+static int dma_bytes;
+static struct esp_pio_buffer *free_pio_buf;
+
+#define DMA_BUFFER_SZ 1024
+
+#define WAKEUP_CHARS 1024
+
+static char serial_name[] __initdata = "ESP serial driver";
+static char serial_version[] __initdata = "2.2";
+
+static struct tty_driver *esp_driver;
+
+/* serial subtype definitions */
+#define SERIAL_TYPE_NORMAL 1
+
+/*
+ * Serial driver configuration section. Here are the various options:
+ *
+ * SERIAL_PARANOIA_CHECK
+ * Check the magic number for the esp_structure where
+ * ever possible.
+ */
+
+#undef SERIAL_PARANOIA_CHECK
+#define SERIAL_DO_RESTART
+
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_FLOW
+
+#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)
+#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \
+ tty->name, (info->flags), serial_driver.refcount,info->count,tty->count,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+static struct esp_struct *ports;
+
+static void change_speed(struct esp_struct *info);
+static void rs_wait_until_sent(struct tty_struct *, int);
+
+/*
+ * The ESP card has a clock rate of 14.7456 MHz (that is, 2**ESPC_SCALE
+ * times the normal 1.8432 Mhz clock of most serial boards).
+ */
+#define BASE_BAUD ((1843200 / 16) * (1 << ESPC_SCALE))
+
+/* Standard COM flags (except for COM4, because of the 8514 problem) */
+#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write. We need to
+ * lock it in case the memcpy_fromfs blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static inline int serial_paranoia_check(struct esp_struct *info,
+ char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+ static const char badmagic[] = KERN_WARNING
+ "Warning: bad magic number for serial struct (%s) in %s\n";
+ static const char badinfo[] = KERN_WARNING
+ "Warning: null esp_struct for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (info->magic != ESP_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static inline unsigned int serial_in(struct esp_struct *info, int offset)
+{
+ return inb(info->port + offset);
+}
+
+static inline void serial_out(struct esp_struct *info, int offset,
+ unsigned char value)
+{
+ outb(value, info->port+offset);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_stop() and rs_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rs_stop(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_stop"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (info->IER & UART_IER_THRI) {
+ info->IER &= ~UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_start(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_start"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Here starts the interrupt handling routines. All of the following
+ * subroutines are declared as inline and are folded into
+ * rs_interrupt(). They were separated out for readability's sake.
+ *
+ * Note: rs_interrupt() is a "fast" interrupt, which means that it
+ * runs with interrupts turned off. People who may want to modify
+ * rs_interrupt() should try to keep the interrupt handler as fast as
+ * possible. After you are done making modifications, it is not a bad
+ * idea to do:
+ *
+ * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
+ *
+ * and look at the resulting assemble code in serial.s.
+ *
+ * - Ted Ts'o (tytso@mit.edu), 7-Mar-93
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver.
+ */
+static inline void rs_sched_event(struct esp_struct *info,
+ int event)
+{
+ info->event |= 1 << event;
+ schedule_work(&info->tqueue);
+}
+
+static DEFINE_SPINLOCK(pio_lock);
+
+static inline struct esp_pio_buffer *get_pio_buffer(void)
+{
+ struct esp_pio_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pio_lock, flags);
+ if (free_pio_buf) {
+ buf = free_pio_buf;
+ free_pio_buf = buf->next;
+ } else {
+ buf = kmalloc(sizeof(struct esp_pio_buffer), GFP_ATOMIC);
+ }
+ spin_unlock_irqrestore(&pio_lock, flags);
+ return buf;
+}
+
+static inline void release_pio_buffer(struct esp_pio_buffer *buf)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&pio_lock, flags);
+ buf->next = free_pio_buf;
+ free_pio_buf = buf;
+ spin_unlock_irqrestore(&pio_lock, flags);
+}
+
+static inline void receive_chars_pio(struct esp_struct *info, int num_bytes)
+{
+ struct tty_struct *tty = info->tty;
+ int i;
+ struct esp_pio_buffer *pio_buf;
+ struct esp_pio_buffer *err_buf;
+ unsigned char status_mask;
+
+ pio_buf = get_pio_buffer();
+
+ if (!pio_buf)
+ return;
+
+ err_buf = get_pio_buffer();
+
+ if (!err_buf) {
+ release_pio_buffer(pio_buf);
+ return;
+ }
+
+ status_mask = (info->read_status_mask >> 2) & 0x07;
+
+ for (i = 0; i < num_bytes - 1; i += 2) {
+ *((unsigned short *)(pio_buf->data + i)) =
+ inw(info->port + UART_ESI_RX);
+ err_buf->data[i] = serial_in(info, UART_ESI_RWS);
+ err_buf->data[i + 1] = (err_buf->data[i] >> 3) & status_mask;
+ err_buf->data[i] &= status_mask;
+ }
+
+ if (num_bytes & 0x0001) {
+ pio_buf->data[num_bytes - 1] = serial_in(info, UART_ESI_RX);
+ err_buf->data[num_bytes - 1] =
+ (serial_in(info, UART_ESI_RWS) >> 3) & status_mask;
+ }
+
+ /* make sure everything is still ok since interrupts were enabled */
+ tty = info->tty;
+
+ if (!tty) {
+ release_pio_buffer(pio_buf);
+ release_pio_buffer(err_buf);
+ info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+ return;
+ }
+
+ status_mask = (info->ignore_status_mask >> 2) & 0x07;
+
+ for (i = 0; i < num_bytes; i++) {
+ if (!(err_buf->data[i] & status_mask)) {
+ *(tty->flip.char_buf_ptr++) = pio_buf->data[i];
+
+ if (err_buf->data[i] & 0x04) {
+ *(tty->flip.flag_buf_ptr++) = TTY_BREAK;
+
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ }
+ else if (err_buf->data[i] & 0x02)
+ *(tty->flip.flag_buf_ptr++) = TTY_FRAME;
+ else if (err_buf->data[i] & 0x01)
+ *(tty->flip.flag_buf_ptr++) = TTY_PARITY;
+ else
+ *(tty->flip.flag_buf_ptr++) = 0;
+
+ tty->flip.count++;
+ }
+ }
+
+ schedule_delayed_work(&tty->flip.work, 1);
+
+ info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+ release_pio_buffer(pio_buf);
+ release_pio_buffer(err_buf);
+}
+
+static inline void receive_chars_dma(struct esp_struct *info, int num_bytes)
+{
+ unsigned long flags;
+ info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+ dma_bytes = num_bytes;
+ info->stat_flags |= ESP_STAT_DMA_RX;
+
+ flags=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+ set_dma_mode(dma, DMA_MODE_READ);
+ set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+ set_dma_count(dma, dma_bytes);
+ enable_dma(dma);
+ release_dma_lock(flags);
+
+ serial_out(info, UART_ESI_CMD1, ESI_START_DMA_RX);
+}
+
+static inline void receive_chars_dma_done(struct esp_struct *info,
+ int status)
+{
+ struct tty_struct *tty = info->tty;
+ int num_bytes;
+ unsigned long flags;
+
+
+ flags=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+
+ info->stat_flags &= ~ESP_STAT_DMA_RX;
+ num_bytes = dma_bytes - get_dma_residue(dma);
+ release_dma_lock(flags);
+
+ info->icount.rx += num_bytes;
+
+ memcpy(tty->flip.char_buf_ptr, dma_buffer, num_bytes);
+ tty->flip.char_buf_ptr += num_bytes;
+ tty->flip.count += num_bytes;
+ memset(tty->flip.flag_buf_ptr, 0, num_bytes);
+ tty->flip.flag_buf_ptr += num_bytes;
+
+ if (num_bytes > 0) {
+ tty->flip.flag_buf_ptr--;
+
+ status &= (0x1c & info->read_status_mask);
+
+ if (status & info->ignore_status_mask) {
+ tty->flip.count--;
+ tty->flip.char_buf_ptr--;
+ tty->flip.flag_buf_ptr--;
+ } else if (status & 0x10) {
+ *tty->flip.flag_buf_ptr = TTY_BREAK;
+ (info->icount.brk)++;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (status & 0x08) {
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ (info->icount.frame)++;
+ }
+ else if (status & 0x04) {
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ (info->icount.parity)++;
+ }
+
+ tty->flip.flag_buf_ptr++;
+
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+
+ if (dma_bytes != num_bytes) {
+ num_bytes = dma_bytes - num_bytes;
+ dma_bytes = 0;
+ receive_chars_dma(info, num_bytes);
+ } else
+ dma_bytes = 0;
+}
+
+/* Caller must hold info->lock */
+
+static inline void transmit_chars_pio(struct esp_struct *info,
+ int space_avail)
+{
+ int i;
+ struct esp_pio_buffer *pio_buf;
+
+ pio_buf = get_pio_buffer();
+
+ if (!pio_buf)
+ return;
+
+ while (space_avail && info->xmit_cnt) {
+ if (info->xmit_tail + space_avail <= ESP_XMIT_SIZE) {
+ memcpy(pio_buf->data,
+ &(info->xmit_buf[info->xmit_tail]),
+ space_avail);
+ } else {
+ i = ESP_XMIT_SIZE - info->xmit_tail;
+ memcpy(pio_buf->data,
+ &(info->xmit_buf[info->xmit_tail]), i);
+ memcpy(&(pio_buf->data[i]), info->xmit_buf,
+ space_avail - i);
+ }
+
+ info->xmit_cnt -= space_avail;
+ info->xmit_tail = (info->xmit_tail + space_avail) &
+ (ESP_XMIT_SIZE - 1);
+
+ for (i = 0; i < space_avail - 1; i += 2) {
+ outw(*((unsigned short *)(pio_buf->data + i)),
+ info->port + UART_ESI_TX);
+ }
+
+ if (space_avail & 0x0001)
+ serial_out(info, UART_ESI_TX,
+ pio_buf->data[space_avail - 1]);
+
+ if (info->xmit_cnt) {
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+ space_avail = serial_in(info, UART_ESI_STAT1) << 8;
+ space_avail |= serial_in(info, UART_ESI_STAT2);
+
+ if (space_avail > info->xmit_cnt)
+ space_avail = info->xmit_cnt;
+ }
+ }
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("THRE...");
+#endif
+
+ if (info->xmit_cnt <= 0) {
+ info->IER &= ~UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1,
+ ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+ }
+
+ release_pio_buffer(pio_buf);
+}
+
+/* Caller must hold info->lock */
+static inline void transmit_chars_dma(struct esp_struct *info, int num_bytes)
+{
+ unsigned long flags;
+
+ dma_bytes = num_bytes;
+
+ if (info->xmit_tail + dma_bytes <= ESP_XMIT_SIZE) {
+ memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
+ dma_bytes);
+ } else {
+ int i = ESP_XMIT_SIZE - info->xmit_tail;
+ memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
+ i);
+ memcpy(&(dma_buffer[i]), info->xmit_buf, dma_bytes - i);
+ }
+
+ info->xmit_cnt -= dma_bytes;
+ info->xmit_tail = (info->xmit_tail + dma_bytes) & (ESP_XMIT_SIZE - 1);
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("THRE...");
+#endif
+
+ if (info->xmit_cnt <= 0) {
+ info->IER &= ~UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+ }
+
+ info->stat_flags |= ESP_STAT_DMA_TX;
+
+ flags=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+ set_dma_mode(dma, DMA_MODE_WRITE);
+ set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+ set_dma_count(dma, dma_bytes);
+ enable_dma(dma);
+ release_dma_lock(flags);
+
+ serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
+}
+
+static inline void transmit_chars_dma_done(struct esp_struct *info)
+{
+ int num_bytes;
+ unsigned long flags;
+
+
+ flags=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+
+ num_bytes = dma_bytes - get_dma_residue(dma);
+ info->icount.tx += dma_bytes;
+ release_dma_lock(flags);
+
+ if (dma_bytes != num_bytes) {
+ dma_bytes -= num_bytes;
+ memmove(dma_buffer, dma_buffer + num_bytes, dma_bytes);
+
+ flags=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+ set_dma_mode(dma, DMA_MODE_WRITE);
+ set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+ set_dma_count(dma, dma_bytes);
+ enable_dma(dma);
+ release_dma_lock(flags);
+
+ serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
+ } else {
+ dma_bytes = 0;
+ info->stat_flags &= ~ESP_STAT_DMA_TX;
+ }
+}
+
+static inline void check_modem_status(struct esp_struct *info)
+{
+ int status;
+
+ serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+ status = serial_in(info, UART_ESI_STAT2);
+
+ if (status & UART_MSR_ANY_DELTA) {
+ /* update input line counters */
+ if (status & UART_MSR_TERI)
+ info->icount.rng++;
+ if (status & UART_MSR_DDSR)
+ info->icount.dsr++;
+ if (status & UART_MSR_DDCD)
+ info->icount.dcd++;
+ if (status & UART_MSR_DCTS)
+ info->icount.cts++;
+ wake_up_interruptible(&info->delta_msr_wait);
+ }
+
+ if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {
+#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
+ printk("ttys%d CD now %s...", info->line,
+ (status & UART_MSR_DCD) ? "on" : "off");
+#endif
+ if (status & UART_MSR_DCD)
+ wake_up_interruptible(&info->open_wait);
+ else {
+#ifdef SERIAL_DEBUG_OPEN
+ printk("scheduling hangup...");
+#endif
+ schedule_work(&info->tqueue_hangup);
+ }
+ }
+}
+
+/*
+ * This is the serial driver's interrupt routine
+ */
+static irqreturn_t rs_interrupt_single(int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ struct esp_struct * info;
+ unsigned err_status;
+ unsigned int scratch;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("rs_interrupt_single(%d)...", irq);
+#endif
+ info = (struct esp_struct *)dev_id;
+ err_status = 0;
+ scratch = serial_in(info, UART_ESI_SID);
+
+ spin_lock(&info->lock);
+
+ if (!info->tty) {
+ spin_unlock(&info->lock);
+ return IRQ_NONE;
+ }
+
+ if (scratch & 0x04) { /* error */
+ serial_out(info, UART_ESI_CMD1, ESI_GET_ERR_STAT);
+ err_status = serial_in(info, UART_ESI_STAT1);
+ serial_in(info, UART_ESI_STAT2);
+
+ if (err_status & 0x01)
+ info->stat_flags |= ESP_STAT_RX_TIMEOUT;
+
+ if (err_status & 0x20) /* UART status */
+ check_modem_status(info);
+
+ if (err_status & 0x80) /* Start break */
+ wake_up_interruptible(&info->break_wait);
+ }
+
+ if ((scratch & 0x88) || /* DMA completed or timed out */
+ (err_status & 0x1c) /* receive error */) {
+ if (info->stat_flags & ESP_STAT_DMA_RX)
+ receive_chars_dma_done(info, err_status);
+ else if (info->stat_flags & ESP_STAT_DMA_TX)
+ transmit_chars_dma_done(info);
+ }
+
+ if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
+ ((scratch & 0x01) || (info->stat_flags & ESP_STAT_RX_TIMEOUT)) &&
+ (info->IER & UART_IER_RDI)) {
+ int num_bytes;
+
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
+ num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
+ num_bytes |= serial_in(info, UART_ESI_STAT2);
+
+ if (num_bytes > (TTY_FLIPBUF_SIZE - info->tty->flip.count))
+ num_bytes = TTY_FLIPBUF_SIZE - info->tty->flip.count;
+
+ if (num_bytes) {
+ if (dma_bytes ||
+ (info->stat_flags & ESP_STAT_USE_PIO) ||
+ (num_bytes <= info->config.pio_threshold))
+ receive_chars_pio(info, num_bytes);
+ else
+ receive_chars_dma(info, num_bytes);
+ }
+ }
+
+ if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
+ (scratch & 0x02) && (info->IER & UART_IER_THRI)) {
+ if ((info->xmit_cnt <= 0) || info->tty->stopped) {
+ info->IER &= ~UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ } else {
+ int num_bytes;
+
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+ num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
+ num_bytes |= serial_in(info, UART_ESI_STAT2);
+
+ if (num_bytes > info->xmit_cnt)
+ num_bytes = info->xmit_cnt;
+
+ if (num_bytes) {
+ if (dma_bytes ||
+ (info->stat_flags & ESP_STAT_USE_PIO) ||
+ (num_bytes <= info->config.pio_threshold))
+ transmit_chars_pio(info, num_bytes);
+ else
+ transmit_chars_dma(info, num_bytes);
+ }
+ }
+ }
+
+ info->last_active = jiffies;
+
+#ifdef SERIAL_DEBUG_INTR
+ printk("end.\n");
+#endif
+ spin_unlock(&info->lock);
+ return IRQ_HANDLED;
+}
+
+/*
+ * -------------------------------------------------------------------
+ * Here ends the serial interrupt routines.
+ * -------------------------------------------------------------------
+ */
+
+static void do_softint(void *private_)
+{
+ struct esp_struct *info = (struct esp_struct *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+ if (!tty)
+ return;
+
+ if (test_and_clear_bit(ESP_EVENT_WRITE_WAKEUP, &info->event)) {
+ tty_wakeup(tty);
+ }
+}
+
+/*
+ * This routine is called from the scheduler tqueue when the interrupt
+ * routine has signalled that a hangup has occurred. The path of
+ * hangup processing is:
+ *
+ * serial interrupt routine -> (scheduler tqueue) ->
+ * do_serial_hangup() -> tty->hangup() -> esp_hangup()
+ *
+ */
+static void do_serial_hangup(void *private_)
+{
+ struct esp_struct *info = (struct esp_struct *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+ if (tty)
+ tty_hangup(tty);
+}
+
+/*
+ * ---------------------------------------------------------------
+ * Low level utility subroutines for the serial driver: routines to
+ * figure out the appropriate timeout for an interrupt chain, routines
+ * to initialize and startup a serial port, and routines to shutdown a
+ * serial port. Useful stuff like that.
+ *
+ * Caller should hold lock
+ * ---------------------------------------------------------------
+ */
+
+static inline void esp_basic_init(struct esp_struct * info)
+{
+ /* put ESPC in enhanced mode */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_MODE);
+
+ if (info->stat_flags & ESP_STAT_NEVER_DMA)
+ serial_out(info, UART_ESI_CMD2, 0x01);
+ else
+ serial_out(info, UART_ESI_CMD2, 0x31);
+
+ /* disable interrupts for now */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+
+ /* set interrupt and DMA channel */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_IRQ);
+
+ if (info->stat_flags & ESP_STAT_NEVER_DMA)
+ serial_out(info, UART_ESI_CMD2, 0x01);
+ else
+ serial_out(info, UART_ESI_CMD2, (dma << 4) | 0x01);
+
+ serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
+
+ if (info->line % 8) /* secondary port */
+ serial_out(info, UART_ESI_CMD2, 0x0d); /* shared */
+ else if (info->irq == 9)
+ serial_out(info, UART_ESI_CMD2, 0x02);
+ else
+ serial_out(info, UART_ESI_CMD2, info->irq);
+
+ /* set error status mask (check this) */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_ERR_MASK);
+
+ if (info->stat_flags & ESP_STAT_NEVER_DMA)
+ serial_out(info, UART_ESI_CMD2, 0xa1);
+ else
+ serial_out(info, UART_ESI_CMD2, 0xbd);
+
+ serial_out(info, UART_ESI_CMD2, 0x00);
+
+ /* set DMA timeout */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_DMA_TMOUT);
+ serial_out(info, UART_ESI_CMD2, 0xff);
+
+ /* set FIFO trigger levels */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
+ serial_out(info, UART_ESI_CMD2, info->config.rx_trigger >> 8);
+ serial_out(info, UART_ESI_CMD2, info->config.rx_trigger);
+ serial_out(info, UART_ESI_CMD2, info->config.tx_trigger >> 8);
+ serial_out(info, UART_ESI_CMD2, info->config.tx_trigger);
+
+ /* Set clock scaling and wait states */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_PRESCALAR);
+ serial_out(info, UART_ESI_CMD2, 0x04 | ESPC_SCALE);
+
+ /* set reinterrupt pacing */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_REINTR);
+ serial_out(info, UART_ESI_CMD2, 0xff);
+}
+
+static int startup(struct esp_struct * info)
+{
+ unsigned long flags;
+ int retval=0;
+ unsigned int num_chars;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (info->flags & ASYNC_INITIALIZED)
+ goto out;
+
+ if (!info->xmit_buf) {
+ info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_ATOMIC);
+ retval = -ENOMEM;
+ if (!info->xmit_buf)
+ goto out;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("starting up ttys%d (irq %d)...", info->line, info->irq);
+#endif
+
+ /* Flush the RX buffer. Using the ESI flush command may cause */
+ /* wild interrupts, so read all the data instead. */
+
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
+ num_chars = serial_in(info, UART_ESI_STAT1) << 8;
+ num_chars |= serial_in(info, UART_ESI_STAT2);
+
+ while (num_chars > 1) {
+ inw(info->port + UART_ESI_RX);
+ num_chars -= 2;
+ }
+
+ if (num_chars)
+ serial_in(info, UART_ESI_RX);
+
+ /* set receive character timeout */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+ serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
+
+ /* clear all flags except the "never DMA" flag */
+ info->stat_flags &= ESP_STAT_NEVER_DMA;
+
+ if (info->stat_flags & ESP_STAT_NEVER_DMA)
+ info->stat_flags |= ESP_STAT_USE_PIO;
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /*
+ * Allocate the IRQ
+ */
+
+ retval = request_irq(info->irq, rs_interrupt_single, SA_SHIRQ,
+ "esp serial", info);
+
+ if (retval) {
+ if (capable(CAP_SYS_ADMIN)) {
+ if (info->tty)
+ set_bit(TTY_IO_ERROR,
+ &info->tty->flags);
+ retval = 0;
+ }
+ goto out_unlocked;
+ }
+
+ if (!(info->stat_flags & ESP_STAT_USE_PIO) && !dma_buffer) {
+ dma_buffer = (char *)__get_dma_pages(
+ GFP_KERNEL, get_order(DMA_BUFFER_SZ));
+
+ /* use PIO mode if DMA buf/chan cannot be allocated */
+ if (!dma_buffer)
+ info->stat_flags |= ESP_STAT_USE_PIO;
+ else if (request_dma(dma, "esp serial")) {
+ free_pages((unsigned long)dma_buffer,
+ get_order(DMA_BUFFER_SZ));
+ dma_buffer = NULL;
+ info->stat_flags |= ESP_STAT_USE_PIO;
+ }
+
+ }
+
+ info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, info->MCR);
+
+ /*
+ * Finally, enable interrupts
+ */
+ /* info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; */
+ info->IER = UART_IER_RLSI | UART_IER_RDI | UART_IER_DMA_TMOUT |
+ UART_IER_DMA_TC;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /*
+ * Set up the tty->alt_speed kludge
+ */
+ if (info->tty) {
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ info->tty->alt_speed = 460800;
+ }
+
+ /*
+ * set the speed of the serial port
+ */
+ change_speed(info);
+ info->flags |= ASYNC_INITIALIZED;
+ return 0;
+
+out:
+ spin_unlock_irqrestore(&info->lock, flags);
+out_unlocked:
+ return retval;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void shutdown(struct esp_struct * info)
+{
+ unsigned long flags, f;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("Shutting down serial port %d (irq %d)....", info->line,
+ info->irq);
+#endif
+
+ spin_lock_irqsave(&info->lock, flags);
+ /*
+ * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
+ * here so the queue might never be waken up
+ */
+ wake_up_interruptible(&info->delta_msr_wait);
+ wake_up_interruptible(&info->break_wait);
+
+ /* stop a DMA transfer on the port being closed */
+ /* DMA lock is higher priority always */
+ if (info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) {
+ f=claim_dma_lock();
+ disable_dma(dma);
+ clear_dma_ff(dma);
+ release_dma_lock(f);
+
+ dma_bytes = 0;
+ }
+
+ /*
+ * Free the IRQ
+ */
+ free_irq(info->irq, info);
+
+ if (dma_buffer) {
+ struct esp_struct *current_port = ports;
+
+ while (current_port) {
+ if ((current_port != info) &&
+ (current_port->flags & ASYNC_INITIALIZED))
+ break;
+
+ current_port = current_port->next_port;
+ }
+
+ if (!current_port) {
+ free_dma(dma);
+ free_pages((unsigned long)dma_buffer,
+ get_order(DMA_BUFFER_SZ));
+ dma_buffer = NULL;
+ }
+ }
+
+ if (info->xmit_buf) {
+ free_page((unsigned long) info->xmit_buf);
+ info->xmit_buf = NULL;
+ }
+
+ info->IER = 0;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+ info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
+
+ info->MCR &= ~UART_MCR_OUT2;
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, info->MCR);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void change_speed(struct esp_struct *info)
+{
+ unsigned short port;
+ int quot = 0;
+ unsigned cflag,cval;
+ int baud, bits;
+ unsigned char flow1 = 0, flow2 = 0;
+ unsigned long flags;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+ cflag = info->tty->termios->c_cflag;
+ port = info->port;
+
+ /* byte size and parity */
+ switch (cflag & CSIZE) {
+ case CS5: cval = 0x00; bits = 7; break;
+ case CS6: cval = 0x01; bits = 8; break;
+ case CS7: cval = 0x02; bits = 9; break;
+ case CS8: cval = 0x03; bits = 10; break;
+ default: cval = 0x00; bits = 7; break;
+ }
+ if (cflag & CSTOPB) {
+ cval |= 0x04;
+ bits++;
+ }
+ if (cflag & PARENB) {
+ cval |= UART_LCR_PARITY;
+ bits++;
+ }
+ if (!(cflag & PARODD))
+ cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+ if (cflag & CMSPAR)
+ cval |= UART_LCR_SPAR;
+#endif
+
+ baud = tty_get_baud_rate(info->tty);
+ if (baud == 38400 &&
+ ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+ quot = info->custom_divisor;
+ else {
+ if (baud == 134)
+ /* Special case since 134 is really 134.5 */
+ quot = (2*BASE_BAUD / 269);
+ else if (baud)
+ quot = BASE_BAUD / baud;
+ }
+ /* If the quotient is ever zero, default to 9600 bps */
+ if (!quot)
+ quot = BASE_BAUD / 9600;
+
+ info->timeout = ((1024 * HZ * bits * quot) / BASE_BAUD) + (HZ / 50);
+
+ /* CTS flow control flag and modem status interrupts */
+ /* info->IER &= ~UART_IER_MSI; */
+ if (cflag & CRTSCTS) {
+ info->flags |= ASYNC_CTS_FLOW;
+ /* info->IER |= UART_IER_MSI; */
+ flow1 = 0x04;
+ flow2 = 0x10;
+ } else
+ info->flags &= ~ASYNC_CTS_FLOW;
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else {
+ info->flags |= ASYNC_CHECK_CD;
+ /* info->IER |= UART_IER_MSI; */
+ }
+
+ /*
+ * Set up parity check flag
+ */
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+ info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask |= UART_LSR_BI;
+
+ info->ignore_status_mask = 0;
+#if 0
+ /* This should be safe, but for some broken bits of hardware... */
+ if (I_IGNPAR(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ info->read_status_mask |= UART_LSR_PE | UART_LSR_FE;
+ }
+#endif
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_BI;
+ info->read_status_mask |= UART_LSR_BI;
+ /*
+ * If we're ignore parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_OE | \
+ UART_LSR_PE | UART_LSR_FE;
+ info->read_status_mask |= UART_LSR_OE | \
+ UART_LSR_PE | UART_LSR_FE;
+ }
+ }
+
+ if (I_IXOFF(info->tty))
+ flow1 |= 0x81;
+
+ spin_lock_irqsave(&info->lock, flags);
+ /* set baud */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_BAUD);
+ serial_out(info, UART_ESI_CMD2, quot >> 8);
+ serial_out(info, UART_ESI_CMD2, quot & 0xff);
+
+ /* set data bits, parity, etc. */
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_LCR);
+ serial_out(info, UART_ESI_CMD2, cval);
+
+ /* Enable flow control */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CNTL);
+ serial_out(info, UART_ESI_CMD2, flow1);
+ serial_out(info, UART_ESI_CMD2, flow2);
+
+ /* set flow control characters (XON/XOFF only) */
+ if (I_IXOFF(info->tty)) {
+ serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CHARS);
+ serial_out(info, UART_ESI_CMD2, START_CHAR(info->tty));
+ serial_out(info, UART_ESI_CMD2, STOP_CHAR(info->tty));
+ serial_out(info, UART_ESI_CMD2, 0x10);
+ serial_out(info, UART_ESI_CMD2, 0x21);
+ switch (cflag & CSIZE) {
+ case CS5:
+ serial_out(info, UART_ESI_CMD2, 0x1f);
+ break;
+ case CS6:
+ serial_out(info, UART_ESI_CMD2, 0x3f);
+ break;
+ case CS7:
+ case CS8:
+ serial_out(info, UART_ESI_CMD2, 0x7f);
+ break;
+ default:
+ serial_out(info, UART_ESI_CMD2, 0xff);
+ break;
+ }
+ }
+
+ /* Set high/low water */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
+ serial_out(info, UART_ESI_CMD2, info->config.flow_off >> 8);
+ serial_out(info, UART_ESI_CMD2, info->config.flow_off);
+ serial_out(info, UART_ESI_CMD2, info->config.flow_on >> 8);
+ serial_out(info, UART_ESI_CMD2, info->config.flow_on);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_put_char"))
+ return;
+
+ if (!tty || !info->xmit_buf)
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (info->xmit_cnt < ESP_XMIT_SIZE - 1) {
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= ESP_XMIT_SIZE-1;
+ info->xmit_cnt++;
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_flush_chars(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_flush_chars"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf)
+ goto out;
+
+ if (!(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+out:
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static int rs_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ int c, t, ret = 0;
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_write"))
+ return 0;
+
+ if (!tty || !info->xmit_buf || !tmp_buf)
+ return 0;
+
+ while (1) {
+ /* Thanks to R. Wolff for suggesting how to do this with */
+ /* interrupts enabled */
+
+ c = count;
+ t = ESP_XMIT_SIZE - info->xmit_cnt - 1;
+
+ if (t < c)
+ c = t;
+
+ t = ESP_XMIT_SIZE - info->xmit_head;
+
+ if (t < c)
+ c = t;
+
+ if (c <= 0)
+ break;
+
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+
+ info->xmit_head = (info->xmit_head + c) & (ESP_XMIT_SIZE-1);
+ info->xmit_cnt += c;
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ }
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ return ret;
+}
+
+static int rs_write_room(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ int ret;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_write_room"))
+ return 0;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ ret = ESP_XMIT_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ spin_unlock_irqrestore(&info->lock, flags);
+ return ret;
+}
+
+static int rs_chars_in_buffer(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer"))
+ return 0;
+ return info->xmit_cnt;
+}
+
+static void rs_flush_buffer(struct tty_struct *tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_flush_buffer"))
+ return;
+ spin_lock_irqsave(&info->lock, flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ spin_unlock_irqrestore(&info->lock, flags);
+ tty_wakeup(tty);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_throttle()
+ *
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void rs_throttle(struct tty_struct * tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("throttle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "rs_throttle"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+ info->IER &= ~UART_IER_RDI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_unthrottle(struct tty_struct * tty)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("unthrottle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "rs_unthrottle"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+ info->IER |= UART_IER_RDI;
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+ serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+ serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static int get_serial_info(struct esp_struct * info,
+ struct serial_struct __user *retinfo)
+{
+ struct serial_struct tmp;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = PORT_16550A;
+ tmp.line = info->line;
+ tmp.port = info->port;
+ tmp.irq = info->irq;
+ tmp.flags = info->flags;
+ tmp.xmit_fifo_size = 1024;
+ tmp.baud_base = BASE_BAUD;
+ tmp.close_delay = info->close_delay;
+ tmp.closing_wait = info->closing_wait;
+ tmp.custom_divisor = info->custom_divisor;
+ tmp.hub6 = 0;
+ if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
+ return -EFAULT;
+ return 0;
+}
+
+static int get_esp_config(struct esp_struct * info,
+ struct hayes_esp_config __user *retinfo)
+{
+ struct hayes_esp_config tmp;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.rx_timeout = info->config.rx_timeout;
+ tmp.rx_trigger = info->config.rx_trigger;
+ tmp.tx_trigger = info->config.tx_trigger;
+ tmp.flow_off = info->config.flow_off;
+ tmp.flow_on = info->config.flow_on;
+ tmp.pio_threshold = info->config.pio_threshold;
+ tmp.dma_channel = (info->stat_flags & ESP_STAT_NEVER_DMA ? 0 : dma);
+
+ return copy_to_user(retinfo, &tmp, sizeof(*retinfo)) ? -EFAULT : 0;
+}
+
+static int set_serial_info(struct esp_struct * info,
+ struct serial_struct __user *new_info)
+{
+ struct serial_struct new_serial;
+ struct esp_struct old_info;
+ unsigned int change_irq;
+ int retval = 0;
+ struct esp_struct *current_async;
+
+ if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+ return -EFAULT;
+ old_info = *info;
+
+ if ((new_serial.type != PORT_16550A) ||
+ (new_serial.hub6) ||
+ (info->port != new_serial.port) ||
+ (new_serial.baud_base != BASE_BAUD) ||
+ (new_serial.irq > 15) ||
+ (new_serial.irq < 2) ||
+ (new_serial.irq == 6) ||
+ (new_serial.irq == 8) ||
+ (new_serial.irq == 13))
+ return -EINVAL;
+
+ change_irq = new_serial.irq != info->irq;
+
+ if (change_irq && (info->line % 8))
+ return -EINVAL;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (change_irq ||
+ (new_serial.close_delay != info->close_delay) ||
+ ((new_serial.flags & ~ASYNC_USR_MASK) !=
+ (info->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ info->custom_divisor = new_serial.custom_divisor;
+ } else {
+ if (new_serial.irq == 2)
+ new_serial.irq = 9;
+
+ if (change_irq) {
+ current_async = ports;
+
+ while (current_async) {
+ if ((current_async->line >= info->line) &&
+ (current_async->line < (info->line + 8))) {
+ if (current_async == info) {
+ if (current_async->count > 1)
+ return -EBUSY;
+ } else if (current_async->count)
+ return -EBUSY;
+ }
+
+ current_async = current_async->next_port;
+ }
+ }
+
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+
+ info->flags = ((info->flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ info->custom_divisor = new_serial.custom_divisor;
+ info->close_delay = new_serial.close_delay * HZ/100;
+ info->closing_wait = new_serial.closing_wait * HZ/100;
+
+ if (change_irq) {
+ /*
+ * We need to shutdown the serial port at the old
+ * port/irq combination.
+ */
+ shutdown(info);
+
+ current_async = ports;
+
+ while (current_async) {
+ if ((current_async->line >= info->line) &&
+ (current_async->line < (info->line + 8)))
+ current_async->irq = new_serial.irq;
+
+ current_async = current_async->next_port;
+ }
+
+ serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
+ if (info->irq == 9)
+ serial_out(info, UART_ESI_CMD2, 0x02);
+ else
+ serial_out(info, UART_ESI_CMD2, info->irq);
+ }
+ }
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ if (((old_info.flags & ASYNC_SPD_MASK) !=
+ (info->flags & ASYNC_SPD_MASK)) ||
+ (old_info.custom_divisor != info->custom_divisor)) {
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ info->tty->alt_speed = 460800;
+ change_speed(info);
+ }
+ } else
+ retval = startup(info);
+
+ return retval;
+}
+
+static int set_esp_config(struct esp_struct * info,
+ struct hayes_esp_config __user * new_info)
+{
+ struct hayes_esp_config new_config;
+ unsigned int change_dma;
+ int retval = 0;
+ struct esp_struct *current_async;
+ unsigned long flags;
+
+ /* Perhaps a non-sysadmin user should be able to do some of these */
+ /* operations. I haven't decided yet. */
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&new_config, new_info, sizeof(new_config)))
+ return -EFAULT;
+
+ if ((new_config.flow_on >= new_config.flow_off) ||
+ (new_config.rx_trigger < 1) ||
+ (new_config.tx_trigger < 1) ||
+ (new_config.flow_off < 1) ||
+ (new_config.flow_on < 1) ||
+ (new_config.rx_trigger > 1023) ||
+ (new_config.tx_trigger > 1023) ||
+ (new_config.flow_off > 1023) ||
+ (new_config.flow_on > 1023) ||
+ (new_config.pio_threshold < 0) ||
+ (new_config.pio_threshold > 1024))
+ return -EINVAL;
+
+ if ((new_config.dma_channel != 1) && (new_config.dma_channel != 3))
+ new_config.dma_channel = 0;
+
+ if (info->stat_flags & ESP_STAT_NEVER_DMA)
+ change_dma = new_config.dma_channel;
+ else
+ change_dma = (new_config.dma_channel != dma);
+
+ if (change_dma) {
+ if (new_config.dma_channel) {
+ /* PIO mode to DMA mode transition OR */
+ /* change current DMA channel */
+
+ current_async = ports;
+
+ while (current_async) {
+ if (current_async == info) {
+ if (current_async->count > 1)
+ return -EBUSY;
+ } else if (current_async->count)
+ return -EBUSY;
+
+ current_async =
+ current_async->next_port;
+ }
+
+ shutdown(info);
+ dma = new_config.dma_channel;
+ info->stat_flags &= ~ESP_STAT_NEVER_DMA;
+
+ /* all ports must use the same DMA channel */
+
+ spin_lock_irqsave(&info->lock, flags);
+ current_async = ports;
+
+ while (current_async) {
+ esp_basic_init(current_async);
+ current_async = current_async->next_port;
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+ } else {
+ /* DMA mode to PIO mode only */
+
+ if (info->count > 1)
+ return -EBUSY;
+
+ shutdown(info);
+ spin_lock_irqsave(&info->lock, flags);
+ info->stat_flags |= ESP_STAT_NEVER_DMA;
+ esp_basic_init(info);
+ spin_unlock_irqrestore(&info->lock, flags);
+ }
+ }
+
+ info->config.pio_threshold = new_config.pio_threshold;
+
+ if ((new_config.flow_off != info->config.flow_off) ||
+ (new_config.flow_on != info->config.flow_on)) {
+ unsigned long flags;
+
+ info->config.flow_off = new_config.flow_off;
+ info->config.flow_on = new_config.flow_on;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
+ serial_out(info, UART_ESI_CMD2, new_config.flow_off >> 8);
+ serial_out(info, UART_ESI_CMD2, new_config.flow_off);
+ serial_out(info, UART_ESI_CMD2, new_config.flow_on >> 8);
+ serial_out(info, UART_ESI_CMD2, new_config.flow_on);
+ spin_unlock_irqrestore(&info->lock, flags);
+ }
+
+ if ((new_config.rx_trigger != info->config.rx_trigger) ||
+ (new_config.tx_trigger != info->config.tx_trigger)) {
+ unsigned long flags;
+
+ info->config.rx_trigger = new_config.rx_trigger;
+ info->config.tx_trigger = new_config.tx_trigger;
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
+ serial_out(info, UART_ESI_CMD2,
+ new_config.rx_trigger >> 8);
+ serial_out(info, UART_ESI_CMD2, new_config.rx_trigger);
+ serial_out(info, UART_ESI_CMD2,
+ new_config.tx_trigger >> 8);
+ serial_out(info, UART_ESI_CMD2, new_config.tx_trigger);
+ spin_unlock_irqrestore(&info->lock, flags);
+ }
+
+ if (new_config.rx_timeout != info->config.rx_timeout) {
+ unsigned long flags;
+
+ info->config.rx_timeout = new_config.rx_timeout;
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (info->IER & UART_IER_RDI) {
+ serial_out(info, UART_ESI_CMD1,
+ ESI_SET_RX_TIMEOUT);
+ serial_out(info, UART_ESI_CMD2,
+ new_config.rx_timeout);
+ }
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ }
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ retval = startup(info);
+
+ return retval;
+}
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int get_lsr_info(struct esp_struct * info, unsigned int __user *value)
+{
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+ status = serial_in(info, UART_ESI_STAT1);
+ spin_unlock_irqrestore(&info->lock, flags);
+ result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
+ return put_user(result,value);
+}
+
+
+static int esp_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+ unsigned char control, status;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ control = info->MCR;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+ status = serial_in(info, UART_ESI_STAT2);
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
+ | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
+ | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0)
+ | ((status & UART_MSR_RI) ? TIOCM_RNG : 0)
+ | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0)
+ | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
+}
+
+static int esp_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+ return -ENODEV;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (set & TIOCM_RTS)
+ info->MCR |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ info->MCR |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ info->MCR &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ info->MCR &= ~UART_MCR_DTR;
+
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, info->MCR);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ return 0;
+}
+
+/*
+ * rs_break() --- routine which turns the break handling on or off
+ */
+static void esp_break(struct tty_struct *tty, int break_state)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "esp_break"))
+ return;
+
+ if (break_state == -1) {
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
+ serial_out(info, UART_ESI_CMD2, 0x01);
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /* FIXME - new style wait needed here */
+ interruptible_sleep_on(&info->break_wait);
+ } else {
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+ spin_unlock_irqrestore(&info->lock, flags);
+ }
+}
+
+static int rs_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+ struct async_icount cprev, cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser; /* user space */
+ void __user *argp = (void __user *)arg;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD) &&
+ (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) &&
+ (cmd != TIOCGHAYESESP) && (cmd != TIOCSHAYESESP)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case TIOCGSERIAL:
+ return get_serial_info(info, argp);
+ case TIOCSSERIAL:
+ return set_serial_info(info, argp);
+ case TIOCSERCONFIG:
+ /* do not reconfigure after initial configuration */
+ return 0;
+
+ case TIOCSERGWILD:
+ return put_user(0L, (unsigned long __user *)argp);
+
+ case TIOCSERGETLSR: /* Get line status register */
+ return get_lsr_info(info, argp);
+
+ case TIOCSERSWILD:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ return 0;
+
+ /*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+ case TIOCMIWAIT:
+ spin_lock_irqsave(&info->lock, flags);
+ cprev = info->icount; /* note the counters on entry */
+ spin_unlock_irqrestore(&info->lock, flags);
+ while (1) {
+ /* FIXME: convert to new style wakeup */
+ interruptible_sleep_on(&info->delta_msr_wait);
+ /* see if a signal did it */
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ spin_lock_irqsave(&info->lock, flags);
+ cnow = info->icount; /* atomic copy */
+ spin_unlock_irqrestore(&info->lock, flags);
+ if (cnow.rng == cprev.rng &&
+ cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd &&
+ cnow.cts == cprev.cts)
+ return -EIO; /* no change => error */
+ if (((arg & TIOCM_RNG) &&
+ (cnow.rng != cprev.rng)) ||
+ ((arg & TIOCM_DSR) &&
+ (cnow.dsr != cprev.dsr)) ||
+ ((arg & TIOCM_CD) &&
+ (cnow.dcd != cprev.dcd)) ||
+ ((arg & TIOCM_CTS) &&
+ (cnow.cts != cprev.cts)) ) {
+ return 0;
+ }
+ cprev = cnow;
+ }
+ /* NOTREACHED */
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ spin_lock_irqsave(&info->lock, flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->lock, flags);
+ p_cuser = argp;
+ if (put_user(cnow.cts, &p_cuser->cts) ||
+ put_user(cnow.dsr, &p_cuser->dsr) ||
+ put_user(cnow.rng, &p_cuser->rng) ||
+ put_user(cnow.dcd, &p_cuser->dcd))
+ return -EFAULT;
+
+ return 0;
+ case TIOCGHAYESESP:
+ return get_esp_config(info, argp);
+ case TIOCSHAYESESP:
+ return set_esp_config(info, argp);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if ( (tty->termios->c_cflag == old_termios->c_cflag)
+ && ( RELEVANT_IFLAG(tty->termios->c_iflag)
+ == RELEVANT_IFLAG(old_termios->c_iflag)))
+ return;
+
+ change_speed(info);
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) &&
+ !(tty->termios->c_cflag & CBAUD)) {
+ info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, info->MCR);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ (tty->termios->c_cflag & CBAUD)) {
+ info->MCR |= (UART_MCR_DTR | UART_MCR_RTS);
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, info->MCR);
+ }
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /* Handle turning of CRTSCTS */
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ rs_start(tty);
+ }
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_close()
+ *
+ * This routine is called when the serial port gets closed. First, we
+ * wait for the last remaining data to be sent. Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ * ------------------------------------------------------------
+ */
+static void rs_close(struct tty_struct *tty, struct file * filp)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (!info || serial_paranoia_check(info, tty->name, "rs_close"))
+ return;
+
+ spin_lock_irqsave(&info->lock, flags);
+
+ if (tty_hung_up_p(filp)) {
+ DBG_CNT("before DEC-hung");
+ goto out;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("rs_close ttys%d, count = %d\n", info->line, info->count);
+#endif
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk("rs_close: bad serial port count; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+ if (--info->count < 0) {
+ printk("rs_close: bad serial port count for ttys%d: %d\n",
+ info->line, info->count);
+ info->count = 0;
+ }
+ if (info->count) {
+ DBG_CNT("before DEC-2");
+ goto out;
+ }
+ info->flags |= ASYNC_CLOSING;
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, info->closing_wait);
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ /* info->IER &= ~UART_IER_RLSI; */
+ info->IER &= ~UART_IER_RDI;
+ info->read_status_mask &= ~UART_LSR_DR;
+ if (info->flags & ASYNC_INITIALIZED) {
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+ serial_out(info, UART_ESI_CMD2, info->IER);
+
+ /* disable receive timeout */
+ serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ rs_wait_until_sent(tty, info->timeout);
+ }
+ shutdown(info);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ tty->closing = 0;
+ info->event = 0;
+ info->tty = NULL;
+
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+ return;
+
+out:
+ spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+ unsigned long flags;
+
+ if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent"))
+ return;
+
+ orig_jiffies = jiffies;
+ char_time = ((info->timeout - HZ / 50) / 1024) / 5;
+
+ if (!char_time)
+ char_time = 1;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+
+ while ((serial_in(info, UART_ESI_STAT1) != 0x03) ||
+ (serial_in(info, UART_ESI_STAT2) != 0xff)) {
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ msleep_interruptible(jiffies_to_msecs(char_time));
+
+ if (signal_pending(current))
+ break;
+
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+
+ spin_lock_irqsave(&info->lock, flags);
+ serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+ serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+ set_current_state(TASK_RUNNING);
+}
+
+/*
+ * esp_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void esp_hangup(struct tty_struct *tty)
+{
+ struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+
+ if (serial_paranoia_check(info, tty->name, "esp_hangup"))
+ return;
+
+ rs_flush_buffer(tty);
+ shutdown(info);
+ info->event = 0;
+ info->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+ wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ * ------------------------------------------------------------
+ * esp_open() and friends
+ * ------------------------------------------------------------
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct esp_struct *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0;
+ unsigned long flags;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) ||
+ (info->flags & ASYNC_CLOSING)) {
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+ if (info->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+#else
+ return -EAGAIN;
+#endif
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready before block: ttys%d, count = %d\n",
+ info->line, info->count);
+#endif
+ spin_lock_irqsave(&info->lock, flags);
+ if (!tty_hung_up_p(filp))
+ info->count--;
+ info->blocked_open++;
+ while (1) {
+ if ((tty->termios->c_cflag & CBAUD)) {
+ unsigned int scratch;
+
+ serial_out(info, UART_ESI_CMD1, ESI_READ_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ scratch = serial_in(info, UART_ESI_STAT1);
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2,
+ scratch | UART_MCR_DTR | UART_MCR_RTS);
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(info->flags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+ if (info->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+#else
+ retval = -EAGAIN;
+#endif
+ break;
+ }
+
+ serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+ if (serial_in(info, UART_ESI_STAT2) & UART_MSR_DCD)
+ do_clocal = 1;
+
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready blocking: ttys%d, count = %d\n",
+ info->line, info->count);
+#endif
+ spin_unlock_irqrestore(&info->lock, flags);
+ schedule();
+ spin_lock_irqsave(&info->lock, flags);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ info->count++;
+ info->blocked_open--;
+ spin_unlock_irqrestore(&info->lock, flags);
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready after blocking: ttys%d, count = %d\n",
+ info->line, info->count);
+#endif
+ if (retval)
+ return retval;
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+}
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain. It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int esp_open(struct tty_struct *tty, struct file * filp)
+{
+ struct esp_struct *info;
+ int retval, line;
+ unsigned long flags;
+
+ line = tty->index;
+ if ((line < 0) || (line >= NR_PORTS))
+ return -ENODEV;
+
+ /* find the port in the chain */
+
+ info = ports;
+
+ while (info && (info->line != line))
+ info = info->next_port;
+
+ if (!info) {
+ serial_paranoia_check(info, tty->name, "esp_open");
+ return -ENODEV;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("esp_open %s, count = %d\n", tty->name, info->count);
+#endif
+ spin_lock_irqsave(&info->lock, flags);
+ info->count++;
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (!tmp_buf) {
+ tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+ }
+
+ /*
+ * Start up serial port
+ */
+ retval = startup(info);
+ if (retval)
+ return retval;
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+ printk("esp_open returning after block_til_ready with %d\n",
+ retval);
+#endif
+ return retval;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("esp_open %s successful...", tty->name);
+#endif
+ return 0;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * espserial_init() and friends
+ *
+ * espserial_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+
+static inline void show_serial_version(void)
+{
+ printk(KERN_INFO "%s version %s (DMA %u)\n",
+ serial_name, serial_version, dma);
+}
+
+/*
+ * This routine is called by espserial_init() to initialize a specific serial
+ * port.
+ */
+static inline int autoconfig(struct esp_struct * info)
+{
+ int port_detected = 0;
+ unsigned long flags;
+
+ if (!request_region(info->port, REGION_SIZE, "esp serial"))
+ return -EIO;
+
+ spin_lock_irqsave(&info->lock, flags);
+ /*
+ * Check for ESP card
+ */
+
+ if (serial_in(info, UART_ESI_BASE) == 0xf3) {
+ serial_out(info, UART_ESI_CMD1, 0x00);
+ serial_out(info, UART_ESI_CMD1, 0x01);
+
+ if ((serial_in(info, UART_ESI_STAT2) & 0x70) == 0x20) {
+ port_detected = 1;
+
+ if (!(info->irq)) {
+ serial_out(info, UART_ESI_CMD1, 0x02);
+
+ if (serial_in(info, UART_ESI_STAT1) & 0x01)
+ info->irq = 3;
+ else
+ info->irq = 4;
+ }
+
+
+ /* put card in enhanced mode */
+ /* this prevents access through */
+ /* the "old" IO ports */
+ esp_basic_init(info);
+
+ /* clear out MCR */
+ serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+ serial_out(info, UART_ESI_CMD2, UART_MCR);
+ serial_out(info, UART_ESI_CMD2, 0x00);
+ }
+ }
+ if (!port_detected)
+ release_region(info->port, REGION_SIZE);
+
+ spin_unlock_irqrestore(&info->lock, flags);
+ return (port_detected);
+}
+
+static struct tty_operations esp_ops = {
+ .open = esp_open,
+ .close = rs_close,
+ .write = rs_write,
+ .put_char = rs_put_char,
+ .flush_chars = rs_flush_chars,
+ .write_room = rs_write_room,
+ .chars_in_buffer = rs_chars_in_buffer,
+ .flush_buffer = rs_flush_buffer,
+ .ioctl = rs_ioctl,
+ .throttle = rs_throttle,
+ .unthrottle = rs_unthrottle,
+ .set_termios = rs_set_termios,
+ .stop = rs_stop,
+ .start = rs_start,
+ .hangup = esp_hangup,
+ .break_ctl = esp_break,
+ .wait_until_sent = rs_wait_until_sent,
+ .tiocmget = esp_tiocmget,
+ .tiocmset = esp_tiocmset,
+};
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+static int __init espserial_init(void)
+{
+ int i, offset;
+ struct esp_struct * info;
+ struct esp_struct *last_primary = NULL;
+ int esp[] = {0x100,0x140,0x180,0x200,0x240,0x280,0x300,0x380};
+
+ esp_driver = alloc_tty_driver(NR_PORTS);
+ if (!esp_driver)
+ return -ENOMEM;
+
+ for (i = 0; i < NR_PRIMARY; i++) {
+ if (irq[i] != 0) {
+ if ((irq[i] < 2) || (irq[i] > 15) || (irq[i] == 6) ||
+ (irq[i] == 8) || (irq[i] == 13))
+ irq[i] = 0;
+ else if (irq[i] == 2)
+ irq[i] = 9;
+ }
+ }
+
+ if ((dma != 1) && (dma != 3))
+ dma = 0;
+
+ if ((rx_trigger < 1) || (rx_trigger > 1023))
+ rx_trigger = 768;
+
+ if ((tx_trigger < 1) || (tx_trigger > 1023))
+ tx_trigger = 768;
+
+ if ((flow_off < 1) || (flow_off > 1023))
+ flow_off = 1016;
+
+ if ((flow_on < 1) || (flow_on > 1023))
+ flow_on = 944;
+
+ if ((rx_timeout < 0) || (rx_timeout > 255))
+ rx_timeout = 128;
+
+ if (flow_on >= flow_off)
+ flow_on = flow_off - 1;
+
+ show_serial_version();
+
+ /* Initialize the tty_driver structure */
+
+ esp_driver->owner = THIS_MODULE;
+ esp_driver->name = "ttyP";
+ esp_driver->devfs_name = "tts/P";
+ esp_driver->major = ESP_IN_MAJOR;
+ esp_driver->minor_start = 0;
+ esp_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ esp_driver->subtype = SERIAL_TYPE_NORMAL;
+ esp_driver->init_termios = tty_std_termios;
+ esp_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ esp_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(esp_driver, &esp_ops);
+ if (tty_register_driver(esp_driver))
+ {
+ printk(KERN_ERR "Couldn't register esp serial driver");
+ put_tty_driver(esp_driver);
+ return 1;
+ }
+
+ info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL);
+
+ if (!info)
+ {
+ printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n");
+ tty_unregister_driver(esp_driver);
+ put_tty_driver(esp_driver);
+ return 1;
+ }
+
+ memset((void *)info, 0, sizeof(struct esp_struct));
+ /* rx_trigger, tx_trigger are needed by autoconfig */
+ info->config.rx_trigger = rx_trigger;
+ info->config.tx_trigger = tx_trigger;
+
+ i = 0;
+ offset = 0;
+
+ do {
+ info->port = esp[i] + offset;
+ info->irq = irq[i];
+ info->line = (i * 8) + (offset / 8);
+
+ if (!autoconfig(info)) {
+ i++;
+ offset = 0;
+ continue;
+ }
+
+ info->custom_divisor = (divisor[i] >> (offset / 2)) & 0xf;
+ info->flags = STD_COM_FLAGS;
+ if (info->custom_divisor)
+ info->flags |= ASYNC_SPD_CUST;
+ info->magic = ESP_MAGIC;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = 30*HZ;
+ INIT_WORK(&info->tqueue, do_softint, info);
+ INIT_WORK(&info->tqueue_hangup, do_serial_hangup, info);
+ info->config.rx_timeout = rx_timeout;
+ info->config.flow_on = flow_on;
+ info->config.flow_off = flow_off;
+ info->config.pio_threshold = pio_threshold;
+ info->next_port = ports;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->delta_msr_wait);
+ init_waitqueue_head(&info->break_wait);
+ spin_lock_init(&info->lock);
+ ports = info;
+ printk(KERN_INFO "ttyP%d at 0x%04x (irq = %d) is an ESP ",
+ info->line, info->port, info->irq);
+
+ if (info->line % 8) {
+ printk("secondary port\n");
+ /* 8 port cards can't do DMA */
+ info->stat_flags |= ESP_STAT_NEVER_DMA;
+
+ if (last_primary)
+ last_primary->stat_flags |= ESP_STAT_NEVER_DMA;
+ } else {
+ printk("primary port\n");
+ last_primary = info;
+ irq[i] = info->irq;
+ }
+
+ if (!dma)
+ info->stat_flags |= ESP_STAT_NEVER_DMA;
+
+ info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL);
+ if (!info)
+ {
+ printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n");
+
+ /* allow use of the already detected ports */
+ return 0;
+ }
+
+ memset((void *)info, 0, sizeof(struct esp_struct));
+ /* rx_trigger, tx_trigger are needed by autoconfig */
+ info->config.rx_trigger = rx_trigger;
+ info->config.tx_trigger = tx_trigger;
+
+ if (offset == 56) {
+ i++;
+ offset = 0;
+ } else {
+ offset += 8;
+ }
+ } while (i < NR_PRIMARY);
+
+ /* free the last port memory allocation */
+ kfree(info);
+
+ return 0;
+}
+
+static void __exit espserial_exit(void)
+{
+ int e1;
+ struct esp_struct *temp_async;
+ struct esp_pio_buffer *pio_buf;
+
+ /* printk("Unloading %s: version %s\n", serial_name, serial_version); */
+ if ((e1 = tty_unregister_driver(esp_driver)))
+ printk("SERIAL: failed to unregister serial driver (%d)\n",
+ e1);
+ put_tty_driver(esp_driver);
+
+ while (ports) {
+ if (ports->port) {
+ release_region(ports->port, REGION_SIZE);
+ }
+ temp_async = ports->next_port;
+ kfree(ports);
+ ports = temp_async;
+ }
+
+ if (dma_buffer)
+ free_pages((unsigned long)dma_buffer,
+ get_order(DMA_BUFFER_SZ));
+
+ if (tmp_buf)
+ free_page((unsigned long)tmp_buf);
+
+ while (free_pio_buf) {
+ pio_buf = free_pio_buf->next;
+ kfree(free_pio_buf);
+ free_pio_buf = pio_buf;
+ }
+}
+
+module_init(espserial_init);
+module_exit(espserial_exit);
diff --git a/drivers/char/ftape/Kconfig b/drivers/char/ftape/Kconfig
new file mode 100644
index 000000000000..7d3ecb56a1bd
--- /dev/null
+++ b/drivers/char/ftape/Kconfig
@@ -0,0 +1,340 @@
+#
+# Ftape configuration
+#
+config ZFTAPE
+ tristate "Zftape, the VFS interface"
+ depends on FTAPE
+ ---help---
+ Normally, you want to say Y or M. DON'T say N here or you
+ WON'T BE ABLE TO USE YOUR FLOPPY TAPE DRIVE.
+
+ The ftape module itself no longer contains the routines necessary
+ to interface with the kernel VFS layer (i.e. to actually write data
+ to and read data from the tape drive). Instead the file system
+ interface (i.e. the hardware independent part of the driver) has
+ been moved to a separate module.
+
+ To compile this driver as a module, choose M here: the
+ module will be called zftape.
+
+ Regardless of whether you say Y or M here, an additional runtime
+ loadable module called `zft-compressor' which contains code to
+ support user transparent on-the-fly compression based on Ross
+ William's lzrw3 algorithm will be produced. If you have enabled the
+ kernel module loader (i.e. have said Y to "Kernel module loader
+ support", above) then `zft-compressor' will be loaded
+ automatically by zftape when needed.
+
+ Despite its name, zftape does NOT use compression by default. The
+ file <file:Documentation/ftape.txt> contains a short description of
+ the most important changes in the file system interface compared to
+ previous versions of ftape. The ftape home page
+ <http://www.instmath.rwth-aachen.de/~heine/ftape/> contains
+ further information.
+
+ IMPORTANT NOTE: zftape can read archives created by previous
+ versions of ftape and provide file mark support (i.e. fast skipping
+ between tape archives) but previous version of ftape will lack file
+ mark support when reading archives produced by zftape.
+
+config ZFT_DFLT_BLK_SZ
+ int "Default block size"
+ depends on ZFTAPE
+ default "10240"
+ ---help---
+ If unsure leave this at its default value, i.e. 10240. Note that
+ you specify only the default block size here. The block size can be
+ changed at run time using the MTSETBLK tape operation with the
+ MTIOCTOP ioctl (i.e. with "mt -f /dev/qft0 setblk #BLKSZ" from the
+ shell command line).
+
+ The probably most striking difference between zftape and previous
+ versions of ftape is the fact that all data must be written or read
+ in multiples of a fixed block size. The block size defaults to
+ 10240 which is what GNU tar uses. The values for the block size
+ should be either 1 or multiples of 1024 up to a maximum value of
+ 63488 (i.e. 62 K). If you specify `1' then zftape's builtin
+ compression will be disabled.
+
+ Reasonable values are `10240' (GNU tar's default block size),
+ `5120' (afio's default block size), `32768' (default block size some
+ backup programs assume for SCSI tape drives) or `1' (no restriction
+ on block size, but disables builtin compression).
+
+comment "The compressor will be built as a module only!"
+ depends on FTAPE && ZFTAPE
+
+config ZFT_COMPRESSOR
+ tristate
+ depends on FTAPE!=n && ZFTAPE!=n
+ default m
+
+config FT_NR_BUFFERS
+ int "Number of ftape buffers (EXPERIMENTAL)"
+ depends on FTAPE && EXPERIMENTAL
+ default "3"
+ help
+ Please leave this at `3' unless you REALLY know what you are doing.
+ It is not necessary to change this value. Values below 3 make the
+ proper use of ftape impossible, values greater than 3 are a waste of
+ memory. You can change the amount of DMA memory used by ftape at
+ runtime with "mt -f /dev/qft0 setdrvbuffer #NUMBUFFERS". Each buffer
+ wastes 32 KB of memory. Please note that this memory cannot be
+ swapped out.
+
+config FT_PROC_FS
+ bool "Enable procfs status report (+2kb)"
+ depends on FTAPE && PROC_FS
+ ---help---
+ Optional. Saying Y will result in creation of a directory
+ `/proc/ftape' under the /proc file system. The files can be viewed
+ with your favorite pager (i.e. use "more /proc/ftape/history" or
+ "less /proc/ftape/history" or simply "cat /proc/ftape/history"). The
+ file will contain some status information about the inserted
+ cartridge, the kernel driver, your tape drive, the floppy disk
+ controller and the error history for the most recent use of the
+ kernel driver. Saying Y will enlarge the size of the ftape driver
+ by approximately 2 KB.
+
+ WARNING: When compiling ftape as a module (i.e. saying M to "Floppy
+ tape drive") it is dangerous to use ftape's /proc file system
+ interface. Accessing `/proc/ftape' while the module is unloaded will
+ result in a kernel Oops. This cannot be fixed from inside ftape.
+
+choice
+ prompt "Debugging output"
+ depends on FTAPE
+ default FT_NORMAL_DEBUG
+
+config FT_NORMAL_DEBUG
+ bool "Normal"
+ ---help---
+ This option controls the amount of debugging output the ftape driver
+ is ABLE to produce; it does not increase or diminish the debugging
+ level itself. If unsure, leave this at its default setting,
+ i.e. choose "Normal".
+
+ Ftape can print lots of debugging messages to the system console
+ resp. kernel log files. Reducing the amount of possible debugging
+ output reduces the size of the kernel module by some KB, so it might
+ be a good idea to use "None" for emergency boot floppies.
+
+ If you want to save memory then the following strategy is
+ recommended: leave this option at its default setting "Normal" until
+ you know that the driver works as expected, afterwards reconfigure
+ the kernel, this time specifying "Reduced" or "None" and recompile
+ and install the kernel as usual. Note that choosing "Excessive"
+ debugging output does not increase the amount of debugging output
+ printed to the console but only makes it possible to produce
+ "Excessive" debugging output.
+
+ Please read <file:Documentation/ftape.txt> for a short description
+ how to control the amount of debugging output.
+
+config FT_FULL_DEBUG
+ bool "Excessive"
+ help
+ Extremely verbose output for driver debugging purposes.
+
+config FT_NO_TRACE
+ bool "Reduced"
+ help
+ Reduced tape driver debugging output.
+
+config FT_NO_TRACE_AT_ALL
+ bool "None"
+ help
+ Suppress all debugging output from the tape drive.
+
+endchoice
+
+comment "Hardware configuration"
+ depends on FTAPE
+
+choice
+ prompt "Floppy tape controllers"
+ depends on FTAPE
+ default FT_STD_FDC
+
+config FT_STD_FDC
+ bool "Standard"
+ ---help---
+ Only change this setting if you have a special controller. If you
+ didn't plug any add-on card into your computer system but just
+ plugged the floppy tape cable into the already existing floppy drive
+ controller then you don't want to change the default setting,
+ i.e. choose "Standard".
+
+ Choose "MACH-2" if you have a Mountain Mach-2 controller.
+ Choose "FC-10/FC-20" if you have a Colorado FC-10 or FC-20
+ controller.
+ Choose "Alt/82078" if you have another controller that is located at
+ an IO base address different from the standard floppy drive
+ controller's base address of `0x3f0', or uses an IRQ (interrupt)
+ channel different from `6', or a DMA channel different from
+ `2'. This is necessary for any controller card that is based on
+ Intel's 82078 FDC such as Seagate's, Exabyte's and Iomega's "high
+ speed" controllers.
+
+ If you choose something other than "Standard" then please make
+ sure that the settings for the IO base address and the IRQ and DMA
+ channel in the configuration menus below are correct. Use the manual
+ of your tape drive to determine the correct settings!
+
+ If you are already successfully using your tape drive with another
+ operating system then you definitely should use the same settings
+ for the IO base, the IRQ and DMA channel that have proven to work
+ with that other OS.
+
+ Note that this menu lets you specify only the default setting for
+ the hardware setup. The hardware configuration can be changed at
+ boot time (when ftape is compiled into the kernel, i.e. if you
+ have said Y to "Floppy tape drive") or module load time (i.e. if you
+ have said M to "Floppy tape drive").
+
+ Please read also the file <file:Documentation/ftape.txt> which
+ contains a short description of the parameters that can be set at
+ boot or load time. If you want to use your floppy tape drive on a
+ PCI-bus based system, please read the file
+ <file:drivers/char/ftape/README.PCI>.
+
+config FT_MACH2
+ bool "MACH-2"
+
+config FT_PROBE_FC10
+ bool "FC-10/FC-20"
+
+config FT_ALT_FDC
+ bool "Alt/82078"
+
+endchoice
+
+comment "Consult the manuals of your tape drive for the correct settings!"
+ depends on FTAPE && !FT_STD_FDC
+
+config FT_FDC_BASE
+ hex "IO base of the floppy disk controller"
+ depends on FTAPE && !FT_STD_FDC
+ default "0"
+ ---help---
+ You don't need to specify a value if the following default
+ settings for the base IO address are correct:
+ <<< MACH-2 : 0x1E0 >>>
+ <<< FC-10/FC-20: 0x180 >>>
+ <<< Secondary : 0x370 >>>
+ Secondary refers to a secondary FDC controller like the "high speed"
+ controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+ Please make sure that the setting for the IO base address
+ specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+ CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+ successfully using the tape drive with another operating system then
+ you definitely should use the same settings for the IO base that has
+ proven to work with that other OS.
+
+ Note that this menu lets you specify only the default setting for
+ the IO base. The hardware configuration can be changed at boot time
+ (when ftape is compiled into the kernel, i.e. if you specified Y to
+ "Floppy tape drive") or module load time (i.e. if you have said M to
+ "Floppy tape drive").
+
+ Please read also the file <file:Documentation/ftape.txt> which
+ contains a short description of the parameters that can be set at
+ boot or load time.
+
+config FT_FDC_IRQ
+ int "IRQ channel of the floppy disk controller"
+ depends on FTAPE && !FT_STD_FDC
+ default "0"
+ ---help---
+ You don't need to specify a value if the following default
+ settings for the interrupt channel are correct:
+ <<< MACH-2 : 6 >>>
+ <<< FC-10/FC-20: 9 >>>
+ <<< Secondary : 6 >>>
+ Secondary refers to secondary a FDC controller like the "high speed"
+ controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+ Please make sure that the setting for the IO base address
+ specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+ CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+ successfully using the tape drive with another operating system then
+ you definitely should use the same settings for the IO base that has
+ proven to work with that other OS.
+
+ Note that this menu lets you specify only the default setting for
+ the IRQ channel. The hardware configuration can be changed at boot
+ time (when ftape is compiled into the kernel, i.e. if you said Y to
+ "Floppy tape drive") or module load time (i.e. if you said M to
+ "Floppy tape drive").
+
+ Please read also the file <file:Documentation/ftape.txt> which
+ contains a short description of the parameters that can be set at
+ boot or load time.
+
+config FT_FDC_DMA
+ int "DMA channel of the floppy disk controller"
+ depends on FTAPE && !FT_STD_FDC
+ default "0"
+ ---help---
+ You don't need to specify a value if the following default
+ settings for the DMA channel are correct:
+ <<< MACH-2 : 2 >>>
+ <<< FC-10/FC-20: 3 >>>
+ <<< Secondary : 2 >>>
+ Secondary refers to a secondary FDC controller like the "high speed"
+ controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+ Please make sure that the setting for the IO base address
+ specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+ CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+ successfully using the tape drive with another operating system then
+ you definitely should use the same settings for the IO base that has
+ proven to work with that other OS.
+
+ Note that this menu lets you specify only the default setting for
+ the DMA channel. The hardware configuration can be changed at boot
+ time (when ftape is compiled into the kernel, i.e. if you said Y to
+ "Floppy tape drive") or module load time (i.e. if you said M to
+ "Floppy tape drive").
+
+ Please read also the file <file:Documentation/ftape.txt> which
+ contains a short description of the parameters that can be set at
+ boot or load time.
+
+config FT_FDC_THR
+ int "Default FIFO threshold (EXPERIMENTAL)"
+ depends on FTAPE && EXPERIMENTAL
+ default "8"
+ help
+ Set the FIFO threshold of the FDC. If this is higher the DMA
+ controller may serve the FDC after a higher latency time. If this is
+ lower, fewer DMA transfers occur leading to less bus contention.
+ You may try to tune this if ftape annoys you with "reduced data
+ rate because of excessive overrun errors" messages. However, this
+ doesn't seem to have too much effect.
+
+ If unsure, don't touch the initial value, i.e. leave it at "8".
+
+config FT_FDC_MAX_RATE
+ int "Maximal data rate to use (EXPERIMENTAL)"
+ depends on FTAPE && EXPERIMENTAL
+ default "2000"
+ ---help---
+ With some motherboard/FDC combinations ftape will not be able to
+ run your FDC/tape drive combination at the highest available
+ speed. If this is the case you'll encounter "reduced data rate
+ because of excessive overrun errors" messages and lots of retries
+ before ftape finally decides to reduce the data rate.
+
+ In this case it might be desirable to tell ftape beforehand that
+ it need not try to run the tape drive at the highest available
+ speed. If unsure, leave this disabled, i.e. leave it at 2000
+ bits/sec.
+
+config FT_ALPHA_CLOCK
+ int "CPU clock frequency of your DEC Alpha" if ALPHA
+ depends on FTAPE
+ default "0"
+ help
+ On some DEC Alpha machines the CPU clock frequency cannot be
+ determined automatically, so you need to specify it here ONLY if
+ running a DEC Alpha, otherwise this setting has no effect.
+
diff --git a/drivers/char/ftape/Makefile b/drivers/char/ftape/Makefile
new file mode 100644
index 000000000000..0e67d2f8b7ec
--- /dev/null
+++ b/drivers/char/ftape/Makefile
@@ -0,0 +1,28 @@
+#
+# Copyright (C) 1997 Claus Heine.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/05 19:17:56 $
+#
+# Makefile for the QIC-40/80/3010/3020 floppy-tape driver for
+# Linux.
+#
+
+obj-$(CONFIG_FTAPE) += lowlevel/
+obj-$(CONFIG_ZFTAPE) += zftape/
+obj-$(CONFIG_ZFT_COMPRESSOR) += compressor/
diff --git a/drivers/char/ftape/README.PCI b/drivers/char/ftape/README.PCI
new file mode 100644
index 000000000000..18de159d36e0
--- /dev/null
+++ b/drivers/char/ftape/README.PCI
@@ -0,0 +1,81 @@
+Some notes for ftape users with PCI motherboards:
+=================================================
+
+The problem:
+------------
+
+There have been some problem reports from people using PCI-bus based
+systems getting overrun errors.
+I wasn't able to reproduce these until I ran ftape on a Intel Plato
+(Premiere PCI II) motherboard with bios version 1.00.08AX1.
+It turned out that if GAT (Guaranteed Access Timing) is enabled (?)
+ftape gets a lot of overrun errors.
+The problem disappears when disabling GAT in the bios.
+Note that Intel removed this setting (permanently disabled) from the
+1.00.10AX1 bios !
+
+It looks like that if GAT is enabled there are often large periods
+(greater than 120 us !??) on the ISA bus that the DMA controller cannot
+service the floppy disk controller.
+I cannot imagine this being acceptable in a decent PCI implementation.
+Maybe this is a `feature' of the chipset. I can only speculate why
+Intel choose to remove the option from the latest Bios...
+
+The lesson of this all is that there may be other motherboard
+implementations having the same of similar problems.
+If you experience a lot of overrun errors during a backup to tape,
+see if there is some setting in the Bios that may influence the
+bus timing.
+
+I judge this a hardware problem and not a limitation of ftape ;-)
+My DOS backup software seems to be suffering from the same problems
+and even refuses to run at 1 Mbps !
+Ftape will reduce the data-rate from 1 Mbps to 500 Kbps if the number
+of overrun errors on a track exceeds a threshold.
+
+
+Possible solutions:
+-------------------
+
+Some of the problems were solved by upgrading the (flash) bios.
+Other suggest that it has to do with the FDC being on the PCI
+bus, but that is not the case with the Intel Premiere II boards.
+[If upgrading the bios doesn't solve the problem you could try
+a floppy disk controller on the isa-bus].
+
+Here is a list of systems and recommended BIOS settings:
+
+
+ Intel Premiere PCI (Revenge):
+
+Bios version 1.00.09.AF2 is reported to work.
+
+
+
+ Intel Premiere PCI II (Plato):
+
+Bios version 1.00.10.AX1 and version 11 beta are ok.
+If using version 1.00.08.AX1, GAT must be disabled !
+
+
+
+ ASUS PCI/I-SP3G:
+
+Preferred settings: ISA-GAT-mode : disabled
+ DMA-linebuffer-mode : standard
+ ISA-masterbuffer-mode : standard
+
+
+ DELL Dimension XPS P90
+
+Bios version A2 is reported to be broken, while bios version A5 works.
+You can get a flash bios upgrade from http://www.dell.com
+
+
+To see if you're having the GAT problem, try making a backup
+under DOS. If it's very slow and often repositions you're
+probably having this problem.
+
+ --//--
+ LocalWords: ftape PCI bios GAT ISA DMA chipset Mbps Kbps FDC isa AF ok ASUS
+ LocalWords: SP linebuffer masterbuffer XPS http www com
diff --git a/drivers/char/ftape/RELEASE-NOTES b/drivers/char/ftape/RELEASE-NOTES
new file mode 100644
index 000000000000..03799dbc05a4
--- /dev/null
+++ b/drivers/char/ftape/RELEASE-NOTES
@@ -0,0 +1,966 @@
+Hey, Emacs, we're -*-Text-*- mode!
+
+===== Release notes for ftape-3.04d 25/11/97 =====
+- The correct pre-processor statement for "else if" is "#elif" not
+ "elsif".
+- Need to call zft_reset_position() when overwriting cartridges
+ previously written with ftape-2.x, sftape, or ancient
+ (pre-ftape-3.x) versions of zftape.
+
+===== Release notes for ftape-3.04c 16/11/97 =====
+- fdc_probe() was calling DUMPREGS with a result length of "1" which
+ was just fine. Undo previous change.
+
+===== Release notes for ftape-3.04b 14/11/97 =====
+
+- patches/2.x.x/floppy.c.diff was somewhat broken, releasing i/o
+ regions it never had allocated.
+- fdc_probe() was calling DUMPREGS with a result length of "1" instead
+ of "10"
+- Writing deleted data marks if the first segents on track zero are
+ should work now.
+- ftformat should now be able to handle those cases where the tape
+ drive sets the read only status bit (QIC-40/80 cartridges with
+ QIC-3010/3020 tape drives) because the header segment is damaged.
+- the MTIOCFTCMD ioctl may now be issued by the superuser ONLY.
+
+===== Release notes for ftape-3.04a 12/11/97 =====
+- Fix an "infinite loop can't be killed by signal" bug in
+ ftape_get_drive_status(). Only relevant when trying to access
+ buggy/misconfigured hardware
+- Try to compensate a bug in the HP Colorado T3000's firmware: it
+ doesn't set the write protect bit for QIC80/QIC40 cartridges.
+
+===== Release notes for ftape-3.04 06/11/97 =====
+- If positioning with fast seeking fails fall back to a slow seek
+ before giving up.
+- (nearly) no retries on "no data errors" when verifying after
+ formatting. Improved tuning of the bad sector map after formatting.
+- the directory layout has changed again to allow for easier kernel
+ integration
+- Module parameter "ftape_tracing" now is called "ft_tracing" because
+ the "ftape_tracing" variable has the version checksum attached to it.
+- `/proc/ftape' interface for 2.0.* kernels. `/proc/ftape' no longer
+ is a directory but a file that contains all the information formerly
+ provided in separate files under the `/proc/ftape/' directory.
+- Most of the configuration options have been prefixed by "CONFIG_FT_"
+ in preparation of the kernel inclusion. The Makefiles under
+ "./ftape/" should be directly usable by the kernel.
+- The MODVERSIONS stuff is now auto-detected.
+- Broke backslashed multi line options in MCONFIG into separate lines
+ using GNU-make's "+=" feature.
+- The html and dvi version of the manual is now installed under
+ '/usr/doc/ftape` with 'make install`
+- New SMP define in MCONFIG. ftape works with SMP if this is defined.
+- attempt to cope with "excessive overrun errors" by gradually
+ increasing FDC FIFO threshold. But this doesn't seem to have too
+ much an effect.
+- New load time configuration parameter "ft_fdc_rate_limit". If you
+ encounter too many overrun errors with a 2Mb controller then you
+ might want to set this to 1000.
+- overrun errors on the last sector in a segment sometimes result in
+ a zero DMA residue. Dunno why, but compensate for it.
+- there were still fdc_read() timeout errors. I think I have fixed it
+ now, please FIXME.
+- Sometimes ftape_write() failed to re-start the tape drive when a
+ segment without a good sector was reached ("wait for empty segment
+ failed"). This is fixed. Especially important for > QIC-3010.
+- sftape (aka ftape-2.x) has vanished. I didn't work on it for
+ ages. It is probably still possible to use the old code with
+ ftape-3.04, if one really needs it (BUT RECOMPILE IT)
+- zftape no longer alters the contents of already existing volume
+ table entries, which makes it possible to fill in missing fields,
+ like time stamps using some user space program.
+- ./contrib/vtblc/ contains such a program.
+- new perl script ./contrib/scripts/listtape that list the contents of a
+ floppy tape cartridge parsing the output of "mt volinfo" + "mt fsf"
+- the MTWEOF implementation has changed a little bit (after I had a
+ look at amanda). Calling MTWEOF while the tape is still held open
+ after writing something to the tape now will terminate the current
+ volume, and start a new one at the current position.
+- the volume table maintained by zftape now is a doubly linked list
+ that grows dynamically as needed.
+
+ formatting floppy tape cartridges
+ ---------------------------------
+ * there is a new user space formatting program that does most of the
+ dirty work in user space (auto-detect, computing the sector
+ coordinates, adjusting time stamps and statistics). It has a
+ simple command line interface.
+ * ftape-format.o has vanished, it has been folded into the low level
+ ftape.o module, and the ioctl interface into zftape.o. Most of the
+ complicated stuff has been moved to user space, so there was no
+ need for a separate module anymore.
+ * there is a new ioctl MTIOCFTCMD that sends a bare QIC-117 command
+ to the tape drive.
+ * there is a new mmap() feature to map the dma buffers into user
+ space to be used by the user level formatting program.
+ * Formatting of yet unformatted or totally degaussed cartridges
+ should be possible now. FIXME.
+
+===== Release notes for ftape-3.03b, <forgot the exact date> ====
+
+ftape-3.03b was released as a beta release only. Its main new feature
+was support of the DITTO-2GB drive. This was made possible by reverse
+engineering done by <fill in his name> after Iomega failed to support
+ftape. Although they had promised to do so (this makes me feel a bit
+sad and uncomfortable about Iomega).
+
+===== Release notes for ftape-3.03a, 22/05/97 ====
+
+- Finally fixed auto-un-loading of modules for kernels > 2.1.18
+- Add an "uninstall" target to the Makefile
+- removed the kdtime hack
+- texi2www didn't properly set the back-reference from a footnote back
+ to the regular text.
+
+ zftape specific
+ ---------------
+ * hide the old compression map volume. Taper doesn't accept the
+ presence of non-Taper volumes and Taper-written volume on the same
+ tape.
+ * EOD (End Of Data) handling was still broken: the expected behavior
+ is to return a zero byte count at the first attempt to read past
+ EOD, return a zero byte count at the second attempt to read past
+ EOD and THEN return -EIO.
+
+ ftape-format specific
+ ---------------------
+ * Detection of QIC-40 cartridges in select_tape_format() was broken
+ and made it impossible to format QIC-3010/3020 cartridges.
+ * There are strange "TR-1 Extra" cartridges out there which weren't
+ detected properly because the don't strictly conform to the
+ QIC-80, Rev. N, spec.
+
+===== Release notes for ftape-3.03, 30/04/97 =====
+
+- Removed kernel integration code from the package. I plan to provide
+ a package that can be integrated into the stock kernel separately
+ (hopefully soon).
+ As a result, a simple `make' command now will build everything.
+- ALL compile time configuration options have been moved to the file
+ `MCONFIG'.
+- Quite a few `low level' changes to allow formatting of cartridges.
+- formatting is implemented as a separate module `ftape-format.o'. The
+ modified `mt' program contains sample code that shows how to use it.
+- The VFS interface has been moved from the `ftape.o' module to the
+ high level modules `zftape.o' resp. `sftape.o'. `ftape.o' contains
+ the hardware support only.
+- A bit of /proc support for kernels > 2.1.28
+- Moved documentation to Doc subdir. INSTALL now contains some real
+ installation notes.
+- `install' target in Makefile.
+
+zftape specific:
+----------------
+
+- zftape works for large cartridges now ( > 2^31 bytes)
+- MTIOCVOLINFO and MTIOCGETSIZE now return the size in KILOBYTES,
+ NO LONGER in bytes.
+
+- permissions for write access to a cartridge have changed:
+ * zftape now also takes the file access mode into account
+ * zftape no longer allows writing in the middle of the recorded
+ media. The tape has to be positioned at BOT or EOD for write
+ access.
+
+- MTBSF has changed. It used to position at the beginning of the
+ previous file when called with count 1. This was different from the
+ expected behavior for other Un*x tape drivers (i.e. SCSI). MTBSF
+ with count 1 should merely position at the beginning of the current
+ volume. Fixed. As a result, `tar --verify' now produces the desired
+ result: it verifies the last written volume, not the pre-last
+ written volume.
+
+- The compression map has vanished --> no need for `mt erase' any
+ more. Fast seeking in a compressed volume is still be possible, but
+ takes slightly longer. As a side effect, you may experience an
+ additional volume showing up in front of all others for old
+ cartridges. This is the tape volume that holds the compression map.
+
+- The compression support for zftape has been moved to a separate
+ module `zft-compressor'. DON'T forget to load it before trying to
+ read back compressed volumes. The stock `zftape.o' module probes for
+ the module `zft-compressor' using the kerneld message channel; you
+ have to install `zft-compressor.o' in a place where modprobe can
+ find it if you want to use this.
+
+- New experimental feature that tries to get the broken down GMT time
+ from user space via a kernel daemon message channel. You need to
+ compile and start the `kdtime' daemon contained in the contrib
+ directory to use it. Needed (?) for time stamps in the header
+ segments and the volume table.
+
+- variable block size mode via MTSETBLK 0
+
+- keep modules locked in memory after the block size has been changed
+
+sftape specific:
+----------------
+
+- end of tape handling should be fixed, i.e. multi volume archives
+ written with `afio' can be read back now.
+
+
+===== Release notes for ftape-3.02a, 09/01/97 =====
+
+No big news:
+- call zft_init() resp. sft_init() when compiling the entire stuff
+ into the kernel image.
+- fix bug in ftape-setup.c when NO_TRACE_AT_ALL was defined.
+- fix bug in sftape-eof.c/zftape-eof.c for old kernels (1.2.*)
+- add support for new module interface for recent kernels
+
+===== Release notes for ftape-3.02, 16/12/96 =====
+- Fixed the `FDC unlock command failed' bug in fdc-io.c. When the FIFO
+ was already locked when ftape was loaded, ftape failed to unlock it.
+- Fixed compilation of `contrib/gnumt'. It now finds `mtio.h' even if
+ ftape is NOT included into the kernel source tree.
+- fc-10.c: include <asm/io.h> for inb() and outb().
+- ftape/sftape/zftape: all global variable now have either a `ftape_',
+ a `ft_', `sft_', `zft_' or `qic_' prefix to prevent name clashes
+ with other parts of the kernel when including ftape into the kernel
+ source tree.
+- Kerneld support has changed. `ftape' now searches for a module
+ `ftape-frontend' when none of the frontend (`sftape' or `zftape') is
+ loaded. Please refer to the `Installation/Loading ftape' section of
+ the TeXinfo manual.
+- Add load resp. boot-time configuration of ftape. There are now
+ variables ft_fdc_base, ft_fdc_dma and ft_fdc_irq corresponding to
+ the former FDC_BASE etc. compile time definitions. One can also use
+ the kernel command line parameters to configure the driver if it is
+ compiled into the kernel. Also, the FC-10/FC-20 support is load-time
+ configurable now as well as the MACH-II hack (ft_probe_fc10,
+ resp. ft_mach2). Please refer to the section `Installation/Configure
+ ftape' of the TeXinfo manual.
+- I removed the MODVERSIONS option from `Makefile.module'. Let me alone
+ with ftape and MODVERSIONS unless you include the ftape sources into
+ the kernel source tree.
+- new vendors in `vendors.h':
+ * HP Colorado T3000
+ * ComByte DoublePlay (including a bug fix for their broken
+ formatting software, thanks to whraven@njackn.com)
+ * Iomega DITTO 2GIG. NOTE: this drive cannot work with ftape because
+ the logical data layout of the cartridges used by this drive does
+ NOT conform to the QIC standards, it is a special Iomega specific
+ format. I've sent mail to Iomega but didn't receive an answer
+ yet. If you want this drive to be supported by ftape, ask Iomega
+ to give me information about it.
+- zftape:
+ * re-introduced the MTIOC_ZFTAPE_GETBLKSZ ioctl for compatibility
+ with zftape 1.06a and earlier. Please don't use it when writing
+ new software, use the MTIOCVOLINFO ioctl instead.
+ * Major overhaul of the code that updates the header segments. Never
+ change the tape label unless erasing the tape. Thus we almost
+ never need to write the header segments, unless we would modify
+ the bad sector map which isn't done yet. Updating of volume table
+ and compression map more secure now although it takes a bit
+ longer.
+ * Fixed bug when aborting a write operation with a signal: zftape
+ now finishes the current volume (i.e. writes an eof marker) at the
+ current position. It didn't before which led to somehow *strange*
+ behavior in this cases.
+ * Keep module locked in memory when using it with the non-rewinding
+ devices and the tape is not logical at BOT. Needed for kerneld
+ support.
+- sftape:
+ * Keep module locked in memory when using it with the non-rewinding
+ devices and the tape is not logical at BOT. Needed for kerneld
+ support.
+
+===== Release notes for ftape-3.01, 14/11/96 =====
+
+- Fixed silly bugs in ftape-3.00:
+ * MAKEDEV.ftape: major device number must be 27, not 23
+ * sftape/sftape-read.c: sftape_read_header_segments() called
+ itself recursively instead of calling ftape_read_header_segment()
+ * zftape/qic-vtbl.h: conversion of ftape's file marks to zftape's
+ internal volume table was broken.
+ * patches/2.x.x/linux-2.0.21.dif: my RCS (resp. CVS) system replaced
+ the `$Revison:' etc. macros in the `ftape.h' concerning part of the
+ patch :-( Fixed.
+ * info/ftape.info: Fixed misspellings (`cp' <-> `cp -r' etc.)
+ * when ftape/sftape or ftape/zftape was compiled into the kernel the
+ variable ftape_status was declared twice. Fixed.
+ * removed reference to undeclared variable kernel_version when not
+ compiling as module
+ * fixed a bug introduced by the use of bit-fields for some flags
+ (i.e. write_protected, no_cartridge, formatted)
+ * flag `header_read' is now reset correctly to zero when tape is
+ removed.
+- fixed a bug in sftape/sftape-eof.c that was already in the original
+ ftape code. MTFSF/BSF was not handled correctly when positioned
+ right before the file mark (think of tar)
+- Changed TRACE macros (following a suggestion of Marcin Dalecki) to use
+ the predefined __FUNCTION__ macro of GCC. Spares about 4k of code.
+- added new vendor id for Iomega DITTO 2GIG
+- fixed a bug already present in zftape-1.06 when aborting a write
+ with a signal: we now finish the current volume at that
+ position. Header segments remain NOT up to date until an explicit call
+ to MTREW or MTOFFL is done.
+
+===== Release notes for ftape-3.00, 14/10/96 =====
+
+- Merged ftape with zftape. There are three modules now:
+ ftape for the hardware support, sftape for the implementation of the
+ original ftape eof mark stuff and zftape that implements zftape's way
+ of handling things (compression, volume table, tape blocks of
+ constant length)
+- Documentation in TeXinfo format in the `info' subdirectory.
+- New ioctls for zftape. See zftape/zftape.h
+- Dummy formatting ioctl for ftape. See ftape.h
+- Kernel patch files for the 2.*.* series to include ftape-3.00 in the
+ kernel source tree. These includes a kernel compatible Config.in
+ script and fairly large online information for the kernel configure
+ script.
+- Support for compiling with Linux-1.2.13.
+- Modified GNU mt from their cpio package that can handle the new
+ ioctls.
+- ftape/sftape/zftape is kerneld save now!
+
+Notes on sftape:
+- sftape implements the eof handling code of the original ftape. If
+ you like to stick with the original ftape stuff, you have to use
+ this module, not zftape.
+- sftape is kerneld save, unlike the original ftape.
+- we keep the entire header segment now in memory, so no need to read
+ it before updating the header segments. Additional memory
+ consumption: 256 bytes.
+
+Notes for zftape:
+- zftape has support for tapes with format code 6 now, which use a
+ slightly different volume table format compared with other floppy
+ tapes.
+- new ioctls for zftape. Have a look at zftape/zftape.h
+- The internal volume table representation has changed for zftape. Old
+ cartridges are converted automatically.
+- zftape no longer uses compression map segments, which have vanished
+ from the QIC specs, but creates volume table entry that reserves
+ enough space for the compression map.
+- zftape is kerneld save now.
+- we keep the entire header segment now in memory, so no need to read
+ it before updating the header segments. Additional memory
+ consumption: 256 bytes.
+
+Notes for contrib/gnumt:
+- modified mt from the GNU cpio package that supports all the new
+ ioctls of zftape.
+Notes for contrib/swapout:
+- This contains the swapout.c program that was written by Kai
+ Harrekilde-Pederson. I simply added a Makefile.
+
+===== Release notes for ftape-2.10, 14/10/96 =====
+
+The ftape maintainer has changed.
+Kai Harrekilde-Petersen <khp@dolphinics.no>
+has resigned from maintaining ftape, and I,
+Claus-Justus Heine <claus@momo.math.rwth-aachen.de>,
+have taken over.
+
+- Added support for tapes with `format code 6', i.e. QIC-3020 tapes
+ with more than 2^16 segments.
+- merged changes made by Bas Laarhoven with ftape-2.09. Refer
+ to his release notes below. I've included them into this
+ file unchanged for your reference.
+- disabled call stack back trace for now. This new feature
+ introduced by the interim release 2.0.x still seems to
+ be buggy.
+- Tried to minimize differences between the ftape version
+ to be included into the kernel source tree and the standalone
+ module version.
+- Reintroduced support for Linux-1.2.13. Please refer to the
+ Install-guide.
+
+===== Release notes for ftape-2.09, 16/06/96 =====
+
+There aren't any really big news in this release, mostly just that I
+(the maintainer) have changed my email address (due to a new job). My
+new address is <khp@dolphinics.no>
+
+- The CLK_48MHZ and FDC_82078SL options has gone (all 2Mbps cards seem
+ to use a 48MHz oscillator anyway and I haven't heard of an 'SL
+ chip out there).
+- The S82078B has been `downgraded' to i82077AA compability.
+- TESTING option revived. Right now, it'll enable the (seriously broken)
+ 2Mbps code. If you enable it, you'll experience a tape drive that's
+ *really* out to lunch!
+- Some (bold) changes in the init code. Please notify me if they
+ break things for you.
+
+===== Release notes for ftape-2.08, 14/03/96 =====
+
+If you correct a problem with ftape, please send your patch to
+khp@dolphinics.no too.
+
+- Updated to reflect that NR_MEM_LISTS is gone in 1.3.74
+- Teac 700 added to list of known drives.
+- The registered device name is now "ft" rather than "ftape".
+
+===== Release notes for ftape-2.07a, 14/03/96 =====
+
+Bugfixes by Marcin Dalecki <dalecki@namu03.gwdg.de>:
+- In the last release it just compiled against 1.3.70;
+ now the params to request_irq() and free_irq are() are fixed, so it also
+ works in 1.3.73 :-)
+- Support for modules is now correct for newer kernels.
+
+===== Release notes for ftape-2.07, 04/03/96 =====
+
+
+- ftape updated to compile against 1.3.70.
+- Iomega 700 and Wangtek 3200 recognised.
+
+
+===== Release notes for ftape-2.06b, 13/02/96 =====
+
+Another simple bugfix version.
+
+- Jumbo 700 recognised.
+- Typo in vendors.h fixed.
+
+
+===== Release notes for ftape-2.06a, 10/02/96 =====
+
+This release is a simple bugfix version.
+
+- Linux/SMP: ftape *should* work.
+- FC-10/20: Only accepts IRQs 3-7, or 9. If IRQ 9, properly tell the card
+ to use IRQ 2. Thanks to Greg Crider (gcrider@iclnet.org) for finding and
+ locating this bug and testing the patch.
+- Insight drive recognised correctly again.
+- Motor-on wakeup version of the Iomega 250 drive added
+
+
+===== Release notes for ftape-2.06, 28/01/96 =====
+
+Special thanks go to Neal Friedman and Steven Sorbom for their
+help in producing and testing this release.
+
+I have continued to clean up the code, with an eye towards inclusion
+of ftape in Linus' official kernel (In fact, as I type this, I am
+running on a kernel with ftape support statically linked). I have
+test-compiled ftape against my 1.2.13 tree without problems.
+Hopefully, everything should be OK for the v1.2.x people.
+
+WARNING! Alan Cox has mailed me that ftape does *NOT* work with
+Linux/SMP. If you try to run ftape under Linux/SMP, it will cause a
+kernel deadlock (which is worse than a panic).
+
+- QIC-3020/TR-3: 1Mbps support works. Neal is capable of reading and
+ writing data to a tape. ftape will automatically detect the type of
+ tape (e.g. TR-3 vs QIC-80) and move the fdc in and out of
+ "perpendicular mode" as necessary.
+- 2Mbps support is disabled by default, since it is not fully
+ debugged. If you are adventurous, remove -DFDC_82078SL in the
+ Makefile and see what happens :-)
+- fdc detection: silly bugs removed (Only 2Mbps fdcs were affected)
+ and added detection of the National Semiconductors PC8744 fdc chip
+ (used in the PC873xx "super-IO" chips).
+- Removed warning about incompatible types when compiling with Linux
+ 1.2.x.
+- README.PCI updated with info about the DELL Dimension XPS P90.
+- Connor TST3200R added to detected drives.
+- `swapout' utility added to distribution. It will dirty 5Meg of
+ memory, trying to swap out other programs. Just say `make swapout'
+ to build it. ftape will do this automatically Real Soon Now (ie:
+ when I have found out which kernel memory alloc function to call).
+
+
+===== Release notes for ftape-2.05, 08/01/96 =====
+
+- For v1.2.x Kernels, you must apply the patch linux-1.2/ksyms.patch to
+ the kernel and rebuild it (it adds the __get_dma_pages symbol to
+ ksyms.c).
+- Included new asm-i386/io.h file from v1.3.x kernel series, to enable
+ gcc v.2.7.[12] to compile v1.2.x kernels (linux-1.2/io.h).
+- Module versions: If you wish to compile ftape as a versioned module,
+ you must first compile your kernel with CONFIG_MODVERSIONS=y.
+ Otherwise, you will get complaints that <linux/modversions.h> does not
+ exist (if that happens, a `touch modversions.h' will help you out).
+- CLK_48MHZ: new define in the Makefile (default: non-zero). If you have
+ a tape controller card that uses the i82078(-1) chip, but cannot get
+ it to work with ftape, try set it to 0 (and please report this).
+- QIC-3010/3020: Complete support is still missing, but will hopefully
+ come soon. Steven Sorbom has kindly provided me with hints about
+ this. Writing of QIC-3020 tapes definitely does NOT work (do not try
+ it! - the drive will not be in "perpendicular mode" and this will ruin
+ the formatting info on the tape).
+- ftape_num_buffers is out of fashion: use NR_BUFFERS instead (and
+ recompile if you want to change it :-).
+
+
+===== Release notes for ftape-2.04, 01/01/96 =====
+
+This version by Kai Harrekilde-Petersen <khp@dolphinics.no>
+
+- ALERT! Support for Kernels earlier then v1.1.85 is about to go away.
+ I intend to clean up some of the code (getting rid of an annoyingly
+ large numbers of #ifdef mostly), which means that support for
+ pre-1.1.85 kernels must go as well.
+- NR_FTAPE_BUFFERS is gone; You can instead select the number of dma
+ buffers by saying `insmod ftape.o ftape_num_buffer=<n>' instead.
+- Configure script gone. ftape will now automagically determine your
+ kernel version by /usr/include/linux/version.h instead.
+- CONFIG_MODVERSIONS now work. All combinations of versioned /
+ unversioned kernel and ftape module works (at least with my 1.3.52
+ kernel).
+- If you have problems with inserting ftape into an old (1.2.x)
+ kernel (e.g. insmod says "1.2.8 does not match 1.2.8), recompile
+ your modules utilities with your new compiler.
+- Reveal TB1400 drive added to vendors.h
+- Support for the i82078-1 (2Mbps) chip is coming along. The
+ biggest problem is that I don't have such a card, which makes
+ testing / debugging somewhat problematic. The second biggest
+ problem is that I do not have the QIC-3010/3020 standards either.
+ Status right now is that the chip is detected, and it should be
+ possible to put it into 2Mbps mode. However, I do not know what
+ "extras" are needed to complete the support. Although putting the
+ i82078 into 1Mbps mode ought to work out of the box, it doesn't
+ (right now, ftape complains about id am errors).
+
+
+===== Release notes for ftape-2.04beta5, 29/12/95 =====
+
+Bas offline linux-tape
+----------------------
+For reasons only known to the majordomo mail list processor, Bas was
+kicked off the linux-tape list sometime during the summer. Being
+overworked at his for-pay job, he didn't notice it much. Instead I
+(Kai, khp@dolphinics.no) has worked on ftape to produce the 2.04(beta)
+version.
+
+zftape
+------
+Note that there exists a much improved version of ftape, written by
+Claus-Justus Heine <claus@willi.math.rwth-aachen.de> which is named
+zftape, which conforms to the QIC-80 specs on how to mark backups, and
+is capable of doing automatic compression. However, zftape makes
+substantial changes to ftape, and I (Kai) have therefore declined to
+integrate zftape into ftape. Hopefully, this will happen soon.
+
+CONFIG_QIC117 removed from the kernel
+-------------------------------------
+The biggest change of all is that ftape now will allocate its dma
+buffers when it is inserted. The means that the CONFIG_QIC117 option
+has disappeared from the Linux kernel as of v1.3.34. If you have an
+earlier kernel, simply answer 'no' to the question will do the trick
+(if you get complains about __get_free_pages() missing, contact the
+linux-tape mailing list).
+
+Note that ftape-2.04beta will work equally well on kernels with and
+without `ftape support'. The only catch is, that you will waste
+around 96-128Kb of precious DMA'able memory on a box that has ftape
+support compiled in.
+
+Now for the real changes:
+
+- FC-20 can now use DMA channels 1, 2, and 3. Thanks to Daniel
+ Cohen, catman@wpi.edu.
+- ftape no longer requires a (gigantic) 96Kb buffer to be statically
+ allocated by the kernel.
+- Added new Iomega drive (8882) to vendors.h
+- -fno-strength-reduce added to Makefile, since GCC is broken.
+- i82078-1 (2Mbps) FDC support started.
+
+
+===== Release notes for ftape-2.03b, 27/05/95 =====
+
+- Prevented verify_area to return error if called with zero length.
+- Fixed a bug in flush_buffers that caused too much padding to be
+ written when a final segment had bad sectors.
+- Increased maximum fast-seek overshoot value from 5 to 10 segments.
+- Breaking loop after 5 retries when positioning fails.
+- Fixed wrong calculation of tape length for QIC-3010 and QIC-3020
+ tapes (densities were swapped).
+- Fixed wrong calculation of overshoot on seek_forward: Wrong sign
+ of error.
+- Suppress (false) error message due to new tape loaded.
+- Added two new CMS drives (11c3 and 11c5) to vendors.h.
+
+
+===== Release notes for ftape-2.03a, 09/05/95 =====
+
+- Fixed display of old error (even if already cleared) in ftape_open.
+- Improved tape length detection, ioctls would fail for 425 ft tapes.
+ Until the tape length is calculated with data from the header
+ segment, we'll use worst-case values.
+- Clear eof_mark after rewinding ioctls.
+- Fixed wrong version message (2.03 had 2.02g id).
+- Fixed bug that caused the fdc to be reset very frequently.
+ This shouldn't affect normal operation but the timing of the
+ report routines has changed again and that may cause problems.
+ We'll just have to find out....
+- Implemented correct write precompensation setting for QIC-3010/3020.
+- Cleaned up fdc_interrupt_wait routine. Hope it still works :-)
+- Finally removed (already disabled) special eof mark handling for
+ gnu tar.
+- Changed order of get_dma_residue and disable_dma in fdc-isr.c
+ because the current order would fail on at least one system.
+ We're back to the original order again, hope (and expect) this
+ doesn't break any other system.
+
+
+===== Release notes for ftape-2.03, 07/05/95 =====
+
+(Changes refer to the first ftape-2.02 release)
+
+Support for wide and extended length tapes
+------------------------------------------
+The Conner TSM 420 and 850 drives are reported to be working.
+I haven't received any reports about other brands; the TSM 420
+and 850 seem to be the most widely used wide drives.
+Extended length tapes (425 ft) with normal QIC-80 drives
+are operating too (At least I've had no reports stating otherwise).
+_Not_ yet completely supported (although they may work) are
+QIC-3020 drives and 2 Mbps floppy disk controllers won't work at
+the highest speed.
+If someone is kind enough to send me one of these, I'll include
+support for it too ;-)
+
+Easier configuration
+--------------------
+Problems due to wrong settings in the Makefile are prevented
+by using a configuration script that sets the necessary (kernel
+version dependent) compile time options.
+This kernel version is now determined from the sources found
+at /usr/src/linux, or if not found, the old way using
+/proc/version.
+Versioned modules will be used automatically when supported
+by- and configured in- the kernel.
+Note that the current modules code (1.1.87) is still broken
+and _needs_ the fix included in the insmod directory.
+Please don't send me any more Oops reports caused by insmod :-(
+
+Reduced module size
+-------------------
+The standard module size is much reduced and some compile time
+options can even reduce it further. (I don't recommend this
+for normal use but it can be handy for rescue diskettes)
+
+Option: Approx. module size:
+
+<standard> 150 Kb
+NO_TRACE 125 Kb
+NO_TRACE_AT_ALL 67 Kb
+
+
+Much improved driver interruption
+---------------------------------
+Most possible loops have been broken and signal detection
+has been improved.
+In most cases the driver can be aborted by ^C (SIGINT) and
+SIGKILL (kill -9) will generate be a sure kill.
+(Note that aborting a tape operation may damage the last
+data written to tape)
+
+Improved error recovery
+-----------------------
+Ftape now returns an error (ENODATA) to the application if
+a segment proves to be unrecoverable and then skips the
+bad segment.
+This causes most applications to continue to work (tar
+and afio) loosing only a small amount (up to 29 Kb) of data.
+Retried read operations will now be done slightly off-track
+to improve the chance of success. Serious head off-track
+errors will be detected.
+
+FC-10 and FC-20 controllers
+---------------------------
+Ftape now supports both the old CMS FC-10 and the newer FC-20
+controllers.
+Because the operation of these cards is still undocumented,
+thus far they will only work with the default settings (See
+Makefile). Any feed-back on how to use them with other settings
+will be welcome !
+Compilation will fail if one changes the settings to illegal
+values.
+
+Kernels and compilers
+---------------------
+Ftape is currently being developed using the 2.5.8 compiler.
+The older 2.4.5 probably works too (Set option in Makefile!).
+I have no experience with any later compilers nor Elf support.
+Any information on this is welcome.
+The latest kernel I have tested ftape with is 1.2.6.
+
+Compression
+-----------
+An impressive collection of changes for ftape including
+on-the-fly compression is still lying on my desk.
+If 2.03 proves to be reliable I might start integrating these
+but as usual, I'm short in time :-(
+
+Formatting
+----------
+There is still no way to format tapes under Linux. As far as
+I know all attempts to write such a program have died now.
+Since formatted tapes are rather common now, I think all we
+need is a utility that writes a worst case pattern and verifies
+that with the drive put in verify mode, reducing margins.
+Any takers ?
+
+Furthermore
+-----------
+Cleaned up messages.
+Prepared to support multiple tape drives on one fdc.
+Thanks to all the people who sent bug reports and helped me
+improve the driver. Without trying to be complete I'll mention
+Gary Anderson (without his accurate reports and unreliable
+hardware there wouldn't be a 2.03), Stefan Kneifel (FC-20),
+Robert Broughton (FC-20, you were almost there ;-), Bjorn
+Ekwall (for the versioned modules and buggy insmod ;-), Peter
+Fox, Christopher Oliver, Ralph Whittaker and not the least
+Linus Torvalds (for Linux and keeping me busy because of
+changes to the kernel ;-)
+Thanks to anyone I forgot, for the bug reports, the ftape
+bashing and the mental support...
+
+
+That's it for now. Have Fun,
+
+Bas.
+
+
+===== Release notes for ftape-2.02g, 06/05/95 =====
+
+- Added extra test to break read-id loop with signal.
+- Changed rewind code to handle negative overshoot for drives
+ that take very long to start or stop.
+- Let use of get/set i/o-regions depend on kernel version.
+- Changed code to use a more general test for conditional
+ compilations depending on kernel version.
+- Improved micro-step functionality to go off-track only
+ while reading (id & data).
+- Added failure on tape-not-referenced bit in ftape_command.
+- Added FOREVER option to read-wait routine.
+- Changed read-id to use shorter timeout causing smaller
+ rewinds on timeout.
+- Made kernel-interface functions static.
+
+
+===== Release notes for ftape-2.02f, 03/05/95 =====
+
+- Added support for dual tape drives on my system, extended Configure
+ script to detect host 'dodo'.
+- Log media defect in history if ecc failed and no data was returned.
+- Fixed Configure script that was failing for kernel versions with
+ double digit version or revision numbers.
+
+
+===== Release notes for ftape-2.02e, 01/05/95 =====
+
+- Fixed reposition loop at logical eot (failing read_id).
+- Fixed 34 segment offset when rewinding.
+- Added fast seek capability for more than 255 segments.
+- Fixed wrong busy result from ftape_command causing reverse
+ seek to fail.
+- Added breakout from infinite rewind loop (if something fails).
+
+
+===== Release notes for ftape-2.02d, 30/04/95 =====
+
+- Improved abortion on signals: Interrupt will make a graceful
+ exit, Kill will be less nice and should be used if everything
+ else fails.
+- Included check for tape-head off track.
+- Implemented exit from tape-start loop.
+- Added kernel io-port registration.
+- Implemented skip of failing segment (ENODATA) on ecc failure.
+ This allows afio and tar to continue when the tape is damaged.
+- Made distinction between drive names with different codes.
+
+
+===== Release notes for ftape-2.02c, 22/04/95 =====
+
+- Fixed too tight command queueing after tape stop/pause command
+ issued from within interrupt service routine (Showed as timeout
+ on Acknowledge errors during retries on some systems)
+- Tried to fix timeouts when using 425 ft tape because the extended
+ length doesn't seem to be detected by the hardware.
+ We now use the format code from the header segment so adjust the
+ timing after reading the header segment.
+- Fixed some messages stating 'unexpected something...' being not
+ unexpected anymore.
+- Started preparations for merge of dynamic buffer allocation and
+ compression code.
+- Changed some debug messages to include relevant segment information
+ at level 4.
+- Included early bail-out when drive offline, preventing a lot of
+ false messages.
+- Moved ftape_parameter_xxx() offsets into function instead of in calls.
+- Removed 'weird, drive busy but no data' error when caused by
+ an error during a read-id.
+- Improved 'timeout on acknowledge' diagnostics.
+- Moved MODULE option into Configure.
+- Reduced code size when no tracing at all was set (Claus Heine).
+- No longer log error code 0 (no error) as an error.
+
+
+===== Release notes for ftape-2.02b, 09/04/95 =====
+
+- Relaxed timing for status operation and displaying
+ abnormal results. Hopefully this shows what's going
+ wrong with the Conner TSM850R drives.
+- Created script for configuration, using version number
+ of kernel source if available, otherwise /proc/version.
+- Fixed conditionals in kernel-interface.c.
+- Removed unavoidable TRACE output.
+
+
+===== Release notes for ftape-2.02a, 01/04/95 =====
+
+- Implemented `new-style' (versioned) modules support for new
+ kernels.
+- Reduced size of module by moving static data to bss.
+- Now using version number of kernel source instead of running
+ kernel for kernel versions >= 1.1.82
+- Added feedback on drive speeds to vendor information.
+- Included fixed insmod sources to distribution (Let's hope
+ the modules distribution get fixed soon :-/).
+
+Note that I haven't yet implemented any of the code extension I
+received. I hope to find some time to do this soon.
+
+
+===== Release notes for ftape-2.02, 15/01/95 =====
+
+
+- Fixed failing repositioning when overshoot was incremented.
+- Fixed rate selection: Because of a deficiency in the QIC-117
+ specification one cannot distinguish between a not implemented
+ and a failing command. Therefor we now try to find out if the
+ drive does support this command before usage.
+- Fixed error retry using wrong offset in fdc-isr.
+- Improved retry code to retry only once on a single no-data
+ error in a segment.
+- Validate sector number extracted from eof mark because an
+ invalid file mark (due to ???) could cause kernel panic.
+- Split ftape-io.c into ftape-io.c and ftape-ctl.c files.
+- Corrected too high media error count after writing to
+ a bad tape.
+- Added #include <asm/segment.h> again because old kernel versions
+ need it.
+- Fixed fdc not being disabled when open failed because no tape
+ drive was found.
+- Fixed problem with soft error in sector 32 (shift operator with
+ shiftcount 32 is not defined).
+
+
+===== Release notes for ftape-2.01, 08/01/95 =====
+
+
+- Removed TESTING setting from distributed Makefile.
+- Fixed `mt asf' failure: Rewind was deferred to close which
+ overruled the fsf ioctl.
+- Prevented non-interruptible commands being interrupted.
+- Added missing timeout.pause setting.
+- Maximum tape speed read from drive type information table.
+ If the information is not in the table (0) the drive will
+ determine the speed itself and put a message in the logfile.
+ This information should then be added to the table in the
+ vendors.h file (and reported to me).
+- Added call to ftape_init_drive after soft reset for those
+ (antique) drives that don't do an implicit seek_load_point
+ after a reset or power up.
+- Don't try to set data rate if reset failed.
+- Prevent update of seek variables when starting from the
+ beginning or the end of the tape.
+- Fixed wrong adjustment of overshoot in seek_forward().
+- Added sync to Makefile (again).
+- Added code to diagnose timer problems (calibr.c).
+- Replaced time differences by timediff calls.
+- Removed reference to do_floppy from object for recent kernels.
+- Fixed wrong display of 'failing dma controller' message.
+- Removed various no longer used #include statements.
+- Added max. tape speed value to vendor-struct.
+- Changed ftape-command to check pre-conditions and wait
+ if needed.
+- Further updated qic117.h to rev G.
+- Combined command name table and restrictions table to one.
+ Extended this table with some new fields.
+- Increased timeout on Ack timer value and included code to
+ report out of spec behaviour.
+- Increased rewind timeout margin to calculated + 20%.
+- Improved data rate selection so it won't fail on some
+ older (pre standard) drives.
+- Changed initialisation code so drive will be rewound if the
+ driver is reloaded and the tape is not at bot.
+- Moved some of the flush operations from close to the ioctls.
+- Added exit code value to failing verify area message.
+- Loop until tape halted in smart-stop.
+- Fast seek handled specially if located at bot or eot.
+- Being more conservative on overshoot value.
+
+
+===== Release notes for ftape-2.00, 31/12/94 =====
+
+ The Install-guide is completely rewritten and now also includes
+some information on how to use the driver. If you're either new
+to ftape or new to Unix tape devices make sure to read it !
+
+ If you own a pci system and experience problems with the
+ftape driver make sure to read the README.PCI file. It contains
+some hints on how to fix your hardware.
+
+ For anybody who hasn't noticed: The version number of the
+driver has been incremented (The latest released version has
+been version 1.14d).
+ This has been done for two major reasons:
+
+ o A new (better) error recovery scheme is implemented.
+ o Support for new drive types has been added.
+
+ All these improvements/changes will probably include a couple
+of new (and old?) bugs. If you encounter any problems that you think
+I'm not yet aware of, feel free to send a report to <bas@vimec.nl>.
+ I recommend keeping a version of ftape-1.14d available, just
+in case ;-)
+
+ This version should work with all kernel versions from 1.0.9 up
+to 1.1.72 (and probably earlier and later versions too).
+
+
+Major new features:
+
+- Better handling of tapes with defects: When a sector repeatedly
+ (SOFT_RETRIES in ftape.h) cannot be written to or read from it is
+ marked as an hard error and gets skipped.
+ The error correction code can handle up to three of these hard
+ errors provided there are no other errors in that segment (32 Kb).
+
+- Allows writing to tapes with defects (although the risk of loosing
+ data increases !)
+ Look for the media-defects entry printed with the statistics when
+ the tape is closed. A non-zero value here shows a bad tape.
+ [the actual count is wrong (too high), this is a known bug].
+
+- Use of backup header segment if first one is failing.
+
+- Support for extended length tapes with QIC-80: both 425 and 1100 ft.
+ 0.25 inch tapes are now recognized and handled.
+
+- Support for new QIC-80 drives with 8 mm `wide' tapes (e.g. Conner
+ TSM 420).
+
+- Support for new QIC-3010 and QIC-3020 drives (experimental) with
+ both 0.25 inch and 8 mm tapes.
+
+Some minor features were added, a couple of small bugs were fixed and
+probably some new ones introduced ;-).
+
+[lseek() didn't make it into this version]
+
+Have fun,
+
+Bas.
+----
+ LocalWords: ftape MCONFIG mt VFS zftape resp sftape proc subdir MTIOCVOLINFO
+ LocalWords: MTIOCGETSIZE BOT EOD MTBSF zft kerneld modprobe kdtime contrib TR
+ LocalWords: MTSETBLK afio uninstall texi www EIO QIC init sft eof aka dma GB
+ LocalWords: SIGKILL MTIOCFTCMD mmap Iomega FDC fdc io gnumt mtio fc asm inb
+ LocalWords: outb ft qic frontend TeXinfo irq mach MODVERSIONS CONFIG html dvi
+ LocalWords: usr doc SMP Mb Dunno FIXME vtblc perl listtape volinfo fsf MTWEOF
+ LocalWords: amanda degaussed ComByte DoublePlay whraven njackn com MTIOC vtbl
+ LocalWords: GETBLKSZ MAKEDEV zftape's linux dif CVS Revison cp MTREW MTOFFL
+ LocalWords: MTFSF BSF Marcin Dalecki GCC Config cpio swapout Kai Harrekilde
+ LocalWords: Pederson khp dolphinics Justus claus momo rwth aachen Laarhoven
diff --git a/drivers/char/ftape/compressor/Makefile b/drivers/char/ftape/compressor/Makefile
new file mode 100644
index 000000000000..1fbd6c4019db
--- /dev/null
+++ b/drivers/char/ftape/compressor/Makefile
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 1997 Claus-Justus Heine.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/compressor/Makefile,v $
+# $Revision: 1.1 $
+# $Date: 1997/10/05 19:12:28 $
+#
+# Makefile for the optional compressor for th zftape VFS
+# interface to the QIC-40/80/3010/3020 floppy-tape driver for
+# Linux.
+#
+
+obj-$(CONFIG_ZFT_COMPRESSOR) += zft-compressor.o
+
+zft-compressor-objs := zftape-compress.o lzrw3.o
+
+CFLAGS_lzrw3.o := -O6 -funroll-all-loops
diff --git a/drivers/char/ftape/compressor/lzrw3.c b/drivers/char/ftape/compressor/lzrw3.c
new file mode 100644
index 000000000000..a032a0ee2a99
--- /dev/null
+++ b/drivers/char/ftape/compressor/lzrw3.c
@@ -0,0 +1,743 @@
+/*
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.c,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:29 $
+ *
+ * Implementation of Ross Williams lzrw3 algorithm. Adaption for zftape.
+ *
+ */
+
+#include "../compressor/lzrw3.h" /* Defines single exported function "compress". */
+
+/******************************************************************************/
+/* */
+/* LZRW3.C */
+/* */
+/******************************************************************************/
+/* */
+/* Author : Ross Williams. */
+/* Date : 30-Jun-1991. */
+/* Release : 1. */
+/* */
+/******************************************************************************/
+/* */
+/* This file contains an implementation of the LZRW3 data compression */
+/* algorithm in C. */
+/* */
+/* The algorithm is a general purpose compression algorithm that runs fast */
+/* and gives reasonable compression. The algorithm is a member of the Lempel */
+/* Ziv family of algorithms and bases its compression on the presence in the */
+/* data of repeated substrings. */
+/* */
+/* This algorithm is unpatented and the code is public domain. As the */
+/* algorithm is based on the LZ77 class of algorithms, it is unlikely to be */
+/* the subject of a patent challenge. */
+/* */
+/* Unlike the LZRW1 and LZRW1-A algorithms, the LZRW3 algorithm is */
+/* deterministic and is guaranteed to yield the same compressed */
+/* representation for a given file each time it is run. */
+/* */
+/* The LZRW3 algorithm was originally designed and implemented */
+/* by Ross Williams on 31-Dec-1990. */
+/* */
+/* Here are the results of applying this code, compiled under THINK C 4.0 */
+/* and running on a Mac-SE (8MHz 68000), to the standard calgary corpus. */
+/* */
+/* +----------------------------------------------------------------+ */
+/* | DATA COMPRESSION TEST | */
+/* | ===================== | */
+/* | Time of run : Sun 30-Jun-1991 09:31PM | */
+/* | Timing accuracy : One part in 100 | */
+/* | Context length : 262144 bytes (= 256.0000K) | */
+/* | Test suite : Calgary Corpus Suite | */
+/* | Files in suite : 14 | */
+/* | Algorithm : LZRW3 | */
+/* | Note: All averages are calculated from the un-rounded values. | */
+/* +----------------------------------------------------------------+ */
+/* | File Name Length CxB ComLen %Remn Bits Com K/s Dec K/s | */
+/* | ---------- ------ --- ------ ----- ---- ------- ------- | */
+/* | rpus:Bib.D 111261 1 55033 49.5 3.96 19.46 32.27 | */
+/* | us:Book1.D 768771 3 467962 60.9 4.87 17.03 31.07 | */
+/* | us:Book2.D 610856 3 317102 51.9 4.15 19.39 34.15 | */
+/* | rpus:Geo.D 102400 1 82424 80.5 6.44 11.65 18.18 | */
+/* | pus:News.D 377109 2 205670 54.5 4.36 17.14 27.47 | */
+/* | pus:Obj1.D 21504 1 13027 60.6 4.85 13.40 18.95 | */
+/* | pus:Obj2.D 246814 1 116286 47.1 3.77 19.31 30.10 | */
+/* | s:Paper1.D 53161 1 27522 51.8 4.14 18.60 31.15 | */
+/* | s:Paper2.D 82199 1 45160 54.9 4.40 18.45 32.84 | */
+/* | rpus:Pic.D 513216 2 122388 23.8 1.91 35.29 51.05 | */
+/* | us:Progc.D 39611 1 19669 49.7 3.97 18.87 30.64 | */
+/* | us:Progl.D 71646 1 28247 39.4 3.15 24.34 40.66 | */
+/* | us:Progp.D 49379 1 19377 39.2 3.14 23.91 39.23 | */
+/* | us:Trans.D 93695 1 33481 35.7 2.86 25.48 40.37 | */
+/* +----------------------------------------------------------------+ */
+/* | Average 224401 1 110953 50.0 4.00 20.17 32.72 | */
+/* +----------------------------------------------------------------+ */
+/* */
+/******************************************************************************/
+
+/******************************************************************************/
+
+/* The following structure is returned by the "compress" function below when */
+/* the user asks the function to return identifying information. */
+/* The most important field in the record is the working memory field which */
+/* tells the calling program how much working memory should be passed to */
+/* "compress" when it is called to perform a compression or decompression. */
+/* LZRW3 uses the same amount of memory during compression and decompression. */
+/* For more information on this structure see "compress.h". */
+
+#define U(X) ((ULONG) X)
+#define SIZE_P_BYTE (U(sizeof(UBYTE *)))
+#define SIZE_WORD (U(sizeof(UWORD )))
+#define ALIGNMENT_FUDGE (U(16))
+#define MEM_REQ ( U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE )
+
+static struct compress_identity identity =
+{
+ U(0x032DDEA8), /* Algorithm identification number. */
+ MEM_REQ, /* Working memory (bytes) required. */
+ "LZRW3", /* Name of algorithm. */
+ "1.0", /* Version number of algorithm. */
+ "31-Dec-1990", /* Date of algorithm. */
+ "Public Domain", /* Copyright notice. */
+ "Ross N. Williams", /* Author of algorithm. */
+ "Renaissance Software", /* Affiliation of author. */
+ "Public Domain" /* Vendor of algorithm. */
+};
+
+LOCAL void compress_compress (UBYTE *,UBYTE *,ULONG,UBYTE *, LONG *);
+LOCAL void compress_decompress(UBYTE *,UBYTE *,LONG, UBYTE *, ULONG *);
+
+/******************************************************************************/
+
+/* This function is the only function exported by this module. */
+/* Depending on its first parameter, the function can be requested to */
+/* compress a block of memory, decompress a block of memory, or to identify */
+/* itself. For more information, see the specification file "compress.h". */
+
+EXPORT void lzrw3_compress(
+ UWORD action, /* Action to be performed. */
+ UBYTE *wrk_mem, /* Address of working memory we can use.*/
+ UBYTE *src_adr, /* Address of input data. */
+ LONG src_len, /* Length of input data. */
+ UBYTE *dst_adr, /* Address to put output data. */
+ void *p_dst_len /* Address of longword for length of output data.*/
+)
+{
+ switch (action)
+ {
+ case COMPRESS_ACTION_IDENTITY:
+ *((struct compress_identity **)p_dst_len)= &identity;
+ break;
+ case COMPRESS_ACTION_COMPRESS:
+ compress_compress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len);
+ break;
+ case COMPRESS_ACTION_DECOMPRESS:
+ compress_decompress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len);
+ break;
+ }
+}
+
+/******************************************************************************/
+/* */
+/* BRIEF DESCRIPTION OF THE LZRW3 ALGORITHM */
+/* ======================================== */
+/* The LZRW3 algorithm is identical to the LZRW1-A algorithm except that */
+/* instead of transmitting history offsets, it transmits hash table indexes. */
+/* In order to decode the indexes, the decompressor must maintain an */
+/* identical hash table. Copy items are straightforward:when the decompressor */
+/* receives a copy item, it simply looks up the hash table to translate the */
+/* index into a pointer into the data already decompressed. To update the */
+/* hash table, it replaces the same table entry with a pointer to the start */
+/* of the newly decoded phrase. The tricky part is with literal items, for at */
+/* the time that the decompressor receives a literal item the decompressor */
+/* does not have the three bytes in the Ziv (that the compressor has) to */
+/* perform the three-byte hash. To solve this problem, in LZRW3, both the */
+/* compressor and decompressor are wired up so that they "buffer" these */
+/* literals and update their hash tables only when three bytes are available. */
+/* This makes the maximum buffering 2 bytes. */
+/* */
+/* Replacement of offsets by hash table indexes yields a few percent extra */
+/* compression at the cost of some speed. LZRW3 is slower than LZRW1, LZRW1-A */
+/* and LZRW2, but yields better compression. */
+/* */
+/* Extra compression could be obtained by using a hash table of depth two. */
+/* However, increasing the depth above one incurs a significant decrease in */
+/* compression speed which was not considered worthwhile. Another reason for */
+/* keeping the depth down to one was to allow easy comparison with the */
+/* LZRW1-A and LZRW2 algorithms so as to demonstrate the exact effect of the */
+/* use of direct hash indexes. */
+/* */
+/* +---+ */
+/* |___|4095 */
+/* |___| */
+/* +---------------------*_|<---+ /----+---\ */
+/* | |___| +---|Hash | */
+/* | |___| |Function| */
+/* | |___| \--------/ */
+/* | |___|0 ^ */
+/* | +---+ | */
+/* | Hash +-----+ */
+/* | Table | */
+/* | --- */
+/* v ^^^ */
+/* +-------------------------------------|----------------+ */
+/* |||||||||||||||||||||||||||||||||||||||||||||||||||||||| */
+/* +-------------------------------------|----------------+ */
+/* | |1......18| | */
+/* |<------- Lempel=History ------------>|<--Ziv-->| | */
+/* | (=bytes already processed) |<-Still to go-->| */
+/* |<-------------------- INPUT BLOCK ------------------->| */
+/* */
+/* The diagram above for LZRW3 looks almost identical to the diagram for */
+/* LZRW1. The difference is that in LZRW3, the compressor transmits hash */
+/* table indices instead of Lempel offsets. For this to work, the */
+/* decompressor must maintain a hash table as well as the compressor and both */
+/* compressor and decompressor must "buffer" literals, as the decompressor */
+/* cannot hash phrases commencing with a literal until another two bytes have */
+/* arrived. */
+/* */
+/* LZRW3 Algorithm Execution Summary */
+/* --------------------------------- */
+/* 1. Hash the first three bytes of the Ziv to yield a hash table index h. */
+/* 2. Look up the hash table yielding history pointer p. */
+/* 3. Match where p points with the Ziv. If there is a match of three or */
+/* more bytes, code those bytes (in the Ziv) as a copy item, otherwise */
+/* code the next byte in the Ziv as a literal item. */
+/* 4. Update the hash table as possible subject to the constraint that only */
+/* phrases commencing three bytes back from the Ziv can be hashed and */
+/* entered into the hash table. (This enables the decompressor to keep */
+/* pace). See the description and code for more details. */
+/* */
+/******************************************************************************/
+/* */
+/* DEFINITION OF COMPRESSED FILE FORMAT */
+/* ==================================== */
+/* * A compressed file consists of a COPY FLAG followed by a REMAINDER. */
+/* * The copy flag CF uses up four bytes with the first byte being the */
+/* least significant. */
+/* * If CF=1, then the compressed file represents the remainder of the file */
+/* exactly. Otherwise CF=0 and the remainder of the file consists of zero */
+/* or more GROUPS, each of which represents one or more bytes. */
+/* * Each group consists of two bytes of CONTROL information followed by */
+/* sixteen ITEMs except for the last group which can contain from one */
+/* to sixteen items. */
+/* * An item can be either a LITERAL item or a COPY item. */
+/* * Each item corresponds to a bit in the control bytes. */
+/* * The first control byte corresponds to the first 8 items in the group */
+/* with bit 0 corresponding to the first item in the group and bit 7 to */
+/* the eighth item in the group. */
+/* * The second control byte corresponds to the second 8 items in the group */
+/* with bit 0 corresponding to the ninth item in the group and bit 7 to */
+/* the sixteenth item in the group. */
+/* * A zero bit in a control word means that the corresponding item is a */
+/* literal item. A one bit corresponds to a copy item. */
+/* * A literal item consists of a single byte which represents itself. */
+/* * A copy item consists of two bytes that represent from 3 to 18 bytes. */
+/* * The first byte in a copy item will be denoted C1. */
+/* * The second byte in a copy item will be denoted C2. */
+/* * Bits will be selected using square brackets. */
+/* For example: C1[0..3] is the low nibble of the first control byte. */
+/* of copy item C1. */
+/* * The LENGTH of a copy item is defined to be C1[0..3]+3 which is a number */
+/* in the range [3,18]. */
+/* * The INDEX of a copy item is defined to be C1[4..7]*256+C2[0..8] which */
+/* is a number in the range [0,4095]. */
+/* * A copy item represents the sequence of bytes */
+/* text[POS-OFFSET..POS-OFFSET+LENGTH-1] where */
+/* text is the entire text of the uncompressed string. */
+/* POS is the index in the text of the character following the */
+/* string represented by all the items preceeding the item */
+/* being defined. */
+/* OFFSET is obtained from INDEX by looking up the hash table. */
+/* */
+/******************************************************************************/
+
+/* The following #define defines the length of the copy flag that appears at */
+/* the start of the compressed file. The value of four bytes was chosen */
+/* because the fast_copy routine on my Macintosh runs faster if the source */
+/* and destination blocks are relatively longword aligned. */
+/* The actual flag data appears in the first byte. The rest are zeroed so as */
+/* to normalize the compressed representation (i.e. not non-deterministic). */
+#define FLAG_BYTES 4
+
+/* The following #defines define the meaning of the values of the copy */
+/* flag at the start of the compressed file. */
+#define FLAG_COMPRESS 0 /* Signals that output was result of compression. */
+#define FLAG_COPY 1 /* Signals that output was simply copied over. */
+
+/* The 68000 microprocessor (on which this algorithm was originally developed */
+/* is fussy about non-aligned arrays of words. To avoid these problems the */
+/* following macro can be used to "waste" from 0 to 3 bytes so as to align */
+/* the argument pointer. */
+#define ULONG_ALIGN_UP(X) ((((ULONG)X)+sizeof(ULONG)-1)&~(sizeof(ULONG)-1))
+
+
+/* The following constant defines the maximum length of an uncompressed item. */
+/* This definition must not be changed; its value is hardwired into the code. */
+/* The longest number of bytes that can be spanned by a single item is 18 */
+/* for the longest copy item. */
+#define MAX_RAW_ITEM (18)
+
+/* The following constant defines the maximum length of an uncompressed group.*/
+/* This definition must not be changed; its value is hardwired into the code. */
+/* A group contains at most 16 items which explains this definition. */
+#define MAX_RAW_GROUP (16*MAX_RAW_ITEM)
+
+/* The following constant defines the maximum length of a compressed group. */
+/* This definition must not be changed; its value is hardwired into the code. */
+/* A compressed group consists of two control bytes followed by up to 16 */
+/* compressed items each of which can have a maximum length of two bytes. */
+#define MAX_CMP_GROUP (2+16*2)
+
+/* The following constant defines the number of entries in the hash table. */
+/* This definition must not be changed; its value is hardwired into the code. */
+#define HASH_TABLE_LENGTH (4096)
+
+/* LZRW3, unlike LZRW1(-A), must initialize its hash table so as to enable */
+/* the compressor and decompressor to stay in step maintaining identical hash */
+/* tables. In an early version of the algorithm, the tables were simply */
+/* initialized to zero and a check for zero was included just before the */
+/* matching code. However, this test costs time. A better solution is to */
+/* initialize all the entries in the hash table to point to a constant */
+/* string. The decompressor does the same. This solution requires no extra */
+/* test. The contents of the string do not matter so long as the string is */
+/* the same for the compressor and decompressor and contains at least */
+/* MAX_RAW_ITEM bytes. I chose consecutive decimal digits because they do not */
+/* have white space problems (e.g. there is no chance that the compiler will */
+/* replace more than one space by a TAB) and because they make the length of */
+/* the string obvious by inspection. */
+#define START_STRING_18 ((UBYTE *) "123456789012345678")
+
+/* In this algorithm, hash values have to be calculated at more than one */
+/* point. The following macro neatens the code up for this. */
+#define HASH(PTR) \
+ (((40543*(((*(PTR))<<8)^((*((PTR)+1))<<4)^(*((PTR)+2))))>>4) & 0xFFF)
+
+/******************************************************************************/
+
+/* Input : Hand over the required amount of working memory in p_wrk_mem. */
+/* Input : Specify input block using p_src_first and src_len. */
+/* Input : Point p_dst_first to the start of the output zone (OZ). */
+/* Input : Point p_dst_len to a ULONG to receive the output length. */
+/* Input : Input block and output zone must not overlap. */
+/* Output : Length of output block written to *p_dst_len. */
+/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. May */
+/* Output : write in OZ=Mem[p_dst_first..p_dst_first+src_len+MAX_CMP_GROUP-1].*/
+/* Output : Upon completion guaranteed *p_dst_len<=src_len+FLAG_BYTES. */
+LOCAL void compress_compress(UBYTE *p_wrk_mem,
+ UBYTE *p_src_first, ULONG src_len,
+ UBYTE *p_dst_first, LONG *p_dst_len)
+{
+ /* p_src and p_dst step through the source and destination blocks. */
+ register UBYTE *p_src = p_src_first;
+ register UBYTE *p_dst = p_dst_first;
+
+ /* The following variables are never modified and are used in the */
+ /* calculations that determine when the main loop terminates. */
+ UBYTE *p_src_post = p_src_first+src_len;
+ UBYTE *p_dst_post = p_dst_first+src_len;
+ UBYTE *p_src_max1 = p_src_first+src_len-MAX_RAW_ITEM;
+ UBYTE *p_src_max16 = p_src_first+src_len-MAX_RAW_ITEM*16;
+
+ /* The variables 'p_control' and 'control' are used to buffer control bits. */
+ /* Before each group is processed, the next two bytes of the output block */
+ /* are set aside for the control word for the group about to be processed. */
+ /* 'p_control' is set to point to the first byte of that word. Meanwhile, */
+ /* 'control' buffers the control bits being generated during the processing */
+ /* of the group. Instead of having a counter to keep track of how many items */
+ /* have been processed (=the number of bits in the control word), at the */
+ /* start of each group, the top word of 'control' is filled with 1 bits. */
+ /* As 'control' is shifted for each item, the 1 bits in the top word are */
+ /* absorbed or destroyed. When they all run out (i.e. when the top word is */
+ /* all zero bits, we know that we are at the end of a group. */
+# define TOPWORD 0xFFFF0000
+ UBYTE *p_control;
+ register ULONG control=TOPWORD;
+
+ /* THe variable 'hash' always points to the first element of the hash table. */
+ UBYTE **hash= (UBYTE **) ULONG_ALIGN_UP(p_wrk_mem);
+
+ /* The following two variables represent the literal buffer. p_h1 points to */
+ /* the hash table entry corresponding to the youngest literal. p_h2 points */
+ /* to the hash table entry corresponding to the second youngest literal. */
+ /* Note: p_h1=0=>p_h2=0 because zero values denote absence of a pending */
+ /* literal. The variables are initialized to zero meaning an empty "buffer". */
+ UBYTE **p_h1=NULL;
+ UBYTE **p_h2=NULL;
+
+ /* To start, we write the flag bytes. Being optimistic, we set the flag to */
+ /* FLAG_COMPRESS. The remaining flag bytes are zeroed so as to keep the */
+ /* algorithm deterministic. */
+ *p_dst++=FLAG_COMPRESS;
+ {UWORD i; for (i=2;i<=FLAG_BYTES;i++) *p_dst++=0;}
+
+ /* Reserve the first word of output as the control word for the first group. */
+ /* Note: This is undone at the end if the input block is empty. */
+ p_control=p_dst; p_dst+=2;
+
+ /* Initialize all elements of the hash table to point to a constant string. */
+ /* Use of an unrolled loop speeds this up considerably. */
+ {UWORD i; UBYTE **p_h=hash;
+# define ZH *p_h++=START_STRING_18
+ for (i=0;i<256;i++) /* 256=HASH_TABLE_LENGTH/16. */
+ {ZH;ZH;ZH;ZH;
+ ZH;ZH;ZH;ZH;
+ ZH;ZH;ZH;ZH;
+ ZH;ZH;ZH;ZH;}
+ }
+
+ /* The main loop processes either 1 or 16 items per iteration. As its */
+ /* termination logic is complicated, I have opted for an infinite loop */
+ /* structure containing 'break' and 'goto' statements. */
+ while (TRUE)
+ {/* Begin main processing loop. */
+
+ /* Note: All the variables here except unroll should be defined within */
+ /* the inner loop. Unfortunately the loop hasn't got a block. */
+ register UBYTE *p; /* Scans through targ phrase during matching. */
+ register UBYTE *p_ziv= NULL ; /* Points to first byte of current Ziv. */
+ register UWORD unroll; /* Loop counter for unrolled inner loop. */
+ register UWORD index; /* Index of current hash table entry. */
+ register UBYTE **p_h0 = NULL ; /* Pointer to current hash table entry. */
+
+ /* Test for overrun and jump to overrun code if necessary. */
+ if (p_dst>p_dst_post)
+ goto overrun;
+
+ /* The following cascade of if statements efficiently catches and deals */
+ /* with varying degrees of closeness to the end of the input block. */
+ /* When we get very close to the end, we stop updating the table and */
+ /* code the remaining bytes as literals. This makes the code simpler. */
+ unroll=16;
+ if (p_src>p_src_max16)
+ {
+ unroll=1;
+ if (p_src>p_src_max1)
+ {
+ if (p_src==p_src_post)
+ break;
+ else
+ goto literal;
+ }
+ }
+
+ /* This inner unrolled loop processes 'unroll' (whose value is either 1 */
+ /* or 16) items. I have chosen to implement this loop with labels and */
+ /* gotos to heighten the ease with which the loop may be implemented with */
+ /* a single decrement and branch instruction in assembly language and */
+ /* also because the labels act as highly readable place markers. */
+ /* (Also because we jump into the loop for endgame literals (see above)). */
+
+ begin_unrolled_loop:
+
+ /* To process the next phrase, we hash the next three bytes and use */
+ /* the resultant hash table index to look up the hash table. A pointer */
+ /* to the entry is stored in p_h0 so as to avoid an array lookup. The */
+ /* hash table entry *p_h0 is looked up yielding a pointer p to a */
+ /* potential match of the Ziv in the history. */
+ index=HASH(p_src);
+ p_h0=&hash[index];
+ p=*p_h0;
+
+ /* Having looked up the candidate position, we are in a position to */
+ /* attempt a match. The match loop has been unrolled using the PS */
+ /* macro so that failure within the first three bytes automatically */
+ /* results in the literal branch being taken. The coding is simple. */
+ /* p_ziv saves p_src so we can let p_src wander. */
+# define PS *p++!=*p_src++
+ p_ziv=p_src;
+ if (PS || PS || PS)
+ {
+ /* Literal. */
+
+ /* Code the literal byte as itself and a zero control bit. */
+ p_src=p_ziv; literal: *p_dst++=*p_src++; control&=0xFFFEFFFF;
+
+ /* We have just coded a literal. If we had two pending ones, that */
+ /* makes three and we can update the hash table. */
+ if (p_h2!=0)
+ {*p_h2=p_ziv-2;}
+
+ /* In any case, rotate the hash table pointers for next time. */
+ p_h2=p_h1; p_h1=p_h0;
+
+ }
+ else
+ {
+ /* Copy */
+
+ /* Match up to 15 remaining bytes using an unrolled loop and code. */
+#if 0
+ PS || PS || PS || PS || PS || PS || PS || PS ||
+ PS || PS || PS || PS || PS || PS || PS || p_src++;
+#else
+ if (
+ !( PS || PS || PS || PS || PS || PS || PS || PS ||
+ PS || PS || PS || PS || PS || PS || PS )
+ ) p_src++;
+#endif
+ *p_dst++=((index&0xF00)>>4)|(--p_src-p_ziv-3);
+ *p_dst++=index&0xFF;
+
+ /* As we have just coded three bytes, we are now in a position to */
+ /* update the hash table with the literal bytes that were pending */
+ /* upon the arrival of extra context bytes. */
+ if (p_h1!=0)
+ {
+ if (p_h2)
+ {*p_h2=p_ziv-2; p_h2=NULL;}
+ *p_h1=p_ziv-1; p_h1=NULL;
+ }
+
+ /* In any case, we can update the hash table based on the current */
+ /* position as we just coded at least three bytes in a copy items. */
+ *p_h0=p_ziv;
+
+ }
+ control>>=1;
+
+ /* This loop is all set up for a decrement and jump instruction! */
+#ifndef linux
+` end_unrolled_loop: if (--unroll) goto begin_unrolled_loop;
+#else
+ /* end_unrolled_loop: */ if (--unroll) goto begin_unrolled_loop;
+#endif
+
+ /* At this point it will nearly always be the end of a group in which */
+ /* case, we have to do some control-word processing. However, near the */
+ /* end of the input block, the inner unrolled loop is only executed once. */
+ /* This necessitates the 'if' test. */
+ if ((control&TOPWORD)==0)
+ {
+ /* Write the control word to the place we saved for it in the output. */
+ *p_control++= control &0xFF;
+ *p_control = (control>>8) &0xFF;
+
+ /* Reserve the next word in the output block for the control word */
+ /* for the group about to be processed. */
+ p_control=p_dst; p_dst+=2;
+
+ /* Reset the control bits buffer. */
+ control=TOPWORD;
+ }
+
+ } /* End main processing loop. */
+
+ /* After the main processing loop has executed, all the input bytes have */
+ /* been processed. However, the control word has still to be written to the */
+ /* word reserved for it in the output at the start of the most recent group. */
+ /* Before writing, the control word has to be shifted so that all the bits */
+ /* are in the right place. The "empty" bit positions are filled with 1s */
+ /* which partially fill the top word. */
+ while(control&TOPWORD) control>>=1;
+ *p_control++= control &0xFF;
+ *p_control++=(control>>8) &0xFF;
+
+ /* If the last group contained no items, delete the control word too. */
+ if (p_control==p_dst) p_dst-=2;
+
+ /* Write the length of the output block to the dst_len parameter and return. */
+ *p_dst_len=p_dst-p_dst_first;
+ return;
+
+ /* Jump here as soon as an overrun is detected. An overrun is defined to */
+ /* have occurred if p_dst>p_dst_first+src_len. That is, the moment the */
+ /* length of the output written so far exceeds the length of the input block.*/
+ /* The algorithm checks for overruns at least at the end of each group */
+ /* which means that the maximum overrun is MAX_CMP_GROUP bytes. */
+ /* Once an overrun occurs, the only thing to do is to set the copy flag and */
+ /* copy the input over. */
+ overrun:
+#if 0
+ *p_dst_first=FLAG_COPY;
+ fast_copy(p_src_first,p_dst_first+FLAG_BYTES,src_len);
+ *p_dst_len=src_len+FLAG_BYTES;
+#else
+ fast_copy(p_src_first,p_dst_first,src_len);
+ *p_dst_len= -src_len; /* return a negative number to indicate uncompressed data */
+#endif
+}
+
+/******************************************************************************/
+
+/* Input : Hand over the required amount of working memory in p_wrk_mem. */
+/* Input : Specify input block using p_src_first and src_len. */
+/* Input : Point p_dst_first to the start of the output zone. */
+/* Input : Point p_dst_len to a ULONG to receive the output length. */
+/* Input : Input block and output zone must not overlap. User knows */
+/* Input : upperbound on output block length from earlier compression. */
+/* Input : In any case, maximum expansion possible is nine times. */
+/* Output : Length of output block written to *p_dst_len. */
+/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. */
+/* Output : Writes only in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. */
+LOCAL void compress_decompress( UBYTE *p_wrk_mem,
+ UBYTE *p_src_first, LONG src_len,
+ UBYTE *p_dst_first, ULONG *p_dst_len)
+{
+ /* Byte pointers p_src and p_dst scan through the input and output blocks. */
+ register UBYTE *p_src = p_src_first+FLAG_BYTES;
+ register UBYTE *p_dst = p_dst_first;
+ /* we need to avoid a SEGV when trying to uncompress corrupt data */
+ register UBYTE *p_dst_post = p_dst_first + *p_dst_len;
+
+ /* The following two variables are never modified and are used to control */
+ /* the main loop. */
+ UBYTE *p_src_post = p_src_first+src_len;
+ UBYTE *p_src_max16 = p_src_first+src_len-(MAX_CMP_GROUP-2);
+
+ /* The hash table is the only resident of the working memory. The hash table */
+ /* contains HASH_TABLE_LENGTH=4096 pointers to positions in the history. To */
+ /* keep Macintoshes happy, it is longword aligned. */
+ UBYTE **hash = (UBYTE **) ULONG_ALIGN_UP(p_wrk_mem);
+
+ /* The variable 'control' is used to buffer the control bits which appear in */
+ /* groups of 16 bits (control words) at the start of each compressed group. */
+ /* When each group is read, bit 16 of the register is set to one. Whenever */
+ /* a new bit is needed, the register is shifted right. When the value of the */
+ /* register becomes 1, we know that we have reached the end of a group. */
+ /* Initializing the register to 1 thus instructs the code to follow that it */
+ /* should read a new control word immediately. */
+ register ULONG control=1;
+
+ /* The value of 'literals' is always in the range 0..3. It is the number of */
+ /* consecutive literal items just seen. We have to record this number so as */
+ /* to know when to update the hash table. When literals gets to 3, there */
+ /* have been three consecutive literals and we can update at the position of */
+ /* the oldest of the three. */
+ register UWORD literals=0;
+
+ /* Check the leading copy flag to see if the compressor chose to use a copy */
+ /* operation instead of a compression operation. If a copy operation was */
+ /* used, then all we need to do is copy the data over, set the output length */
+ /* and return. */
+#if 0
+ if (*p_src_first==FLAG_COPY)
+ {
+ fast_copy(p_src_first+FLAG_BYTES,p_dst_first,src_len-FLAG_BYTES);
+ *p_dst_len=src_len-FLAG_BYTES;
+ return;
+ }
+#else
+ if ( src_len < 0 )
+ {
+ fast_copy(p_src_first,p_dst_first,-src_len );
+ *p_dst_len = (ULONG)-src_len;
+ return;
+ }
+#endif
+
+ /* Initialize all elements of the hash table to point to a constant string. */
+ /* Use of an unrolled loop speeds this up considerably. */
+ {UWORD i; UBYTE **p_h=hash;
+# define ZJ *p_h++=START_STRING_18
+ for (i=0;i<256;i++) /* 256=HASH_TABLE_LENGTH/16. */
+ {ZJ;ZJ;ZJ;ZJ;
+ ZJ;ZJ;ZJ;ZJ;
+ ZJ;ZJ;ZJ;ZJ;
+ ZJ;ZJ;ZJ;ZJ;}
+ }
+
+ /* The outer loop processes either 1 or 16 items per iteration depending on */
+ /* how close p_src is to the end of the input block. */
+ while (p_src!=p_src_post)
+ {/* Start of outer loop */
+
+ register UWORD unroll; /* Counts unrolled loop executions. */
+
+ /* When 'control' has the value 1, it means that the 16 buffered control */
+ /* bits that were read in at the start of the current group have all been */
+ /* shifted out and that all that is left is the 1 bit that was injected */
+ /* into bit 16 at the start of the current group. When we reach the end */
+ /* of a group, we have to load a new control word and inject a new 1 bit. */
+ if (control==1)
+ {
+ control=0x10000|*p_src++;
+ control|=(*p_src++)<<8;
+ }
+
+ /* If it is possible that we are within 16 groups from the end of the */
+ /* input, execute the unrolled loop only once, else process a whole group */
+ /* of 16 items by looping 16 times. */
+ unroll= p_src<=p_src_max16 ? 16 : 1;
+
+ /* This inner loop processes one phrase (item) per iteration. */
+ while (unroll--)
+ { /* Begin unrolled inner loop. */
+
+ /* Process a literal or copy item depending on the next control bit. */
+ if (control&1)
+ {
+ /* Copy item. */
+
+ register UBYTE *p; /* Points to place from which to copy. */
+ register UWORD lenmt; /* Length of copy item minus three. */
+ register UBYTE **p_hte; /* Pointer to current hash table entry.*/
+ register UBYTE *p_ziv=p_dst; /* Pointer to start of current Ziv. */
+
+ /* Read and dismantle the copy word. Work out from where to copy. */
+ lenmt=*p_src++;
+ p_hte=&hash[((lenmt&0xF0)<<4)|*p_src++];
+ p=*p_hte;
+ lenmt&=0xF;
+
+ /* Now perform the copy using a half unrolled loop. */
+ *p_dst++=*p++;
+ *p_dst++=*p++;
+ *p_dst++=*p++;
+ while (lenmt--)
+ *p_dst++=*p++;
+
+ /* Because we have just received 3 or more bytes in a copy item */
+ /* (whose bytes we have just installed in the output), we are now */
+ /* in a position to flush all the pending literal hashings that had */
+ /* been postponed for lack of bytes. */
+ if (literals>0)
+ {
+ register UBYTE *r=p_ziv-literals;
+ hash[HASH(r)]=r;
+ if (literals==2)
+ {r++; hash[HASH(r)]=r;}
+ literals=0;
+ }
+
+ /* In any case, we can immediately update the hash table with the */
+ /* current position. We don't need to do a HASH(...) to work out */
+ /* where to put the pointer, as the compressor just told us!!! */
+ *p_hte=p_ziv;
+
+ }
+ else
+ {
+ /* Literal item. */
+
+ /* Copy over the literal byte. */
+ *p_dst++=*p_src++;
+
+ /* If we now have three literals waiting to be hashed into the hash */
+ /* table, we can do one of them now (because there are three). */
+ if (++literals == 3)
+ {register UBYTE *p=p_dst-3; hash[HASH(p)]=p; literals=2;}
+ }
+
+ /* Shift the control buffer so the next control bit is in bit 0. */
+ control>>=1;
+#if 1
+ if (p_dst > p_dst_post)
+ {
+ /* Shit: we tried to decompress corrupt data */
+ *p_dst_len = 0;
+ return;
+ }
+#endif
+ } /* End unrolled inner loop. */
+
+ } /* End of outer loop */
+
+ /* Write the length of the decompressed data before returning. */
+ *p_dst_len=p_dst-p_dst_first;
+}
+
+/******************************************************************************/
+/* End of LZRW3.C */
+/******************************************************************************/
diff --git a/drivers/char/ftape/compressor/lzrw3.h b/drivers/char/ftape/compressor/lzrw3.h
new file mode 100644
index 000000000000..533feba47526
--- /dev/null
+++ b/drivers/char/ftape/compressor/lzrw3.h
@@ -0,0 +1,253 @@
+#ifndef _LZRW3_H
+#define _LZRW3_H
+/*
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:30 $
+ *
+ * include files for lzrw3. Only slighty modified from the original
+ * version. Assembles the three include files compress.h, port.h and
+ * fastcopy.h from the original lzrw3 package.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+
+/******************************************************************************/
+/* */
+/* COMPRESS.H */
+/* */
+/******************************************************************************/
+/* */
+/* Author : Ross Williams. */
+/* Date : December 1989. */
+/* */
+/* This header file defines the interface to a set of functions called */
+/* 'compress', each member of which implements a particular data compression */
+/* algorithm. */
+/* */
+/* Normally in C programming, for each .H file, there is a corresponding .C */
+/* file that implements the functions promised in the .H file. */
+/* Here, there are many .C files corresponding to this header file. */
+/* Each comforming implementation file contains a single function */
+/* called 'compress' that implements a single data compression */
+/* algorithm that conforms with the interface specified in this header file. */
+/* Only one algorithm can be linked in at a time in this organization. */
+/* */
+/******************************************************************************/
+/* */
+/* DEFINITION OF FUNCTION COMPRESS */
+/* =============================== */
+/* */
+/* Summary of Function Compress */
+/* ---------------------------- */
+/* The action that 'compress' takes depends on its first argument called */
+/* 'action'. The function provides three actions: */
+/* */
+/* - Return information about the algorithm. */
+/* - Compress a block of memory. */
+/* - Decompress a block of memory. */
+/* */
+/* Parameters */
+/* ---------- */
+/* See the formal C definition later for a description of the parameters. */
+/* */
+/* Constants */
+/* --------- */
+/* COMPRESS_OVERRUN: The constant COMPRESS_OVERRUN defines by how many bytes */
+/* an algorithm is allowed to expand a block during a compression operation. */
+/* */
+/* Although compression algorithms usually compress data, there will always */
+/* be data that a given compressor will expand (this can be proven). */
+/* Fortunately, the degree of expansion can be limited to a single bit, by */
+/* copying over the input data if the data gets bigger during compression. */
+/* To allow for this possibility, the first bit of a compressed */
+/* representation can be used as a flag indicating whether the */
+/* input data was copied over, or truly compressed. In practice, the first */
+/* byte would be used to store this bit so as to maintain byte alignment. */
+/* */
+/* Unfortunately, in general, the only way to tell if an algorithm will */
+/* expand a particular block of data is to run the algorithm on the data. */
+/* If the algorithm does not continuously monitor how many output bytes it */
+/* has written, it might write an output block far larger than the input */
+/* block before realizing that it has done so. */
+/* On the other hand, continuous checks on output length are inefficient. */
+/* */
+/* To cater for all these problems, this interface definition: */
+/* > Allows a compression algorithm to return an output block that is up to */
+/* COMPRESS_OVERRUN bytes longer than the input block. */
+/* > Allows a compression algorithm to write up to COMPRESS_OVERRUN bytes */
+/* more than the length of the input block to the memory of the output */
+/* block regardless of the length of the output block eventually returned. */
+/* This allows an algorithm to overrun the length of the input block in the */
+/* output block by up to COMPRESS_OVERRUN bytes between expansion checks. */
+/* */
+/* The problem does not arise for decompression. */
+/* */
+/* Identity Action */
+/* --------------- */
+/* > action must be COMPRESS_ACTION_IDENTITY. */
+/* > p_dst_len must point to a longword to receive a longword address. */
+/* > The value of the other parameters does not matter. */
+/* > After execution, the longword that p_dst_len points to will be a pointer */
+/* to a structure of type compress_identity. */
+/* Thus, for example, after the call, (*p_dst_len)->memory will return the */
+/* number of bytes of working memory that the algorithm requires to run. */
+/* > The values of the identity structure returned are fixed constant */
+/* attributes of the algorithm and must not vary from call to call. */
+/* */
+/* Common Requirements for Compression and Decompression Actions */
+/* ------------------------------------------------------------- */
+/* > wrk_mem must point to an unused block of memory of a length specified in */
+/* the algorithm's identity block. The identity block can be obtained by */
+/* making a separate call to compress, specifying the identity action. */
+/* > The INPUT BLOCK is defined to be Memory[src_addr,src_addr+src_len-1]. */
+/* > dst_len will be used to denote *p_dst_len. */
+/* > dst_len is not read by compress, only written. */
+/* > The value of dst_len is defined only upon termination. */
+/* > The OUTPUT BLOCK is defined to be Memory[dst_addr,dst_addr+dst_len-1]. */
+/* */
+/* Compression Action */
+/* ------------------ */
+/* > action must be COMPRESS_ACTION_COMPRESS. */
+/* > src_len must be in the range [0,COMPRESS_MAX_ORG]. */
+/* > The OUTPUT ZONE is defined to be */
+/* Memory[dst_addr,dst_addr+src_len-1+COMPRESS_OVERRUN]. */
+/* > The function can modify any part of the output zone regardless of the */
+/* final length of the output block. */
+/* > The input block and the output zone must not overlap. */
+/* > dst_len will be in the range [0,src_len+COMPRESS_OVERRUN]. */
+/* > dst_len will be in the range [0,COMPRESS_MAX_COM] (from prev fact). */
+/* > The output block will consist of a representation of the input block. */
+/* */
+/* Decompression Action */
+/* -------------------- */
+/* > action must be COMPRESS_ACTION_DECOMPRESS. */
+/* > The input block must be the result of an earlier compression operation. */
+/* > If the previous fact is true, the following facts must also be true: */
+/* > src_len will be in the range [0,COMPRESS_MAX_COM]. */
+/* > dst_len will be in the range [0,COMPRESS_MAX_ORG]. */
+/* > The input and output blocks must not overlap. */
+/* > Only the output block is modified. */
+/* > Upon termination, the output block will consist of the bytes contained */
+/* in the input block passed to the earlier compression operation. */
+/* */
+/******************************************************************************/
+
+/******************************************************************************/
+/* */
+/* PORT.H */
+/* */
+/******************************************************************************/
+/* */
+/* This module contains macro definitions and types that are likely to */
+/* change between computers. */
+/* */
+/******************************************************************************/
+
+#ifndef DONE_PORT /* Only do this if not previously done. */
+
+ #ifdef THINK_C
+ #define UBYTE unsigned char /* Unsigned byte */
+ #define UWORD unsigned int /* Unsigned word (2 bytes) */
+ #define ULONG unsigned long /* Unsigned word (4 bytes) */
+ #define BOOL unsigned char /* Boolean */
+ #define FOPEN_BINARY_READ "rb" /* Mode string for binary reading. */
+ #define FOPEN_BINARY_WRITE "wb" /* Mode string for binary writing. */
+ #define FOPEN_TEXT_APPEND "a" /* Mode string for text appending. */
+ #define REAL double /* USed for floating point stuff. */
+ #endif
+ #if defined(LINUX) || defined(linux)
+ #define UBYTE __u8 /* Unsigned byte */
+ #define UWORD __u16 /* Unsigned word (2 bytes) */
+ #define ULONG __u32 /* Unsigned word (4 bytes) */
+ #define LONG __s32 /* Signed word (4 bytes) */
+ #define BOOL is not used here /* Boolean */
+ #define FOPEN_BINARY_READ not used /* Mode string for binary reading. */
+ #define FOPEN_BINARY_WRITE not used /* Mode string for binary writing. */
+ #define FOPEN_TEXT_APPEND not used /* Mode string for text appending. */
+ #define REAL not used /* USed for floating point stuff. */
+ #ifndef TRUE
+ #define TRUE 1
+ #endif
+ #endif
+
+ #define DONE_PORT /* Don't do all this again. */
+ #define MALLOC_FAIL NULL /* Failure status from malloc() */
+ #define LOCAL static /* For non-exported routines. */
+ #define EXPORT /* Signals exported function. */
+ #define then /* Useful for aligning ifs. */
+
+#endif
+
+/******************************************************************************/
+/* End of PORT.H */
+/******************************************************************************/
+
+#define COMPRESS_ACTION_IDENTITY 0
+#define COMPRESS_ACTION_COMPRESS 1
+#define COMPRESS_ACTION_DECOMPRESS 2
+
+#define COMPRESS_OVERRUN 1024
+#define COMPRESS_MAX_COM 0x70000000
+#define COMPRESS_MAX_ORG (COMPRESS_MAX_COM-COMPRESS_OVERRUN)
+
+#define COMPRESS_MAX_STRLEN 255
+
+/* The following structure provides information about the algorithm. */
+/* > The top bit of id must be zero. The remaining bits must be chosen by */
+/* the author of the algorithm by tossing a coin 31 times. */
+/* > The amount of memory requested by the algorithm is specified in bytes */
+/* and must be in the range [0,0x70000000]. */
+/* > All strings s must be such that strlen(s)<=COMPRESS_MAX_STRLEN. */
+struct compress_identity
+ {
+ ULONG id; /* Identifying number of algorithm. */
+ ULONG memory; /* Number of bytes of working memory required. */
+
+ char *name; /* Name of algorithm. */
+ char *version; /* Version number. */
+ char *date; /* Date of release of this version. */
+ char *copyright; /* Copyright message. */
+
+ char *author; /* Author of algorithm. */
+ char *affiliation; /* Affiliation of author. */
+ char *vendor; /* Where the algorithm can be obtained. */
+ };
+
+void lzrw3_compress( /* Single function interface to compression algorithm. */
+UWORD action, /* Action to be performed. */
+UBYTE *wrk_mem, /* Working memory temporarily given to routine to use. */
+UBYTE *src_adr, /* Address of input data. */
+LONG src_len, /* Length of input data. */
+UBYTE *dst_adr, /* Address of output data. */
+void *p_dst_len /* Pointer to a longword where routine will write: */
+ /* If action=..IDENTITY => Adr of id structure. */
+ /* If action=..COMPRESS => Length of output data. */
+ /* If action=..DECOMPRESS => Length of output data. */
+);
+
+/******************************************************************************/
+/* End of COMPRESS.H */
+/******************************************************************************/
+
+
+/******************************************************************************/
+/* fast_copy.h */
+/******************************************************************************/
+
+/* This function copies a block of memory very quickly. */
+/* The exact speed depends on the relative alignment of the blocks of memory. */
+/* PRE : 0<=src_len<=(2^32)-1 . */
+/* PRE : Source and destination blocks must not overlap. */
+/* POST : MEM[dst_adr,dst_adr+src_len-1]=MEM[src_adr,src_adr+src_len-1]. */
+/* POST : MEM[dst_adr,dst_adr+src_len-1] is the only memory changed. */
+
+#define fast_copy(src,dst,len) memcpy(dst,src,len)
+
+/******************************************************************************/
+/* End of fast_copy.h */
+/******************************************************************************/
+
+#endif
diff --git a/drivers/char/ftape/compressor/zftape-compress.c b/drivers/char/ftape/compressor/zftape-compress.c
new file mode 100644
index 000000000000..220a227e6061
--- /dev/null
+++ b/drivers/char/ftape/compressor/zftape-compress.c
@@ -0,0 +1,1203 @@
+/*
+ * Copyright (C) 1994-1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * This file implements a "generic" interface between the *
+ * zftape-driver and a compression-algorithm. The *
+ * compression-algorithm currently used is a LZ77. I use the *
+ * implementation lzrw3 by Ross N. Williams (Renaissance *
+ * Software). The compression program itself is in the file
+ * lzrw3.c * and lzrw3.h. To adopt another compression algorithm
+ * the functions * zft_compress() and zft_uncompress() must be
+ * changed * appropriately. See below.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../compressor/zftape-compress.h"
+#include "../zftape/zftape-vtbl.h"
+#include "../compressor/lzrw3.h"
+
+/*
+ * global variables
+ */
+
+/* I handle the allocation of this buffer as a special case, because
+ * it's size varies depending on the tape length inserted.
+ */
+
+/* local variables
+ */
+static void *zftc_wrk_mem = NULL;
+static __u8 *zftc_buf = NULL;
+static void *zftc_scratch_buf = NULL;
+
+/* compression statistics
+ */
+static unsigned int zftc_wr_uncompressed = 0;
+static unsigned int zftc_wr_compressed = 0;
+static unsigned int zftc_rd_uncompressed = 0;
+static unsigned int zftc_rd_compressed = 0;
+
+/* forward */
+static int zftc_write(int *write_cnt,
+ __u8 *dst_buf, const int seg_sz,
+ const __u8 __user *src_buf, const int req_len,
+ const zft_position *pos, const zft_volinfo *volume);
+static int zftc_read(int *read_cnt,
+ __u8 __user *dst_buf, const int to_do,
+ const __u8 *src_buf, const int seg_sz,
+ const zft_position *pos, const zft_volinfo *volume);
+static int zftc_seek(unsigned int new_block_pos,
+ zft_position *pos, const zft_volinfo *volume,
+ __u8 *buffer);
+static void zftc_lock (void);
+static void zftc_reset (void);
+static void zftc_cleanup(void);
+static void zftc_stats (void);
+
+/* compressed segment. This conforms to QIC-80-MC, Revision K.
+ *
+ * Rev. K applies to tapes with `fixed length format' which is
+ * indicated by format code 2,3 and 5. See below for format code 4 and 6
+ *
+ * 2 bytes: offset of compression segment structure
+ * 29k > offset >= 29k-18: data from previous segment ens in this
+ * segment and no compressed block starts
+ * in this segment
+ * offset == 0: data from previous segment occupies entire
+ * segment and continues in next segment
+ * n bytes: remainder from previous segment
+ *
+ * Rev. K:
+ * 4 bytes: 4 bytes: files set byte offset
+ * Post Rev. K and QIC-3020/3020:
+ * 8 bytes: 8 bytes: files set byte offset
+ * 2 bytes: byte count N (amount of data following)
+ * bit 15 is set if data is compressed, bit 15 is not
+ * set if data is uncompressed
+ * N bytes: data (as much as specified in the byte count)
+ * 2 bytes: byte count N_1 of next cluster
+ * N_1 bytes: data of next cluset
+ * 2 bytes: byte count N_2 of next cluster
+ * N_2 bytes: ...
+ *
+ * Note that the `N' byte count accounts only for the bytes that in the
+ * current segment if the cluster spans to the next segment.
+ */
+
+typedef struct
+{
+ int cmpr_pos; /* actual position in compression buffer */
+ int cmpr_sz; /* what is left in the compression buffer
+ * when copying the compressed data to the
+ * deblock buffer
+ */
+ unsigned int first_block; /* location of header information in
+ * this segment
+ */
+ unsigned int count; /* amount of data of current block
+ * contained in current segment
+ */
+ unsigned int offset; /* offset in current segment */
+ unsigned int spans:1; /* might continue in next segment */
+ unsigned int uncmpr; /* 0x8000 if this block contains
+ * uncompressed data
+ */
+ __s64 foffs; /* file set byte offset, same as in
+ * compression map segment
+ */
+} cmpr_info;
+
+static cmpr_info cseg; /* static data. Must be kept uptodate and shared by
+ * read, write and seek functions
+ */
+
+#define DUMP_CMPR_INFO(level, msg, info) \
+ TRACE(level, msg "\n" \
+ KERN_INFO "cmpr_pos : %d\n" \
+ KERN_INFO "cmpr_sz : %d\n" \
+ KERN_INFO "first_block: %d\n" \
+ KERN_INFO "count : %d\n" \
+ KERN_INFO "offset : %d\n" \
+ KERN_INFO "spans : %d\n" \
+ KERN_INFO "uncmpr : 0x%04x\n" \
+ KERN_INFO "foffs : " LL_X, \
+ (info)->cmpr_pos, (info)->cmpr_sz, (info)->first_block, \
+ (info)->count, (info)->offset, (info)->spans == 1, \
+ (info)->uncmpr, LL((info)->foffs))
+
+/* dispatch compression segment info, return error code
+ *
+ * afterwards, cseg->offset points to start of data of the NEXT
+ * compressed block, and cseg->count contains the amount of data
+ * left in the actual compressed block. cseg->spans is set to 1 if
+ * the block is continued in the following segment. Otherwise it is
+ * set to 0.
+ */
+static int get_cseg (cmpr_info *cinfo, const __u8 *buff,
+ const unsigned int seg_sz,
+ const zft_volinfo *volume)
+{
+ TRACE_FUN(ft_t_flow);
+
+ cinfo->first_block = GET2(buff, 0);
+ if (cinfo->first_block == 0) { /* data spans to next segment */
+ cinfo->count = seg_sz - sizeof(__u16);
+ cinfo->offset = seg_sz;
+ cinfo->spans = 1;
+ } else { /* cluster definetely ends in this segment */
+ if (cinfo->first_block > seg_sz) {
+ /* data corrupted */
+ TRACE_ABORT(-EIO, ft_t_err, "corrupted data:\n"
+ KERN_INFO "segment size: %d\n"
+ KERN_INFO "first block : %d",
+ seg_sz, cinfo->first_block);
+ }
+ cinfo->count = cinfo->first_block - sizeof(__u16);
+ cinfo->offset = cinfo->first_block;
+ cinfo->spans = 0;
+ }
+ /* now get the offset the first block should have in the
+ * uncompressed data stream.
+ *
+ * For this magic `18' refer to CRF-3 standard or QIC-80MC,
+ * Rev. K.
+ */
+ if ((seg_sz - cinfo->offset) > 18) {
+ if (volume->qic113) { /* > revision K */
+ TRACE(ft_t_data_flow, "New QIC-113 compliance");
+ cinfo->foffs = GET8(buff, cinfo->offset);
+ cinfo->offset += sizeof(__s64);
+ } else {
+ TRACE(/* ft_t_data_flow */ ft_t_noise, "pre QIC-113 version");
+ cinfo->foffs = (__s64)GET4(buff, cinfo->offset);
+ cinfo->offset += sizeof(__u32);
+ }
+ }
+ if (cinfo->foffs > volume->size) {
+ TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n"
+ KERN_INFO "offset in current volume: %d\n"
+ KERN_INFO "size of current volume : %d",
+ (int)(cinfo->foffs>>10), (int)(volume->size>>10));
+ }
+ if (cinfo->cmpr_pos + cinfo->count > volume->blk_sz) {
+ TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n"
+ KERN_INFO "block size : %d\n"
+ KERN_INFO "data record: %d",
+ volume->blk_sz, cinfo->cmpr_pos + cinfo->count);
+ }
+ DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", cinfo);
+ TRACE_EXIT 0;
+}
+
+/* This one is called, when a new cluster starts in same segment.
+ *
+ * Note: if this is the first cluster in the current segment, we must
+ * not check whether there are more than 18 bytes available because
+ * this have already been done in get_cseg() and there may be less
+ * than 18 bytes available due to header information.
+ *
+ */
+static void get_next_cluster(cmpr_info *cluster, const __u8 *buff,
+ const int seg_sz, const int finish)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (seg_sz - cluster->offset > 18 || cluster->foffs != 0) {
+ cluster->count = GET2(buff, cluster->offset);
+ cluster->uncmpr = cluster->count & 0x8000;
+ cluster->count -= cluster->uncmpr;
+ cluster->offset += sizeof(__u16);
+ cluster->foffs = 0;
+ if ((cluster->offset + cluster->count) < seg_sz) {
+ cluster->spans = 0;
+ } else if (cluster->offset + cluster->count == seg_sz) {
+ cluster->spans = !finish;
+ } else {
+ /* either an error or a volume written by an
+ * old version. If this is a data error, then we'll
+ * catch it later.
+ */
+ TRACE(ft_t_data_flow, "Either error or old volume");
+ cluster->spans = 1;
+ cluster->count = seg_sz - cluster->offset;
+ }
+ } else {
+ cluster->count = 0;
+ cluster->spans = 0;
+ cluster->foffs = 0;
+ }
+ DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */ , "", cluster);
+ TRACE_EXIT;
+}
+
+static void zftc_lock(void)
+{
+}
+
+/* this function is needed for zftape_reset_position in zftape-io.c
+ */
+static void zftc_reset(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ memset((void *)&cseg, '\0', sizeof(cseg));
+ zftc_stats();
+ TRACE_EXIT;
+}
+
+static int cmpr_mem_initialized = 0;
+static unsigned int alloc_blksz = 0;
+
+static int zft_allocate_cmpr_mem(unsigned int blksz)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (cmpr_mem_initialized && blksz == alloc_blksz) {
+ TRACE_EXIT 0;
+ }
+ TRACE_CATCH(zft_vmalloc_once(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE),
+ zftc_cleanup());
+ TRACE_CATCH(zft_vmalloc_always(&zftc_buf, blksz + CMPR_OVERRUN),
+ zftc_cleanup());
+ alloc_blksz = blksz;
+ TRACE_CATCH(zft_vmalloc_always(&zftc_scratch_buf, blksz+CMPR_OVERRUN),
+ zftc_cleanup());
+ cmpr_mem_initialized = 1;
+ TRACE_EXIT 0;
+}
+
+static void zftc_cleanup(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_vfree(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE);
+ zft_vfree(&zftc_buf, alloc_blksz + CMPR_OVERRUN);
+ zft_vfree(&zftc_scratch_buf, alloc_blksz + CMPR_OVERRUN);
+ cmpr_mem_initialized = alloc_blksz = 0;
+ TRACE_EXIT;
+}
+
+/*****************************************************************************
+ * *
+ * The following two functions "ftape_compress()" and *
+ * "ftape_uncompress()" are the interface to the actual compression *
+ * algorithm (i.e. they are calling the "compress()" function from *
+ * the lzrw3 package for now). These routines could quite easily be *
+ * changed to adopt another compression algorithm instead of lzrw3, *
+ * which currently is used. *
+ * *
+ *****************************************************************************/
+
+/* called by zft_compress_write() to perform the compression. Must
+ * return the size of the compressed data.
+ *
+ * NOTE: The size of the compressed data should not exceed the size of
+ * the uncompressed data. Most compression algorithms have means
+ * to store data unchanged if the "compressed" data amount would
+ * exceed the original one. Mostly this is done by storing some
+ * flag-bytes in front of the compressed data to indicate if it
+ * is compressed or not. Thus the worst compression result
+ * length is the original length plus those flag-bytes.
+ *
+ * We don't want that, as the QIC-80 standard provides a means
+ * of marking uncompressed blocks by simply setting bit 15 of
+ * the compressed block's length. Thus a compessed block can
+ * have at most a length of 2^15-1 bytes. The QIC-80 standard
+ * restricts the block-length even further, allowing only 29k -
+ * 6 bytes.
+ *
+ * Currently, the maximum blocksize used by zftape is 28k.
+ *
+ * In short: don't exceed the length of the input-package, set
+ * bit 15 of the compressed size to 1 if you have copied data
+ * instead of compressing it.
+ */
+static int zft_compress(__u8 *in_buffer, unsigned int in_sz, __u8 *out_buffer)
+{
+ __s32 compressed_sz;
+ TRACE_FUN(ft_t_flow);
+
+
+ lzrw3_compress(COMPRESS_ACTION_COMPRESS, zftc_wrk_mem,
+ in_buffer, in_sz, out_buffer, &compressed_sz);
+ if (TRACE_LEVEL >= ft_t_info) {
+ /* the compiler will optimize this away when
+ * compiled with NO_TRACE_AT_ALL option
+ */
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "before compression: %d bytes\n"
+ KERN_INFO "after compresison : %d bytes",
+ in_sz,
+ (int)(compressed_sz < 0
+ ? -compressed_sz : compressed_sz));
+ /* for statistical purposes
+ */
+ zftc_wr_compressed += (compressed_sz < 0
+ ? -compressed_sz : compressed_sz);
+ zftc_wr_uncompressed += in_sz;
+ }
+ TRACE_EXIT (int)compressed_sz;
+}
+
+/* called by zft_compress_read() to decompress the data. Must
+ * return the size of the decompressed data for sanity checks
+ * (compared with zft_blk_sz)
+ *
+ * NOTE: Read the note for zft_compress() above! If bit 15 of the
+ * parameter in_sz is set, then the data in in_buffer isn't
+ * compressed, which must be handled by the un-compression
+ * algorithm. (I changed lzrw3 to handle this.)
+ *
+ * The parameter max_out_sz is needed to prevent buffer overruns when
+ * uncompressing corrupt data.
+ */
+static unsigned int zft_uncompress(__u8 *in_buffer,
+ int in_sz,
+ __u8 *out_buffer,
+ unsigned int max_out_sz)
+{
+ TRACE_FUN(ft_t_flow);
+
+ lzrw3_compress(COMPRESS_ACTION_DECOMPRESS, zftc_wrk_mem,
+ in_buffer, (__s32)in_sz,
+ out_buffer, (__u32 *)&max_out_sz);
+
+ if (TRACE_LEVEL >= ft_t_info) {
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "before decompression: %d bytes\n"
+ KERN_INFO "after decompression : %d bytes",
+ in_sz < 0 ? -in_sz : in_sz,(int)max_out_sz);
+ /* for statistical purposes
+ */
+ zftc_rd_compressed += in_sz < 0 ? -in_sz : in_sz;
+ zftc_rd_uncompressed += max_out_sz;
+ }
+ TRACE_EXIT (unsigned int)max_out_sz;
+}
+
+/* print some statistics about the efficiency of the compression to
+ * the kernel log
+ */
+static void zftc_stats(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (TRACE_LEVEL < ft_t_info) {
+ TRACE_EXIT;
+ }
+ if (zftc_wr_uncompressed != 0) {
+ if (zftc_wr_compressed > (1<<14)) {
+ TRACE(ft_t_info, "compression statistics (writing):\n"
+ KERN_INFO " compr./uncmpr. : %3d %%",
+ (((zftc_wr_compressed>>10) * 100)
+ / (zftc_wr_uncompressed>>10)));
+ } else {
+ TRACE(ft_t_info, "compression statistics (writing):\n"
+ KERN_INFO " compr./uncmpr. : %3d %%",
+ ((zftc_wr_compressed * 100)
+ / zftc_wr_uncompressed));
+ }
+ }
+ if (zftc_rd_uncompressed != 0) {
+ if (zftc_rd_compressed > (1<<14)) {
+ TRACE(ft_t_info, "compression statistics (reading):\n"
+ KERN_INFO " compr./uncmpr. : %3d %%",
+ (((zftc_rd_compressed>>10) * 100)
+ / (zftc_rd_uncompressed>>10)));
+ } else {
+ TRACE(ft_t_info, "compression statistics (reading):\n"
+ KERN_INFO " compr./uncmpr. : %3d %%",
+ ((zftc_rd_compressed * 100)
+ / zftc_rd_uncompressed));
+ }
+ }
+ /* only print it once: */
+ zftc_wr_uncompressed =
+ zftc_wr_compressed =
+ zftc_rd_uncompressed =
+ zftc_rd_compressed = 0;
+ TRACE_EXIT;
+}
+
+/* start new compressed block
+ */
+static int start_new_cseg(cmpr_info *cluster,
+ char *dst_buf,
+ const zft_position *pos,
+ const unsigned int blk_sz,
+ const char *src_buf,
+ const int this_segs_sz,
+ const int qic113)
+{
+ int size_left;
+ int cp_cnt;
+ int buf_pos;
+ TRACE_FUN(ft_t_flow);
+
+ size_left = this_segs_sz - sizeof(__u16) - cluster->cmpr_sz;
+ TRACE(ft_t_data_flow,"\n"
+ KERN_INFO "segment size : %d\n"
+ KERN_INFO "compressed_sz: %d\n"
+ KERN_INFO "size_left : %d",
+ this_segs_sz, cluster->cmpr_sz, size_left);
+ if (size_left > 18) { /* start a new cluseter */
+ cp_cnt = cluster->cmpr_sz;
+ cluster->cmpr_sz = 0;
+ buf_pos = cp_cnt + sizeof(__u16);
+ PUT2(dst_buf, 0, buf_pos);
+
+ if (qic113) {
+ __s64 foffs = pos->volume_pos;
+ if (cp_cnt) foffs += (__s64)blk_sz;
+
+ TRACE(ft_t_data_flow, "new style QIC-113 header");
+ PUT8(dst_buf, buf_pos, foffs);
+ buf_pos += sizeof(__s64);
+ } else {
+ __u32 foffs = (__u32)pos->volume_pos;
+ if (cp_cnt) foffs += (__u32)blk_sz;
+
+ TRACE(ft_t_data_flow, "old style QIC-80MC header");
+ PUT4(dst_buf, buf_pos, foffs);
+ buf_pos += sizeof(__u32);
+ }
+ } else if (size_left >= 0) {
+ cp_cnt = cluster->cmpr_sz;
+ cluster->cmpr_sz = 0;
+ buf_pos = cp_cnt + sizeof(__u16);
+ PUT2(dst_buf, 0, buf_pos);
+ /* zero unused part of segment. */
+ memset(dst_buf + buf_pos, '\0', size_left);
+ buf_pos = this_segs_sz;
+ } else { /* need entire segment and more space */
+ PUT2(dst_buf, 0, 0);
+ cp_cnt = this_segs_sz - sizeof(__u16);
+ cluster->cmpr_sz -= cp_cnt;
+ buf_pos = this_segs_sz;
+ }
+ memcpy(dst_buf + sizeof(__u16), src_buf + cluster->cmpr_pos, cp_cnt);
+ cluster->cmpr_pos += cp_cnt;
+ TRACE_EXIT buf_pos;
+}
+
+/* return-value: the number of bytes removed from the user-buffer
+ * `src_buf' or error code
+ *
+ * int *write_cnt : how much actually has been moved to the
+ * dst_buf. Need not be initialized when
+ * function returns with an error code
+ * (negativ return value)
+ * __u8 *dst_buf : kernel space buffer where the has to be
+ * copied to. The contents of this buffers
+ * goes to a specific segment.
+ * const int seg_sz : the size of the segment dst_buf will be
+ * copied to.
+ * const zft_position *pos : struct containing the coordinates in
+ * the current volume (byte position,
+ * segment id of current segment etc)
+ * const zft_volinfo *volume: information about the current volume,
+ * size etc.
+ * const __u8 *src_buf : user space buffer that contains the
+ * data the user wants to be written to
+ * tape.
+ * const int req_len : the amount of data the user wants to be
+ * written to tape.
+ */
+static int zftc_write(int *write_cnt,
+ __u8 *dst_buf, const int seg_sz,
+ const __u8 __user *src_buf, const int req_len,
+ const zft_position *pos, const zft_volinfo *volume)
+{
+ int req_len_left = req_len;
+ int result;
+ int len_left;
+ int buf_pos_write = pos->seg_byte_pos;
+ TRACE_FUN(ft_t_flow);
+
+ /* Note: we do not unlock the module because
+ * there are some values cached in that `cseg' variable. We
+ * don't don't want to use this information when being
+ * unloaded by kerneld even when the tape is full or when we
+ * cannot allocate enough memory.
+ */
+ if (pos->tape_pos > (volume->size-volume->blk_sz-ZFT_CMPR_OVERHEAD)) {
+ TRACE_EXIT -ENOSPC;
+ }
+ if (zft_allocate_cmpr_mem(volume->blk_sz) < 0) {
+ /* should we unlock the module? But it shouldn't
+ * be locked anyway ...
+ */
+ TRACE_EXIT -ENOMEM;
+ }
+ if (buf_pos_write == 0) { /* fill a new segment */
+ *write_cnt = buf_pos_write = start_new_cseg(&cseg,
+ dst_buf,
+ pos,
+ volume->blk_sz,
+ zftc_buf,
+ seg_sz,
+ volume->qic113);
+ if (cseg.cmpr_sz == 0 && cseg.cmpr_pos != 0) {
+ req_len_left -= result = volume->blk_sz;
+ cseg.cmpr_pos = 0;
+ } else {
+ result = 0;
+ }
+ } else {
+ *write_cnt = result = 0;
+ }
+
+ len_left = seg_sz - buf_pos_write;
+ while ((req_len_left > 0) && (len_left > 18)) {
+ /* now we have some size left for a new compressed
+ * block. We know, that the compression buffer is
+ * empty (else there wouldn't be any space left).
+ */
+ if (copy_from_user(zftc_scratch_buf, src_buf + result,
+ volume->blk_sz) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ req_len_left -= volume->blk_sz;
+ cseg.cmpr_sz = zft_compress(zftc_scratch_buf, volume->blk_sz,
+ zftc_buf);
+ if (cseg.cmpr_sz < 0) {
+ cseg.uncmpr = 0x8000;
+ cseg.cmpr_sz = -cseg.cmpr_sz;
+ } else {
+ cseg.uncmpr = 0;
+ }
+ /* increment "result" iff we copied the entire
+ * compressed block to the zft_deblock_buf
+ */
+ len_left -= sizeof(__u16);
+ if (len_left >= cseg.cmpr_sz) {
+ len_left -= cseg.count = cseg.cmpr_sz;
+ cseg.cmpr_pos = cseg.cmpr_sz = 0;
+ result += volume->blk_sz;
+ } else {
+ cseg.cmpr_sz -=
+ cseg.cmpr_pos =
+ cseg.count = len_left;
+ len_left = 0;
+ }
+ PUT2(dst_buf, buf_pos_write, cseg.uncmpr | cseg.count);
+ buf_pos_write += sizeof(__u16);
+ memcpy(dst_buf + buf_pos_write, zftc_buf, cseg.count);
+ buf_pos_write += cseg.count;
+ *write_cnt += cseg.count + sizeof(__u16);
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ }
+ /* erase the remainder of the segment if less than 18 bytes
+ * left (18 bytes is due to the QIC-80 standard)
+ */
+ if (len_left <= 18) {
+ memset(dst_buf + buf_pos_write, '\0', len_left);
+ (*write_cnt) += len_left;
+ }
+ TRACE(ft_t_data_flow, "returning %d", result);
+ TRACE_EXIT result;
+}
+
+/* out:
+ *
+ * int *read_cnt: the number of bytes we removed from the zft_deblock_buf
+ * (result)
+ * int *to_do : the remaining size of the read-request.
+ *
+ * in:
+ *
+ * char *buff : buff is the address of the upper part of the user
+ * buffer, that hasn't been filled with data yet.
+
+ * int buf_pos_read : copy of from _ftape_read()
+ * int buf_len_read : copy of buf_len_rd from _ftape_read()
+ * char *zft_deblock_buf: zft_deblock_buf
+ * unsigned short blk_sz: the block size valid for this volume, may differ
+ * from zft_blk_sz.
+ * int finish: if != 0 means that this is the last segment belonging
+ * to this volume
+ * returns the amount of data actually copied to the user-buffer
+ *
+ * to_do MUST NOT SHRINK except to indicate an EOF. In this case *to_do has to
+ * be set to 0
+ */
+static int zftc_read (int *read_cnt,
+ __u8 __user *dst_buf, const int to_do,
+ const __u8 *src_buf, const int seg_sz,
+ const zft_position *pos, const zft_volinfo *volume)
+{
+ int uncompressed_sz;
+ int result = 0;
+ int remaining = to_do;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE_CATCH(zft_allocate_cmpr_mem(volume->blk_sz),);
+ if (pos->seg_byte_pos == 0) {
+ /* new segment just read
+ */
+ TRACE_CATCH(get_cseg(&cseg, src_buf, seg_sz, volume),
+ *read_cnt = 0);
+ memcpy(zftc_buf + cseg.cmpr_pos, src_buf + sizeof(__u16),
+ cseg.count);
+ cseg.cmpr_pos += cseg.count;
+ *read_cnt = cseg.offset;
+ DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", &cseg);
+ } else {
+ *read_cnt = 0;
+ }
+ /* loop and uncompress until user buffer full or
+ * deblock-buffer empty
+ */
+ TRACE(ft_t_data_flow, "compressed_sz: %d, compos : %d, *read_cnt: %d",
+ cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt);
+ while ((cseg.spans == 0) && (remaining > 0)) {
+ if (cseg.cmpr_pos != 0) { /* cmpr buf is not empty */
+ uncompressed_sz =
+ zft_uncompress(zftc_buf,
+ cseg.uncmpr == 0x8000 ?
+ -cseg.cmpr_pos : cseg.cmpr_pos,
+ zftc_scratch_buf,
+ volume->blk_sz);
+ if (uncompressed_sz != volume->blk_sz) {
+ *read_cnt = 0;
+ TRACE_ABORT(-EIO, ft_t_warn,
+ "Uncompressed blk (%d) != blk size (%d)",
+ uncompressed_sz, volume->blk_sz);
+ }
+ if (copy_to_user(dst_buf + result,
+ zftc_scratch_buf,
+ uncompressed_sz) != 0 ) {
+ TRACE_EXIT -EFAULT;
+ }
+ remaining -= uncompressed_sz;
+ result += uncompressed_sz;
+ cseg.cmpr_pos = 0;
+ }
+ if (remaining > 0) {
+ get_next_cluster(&cseg, src_buf, seg_sz,
+ volume->end_seg == pos->seg_pos);
+ if (cseg.count != 0) {
+ memcpy(zftc_buf, src_buf + cseg.offset,
+ cseg.count);
+ cseg.cmpr_pos = cseg.count;
+ cseg.offset += cseg.count;
+ *read_cnt += cseg.count + sizeof(__u16);
+ } else {
+ remaining = 0;
+ }
+ }
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "compressed_sz: %d\n"
+ KERN_INFO "compos : %d\n"
+ KERN_INFO "*read_cnt : %d",
+ cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt);
+ }
+ if (seg_sz - cseg.offset <= 18) {
+ *read_cnt += seg_sz - cseg.offset;
+ TRACE(ft_t_data_flow, "expanding read cnt to: %d", *read_cnt);
+ }
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "segment size : %d\n"
+ KERN_INFO "read count : %d\n"
+ KERN_INFO "buf_pos_read : %d\n"
+ KERN_INFO "remaining : %d",
+ seg_sz, *read_cnt, pos->seg_byte_pos,
+ seg_sz - *read_cnt - pos->seg_byte_pos);
+ TRACE(ft_t_data_flow, "returning: %d", result);
+ TRACE_EXIT result;
+}
+
+/* seeks to the new data-position. Reads sometimes a segment.
+ *
+ * start_seg and end_seg give the boundaries of the current volume
+ * blk_sz is the blk_sz of the current volume as stored in the
+ * volume label
+ *
+ * We don't allow blocksizes less than 1024 bytes, therefore we don't need
+ * a 64 bit argument for new_block_pos.
+ */
+
+static int seek_in_segment(const unsigned int to_do, cmpr_info *c_info,
+ const char *src_buf, const int seg_sz,
+ const int seg_pos, const zft_volinfo *volume);
+static int slow_seek_forward_until_error(const unsigned int distance,
+ cmpr_info *c_info, zft_position *pos,
+ const zft_volinfo *volume, __u8 *buf);
+static int search_valid_segment(unsigned int segment,
+ const unsigned int end_seg,
+ const unsigned int max_foffs,
+ zft_position *pos, cmpr_info *c_info,
+ const zft_volinfo *volume, __u8 *buf);
+static int slow_seek_forward(unsigned int dest, cmpr_info *c_info,
+ zft_position *pos, const zft_volinfo *volume,
+ __u8 *buf);
+static int compute_seg_pos(unsigned int dest, zft_position *pos,
+ const zft_volinfo *volume);
+
+#define ZFT_SLOW_SEEK_THRESHOLD 10 /* segments */
+#define ZFT_FAST_SEEK_MAX_TRIALS 10 /* times */
+#define ZFT_FAST_SEEK_BACKUP 10 /* segments */
+
+static int zftc_seek(unsigned int new_block_pos,
+ zft_position *pos, const zft_volinfo *volume, __u8 *buf)
+{
+ unsigned int dest;
+ int limit;
+ int distance;
+ int result = 0;
+ int seg_dist;
+ int new_seg;
+ int old_seg = 0;
+ int fast_seek_trials = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (new_block_pos == 0) {
+ pos->seg_pos = volume->start_seg;
+ pos->seg_byte_pos = 0;
+ pos->volume_pos = 0;
+ zftc_reset();
+ TRACE_EXIT 0;
+ }
+ dest = new_block_pos * (volume->blk_sz >> 10);
+ distance = dest - (pos->volume_pos >> 10);
+ while (distance != 0) {
+ seg_dist = compute_seg_pos(dest, pos, volume);
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "seg_dist: %d\n"
+ KERN_INFO "distance: %d\n"
+ KERN_INFO "dest : %d\n"
+ KERN_INFO "vpos : %d\n"
+ KERN_INFO "seg_pos : %d\n"
+ KERN_INFO "trials : %d",
+ seg_dist, distance, dest,
+ (unsigned int)(pos->volume_pos>>10), pos->seg_pos,
+ fast_seek_trials);
+ if (distance > 0) {
+ if (seg_dist < 0) {
+ TRACE(ft_t_bug, "BUG: distance %d > 0, "
+ "segment difference %d < 0",
+ distance, seg_dist);
+ result = -EIO;
+ break;
+ }
+ new_seg = pos->seg_pos + seg_dist;
+ if (new_seg > volume->end_seg) {
+ new_seg = volume->end_seg;
+ }
+ if (old_seg == new_seg || /* loop */
+ seg_dist <= ZFT_SLOW_SEEK_THRESHOLD ||
+ fast_seek_trials >= ZFT_FAST_SEEK_MAX_TRIALS) {
+ TRACE(ft_t_noise, "starting slow seek:\n"
+ KERN_INFO "fast seek failed too often: %s\n"
+ KERN_INFO "near target position : %s\n"
+ KERN_INFO "looping between two segs : %s",
+ (fast_seek_trials >=
+ ZFT_FAST_SEEK_MAX_TRIALS)
+ ? "yes" : "no",
+ (seg_dist <= ZFT_SLOW_SEEK_THRESHOLD)
+ ? "yes" : "no",
+ (old_seg == new_seg)
+ ? "yes" : "no");
+ result = slow_seek_forward(dest, &cseg,
+ pos, volume, buf);
+ break;
+ }
+ old_seg = new_seg;
+ limit = volume->end_seg;
+ fast_seek_trials ++;
+ for (;;) {
+ result = search_valid_segment(new_seg, limit,
+ volume->size,
+ pos, &cseg,
+ volume, buf);
+ if (result == 0 || result == -EINTR) {
+ break;
+ }
+ if (new_seg == volume->start_seg) {
+ result = -EIO; /* set errror
+ * condition
+ */
+ break;
+ }
+ limit = new_seg;
+ new_seg -= ZFT_FAST_SEEK_BACKUP;
+ if (new_seg < volume->start_seg) {
+ new_seg = volume->start_seg;
+ }
+ }
+ if (result < 0) {
+ TRACE(ft_t_warn,
+ "Couldn't find a readable segment");
+ break;
+ }
+ } else /* if (distance < 0) */ {
+ if (seg_dist > 0) {
+ TRACE(ft_t_bug, "BUG: distance %d < 0, "
+ "segment difference %d >0",
+ distance, seg_dist);
+ result = -EIO;
+ break;
+ }
+ new_seg = pos->seg_pos + seg_dist;
+ if (fast_seek_trials > 0 && seg_dist == 0) {
+ /* this avoids sticking to the same
+ * segment all the time. On the other hand:
+ * if we got here for the first time, and the
+ * deblock_buffer still contains a valid
+ * segment, then there is no need to skip to
+ * the previous segment if the desired position
+ * is inside this segment.
+ */
+ new_seg --;
+ }
+ if (new_seg < volume->start_seg) {
+ new_seg = volume->start_seg;
+ }
+ limit = pos->seg_pos;
+ fast_seek_trials ++;
+ for (;;) {
+ result = search_valid_segment(new_seg, limit,
+ pos->volume_pos,
+ pos, &cseg,
+ volume, buf);
+ if (result == 0 || result == -EINTR) {
+ break;
+ }
+ if (new_seg == volume->start_seg) {
+ result = -EIO; /* set errror
+ * condition
+ */
+ break;
+ }
+ limit = new_seg;
+ new_seg -= ZFT_FAST_SEEK_BACKUP;
+ if (new_seg < volume->start_seg) {
+ new_seg = volume->start_seg;
+ }
+ }
+ if (result < 0) {
+ TRACE(ft_t_warn,
+ "Couldn't find a readable segment");
+ break;
+ }
+ }
+ distance = dest - (pos->volume_pos >> 10);
+ }
+ TRACE_EXIT result;
+}
+
+
+/* advance inside the given segment at most to_do bytes.
+ * of kilobytes moved
+ */
+
+static int seek_in_segment(const unsigned int to_do,
+ cmpr_info *c_info,
+ const char *src_buf,
+ const int seg_sz,
+ const int seg_pos,
+ const zft_volinfo *volume)
+{
+ int result = 0;
+ int blk_sz = volume->blk_sz >> 10;
+ int remaining = to_do;
+ TRACE_FUN(ft_t_flow);
+
+ if (c_info->offset == 0) {
+ /* new segment just read
+ */
+ TRACE_CATCH(get_cseg(c_info, src_buf, seg_sz, volume),);
+ c_info->cmpr_pos += c_info->count;
+ DUMP_CMPR_INFO(ft_t_noise, "", c_info);
+ }
+ /* loop and uncompress until user buffer full or
+ * deblock-buffer empty
+ */
+ TRACE(ft_t_noise, "compressed_sz: %d, compos : %d",
+ c_info->cmpr_sz, c_info->cmpr_pos);
+ while (c_info->spans == 0 && remaining > 0) {
+ if (c_info->cmpr_pos != 0) { /* cmpr buf is not empty */
+ result += blk_sz;
+ remaining -= blk_sz;
+ c_info->cmpr_pos = 0;
+ }
+ if (remaining > 0) {
+ get_next_cluster(c_info, src_buf, seg_sz,
+ volume->end_seg == seg_pos);
+ if (c_info->count != 0) {
+ c_info->cmpr_pos = c_info->count;
+ c_info->offset += c_info->count;
+ } else {
+ break;
+ }
+ }
+ /* Allow escape from this loop on signal!
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ DUMP_CMPR_INFO(ft_t_noise, "", c_info);
+ TRACE(ft_t_noise, "to_do: %d", remaining);
+ }
+ if (seg_sz - c_info->offset <= 18) {
+ c_info->offset = seg_sz;
+ }
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "segment size : %d\n"
+ KERN_INFO "buf_pos_read : %d\n"
+ KERN_INFO "remaining : %d",
+ seg_sz, c_info->offset,
+ seg_sz - c_info->offset);
+ TRACE_EXIT result;
+}
+
+static int slow_seek_forward_until_error(const unsigned int distance,
+ cmpr_info *c_info,
+ zft_position *pos,
+ const zft_volinfo *volume,
+ __u8 *buf)
+{
+ unsigned int remaining = distance;
+ int seg_sz;
+ int seg_pos;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ seg_pos = pos->seg_pos;
+ do {
+ TRACE_CATCH(seg_sz = zft_fetch_segment(seg_pos, buf,
+ FT_RD_AHEAD),);
+ /* now we have the contents of the actual segment in
+ * the deblock buffer
+ */
+ TRACE_CATCH(result = seek_in_segment(remaining, c_info, buf,
+ seg_sz, seg_pos,volume),);
+ remaining -= result;
+ pos->volume_pos += result<<10;
+ pos->seg_pos = seg_pos;
+ pos->seg_byte_pos = c_info->offset;
+ seg_pos ++;
+ if (seg_pos <= volume->end_seg && c_info->offset == seg_sz) {
+ pos->seg_pos ++;
+ pos->seg_byte_pos = 0;
+ c_info->offset = 0;
+ }
+ /* Allow escape from this loop on signal!
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "remaining: %d\n"
+ KERN_INFO "seg_pos: %d\n"
+ KERN_INFO "end_seg: %d\n"
+ KERN_INFO "result: %d",
+ remaining, seg_pos, volume->end_seg, result);
+ } while (remaining > 0 && seg_pos <= volume->end_seg);
+ TRACE_EXIT 0;
+}
+
+/* return segment id of next segment containing valid data, -EIO otherwise
+ */
+static int search_valid_segment(unsigned int segment,
+ const unsigned int end_seg,
+ const unsigned int max_foffs,
+ zft_position *pos,
+ cmpr_info *c_info,
+ const zft_volinfo *volume,
+ __u8 *buf)
+{
+ cmpr_info tmp_info;
+ int seg_sz;
+ TRACE_FUN(ft_t_flow);
+
+ memset(&tmp_info, 0, sizeof(cmpr_info));
+ while (segment <= end_seg) {
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ TRACE(ft_t_noise,
+ "Searching readable segment between %d and %d",
+ segment, end_seg);
+ seg_sz = zft_fetch_segment(segment, buf, FT_RD_AHEAD);
+ if ((seg_sz > 0) &&
+ (get_cseg (&tmp_info, buf, seg_sz, volume) >= 0) &&
+ (tmp_info.foffs != 0 || segment == volume->start_seg)) {
+ if ((tmp_info.foffs>>10) > max_foffs) {
+ TRACE_ABORT(-EIO, ft_t_noise, "\n"
+ KERN_INFO "cseg.foff: %d\n"
+ KERN_INFO "dest : %d",
+ (int)(tmp_info.foffs >> 10),
+ max_foffs);
+ }
+ DUMP_CMPR_INFO(ft_t_noise, "", &tmp_info);
+ *c_info = tmp_info;
+ pos->seg_pos = segment;
+ pos->volume_pos = c_info->foffs;
+ pos->seg_byte_pos = c_info->offset;
+ TRACE(ft_t_noise, "found segment at %d", segment);
+ TRACE_EXIT 0;
+ }
+ segment++;
+ }
+ TRACE_EXIT -EIO;
+}
+
+static int slow_seek_forward(unsigned int dest,
+ cmpr_info *c_info,
+ zft_position *pos,
+ const zft_volinfo *volume,
+ __u8 *buf)
+{
+ unsigned int distance;
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ distance = dest - (pos->volume_pos >> 10);
+ while ((distance > 0) &&
+ (result = slow_seek_forward_until_error(distance,
+ c_info,
+ pos,
+ volume,
+ buf)) < 0) {
+ if (result == -EINTR) {
+ break;
+ }
+ TRACE(ft_t_noise, "seg_pos: %d", pos->seg_pos);
+ /* the failing segment is either pos->seg_pos or
+ * pos->seg_pos + 1. There is no need to further try
+ * that segment, because ftape_read_segment() already
+ * has tried very much to read it. So we start with
+ * following segment, which is pos->seg_pos + 1
+ */
+ if(search_valid_segment(pos->seg_pos+1, volume->end_seg, dest,
+ pos, c_info,
+ volume, buf) < 0) {
+ TRACE(ft_t_noise, "search_valid_segment() failed");
+ result = -EIO;
+ break;
+ }
+ distance = dest - (pos->volume_pos >> 10);
+ result = 0;
+ TRACE(ft_t_noise, "segment: %d", pos->seg_pos);
+ /* found valid segment, retry the seek */
+ }
+ TRACE_EXIT result;
+}
+
+static int compute_seg_pos(const unsigned int dest,
+ zft_position *pos,
+ const zft_volinfo *volume)
+{
+ int segment;
+ int distance = dest - (pos->volume_pos >> 10);
+ unsigned int raw_size;
+ unsigned int virt_size;
+ unsigned int factor;
+ TRACE_FUN(ft_t_flow);
+
+ if (distance >= 0) {
+ raw_size = volume->end_seg - pos->seg_pos + 1;
+ virt_size = ((unsigned int)(volume->size>>10)
+ - (unsigned int)(pos->volume_pos>>10)
+ + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1);
+ virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS;
+ if (virt_size == 0 || raw_size == 0) {
+ TRACE_EXIT 0;
+ }
+ if (raw_size >= (1<<25)) {
+ factor = raw_size/(virt_size>>7);
+ } else {
+ factor = (raw_size<<7)/virt_size;
+ }
+ segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS);
+ segment = (segment * factor)>>7;
+ } else {
+ raw_size = pos->seg_pos - volume->start_seg + 1;
+ virt_size = ((unsigned int)(pos->volume_pos>>10)
+ + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1);
+ virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS;
+ if (virt_size == 0 || raw_size == 0) {
+ TRACE_EXIT 0;
+ }
+ if (raw_size >= (1<<25)) {
+ factor = raw_size/(virt_size>>7);
+ } else {
+ factor = (raw_size<<7)/virt_size;
+ }
+ segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS);
+ }
+ TRACE(ft_t_noise, "factor: %d/%d", factor, 1<<7);
+ TRACE_EXIT segment;
+}
+
+static struct zft_cmpr_ops cmpr_ops = {
+ zftc_write,
+ zftc_read,
+ zftc_seek,
+ zftc_lock,
+ zftc_reset,
+ zftc_cleanup
+};
+
+int zft_compressor_init(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+#ifdef MODULE
+ printk(KERN_INFO "zftape compressor v1.00a 970514 for " FTAPE_VERSION "\n");
+ if (TRACE_LEVEL >= ft_t_info) {
+ printk(
+KERN_INFO "(c) 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO "Compressor for zftape (lzrw3 algorithm)\n");
+ }
+#else /* !MODULE */
+ /* print a short no-nonsense boot message */
+ printk("zftape compressor v1.00a 970514\n");
+ printk("For use with " FTAPE_VERSION "\n");
+#endif /* MODULE */
+ TRACE(ft_t_info, "zft_compressor_init @ 0x%p", zft_compressor_init);
+ TRACE(ft_t_info, "installing compressor for zftape ...");
+ TRACE_CATCH(zft_cmpr_register(&cmpr_ops),);
+ TRACE_EXIT 0;
+}
+
+#ifdef MODULE
+
+MODULE_AUTHOR(
+ "(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de");
+MODULE_DESCRIPTION(
+"Compression routines for zftape. Uses the lzrw3 algorithm by Ross Williams");
+MODULE_LICENSE("GPL");
+
+/* Called by modules package when installing the driver
+ */
+int init_module(void)
+{
+ return zft_compressor_init();
+}
+
+#endif /* MODULE */
diff --git a/drivers/char/ftape/compressor/zftape-compress.h b/drivers/char/ftape/compressor/zftape-compress.h
new file mode 100644
index 000000000000..f200741e33bf
--- /dev/null
+++ b/drivers/char/ftape/compressor/zftape-compress.h
@@ -0,0 +1,83 @@
+#ifndef _ZFTAPE_COMPRESS_H
+#define _ZFTAPE_COMPRESS_H
+/*
+ * Copyright (c) 1994-1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/zftape-compress.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:32 $
+ *
+ * This file contains macros and definitions for zftape's
+ * builtin compression code.
+ *
+ */
+
+#include "../zftape/zftape-buffers.h"
+#include "../zftape/zftape-vtbl.h"
+#include "../compressor/lzrw3.h"
+
+/* CMPR_WRK_MEM_SIZE gives the size of the compression wrk_mem */
+/* I got these out of lzrw3.c */
+#define U(X) ((__u32) X)
+#define SIZE_P_BYTE (U(sizeof(__u8 *)))
+#define ALIGNMENT_FUDGE (U(16))
+
+#define CMPR_WRK_MEM_SIZE (U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE)
+
+/* the maximum number of bytes the size of the "compressed" data can
+ * exceed the uncompressed data. As it is quite useless to compress
+ * data twice it is sometimes the case that it is more efficient to
+ * copy a block of data but to feed it to the "compression"
+ * algorithm. In this case there are some flag bytes or the like
+ * proceding the "compressed" data. THAT MUST NOT BE THE CASE for the
+ * algorithm we use for this driver. Instead, the high bit 15 of
+ * compressed_size:
+ *
+ * compressed_size = ftape_compress()
+ *
+ * must be set in such a case.
+ *
+ * Nevertheless, it might also be as for lzrw3 that there is an
+ * "intermediate" overrun that exceeds the amount of the compressed
+ * data that is actually produced. During the algorithm we need in the
+ * worst case MAX_CMP_GROUP bytes more than the input-size.
+ */
+#define MAX_CMP_GROUP (2+16*2) /* from lzrw3.c */
+
+#define CMPR_OVERRUN MAX_CMP_GROUP /* during compression */
+
+/****************************************************/
+
+#define CMPR_BUFFER_SIZE (MAX_BLOCK_SIZE + CMPR_OVERRUN)
+
+/* the compression map stores the byte offset compressed blocks within
+ * the current volume for catridges with format code 2,3 and 5
+ * (and old versions of zftape) and the offset measured in kilobytes for
+ * format code 4 and 6. This gives us a possible max. size of a
+ * compressed volume of 1024*4GIG which should be enough.
+ */
+typedef __u32 CmprMap;
+
+/* globals
+ */
+
+/* exported functions
+ */
+
+#endif /* _ZFTAPE_COMPRESS_H */
diff --git a/drivers/char/ftape/lowlevel/Makefile b/drivers/char/ftape/lowlevel/Makefile
new file mode 100644
index 000000000000..febab07ba427
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/Makefile
@@ -0,0 +1,43 @@
+#
+# Copyright (C) 1996, 1997 Clau-Justus Heine.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/07 09:26:02 $
+#
+# Makefile for the lowlevel part QIC-40/80/3010/3020 floppy-tape
+# driver for Linux.
+#
+
+obj-$(CONFIG_FTAPE) += ftape.o
+
+ftape-objs := ftape-init.o fdc-io.o fdc-isr.o \
+ ftape-bsm.o ftape-ctl.o ftape-read.o ftape-rw.o \
+ ftape-write.o ftape-io.o ftape-calibr.o ftape-ecc.o fc-10.o \
+ ftape-buffer.o ftape-format.o ftape_syms.o
+
+ifeq ($(CONFIG_FTAPE),y)
+ftape-objs += ftape-setup.o
+endif
+
+ifndef CONFIG_FT_NO_TRACE_AT_ALL
+ftape-objs += ftape-tracing.o
+endif
+
+ifeq ($(CONFIG_FT_PROC_FS),y)
+ftape-objs += ftape-proc.o
+endif
diff --git a/drivers/char/ftape/lowlevel/fc-10.c b/drivers/char/ftape/lowlevel/fc-10.c
new file mode 100644
index 000000000000..9bc1cddade76
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fc-10.c
@@ -0,0 +1,175 @@
+/*
+ *
+
+ Copyright (C) 1993,1994 Jon Tombs.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ The entire guts of this program was written by dosemu, modified to
+ record reads and writes to the ports in the 0x180-0x188 address space,
+ while running the CMS program TAPE.EXE V2.0.5 supplied with the drive.
+
+ Modified to use an array of addresses and generally cleaned up (made
+ much shorter) 4 June 94, dosemu isn't that good at writing short code it
+ would seem :-). Made independent of 0x180, but I doubt it will work
+ at any other address.
+
+ Modified for distribution with ftape source. 21 June 94, SJL.
+
+ Modifications on 20 October 95, by Daniel Cohen (catman@wpi.edu):
+ Modified to support different DMA, IRQ, and IO Ports. Borland's
+ Turbo Debugger in virtual 8086 mode (TD386.EXE with hardware breakpoints
+ provided by the TDH386.SYS Device Driver) was used on the CMS program
+ TAPE V4.0.5. I set breakpoints on I/O to ports 0x180-0x187. Note that
+ CMS's program will not successfully configure the tape drive if you set
+ breakpoints on IO Reads, but you can set them on IO Writes without problems.
+ Known problems:
+ - You can not use DMA Channels 5 or 7.
+
+ Modification on 29 January 96, by Daniel Cohen (catman@wpi.edu):
+ Modified to only accept IRQs 3 - 7, or 9. Since we can only send a 3 bit
+ number representing the IRQ to the card, special handling is required when
+ IRQ 9 is selected. IRQ 2 and 9 are the same, and we should request IRQ 9
+ from the kernel while telling the card to use IRQ 2. Thanks to Greg
+ Crider (gcrider@iclnet.org) for finding and locating this bug, as well as
+ testing the patch.
+
+ Modification on 11 December 96, by Claus Heine (claus@momo.math.rwth-aachen.de):
+ Modified a little to use variahle ft_fdc_base, ft_fdc_irq, ft_fdc_dma
+ instead of preprocessor symbols. Thus we can compile this into the module
+ or kernel and let the user specify the options as command line arguments.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:04 $
+ *
+ * This file contains code for the CMS FC-10/FC-20 card.
+ */
+
+#include <asm/io.h>
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/fc-10.h"
+
+static __u16 inbs_magic[] = {
+ 0x3, 0x3, 0x0, 0x4, 0x7, 0x2, 0x5, 0x3, 0x1, 0x4,
+ 0x3, 0x5, 0x2, 0x0, 0x3, 0x7, 0x4, 0x2,
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+};
+
+static __u16 fc10_ports[] = {
+ 0x180, 0x210, 0x2A0, 0x300, 0x330, 0x340, 0x370
+};
+
+int fc10_enable(void)
+{
+ int i;
+ __u8 cardConfig = 0x00;
+ __u8 x;
+ TRACE_FUN(ft_t_flow);
+
+/* This code will only work if the FC-10 (or FC-20) is set to
+ * use DMA channels 1, 2, or 3. DMA channels 5 and 7 seem to be
+ * initialized by the same command as channels 1 and 3, respectively.
+ */
+ if (ft_fdc_dma > 3) {
+ TRACE_ABORT(0, ft_t_err,
+"Error: The FC-10/20 must be set to use DMA channels 1, 2, or 3!");
+ }
+/* Only allow the FC-10/20 to use IRQ 3-7, or 9. Note that CMS's program
+ * only accepts IRQ's 2-7, but in linux, IRQ 2 is the same as IRQ 9.
+ */
+ if (ft_fdc_irq < 3 || ft_fdc_irq == 8 || ft_fdc_irq > 9) {
+ TRACE_ABORT(0, ft_t_err,
+"Error: The FC-10/20 must be set to use IRQ levels 3 - 7, or 9!\n"
+KERN_INFO "Note: IRQ 9 is the same as IRQ 2");
+ }
+ /* Clear state machine ???
+ */
+ for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+ inb(ft_fdc_base + inbs_magic[i]);
+ }
+ outb(0x0, ft_fdc_base);
+
+ x = inb(ft_fdc_base);
+ if (x == 0x13 || x == 0x93) {
+ for (i = 1; i < 8; i++) {
+ if (inb(ft_fdc_base + i) != x) {
+ TRACE_EXIT 0;
+ }
+ }
+ } else {
+ TRACE_EXIT 0;
+ }
+
+ outb(0x8, ft_fdc_base);
+
+ for (i = 0; i < 8; i++) {
+ if (inb(ft_fdc_base + i) != 0x0) {
+ TRACE_EXIT 0;
+ }
+ }
+ outb(0x10, ft_fdc_base);
+
+ for (i = 0; i < 8; i++) {
+ if (inb(ft_fdc_base + i) != 0xff) {
+ TRACE_EXIT 0;
+ }
+ }
+
+ /* Okay, we found a FC-10 card ! ???
+ */
+ outb(0x0, fdc.ccr);
+
+ /* Clear state machine again ???
+ */
+ for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+ inb(ft_fdc_base + inbs_magic[i]);
+ }
+ /* Send io port */
+ for (i = 0; i < NR_ITEMS(fc10_ports); i++)
+ if (ft_fdc_base == fc10_ports[i])
+ cardConfig = i + 1;
+ if (cardConfig == 0) {
+ TRACE_EXIT 0; /* Invalid I/O Port */
+ }
+ /* and IRQ - If using IRQ 9, tell the FC card it is actually IRQ 2 */
+ if (ft_fdc_irq != 9)
+ cardConfig |= ft_fdc_irq << 3;
+ else
+ cardConfig |= 2 << 3;
+
+ /* and finally DMA Channel */
+ cardConfig |= ft_fdc_dma << 6;
+ outb(cardConfig, ft_fdc_base); /* DMA [2 bits]/IRQ [3 bits]/BASE [3 bits] */
+
+ /* Enable FC-10 ???
+ */
+ outb(0, fdc.ccr);
+ outb(0, fdc.dor2);
+ outb(FDC_DMA_MODE /* 8 */, fdc.dor);
+ outb(FDC_DMA_MODE /* 8 */, fdc.dor);
+ outb(1, fdc.dor2);
+
+ /*************************************
+ *
+ * cH: why the hell should this be necessary? This is done
+ * by fdc_reset()!!!
+ *
+ *************************************/
+ /* Initialize fdc, select drive B:
+ */
+ outb(FDC_DMA_MODE, fdc.dor); /* assert reset, dma & irq enabled */
+ /* 0x08 */
+ outb(FDC_DMA_MODE|FDC_RESET_NOT, fdc.dor); /* release reset */
+ /* 0x08 | 0x04 = 0x0c */
+ outb(FDC_DMA_MODE|FDC_RESET_NOT|FDC_MOTOR_1|FTAPE_SEL_B, fdc.dor);
+ /* 0x08 | 0x04 | 0x20 | 0x01 = 0x2d */
+ /* select drive 1 */ /* why not drive 0 ???? */
+ TRACE_EXIT (x == 0x93) ? 2 : 1;
+}
diff --git a/drivers/char/ftape/lowlevel/fc-10.h b/drivers/char/ftape/lowlevel/fc-10.h
new file mode 100644
index 000000000000..da7b88bca889
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fc-10.h
@@ -0,0 +1,39 @@
+#ifndef _FC_10_H
+#define _FC_10_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/09/19 09:05:22 $
+ *
+ * This file contains definitions for the FC-10 code
+ * of the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+/*
+ * fc-10.c defined global vars.
+ */
+
+/*
+ * fc-10.c defined global functions.
+ */
+extern int fc10_enable(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/fdc-io.c b/drivers/char/ftape/lowlevel/fdc-io.c
new file mode 100644
index 000000000000..1704a2a57048
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-io.c
@@ -0,0 +1,1352 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.c,v $
+ * $Revision: 1.7.4.2 $
+ * $Date: 1997/11/16 14:48:17 $
+ *
+ * This file contains the low-level floppy disk interface code
+ * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ * Linux.
+ */
+
+#include <linux/config.h> /* for CONFIG_FT_* */
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/fdc-isr.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/fc-10.h"
+
+/* Global vars.
+ */
+static int ftape_motor;
+volatile int ftape_current_cylinder = -1;
+volatile fdc_mode_enum fdc_mode = fdc_idle;
+fdc_config_info fdc;
+DECLARE_WAIT_QUEUE_HEAD(ftape_wait_intr);
+
+unsigned int ft_fdc_base = CONFIG_FT_FDC_BASE;
+unsigned int ft_fdc_irq = CONFIG_FT_FDC_IRQ;
+unsigned int ft_fdc_dma = CONFIG_FT_FDC_DMA;
+unsigned int ft_fdc_threshold = CONFIG_FT_FDC_THR; /* bytes */
+unsigned int ft_fdc_rate_limit = CONFIG_FT_FDC_MAX_RATE; /* bits/sec */
+int ft_probe_fc10 = CONFIG_FT_PROBE_FC10;
+int ft_mach2 = CONFIG_FT_MACH2;
+
+/* Local vars.
+ */
+static spinlock_t fdc_io_lock;
+static unsigned int fdc_calibr_count;
+static unsigned int fdc_calibr_time;
+static int fdc_status;
+volatile __u8 fdc_head; /* FDC head from sector id */
+volatile __u8 fdc_cyl; /* FDC track from sector id */
+volatile __u8 fdc_sect; /* FDC sector from sector id */
+static int fdc_data_rate = 500; /* data rate (Kbps) */
+static int fdc_rate_code; /* data rate code (0 == 500 Kbps) */
+static int fdc_seek_rate = 2; /* step rate (msec) */
+static void (*do_ftape) (void);
+static int fdc_fifo_state; /* original fifo setting - fifo enabled */
+static int fdc_fifo_thr; /* original fifo setting - threshold */
+static int fdc_lock_state; /* original lock setting - locked */
+static int fdc_fifo_locked; /* has fifo && lock set ? */
+static __u8 fdc_precomp; /* default precomp. value (nsec) */
+static __u8 fdc_prec_code; /* fdc precomp. select code */
+
+static char ftape_id[] = "ftape"; /* used by request irq and free irq */
+
+static int fdc_set_seek_rate(int seek_rate);
+
+void fdc_catch_stray_interrupts(int count)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ if (count == 0) {
+ ft_expected_stray_interrupts = 0;
+ } else {
+ ft_expected_stray_interrupts += count;
+ }
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+}
+
+/* Wait during a timeout period for a given FDC status.
+ * If usecs == 0 then just test status, else wait at least for usecs.
+ * Returns -ETIME on timeout. Function must be calibrated first !
+ */
+static int fdc_wait(unsigned int usecs, __u8 mask, __u8 state)
+{
+ int count_1 = (fdc_calibr_count * usecs +
+ fdc_calibr_count - 1) / fdc_calibr_time;
+
+ do {
+ fdc_status = inb_p(fdc.msr);
+ if ((fdc_status & mask) == state) {
+ return 0;
+ }
+ } while (count_1-- >= 0);
+ return -ETIME;
+}
+
+int fdc_ready_wait(unsigned int usecs)
+{
+ return fdc_wait(usecs, FDC_DATA_READY | FDC_BUSY, FDC_DATA_READY);
+}
+
+/* Why can't we just use udelay()?
+ */
+static void fdc_usec_wait(unsigned int usecs)
+{
+ fdc_wait(usecs, 0, 1); /* will always timeout ! */
+}
+
+static int fdc_ready_out_wait(unsigned int usecs)
+{
+ fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
+ return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_OUT_READY);
+}
+
+void fdc_wait_calibrate(void)
+{
+ ftape_calibrate("fdc_wait",
+ fdc_usec_wait, &fdc_calibr_count, &fdc_calibr_time);
+}
+
+/* Wait for a (short) while for the FDC to become ready
+ * and transfer the next command byte.
+ * Return -ETIME on timeout on getting ready (depends on hardware!).
+ */
+static int fdc_write(const __u8 data)
+{
+ fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
+ if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_IN_READY) < 0) {
+ return -ETIME;
+ } else {
+ outb(data, fdc.fifo);
+ return 0;
+ }
+}
+
+/* Wait for a (short) while for the FDC to become ready
+ * and transfer the next result byte.
+ * Return -ETIME if timeout on getting ready (depends on hardware!).
+ */
+static int fdc_read(__u8 * data)
+{
+ fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
+ if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_OUT_READY) < 0) {
+ return -ETIME;
+ } else {
+ *data = inb(fdc.fifo);
+ return 0;
+ }
+}
+
+/* Output a cmd_len long command string to the FDC.
+ * The FDC should be ready to receive a new command or
+ * an error (EBUSY or ETIME) will occur.
+ */
+int fdc_command(const __u8 * cmd_data, int cmd_len)
+{
+ int result = 0;
+ unsigned long flags;
+ int count = cmd_len;
+ int retry = 0;
+#ifdef TESTING
+ static unsigned int last_time;
+ unsigned int time;
+#endif
+ TRACE_FUN(ft_t_any);
+
+ fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ if (!in_interrupt())
+ /* Yes, I know, too much comments inside this function
+ * ...
+ *
+ * Yet another bug in the original driver. All that
+ * havoc is caused by the fact that the isr() sends
+ * itself a command to the floppy tape driver (pause,
+ * micro step pause). Now, the problem is that
+ * commands are transmitted via the fdc_seek
+ * command. But: the fdc performs seeks in the
+ * background i.e. it doesn't signal busy while
+ * sending the step pulses to the drive. Therefore the
+ * non-interrupt level driver has no chance to tell
+ * whether the isr() just has issued a seek. Therefore
+ * we HAVE TO have a look at the ft_hide_interrupt
+ * flag: it signals the non-interrupt level part of
+ * the driver that it has to wait for the fdc until it
+ * has completet seeking.
+ *
+ * THIS WAS PRESUMABLY THE REASON FOR ALL THAT
+ * "fdc_read timeout" errors, I HOPE :-)
+ */
+ if (ft_hide_interrupt) {
+ restore_flags(flags);
+ TRACE(ft_t_info,
+ "Waiting for the isr() completing fdc_seek()");
+ if (fdc_interrupt_wait(2 * FT_SECOND) < 0) {
+ TRACE(ft_t_warn,
+ "Warning: timeout waiting for isr() seek to complete");
+ }
+ if (ft_hide_interrupt || !ft_seek_completed) {
+ /* There cannot be another
+ * interrupt. The isr() only stops
+ * the tape and the next interrupt
+ * won't come until we have send our
+ * command to the drive.
+ */
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "BUG? isr() is still seeking?\n"
+ KERN_INFO "hide: %d\n"
+ KERN_INFO "seek: %d",
+ ft_hide_interrupt,
+ ft_seek_completed);
+
+ }
+ fdc_usec_wait(FT_RQM_DELAY); /* wait for valid RQM status */
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ }
+ fdc_status = inb(fdc.msr);
+ if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_IN_READY) {
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ TRACE_ABORT(-EBUSY, ft_t_err, "fdc not ready");
+ }
+ fdc_mode = *cmd_data; /* used by isr */
+#ifdef TESTING
+ if (fdc_mode == FDC_SEEK) {
+ time = ftape_timediff(last_time, ftape_timestamp());
+ if (time < 6000) {
+ TRACE(ft_t_bug,"Warning: short timeout between seek commands: %d",
+ time);
+ }
+ }
+#endif
+ if (!in_interrupt()) {
+ /* shouldn't be cleared if called from isr
+ */
+ ft_interrupt_seen = 0;
+ }
+ while (count) {
+ result = fdc_write(*cmd_data);
+ if (result < 0) {
+ TRACE(ft_t_fdc_dma,
+ "fdc_mode = %02x, status = %02x at index %d",
+ (int) fdc_mode, (int) fdc_status,
+ cmd_len - count);
+ if (++retry <= 3) {
+ TRACE(ft_t_warn, "fdc_write timeout, retry");
+ } else {
+ TRACE(ft_t_err, "fdc_write timeout, fatal");
+ /* recover ??? */
+ break;
+ }
+ } else {
+ --count;
+ ++cmd_data;
+ }
+ }
+#ifdef TESTING
+ if (fdc_mode == FDC_SEEK) {
+ last_time = ftape_timestamp();
+ }
+#endif
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ TRACE_EXIT result;
+}
+
+/* Input a res_len long result string from the FDC.
+ * The FDC should be ready to send the result or an error
+ * (EBUSY or ETIME) will occur.
+ */
+int fdc_result(__u8 * res_data, int res_len)
+{
+ int result = 0;
+ unsigned long flags;
+ int count = res_len;
+ int retry = 0;
+ TRACE_FUN(ft_t_any);
+
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ fdc_status = inb(fdc.msr);
+ if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_OUT_READY) {
+ TRACE(ft_t_err, "fdc not ready");
+ result = -EBUSY;
+ } else while (count) {
+ if (!(fdc_status & FDC_BUSY)) {
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ TRACE_ABORT(-EIO, ft_t_err, "premature end of result phase");
+ }
+ result = fdc_read(res_data);
+ if (result < 0) {
+ TRACE(ft_t_fdc_dma,
+ "fdc_mode = %02x, status = %02x at index %d",
+ (int) fdc_mode,
+ (int) fdc_status,
+ res_len - count);
+ if (++retry <= 3) {
+ TRACE(ft_t_warn, "fdc_read timeout, retry");
+ } else {
+ TRACE(ft_t_err, "fdc_read timeout, fatal");
+ /* recover ??? */
+ break;
+ ++retry;
+ }
+ } else {
+ --count;
+ ++res_data;
+ }
+ }
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ fdc_usec_wait(FT_RQM_DELAY); /* allow FDC to negate BSY */
+ TRACE_EXIT result;
+}
+
+/* Handle command and result phases for
+ * commands without data phase.
+ */
+static int fdc_issue_command(const __u8 * out_data, int out_count,
+ __u8 * in_data, int in_count)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (out_count > 0) {
+ TRACE_CATCH(fdc_command(out_data, out_count),);
+ }
+ /* will take 24 - 30 usec for fdc_sense_drive_status and
+ * fdc_sense_interrupt_status commands.
+ * 35 fails sometimes (5/9/93 SJL)
+ * On a loaded system it incidentally takes longer than
+ * this for the fdc to get ready ! ?????? WHY ??????
+ * So until we know what's going on use a very long timeout.
+ */
+ TRACE_CATCH(fdc_ready_out_wait(500 /* usec */),);
+ if (in_count > 0) {
+ TRACE_CATCH(fdc_result(in_data, in_count),
+ TRACE(ft_t_err, "result phase aborted"));
+ }
+ TRACE_EXIT 0;
+}
+
+/* Wait for FDC interrupt with timeout (in milliseconds).
+ * Signals are blocked so the wait will not be aborted.
+ * Note: interrupts must be enabled ! (23/05/93 SJL)
+ */
+int fdc_interrupt_wait(unsigned int time)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ sigset_t old_sigmask;
+ static int resetting;
+ long timeout;
+
+ TRACE_FUN(ft_t_fdc_dma);
+
+ if (waitqueue_active(&ftape_wait_intr)) {
+ TRACE_ABORT(-EIO, ft_t_err, "error: nested call");
+ }
+ /* timeout time will be up to USPT microseconds too long ! */
+ timeout = (1000 * time + FT_USPT - 1) / FT_USPT;
+
+ spin_lock_irq(&current->sighand->siglock);
+ old_sigmask = current->blocked;
+ sigfillset(&current->blocked);
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&ftape_wait_intr, &wait);
+ while (!ft_interrupt_seen && timeout) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ timeout = schedule_timeout(timeout);
+ }
+
+ spin_lock_irq(&current->sighand->siglock);
+ current->blocked = old_sigmask;
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ remove_wait_queue(&ftape_wait_intr, &wait);
+ /* the following IS necessary. True: as well
+ * wake_up_interruptible() as the schedule() set TASK_RUNNING
+ * when they wakeup a task, BUT: it may very well be that
+ * ft_interrupt_seen is already set to 1 when we enter here
+ * in which case schedule() gets never called, and
+ * TASK_RUNNING never set. This has the funny effect that we
+ * execute all the code until we leave kernel space, but then
+ * the task is stopped (a task CANNOT be preempted while in
+ * kernel mode. Sending a pair of SIGSTOP/SIGCONT to the
+ * tasks wakes it up again. Funny! :-)
+ */
+ current->state = TASK_RUNNING;
+ if (ft_interrupt_seen) { /* woken up by interrupt */
+ ft_interrupt_seen = 0;
+ TRACE_EXIT 0;
+ }
+ /* Original comment:
+ * In first instance, next statement seems unnecessary since
+ * it will be cleared in fdc_command. However, a small part of
+ * the software seems to rely on this being cleared here
+ * (ftape_close might fail) so stick to it until things get fixed !
+ */
+ /* My deeply sought of knowledge:
+ * Behold NO! It is obvious. fdc_reset() doesn't call fdc_command()
+ * but nevertheless uses fdc_interrupt_wait(). OF COURSE this needs to
+ * be reset here.
+ */
+ ft_interrupt_seen = 0; /* clear for next call */
+ if (!resetting) {
+ resetting = 1; /* break infinite recursion if reset fails */
+ TRACE(ft_t_any, "cleanup reset");
+ fdc_reset();
+ resetting = 0;
+ }
+ TRACE_EXIT (signal_pending(current)) ? -EINTR : -ETIME;
+}
+
+/* Start/stop drive motor. Enable DMA mode.
+ */
+void fdc_motor(int motor)
+{
+ int unit = ft_drive_sel;
+ int data = unit | FDC_RESET_NOT | FDC_DMA_MODE;
+ TRACE_FUN(ft_t_any);
+
+ ftape_motor = motor;
+ if (ftape_motor) {
+ data |= FDC_MOTOR_0 << unit;
+ TRACE(ft_t_noise, "turning motor %d on", unit);
+ } else {
+ TRACE(ft_t_noise, "turning motor %d off", unit);
+ }
+ if (ft_mach2) {
+ outb_p(data, fdc.dor2);
+ } else {
+ outb_p(data, fdc.dor);
+ }
+ ftape_sleep(10 * FT_MILLISECOND);
+ TRACE_EXIT;
+}
+
+static void fdc_update_dsr(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_flow, "rate = %d Kbps, precomp = %d ns",
+ fdc_data_rate, fdc_precomp);
+ if (fdc.type >= i82077) {
+ outb_p((fdc_rate_code & 0x03) | fdc_prec_code, fdc.dsr);
+ } else {
+ outb_p(fdc_rate_code & 0x03, fdc.ccr);
+ }
+ TRACE_EXIT;
+}
+
+void fdc_set_write_precomp(int precomp)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_noise, "New precomp: %d nsec", precomp);
+ fdc_precomp = precomp;
+ /* write precompensation can be set in multiples of 41.67 nsec.
+ * round the parameter to the nearest multiple and convert it
+ * into a fdc setting. Note that 0 means default to the fdc,
+ * 7 is used instead of that.
+ */
+ fdc_prec_code = ((fdc_precomp + 21) / 42) << 2;
+ if (fdc_prec_code == 0 || fdc_prec_code > (6 << 2)) {
+ fdc_prec_code = 7 << 2;
+ }
+ fdc_update_dsr();
+ TRACE_EXIT;
+}
+
+/* Reprogram the 82078 registers to use Data Rate Table 1 on all drives.
+ */
+static void fdc_set_drive_specs(void)
+{
+ __u8 cmd[] = { FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0};
+ int result;
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_flow, "Setting of drive specs called");
+ if (fdc.type >= i82078_1) {
+ cmd[1] = (0 << 5) | (2 << 2);
+ cmd[2] = (1 << 5) | (2 << 2);
+ cmd[3] = (2 << 5) | (2 << 2);
+ cmd[4] = (3 << 5) | (2 << 2);
+ result = fdc_command(cmd, NR_ITEMS(cmd));
+ if (result < 0) {
+ TRACE(ft_t_err, "Setting of drive specs failed");
+ }
+ }
+ TRACE_EXIT;
+}
+
+/* Select clock for fdc, must correspond with tape drive setting !
+ * This also influences the fdc timing so we must adjust some values.
+ */
+int fdc_set_data_rate(int rate)
+{
+ int bad_rate = 0;
+ TRACE_FUN(ft_t_any);
+
+ /* Select clock for fdc, must correspond with tape drive setting !
+ * This also influences the fdc timing so we must adjust some values.
+ */
+ TRACE(ft_t_fdc_dma, "new rate = %d", rate);
+ switch (rate) {
+ case 250:
+ fdc_rate_code = fdc_data_rate_250;
+ break;
+ case 500:
+ fdc_rate_code = fdc_data_rate_500;
+ break;
+ case 1000:
+ if (fdc.type < i82077) {
+ bad_rate = 1;
+ } else {
+ fdc_rate_code = fdc_data_rate_1000;
+ }
+ break;
+ case 2000:
+ if (fdc.type < i82078_1) {
+ bad_rate = 1;
+ } else {
+ fdc_rate_code = fdc_data_rate_2000;
+ }
+ break;
+ default:
+ bad_rate = 1;
+ }
+ if (bad_rate) {
+ TRACE_ABORT(-EIO,
+ ft_t_fdc_dma, "%d is not a valid data rate", rate);
+ }
+ fdc_data_rate = rate;
+ fdc_update_dsr();
+ fdc_set_seek_rate(fdc_seek_rate); /* clock changed! */
+ ftape_udelay(1000);
+ TRACE_EXIT 0;
+}
+
+/* keep the unit select if keep_select is != 0,
+ */
+static void fdc_dor_reset(int keep_select)
+{
+ __u8 fdc_ctl = ft_drive_sel;
+
+ if (keep_select != 0) {
+ fdc_ctl |= FDC_DMA_MODE;
+ if (ftape_motor) {
+ fdc_ctl |= FDC_MOTOR_0 << ft_drive_sel;
+ }
+ }
+ ftape_udelay(10); /* ??? but seems to be necessary */
+ if (ft_mach2) {
+ outb_p(fdc_ctl & 0x0f, fdc.dor);
+ outb_p(fdc_ctl, fdc.dor2);
+ } else {
+ outb_p(fdc_ctl, fdc.dor);
+ }
+ fdc_usec_wait(10); /* delay >= 14 fdc clocks */
+ if (keep_select == 0) {
+ fdc_ctl = 0;
+ }
+ fdc_ctl |= FDC_RESET_NOT;
+ if (ft_mach2) {
+ outb_p(fdc_ctl & 0x0f, fdc.dor);
+ outb_p(fdc_ctl, fdc.dor2);
+ } else {
+ outb_p(fdc_ctl, fdc.dor);
+ }
+}
+
+/* Reset the floppy disk controller. Leave the ftape_unit selected.
+ */
+void fdc_reset(void)
+{
+ int st0;
+ int i;
+ int dummy;
+ unsigned long flags;
+ TRACE_FUN(ft_t_any);
+
+ spin_lock_irqsave(&fdc_io_lock, flags);
+
+ fdc_dor_reset(1); /* keep unit selected */
+
+ fdc_mode = fdc_idle;
+
+ /* maybe the cli()/sti() pair is not necessary, BUT:
+ * the following line MUST be here. Otherwise fdc_interrupt_wait()
+ * won't wait. Note that fdc_reset() is called from
+ * ftape_dumb_stop() when the fdc is busy transferring data. In this
+ * case fdc_isr() MOST PROBABLY sets ft_interrupt_seen, and tries
+ * to get the result bytes from the fdc etc. CLASH.
+ */
+ ft_interrupt_seen = 0;
+
+ /* Program data rate
+ */
+ fdc_update_dsr(); /* restore data rate and precomp */
+
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+
+ /*
+ * Wait for first polling cycle to complete
+ */
+ if (fdc_interrupt_wait(1 * FT_SECOND) < 0) {
+ TRACE(ft_t_err, "no drive polling interrupt!");
+ } else { /* clear all disk-changed statuses */
+ for (i = 0; i < 4; ++i) {
+ if(fdc_sense_interrupt_status(&st0, &dummy) != 0) {
+ TRACE(ft_t_err, "sense failed for %d", i);
+ }
+ if (i == ft_drive_sel) {
+ ftape_current_cylinder = dummy;
+ }
+ }
+ TRACE(ft_t_noise, "drive polling completed");
+ }
+ /*
+ * SPECIFY COMMAND
+ */
+ fdc_set_seek_rate(fdc_seek_rate);
+ /*
+ * DRIVE SPECIFICATION COMMAND (if fdc type known)
+ */
+ if (fdc.type >= i82078_1) {
+ fdc_set_drive_specs();
+ }
+ TRACE_EXIT;
+}
+
+#if !defined(CLK_48MHZ)
+# define CLK_48MHZ 1
+#endif
+
+/* When we're done, put the fdc into reset mode so that the regular
+ * floppy disk driver will figure out that something is wrong and
+ * initialize the controller the way it wants.
+ */
+void fdc_disable(void)
+{
+ __u8 cmd1[] = {FDC_CONFIGURE, 0x00, 0x00, 0x00};
+ __u8 cmd2[] = {FDC_LOCK};
+ __u8 cmd3[] = {FDC_UNLOCK};
+ __u8 stat[1];
+ TRACE_FUN(ft_t_flow);
+
+ if (!fdc_fifo_locked) {
+ fdc_reset();
+ TRACE_EXIT;
+ }
+ if (fdc_issue_command(cmd3, 1, stat, 1) < 0 || stat[0] != 0x00) {
+ fdc_dor_reset(0);
+ TRACE_ABORT(/**/, ft_t_bug,
+ "couldn't unlock fifo, configuration remains changed");
+ }
+ fdc_fifo_locked = 0;
+ if (CLK_48MHZ && fdc.type >= i82078) {
+ cmd1[0] |= FDC_CLK48_BIT;
+ }
+ cmd1[2] = ((fdc_fifo_state) ? 0 : 0x20) + (fdc_fifo_thr - 1);
+ if (fdc_command(cmd1, NR_ITEMS(cmd1)) < 0) {
+ fdc_dor_reset(0);
+ TRACE_ABORT(/**/, ft_t_bug,
+ "couldn't reconfigure fifo to old state");
+ }
+ if (fdc_lock_state &&
+ fdc_issue_command(cmd2, 1, stat, 1) < 0) {
+ fdc_dor_reset(0);
+ TRACE_ABORT(/**/, ft_t_bug, "couldn't lock old state again");
+ }
+ TRACE(ft_t_noise, "fifo restored: %sabled, thr. %d, %slocked",
+ fdc_fifo_state ? "en" : "dis",
+ fdc_fifo_thr, (fdc_lock_state) ? "" : "not ");
+ fdc_dor_reset(0);
+ TRACE_EXIT;
+}
+
+/* Specify FDC seek-rate (milliseconds)
+ */
+static int fdc_set_seek_rate(int seek_rate)
+{
+ /* set step rate, dma mode, and minimal head load and unload times
+ */
+ __u8 in[3] = { FDC_SPECIFY, 1, (1 << 1)};
+
+ fdc_seek_rate = seek_rate;
+ in[1] |= (16 - (fdc_data_rate * fdc_seek_rate) / 500) << 4;
+
+ return fdc_command(in, 3);
+}
+
+/* Sense drive status: get unit's drive status (ST3)
+ */
+int fdc_sense_drive_status(int *st3)
+{
+ __u8 out[2];
+ __u8 in[1];
+ TRACE_FUN(ft_t_any);
+
+ out[0] = FDC_SENSED;
+ out[1] = ft_drive_sel;
+ TRACE_CATCH(fdc_issue_command(out, 2, in, 1),);
+ *st3 = in[0];
+ TRACE_EXIT 0;
+}
+
+/* Sense Interrupt Status command:
+ * should be issued at the end of each seek.
+ * get ST0 and current cylinder.
+ */
+int fdc_sense_interrupt_status(int *st0, int *current_cylinder)
+{
+ __u8 out[1];
+ __u8 in[2];
+ TRACE_FUN(ft_t_any);
+
+ out[0] = FDC_SENSEI;
+ TRACE_CATCH(fdc_issue_command(out, 1, in, 2),);
+ *st0 = in[0];
+ *current_cylinder = in[1];
+ TRACE_EXIT 0;
+}
+
+/* step to track
+ */
+int fdc_seek(int track)
+{
+ __u8 out[3];
+ int st0, pcn;
+#ifdef TESTING
+ unsigned int time;
+#endif
+ TRACE_FUN(ft_t_any);
+
+ out[0] = FDC_SEEK;
+ out[1] = ft_drive_sel;
+ out[2] = track;
+#ifdef TESTING
+ time = ftape_timestamp();
+#endif
+ /* We really need this command to work !
+ */
+ ft_seek_completed = 0;
+ TRACE_CATCH(fdc_command(out, 3),
+ fdc_reset();
+ TRACE(ft_t_noise, "destination was: %d, resetting FDC...",
+ track));
+ /* Handle interrupts until ft_seek_completed or timeout.
+ */
+ for (;;) {
+ TRACE_CATCH(fdc_interrupt_wait(2 * FT_SECOND),);
+ if (ft_seek_completed) {
+ TRACE_CATCH(fdc_sense_interrupt_status(&st0, &pcn),);
+ if ((st0 & ST0_SEEK_END) == 0) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "no seek-end after seek completion !??");
+ }
+ break;
+ }
+ }
+#ifdef TESTING
+ time = ftape_timediff(time, ftape_timestamp()) / abs(track - ftape_current_cylinder);
+ if ((time < 900 || time > 3100) && abs(track - ftape_current_cylinder) > 5) {
+ TRACE(ft_t_warn, "Wrong FDC STEP interval: %d usecs (%d)",
+ time, track - ftape_current_cylinder);
+ }
+#endif
+ /* Verify whether we issued the right tape command.
+ */
+ /* Verify that we seek to the proper track. */
+ if (pcn != track) {
+ TRACE_ABORT(-EIO, ft_t_err, "bad seek..");
+ }
+ ftape_current_cylinder = track;
+ TRACE_EXIT 0;
+}
+
+static int perpend_mode; /* set if fdc is in perpendicular mode */
+
+static int perpend_off(void)
+{
+ __u8 perpend[] = {FDC_PERPEND, 0x00};
+ TRACE_FUN(ft_t_any);
+
+ if (perpend_mode) {
+ /* Turn off perpendicular mode */
+ perpend[1] = 0x80;
+ TRACE_CATCH(fdc_command(perpend, 2),
+ TRACE(ft_t_err,"Perpendicular mode exit failed!"));
+ perpend_mode = 0;
+ }
+ TRACE_EXIT 0;
+}
+
+static int handle_perpend(int segment_id)
+{
+ __u8 perpend[] = {FDC_PERPEND, 0x00};
+ TRACE_FUN(ft_t_any);
+
+ /* When writing QIC-3020 tapes, turn on perpendicular mode
+ * if tape is moving in forward direction (even tracks).
+ */
+ if (ft_qic_std == QIC_TAPE_QIC3020 &&
+ ((segment_id / ft_segments_per_track) & 1) == 0) {
+/* FIXME: some i82077 seem to support perpendicular mode as
+ * well.
+ */
+#if 0
+ if (fdc.type < i82077AA) {}
+#else
+ if (fdc.type < i82077 && ft_data_rate < 1000) {
+#endif
+ /* fdc does not support perpendicular mode: complain
+ */
+ TRACE_ABORT(-EIO, ft_t_err,
+ "Your FDC does not support QIC-3020.");
+ }
+ perpend[1] = 0x03 /* 0x83 + (0x4 << ft_drive_sel) */ ;
+ TRACE_CATCH(fdc_command(perpend, 2),
+ TRACE(ft_t_err,"Perpendicular mode entry failed!"));
+ TRACE(ft_t_flow, "Perpendicular mode set");
+ perpend_mode = 1;
+ TRACE_EXIT 0;
+ }
+ TRACE_EXIT perpend_off();
+}
+
+static inline void fdc_setup_dma(char mode,
+ volatile void *addr, unsigned int count)
+{
+ /* Program the DMA controller.
+ */
+ disable_dma(fdc.dma);
+ clear_dma_ff(fdc.dma);
+ set_dma_mode(fdc.dma, mode);
+ set_dma_addr(fdc.dma, virt_to_bus((void*)addr));
+ set_dma_count(fdc.dma, count);
+ enable_dma(fdc.dma);
+}
+
+/* Setup fdc and dma for formatting the next segment
+ */
+int fdc_setup_formatting(buffer_struct * buff)
+{
+ unsigned long flags;
+ __u8 out[6] = {
+ FDC_FORMAT, 0x00, 3, 4 * FT_SECTORS_PER_SEGMENT, 0x00, 0x6b
+ };
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(handle_perpend(buff->segment_id),);
+ /* Program the DMA controller.
+ */
+ TRACE(ft_t_fdc_dma,
+ "phys. addr. = %lx", virt_to_bus((void*) buff->ptr));
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ fdc_setup_dma(DMA_MODE_WRITE, buff->ptr, FT_SECTORS_PER_SEGMENT * 4);
+ /* Issue FDC command to start reading/writing.
+ */
+ out[1] = ft_drive_sel;
+ out[4] = buff->gap3;
+ TRACE_CATCH(fdc_setup_error = fdc_command(out, sizeof(out)),
+ restore_flags(flags); fdc_mode = fdc_idle);
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ TRACE_EXIT 0;
+}
+
+
+/* Setup Floppy Disk Controller and DMA to read or write the next cluster
+ * of good sectors from or to the current segment.
+ */
+int fdc_setup_read_write(buffer_struct * buff, __u8 operation)
+{
+ unsigned long flags;
+ __u8 out[9];
+ int dma_mode;
+ TRACE_FUN(ft_t_any);
+
+ switch(operation) {
+ case FDC_VERIFY:
+ if (fdc.type < i82077) {
+ operation = FDC_READ;
+ }
+ case FDC_READ:
+ case FDC_READ_DELETED:
+ dma_mode = DMA_MODE_READ;
+ TRACE(ft_t_fdc_dma, "xfer %d sectors to 0x%p",
+ buff->sector_count, buff->ptr);
+ TRACE_CATCH(perpend_off(),);
+ break;
+ case FDC_WRITE_DELETED:
+ TRACE(ft_t_noise, "deleting segment %d", buff->segment_id);
+ case FDC_WRITE:
+ dma_mode = DMA_MODE_WRITE;
+ /* When writing QIC-3020 tapes, turn on perpendicular mode
+ * if tape is moving in forward direction (even tracks).
+ */
+ TRACE_CATCH(handle_perpend(buff->segment_id),);
+ TRACE(ft_t_fdc_dma, "xfer %d sectors from 0x%p",
+ buff->sector_count, buff->ptr);
+ break;
+ default:
+ TRACE_ABORT(-EIO,
+ ft_t_bug, "bug: invalid operation parameter");
+ }
+ TRACE(ft_t_fdc_dma, "phys. addr. = %lx",virt_to_bus((void*)buff->ptr));
+ spin_lock_irqsave(&fdc_io_lock, flags);
+ if (operation != FDC_VERIFY) {
+ fdc_setup_dma(dma_mode, buff->ptr,
+ FT_SECTOR_SIZE * buff->sector_count);
+ }
+ /* Issue FDC command to start reading/writing.
+ */
+ out[0] = operation;
+ out[1] = ft_drive_sel;
+ out[2] = buff->cyl;
+ out[3] = buff->head;
+ out[4] = buff->sect + buff->sector_offset;
+ out[5] = 3; /* Sector size of 1K. */
+ out[6] = out[4] + buff->sector_count - 1; /* last sector */
+ out[7] = 109; /* Gap length. */
+ out[8] = 0xff; /* No limit to transfer size. */
+ TRACE(ft_t_fdc_dma, "C: 0x%02x, H: 0x%02x, R: 0x%02x, cnt: 0x%02x",
+ out[2], out[3], out[4], out[6] - out[4] + 1);
+ spin_unlock_irqrestore(&fdc_io_lock, flags);
+ TRACE_CATCH(fdc_setup_error = fdc_command(out, 9),fdc_mode = fdc_idle);
+ TRACE_EXIT 0;
+}
+
+int fdc_fifo_threshold(__u8 threshold,
+ int *fifo_state, int *lock_state, int *fifo_thr)
+{
+ const __u8 cmd0[] = {FDC_DUMPREGS};
+ __u8 cmd1[] = {FDC_CONFIGURE, 0, (0x0f & (threshold - 1)), 0};
+ const __u8 cmd2[] = {FDC_LOCK};
+ const __u8 cmd3[] = {FDC_UNLOCK};
+ __u8 reg[10];
+ __u8 stat;
+ int i;
+ int result;
+ TRACE_FUN(ft_t_any);
+
+ if (CLK_48MHZ && fdc.type >= i82078) {
+ cmd1[0] |= FDC_CLK48_BIT;
+ }
+ /* Dump fdc internal registers for examination
+ */
+ TRACE_CATCH(fdc_command(cmd0, NR_ITEMS(cmd0)),
+ TRACE(ft_t_warn, "dumpreg cmd failed, fifo unchanged"));
+ /* Now read fdc internal registers from fifo
+ */
+ for (i = 0; i < (int)NR_ITEMS(reg); ++i) {
+ fdc_read(&reg[i]);
+ TRACE(ft_t_fdc_dma, "Register %d = 0x%02x", i, reg[i]);
+ }
+ if (fifo_state && lock_state && fifo_thr) {
+ *fifo_state = (reg[8] & 0x20) == 0;
+ *lock_state = reg[7] & 0x80;
+ *fifo_thr = 1 + (reg[8] & 0x0f);
+ }
+ TRACE(ft_t_noise,
+ "original fifo state: %sabled, threshold %d, %slocked",
+ ((reg[8] & 0x20) == 0) ? "en" : "dis",
+ 1 + (reg[8] & 0x0f), (reg[7] & 0x80) ? "" : "not ");
+ /* If fdc is already locked, unlock it first ! */
+ if (reg[7] & 0x80) {
+ fdc_ready_wait(100);
+ TRACE_CATCH(fdc_issue_command(cmd3, NR_ITEMS(cmd3), &stat, 1),
+ TRACE(ft_t_bug, "FDC unlock command failed, "
+ "configuration unchanged"));
+ }
+ fdc_fifo_locked = 0;
+ /* Enable fifo and set threshold at xx bytes to allow a
+ * reasonably large latency and reduce number of dma bursts.
+ */
+ fdc_ready_wait(100);
+ if ((result = fdc_command(cmd1, NR_ITEMS(cmd1))) < 0) {
+ TRACE(ft_t_bug, "configure cmd failed, fifo unchanged");
+ }
+ /* Now lock configuration so reset will not change it
+ */
+ if(fdc_issue_command(cmd2, NR_ITEMS(cmd2), &stat, 1) < 0 ||
+ stat != 0x10) {
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "FDC lock command failed, stat = 0x%02x", stat);
+ }
+ fdc_fifo_locked = 1;
+ TRACE_EXIT result;
+}
+
+static int fdc_fifo_enable(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (fdc_fifo_locked) {
+ TRACE_ABORT(0, ft_t_warn, "Fifo not enabled because locked");
+ }
+ TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
+ &fdc_fifo_state,
+ &fdc_lock_state,
+ &fdc_fifo_thr),);
+ TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
+ NULL, NULL, NULL),);
+ TRACE_EXIT 0;
+}
+
+/* Determine fd controller type
+ */
+static __u8 fdc_save_state[2];
+
+static int fdc_probe(void)
+{
+ __u8 cmd[1];
+ __u8 stat[16]; /* must be able to hold dumpregs & save results */
+ int i;
+ TRACE_FUN(ft_t_any);
+
+ /* Try to find out what kind of fd controller we have to deal with
+ * Scheme borrowed from floppy driver:
+ * first try if FDC_DUMPREGS command works
+ * (this indicates that we have a 82072 or better)
+ * then try the FDC_VERSION command (82072 doesn't support this)
+ * then try the FDC_UNLOCK command (some older 82077's don't support this)
+ * then try the FDC_PARTID command (82078's support this)
+ */
+ cmd[0] = FDC_DUMPREGS;
+ if (fdc_issue_command(cmd, 1, stat, 1) != 0) {
+ TRACE_ABORT(no_fdc, ft_t_bug, "No FDC found");
+ }
+ if (stat[0] == 0x80) {
+ /* invalid command: must be pre 82072 */
+ TRACE_ABORT(i8272,
+ ft_t_warn, "Type 8272A/765A compatible FDC found");
+ }
+ fdc_result(&stat[1], 9);
+ fdc_save_state[0] = stat[7];
+ fdc_save_state[1] = stat[8];
+ cmd[0] = FDC_VERSION;
+ if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
+ TRACE_ABORT(i8272, ft_t_warn, "Type 82072 FDC found");
+ }
+ if (*stat != 0x90) {
+ TRACE_ABORT(i8272, ft_t_warn, "Unknown FDC found");
+ }
+ cmd[0] = FDC_UNLOCK;
+ if(fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] != 0x00) {
+ TRACE_ABORT(i8272, ft_t_warn,
+ "Type pre-1991 82077 FDC found, "
+ "treating it like a 82072");
+ }
+ if (fdc_save_state[0] & 0x80) { /* was locked */
+ cmd[0] = FDC_LOCK; /* restore lock */
+ (void)fdc_issue_command(cmd, 1, stat, 1);
+ TRACE(ft_t_warn, "FDC is already locked");
+ }
+ /* Test for a i82078 FDC */
+ cmd[0] = FDC_PARTID;
+ if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
+ /* invalid command: not a i82078xx type FDC */
+ for (i = 0; i < 4; ++i) {
+ outb_p(i, fdc.tdr);
+ if ((inb_p(fdc.tdr) & 0x03) != i) {
+ TRACE_ABORT(i82077,
+ ft_t_warn, "Type 82077 FDC found");
+ }
+ }
+ TRACE_ABORT(i82077AA, ft_t_warn, "Type 82077AA FDC found");
+ }
+ /* FDC_PARTID cmd succeeded */
+ switch (stat[0] >> 5) {
+ case 0x0:
+ /* i82078SL or i82078-1. The SL part cannot run at
+ * 2Mbps (the SL and -1 dies are identical; they are
+ * speed graded after production, according to Intel).
+ * Some SL's can be detected by doing a SAVE cmd and
+ * look at bit 7 of the first byte (the SEL3V# bit).
+ * If it is 0, the part runs off 3Volts, and hence it
+ * is a SL.
+ */
+ cmd[0] = FDC_SAVE;
+ if(fdc_issue_command(cmd, 1, stat, 16) < 0) {
+ TRACE(ft_t_err, "FDC_SAVE failed. Dunno why");
+ /* guess we better claim the fdc to be a i82078 */
+ TRACE_ABORT(i82078,
+ ft_t_warn,
+ "Type i82078 FDC (i suppose) found");
+ }
+ if ((stat[0] & FDC_SEL3V_BIT)) {
+ /* fdc running off 5Volts; Pray that it's a i82078-1
+ */
+ TRACE_ABORT(i82078_1, ft_t_warn,
+ "Type i82078-1 or 5Volt i82078SL FDC found");
+ }
+ TRACE_ABORT(i82078, ft_t_warn,
+ "Type 3Volt i82078SL FDC (1Mbps) found");
+ case 0x1:
+ case 0x2: /* S82078B */
+ /* The '78B isn't '78 compatible. Detect it as a '77AA */
+ TRACE_ABORT(i82077AA, ft_t_warn, "Type i82077AA FDC found");
+ case 0x3: /* NSC PC8744 core; used in several super-IO chips */
+ TRACE_ABORT(i82077AA,
+ ft_t_warn, "Type 82077AA compatible FDC found");
+ default:
+ TRACE(ft_t_warn, "A previously undetected FDC found");
+ TRACE_ABORT(i82077AA, ft_t_warn,
+ "Treating it as a 82077AA. Please report partid= %d",
+ stat[0]);
+ } /* switch(stat[ 0] >> 5) */
+ TRACE_EXIT no_fdc;
+}
+
+static int fdc_request_regions(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_mach2 || ft_probe_fc10) {
+ if (!request_region(fdc.sra, 8, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+ TRACE_EXIT -EBUSY;
+#else
+ TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
+#endif
+ }
+ } else {
+ if (!request_region(fdc.sra, 6, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+ TRACE_EXIT -EBUSY;
+#else
+ TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
+#endif
+ }
+ if (!request_region(fdc.sra + 7, 1, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+ release_region(fdc.sra, 6);
+ TRACE_EXIT -EBUSY;
+#else
+ TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra + 7);
+#endif
+ }
+ }
+ TRACE_EXIT 0;
+}
+
+void fdc_release_regions(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (fdc.sra != 0) {
+ if (fdc.dor2 != 0) {
+ release_region(fdc.sra, 8);
+ } else {
+ release_region(fdc.sra, 6);
+ release_region(fdc.dir, 1);
+ }
+ }
+ TRACE_EXIT;
+}
+
+static int fdc_config_regs(unsigned int fdc_base,
+ unsigned int fdc_irq,
+ unsigned int fdc_dma)
+{
+ TRACE_FUN(ft_t_flow);
+
+ fdc.irq = fdc_irq;
+ fdc.dma = fdc_dma;
+ fdc.sra = fdc_base;
+ fdc.srb = fdc_base + 1;
+ fdc.dor = fdc_base + 2;
+ fdc.tdr = fdc_base + 3;
+ fdc.msr = fdc.dsr = fdc_base + 4;
+ fdc.fifo = fdc_base + 5;
+ fdc.dir = fdc.ccr = fdc_base + 7;
+ fdc.dor2 = (ft_mach2 || ft_probe_fc10) ? fdc_base + 6 : 0;
+ TRACE_CATCH(fdc_request_regions(), fdc.sra = 0);
+ TRACE_EXIT 0;
+}
+
+static int fdc_config(void)
+{
+ static int already_done;
+ TRACE_FUN(ft_t_any);
+
+ if (already_done) {
+ TRACE_CATCH(fdc_request_regions(),);
+ *(fdc.hook) = fdc_isr; /* hook our handler in */
+ TRACE_EXIT 0;
+ }
+ if (ft_probe_fc10) {
+ int fc_type;
+
+ TRACE_CATCH(fdc_config_regs(ft_fdc_base,
+ ft_fdc_irq, ft_fdc_dma),);
+ fc_type = fc10_enable();
+ if (fc_type != 0) {
+ TRACE(ft_t_warn, "FC-%c0 controller found", '0' + fc_type);
+ fdc.type = fc10;
+ fdc.hook = &do_ftape;
+ *(fdc.hook) = fdc_isr; /* hook our handler in */
+ already_done = 1;
+ TRACE_EXIT 0;
+ } else {
+ TRACE(ft_t_warn, "FC-10/20 controller not found");
+ fdc_release_regions();
+ fdc.type = no_fdc;
+ ft_probe_fc10 = 0;
+ ft_fdc_base = 0x3f0;
+ ft_fdc_irq = 6;
+ ft_fdc_dma = 2;
+ }
+ }
+ TRACE(ft_t_warn, "fdc base: 0x%x, irq: %d, dma: %d",
+ ft_fdc_base, ft_fdc_irq, ft_fdc_dma);
+ TRACE_CATCH(fdc_config_regs(ft_fdc_base, ft_fdc_irq, ft_fdc_dma),);
+ fdc.hook = &do_ftape;
+ *(fdc.hook) = fdc_isr; /* hook our handler in */
+ already_done = 1;
+ TRACE_EXIT 0;
+}
+
+static irqreturn_t ftape_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ void (*handler) (void) = *fdc.hook;
+ int handled = 0;
+ TRACE_FUN(ft_t_any);
+
+ *fdc.hook = NULL;
+ if (handler) {
+ handled = 1;
+ handler();
+ } else {
+ TRACE(ft_t_bug, "Unexpected ftape interrupt");
+ }
+ TRACE_EXIT IRQ_RETVAL(handled);
+}
+
+static int fdc_grab_irq_and_dma(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (fdc.hook == &do_ftape) {
+ /* Get fast interrupt handler.
+ */
+ if (request_irq(fdc.irq, ftape_interrupt,
+ SA_INTERRUPT, "ft", ftape_id)) {
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "Unable to grab IRQ%d for ftape driver",
+ fdc.irq);
+ }
+ if (request_dma(fdc.dma, ftape_id)) {
+ free_irq(fdc.irq, ftape_id);
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "Unable to grab DMA%d for ftape driver",
+ fdc.dma);
+ }
+ }
+ if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
+ /* Using same dma channel or irq as standard fdc, need
+ * to disable the dma-gate on the std fdc. This
+ * couldn't be done in the floppy driver as some
+ * laptops are using the dma-gate to enter a low power
+ * or even suspended state :-(
+ */
+ outb_p(FDC_RESET_NOT, 0x3f2);
+ TRACE(ft_t_noise, "DMA-gate on standard fdc disabled");
+ }
+ TRACE_EXIT 0;
+}
+
+int fdc_release_irq_and_dma(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (fdc.hook == &do_ftape) {
+ disable_dma(fdc.dma); /* just in case... */
+ free_dma(fdc.dma);
+ free_irq(fdc.irq, ftape_id);
+ }
+ if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
+ /* Using same dma channel as standard fdc, need to
+ * disable the dma-gate on the std fdc. This couldn't
+ * be done in the floppy driver as some laptops are
+ * using the dma-gate to enter a low power or even
+ * suspended state :-(
+ */
+ outb_p(FDC_RESET_NOT | FDC_DMA_MODE, 0x3f2);
+ TRACE(ft_t_noise, "DMA-gate on standard fdc enabled again");
+ }
+ TRACE_EXIT 0;
+}
+
+int fdc_init(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ /* find a FDC to use */
+ TRACE_CATCH(fdc_config(),);
+ TRACE_CATCH(fdc_grab_irq_and_dma(), fdc_release_regions());
+ ftape_motor = 0;
+ fdc_catch_stray_interrupts(0); /* clear number of awainted
+ * stray interrupte
+ */
+ fdc_catch_stray_interrupts(1); /* one always comes (?) */
+ TRACE(ft_t_flow, "resetting fdc");
+ fdc_set_seek_rate(2); /* use nominal QIC step rate */
+ fdc_reset(); /* init fdc & clear track counters */
+ if (fdc.type == no_fdc) { /* no FC-10 or FC-20 found */
+ fdc.type = fdc_probe();
+ fdc_reset(); /* update with new knowledge */
+ }
+ if (fdc.type == no_fdc) {
+ fdc_release_irq_and_dma();
+ fdc_release_regions();
+ TRACE_EXIT -ENXIO;
+ }
+ if (fdc.type >= i82077) {
+ if (fdc_fifo_enable() < 0) {
+ TRACE(ft_t_warn, "couldn't enable fdc fifo !");
+ } else {
+ TRACE(ft_t_flow, "fdc fifo enabled and locked");
+ }
+ }
+ TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/fdc-io.h b/drivers/char/ftape/lowlevel/fdc-io.h
new file mode 100644
index 000000000000..7ec3c72178bb
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-io.h
@@ -0,0 +1,252 @@
+#ifndef _FDC_IO_H
+#define _FDC_IO_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.h,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:18:06 $
+ *
+ * This file contains the declarations for the low level
+ * functions that communicate with the floppy disk controller,
+ * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ * Linux.
+ */
+
+#include <linux/fdreg.h>
+
+#include "../lowlevel/ftape-bsm.h"
+
+#define FDC_SK_BIT (0x20)
+#define FDC_MT_BIT (0x80)
+
+#define FDC_READ (FD_READ & ~(FDC_SK_BIT | FDC_MT_BIT))
+#define FDC_WRITE (FD_WRITE & ~FDC_MT_BIT)
+#define FDC_READ_DELETED (0x4c)
+#define FDC_WRITE_DELETED (0x49)
+#define FDC_VERIFY (0x56)
+#define FDC_READID (0x4a)
+#define FDC_SENSED (0x04)
+#define FDC_SENSEI (FD_SENSEI)
+#define FDC_FORMAT (FD_FORMAT)
+#define FDC_RECAL (FD_RECALIBRATE)
+#define FDC_SEEK (FD_SEEK)
+#define FDC_SPECIFY (FD_SPECIFY)
+#define FDC_RECALIBR (FD_RECALIBRATE)
+#define FDC_VERSION (FD_VERSION)
+#define FDC_PERPEND (FD_PERPENDICULAR)
+#define FDC_DUMPREGS (FD_DUMPREGS)
+#define FDC_LOCK (FD_LOCK)
+#define FDC_UNLOCK (FD_UNLOCK)
+#define FDC_CONFIGURE (FD_CONFIGURE)
+#define FDC_DRIVE_SPEC (0x8e) /* i82078 has this (any others?) */
+#define FDC_PARTID (0x18) /* i82078 has this */
+#define FDC_SAVE (0x2e) /* i82078 has this (any others?) */
+#define FDC_RESTORE (0x4e) /* i82078 has this (any others?) */
+
+#define FDC_STATUS_MASK (STATUS_BUSY | STATUS_DMA | STATUS_DIR | STATUS_READY)
+#define FDC_DATA_READY (STATUS_READY)
+#define FDC_DATA_OUTPUT (STATUS_DIR)
+#define FDC_DATA_READY_MASK (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_OUT_READY (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_IN_READY (STATUS_READY)
+#define FDC_BUSY (STATUS_BUSY)
+#define FDC_CLK48_BIT (0x80)
+#define FDC_SEL3V_BIT (0x40)
+
+#define ST0_INT_MASK (ST0_INTR)
+#define FDC_INT_NORMAL (ST0_INTR & 0x00)
+#define FDC_INT_ABNORMAL (ST0_INTR & 0x40)
+#define FDC_INT_INVALID (ST0_INTR & 0x80)
+#define FDC_INT_READYCH (ST0_INTR & 0xC0)
+#define ST0_SEEK_END (ST0_SE)
+#define ST3_TRACK_0 (ST3_TZ)
+
+#define FDC_RESET_NOT (0x04)
+#define FDC_DMA_MODE (0x08)
+#define FDC_MOTOR_0 (0x10)
+#define FDC_MOTOR_1 (0x20)
+
+typedef struct {
+ void (**hook) (void); /* our wedge into the isr */
+ enum {
+ no_fdc, i8272, i82077, i82077AA, fc10,
+ i82078, i82078_1
+ } type; /* FDC type */
+ unsigned int irq; /* FDC irq nr */
+ unsigned int dma; /* FDC dma channel nr */
+ __u16 sra; /* Status register A (PS/2 only) */
+ __u16 srb; /* Status register B (PS/2 only) */
+ __u16 dor; /* Digital output register */
+ __u16 tdr; /* Tape Drive Register (82077SL-1 &
+ 82078 only) */
+ __u16 msr; /* Main Status Register */
+ __u16 dsr; /* Datarate Select Register (8207x only) */
+ __u16 fifo; /* Data register / Fifo on 8207x */
+ __u16 dir; /* Digital Input Register */
+ __u16 ccr; /* Configuration Control Register */
+ __u16 dor2; /* Alternate dor on MACH-2 controller,
+ also used with FC-10, meaning unknown */
+} fdc_config_info;
+
+typedef enum {
+ fdc_data_rate_250 = 2,
+ fdc_data_rate_300 = 1, /* any fdc in default configuration */
+ fdc_data_rate_500 = 0,
+ fdc_data_rate_1000 = 3,
+ fdc_data_rate_2000 = 1, /* i82078-1: when using Data Rate Table #2 */
+} fdc_data_rate_type;
+
+typedef enum {
+ fdc_idle = 0,
+ fdc_reading_data = FDC_READ,
+ fdc_seeking = FDC_SEEK,
+ fdc_writing_data = FDC_WRITE,
+ fdc_deleting = FDC_WRITE_DELETED,
+ fdc_reading_id = FDC_READID,
+ fdc_recalibrating = FDC_RECAL,
+ fdc_formatting = FDC_FORMAT,
+ fdc_verifying = FDC_VERIFY
+} fdc_mode_enum;
+
+typedef enum {
+ waiting = 0,
+ reading,
+ writing,
+ formatting,
+ verifying,
+ deleting,
+ done,
+ error,
+ mmapped,
+} buffer_state_enum;
+
+typedef struct {
+ __u8 *address;
+ volatile buffer_state_enum status;
+ volatile __u8 *ptr;
+ volatile unsigned int bytes;
+ volatile unsigned int segment_id;
+
+ /* bitmap for remainder of segment not yet handled.
+ * one bit set for each bad sector that must be skipped.
+ */
+ volatile SectorMap bad_sector_map;
+
+ /* bitmap with bad data blocks in data buffer.
+ * the errors in this map may be retried.
+ */
+ volatile SectorMap soft_error_map;
+
+ /* bitmap with bad data blocks in data buffer
+ * the errors in this map may not be retried.
+ */
+ volatile SectorMap hard_error_map;
+
+ /* retry counter for soft errors.
+ */
+ volatile int retry;
+
+ /* sectors to skip on retry ???
+ */
+ volatile unsigned int skip;
+
+ /* nr of data blocks in data buffer
+ */
+ volatile unsigned int data_offset;
+
+ /* offset in segment for first sector to be handled.
+ */
+ volatile unsigned int sector_offset;
+
+ /* size of cluster of good sectors to be handled.
+ */
+ volatile unsigned int sector_count;
+
+ /* size of remaining part of segment to be handled.
+ */
+ volatile unsigned int remaining;
+
+ /* points to next segment (contiguous) to be handled,
+ * or is zero if no read-ahead is allowed.
+ */
+ volatile unsigned int next_segment;
+
+ /* flag being set if deleted data was read.
+ */
+ volatile int deleted;
+
+ /* floppy coordinates of first sector in segment */
+ volatile __u8 head;
+ volatile __u8 cyl;
+ volatile __u8 sect;
+
+ /* gap to use when formatting */
+ __u8 gap3;
+ /* flag set when buffer is mmaped */
+ int mmapped;
+} buffer_struct;
+
+/*
+ * fdc-io.c defined public variables
+ */
+extern volatile fdc_mode_enum fdc_mode;
+extern int fdc_setup_error; /* outdated ??? */
+extern wait_queue_head_t ftape_wait_intr;
+extern volatile int ftape_current_cylinder; /* track nr FDC thinks we're on */
+extern volatile __u8 fdc_head; /* FDC head */
+extern volatile __u8 fdc_cyl; /* FDC track */
+extern volatile __u8 fdc_sect; /* FDC sector */
+extern fdc_config_info fdc; /* FDC hardware configuration */
+
+extern unsigned int ft_fdc_base;
+extern unsigned int ft_fdc_irq;
+extern unsigned int ft_fdc_dma;
+extern unsigned int ft_fdc_threshold;
+extern unsigned int ft_fdc_rate_limit;
+extern int ft_probe_fc10;
+extern int ft_mach2;
+/*
+ * fdc-io.c defined public functions
+ */
+extern void fdc_catch_stray_interrupts(int count);
+extern int fdc_ready_wait(unsigned int timeout);
+extern int fdc_command(const __u8 * cmd_data, int cmd_len);
+extern int fdc_result(__u8 * res_data, int res_len);
+extern int fdc_interrupt_wait(unsigned int time);
+extern int fdc_seek(int track);
+extern int fdc_sense_drive_status(int *st3);
+extern void fdc_motor(int motor);
+extern void fdc_reset(void);
+extern void fdc_disable(void);
+extern int fdc_fifo_threshold(__u8 threshold,
+ int *fifo_state, int *lock_state, int *fifo_thr);
+extern void fdc_wait_calibrate(void);
+extern int fdc_sense_interrupt_status(int *st0, int *current_cylinder);
+extern void fdc_save_drive_specs(void);
+extern void fdc_restore_drive_specs(void);
+extern int fdc_set_data_rate(int rate);
+extern void fdc_set_write_precomp(int precomp);
+extern int fdc_release_irq_and_dma(void);
+extern void fdc_release_regions(void);
+extern int fdc_init(void);
+extern int fdc_setup_read_write(buffer_struct * buff, __u8 operation);
+extern int fdc_setup_formatting(buffer_struct * buff);
+#endif
diff --git a/drivers/char/ftape/lowlevel/fdc-isr.c b/drivers/char/ftape/lowlevel/fdc-isr.c
new file mode 100644
index 000000000000..ad2bc733ae1b
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-isr.c
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.c,v $
+ * $Revision: 1.9 $
+ * $Date: 1997/10/17 23:01:53 $
+ *
+ * This file contains the interrupt service routine and
+ * associated code for the QIC-40/80/3010/3020 floppy-tape driver
+ * "ftape" for Linux.
+ */
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#define volatile /* */
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-isr.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/* Global vars.
+ */
+volatile int ft_expected_stray_interrupts;
+volatile int ft_interrupt_seen;
+volatile int ft_seek_completed;
+volatile int ft_hide_interrupt;
+/* Local vars.
+ */
+typedef enum {
+ no_error = 0, id_am_error = 0x01, id_crc_error = 0x02,
+ data_am_error = 0x04, data_crc_error = 0x08,
+ no_data_error = 0x10, overrun_error = 0x20,
+} error_cause;
+static int stop_read_ahead;
+
+
+static void print_error_cause(int cause)
+{
+ TRACE_FUN(ft_t_any);
+
+ switch (cause) {
+ case no_data_error:
+ TRACE(ft_t_noise, "no data error");
+ break;
+ case id_am_error:
+ TRACE(ft_t_noise, "id am error");
+ break;
+ case id_crc_error:
+ TRACE(ft_t_noise, "id crc error");
+ break;
+ case data_am_error:
+ TRACE(ft_t_noise, "data am error");
+ break;
+ case data_crc_error:
+ TRACE(ft_t_noise, "data crc error");
+ break;
+ case overrun_error:
+ TRACE(ft_t_noise, "overrun error");
+ break;
+ default:;
+ }
+ TRACE_EXIT;
+}
+
+static char *fdc_mode_txt(fdc_mode_enum mode)
+{
+ switch (mode) {
+ case fdc_idle:
+ return "fdc_idle";
+ case fdc_reading_data:
+ return "fdc_reading_data";
+ case fdc_seeking:
+ return "fdc_seeking";
+ case fdc_writing_data:
+ return "fdc_writing_data";
+ case fdc_reading_id:
+ return "fdc_reading_id";
+ case fdc_recalibrating:
+ return "fdc_recalibrating";
+ case fdc_formatting:
+ return "fdc_formatting";
+ case fdc_verifying:
+ return "fdc_verifying";
+ default:
+ return "unknown";
+ }
+}
+
+static inline error_cause decode_irq_cause(fdc_mode_enum mode, __u8 st[])
+{
+ error_cause cause = no_error;
+ TRACE_FUN(ft_t_any);
+
+ /* Valid st[], decode cause of interrupt.
+ */
+ switch (st[0] & ST0_INT_MASK) {
+ case FDC_INT_NORMAL:
+ TRACE(ft_t_fdc_dma,"normal completion: %s",fdc_mode_txt(mode));
+ break;
+ case FDC_INT_ABNORMAL:
+ TRACE(ft_t_flow, "abnormal completion %s", fdc_mode_txt(mode));
+ TRACE(ft_t_fdc_dma, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x",
+ st[0], st[1], st[2]);
+ TRACE(ft_t_fdc_dma,
+ "C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x",
+ st[3], st[4], st[5], st[6]);
+ if (st[1] & 0x01) {
+ if (st[2] & 0x01) {
+ cause = data_am_error;
+ } else {
+ cause = id_am_error;
+ }
+ } else if (st[1] & 0x20) {
+ if (st[2] & 0x20) {
+ cause = data_crc_error;
+ } else {
+ cause = id_crc_error;
+ }
+ } else if (st[1] & 0x04) {
+ cause = no_data_error;
+ } else if (st[1] & 0x10) {
+ cause = overrun_error;
+ }
+ print_error_cause(cause);
+ break;
+ case FDC_INT_INVALID:
+ TRACE(ft_t_flow, "invalid completion %s", fdc_mode_txt(mode));
+ break;
+ case FDC_INT_READYCH:
+ if (st[0] & ST0_SEEK_END) {
+ TRACE(ft_t_flow, "drive poll completed");
+ } else {
+ TRACE(ft_t_flow, "ready change %s",fdc_mode_txt(mode));
+ }
+ break;
+ default:
+ break;
+ }
+ TRACE_EXIT cause;
+}
+
+static void update_history(error_cause cause)
+{
+ switch (cause) {
+ case id_am_error:
+ ft_history.id_am_errors++;
+ break;
+ case id_crc_error:
+ ft_history.id_crc_errors++;
+ break;
+ case data_am_error:
+ ft_history.data_am_errors++;
+ break;
+ case data_crc_error:
+ ft_history.data_crc_errors++;
+ break;
+ case overrun_error:
+ ft_history.overrun_errors++;
+ break;
+ case no_data_error:
+ ft_history.no_data_errors++;
+ break;
+ default:;
+ }
+}
+
+static void skip_bad_sector(buffer_struct * buff)
+{
+ TRACE_FUN(ft_t_any);
+
+ /* Mark sector as soft error and skip it
+ */
+ if (buff->remaining > 0) {
+ ++buff->sector_offset;
+ ++buff->data_offset;
+ --buff->remaining;
+ buff->ptr += FT_SECTOR_SIZE;
+ buff->bad_sector_map >>= 1;
+ } else {
+ /* Hey, what is this????????????? C code: if we shift
+ * more than 31 bits, we get no shift. That's bad!!!!!!
+ */
+ ++buff->sector_offset; /* hack for error maps */
+ TRACE(ft_t_warn, "skipping last sector in segment");
+ }
+ TRACE_EXIT;
+}
+
+static void update_error_maps(buffer_struct * buff, unsigned int error_offset)
+{
+ int hard = 0;
+ TRACE_FUN(ft_t_any);
+
+ if (buff->retry < FT_SOFT_RETRIES) {
+ buff->soft_error_map |= (1 << error_offset);
+ } else {
+ buff->hard_error_map |= (1 << error_offset);
+ buff->soft_error_map &= ~buff->hard_error_map;
+ buff->retry = -1; /* will be set to 0 in setup_segment */
+ hard = 1;
+ }
+ TRACE(ft_t_noise, "sector %d : %s error\n"
+ KERN_INFO "hard map: 0x%08lx\n"
+ KERN_INFO "soft map: 0x%08lx",
+ FT_SECTOR(error_offset), hard ? "hard" : "soft",
+ (long) buff->hard_error_map, (long) buff->soft_error_map);
+ TRACE_EXIT;
+}
+
+static void print_progress(buffer_struct *buff, error_cause cause)
+{
+ TRACE_FUN(ft_t_any);
+
+ switch (cause) {
+ case no_error:
+ TRACE(ft_t_flow,"%d Sector(s) transferred", buff->sector_count);
+ break;
+ case no_data_error:
+ TRACE(ft_t_flow, "Sector %d not found",
+ FT_SECTOR(buff->sector_offset));
+ break;
+ case overrun_error:
+ /* got an overrun error on the first byte, must be a
+ * hardware problem
+ */
+ TRACE(ft_t_bug,
+ "Unexpected error: failing DMA or FDC controller ?");
+ break;
+ case data_crc_error:
+ TRACE(ft_t_flow, "Error in sector %d",
+ FT_SECTOR(buff->sector_offset - 1));
+ break;
+ case id_crc_error:
+ case id_am_error:
+ case data_am_error:
+ TRACE(ft_t_flow, "Error in sector %d",
+ FT_SECTOR(buff->sector_offset));
+ break;
+ default:
+ TRACE(ft_t_flow, "Unexpected error at sector %d",
+ FT_SECTOR(buff->sector_offset));
+ break;
+ }
+ TRACE_EXIT;
+}
+
+/*
+ * Error cause: Amount xferred: Action:
+ *
+ * id_am_error 0 mark bad and skip
+ * id_crc_error 0 mark bad and skip
+ * data_am_error 0 mark bad and skip
+ * data_crc_error % 1024 mark bad and skip
+ * no_data_error 0 retry on write
+ * mark bad and skip on read
+ * overrun_error [ 0..all-1 ] mark bad and skip
+ * no_error all continue
+ */
+
+/* the arg `sector' is returned by the fdc and tells us at which sector we
+ * are positioned at (relative to starting sector of segment)
+ */
+static void determine_verify_progress(buffer_struct *buff,
+ error_cause cause,
+ __u8 sector)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (cause == no_error && sector == 1) {
+ buff->sector_offset = FT_SECTORS_PER_SEGMENT;
+ buff->remaining = 0;
+ if (TRACE_LEVEL >= ft_t_flow) {
+ print_progress(buff, cause);
+ }
+ } else {
+ buff->sector_offset = sector - buff->sect;
+ buff->remaining = FT_SECTORS_PER_SEGMENT - buff->sector_offset;
+ TRACE(ft_t_noise, "%ssector offset: 0x%04x",
+ (cause == no_error) ? "unexpected " : "",
+ buff->sector_offset);
+ switch (cause) {
+ case overrun_error:
+ break;
+#if 0
+ case no_data_error:
+ buff->retry = FT_SOFT_RETRIES;
+ if (buff->hard_error_map &&
+ buff->sector_offset > 1 &&
+ (buff->hard_error_map &
+ (1 << (buff->sector_offset-2)))) {
+ buff->retry --;
+ }
+ break;
+#endif
+ default:
+ buff->retry = FT_SOFT_RETRIES;
+ break;
+ }
+ if (TRACE_LEVEL >= ft_t_flow) {
+ print_progress(buff, cause);
+ }
+ /* Sector_offset points to the problem area Now adjust
+ * sector_offset so it always points one past he failing
+ * sector. I.e. skip the bad sector.
+ */
+ ++buff->sector_offset;
+ --buff->remaining;
+ update_error_maps(buff, buff->sector_offset - 1);
+ }
+ TRACE_EXIT;
+}
+
+static void determine_progress(buffer_struct *buff,
+ error_cause cause,
+ __u8 sector)
+{
+ unsigned int dma_residue;
+ TRACE_FUN(ft_t_any);
+
+ /* Using less preferred order of disable_dma and
+ * get_dma_residue because this seems to fail on at least one
+ * system if reversed!
+ */
+ dma_residue = get_dma_residue(fdc.dma);
+ disable_dma(fdc.dma);
+ if (cause != no_error || dma_residue != 0) {
+ TRACE(ft_t_noise, "%sDMA residue: 0x%04x",
+ (cause == no_error) ? "unexpected " : "",
+ dma_residue);
+ /* adjust to actual value: */
+ if (dma_residue == 0) {
+ /* this happens sometimes with overrun errors.
+ * I don't know whether we could ignore the
+ * overrun error. Play save.
+ */
+ buff->sector_count --;
+ } else {
+ buff->sector_count -= ((dma_residue +
+ (FT_SECTOR_SIZE - 1)) /
+ FT_SECTOR_SIZE);
+ }
+ }
+ /* Update var's influenced by the DMA operation.
+ */
+ if (buff->sector_count > 0) {
+ buff->sector_offset += buff->sector_count;
+ buff->data_offset += buff->sector_count;
+ buff->ptr += (buff->sector_count *
+ FT_SECTOR_SIZE);
+ buff->remaining -= buff->sector_count;
+ buff->bad_sector_map >>= buff->sector_count;
+ }
+ if (TRACE_LEVEL >= ft_t_flow) {
+ print_progress(buff, cause);
+ }
+ if (cause != no_error) {
+ if (buff->remaining == 0) {
+ TRACE(ft_t_warn, "foo?\n"
+ KERN_INFO "count : %d\n"
+ KERN_INFO "offset: %d\n"
+ KERN_INFO "soft : %08x\n"
+ KERN_INFO "hard : %08x",
+ buff->sector_count,
+ buff->sector_offset,
+ buff->soft_error_map,
+ buff->hard_error_map);
+ }
+ /* Sector_offset points to the problem area, except if we got
+ * a data_crc_error. In that case it points one past the
+ * failing sector.
+ *
+ * Now adjust sector_offset so it always points one past he
+ * failing sector. I.e. skip the bad sector.
+ */
+ if (cause != data_crc_error) {
+ skip_bad_sector(buff);
+ }
+ update_error_maps(buff, buff->sector_offset - 1);
+ }
+ TRACE_EXIT;
+}
+
+static int calc_steps(int cmd)
+{
+ if (ftape_current_cylinder > cmd) {
+ return ftape_current_cylinder - cmd;
+ } else {
+ return ftape_current_cylinder + cmd;
+ }
+}
+
+static void pause_tape(int retry, int mode)
+{
+ int result;
+ __u8 out[3] = {FDC_SEEK, ft_drive_sel, 0};
+ TRACE_FUN(ft_t_any);
+
+ /* We'll use a raw seek command to get the tape to rewind and
+ * stop for a retry.
+ */
+ ++ft_history.rewinds;
+ if (qic117_cmds[ftape_current_command].non_intr) {
+ TRACE(ft_t_warn, "motion command may be issued too soon");
+ }
+ if (retry && (mode == fdc_reading_data ||
+ mode == fdc_reading_id ||
+ mode == fdc_verifying)) {
+ ftape_current_command = QIC_MICRO_STEP_PAUSE;
+ ftape_might_be_off_track = 1;
+ } else {
+ ftape_current_command = QIC_PAUSE;
+ }
+ out[2] = calc_steps(ftape_current_command);
+ result = fdc_command(out, 3); /* issue QIC_117 command */
+ ftape_current_cylinder = out[ 2];
+ if (result < 0) {
+ TRACE(ft_t_noise, "qic-pause failed, status = %d", result);
+ } else {
+ ft_location.known = 0;
+ ft_runner_status = idle;
+ ft_hide_interrupt = 1;
+ ftape_tape_running = 0;
+ }
+ TRACE_EXIT;
+}
+
+static void continue_xfer(buffer_struct *buff,
+ fdc_mode_enum mode,
+ unsigned int skip)
+{
+ int write = 0;
+ TRACE_FUN(ft_t_any);
+
+ if (mode == fdc_writing_data || mode == fdc_deleting) {
+ write = 1;
+ }
+ /* This part can be removed if it never happens
+ */
+ if (skip > 0 &&
+ (ft_runner_status != running ||
+ (write && (buff->status != writing)) ||
+ (!write && (buff->status != reading &&
+ buff->status != verifying)))) {
+ TRACE(ft_t_err, "unexpected runner/buffer state %d/%d",
+ ft_runner_status, buff->status);
+ buff->status = error;
+ /* finish this buffer: */
+ (void)ftape_next_buffer(ft_queue_head);
+ ft_runner_status = aborting;
+ fdc_mode = fdc_idle;
+ } else if (buff->remaining > 0 && ftape_calc_next_cluster(buff) > 0) {
+ /* still sectors left in current segment, continue
+ * with this segment
+ */
+ if (fdc_setup_read_write(buff, mode) < 0) {
+ /* failed, abort operation
+ */
+ buff->bytes = buff->ptr - buff->address;
+ buff->status = error;
+ /* finish this buffer: */
+ (void)ftape_next_buffer(ft_queue_head);
+ ft_runner_status = aborting;
+ fdc_mode = fdc_idle;
+ }
+ } else {
+ /* current segment completed
+ */
+ unsigned int last_segment = buff->segment_id;
+ int eot = ((last_segment + 1) % ft_segments_per_track) == 0;
+ unsigned int next = buff->next_segment; /* 0 means stop ! */
+
+ buff->bytes = buff->ptr - buff->address;
+ buff->status = done;
+ buff = ftape_next_buffer(ft_queue_head);
+ if (eot) {
+ /* finished last segment on current track,
+ * can't continue
+ */
+ ft_runner_status = logical_eot;
+ fdc_mode = fdc_idle;
+ TRACE_EXIT;
+ }
+ if (next <= 0) {
+ /* don't continue with next segment
+ */
+ TRACE(ft_t_noise, "no %s allowed, stopping tape",
+ (write) ? "write next" : "read ahead");
+ pause_tape(0, mode);
+ ft_runner_status = idle; /* not quite true until
+ * next irq
+ */
+ TRACE_EXIT;
+ }
+ /* continue with next segment
+ */
+ if (buff->status != waiting) {
+ TRACE(ft_t_noise, "all input buffers %s, pausing tape",
+ (write) ? "empty" : "full");
+ pause_tape(0, mode);
+ ft_runner_status = idle; /* not quite true until
+ * next irq
+ */
+ TRACE_EXIT;
+ }
+ if (write && next != buff->segment_id) {
+ TRACE(ft_t_noise,
+ "segments out of order, aborting write");
+ ft_runner_status = do_abort;
+ fdc_mode = fdc_idle;
+ TRACE_EXIT;
+ }
+ ftape_setup_new_segment(buff, next, 0);
+ if (stop_read_ahead) {
+ buff->next_segment = 0;
+ stop_read_ahead = 0;
+ }
+ if (ftape_calc_next_cluster(buff) == 0 ||
+ fdc_setup_read_write(buff, mode) != 0) {
+ TRACE(ft_t_err, "couldn't start %s-ahead",
+ write ? "write" : "read");
+ ft_runner_status = do_abort;
+ fdc_mode = fdc_idle;
+ } else {
+ /* keep on going */
+ switch (ft_driver_state) {
+ case reading: buff->status = reading; break;
+ case verifying: buff->status = verifying; break;
+ case writing: buff->status = writing; break;
+ case deleting: buff->status = deleting; break;
+ default:
+ TRACE(ft_t_err,
+ "BUG: ft_driver_state %d should be one out of "
+ "{reading, writing, verifying, deleting}",
+ ft_driver_state);
+ buff->status = write ? writing : reading;
+ break;
+ }
+ }
+ }
+ TRACE_EXIT;
+}
+
+static void retry_sector(buffer_struct *buff,
+ int mode,
+ unsigned int skip)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_noise, "%s error, will retry",
+ (mode == fdc_writing_data || mode == fdc_deleting) ? "write" : "read");
+ pause_tape(1, mode);
+ ft_runner_status = aborting;
+ buff->status = error;
+ buff->skip = skip;
+ TRACE_EXIT;
+}
+
+static unsigned int find_resume_point(buffer_struct *buff)
+{
+ int i = 0;
+ SectorMap mask;
+ SectorMap map;
+ TRACE_FUN(ft_t_any);
+
+ /* This function is to be called after all variables have been
+ * updated to point past the failing sector.
+ * If there are any soft errors before the failing sector,
+ * find the first soft error and return the sector offset.
+ * Otherwise find the last hard error.
+ * Note: there should always be at least one hard or soft error !
+ */
+ if (buff->sector_offset < 1 || buff->sector_offset > 32) {
+ TRACE(ft_t_bug, "BUG: sector_offset = %d",
+ buff->sector_offset);
+ TRACE_EXIT 0;
+ }
+ if (buff->sector_offset >= 32) { /* C-limitation on shift ! */
+ mask = 0xffffffff;
+ } else {
+ mask = (1 << buff->sector_offset) - 1;
+ }
+ map = buff->soft_error_map & mask;
+ if (map) {
+ while ((map & (1 << i)) == 0) {
+ ++i;
+ }
+ TRACE(ft_t_noise, "at sector %d", FT_SECTOR(i));
+ } else {
+ map = buff->hard_error_map & mask;
+ i = buff->sector_offset - 1;
+ if (map) {
+ while ((map & (1 << i)) == 0) {
+ --i;
+ }
+ TRACE(ft_t_noise, "after sector %d", FT_SECTOR(i));
+ ++i; /* first sector after last hard error */
+ } else {
+ TRACE(ft_t_bug, "BUG: no soft or hard errors");
+ }
+ }
+ TRACE_EXIT i;
+}
+
+/* check possible dma residue when formatting, update position record in
+ * buffer struct. This is, of course, modelled after determine_progress(), but
+ * we don't need to set up for retries because the format process cannot be
+ * interrupted (except at the end of the tape track).
+ */
+static int determine_fmt_progress(buffer_struct *buff, error_cause cause)
+{
+ unsigned int dma_residue;
+ TRACE_FUN(ft_t_any);
+
+ /* Using less preferred order of disable_dma and
+ * get_dma_residue because this seems to fail on at least one
+ * system if reversed!
+ */
+ dma_residue = get_dma_residue(fdc.dma);
+ disable_dma(fdc.dma);
+ if (cause != no_error || dma_residue != 0) {
+ TRACE(ft_t_info, "DMA residue = 0x%04x", dma_residue);
+ fdc_mode = fdc_idle;
+ switch(cause) {
+ case no_error:
+ ft_runner_status = aborting;
+ buff->status = idle;
+ break;
+ case overrun_error:
+ /* got an overrun error on the first byte, must be a
+ * hardware problem
+ */
+ TRACE(ft_t_bug,
+ "Unexpected error: failing DMA controller ?");
+ ft_runner_status = do_abort;
+ buff->status = error;
+ break;
+ default:
+ TRACE(ft_t_noise, "Unexpected error at segment %d",
+ buff->segment_id);
+ ft_runner_status = do_abort;
+ buff->status = error;
+ break;
+ }
+ TRACE_EXIT -EIO; /* can only retry entire track in format mode
+ */
+ }
+ /* Update var's influenced by the DMA operation.
+ */
+ buff->ptr += FT_SECTORS_PER_SEGMENT * 4;
+ buff->bytes -= FT_SECTORS_PER_SEGMENT * 4;
+ buff->remaining -= FT_SECTORS_PER_SEGMENT;
+ buff->segment_id ++; /* done with segment */
+ TRACE_EXIT 0;
+}
+
+/*
+ * Continue formatting, switch buffers if there is no data left in
+ * current buffer. This is, of course, modelled after
+ * continue_xfer(), but we don't need to set up for retries because
+ * the format process cannot be interrupted (except at the end of the
+ * tape track).
+ */
+static void continue_formatting(buffer_struct *buff)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (buff->remaining <= 0) { /* no space left in dma buffer */
+ unsigned int next = buff->next_segment;
+
+ if (next == 0) { /* end of tape track */
+ buff->status = done;
+ ft_runner_status = logical_eot;
+ fdc_mode = fdc_idle;
+ TRACE(ft_t_noise, "Done formatting track %d",
+ ft_location.track);
+ TRACE_EXIT;
+ }
+ /*
+ * switch to next buffer!
+ */
+ buff->status = done;
+ buff = ftape_next_buffer(ft_queue_head);
+
+ if (buff->status != waiting || next != buff->segment_id) {
+ goto format_setup_error;
+ }
+ }
+ if (fdc_setup_formatting(buff) < 0) {
+ goto format_setup_error;
+ }
+ buff->status = formatting;
+ TRACE(ft_t_fdc_dma, "Formatting segment %d on track %d",
+ buff->segment_id, ft_location.track);
+ TRACE_EXIT;
+ format_setup_error:
+ ft_runner_status = do_abort;
+ fdc_mode = fdc_idle;
+ buff->status = error;
+ TRACE(ft_t_err, "Error setting up for segment %d on track %d",
+ buff->segment_id, ft_location.track);
+ TRACE_EXIT;
+
+}
+
+/* this handles writing, read id, reading and formatting
+ */
+static void handle_fdc_busy(buffer_struct *buff)
+{
+ static int no_data_error_count;
+ int retry = 0;
+ error_cause cause;
+ __u8 in[7];
+ int skip;
+ fdc_mode_enum fmode = fdc_mode;
+ TRACE_FUN(ft_t_any);
+
+ if (fdc_result(in, 7) < 0) { /* better get it fast ! */
+ TRACE(ft_t_err,
+ "Probably fatal error during FDC Result Phase\n"
+ KERN_INFO
+ "drive may hang until (power on) reset :-(");
+ /* what to do next ????
+ */
+ TRACE_EXIT;
+ }
+ cause = decode_irq_cause(fdc_mode, in);
+#ifdef TESTING
+ { int i;
+ for (i = 0; i < (int)ft_nr_buffers; ++i)
+ TRACE(ft_t_any, "buffer[%d] status: %d, segment_id: %d",
+ i, ft_buffer[i]->status, ft_buffer[i]->segment_id);
+ }
+#endif
+ if (fmode == fdc_reading_data && ft_driver_state == verifying) {
+ fmode = fdc_verifying;
+ }
+ switch (fmode) {
+ case fdc_verifying:
+ if (ft_runner_status == aborting ||
+ ft_runner_status == do_abort) {
+ TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode));
+ break;
+ }
+ if (buff->retry > 0) {
+ TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+ }
+ switch (cause) {
+ case no_error:
+ no_data_error_count = 0;
+ determine_verify_progress(buff, cause, in[5]);
+ if (in[2] & 0x40) {
+ /* This should not happen when verifying
+ */
+ TRACE(ft_t_warn,
+ "deleted data in segment %d/%d",
+ buff->segment_id,
+ FT_SECTOR(buff->sector_offset - 1));
+ buff->remaining = 0; /* abort transfer */
+ buff->hard_error_map = EMPTY_SEGMENT;
+ skip = 1;
+ } else {
+ skip = 0;
+ }
+ continue_xfer(buff, fdc_mode, skip);
+ break;
+ case no_data_error:
+ no_data_error_count ++;
+ case overrun_error:
+ retry ++;
+ case id_am_error:
+ case id_crc_error:
+ case data_am_error:
+ case data_crc_error:
+ determine_verify_progress(buff, cause, in[5]);
+ if (cause == no_data_error) {
+ if (no_data_error_count >= 2) {
+ TRACE(ft_t_warn,
+ "retrying because of successive "
+ "no data errors");
+ no_data_error_count = 0;
+ } else {
+ retry --;
+ }
+ } else {
+ no_data_error_count = 0;
+ }
+ if (retry) {
+ skip = find_resume_point(buff);
+ } else {
+ skip = buff->sector_offset;
+ }
+ if (retry && skip < 32) {
+ retry_sector(buff, fdc_mode, skip);
+ } else {
+ continue_xfer(buff, fdc_mode, skip);
+ }
+ update_history(cause);
+ break;
+ default:
+ /* Don't know why this could happen
+ * but find out.
+ */
+ determine_verify_progress(buff, cause, in[5]);
+ retry_sector(buff, fdc_mode, 0);
+ TRACE(ft_t_err, "Error: unexpected error");
+ break;
+ }
+ break;
+ case fdc_reading_data:
+#ifdef TESTING
+ /* I'm sorry, but: NOBODY ever used this trace
+ * messages for ages. I guess that Bas was the last person
+ * that ever really used this (thank you, between the lines)
+ */
+ if (cause == no_error) {
+ TRACE(ft_t_flow,"reading segment %d",buff->segment_id);
+ } else {
+ TRACE(ft_t_noise, "error reading segment %d",
+ buff->segment_id);
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO
+ "IRQ:C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x\n"
+ KERN_INFO
+ "BUF:C: 0x%02x, H: 0x%02x, R: 0x%02x",
+ in[3], in[4], in[5], in[6],
+ buff->cyl, buff->head, buff->sect);
+ }
+#endif
+ if (ft_runner_status == aborting ||
+ ft_runner_status == do_abort) {
+ TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode));
+ break;
+ }
+ if (buff->bad_sector_map == FAKE_SEGMENT) {
+ /* This condition occurs when reading a `fake'
+ * sector that's not accessible. Doesn't
+ * really matter as we would have ignored it
+ * anyway !
+ *
+ * Chance is that we're past the next segment
+ * now, so the next operation may fail and
+ * result in a retry.
+ */
+ buff->remaining = 0; /* skip failing sector */
+ /* buff->ptr = buff->address; */
+ /* fake success: */
+ continue_xfer(buff, fdc_mode, 1);
+ /* trace calls are expensive: place them AFTER
+ * the real stuff has been done.
+ *
+ */
+ TRACE(ft_t_noise, "skipping empty segment %d (read), size? %d",
+ buff->segment_id, buff->ptr - buff->address);
+ TRACE_EXIT;
+ }
+ if (buff->retry > 0) {
+ TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+ }
+ switch (cause) {
+ case no_error:
+ determine_progress(buff, cause, in[5]);
+ if (in[2] & 0x40) {
+ /* Handle deleted data in header segments.
+ * Skip segment and force read-ahead.
+ */
+ TRACE(ft_t_warn,
+ "deleted data in segment %d/%d",
+ buff->segment_id,
+ FT_SECTOR(buff->sector_offset - 1));
+ buff->deleted = 1;
+ buff->remaining = 0;/*abort transfer */
+ buff->soft_error_map |=
+ (-1L << buff->sector_offset);
+ if (buff->segment_id == 0) {
+ /* stop on next segment */
+ stop_read_ahead = 1;
+ }
+ /* force read-ahead: */
+ buff->next_segment =
+ buff->segment_id + 1;
+ skip = (FT_SECTORS_PER_SEGMENT -
+ buff->sector_offset);
+ } else {
+ skip = 0;
+ }
+ continue_xfer(buff, fdc_mode, skip);
+ break;
+ case no_data_error:
+ /* Tape started too far ahead of or behind the
+ * right sector. This may also happen in the
+ * middle of a segment !
+ *
+ * Handle no-data as soft error. If next
+ * sector fails too, a retry (with needed
+ * reposition) will follow.
+ */
+ retry ++;
+ case id_am_error:
+ case id_crc_error:
+ case data_am_error:
+ case data_crc_error:
+ case overrun_error:
+ retry += (buff->soft_error_map != 0 ||
+ buff->hard_error_map != 0);
+ determine_progress(buff, cause, in[5]);
+#if 1 || defined(TESTING)
+ if (cause == overrun_error) retry ++;
+#endif
+ if (retry) {
+ skip = find_resume_point(buff);
+ } else {
+ skip = buff->sector_offset;
+ }
+ /* Try to resume with next sector on single
+ * errors (let ecc correct it), but retry on
+ * no_data (we'll be past the target when we
+ * get here so we cannot retry) or on
+ * multiple errors (reduce chance on ecc
+ * failure).
+ */
+ /* cH: 23/02/97: if the last sector in the
+ * segment was a hard error, then there is
+ * no sense in a retry. This occasion seldom
+ * occurs but ... @:³²¸`@%&§$
+ */
+ if (retry && skip < 32) {
+ retry_sector(buff, fdc_mode, skip);
+ } else {
+ continue_xfer(buff, fdc_mode, skip);
+ }
+ update_history(cause);
+ break;
+ default:
+ /* Don't know why this could happen
+ * but find out.
+ */
+ determine_progress(buff, cause, in[5]);
+ retry_sector(buff, fdc_mode, 0);
+ TRACE(ft_t_err, "Error: unexpected error");
+ break;
+ }
+ break;
+ case fdc_reading_id:
+ if (cause == no_error) {
+ fdc_cyl = in[3];
+ fdc_head = in[4];
+ fdc_sect = in[5];
+ TRACE(ft_t_fdc_dma,
+ "id read: C: 0x%02x, H: 0x%02x, R: 0x%02x",
+ fdc_cyl, fdc_head, fdc_sect);
+ } else { /* no valid information, use invalid sector */
+ fdc_cyl = fdc_head = fdc_sect = 0;
+ TRACE(ft_t_flow, "Didn't find valid sector Id");
+ }
+ fdc_mode = fdc_idle;
+ break;
+ case fdc_deleting:
+ case fdc_writing_data:
+#ifdef TESTING
+ if (cause == no_error) {
+ TRACE(ft_t_flow, "writing segment %d", buff->segment_id);
+ } else {
+ TRACE(ft_t_noise, "error writing segment %d",
+ buff->segment_id);
+ }
+#endif
+ if (ft_runner_status == aborting ||
+ ft_runner_status == do_abort) {
+ TRACE(ft_t_flow, "aborting %s",fdc_mode_txt(fdc_mode));
+ break;
+ }
+ if (buff->retry > 0) {
+ TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+ }
+ if (buff->bad_sector_map == FAKE_SEGMENT) {
+ /* This condition occurs when trying to write to a
+ * `fake' sector that's not accessible. Doesn't really
+ * matter as it isn't used anyway ! Might be located
+ * at wrong segment, then we'll fail on the next
+ * segment.
+ */
+ TRACE(ft_t_noise, "skipping empty segment (write)");
+ buff->remaining = 0; /* skip failing sector */
+ /* fake success: */
+ continue_xfer(buff, fdc_mode, 1);
+ break;
+ }
+ switch (cause) {
+ case no_error:
+ determine_progress(buff, cause, in[5]);
+ continue_xfer(buff, fdc_mode, 0);
+ break;
+ case no_data_error:
+ case id_am_error:
+ case id_crc_error:
+ case data_am_error:
+ case overrun_error:
+ update_history(cause);
+ determine_progress(buff, cause, in[5]);
+ skip = find_resume_point(buff);
+ retry_sector(buff, fdc_mode, skip);
+ break;
+ default:
+ if (in[1] & 0x02) {
+ TRACE(ft_t_err, "media not writable");
+ } else {
+ TRACE(ft_t_bug, "unforeseen write error");
+ }
+ fdc_mode = fdc_idle;
+ break;
+ }
+ break; /* fdc_deleting || fdc_writing_data */
+ case fdc_formatting:
+ /* The interrupt comes after formatting a segment. We then
+ * have to set up QUICKLY for the next segment. But
+ * afterwards, there is plenty of time.
+ */
+ switch (cause) {
+ case no_error:
+ /* would like to keep most of the formatting stuff
+ * outside the isr code, but timing is too critical
+ */
+ if (determine_fmt_progress(buff, cause) >= 0) {
+ continue_formatting(buff);
+ }
+ break;
+ case no_data_error:
+ case id_am_error:
+ case id_crc_error:
+ case data_am_error:
+ case overrun_error:
+ default:
+ determine_fmt_progress(buff, cause);
+ update_history(cause);
+ if (in[1] & 0x02) {
+ TRACE(ft_t_err, "media not writable");
+ } else {
+ TRACE(ft_t_bug, "unforeseen write error");
+ }
+ break;
+ } /* cause */
+ break;
+ default:
+ TRACE(ft_t_warn, "Warning: unexpected irq during: %s",
+ fdc_mode_txt(fdc_mode));
+ fdc_mode = fdc_idle;
+ break;
+ }
+ TRACE_EXIT;
+}
+
+/* FDC interrupt service routine.
+ */
+void fdc_isr(void)
+{
+ static int isr_active;
+#ifdef TESTING
+ unsigned int t0 = ftape_timestamp();
+#endif
+ TRACE_FUN(ft_t_any);
+
+ if (isr_active++) {
+ --isr_active;
+ TRACE(ft_t_bug, "BUG: nested interrupt, not good !");
+ *fdc.hook = fdc_isr; /* hook our handler into the fdc
+ * code again
+ */
+ TRACE_EXIT;
+ }
+ sti();
+ if (inb_p(fdc.msr) & FDC_BUSY) { /* Entering Result Phase */
+ ft_hide_interrupt = 0;
+ handle_fdc_busy(ftape_get_buffer(ft_queue_head));
+ if (ft_runner_status == do_abort) {
+ /* cease operation, remember tape position
+ */
+ TRACE(ft_t_flow, "runner aborting");
+ ft_runner_status = aborting;
+ ++ft_expected_stray_interrupts;
+ }
+ } else { /* !FDC_BUSY */
+ /* clear interrupt, cause should be gotten by issuing
+ * a Sense Interrupt Status command.
+ */
+ if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) {
+ if (ft_hide_interrupt) {
+ int st0;
+ int pcn;
+
+ if (fdc_sense_interrupt_status(&st0, &pcn) < 0)
+ TRACE(ft_t_err,
+ "sense interrupt status failed");
+ ftape_current_cylinder = pcn;
+ TRACE(ft_t_flow, "handled hidden interrupt");
+ }
+ ft_seek_completed = 1;
+ fdc_mode = fdc_idle;
+ } else if (!waitqueue_active(&ftape_wait_intr)) {
+ if (ft_expected_stray_interrupts == 0) {
+ TRACE(ft_t_warn, "unexpected stray interrupt");
+ } else {
+ TRACE(ft_t_flow, "expected stray interrupt");
+ --ft_expected_stray_interrupts;
+ }
+ } else {
+ if (fdc_mode == fdc_reading_data ||
+ fdc_mode == fdc_verifying ||
+ fdc_mode == fdc_writing_data ||
+ fdc_mode == fdc_deleting ||
+ fdc_mode == fdc_formatting ||
+ fdc_mode == fdc_reading_id) {
+ if (inb_p(fdc.msr) & FDC_BUSY) {
+ TRACE(ft_t_bug,
+ "***** FDC failure, busy too late");
+ } else {
+ TRACE(ft_t_bug,
+ "***** FDC failure, no busy");
+ }
+ } else {
+ TRACE(ft_t_fdc_dma, "awaited stray interrupt");
+ }
+ }
+ ft_hide_interrupt = 0;
+ }
+ /* Handle sleep code.
+ */
+ if (!ft_hide_interrupt) {
+ ft_interrupt_seen ++;
+ if (waitqueue_active(&ftape_wait_intr)) {
+ wake_up_interruptible(&ftape_wait_intr);
+ }
+ } else {
+ TRACE(ft_t_flow, "hiding interrupt while %s",
+ waitqueue_active(&ftape_wait_intr) ? "waiting":"active");
+ }
+#ifdef TESTING
+ t0 = ftape_timediff(t0, ftape_timestamp());
+ if (t0 >= 1000) {
+ /* only tell us about long calls */
+ TRACE(ft_t_noise, "isr() duration: %5d usec", t0);
+ }
+#endif
+ *fdc.hook = fdc_isr; /* hook our handler into the fdc code again */
+ --isr_active;
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/fdc-isr.h b/drivers/char/ftape/lowlevel/fdc-isr.h
new file mode 100644
index 000000000000..065aa978942d
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-isr.h
@@ -0,0 +1,55 @@
+#ifndef _FDC_ISR_H
+#define _FDC_ISR_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:07 $
+ *
+ * This file declares the global variables necessary to
+ * synchronize the interrupt service routine (isr) with the
+ * remainder of the QIC-40/80/3010/3020 floppy-tape driver
+ * "ftape" for Linux.
+ */
+
+/*
+ * fdc-isr.c defined public variables
+ */
+extern volatile int ft_expected_stray_interrupts; /* masks stray interrupts */
+extern volatile int ft_seek_completed; /* flag set by isr */
+extern volatile int ft_interrupt_seen; /* flag set by isr */
+extern volatile int ft_hide_interrupt; /* flag set by isr */
+
+/*
+ * fdc-io.c defined public functions
+ */
+extern void fdc_isr(void);
+
+/*
+ * A kernel hook that steals one interrupt from the floppy
+ * driver (Should be fixed when the new fdc driver gets ready)
+ * See the linux kernel source files:
+ * drivers/block/floppy.c & drivers/block/blk.h
+ * for the details.
+ */
+extern void (*do_floppy) (void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.c b/drivers/char/ftape/lowlevel/ftape-bsm.c
new file mode 100644
index 000000000000..d1a301cc344f
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-bsm.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:15:15 $
+ *
+ * This file contains the bad-sector map handling code for
+ * the QIC-117 floppy tape driver for Linux.
+ * QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented.
+ */
+
+#include <linux/string.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+
+/* Global vars.
+ */
+
+/* Local vars.
+ */
+static __u8 *bad_sector_map;
+static SectorCount *bsm_hash_ptr;
+
+typedef enum {
+ forward, backward
+} mode_type;
+
+#if 0
+static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map);
+#endif
+
+#if 0
+/* fix_tape converts a normal QIC-80 tape into a 'wide' tape.
+ * For testing purposes only !
+ */
+void fix_tape(__u8 * buffer, ft_format_type new_code)
+{
+ static __u8 list[BAD_SECTOR_MAP_SIZE];
+ SectorMap *src_ptr = (SectorMap *) list;
+ __u8 *dst_ptr = bad_sector_map;
+ SectorMap map;
+ unsigned int sector = 1;
+ int i;
+
+ if (format_code != fmt_var && format_code != fmt_big) {
+ memcpy(list, bad_sector_map, sizeof(list));
+ memset(bad_sector_map, 0, sizeof(bad_sector_map));
+ while ((__u8 *) src_ptr - list < sizeof(list)) {
+ map = *src_ptr++;
+ if (map == EMPTY_SEGMENT) {
+ *(SectorMap *) dst_ptr = 0x800000 + sector;
+ dst_ptr += 3;
+ sector += SECTORS_PER_SEGMENT;
+ } else {
+ for (i = 0; i < SECTORS_PER_SEGMENT; ++i) {
+ if (map & 1) {
+ *(SewctorMap *) dst_ptr = sector;
+ dst_ptr += 3;
+ }
+ map >>= 1;
+ ++sector;
+ }
+ }
+ }
+ }
+ bad_sector_map_changed = 1;
+ *(buffer + 4) = new_code; /* put new format code */
+ if (format_code != fmt_var && new_code == fmt_big) {
+ PUT4(buffer, FT_6_HSEG_1, (__u32)GET2(buffer, 6));
+ PUT4(buffer, FT_6_HSEG_2, (__u32)GET2(buffer, 8));
+ PUT4(buffer, FT_6_FRST_SEG, (__u32)GET2(buffer, 10));
+ PUT4(buffer, FT_6_LAST_SEG, (__u32)GET2(buffer, 12));
+ memset(buffer+6, '\0', 8);
+ }
+ format_code = new_code;
+}
+
+#endif
+
+/* given buffer that contains a header segment, find the end of
+ * of the bsm list
+ */
+__u8 * ftape_find_end_of_bsm_list(__u8 * address)
+{
+ __u8 *ptr = address + FT_HEADER_END; /* start of bsm list */
+ __u8 *limit = address + FT_SEGMENT_SIZE;
+ while (ptr + 2 < limit) {
+ if (ptr[0] || ptr[1] || ptr[2]) {
+ ptr += 3;
+ } else {
+ return ptr;
+ }
+ }
+ return NULL;
+}
+
+static inline void put_sector(SectorCount *ptr, unsigned int sector)
+{
+ ptr->bytes[0] = sector & 0xff;
+ sector >>= 8;
+ ptr->bytes[1] = sector & 0xff;
+ sector >>= 8;
+ ptr->bytes[2] = sector & 0xff;
+}
+
+static inline unsigned int get_sector(SectorCount *ptr)
+{
+#if 1
+ unsigned int sector;
+
+ sector = ptr->bytes[0];
+ sector += ptr->bytes[1] << 8;
+ sector += ptr->bytes[2] << 16;
+
+ return sector;
+#else
+ /* GET4 gets the next four bytes in Intel little endian order
+ * and converts them to host byte order and handles unaligned
+ * access.
+ */
+ return (GET4(ptr, 0) & 0x00ffffff); /* back to host byte order */
+#endif
+}
+
+static void bsm_debug_fake(void)
+{
+ /* for testing of bad sector handling at end of tape
+ */
+#if 0
+ ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 3,
+ 0x000003e0;
+ ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 2,
+ 0xff3fffff;
+ ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 1,
+ 0xffffe000;
+#endif
+ /* Enable to test bad sector handling
+ */
+#if 0
+ ftape_put_bad_sector_entry(30, 0xfffffffe)
+ ftape_put_bad_sector_entry(32, 0x7fffffff);
+ ftape_put_bad_sector_entry(34, 0xfffeffff);
+ ftape_put_bad_sector_entry(36, 0x55555555);
+ ftape_put_bad_sector_entry(38, 0xffffffff);
+ ftape_put_bad_sector_entry(50, 0xffff0000);
+ ftape_put_bad_sector_entry(51, 0xffffffff);
+ ftape_put_bad_sector_entry(52, 0xffffffff);
+ ftape_put_bad_sector_entry(53, 0x0000ffff);
+#endif
+ /* Enable when testing multiple volume tar dumps.
+ */
+#if 0
+ {
+ int i;
+
+ for (i = ft_first_data_segment;
+ i <= ft_last_data_segment - 7; ++i) {
+ ftape_put_bad_sector_entry(i, EMPTY_SEGMENT);
+ }
+ }
+#endif
+ /* Enable when testing bit positions in *_error_map
+ */
+#if 0
+ {
+ int i;
+
+ for (i = first_data_segment; i <= last_data_segment; ++i) {
+ ftape_put_bad_sector_entry(i,
+ ftape_get_bad_sector_entry(i)
+ | 0x00ff00ff);
+ }
+ }
+#endif
+}
+
+static void print_bad_sector_map(void)
+{
+ unsigned int good_sectors;
+ unsigned int total_bad = 0;
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_format_code == fmt_big ||
+ ft_format_code == fmt_var ||
+ ft_format_code == fmt_1100ft) {
+ SectorCount *ptr = (SectorCount *)bad_sector_map;
+ unsigned int sector;
+ __u16 *ptr16;
+
+ while((sector = get_sector(ptr++)) != 0) {
+ if ((ft_format_code == fmt_big ||
+ ft_format_code == fmt_var) &&
+ sector & 0x800000) {
+ total_bad += FT_SECTORS_PER_SEGMENT - 3;
+ TRACE(ft_t_noise, "bad segment at sector: %6d",
+ sector & 0x7fffff);
+ } else {
+ ++total_bad;
+ TRACE(ft_t_noise, "bad sector: %6d", sector);
+ }
+ }
+ /* Display old ftape's end-of-file marks
+ */
+ ptr16 = (__u16*)ptr;
+ while ((sector = get_unaligned(ptr16++)) != 0) {
+ TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d",
+ sector, get_unaligned(ptr16++));
+ }
+ } else { /* fixed size format */
+ for (i = ft_first_data_segment;
+ i < (int)(ft_segments_per_track * ft_tracks_per_tape); ++i) {
+ SectorMap map = ((SectorMap *) bad_sector_map)[i];
+
+ if (map) {
+ TRACE(ft_t_noise,
+ "bsm for segment %4d: 0x%08x", i, (unsigned int)map);
+ total_bad += ((map == EMPTY_SEGMENT)
+ ? FT_SECTORS_PER_SEGMENT - 3
+ : count_ones(map));
+ }
+ }
+ }
+ good_sectors =
+ ((ft_segments_per_track * ft_tracks_per_tape - ft_first_data_segment)
+ * (FT_SECTORS_PER_SEGMENT - 3)) - total_bad;
+ TRACE(ft_t_info, "%d Kb usable on this tape", good_sectors);
+ if (total_bad == 0) {
+ TRACE(ft_t_info,
+ "WARNING: this tape has no bad blocks registered !");
+ } else {
+ TRACE(ft_t_info, "%d bad sectors", total_bad);
+ }
+ TRACE_EXIT;
+}
+
+
+void ftape_extract_bad_sector_map(__u8 * buffer)
+{
+ TRACE_FUN(ft_t_any);
+
+ /* Fill the bad sector map with the contents of buffer.
+ */
+ if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+ /* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed
+ * sector log but use this area to extend the bad sector map.
+ */
+ bad_sector_map = &buffer[FT_HEADER_END];
+ } else {
+ /* non-wide QIC-80 tapes have a failed sector log area that
+ * mustn't be included in the bad sector map.
+ */
+ bad_sector_map = &buffer[FT_FSL + FT_FSL_SIZE];
+ }
+ if (ft_format_code == fmt_1100ft ||
+ ft_format_code == fmt_var ||
+ ft_format_code == fmt_big) {
+ bsm_hash_ptr = (SectorCount *)bad_sector_map;
+ } else {
+ bsm_hash_ptr = NULL;
+ }
+ bsm_debug_fake();
+ if (TRACE_LEVEL >= ft_t_info) {
+ print_bad_sector_map();
+ }
+ TRACE_EXIT;
+}
+
+static inline SectorMap cvt2map(unsigned int sector)
+{
+ return 1 << (((sector & 0x7fffff) - 1) % FT_SECTORS_PER_SEGMENT);
+}
+
+static inline int cvt2segment(unsigned int sector)
+{
+ return ((sector & 0x7fffff) - 1) / FT_SECTORS_PER_SEGMENT;
+}
+
+static int forward_seek_entry(int segment_id,
+ SectorCount **ptr,
+ SectorMap *map)
+{
+ unsigned int sector;
+ int segment;
+
+ do {
+ sector = get_sector((*ptr)++);
+ segment = cvt2segment(sector);
+ } while (sector != 0 && segment < segment_id);
+ (*ptr) --; /* point to first sector >= segment_id */
+ /* Get all sectors in segment_id
+ */
+ if (sector == 0 || segment != segment_id) {
+ *map = 0;
+ return 0;
+ } else if ((sector & 0x800000) &&
+ (ft_format_code == fmt_var || ft_format_code == fmt_big)) {
+ *map = EMPTY_SEGMENT;
+ return FT_SECTORS_PER_SEGMENT;
+ } else {
+ int count = 1;
+ SectorCount *tmp_ptr = (*ptr) + 1;
+
+ *map = cvt2map(sector);
+ while ((sector = get_sector(tmp_ptr++)) != 0 &&
+ (segment = cvt2segment(sector)) == segment_id) {
+ *map |= cvt2map(sector);
+ ++count;
+ }
+ return count;
+ }
+}
+
+static int backwards_seek_entry(int segment_id,
+ SectorCount **ptr,
+ SectorMap *map)
+{
+ unsigned int sector;
+ int segment; /* max unsigned int */
+
+ if (*ptr <= (SectorCount *)bad_sector_map) {
+ *map = 0;
+ return 0;
+ }
+ do {
+ sector = get_sector(--(*ptr));
+ segment = cvt2segment(sector);
+ } while (*ptr > (SectorCount *)bad_sector_map && segment > segment_id);
+ if (segment > segment_id) { /* at start of list, no entry found */
+ *map = 0;
+ return 0;
+ } else if (segment < segment_id) {
+ /* before smaller entry, adjust for overshoot */
+ (*ptr) ++;
+ *map = 0;
+ return 0;
+ } else if ((sector & 0x800000) &&
+ (ft_format_code == fmt_big || ft_format_code == fmt_var)) {
+ *map = EMPTY_SEGMENT;
+ return FT_SECTORS_PER_SEGMENT;
+ } else { /* get all sectors in segment_id */
+ int count = 1;
+
+ *map = cvt2map(sector);
+ while(*ptr > (SectorCount *)bad_sector_map) {
+ sector = get_sector(--(*ptr));
+ segment = cvt2segment(sector);
+ if (segment != segment_id) {
+ break;
+ }
+ *map |= cvt2map(sector);
+ ++count;
+ }
+ if (segment < segment_id) {
+ (*ptr) ++;
+ }
+ return count;
+ }
+}
+
+#if 0
+static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map)
+{
+ SectorCount *ptr = (SectorCount *)bad_sector_map;
+ int count;
+ int new_count;
+ SectorMap map;
+ TRACE_FUN(ft_t_any);
+
+ if (ft_format_code == fmt_1100ft ||
+ ft_format_code == fmt_var ||
+ ft_format_code == fmt_big) {
+ count = forward_seek_entry(segment_id, &ptr, &map);
+ new_count = count_ones(new_map);
+ /* If format code == 4 put empty segment instead of 32
+ * bad sectors.
+ */
+ if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+ if (new_count == FT_SECTORS_PER_SEGMENT) {
+ new_count = 1;
+ }
+ if (count == FT_SECTORS_PER_SEGMENT) {
+ count = 1;
+ }
+ }
+ if (count != new_count) {
+ /* insert (or delete if < 0) new_count - count
+ * entries. Move trailing part of list
+ * including terminating 0.
+ */
+ SectorCount *hi_ptr = ptr;
+
+ do {
+ } while (get_sector(hi_ptr++) != 0);
+ /* Note: ptr is of type byte *, and each bad sector
+ * consumes 3 bytes.
+ */
+ memmove(ptr + new_count, ptr + count,
+ (size_t)(hi_ptr - (ptr + count))*sizeof(SectorCount));
+ }
+ TRACE(ft_t_noise, "putting map 0x%08x at %p, segment %d",
+ (unsigned int)new_map, ptr, segment_id);
+ if (new_count == 1 && new_map == EMPTY_SEGMENT) {
+ put_sector(ptr++, (0x800001 +
+ segment_id *
+ FT_SECTORS_PER_SEGMENT));
+ } else {
+ int i = 0;
+
+ while (new_map) {
+ if (new_map & 1) {
+ put_sector(ptr++,
+ 1 + segment_id *
+ FT_SECTORS_PER_SEGMENT + i);
+ }
+ ++i;
+ new_map >>= 1;
+ }
+ }
+ } else {
+ ((SectorMap *) bad_sector_map)[segment_id] = new_map;
+ }
+ TRACE_EXIT;
+}
+#endif /* 0 */
+
+SectorMap ftape_get_bad_sector_entry(int segment_id)
+{
+ if (ft_used_header_segment == -1) {
+ /* When reading header segment we'll need a blank map.
+ */
+ return 0;
+ } else if (bsm_hash_ptr != NULL) {
+ /* Invariants:
+ * map - mask value returned on last call.
+ * bsm_hash_ptr - points to first sector greater or equal to
+ * first sector in last_referenced segment.
+ * last_referenced - segment id used in the last call,
+ * sector and map belong to this id.
+ * This code is designed for sequential access and retries.
+ * For true random access it may have to be redesigned.
+ */
+ static int last_reference = -1;
+ static SectorMap map;
+
+ if (segment_id > last_reference) {
+ /* Skip all sectors before segment_id
+ */
+ forward_seek_entry(segment_id, &bsm_hash_ptr, &map);
+ } else if (segment_id < last_reference) {
+ /* Skip backwards until begin of buffer or
+ * first sector in segment_id
+ */
+ backwards_seek_entry(segment_id, &bsm_hash_ptr, &map);
+ } /* segment_id == last_reference : keep map */
+ last_reference = segment_id;
+ return map;
+ } else {
+ return ((SectorMap *) bad_sector_map)[segment_id];
+ }
+}
+
+/* This is simply here to prevent us from overwriting other kernel
+ * data. Writes will result in NULL Pointer dereference.
+ */
+void ftape_init_bsm(void)
+{
+ bad_sector_map = NULL;
+ bsm_hash_ptr = NULL;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.h b/drivers/char/ftape/lowlevel/ftape-bsm.h
new file mode 100644
index 000000000000..ed45465af4d4
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-bsm.h
@@ -0,0 +1,66 @@
+#ifndef _FTAPE_BSM_H
+#define _FTAPE_BSM_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:07 $
+ *
+ * This file contains definitions for the bad sector map handling
+ * routines for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+#include <linux/ftape-header-segment.h>
+
+#define EMPTY_SEGMENT (0xffffffff)
+#define FAKE_SEGMENT (0xfffffffe)
+
+/* maximum (format code 4) bad sector map size (bytes).
+ */
+#define BAD_SECTOR_MAP_SIZE (29 * SECTOR_SIZE - 256)
+
+/* format code 4 bad sector entry, ftape uses this
+ * internally for all format codes
+ */
+typedef __u32 SectorMap;
+/* variable and 1100 ft bad sector map entry. These three bytes represent
+ * a single sector address measured from BOT.
+ */
+typedef struct NewSectorMap {
+ __u8 bytes[3];
+} SectorCount;
+
+
+/*
+ * ftape-bsm.c defined global vars.
+ */
+
+/*
+ * ftape-bsm.c defined global functions.
+ */
+extern void update_bad_sector_map(__u8 * buffer);
+extern void ftape_extract_bad_sector_map(__u8 * buffer);
+extern SectorMap ftape_get_bad_sector_entry(int segment_id);
+extern __u8 *ftape_find_end_of_bsm_list(__u8 * address);
+extern void ftape_init_bsm(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.c b/drivers/char/ftape/lowlevel/ftape-buffer.c
new file mode 100644
index 000000000000..54af20cd9a2c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-buffer.c
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/16 23:33:11 $
+ *
+ * This file contains the allocator/dealloctor for ftape's dynamic dma
+ * buffer.
+ */
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <asm/dma.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-tracing.h"
+
+/* DMA'able memory allocation stuff.
+ */
+
+static inline void *dmaalloc(size_t size)
+{
+ unsigned long addr;
+
+ if (size == 0) {
+ return NULL;
+ }
+ addr = __get_dma_pages(GFP_KERNEL, get_order(size));
+ if (addr) {
+ struct page *page;
+
+ for (page = virt_to_page(addr); page < virt_to_page(addr+size); page++)
+ SetPageReserved(page);
+ }
+ return (void *)addr;
+}
+
+static inline void dmafree(void *addr, size_t size)
+{
+ if (size > 0) {
+ struct page *page;
+
+ for (page = virt_to_page((unsigned long)addr);
+ page < virt_to_page((unsigned long)addr+size); page++)
+ ClearPageReserved(page);
+ free_pages((unsigned long) addr, get_order(size));
+ }
+}
+
+static int add_one_buffer(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_nr_buffers >= FT_MAX_NR_BUFFERS) {
+ TRACE_EXIT -ENOMEM;
+ }
+ ft_buffer[ft_nr_buffers] = kmalloc(sizeof(buffer_struct), GFP_KERNEL);
+ if (ft_buffer[ft_nr_buffers] == NULL) {
+ TRACE_EXIT -ENOMEM;
+ }
+ memset(ft_buffer[ft_nr_buffers], 0, sizeof(buffer_struct));
+ ft_buffer[ft_nr_buffers]->address = dmaalloc(FT_BUFF_SIZE);
+ if (ft_buffer[ft_nr_buffers]->address == NULL) {
+ kfree(ft_buffer[ft_nr_buffers]);
+ ft_buffer[ft_nr_buffers] = NULL;
+ TRACE_EXIT -ENOMEM;
+ }
+ ft_nr_buffers ++;
+ TRACE(ft_t_info, "buffer nr #%d @ %p, dma area @ %p",
+ ft_nr_buffers,
+ ft_buffer[ft_nr_buffers-1],
+ ft_buffer[ft_nr_buffers-1]->address);
+ TRACE_EXIT 0;
+}
+
+static void del_one_buffer(void)
+{
+ TRACE_FUN(ft_t_flow);
+ if (ft_nr_buffers > 0) {
+ TRACE(ft_t_info, "releasing buffer nr #%d @ %p, dma area @ %p",
+ ft_nr_buffers,
+ ft_buffer[ft_nr_buffers-1],
+ ft_buffer[ft_nr_buffers-1]->address);
+ ft_nr_buffers --;
+ dmafree(ft_buffer[ft_nr_buffers]->address, FT_BUFF_SIZE);
+ kfree(ft_buffer[ft_nr_buffers]);
+ ft_buffer[ft_nr_buffers] = NULL;
+ }
+ TRACE_EXIT;
+}
+
+int ftape_set_nr_buffers(int cnt)
+{
+ int delta = cnt - ft_nr_buffers;
+ TRACE_FUN(ft_t_flow);
+
+ if (delta > 0) {
+ while (delta--) {
+ if (add_one_buffer() < 0) {
+ TRACE_EXIT -ENOMEM;
+ }
+ }
+ } else if (delta < 0) {
+ while (delta++) {
+ del_one_buffer();
+ }
+ }
+ ftape_zap_read_buffers();
+ TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.h b/drivers/char/ftape/lowlevel/ftape-buffer.h
new file mode 100644
index 000000000000..eec99cee8f82
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-buffer.h
@@ -0,0 +1,32 @@
+#ifndef _FTAPE_BUFFER_H
+#define _FTAPE_BUFFER_H
+
+/*
+ * Copyright (C) 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:08 $
+ *
+ * This file contains the allocator/dealloctor for ftape's dynamic dma
+ * buffer.
+ */
+
+extern int ftape_set_nr_buffers(int cnt);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.c b/drivers/char/ftape/lowlevel/ftape-calibr.c
new file mode 100644
index 000000000000..956b2586e138
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-calibr.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:08 $
+ *
+ * GP calibration routine for processor speed dependent
+ * functions.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/jiffies.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#if defined(__alpha__)
+# include <asm/hwrpb.h>
+#elif defined(__x86_64__)
+# include <asm/msr.h>
+# include <asm/timex.h>
+#elif defined(__i386__)
+# include <linux/timex.h>
+#endif
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/fdc-io.h"
+
+#undef DEBUG
+
+#if !defined(__alpha__) && !defined(__i386__) && !defined(__x86_64__)
+# error Ftape is not implemented for this architecture!
+#endif
+
+#if defined(__alpha__) || defined(__x86_64__)
+static unsigned long ps_per_cycle = 0;
+#endif
+
+static spinlock_t calibr_lock;
+
+/*
+ * Note: On Intel PCs, the clock ticks at 100 Hz (HZ==100) which is
+ * too slow for certain timeouts (and that clock doesn't even tick
+ * when interrupts are disabled). For that reason, the 8254 timer is
+ * used directly to implement fine-grained timeouts. However, on
+ * Alpha PCs, the 8254 is *not* used to implement the clock tick
+ * (which is 1024 Hz, normally) and the 8254 timer runs at some
+ * "random" frequency (it seems to run at 18Hz, but it's not safe to
+ * rely on this value). Instead, we use the Alpha's "rpcc"
+ * instruction to read cycle counts. As this is a 32 bit counter,
+ * it will overflow only once per 30 seconds (on a 200MHz machine),
+ * which is plenty.
+ */
+
+unsigned int ftape_timestamp(void)
+{
+#if defined(__alpha__)
+ unsigned long r;
+
+ asm volatile ("rpcc %0" : "=r" (r));
+ return r;
+#elif defined(__x86_64__)
+ unsigned long r;
+ rdtscl(r);
+ return r;
+#elif defined(__i386__)
+
+/*
+ * Note that there is some time between counter underflowing and jiffies
+ * increasing, so the code below won't always give correct output.
+ * -Vojtech
+ */
+
+ unsigned long flags;
+ __u16 lo;
+ __u16 hi;
+
+ spin_lock_irqsave(&calibr_lock, flags);
+ outb_p(0x00, 0x43); /* latch the count ASAP */
+ lo = inb_p(0x40); /* read the latched count */
+ lo |= inb(0x40) << 8;
+ hi = jiffies;
+ spin_unlock_irqrestore(&calibr_lock, flags);
+ return ((hi + 1) * (unsigned int) LATCH) - lo; /* downcounter ! */
+#endif
+}
+
+static unsigned int short_ftape_timestamp(void)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+ return ftape_timestamp();
+#elif defined(__i386__)
+ unsigned int count;
+ unsigned long flags;
+
+ spin_lock_irqsave(&calibr_lock, flags);
+ outb_p(0x00, 0x43); /* latch the count ASAP */
+ count = inb_p(0x40); /* read the latched count */
+ count |= inb(0x40) << 8;
+ spin_unlock_irqrestore(&calibr_lock, flags);
+ return (LATCH - count); /* normal: downcounter */
+#endif
+}
+
+static unsigned int diff(unsigned int t0, unsigned int t1)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+ return (t1 - t0);
+#elif defined(__i386__)
+ /*
+ * This is tricky: to work for both short and full ftape_timestamps
+ * we'll have to discriminate between these.
+ * If it _looks_ like short stamps with wrapping around we'll
+ * asume it are. This will generate a small error if it really
+ * was a (very large) delta from full ftape_timestamps.
+ */
+ return (t1 <= t0 && t0 <= LATCH) ? t1 + LATCH - t0 : t1 - t0;
+#endif
+}
+
+static unsigned int usecs(unsigned int count)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+ return (ps_per_cycle * count) / 1000000UL;
+#elif defined(__i386__)
+ return (10000 * count) / ((CLOCK_TICK_RATE + 50) / 100);
+#endif
+}
+
+unsigned int ftape_timediff(unsigned int t0, unsigned int t1)
+{
+ /*
+ * Calculate difference in usec for ftape_timestamp results t0 & t1.
+ * Note that on the i386 platform with short time-stamps, the
+ * maximum allowed timespan is 1/HZ or we'll lose ticks!
+ */
+ return usecs(diff(t0, t1));
+}
+
+/* To get an indication of the I/O performance,
+ * measure the duration of the inb() function.
+ */
+static void time_inb(void)
+{
+ int i;
+ int t0, t1;
+ unsigned long flags;
+ int status;
+ TRACE_FUN(ft_t_any);
+
+ spin_lock_irqsave(&calibr_lock, flags);
+ t0 = short_ftape_timestamp();
+ for (i = 0; i < 1000; ++i) {
+ status = inb(fdc.msr);
+ }
+ t1 = short_ftape_timestamp();
+ spin_unlock_irqrestore(&calibr_lock, flags);
+ TRACE(ft_t_info, "inb() duration: %d nsec", ftape_timediff(t0, t1));
+ TRACE_EXIT;
+}
+
+static void init_clock(void)
+{
+ TRACE_FUN(ft_t_any);
+
+#if defined(__x86_64__)
+ ps_per_cycle = 1000000000UL / cpu_khz;
+#elif defined(__alpha__)
+ extern struct hwrpb_struct *hwrpb;
+ ps_per_cycle = (1000*1000*1000*1000UL) / hwrpb->cycle_freq;
+#endif
+ TRACE_EXIT;
+}
+
+/*
+ * Input: function taking int count as parameter.
+ * pointers to calculated calibration variables.
+ */
+void ftape_calibrate(char *name,
+ void (*fun) (unsigned int),
+ unsigned int *calibr_count,
+ unsigned int *calibr_time)
+{
+ static int first_time = 1;
+ int i;
+ unsigned int tc = 0;
+ unsigned int count;
+ unsigned int time;
+#if defined(__i386__)
+ unsigned int old_tc = 0;
+ unsigned int old_count = 1;
+ unsigned int old_time = 1;
+#endif
+ TRACE_FUN(ft_t_flow);
+
+ if (first_time) { /* get idea of I/O performance */
+ init_clock();
+ time_inb();
+ first_time = 0;
+ }
+ /* value of timeout must be set so that on very slow systems
+ * it will give a time less than one jiffy, and on
+ * very fast systems it'll give reasonable precision.
+ */
+
+ count = 40;
+ for (i = 0; i < 15; ++i) {
+ unsigned int t0;
+ unsigned int t1;
+ unsigned int once;
+ unsigned int multiple;
+ unsigned long flags;
+
+ *calibr_count =
+ *calibr_time = count; /* set TC to 1 */
+ spin_lock_irqsave(&calibr_lock, flags);
+ fun(0); /* dummy, get code into cache */
+ t0 = short_ftape_timestamp();
+ fun(0); /* overhead + one test */
+ t1 = short_ftape_timestamp();
+ once = diff(t0, t1);
+ t0 = short_ftape_timestamp();
+ fun(count); /* overhead + count tests */
+ t1 = short_ftape_timestamp();
+ multiple = diff(t0, t1);
+ spin_unlock_irqrestore(&calibr_lock, flags);
+ time = ftape_timediff(0, multiple - once);
+ tc = (1000 * time) / (count - 1);
+ TRACE(ft_t_any, "once:%3d us,%6d times:%6d us, TC:%5d ns",
+ usecs(once), count - 1, usecs(multiple), tc);
+#if defined(__alpha__) || defined(__x86_64__)
+ /*
+ * Increase the calibration count exponentially until the
+ * calibration time exceeds 100 ms.
+ */
+ if (time >= 100*1000) {
+ break;
+ }
+#elif defined(__i386__)
+ /*
+ * increase the count until the resulting time nears 2/HZ,
+ * then the tc will drop sharply because we lose LATCH counts.
+ */
+ if (tc <= old_tc / 2) {
+ time = old_time;
+ count = old_count;
+ break;
+ }
+ old_tc = tc;
+ old_count = count;
+ old_time = time;
+#endif
+ count *= 2;
+ }
+ *calibr_count = count - 1;
+ *calibr_time = time;
+ TRACE(ft_t_info, "TC for `%s()' = %d nsec (at %d counts)",
+ name, (1000 * *calibr_time) / *calibr_count, *calibr_count);
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.h b/drivers/char/ftape/lowlevel/ftape-calibr.h
new file mode 100644
index 000000000000..0c7e75246c7d
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-calibr.h
@@ -0,0 +1,37 @@
+#ifndef _FTAPE_CALIBR_H
+#define _FTAPE_CALIBR_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/09/19 09:05:26 $
+ *
+ * This file contains a gp calibration routine for
+ * hardware dependent timeout functions.
+ */
+
+extern void ftape_calibrate(char *name,
+ void (*fun) (unsigned int),
+ unsigned int *calibr_count,
+ unsigned int *calibr_time);
+extern unsigned int ftape_timestamp(void);
+extern unsigned int ftape_timediff(unsigned int t0, unsigned int t1);
+
+#endif /* _FTAPE_CALIBR_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.c b/drivers/char/ftape/lowlevel/ftape-ctl.c
new file mode 100644
index 000000000000..32e043911790
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ctl.c
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/11/11 14:37:44 $
+ *
+ * This file contains the non-read/write ftape functions for the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* ease porting between pre-2.4.x and later kernels */
+#define vma_get_pgoff(v) ((v)->vm_pgoff)
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/* Global vars.
+ */
+ftape_info ftape_status = {
+/* vendor information */
+ { 0, }, /* drive type */
+/* data rates */
+ 500, /* used data rate */
+ 500, /* drive max rate */
+ 500, /* fdc max rate */
+/* drive selection, either FTAPE_SEL_A/B/C/D */
+ -1, /* drive selection */
+/* flags set after decode the drive and tape status */
+ 0, /* formatted */
+ 1, /* no tape */
+ 1, /* write protected */
+ 1, /* new tape */
+/* values of last queried drive/tape status and error */
+ {{0,}}, /* last error code */
+ {{0,}}, /* drive status, configuration, tape status */
+/* cartridge geometry */
+ 20, /* tracks_per_tape */
+ 102, /* segments_per_track */
+/* location of header segments, etc. */
+ -1, /* used_header_segment */
+ -1, /* header_segment_1 */
+ -1, /* header_segment_2 */
+ -1, /* first_data_segment */
+ -1, /* last_data_segment */
+/* the format code as stored in the header segment */
+ fmt_normal, /* format code */
+/* the default for the qic std: unknown */
+ -1,
+/* is tape running? */
+ idle, /* runner_state */
+/* is tape reading/writing/verifying/formatting/deleting */
+ idle, /* driver state */
+/* flags fatal hardware error */
+ 1, /* failure */
+/* history record */
+ { 0, } /* history record */
+};
+
+int ftape_segments_per_head = 1020;
+int ftape_segments_per_cylinder = 4;
+int ftape_init_drive_needed = 1; /* need to be global for ftape_reset_drive()
+ * in ftape-io.c
+ */
+
+/* Local vars.
+ */
+static const vendor_struct vendors[] = QIC117_VENDORS;
+static const wakeup_method methods[] = WAKEUP_METHODS;
+
+const ftape_info *ftape_get_status(void)
+{
+#if defined(STATUS_PARANOYA)
+ static ftape_info get_status;
+
+ get_status = ftape_status;
+ return &get_status;
+#else
+ return &ftape_status; /* maybe return only a copy of it to assure
+ * read only access
+ */
+#endif
+}
+
+static int ftape_not_operational(int status)
+{
+ /* return true if status indicates tape can not be used.
+ */
+ return ((status ^ QIC_STATUS_CARTRIDGE_PRESENT) &
+ (QIC_STATUS_ERROR |
+ QIC_STATUS_CARTRIDGE_PRESENT |
+ QIC_STATUS_NEW_CARTRIDGE));
+}
+
+int ftape_seek_to_eot(void)
+{
+ int status;
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+ while ((status & QIC_STATUS_AT_EOT) == 0) {
+ if (ftape_not_operational(status)) {
+ TRACE_EXIT -EIO;
+ }
+ TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_FORWARD,
+ ftape_timeout.rewind,&status),);
+ }
+ TRACE_EXIT 0;
+}
+
+int ftape_seek_to_bot(void)
+{
+ int status;
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+ while ((status & QIC_STATUS_AT_BOT) == 0) {
+ if (ftape_not_operational(status)) {
+ TRACE_EXIT -EIO;
+ }
+ TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_REVERSE,
+ ftape_timeout.rewind,&status),);
+ }
+ TRACE_EXIT 0;
+}
+
+static int ftape_new_cartridge(void)
+{
+ ft_location.track = -1; /* force seek on first access */
+ ftape_zap_read_buffers();
+ ftape_zap_write_buffers();
+ return 0;
+}
+
+int ftape_abort_operation(void)
+{
+ int result = 0;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_runner_status == running) {
+ TRACE(ft_t_noise, "aborting runner, waiting");
+
+ ft_runner_status = do_abort;
+ /* set timeout so that the tape will run to logical EOT
+ * if we missed the last sector and there are no queue pulses.
+ */
+ result = ftape_dumb_stop();
+ }
+ if (ft_runner_status != idle) {
+ if (ft_runner_status == do_abort) {
+ TRACE(ft_t_noise, "forcing runner abort");
+ }
+ TRACE(ft_t_noise, "stopping tape");
+ result = ftape_stop_tape(&status);
+ ft_location.known = 0;
+ ft_runner_status = idle;
+ }
+ ftape_reset_buffer();
+ ftape_zap_read_buffers();
+ ftape_set_state(idle);
+ TRACE_EXIT result;
+}
+
+static int lookup_vendor_id(unsigned int vendor_id)
+{
+ int i = 0;
+
+ while (vendors[i].vendor_id != vendor_id) {
+ if (++i >= NR_ITEMS(vendors)) {
+ return -1;
+ }
+ }
+ return i;
+}
+
+static void ftape_detach_drive(void)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_flow, "disabling tape drive and fdc");
+ ftape_put_drive_to_sleep(ft_drive_type.wake_up);
+ fdc_catch_stray_interrupts(1); /* one always comes */
+ fdc_disable();
+ fdc_release_irq_and_dma();
+ fdc_release_regions();
+ TRACE_EXIT;
+}
+
+static void clear_history(void)
+{
+ ft_history.used = 0;
+ ft_history.id_am_errors =
+ ft_history.id_crc_errors =
+ ft_history.data_am_errors =
+ ft_history.data_crc_errors =
+ ft_history.overrun_errors =
+ ft_history.no_data_errors =
+ ft_history.retries =
+ ft_history.crc_errors =
+ ft_history.crc_failures =
+ ft_history.ecc_failures =
+ ft_history.corrected =
+ ft_history.defects =
+ ft_history.rewinds = 0;
+}
+
+static int ftape_activate_drive(vendor_struct * drive_type)
+{
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ /* If we already know the drive type, wake it up.
+ * Else try to find out what kind of drive is attached.
+ */
+ if (drive_type->wake_up != unknown_wake_up) {
+ TRACE(ft_t_flow, "enabling tape drive and fdc");
+ result = ftape_wakeup_drive(drive_type->wake_up);
+ if (result < 0) {
+ TRACE(ft_t_err, "known wakeup method failed");
+ }
+ } else {
+ wake_up_types method;
+ const ft_trace_t old_tracing = TRACE_LEVEL;
+ if (TRACE_LEVEL < ft_t_flow) {
+ SET_TRACE_LEVEL(ft_t_bug);
+ }
+
+ /* Try to awaken the drive using all known methods.
+ * Lower tracing for a while.
+ */
+ for (method=no_wake_up; method < NR_ITEMS(methods); ++method) {
+ drive_type->wake_up = method;
+#ifdef CONFIG_FT_TWO_DRIVES
+ /* Test setup for dual drive configuration.
+ * /dev/rft2 uses mountain wakeup
+ * /dev/rft3 uses colorado wakeup
+ * Other systems will use the normal scheme.
+ */
+ if ((ft_drive_sel < 2) ||
+ (ft_drive_sel == 2 && method == FT_WAKE_UP_1) ||
+ (ft_drive_sel == 3 && method == FT_WAKE_UP_2)) {
+ result=ftape_wakeup_drive(drive_type->wake_up);
+ } else {
+ result = -EIO;
+ }
+#else
+ result = ftape_wakeup_drive(drive_type->wake_up);
+#endif
+ if (result >= 0) {
+ TRACE(ft_t_warn, "drive wakeup method: %s",
+ methods[drive_type->wake_up].name);
+ break;
+ }
+ }
+ SET_TRACE_LEVEL(old_tracing);
+
+ if (method >= NR_ITEMS(methods)) {
+ /* no response at all, cannot open this drive */
+ drive_type->wake_up = unknown_wake_up;
+ TRACE(ft_t_err, "no tape drive found !");
+ result = -ENODEV;
+ }
+ }
+ TRACE_EXIT result;
+}
+
+static int ftape_get_drive_status(void)
+{
+ int result;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ ft_no_tape = ft_write_protected = 0;
+ /* Tape drive is activated now.
+ * First clear error status if present.
+ */
+ do {
+ result = ftape_ready_wait(ftape_timeout.reset, &status);
+ if (result < 0) {
+ if (result == -ETIME) {
+ TRACE(ft_t_err, "ftape_ready_wait timeout");
+ } else if (result == -EINTR) {
+ TRACE(ft_t_err, "ftape_ready_wait aborted");
+ } else {
+ TRACE(ft_t_err, "ftape_ready_wait failed");
+ }
+ TRACE_EXIT -EIO;
+ }
+ /* Clear error condition (drive is ready !)
+ */
+ if (status & QIC_STATUS_ERROR) {
+ unsigned int error;
+ qic117_cmd_t command;
+
+ TRACE(ft_t_err, "error status set");
+ result = ftape_report_error(&error, &command, 1);
+ if (result < 0) {
+ TRACE(ft_t_err,
+ "report_error_code failed: %d", result);
+ /* hope it's working next time */
+ ftape_reset_drive();
+ TRACE_EXIT -EIO;
+ } else if (error != 0) {
+ TRACE(ft_t_noise, "error code : %d", error);
+ TRACE(ft_t_noise, "error command: %d", command);
+ }
+ }
+ if (status & QIC_STATUS_NEW_CARTRIDGE) {
+ unsigned int error;
+ qic117_cmd_t command;
+ const ft_trace_t old_tracing = TRACE_LEVEL;
+ SET_TRACE_LEVEL(ft_t_bug);
+
+ /* Undocumented feature: Must clear (not present!)
+ * error here or we'll fail later.
+ */
+ ftape_report_error(&error, &command, 1);
+
+ SET_TRACE_LEVEL(old_tracing);
+ TRACE(ft_t_info, "status: new cartridge");
+ ft_new_tape = 1;
+ } else {
+ ft_new_tape = 0;
+ }
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ } while (status & QIC_STATUS_ERROR);
+
+ ft_no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT);
+ ft_write_protected = (status & QIC_STATUS_WRITE_PROTECT) != 0;
+ if (ft_no_tape) {
+ TRACE(ft_t_warn, "no cartridge present");
+ } else {
+ if (ft_write_protected) {
+ TRACE(ft_t_noise, "Write protected cartridge");
+ }
+ }
+ TRACE_EXIT 0;
+}
+
+static void ftape_log_vendor_id(void)
+{
+ int vendor_index;
+ TRACE_FUN(ft_t_flow);
+
+ ftape_report_vendor_id(&ft_drive_type.vendor_id);
+ vendor_index = lookup_vendor_id(ft_drive_type.vendor_id);
+ if (ft_drive_type.vendor_id == UNKNOWN_VENDOR &&
+ ft_drive_type.wake_up == wake_up_colorado) {
+ vendor_index = 0;
+ /* hack to get rid of all this mail */
+ ft_drive_type.vendor_id = 0;
+ }
+ if (vendor_index < 0) {
+ /* Unknown vendor id, first time opening device. The
+ * drive_type remains set to type found at wakeup
+ * time, this will probably keep the driver operating
+ * for this new vendor.
+ */
+ TRACE(ft_t_warn, "\n"
+ KERN_INFO "============ unknown vendor id ===========\n"
+ KERN_INFO "A new, yet unsupported tape drive is found\n"
+ KERN_INFO "Please report the following values:\n"
+ KERN_INFO " Vendor id : 0x%04x\n"
+ KERN_INFO " Wakeup method : %s\n"
+ KERN_INFO "And a description of your tape drive\n"
+ KERN_INFO "to "THE_FTAPE_MAINTAINER"\n"
+ KERN_INFO "==========================================",
+ ft_drive_type.vendor_id,
+ methods[ft_drive_type.wake_up].name);
+ ft_drive_type.speed = 0; /* unknown */
+ } else {
+ ft_drive_type.name = vendors[vendor_index].name;
+ ft_drive_type.speed = vendors[vendor_index].speed;
+ TRACE(ft_t_info, "tape drive type: %s", ft_drive_type.name);
+ /* scan all methods for this vendor_id in table */
+ while(ft_drive_type.wake_up != vendors[vendor_index].wake_up) {
+ if (vendor_index < NR_ITEMS(vendors) - 1 &&
+ vendors[vendor_index + 1].vendor_id
+ ==
+ ft_drive_type.vendor_id) {
+ ++vendor_index;
+ } else {
+ break;
+ }
+ }
+ if (ft_drive_type.wake_up != vendors[vendor_index].wake_up) {
+ TRACE(ft_t_warn, "\n"
+ KERN_INFO "==========================================\n"
+ KERN_INFO "wakeup type mismatch:\n"
+ KERN_INFO "found: %s, expected: %s\n"
+ KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n"
+ KERN_INFO "==========================================",
+ methods[ft_drive_type.wake_up].name,
+ methods[vendors[vendor_index].wake_up].name);
+ }
+ }
+ TRACE_EXIT;
+}
+
+void ftape_calc_timeouts(unsigned int qic_std,
+ unsigned int data_rate,
+ unsigned int tape_len)
+{
+ int speed; /* deci-ips ! */
+ int ff_speed;
+ int length;
+ TRACE_FUN(ft_t_any);
+
+ /* tape transport speed
+ * data rate: QIC-40 QIC-80 QIC-3010 QIC-3020
+ *
+ * 250 Kbps 25 ips n/a n/a n/a
+ * 500 Kbps 50 ips 34 ips 22.6 ips n/a
+ * 1 Mbps n/a 68 ips 45.2 ips 22.6 ips
+ * 2 Mbps n/a n/a n/a 45.2 ips
+ *
+ * fast tape transport speed is at least 68 ips.
+ */
+ switch (qic_std) {
+ case QIC_TAPE_QIC40:
+ speed = (data_rate == 250) ? 250 : 500;
+ break;
+ case QIC_TAPE_QIC80:
+ speed = (data_rate == 500) ? 340 : 680;
+ break;
+ case QIC_TAPE_QIC3010:
+ speed = (data_rate == 500) ? 226 : 452;
+ break;
+ case QIC_TAPE_QIC3020:
+ speed = (data_rate == 1000) ? 226 : 452;
+ break;
+ default:
+ TRACE(ft_t_bug, "Unknown qic_std (bug) ?");
+ speed = 500;
+ break;
+ }
+ if (ft_drive_type.speed == 0) {
+ unsigned long t0;
+ static int dt = 0; /* keep gcc from complaining */
+ static int first_time = 1;
+
+ /* Measure the time it takes to wind to EOT and back to BOT.
+ * If the tape length is known, calculate the rewind speed.
+ * Else keep the time value for calculation of the rewind
+ * speed later on, when the length _is_ known.
+ * Ask for a report only when length and speed are both known.
+ */
+ if (first_time) {
+ ftape_seek_to_bot();
+ t0 = jiffies;
+ ftape_seek_to_eot();
+ ftape_seek_to_bot();
+ dt = (int) (((jiffies - t0) * FT_USPT) / 1000);
+ if (dt < 1) {
+ dt = 1; /* prevent div by zero on failures */
+ }
+ first_time = 0;
+ TRACE(ft_t_info,
+ "trying to determine seek timeout, got %d msec",
+ dt);
+ }
+ if (tape_len != 0) {
+ ft_drive_type.speed =
+ (2 * 12 * tape_len * 1000) / dt;
+ TRACE(ft_t_warn, "\n"
+ KERN_INFO "==========================================\n"
+ KERN_INFO "drive type: %s\n"
+ KERN_INFO "delta time = %d ms, length = %d ft\n"
+ KERN_INFO "has a maximum tape speed of %d ips\n"
+ KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n"
+ KERN_INFO "==========================================",
+ ft_drive_type.name, dt, tape_len,
+ ft_drive_type.speed);
+ }
+ }
+ /* Handle unknown length tapes as very long ones. We'll
+ * determine the actual length from a header segment later.
+ * This is normal for all modern (Wide,TR1/2/3) formats.
+ */
+ if (tape_len <= 0) {
+ TRACE(ft_t_noise,
+ "Unknown tape length, using maximal timeouts");
+ length = QIC_TOP_TAPE_LEN; /* use worst case values */
+ } else {
+ length = tape_len; /* use actual values */
+ }
+ if (ft_drive_type.speed == 0) {
+ ff_speed = speed;
+ } else {
+ ff_speed = ft_drive_type.speed;
+ }
+ /* time to go from bot to eot at normal speed (data rate):
+ * time = (1+delta) * length (ft) * 12 (inch/ft) / speed (ips)
+ * delta = 10 % for seek speed, 20 % for rewind speed.
+ */
+ ftape_timeout.seek = (length * 132 * FT_SECOND) / speed;
+ ftape_timeout.rewind = (length * 144 * FT_SECOND) / (10 * ff_speed);
+ ftape_timeout.reset = 20 * FT_SECOND + ftape_timeout.rewind;
+ TRACE(ft_t_noise, "timeouts for speed = %d, length = %d\n"
+ KERN_INFO "seek timeout : %d sec\n"
+ KERN_INFO "rewind timeout: %d sec\n"
+ KERN_INFO "reset timeout : %d sec",
+ speed, length,
+ (ftape_timeout.seek + 500) / 1000,
+ (ftape_timeout.rewind + 500) / 1000,
+ (ftape_timeout.reset + 500) / 1000);
+ TRACE_EXIT;
+}
+
+/* This function calibrates the datarate (i.e. determines the maximal
+ * usable data rate) and sets the global variable ft_qic_std to qic_std
+ *
+ */
+int ftape_calibrate_data_rate(unsigned int qic_std)
+{
+ int rate = ft_fdc_rate_limit;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ ft_qic_std = qic_std;
+
+ if (ft_qic_std == -1) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "Unable to determine data rate if QIC standard is unknown");
+ }
+
+ /* Select highest rate supported by both fdc and drive.
+ * Start with highest rate supported by the fdc.
+ */
+ while (fdc_set_data_rate(rate) < 0 && rate > 250) {
+ rate /= 2;
+ }
+ TRACE(ft_t_info,
+ "Highest FDC supported data rate: %d Kbps", rate);
+ ft_fdc_max_rate = rate;
+ do {
+ result = ftape_set_data_rate(rate, ft_qic_std);
+ } while (result == -EINVAL && (rate /= 2) > 250);
+ if (result < 0) {
+ TRACE_ABORT(-EIO, ft_t_err, "set datarate failed");
+ }
+ ft_data_rate = rate;
+ TRACE_EXIT 0;
+}
+
+static int ftape_init_drive(void)
+{
+ int status;
+ qic_model model;
+ unsigned int qic_std;
+ unsigned int data_rate;
+ TRACE_FUN(ft_t_flow);
+
+ ftape_init_drive_needed = 0; /* don't retry if this fails ? */
+ TRACE_CATCH(ftape_report_raw_drive_status(&status),);
+ if (status & QIC_STATUS_CARTRIDGE_PRESENT) {
+ if (!(status & QIC_STATUS_AT_BOT)) {
+ /* Antique drives will get here after a soft reset,
+ * modern ones only if the driver is loaded when the
+ * tape wasn't rewound properly.
+ */
+ /* Tape should be at bot if new cartridge ! */
+ ftape_seek_to_bot();
+ }
+ if (!(status & QIC_STATUS_REFERENCED)) {
+ TRACE(ft_t_flow, "starting seek_load_point");
+ TRACE_CATCH(ftape_command_wait(QIC_SEEK_LOAD_POINT,
+ ftape_timeout.reset,
+ &status),);
+ }
+ }
+ ft_formatted = (status & QIC_STATUS_REFERENCED) != 0;
+ if (!ft_formatted) {
+ TRACE(ft_t_warn, "Warning: tape is not formatted !");
+ }
+
+ /* report configuration aborts when ftape_tape_len == -1
+ * unknown qic_std is okay if not formatted.
+ */
+ TRACE_CATCH(ftape_report_configuration(&model,
+ &data_rate,
+ &qic_std,
+ &ftape_tape_len),);
+
+ /* Maybe add the following to the /proc entry
+ */
+ TRACE(ft_t_info, "%s drive @ %d Kbps",
+ (model == prehistoric) ? "prehistoric" :
+ ((model == pre_qic117c) ? "pre QIC-117C" :
+ ((model == post_qic117b) ? "post QIC-117B" :
+ "post QIC-117D")), data_rate);
+
+ if (ft_formatted) {
+ /* initialize ft_used_data_rate to maximum value
+ * and set ft_qic_std
+ */
+ TRACE_CATCH(ftape_calibrate_data_rate(qic_std),);
+ if (ftape_tape_len == 0) {
+ TRACE(ft_t_info, "unknown length QIC-%s tape",
+ (ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+ ((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+ ((ft_qic_std == QIC_TAPE_QIC3010)
+ ? "3010" : "3020")));
+ } else {
+ TRACE(ft_t_info, "%d ft. QIC-%s tape", ftape_tape_len,
+ (ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+ ((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+ ((ft_qic_std == QIC_TAPE_QIC3010)
+ ? "3010" : "3020")));
+ }
+ ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+ /* soft write-protect QIC-40/QIC-80 cartridges used with a
+ * Colorado T3000 drive. Buggy hardware!
+ */
+ if ((ft_drive_type.vendor_id == 0x011c6) &&
+ ((ft_qic_std == QIC_TAPE_QIC40 ||
+ ft_qic_std == QIC_TAPE_QIC80) &&
+ !ft_write_protected)) {
+ TRACE(ft_t_warn, "\n"
+ KERN_INFO "The famous Colorado T3000 bug:\n"
+ KERN_INFO "%s drives can't write QIC40 and QIC80\n"
+ KERN_INFO "cartridges but don't set the write-protect flag!",
+ ft_drive_type.name);
+ ft_write_protected = 1;
+ }
+ } else {
+ /* Doesn't make too much sense to set the data rate
+ * because we don't know what to use for the write
+ * precompensation.
+ * Need to do this again when formatting the cartridge.
+ */
+ ft_data_rate = data_rate;
+ ftape_calc_timeouts(QIC_TAPE_QIC40,
+ data_rate,
+ ftape_tape_len);
+ }
+ ftape_new_cartridge();
+ TRACE_EXIT 0;
+}
+
+static void ftape_munmap(void)
+{
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+ for (i = 0; i < ft_nr_buffers; i++) {
+ ft_buffer[i]->mmapped = 0;
+ }
+ TRACE_EXIT;
+}
+
+/* Map the dma buffers into the virtual address range given by vma.
+ * We only check the caller doesn't map non-existent buffers. We
+ * don't check for multiple mappings.
+ */
+int ftape_mmap(struct vm_area_struct *vma)
+{
+ int num_buffers;
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_failure) {
+ TRACE_EXIT -ENODEV;
+ }
+ if (!(vma->vm_flags & (VM_READ|VM_WRITE))) {
+ TRACE_ABORT(-EINVAL, ft_t_err, "Undefined mmap() access");
+ }
+ if (vma_get_pgoff(vma) != 0) {
+ TRACE_ABORT(-EINVAL, ft_t_err, "page offset must be 0");
+ }
+ if ((vma->vm_end - vma->vm_start) % FT_BUFF_SIZE != 0) {
+ TRACE_ABORT(-EINVAL, ft_t_err,
+ "size = %ld, should be a multiple of %d",
+ vma->vm_end - vma->vm_start,
+ FT_BUFF_SIZE);
+ }
+ num_buffers = (vma->vm_end - vma->vm_start) / FT_BUFF_SIZE;
+ if (num_buffers > ft_nr_buffers) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_err, "size = %ld, should be less than %d",
+ vma->vm_end - vma->vm_start,
+ ft_nr_buffers * FT_BUFF_SIZE);
+ }
+ if (ft_driver_state != idle) {
+ /* this also clears the buffer states
+ */
+ ftape_abort_operation();
+ } else {
+ ftape_reset_buffer();
+ }
+ for (i = 0; i < num_buffers; i++) {
+ unsigned long pfn;
+
+ pfn = virt_to_phys(ft_buffer[i]->address) >> PAGE_SHIFT;
+ TRACE_CATCH(remap_pfn_range(vma, vma->vm_start +
+ i * FT_BUFF_SIZE,
+ pfn,
+ FT_BUFF_SIZE,
+ vma->vm_page_prot),
+ _res = -EAGAIN);
+ TRACE(ft_t_noise, "remapped dma buffer @ %p to location @ %p",
+ ft_buffer[i]->address,
+ (void *)(vma->vm_start + i * FT_BUFF_SIZE));
+ }
+ for (i = 0; i < num_buffers; i++) {
+ memset(ft_buffer[i]->address, 0xAA, FT_BUFF_SIZE);
+ ft_buffer[i]->mmapped++;
+ }
+ TRACE_EXIT 0;
+}
+
+static void ftape_init_driver(void); /* forward declaration */
+
+/* OPEN routine called by kernel-interface code
+ */
+int ftape_enable(int drive_selection)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (ft_drive_sel == -1 || ft_drive_sel != drive_selection) {
+ /* Other selection than last time
+ */
+ ftape_init_driver();
+ }
+ ft_drive_sel = FTAPE_SEL(drive_selection);
+ ft_failure = 0;
+ TRACE_CATCH(fdc_init(),); /* init & detect fdc */
+ TRACE_CATCH(ftape_activate_drive(&ft_drive_type),
+ fdc_disable();
+ fdc_release_irq_and_dma();
+ fdc_release_regions());
+ TRACE_CATCH(ftape_get_drive_status(), ftape_detach_drive());
+ if (ft_drive_type.vendor_id == UNKNOWN_VENDOR) {
+ ftape_log_vendor_id();
+ }
+ if (ft_new_tape) {
+ ftape_init_drive_needed = 1;
+ }
+ if (!ft_no_tape && ftape_init_drive_needed) {
+ TRACE_CATCH(ftape_init_drive(), ftape_detach_drive());
+ }
+ ftape_munmap(); /* clear the mmap flag */
+ clear_history();
+ TRACE_EXIT 0;
+}
+
+/* release routine called by the high level interface modules
+ * zftape or sftape.
+ */
+void ftape_disable(void)
+{
+ int i;
+ TRACE_FUN(ft_t_any);
+
+ for (i = 0; i < ft_nr_buffers; i++) {
+ if (ft_buffer[i]->mmapped) {
+ TRACE(ft_t_noise, "first byte of buffer %d: 0x%02x",
+ i, *ft_buffer[i]->address);
+ }
+ }
+ if (sigtestsetmask(&current->pending.signal, _DONT_BLOCK) &&
+ !(sigtestsetmask(&current->pending.signal, _NEVER_BLOCK)) &&
+ ftape_tape_running) {
+ TRACE(ft_t_warn,
+ "Interrupted by fatal signal and tape still running");
+ ftape_dumb_stop();
+ ftape_abort_operation(); /* it's annoying */
+ } else {
+ ftape_set_state(idle);
+ }
+ ftape_detach_drive();
+ if (ft_history.used) {
+ TRACE(ft_t_info, "== Non-fatal errors this run: ==");
+ TRACE(ft_t_info, "fdc isr statistics:\n"
+ KERN_INFO " id_am_errors : %3d\n"
+ KERN_INFO " id_crc_errors : %3d\n"
+ KERN_INFO " data_am_errors : %3d\n"
+ KERN_INFO " data_crc_errors : %3d\n"
+ KERN_INFO " overrun_errors : %3d\n"
+ KERN_INFO " no_data_errors : %3d\n"
+ KERN_INFO " retries : %3d",
+ ft_history.id_am_errors, ft_history.id_crc_errors,
+ ft_history.data_am_errors, ft_history.data_crc_errors,
+ ft_history.overrun_errors, ft_history.no_data_errors,
+ ft_history.retries);
+ if (ft_history.used & 1) {
+ TRACE(ft_t_info, "ecc statistics:\n"
+ KERN_INFO " crc_errors : %3d\n"
+ KERN_INFO " crc_failures : %3d\n"
+ KERN_INFO " ecc_failures : %3d\n"
+ KERN_INFO " sectors corrected: %3d",
+ ft_history.crc_errors, ft_history.crc_failures,
+ ft_history.ecc_failures, ft_history.corrected);
+ }
+ if (ft_history.defects > 0) {
+ TRACE(ft_t_warn, "Warning: %d media defects!",
+ ft_history.defects);
+ }
+ if (ft_history.rewinds > 0) {
+ TRACE(ft_t_info, "tape motion statistics:\n"
+ KERN_INFO "repositions : %3d",
+ ft_history.rewinds);
+ }
+ }
+ ft_failure = 1;
+ TRACE_EXIT;
+}
+
+static void ftape_init_driver(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ ft_drive_type.vendor_id = UNKNOWN_VENDOR;
+ ft_drive_type.speed = 0;
+ ft_drive_type.wake_up = unknown_wake_up;
+ ft_drive_type.name = "Unknown";
+
+ ftape_timeout.seek = 650 * FT_SECOND;
+ ftape_timeout.reset = 670 * FT_SECOND;
+ ftape_timeout.rewind = 650 * FT_SECOND;
+ ftape_timeout.head_seek = 15 * FT_SECOND;
+ ftape_timeout.stop = 5 * FT_SECOND;
+ ftape_timeout.pause = 16 * FT_SECOND;
+
+ ft_qic_std = -1;
+ ftape_tape_len = 0; /* unknown */
+ ftape_current_command = 0;
+ ftape_current_cylinder = -1;
+
+ ft_segments_per_track = 102;
+ ftape_segments_per_head = 1020;
+ ftape_segments_per_cylinder = 4;
+ ft_tracks_per_tape = 20;
+
+ ft_failure = 1;
+
+ ft_formatted = 0;
+ ft_no_tape = 1;
+ ft_write_protected = 1;
+ ft_new_tape = 1;
+
+ ft_driver_state = idle;
+
+ ft_data_rate =
+ ft_fdc_max_rate = 500;
+ ft_drive_max_rate = 0; /* triggers set_rate_test() */
+
+ ftape_init_drive_needed = 1;
+
+ ft_header_segment_1 = -1;
+ ft_header_segment_2 = -1;
+ ft_used_header_segment = -1;
+ ft_first_data_segment = -1;
+ ft_last_data_segment = -1;
+
+ ft_location.track = -1;
+ ft_location.known = 0;
+
+ ftape_tape_running = 0;
+ ftape_might_be_off_track = 1;
+
+ ftape_new_cartridge(); /* init some tape related variables */
+ ftape_init_bsm();
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.h b/drivers/char/ftape/lowlevel/ftape-ctl.h
new file mode 100644
index 000000000000..5f5e30bc3615
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ctl.h
@@ -0,0 +1,162 @@
+#ifndef _FTAPE_CTL_H
+#define _FTAPE_CTL_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:09 $
+ *
+ * This file contains the non-standard IOCTL related definitions
+ * for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ * Linux.
+ */
+
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+#include <linux/ftape-vendors.h>
+
+#include "../lowlevel/ftape-rw.h"
+#include <linux/ftape-header-segment.h>
+
+typedef struct {
+ int used; /* any reading or writing done */
+ /* isr statistics */
+ unsigned int id_am_errors; /* id address mark not found */
+ unsigned int id_crc_errors; /* crc error in id address mark */
+ unsigned int data_am_errors; /* data address mark not found */
+ unsigned int data_crc_errors; /* crc error in data field */
+ unsigned int overrun_errors; /* fdc access timing problem */
+ unsigned int no_data_errors; /* sector not found */
+ unsigned int retries; /* number of tape retries */
+ /* ecc statistics */
+ unsigned int crc_errors; /* crc error in data */
+ unsigned int crc_failures; /* bad data without crc error */
+ unsigned int ecc_failures; /* failed to correct */
+ unsigned int corrected; /* total sectors corrected */
+ /* general statistics */
+ unsigned int rewinds; /* number of tape rewinds */
+ unsigned int defects; /* bad sectors due to media defects */
+} history_record;
+
+/* this structure contains * ALL * information that we want
+ * our child modules to know about, but don't want them to
+ * modify.
+ */
+typedef struct {
+ /* vendor information */
+ vendor_struct fti_drive_type;
+ /* data rates */
+ unsigned int fti_used_data_rate;
+ unsigned int fti_drive_max_rate;
+ unsigned int fti_fdc_max_rate;
+ /* drive selection, either FTAPE_SEL_A/B/C/D */
+ int fti_drive_sel;
+ /* flags set after decode the drive and tape status */
+ unsigned int fti_formatted :1;
+ unsigned int fti_no_tape :1;
+ unsigned int fti_write_protected:1;
+ unsigned int fti_new_tape :1;
+ /* values of last queried drive/tape status and error */
+ ft_drive_error fti_last_error;
+ ft_drive_status fti_last_status;
+ /* cartridge geometry */
+ unsigned int fti_tracks_per_tape;
+ unsigned int fti_segments_per_track;
+ /* location of header segments, etc. */
+ int fti_used_header_segment;
+ int fti_header_segment_1;
+ int fti_header_segment_2;
+ int fti_first_data_segment;
+ int fti_last_data_segment;
+ /* the format code as stored in the header segment */
+ ft_format_type fti_format_code;
+ /* the following is the sole reason for the ftape_set_status() call */
+ unsigned int fti_qic_std;
+ /* is tape running? */
+ volatile enum runner_status_enum fti_runner_status;
+ /* is tape reading/writing/verifying/formatting/deleting */
+ buffer_state_enum fti_state;
+ /* flags fatal hardware error */
+ unsigned int fti_failure:1;
+ /* history record */
+ history_record fti_history;
+} ftape_info;
+
+/* vendor information */
+#define ft_drive_type ftape_status.fti_drive_type
+/* data rates */
+#define ft_data_rate ftape_status.fti_used_data_rate
+#define ft_drive_max_rate ftape_status.fti_drive_max_rate
+#define ft_fdc_max_rate ftape_status.fti_fdc_max_rate
+/* drive selection, either FTAPE_SEL_A/B/C/D */
+#define ft_drive_sel ftape_status.fti_drive_sel
+/* flags set after decode the drive and tape status */
+#define ft_formatted ftape_status.fti_formatted
+#define ft_no_tape ftape_status.fti_no_tape
+#define ft_write_protected ftape_status.fti_write_protected
+#define ft_new_tape ftape_status.fti_new_tape
+/* values of last queried drive/tape status and error */
+#define ft_last_error ftape_status.fti_last_error
+#define ft_last_status ftape_status.fti_last_status
+/* cartridge geometry */
+#define ft_tracks_per_tape ftape_status.fti_tracks_per_tape
+#define ft_segments_per_track ftape_status.fti_segments_per_track
+/* the format code as stored in the header segment */
+#define ft_format_code ftape_status.fti_format_code
+/* the qic status as returned by report drive configuration */
+#define ft_qic_std ftape_status.fti_qic_std
+#define ft_used_header_segment ftape_status.fti_used_header_segment
+#define ft_header_segment_1 ftape_status.fti_header_segment_1
+#define ft_header_segment_2 ftape_status.fti_header_segment_2
+#define ft_first_data_segment ftape_status.fti_first_data_segment
+#define ft_last_data_segment ftape_status.fti_last_data_segment
+/* is tape running? */
+#define ft_runner_status ftape_status.fti_runner_status
+/* is tape reading/writing/verifying/formatting/deleting */
+#define ft_driver_state ftape_status.fti_state
+/* flags fatal hardware error */
+#define ft_failure ftape_status.fti_failure
+/* history record */
+#define ft_history ftape_status.fti_history
+
+/*
+ * ftape-ctl.c defined global vars.
+ */
+extern ftape_info ftape_status;
+extern int ftape_segments_per_head;
+extern int ftape_segments_per_cylinder;
+extern int ftape_init_drive_needed;
+
+/*
+ * ftape-ctl.c defined global functions.
+ */
+extern int ftape_mmap(struct vm_area_struct *vma);
+extern int ftape_enable(int drive_selection);
+extern void ftape_disable(void);
+extern int ftape_seek_to_bot(void);
+extern int ftape_seek_to_eot(void);
+extern int ftape_abort_operation(void);
+extern void ftape_calc_timeouts(unsigned int qic_std,
+ unsigned int data_rate,
+ unsigned int tape_len);
+extern int ftape_calibrate_data_rate(unsigned int qic_std);
+extern const ftape_info *ftape_get_status(void);
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.c b/drivers/char/ftape/lowlevel/ftape-ecc.c
new file mode 100644
index 000000000000..e5632f674bc8
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ecc.c
@@ -0,0 +1,853 @@
+/*
+ *
+ * Copyright (c) 1993 Ning and David Mosberger.
+
+ This is based on code originally written by Bas Laarhoven (bas@vimec.nl)
+ and David L. Brown, Jr., and incorporates improvements suggested by
+ Kai Harrekilde-Petersen.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:18:10 $
+ *
+ * This file contains the Reed-Solomon error correction code
+ * for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-ecc.h"
+
+/* Machines that are big-endian should define macro BIG_ENDIAN.
+ * Unfortunately, there doesn't appear to be a standard include file
+ * that works for all OSs.
+ */
+
+#if defined(__sparc__) || defined(__hppa)
+#define BIG_ENDIAN
+#endif /* __sparc__ || __hppa */
+
+#if defined(__mips__)
+#error Find a smart way to determine the Endianness of the MIPS CPU
+#endif
+
+/* Notice: to minimize the potential for confusion, we use r to
+ * denote the independent variable of the polynomials in the
+ * Galois Field GF(2^8). We reserve x for polynomials that
+ * that have coefficients in GF(2^8).
+ *
+ * The Galois Field in which coefficient arithmetic is performed are
+ * the polynomials over Z_2 (i.e., 0 and 1) modulo the irreducible
+ * polynomial f(r), where f(r)=r^8 + r^7 + r^2 + r + 1. A polynomial
+ * is represented as a byte with the MSB as the coefficient of r^7 and
+ * the LSB as the coefficient of r^0. For example, the binary
+ * representation of f(x) is 0x187 (of course, this doesn't fit into 8
+ * bits). In this field, the polynomial r is a primitive element.
+ * That is, r^i with i in 0,...,255 enumerates all elements in the
+ * field.
+ *
+ * The generator polynomial for the QIC-80 ECC is
+ *
+ * g(x) = x^3 + r^105*x^2 + r^105*x + 1
+ *
+ * which can be factored into:
+ *
+ * g(x) = (x-r^-1)(x-r^0)(x-r^1)
+ *
+ * the byte representation of the coefficients are:
+ *
+ * r^105 = 0xc0
+ * r^-1 = 0xc3
+ * r^0 = 0x01
+ * r^1 = 0x02
+ *
+ * Notice that r^-1 = r^254 as exponent arithmetic is performed
+ * modulo 2^8-1 = 255.
+ *
+ * For more information on Galois Fields and Reed-Solomon codes, refer
+ * to any good book. I found _An Introduction to Error Correcting
+ * Codes with Applications_ by S. A. Vanstone and P. C. van Oorschot
+ * to be a good introduction into the former. _CODING THEORY: The
+ * Essentials_ I found very useful for its concise description of
+ * Reed-Solomon encoding/decoding.
+ *
+ */
+
+typedef __u8 Matrix[3][3];
+
+/*
+ * gfpow[] is defined such that gfpow[i] returns r^i if
+ * i is in the range [0..255].
+ */
+static const __u8 gfpow[] =
+{
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+ 0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
+ 0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
+ 0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
+ 0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
+ 0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
+ 0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
+ 0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
+ 0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
+ 0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
+ 0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
+ 0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
+ 0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
+ 0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
+ 0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
+ 0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
+ 0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
+ 0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
+ 0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
+ 0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
+ 0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
+ 0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
+ 0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
+ 0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
+ 0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
+ 0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
+ 0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
+ 0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
+ 0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
+ 0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
+ 0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
+ 0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3, 0x01
+};
+
+/*
+ * This is a log table. That is, gflog[r^i] returns i (modulo f(r)).
+ * gflog[0] is undefined and the first element is therefore not valid.
+ */
+static const __u8 gflog[256] =
+{
+ 0xff, 0x00, 0x01, 0x63, 0x02, 0xc6, 0x64, 0x6a,
+ 0x03, 0xcd, 0xc7, 0xbc, 0x65, 0x7e, 0x6b, 0x2a,
+ 0x04, 0x8d, 0xce, 0x4e, 0xc8, 0xd4, 0xbd, 0xe1,
+ 0x66, 0xdd, 0x7f, 0x31, 0x6c, 0x20, 0x2b, 0xf3,
+ 0x05, 0x57, 0x8e, 0xe8, 0xcf, 0xac, 0x4f, 0x83,
+ 0xc9, 0xd9, 0xd5, 0x41, 0xbe, 0x94, 0xe2, 0xb4,
+ 0x67, 0x27, 0xde, 0xf0, 0x80, 0xb1, 0x32, 0x35,
+ 0x6d, 0x45, 0x21, 0x12, 0x2c, 0x0d, 0xf4, 0x38,
+ 0x06, 0x9b, 0x58, 0x1a, 0x8f, 0x79, 0xe9, 0x70,
+ 0xd0, 0xc2, 0xad, 0xa8, 0x50, 0x75, 0x84, 0x48,
+ 0xca, 0xfc, 0xda, 0x8a, 0xd6, 0x54, 0x42, 0x24,
+ 0xbf, 0x98, 0x95, 0xf9, 0xe3, 0x5e, 0xb5, 0x15,
+ 0x68, 0x61, 0x28, 0xba, 0xdf, 0x4c, 0xf1, 0x2f,
+ 0x81, 0xe6, 0xb2, 0x3f, 0x33, 0xee, 0x36, 0x10,
+ 0x6e, 0x18, 0x46, 0xa6, 0x22, 0x88, 0x13, 0xf7,
+ 0x2d, 0xb8, 0x0e, 0x3d, 0xf5, 0xa4, 0x39, 0x3b,
+ 0x07, 0x9e, 0x9c, 0x9d, 0x59, 0x9f, 0x1b, 0x08,
+ 0x90, 0x09, 0x7a, 0x1c, 0xea, 0xa0, 0x71, 0x5a,
+ 0xd1, 0x1d, 0xc3, 0x7b, 0xae, 0x0a, 0xa9, 0x91,
+ 0x51, 0x5b, 0x76, 0x72, 0x85, 0xa1, 0x49, 0xeb,
+ 0xcb, 0x7c, 0xfd, 0xc4, 0xdb, 0x1e, 0x8b, 0xd2,
+ 0xd7, 0x92, 0x55, 0xaa, 0x43, 0x0b, 0x25, 0xaf,
+ 0xc0, 0x73, 0x99, 0x77, 0x96, 0x5c, 0xfa, 0x52,
+ 0xe4, 0xec, 0x5f, 0x4a, 0xb6, 0xa2, 0x16, 0x86,
+ 0x69, 0xc5, 0x62, 0xfe, 0x29, 0x7d, 0xbb, 0xcc,
+ 0xe0, 0xd3, 0x4d, 0x8c, 0xf2, 0x1f, 0x30, 0xdc,
+ 0x82, 0xab, 0xe7, 0x56, 0xb3, 0x93, 0x40, 0xd8,
+ 0x34, 0xb0, 0xef, 0x26, 0x37, 0x0c, 0x11, 0x44,
+ 0x6f, 0x78, 0x19, 0x9a, 0x47, 0x74, 0xa7, 0xc1,
+ 0x23, 0x53, 0x89, 0xfb, 0x14, 0x5d, 0xf8, 0x97,
+ 0x2e, 0x4b, 0xb9, 0x60, 0x0f, 0xed, 0x3e, 0xe5,
+ 0xf6, 0x87, 0xa5, 0x17, 0x3a, 0xa3, 0x3c, 0xb7
+};
+
+/* This is a multiplication table for the factor 0xc0 (i.e., r^105 (mod f(r)).
+ * gfmul_c0[f] returns r^105 * f(r) (modulo f(r)).
+ */
+static const __u8 gfmul_c0[256] =
+{
+ 0x00, 0xc0, 0x07, 0xc7, 0x0e, 0xce, 0x09, 0xc9,
+ 0x1c, 0xdc, 0x1b, 0xdb, 0x12, 0xd2, 0x15, 0xd5,
+ 0x38, 0xf8, 0x3f, 0xff, 0x36, 0xf6, 0x31, 0xf1,
+ 0x24, 0xe4, 0x23, 0xe3, 0x2a, 0xea, 0x2d, 0xed,
+ 0x70, 0xb0, 0x77, 0xb7, 0x7e, 0xbe, 0x79, 0xb9,
+ 0x6c, 0xac, 0x6b, 0xab, 0x62, 0xa2, 0x65, 0xa5,
+ 0x48, 0x88, 0x4f, 0x8f, 0x46, 0x86, 0x41, 0x81,
+ 0x54, 0x94, 0x53, 0x93, 0x5a, 0x9a, 0x5d, 0x9d,
+ 0xe0, 0x20, 0xe7, 0x27, 0xee, 0x2e, 0xe9, 0x29,
+ 0xfc, 0x3c, 0xfb, 0x3b, 0xf2, 0x32, 0xf5, 0x35,
+ 0xd8, 0x18, 0xdf, 0x1f, 0xd6, 0x16, 0xd1, 0x11,
+ 0xc4, 0x04, 0xc3, 0x03, 0xca, 0x0a, 0xcd, 0x0d,
+ 0x90, 0x50, 0x97, 0x57, 0x9e, 0x5e, 0x99, 0x59,
+ 0x8c, 0x4c, 0x8b, 0x4b, 0x82, 0x42, 0x85, 0x45,
+ 0xa8, 0x68, 0xaf, 0x6f, 0xa6, 0x66, 0xa1, 0x61,
+ 0xb4, 0x74, 0xb3, 0x73, 0xba, 0x7a, 0xbd, 0x7d,
+ 0x47, 0x87, 0x40, 0x80, 0x49, 0x89, 0x4e, 0x8e,
+ 0x5b, 0x9b, 0x5c, 0x9c, 0x55, 0x95, 0x52, 0x92,
+ 0x7f, 0xbf, 0x78, 0xb8, 0x71, 0xb1, 0x76, 0xb6,
+ 0x63, 0xa3, 0x64, 0xa4, 0x6d, 0xad, 0x6a, 0xaa,
+ 0x37, 0xf7, 0x30, 0xf0, 0x39, 0xf9, 0x3e, 0xfe,
+ 0x2b, 0xeb, 0x2c, 0xec, 0x25, 0xe5, 0x22, 0xe2,
+ 0x0f, 0xcf, 0x08, 0xc8, 0x01, 0xc1, 0x06, 0xc6,
+ 0x13, 0xd3, 0x14, 0xd4, 0x1d, 0xdd, 0x1a, 0xda,
+ 0xa7, 0x67, 0xa0, 0x60, 0xa9, 0x69, 0xae, 0x6e,
+ 0xbb, 0x7b, 0xbc, 0x7c, 0xb5, 0x75, 0xb2, 0x72,
+ 0x9f, 0x5f, 0x98, 0x58, 0x91, 0x51, 0x96, 0x56,
+ 0x83, 0x43, 0x84, 0x44, 0x8d, 0x4d, 0x8a, 0x4a,
+ 0xd7, 0x17, 0xd0, 0x10, 0xd9, 0x19, 0xde, 0x1e,
+ 0xcb, 0x0b, 0xcc, 0x0c, 0xc5, 0x05, 0xc2, 0x02,
+ 0xef, 0x2f, 0xe8, 0x28, 0xe1, 0x21, 0xe6, 0x26,
+ 0xf3, 0x33, 0xf4, 0x34, 0xfd, 0x3d, 0xfa, 0x3a
+};
+
+
+/* Returns V modulo 255 provided V is in the range -255,-254,...,509.
+ */
+static inline __u8 mod255(int v)
+{
+ if (v > 0) {
+ if (v < 255) {
+ return v;
+ } else {
+ return v - 255;
+ }
+ } else {
+ return v + 255;
+ }
+}
+
+
+/* Add two numbers in the field. Addition in this field is equivalent
+ * to a bit-wise exclusive OR operation---subtraction is therefore
+ * identical to addition.
+ */
+static inline __u8 gfadd(__u8 a, __u8 b)
+{
+ return a ^ b;
+}
+
+
+/* Add two vectors of numbers in the field. Each byte in A and B gets
+ * added individually.
+ */
+static inline unsigned long gfadd_long(unsigned long a, unsigned long b)
+{
+ return a ^ b;
+}
+
+
+/* Multiply two numbers in the field:
+ */
+static inline __u8 gfmul(__u8 a, __u8 b)
+{
+ if (a && b) {
+ return gfpow[mod255(gflog[a] + gflog[b])];
+ } else {
+ return 0;
+ }
+}
+
+
+/* Just like gfmul, except we have already looked up the log of the
+ * second number.
+ */
+static inline __u8 gfmul_exp(__u8 a, int b)
+{
+ if (a) {
+ return gfpow[mod255(gflog[a] + b)];
+ } else {
+ return 0;
+ }
+}
+
+
+/* Just like gfmul_exp, except that A is a vector of numbers. That
+ * is, each byte in A gets multiplied by gfpow[mod255(B)].
+ */
+static inline unsigned long gfmul_exp_long(unsigned long a, int b)
+{
+ __u8 t;
+
+ if (sizeof(long) == 4) {
+ return (
+ ((t = (__u32)a >> 24 & 0xff) ?
+ (((__u32) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+ ((t = (__u32)a >> 16 & 0xff) ?
+ (((__u32) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+ ((t = (__u32)a >> 8 & 0xff) ?
+ (((__u32) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+ ((t = (__u32)a >> 0 & 0xff) ?
+ (((__u32) gfpow[mod255(gflog[t] + b)]) << 0) : 0));
+ } else if (sizeof(long) == 8) {
+ return (
+ ((t = (__u64)a >> 56 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 56) : 0) |
+ ((t = (__u64)a >> 48 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 48) : 0) |
+ ((t = (__u64)a >> 40 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 40) : 0) |
+ ((t = (__u64)a >> 32 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 32) : 0) |
+ ((t = (__u64)a >> 24 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+ ((t = (__u64)a >> 16 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+ ((t = (__u64)a >> 8 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+ ((t = (__u64)a >> 0 & 0xff) ?
+ (((__u64) gfpow[mod255(gflog[t] + b)]) << 0) : 0));
+ } else {
+ TRACE_FUN(ft_t_any);
+ TRACE_ABORT(-1, ft_t_err, "Error: size of long is %d bytes",
+ (int)sizeof(long));
+ }
+}
+
+
+/* Divide two numbers in the field. Returns a/b (modulo f(x)).
+ */
+static inline __u8 gfdiv(__u8 a, __u8 b)
+{
+ if (!b) {
+ TRACE_FUN(ft_t_any);
+ TRACE_ABORT(0xff, ft_t_bug, "Error: division by zero");
+ } else if (a == 0) {
+ return 0;
+ } else {
+ return gfpow[mod255(gflog[a] - gflog[b])];
+ }
+}
+
+
+/* The following functions return the inverse of the matrix of the
+ * linear system that needs to be solved to determine the error
+ * magnitudes. The first deals with matrices of rank 3, while the
+ * second deals with matrices of rank 2. The error indices are passed
+ * in arguments L0,..,L2 (0=first sector, 31=last sector). The error
+ * indices must be sorted in ascending order, i.e., L0<L1<L2.
+ *
+ * The linear system that needs to be solved for the error magnitudes
+ * is A * b = s, where s is the known vector of syndromes, b is the
+ * vector of error magnitudes and A in the ORDER=3 case:
+ *
+ * A_3 = {{1/r^L[0], 1/r^L[1], 1/r^L[2]},
+ * { 1, 1, 1},
+ * { r^L[0], r^L[1], r^L[2]}}
+ */
+static inline int gfinv3(__u8 l0,
+ __u8 l1,
+ __u8 l2,
+ Matrix Ainv)
+{
+ __u8 det;
+ __u8 t20, t10, t21, t12, t01, t02;
+ int log_det;
+
+ /* compute some intermediate results: */
+ t20 = gfpow[l2 - l0]; /* t20 = r^l2/r^l0 */
+ t10 = gfpow[l1 - l0]; /* t10 = r^l1/r^l0 */
+ t21 = gfpow[l2 - l1]; /* t21 = r^l2/r^l1 */
+ t12 = gfpow[l1 - l2 + 255]; /* t12 = r^l1/r^l2 */
+ t01 = gfpow[l0 - l1 + 255]; /* t01 = r^l0/r^l1 */
+ t02 = gfpow[l0 - l2 + 255]; /* t02 = r^l0/r^l2 */
+ /* Calculate the determinant of matrix A_3^-1 (sometimes
+ * called the Vandermonde determinant):
+ */
+ det = gfadd(t20, gfadd(t10, gfadd(t21, gfadd(t12, gfadd(t01, t02)))));
+ if (!det) {
+ TRACE_FUN(ft_t_any);
+ TRACE_ABORT(0, ft_t_err,
+ "Inversion failed (3 CRC errors, >0 CRC failures)");
+ }
+ log_det = 255 - gflog[det];
+
+ /* Now, calculate all of the coefficients:
+ */
+ Ainv[0][0]= gfmul_exp(gfadd(gfpow[l1], gfpow[l2]), log_det);
+ Ainv[0][1]= gfmul_exp(gfadd(t21, t12), log_det);
+ Ainv[0][2]= gfmul_exp(gfadd(gfpow[255 - l1], gfpow[255 - l2]),log_det);
+
+ Ainv[1][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l2]), log_det);
+ Ainv[1][1]= gfmul_exp(gfadd(t20, t02), log_det);
+ Ainv[1][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l2]),log_det);
+
+ Ainv[2][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l1]), log_det);
+ Ainv[2][1]= gfmul_exp(gfadd(t10, t01), log_det);
+ Ainv[2][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l1]),log_det);
+
+ return 1;
+}
+
+
+static inline int gfinv2(__u8 l0, __u8 l1, Matrix Ainv)
+{
+ __u8 det;
+ __u8 t1, t2;
+ int log_det;
+
+ t1 = gfpow[255 - l0];
+ t2 = gfpow[255 - l1];
+ det = gfadd(t1, t2);
+ if (!det) {
+ TRACE_FUN(ft_t_any);
+ TRACE_ABORT(0, ft_t_err,
+ "Inversion failed (2 CRC errors, >0 CRC failures)");
+ }
+ log_det = 255 - gflog[det];
+
+ /* Now, calculate all of the coefficients:
+ */
+ Ainv[0][0] = Ainv[1][0] = gfpow[log_det];
+
+ Ainv[0][1] = gfmul_exp(t2, log_det);
+ Ainv[1][1] = gfmul_exp(t1, log_det);
+
+ return 1;
+}
+
+
+/* Multiply matrix A by vector S and return result in vector B. M is
+ * assumed to be of order NxN, S and B of order Nx1.
+ */
+static inline void gfmat_mul(int n, Matrix A,
+ __u8 *s, __u8 *b)
+{
+ int i, j;
+ __u8 dot_prod;
+
+ for (i = 0; i < n; ++i) {
+ dot_prod = 0;
+ for (j = 0; j < n; ++j) {
+ dot_prod = gfadd(dot_prod, gfmul(A[i][j], s[j]));
+ }
+ b[i] = dot_prod;
+ }
+}
+
+
+
+/* The Reed Solomon ECC codes are computed over the N-th byte of each
+ * block, where N=SECTOR_SIZE. There are up to 29 blocks of data, and
+ * 3 blocks of ECC. The blocks are stored contiguously in memory. A
+ * segment, consequently, is assumed to have at least 4 blocks: one or
+ * more data blocks plus three ECC blocks.
+ *
+ * Notice: In QIC-80 speak, a CRC error is a sector with an incorrect
+ * CRC. A CRC failure is a sector with incorrect data, but
+ * a valid CRC. In the error control literature, the former
+ * is usually called "erasure", the latter "error."
+ */
+/* Compute the parity bytes for C columns of data, where C is the
+ * number of bytes that fit into a long integer. We use a linear
+ * feed-back register to do this. The parity bytes P[0], P[STRIDE],
+ * P[2*STRIDE] are computed such that:
+ *
+ * x^k * p(x) + m(x) = 0 (modulo g(x))
+ *
+ * where k = NBLOCKS,
+ * p(x) = P[0] + P[STRIDE]*x + P[2*STRIDE]*x^2, and
+ * m(x) = sum_{i=0}^k m_i*x^i.
+ * m_i = DATA[i*SECTOR_SIZE]
+ */
+static inline void set_parity(unsigned long *data,
+ int nblocks,
+ unsigned long *p,
+ int stride)
+{
+ unsigned long p0, p1, p2, t1, t2, *end;
+
+ end = data + nblocks * (FT_SECTOR_SIZE / sizeof(long));
+ p0 = p1 = p2 = 0;
+ while (data < end) {
+ /* The new parity bytes p0_i, p1_i, p2_i are computed
+ * from the old values p0_{i-1}, p1_{i-1}, p2_{i-1}
+ * recursively as:
+ *
+ * p0_i = p1_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+ * p1_i = p2_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+ * p2_i = (m_{i-1} - p0_{i-1})
+ *
+ * With the initial condition: p0_0 = p1_0 = p2_0 = 0.
+ */
+ t1 = gfadd_long(*data, p0);
+ /*
+ * Multiply each byte in t1 by 0xc0:
+ */
+ if (sizeof(long) == 4) {
+ t2= (((__u32) gfmul_c0[(__u32)t1 >> 24 & 0xff]) << 24 |
+ ((__u32) gfmul_c0[(__u32)t1 >> 16 & 0xff]) << 16 |
+ ((__u32) gfmul_c0[(__u32)t1 >> 8 & 0xff]) << 8 |
+ ((__u32) gfmul_c0[(__u32)t1 >> 0 & 0xff]) << 0);
+ } else if (sizeof(long) == 8) {
+ t2= (((__u64) gfmul_c0[(__u64)t1 >> 56 & 0xff]) << 56 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 48 & 0xff]) << 48 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 40 & 0xff]) << 40 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 32 & 0xff]) << 32 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 24 & 0xff]) << 24 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 16 & 0xff]) << 16 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 8 & 0xff]) << 8 |
+ ((__u64) gfmul_c0[(__u64)t1 >> 0 & 0xff]) << 0);
+ } else {
+ TRACE_FUN(ft_t_any);
+ TRACE(ft_t_err, "Error: long is of size %d",
+ (int) sizeof(long));
+ TRACE_EXIT;
+ }
+ p0 = gfadd_long(t2, p1);
+ p1 = gfadd_long(t2, p2);
+ p2 = t1;
+ data += FT_SECTOR_SIZE / sizeof(long);
+ }
+ *p = p0;
+ p += stride;
+ *p = p1;
+ p += stride;
+ *p = p2;
+ return;
+}
+
+
+/* Compute the 3 syndrome values. DATA should point to the first byte
+ * of the column for which the syndromes are desired. The syndromes
+ * are computed over the first NBLOCKS of rows. The three bytes will
+ * be placed in S[0], S[1], and S[2].
+ *
+ * S[i] is the value of the "message" polynomial m(x) evaluated at the
+ * i-th root of the generator polynomial g(x).
+ *
+ * As g(x)=(x-r^-1)(x-1)(x-r^1) we evaluate the message polynomial at
+ * x=r^-1 to get S[0], at x=r^0=1 to get S[1], and at x=r to get S[2].
+ * This could be done directly and efficiently via the Horner scheme.
+ * However, it would require multiplication tables for the factors
+ * r^-1 (0xc3) and r (0x02). The following scheme does not require
+ * any multiplication tables beyond what's needed for set_parity()
+ * anyway and is slightly faster if there are no errors and slightly
+ * slower if there are errors. The latter is hopefully the infrequent
+ * case.
+ *
+ * To understand the alternative algorithm, notice that set_parity(m,
+ * k, p) computes parity bytes such that:
+ *
+ * x^k * p(x) = m(x) (modulo g(x)).
+ *
+ * That is, to evaluate m(r^m), where r^m is a root of g(x), we can
+ * simply evaluate (r^m)^k*p(r^m). Also, notice that p is 0 if and
+ * only if s is zero. That is, if all parity bytes are 0, we know
+ * there is no error in the data and consequently there is no need to
+ * compute s(x) at all! In all other cases, we compute s(x) from p(x)
+ * by evaluating (r^m)^k*p(r^m) for m=-1, m=0, and m=1. The p(x)
+ * polynomial is evaluated via the Horner scheme.
+ */
+static int compute_syndromes(unsigned long *data, int nblocks, unsigned long *s)
+{
+ unsigned long p[3];
+
+ set_parity(data, nblocks, p, 1);
+ if (p[0] | p[1] | p[2]) {
+ /* Some of the checked columns do not have a zero
+ * syndrome. For simplicity, we compute the syndromes
+ * for all columns that we have computed the
+ * remainders for.
+ */
+ s[0] = gfmul_exp_long(
+ gfadd_long(p[0],
+ gfmul_exp_long(
+ gfadd_long(p[1],
+ gfmul_exp_long(p[2], -1)),
+ -1)),
+ -nblocks);
+ s[1] = gfadd_long(gfadd_long(p[2], p[1]), p[0]);
+ s[2] = gfmul_exp_long(
+ gfadd_long(p[0],
+ gfmul_exp_long(
+ gfadd_long(p[1],
+ gfmul_exp_long(p[2], 1)),
+ 1)),
+ nblocks);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+
+/* Correct the block in the column pointed to by DATA. There are NBAD
+ * CRC errors and their indices are in BAD_LOC[0], up to
+ * BAD_LOC[NBAD-1]. If NBAD>1, Ainv holds the inverse of the matrix
+ * of the linear system that needs to be solved to determine the error
+ * magnitudes. S[0], S[1], and S[2] are the syndrome values. If row
+ * j gets corrected, then bit j will be set in CORRECTION_MAP.
+ */
+static inline int correct_block(__u8 *data, int nblocks,
+ int nbad, int *bad_loc, Matrix Ainv,
+ __u8 *s,
+ SectorMap * correction_map)
+{
+ int ncorrected = 0;
+ int i;
+ __u8 t1, t2;
+ __u8 c0, c1, c2; /* check bytes */
+ __u8 error_mag[3], log_error_mag;
+ __u8 *dp, l, e;
+ TRACE_FUN(ft_t_any);
+
+ switch (nbad) {
+ case 0:
+ /* might have a CRC failure: */
+ if (s[0] == 0) {
+ /* more than one error */
+ TRACE_ABORT(-1, ft_t_err,
+ "ECC failed (0 CRC errors, >1 CRC failures)");
+ }
+ t1 = gfdiv(s[1], s[0]);
+ if ((bad_loc[nbad++] = gflog[t1]) >= nblocks) {
+ TRACE(ft_t_err,
+ "ECC failed (0 CRC errors, >1 CRC failures)");
+ TRACE_ABORT(-1, ft_t_err,
+ "attempt to correct data at %d", bad_loc[0]);
+ }
+ error_mag[0] = s[1];
+ break;
+ case 1:
+ t1 = gfadd(gfmul_exp(s[1], bad_loc[0]), s[2]);
+ t2 = gfadd(gfmul_exp(s[0], bad_loc[0]), s[1]);
+ if (t1 == 0 && t2 == 0) {
+ /* one erasure, no error: */
+ Ainv[0][0] = gfpow[bad_loc[0]];
+ } else if (t1 == 0 || t2 == 0) {
+ /* one erasure and more than one error: */
+ TRACE_ABORT(-1, ft_t_err,
+ "ECC failed (1 erasure, >1 error)");
+ } else {
+ /* one erasure, one error: */
+ if ((bad_loc[nbad++] = gflog[gfdiv(t1, t2)])
+ >= nblocks) {
+ TRACE(ft_t_err, "ECC failed "
+ "(1 CRC errors, >1 CRC failures)");
+ TRACE_ABORT(-1, ft_t_err,
+ "attempt to correct data at %d",
+ bad_loc[1]);
+ }
+ if (!gfinv2(bad_loc[0], bad_loc[1], Ainv)) {
+ /* inversion failed---must have more
+ * than one error
+ */
+ TRACE_EXIT -1;
+ }
+ }
+ /* FALL THROUGH TO ERROR MAGNITUDE COMPUTATION:
+ */
+ case 2:
+ case 3:
+ /* compute error magnitudes: */
+ gfmat_mul(nbad, Ainv, s, error_mag);
+ break;
+
+ default:
+ TRACE_ABORT(-1, ft_t_err,
+ "Internal Error: number of CRC errors > 3");
+ }
+
+ /* Perform correction by adding ERROR_MAG[i] to the byte at
+ * offset BAD_LOC[i]. Also add the value of the computed
+ * error polynomial to the syndrome values. If the correction
+ * was successful, the resulting check bytes should be zero
+ * (i.e., the corrected data is a valid code word).
+ */
+ c0 = s[0];
+ c1 = s[1];
+ c2 = s[2];
+ for (i = 0; i < nbad; ++i) {
+ e = error_mag[i];
+ if (e) {
+ /* correct the byte at offset L by magnitude E: */
+ l = bad_loc[i];
+ dp = &data[l * FT_SECTOR_SIZE];
+ *dp = gfadd(*dp, e);
+ *correction_map |= 1 << l;
+ ++ncorrected;
+
+ log_error_mag = gflog[e];
+ c0 = gfadd(c0, gfpow[mod255(log_error_mag - l)]);
+ c1 = gfadd(c1, e);
+ c2 = gfadd(c2, gfpow[mod255(log_error_mag + l)]);
+ }
+ }
+ if (c0 || c1 || c2) {
+ TRACE_ABORT(-1, ft_t_err,
+ "ECC self-check failed, too many errors");
+ }
+ TRACE_EXIT ncorrected;
+}
+
+
+#if defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID)
+
+/* Perform a sanity check on the computed parity bytes:
+ */
+static int sanity_check(unsigned long *data, int nblocks)
+{
+ TRACE_FUN(ft_t_any);
+ unsigned long s[3];
+
+ if (!compute_syndromes(data, nblocks, s)) {
+ TRACE_ABORT(0, ft_bug,
+ "Internal Error: syndrome self-check failed");
+ }
+ TRACE_EXIT 1;
+}
+
+#endif /* defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) */
+
+/* Compute the parity for an entire segment of data.
+ */
+int ftape_ecc_set_segment_parity(struct memory_segment *mseg)
+{
+ int i;
+ __u8 *parity_bytes;
+
+ parity_bytes = &mseg->data[(mseg->blocks - 3) * FT_SECTOR_SIZE];
+ for (i = 0; i < FT_SECTOR_SIZE; i += sizeof(long)) {
+ set_parity((unsigned long *) &mseg->data[i], mseg->blocks - 3,
+ (unsigned long *) &parity_bytes[i],
+ FT_SECTOR_SIZE / sizeof(long));
+#ifdef ECC_PARANOID
+ if (!sanity_check((unsigned long *) &mseg->data[i],
+ mseg->blocks)) {
+ return -1;
+ }
+#endif /* ECC_PARANOID */
+ }
+ return 0;
+}
+
+
+/* Checks and corrects (if possible) the segment MSEG. Returns one of
+ * ECC_OK, ECC_CORRECTED, and ECC_FAILED.
+ */
+int ftape_ecc_correct_data(struct memory_segment *mseg)
+{
+ int col, i, result;
+ int ncorrected = 0;
+ int nerasures = 0; /* # of erasures (CRC errors) */
+ int erasure_loc[3]; /* erasure locations */
+ unsigned long ss[3];
+ __u8 s[3];
+ Matrix Ainv;
+ TRACE_FUN(ft_t_flow);
+
+ mseg->corrected = 0;
+
+ /* find first column that has non-zero syndromes: */
+ for (col = 0; col < FT_SECTOR_SIZE; col += sizeof(long)) {
+ if (!compute_syndromes((unsigned long *) &mseg->data[col],
+ mseg->blocks, ss)) {
+ /* something is wrong---have to fix things */
+ break;
+ }
+ }
+ if (col >= FT_SECTOR_SIZE) {
+ /* all syndromes are ok, therefore nothing to correct */
+ TRACE_EXIT ECC_OK;
+ }
+ /* count the number of CRC errors if there were any: */
+ if (mseg->read_bad) {
+ for (i = 0; i < mseg->blocks; i++) {
+ if (BAD_CHECK(mseg->read_bad, i)) {
+ if (nerasures >= 3) {
+ /* this is too much for ECC */
+ TRACE_ABORT(ECC_FAILED, ft_t_err,
+ "ECC failed (>3 CRC errors)");
+ } /* if */
+ erasure_loc[nerasures++] = i;
+ }
+ }
+ }
+ /*
+ * If there are at least 2 CRC errors, determine inverse of matrix
+ * of linear system to be solved:
+ */
+ switch (nerasures) {
+ case 2:
+ if (!gfinv2(erasure_loc[0], erasure_loc[1], Ainv)) {
+ TRACE_EXIT ECC_FAILED;
+ }
+ break;
+ case 3:
+ if (!gfinv3(erasure_loc[0], erasure_loc[1],
+ erasure_loc[2], Ainv)) {
+ TRACE_EXIT ECC_FAILED;
+ }
+ break;
+ default:
+ /* this is not an error condition... */
+ break;
+ }
+
+ do {
+ for (i = 0; i < sizeof(long); ++i) {
+ s[0] = ss[0];
+ s[1] = ss[1];
+ s[2] = ss[2];
+ if (s[0] | s[1] | s[2]) {
+#ifdef BIG_ENDIAN
+ result = correct_block(
+ &mseg->data[col + sizeof(long) - 1 - i],
+ mseg->blocks,
+ nerasures,
+ erasure_loc,
+ Ainv,
+ s,
+ &mseg->corrected);
+#else
+ result = correct_block(&mseg->data[col + i],
+ mseg->blocks,
+ nerasures,
+ erasure_loc,
+ Ainv,
+ s,
+ &mseg->corrected);
+#endif
+ if (result < 0) {
+ TRACE_EXIT ECC_FAILED;
+ }
+ ncorrected += result;
+ }
+ ss[0] >>= 8;
+ ss[1] >>= 8;
+ ss[2] >>= 8;
+ }
+
+#ifdef ECC_SANITY_CHECK
+ if (!sanity_check((unsigned long *) &mseg->data[col],
+ mseg->blocks)) {
+ TRACE_EXIT ECC_FAILED;
+ }
+#endif /* ECC_SANITY_CHECK */
+
+ /* find next column with non-zero syndromes: */
+ while ((col += sizeof(long)) < FT_SECTOR_SIZE) {
+ if (!compute_syndromes((unsigned long *)
+ &mseg->data[col], mseg->blocks, ss)) {
+ /* something is wrong---have to fix things */
+ break;
+ }
+ }
+ } while (col < FT_SECTOR_SIZE);
+ if (ncorrected && nerasures == 0) {
+ TRACE(ft_t_warn, "block contained error not caught by CRC");
+ }
+ TRACE((ncorrected > 0) ? ft_t_noise : ft_t_any, "number of corrections: %d", ncorrected);
+ TRACE_EXIT ncorrected ? ECC_CORRECTED : ECC_OK;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.h b/drivers/char/ftape/lowlevel/ftape-ecc.h
new file mode 100644
index 000000000000..4829146fe9a0
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ecc.h
@@ -0,0 +1,84 @@
+#ifndef _FTAPE_ECC_H_
+#define _FTAPE_ECC_H_
+
+/*
+ * Copyright (C) 1993 Ning and David Mosberger.
+ * Original:
+ * Copyright (C) 1993 Bas Laarhoven.
+ * Copyright (C) 1992 David L. Brown, Jr.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:11 $
+ *
+ * This file contains the definitions for the
+ * Reed-Solomon error correction code
+ * for the QIC-40/80 tape streamer device driver.
+ */
+
+#include "../lowlevel/ftape-bsm.h"
+
+#define BAD_CLEAR(entry) ((entry)=0)
+#define BAD_SET(entry,sector) ((entry)|=(1<<(sector)))
+#define BAD_CHECK(entry,sector) ((entry)&(1<<(sector)))
+
+/*
+ * Return values for ecc_correct_data:
+ */
+enum {
+ ECC_OK, /* Data was correct. */
+ ECC_CORRECTED, /* Correctable error in data. */
+ ECC_FAILED, /* Could not correct data. */
+};
+
+/*
+ * Representation of an in memory segment. MARKED_BAD lists the
+ * sectors that were marked bad during formatting. If the N-th sector
+ * in a segment is marked bad, bit 1<<N will be set in MARKED_BAD.
+ * The sectors should be read in from the disk and packed, as if the
+ * bad sectors were not there, and the segment just contained fewer
+ * sectors. READ_SECTORS is a bitmap of errors encountered while
+ * reading the data. These offsets are relative to the packed data.
+ * BLOCKS is a count of the sectors not marked bad. This is just to
+ * prevent having to count the zero bits in MARKED_BAD each time this
+ * is needed. DATA is the actual sector packed data from (or to) the
+ * tape.
+ */
+ struct memory_segment {
+ SectorMap marked_bad;
+ SectorMap read_bad;
+ int blocks;
+ __u8 *data;
+ SectorMap corrected;
+ };
+
+/*
+ * ecc.c defined global variables:
+ */
+#ifdef TEST
+extern int ftape_ecc_tracing;
+#endif
+
+/*
+ * ecc.c defined global functions:
+ */
+extern int ftape_ecc_correct_data(struct memory_segment *data);
+extern int ftape_ecc_set_segment_parity(struct memory_segment *data);
+
+#endif /* _FTAPE_ECC_H_ */
diff --git a/drivers/char/ftape/lowlevel/ftape-format.c b/drivers/char/ftape/lowlevel/ftape-format.c
new file mode 100644
index 000000000000..5dd4c59a3f34
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-format.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.c,v $
+ * $Revision: 1.2.4.1 $
+ * $Date: 1997/11/14 16:05:39 $
+ *
+ * This file contains the code to support formatting of floppy
+ * tape cartridges with the QIC-40/80/3010/3020 floppy-tape
+ * driver "ftape" for Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-format.h"
+
+#if defined(TESTING)
+#define FT_FMT_SEGS_PER_BUF 50
+#else
+#define FT_FMT_SEGS_PER_BUF (FT_BUFF_SIZE/(4*FT_SECTORS_PER_SEGMENT))
+#endif
+
+static spinlock_t ftape_format_lock;
+
+/*
+ * first segment of the new buffer
+ */
+static int switch_segment;
+
+/*
+ * at most 256 segments fit into one 32 kb buffer. Even TR-1 cartridges have
+ * more than this many segments per track, so better be careful.
+ *
+ * buffer_struct *buff: buffer to store the formatting coordinates in
+ * int start: starting segment for this buffer.
+ * int spt: segments per track
+ *
+ * Note: segment ids are relative to the start of the track here.
+ */
+static void setup_format_buffer(buffer_struct *buff, int start, int spt,
+ __u8 gap3)
+{
+ int to_do = spt - start;
+ TRACE_FUN(ft_t_flow);
+
+ if (to_do > FT_FMT_SEGS_PER_BUF) {
+ to_do = FT_FMT_SEGS_PER_BUF;
+ }
+ buff->ptr = buff->address;
+ buff->remaining = to_do * FT_SECTORS_PER_SEGMENT; /* # sectors */
+ buff->bytes = buff->remaining * 4; /* need 4 bytes per sector */
+ buff->gap3 = gap3;
+ buff->segment_id = start;
+ buff->next_segment = start + to_do;
+ if (buff->next_segment >= spt) {
+ buff->next_segment = 0; /* 0 means: stop runner */
+ }
+ buff->status = waiting; /* tells the isr that it can use
+ * this buffer
+ */
+ TRACE_EXIT;
+}
+
+
+/*
+ * start formatting a new track.
+ */
+int ftape_format_track(const unsigned int track, const __u8 gap3)
+{
+ unsigned long flags;
+ buffer_struct *tail, *head;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+ if (track & 1) {
+ if (!(status & QIC_STATUS_AT_EOT)) {
+ TRACE_CATCH(ftape_seek_to_eot(),);
+ }
+ } else {
+ if (!(status & QIC_STATUS_AT_BOT)) {
+ TRACE_CATCH(ftape_seek_to_bot(),);
+ }
+ }
+ ftape_abort_operation(); /* this sets ft_head = ft_tail = 0 */
+ ftape_set_state(formatting);
+
+ TRACE(ft_t_noise,
+ "Formatting track %d, logical: from segment %d to %d",
+ track, track * ft_segments_per_track,
+ (track + 1) * ft_segments_per_track - 1);
+
+ /*
+ * initialize the buffer switching protocol for this track
+ */
+ head = ftape_get_buffer(ft_queue_head); /* tape isn't running yet */
+ tail = ftape_get_buffer(ft_queue_tail); /* tape isn't running yet */
+ switch_segment = 0;
+ do {
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ setup_format_buffer(tail, switch_segment,
+ ft_segments_per_track, gap3);
+ switch_segment = tail->next_segment;
+ } while ((switch_segment != 0) &&
+ ((tail = ftape_next_buffer(ft_queue_tail)) != head));
+ /* go */
+ head->status = formatting;
+ TRACE_CATCH(ftape_seek_head_to_track(track),);
+ TRACE_CATCH(ftape_command(QIC_LOGICAL_FORWARD),);
+ spin_lock_irqsave(&ftape_format_lock, flags);
+ TRACE_CATCH(fdc_setup_formatting(head), restore_flags(flags));
+ spin_unlock_irqrestore(&ftape_format_lock, flags);
+ TRACE_EXIT 0;
+}
+
+/* return segment id of segment currently being formatted and do the
+ * buffer switching stuff.
+ */
+int ftape_format_status(unsigned int *segment_id)
+{
+ buffer_struct *tail = ftape_get_buffer(ft_queue_tail);
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ while (switch_segment != 0 &&
+ ftape_get_buffer(ft_queue_head) != tail) {
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ /* need more buffers, first wait for empty buffer
+ */
+ TRACE_CATCH(ftape_wait_segment(formatting),);
+ /* don't worry for gap3. If we ever hit this piece of code,
+ * then all buffer already have the correct gap3 set!
+ */
+ setup_format_buffer(tail, switch_segment,
+ ft_segments_per_track, tail->gap3);
+ switch_segment = tail->next_segment;
+ if (switch_segment != 0) {
+ tail = ftape_next_buffer(ft_queue_tail);
+ }
+ }
+ /* should runner stop ?
+ */
+ if (ft_runner_status == aborting || ft_runner_status == do_abort) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+ TRACE(ft_t_warn, "Error formatting segment %d",
+ ftape_get_buffer(ft_queue_head)->segment_id);
+ (void)ftape_abort_operation();
+ TRACE_EXIT (head->status != error) ? -EAGAIN : -EIO;
+ }
+ /*
+ * don't care if the timer expires, this is just kind of a
+ * "select" operation that lets the calling process sleep
+ * until something has happened
+ */
+ if (fdc_interrupt_wait(5 * FT_SECOND) < 0) {
+ TRACE(ft_t_noise, "End of track %d at segment %d",
+ ft_location.track,
+ ftape_get_buffer(ft_queue_head)->segment_id);
+ result = 1; /* end of track, unlock module */
+ } else {
+ result = 0;
+ }
+ /*
+ * the calling process should use the seg id to determine
+ * which parts of the dma buffers can be safely overwritten
+ * with new data.
+ */
+ *segment_id = ftape_get_buffer(ft_queue_head)->segment_id;
+ /*
+ * Internally we start counting segment ids from the start of
+ * each track when formatting, but externally we keep them
+ * relative to the start of the tape:
+ */
+ *segment_id += ft_location.track * ft_segments_per_track;
+ TRACE_EXIT result;
+}
+
+/*
+ * The segment id is relative to the start of the tape
+ */
+int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm)
+{
+ int result;
+ int verify_done = 0;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Verifying segment %d", segment_id);
+
+ if (ft_driver_state != verifying) {
+ TRACE(ft_t_noise, "calling ftape_abort_operation");
+ if (ftape_abort_operation() < 0) {
+ TRACE(ft_t_err, "ftape_abort_operation failed");
+ TRACE_EXIT -EIO;
+ }
+ }
+ *bsm = 0x00000000;
+ ftape_set_state(verifying);
+ for (;;) {
+ buffer_struct *tail;
+ /*
+ * Allow escape from this loop on signal
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ /*
+ * Search all full buffers for the first matching the
+ * wanted segment. Clear other buffers on the fly.
+ */
+ tail = ftape_get_buffer(ft_queue_tail);
+ while (!verify_done && tail->status == done) {
+ /*
+ * Allow escape from this loop on signal !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ if (tail->segment_id == segment_id) {
+ /* If out buffer is already full,
+ * return its contents.
+ */
+ TRACE(ft_t_flow, "found segment in cache: %d",
+ segment_id);
+ if ((tail->soft_error_map |
+ tail->hard_error_map) != 0) {
+ TRACE(ft_t_info,"bsm[%d] = 0x%08lx",
+ segment_id,
+ (unsigned long)
+ (tail->soft_error_map |
+ tail->hard_error_map));
+ *bsm = (tail->soft_error_map |
+ tail->hard_error_map);
+ }
+ verify_done = 1;
+ } else {
+ TRACE(ft_t_flow,"zapping segment in cache: %d",
+ tail->segment_id);
+ }
+ tail->status = waiting;
+ tail = ftape_next_buffer(ft_queue_tail);
+ }
+ if (!verify_done && tail->status == verifying) {
+ if (tail->segment_id == segment_id) {
+ switch(ftape_wait_segment(verifying)) {
+ case 0:
+ break;
+ case -EINTR:
+ TRACE_ABORT(-EINTR, ft_t_warn,
+ "interrupted by "
+ "non-blockable signal");
+ break;
+ default:
+ ftape_abort_operation();
+ ftape_set_state(verifying);
+ /* be picky */
+ TRACE_ABORT(-EIO, ft_t_warn,
+ "wait_segment failed");
+ }
+ } else {
+ /* We're reading the wrong segment,
+ * stop runner.
+ */
+ TRACE(ft_t_noise, "verifying wrong segment");
+ ftape_abort_operation();
+ ftape_set_state(verifying);
+ }
+ }
+ /* should runner stop ?
+ */
+ if (ft_runner_status == aborting) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+ if (head->status == error ||
+ head->status == verifying) {
+ /* no data or overrun error */
+ head->status = waiting;
+ }
+ TRACE_CATCH(ftape_dumb_stop(),);
+ } else {
+ /* If just passed last segment on tape: wait
+ * for BOT or EOT mark. Sets ft_runner_status to
+ * idle if at lEOT and successful
+ */
+ TRACE_CATCH(ftape_handle_logical_eot(),);
+ }
+ if (verify_done) {
+ TRACE_EXIT 0;
+ }
+ /* Now at least one buffer is idle!
+ * Restart runner & tape if needed.
+ */
+ /* We could optimize the following a little bit. We know that
+ * the bad sector map is empty.
+ */
+ tail = ftape_get_buffer(ft_queue_tail);
+ if (tail->status == waiting) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+
+ ftape_setup_new_segment(head, segment_id, -1);
+ ftape_calc_next_cluster(head);
+ if (ft_runner_status == idle) {
+ result = ftape_start_tape(segment_id,
+ head->sector_offset);
+ switch(result) {
+ case 0:
+ break;
+ case -ETIME:
+ case -EINTR:
+ TRACE_ABORT(result, ft_t_err, "Error: "
+ "segment %d unreachable",
+ segment_id);
+ break;
+ default:
+ *bsm = EMPTY_SEGMENT;
+ TRACE_EXIT 0;
+ break;
+ }
+ }
+ head->status = verifying;
+ fdc_setup_read_write(head, FDC_VERIFY);
+ }
+ }
+ /* not reached */
+ TRACE_EXIT -EIO;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-format.h b/drivers/char/ftape/lowlevel/ftape-format.h
new file mode 100644
index 000000000000..f15161566643
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-format.h
@@ -0,0 +1,37 @@
+#ifndef _FTAPE_FORMAT_H
+#define _FTAPE_FORMAT_H
+
+/*
+ * Copyright (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:13 $
+ *
+ * This file contains the low level definitions for the
+ * formatting support for the QIC-40/80/3010/3020 floppy-tape
+ * driver "ftape" for Linux.
+ */
+
+#ifdef __KERNEL__
+extern int ftape_format_track(const unsigned int track, const __u8 gap3);
+extern int ftape_format_status(unsigned int *segment_id);
+extern int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm);
+#endif /* __KERNEL__ */
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-init.c b/drivers/char/ftape/lowlevel/ftape-init.c
new file mode 100644
index 000000000000..b54260d457c2
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-init.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * This file contains the code that interfaces the kernel
+ * for the QIC-40/80/3010/3020 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/major.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include <linux/qic117.h>
+#ifdef CONFIG_ZFTAPE
+#include <linux/zftape.h>
+#endif
+
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-proc.h"
+#include "../lowlevel/ftape-tracing.h"
+
+
+#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL)
+static int ft_tracing = -1;
+#endif
+
+
+/* Called by modules package when installing the driver
+ * or by kernel during the initialization phase
+ */
+static int __init ftape_init(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+#ifdef MODULE
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+ if (ft_tracing != -1) {
+ ftape_tracing = ft_tracing;
+ }
+#endif
+ printk(KERN_INFO FTAPE_VERSION "\n");
+ if (TRACE_LEVEL >= ft_t_info) {
+ printk(
+KERN_INFO "(c) 1993-1996 Bas Laarhoven (bas@vimec.nl)\n"
+KERN_INFO "(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no)\n"
+KERN_INFO "(c) 1996-1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO "QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives\n");
+ }
+#else /* !MODULE */
+ /* print a short no-nonsense boot message */
+ printk(KERN_INFO FTAPE_VERSION "\n");
+#endif /* MODULE */
+ TRACE(ft_t_info, "installing QIC-117 floppy tape hardware drive ... ");
+ TRACE(ft_t_info, "ftape_init @ 0x%p", ftape_init);
+ /* Allocate the DMA buffers. They are deallocated at cleanup() time.
+ */
+#ifdef TESTING
+#ifdef MODULE
+ while (ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS) < 0) {
+ ftape_sleep(FT_SECOND/20);
+ if (signal_pending(current)) {
+ (void)ftape_set_nr_buffers(0);
+ TRACE(ft_t_bug,
+ "Killed by signal while allocating buffers.");
+ TRACE_ABORT(-EINTR,
+ ft_t_bug, "Free up memory and retry");
+ }
+ }
+#else
+ TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS),
+ (void)ftape_set_nr_buffers(0));
+#endif
+#else
+ TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS),
+ (void)ftape_set_nr_buffers(0));
+#endif
+ ft_drive_sel = -1;
+ ft_failure = 1; /* inhibit any operation but open */
+ ftape_udelay_calibrate(); /* must be before fdc_wait_calibrate ! */
+ fdc_wait_calibrate();
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+ (void)ftape_proc_init();
+#endif
+#ifdef CONFIG_ZFTAPE
+ (void)zft_init();
+#endif
+ TRACE_EXIT 0;
+}
+
+module_param(ft_fdc_base, uint, 0);
+MODULE_PARM_DESC(ft_fdc_base, "Base address of FDC controller.");
+module_param(ft_fdc_irq, uint, 0);
+MODULE_PARM_DESC(ft_fdc_irq, "IRQ (interrupt channel) to use.");
+module_param(ft_fdc_dma, uint, 0);
+MODULE_PARM_DESC(ft_fdc_dma, "DMA channel to use.");
+module_param(ft_fdc_threshold, uint, 0);
+MODULE_PARM_DESC(ft_fdc_threshold, "Threshold of the FDC Fifo.");
+module_param(ft_fdc_rate_limit, uint, 0);
+MODULE_PARM_DESC(ft_fdc_rate_limit, "Maximal data rate for FDC.");
+module_param(ft_probe_fc10, bool, 0);
+MODULE_PARM_DESC(ft_probe_fc10,
+ "If non-zero, probe for a Colorado FC-10/FC-20 controller.");
+module_param(ft_mach2, bool, 0);
+MODULE_PARM_DESC(ft_mach2,
+ "If non-zero, probe for a Mountain MACH-2 controller.");
+#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL)
+module_param(ft_tracing, int, 0644);
+MODULE_PARM_DESC(ft_tracing,
+ "Amount of debugging output, 0 <= tracing <= 8, default 3.");
+#endif
+
+MODULE_AUTHOR(
+ "(c) 1993-1996 Bas Laarhoven (bas@vimec.nl), "
+ "(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no), "
+ "(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)");
+MODULE_DESCRIPTION(
+ "QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives.");
+MODULE_LICENSE("GPL");
+
+static void __exit ftape_exit(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+ ftape_proc_destroy();
+#endif
+ (void)ftape_set_nr_buffers(0);
+ printk(KERN_INFO "ftape: unloaded.\n");
+ TRACE_EXIT;
+}
+
+module_init(ftape_init);
+module_exit(ftape_exit);
diff --git a/drivers/char/ftape/lowlevel/ftape-init.h b/drivers/char/ftape/lowlevel/ftape-init.h
new file mode 100644
index 000000000000..99a7b8ab086f
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-init.h
@@ -0,0 +1,43 @@
+#ifndef _FTAPE_INIT_H
+#define _FTAPE_INIT_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-init.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:16 $
+ *
+ * This file contains the definitions for the interface to
+ * the Linux kernel for floppy tape driver ftape.
+ *
+ */
+
+#include <linux/linkage.h>
+#include <linux/signal.h>
+
+#define _NEVER_BLOCK (sigmask(SIGKILL) | sigmask(SIGSTOP))
+#define _DONT_BLOCK (_NEVER_BLOCK | sigmask(SIGINT))
+#define _DO_BLOCK (sigmask(SIGPIPE))
+
+#ifndef QIC117_TAPE_MAJOR
+#define QIC117_TAPE_MAJOR 27
+#endif
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-io.c b/drivers/char/ftape/lowlevel/ftape-io.c
new file mode 100644
index 000000000000..259015aeff55
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-io.c
@@ -0,0 +1,992 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996 Kai Harrekilde-Petersen,
+ * (C) 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/11/11 14:02:36 $
+ *
+ * This file contains the general control functions for the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <asm/system.h>
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+#include <linux/delay.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-calibr.h"
+
+/* Global vars.
+ */
+/* NOTE: sectors start numbering at 1, all others at 0 ! */
+ft_timeout_table ftape_timeout;
+unsigned int ftape_tape_len;
+volatile qic117_cmd_t ftape_current_command;
+const struct qic117_command_table qic117_cmds[] = QIC117_COMMANDS;
+int ftape_might_be_off_track;
+
+/* Local vars.
+ */
+static int diagnostic_mode;
+static unsigned int ftape_udelay_count;
+static unsigned int ftape_udelay_time;
+
+void ftape_udelay(unsigned int usecs)
+{
+ volatile int count = (ftape_udelay_count * usecs +
+ ftape_udelay_count - 1) / ftape_udelay_time;
+ volatile int i;
+
+ while (count-- > 0) {
+ for (i = 0; i < 20; ++i);
+ }
+}
+
+void ftape_udelay_calibrate(void)
+{
+ ftape_calibrate("ftape_udelay",
+ ftape_udelay, &ftape_udelay_count, &ftape_udelay_time);
+}
+
+/* Delay (msec) routine.
+ */
+void ftape_sleep(unsigned int time)
+{
+ TRACE_FUN(ft_t_any);
+
+ time *= 1000; /* msecs -> usecs */
+ if (time < FT_USPT) {
+ /* Time too small for scheduler, do a busy wait ! */
+ ftape_udelay(time);
+ } else {
+ long timeout;
+ unsigned long flags;
+ unsigned int ticks = (time + FT_USPT - 1) / FT_USPT;
+
+ TRACE(ft_t_any, "%d msec, %d ticks", time/1000, ticks);
+ timeout = ticks;
+ save_flags(flags);
+ sti();
+ msleep_interruptible(jiffies_to_msecs(timeout));
+ /* Mmm. Isn't current->blocked == 0xffffffff ?
+ */
+ if (signal_pending(current)) {
+ TRACE(ft_t_err, "awoken by non-blocked signal :-(");
+ }
+ restore_flags(flags);
+ }
+ TRACE_EXIT;
+}
+
+/* send a command or parameter to the drive
+ * Generates # of step pulses.
+ */
+static inline int ft_send_to_drive(int arg)
+{
+ /* Always wait for a command_timeout period to separate
+ * individuals commands and/or parameters.
+ */
+ ftape_sleep(3 * FT_MILLISECOND);
+ /* Keep cylinder nr within range, step towards home if possible.
+ */
+ if (ftape_current_cylinder >= arg) {
+ return fdc_seek(ftape_current_cylinder - arg);
+ } else {
+ return fdc_seek(ftape_current_cylinder + arg);
+ }
+}
+
+/* forward */ int ftape_report_raw_drive_status(int *status);
+
+static int ft_check_cmd_restrictions(qic117_cmd_t command)
+{
+ int status = -1;
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_flow, "%s", qic117_cmds[command].name);
+ /* A new motion command during an uninterruptible (motion)
+ * command requires a ready status before the new command can
+ * be issued. Otherwise a new motion command needs to be
+ * checked against required status.
+ */
+ if (qic117_cmds[command].cmd_type == motion &&
+ qic117_cmds[ftape_current_command].non_intr) {
+ ftape_report_raw_drive_status(&status);
+ if ((status & QIC_STATUS_READY) == 0) {
+ TRACE(ft_t_noise,
+ "motion cmd (%d) during non-intr cmd (%d)",
+ command, ftape_current_command);
+ TRACE(ft_t_noise, "waiting until drive gets ready");
+ ftape_ready_wait(ftape_timeout.seek,
+ &status);
+ }
+ }
+ if (qic117_cmds[command].mask != 0) {
+ __u8 difference;
+ /* Some commands do require a certain status:
+ */
+ if (status == -1) { /* not yet set */
+ ftape_report_raw_drive_status(&status);
+ }
+ difference = ((status ^ qic117_cmds[command].state) &
+ qic117_cmds[command].mask);
+ /* Wait until the drive gets
+ * ready. This may last forever if
+ * the drive never gets ready...
+ */
+ while ((difference & QIC_STATUS_READY) != 0) {
+ TRACE(ft_t_noise, "command %d issued while not ready",
+ command);
+ TRACE(ft_t_noise, "waiting until drive gets ready");
+ if (ftape_ready_wait(ftape_timeout.seek,
+ &status) == -EINTR) {
+ /* Bail out on signal !
+ */
+ TRACE_ABORT(-EINTR, ft_t_warn,
+ "interrupted by non-blockable signal");
+ }
+ difference = ((status ^ qic117_cmds[command].state) &
+ qic117_cmds[command].mask);
+ }
+ while ((difference & QIC_STATUS_ERROR) != 0) {
+ int err;
+ qic117_cmd_t cmd;
+
+ TRACE(ft_t_noise,
+ "command %d issued while error pending",
+ command);
+ TRACE(ft_t_noise, "clearing error status");
+ ftape_report_error(&err, &cmd, 1);
+ ftape_report_raw_drive_status(&status);
+ difference = ((status ^ qic117_cmds[command].state) &
+ qic117_cmds[command].mask);
+ if ((difference & QIC_STATUS_ERROR) != 0) {
+ /* Bail out on fatal signal !
+ */
+ FT_SIGNAL_EXIT(_NEVER_BLOCK);
+ }
+ }
+ if (difference) {
+ /* Any remaining difference can't be solved
+ * here.
+ */
+ if (difference & (QIC_STATUS_CARTRIDGE_PRESENT |
+ QIC_STATUS_NEW_CARTRIDGE |
+ QIC_STATUS_REFERENCED)) {
+ TRACE(ft_t_warn,
+ "Fatal: tape removed or reinserted !");
+ ft_failure = 1;
+ } else {
+ TRACE(ft_t_err, "wrong state: 0x%02x should be: 0x%02x",
+ status & qic117_cmds[command].mask,
+ qic117_cmds[command].state);
+ }
+ TRACE_EXIT -EIO;
+ }
+ if (~status & QIC_STATUS_READY & qic117_cmds[command].mask) {
+ TRACE_ABORT(-EBUSY, ft_t_err, "Bad: still busy!");
+ }
+ }
+ TRACE_EXIT 0;
+}
+
+/* Issue a tape command:
+ */
+int ftape_command(qic117_cmd_t command)
+{
+ int result = 0;
+ static int level;
+ TRACE_FUN(ft_t_any);
+
+ if ((unsigned int)command > NR_ITEMS(qic117_cmds)) {
+ /* This is a bug we'll want to know about too.
+ */
+ TRACE_ABORT(-EIO, ft_t_bug, "bug - bad command: %d", command);
+ }
+ if (++level > 5) { /* This is a bug we'll want to know about. */
+ --level;
+ TRACE_ABORT(-EIO, ft_t_bug, "bug - recursion for command: %d",
+ command);
+ }
+ /* disable logging and restriction check for some commands,
+ * check all other commands that have a prescribed starting
+ * status.
+ */
+ if (diagnostic_mode) {
+ TRACE(ft_t_flow, "diagnostic command %d", command);
+ } else if (command == QIC_REPORT_DRIVE_STATUS ||
+ command == QIC_REPORT_NEXT_BIT) {
+ TRACE(ft_t_any, "%s", qic117_cmds[command].name);
+ } else {
+ TRACE_CATCH(ft_check_cmd_restrictions(command), --level);
+ }
+ /* Now all conditions are met or result was < 0.
+ */
+ result = ft_send_to_drive((unsigned int)command);
+ if (qic117_cmds[command].cmd_type == motion &&
+ command != QIC_LOGICAL_FORWARD && command != QIC_STOP_TAPE) {
+ ft_location.known = 0;
+ }
+ ftape_current_command = command;
+ --level;
+ TRACE_EXIT result;
+}
+
+/* Send a tape command parameter:
+ * Generates command # of step pulses.
+ * Skips tape-status call !
+ */
+int ftape_parameter(unsigned int parameter)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE(ft_t_flow, "called with parameter = %d", parameter);
+ TRACE_EXIT ft_send_to_drive(parameter + 2);
+}
+
+/* Wait for the drive to get ready.
+ * timeout time in milli-seconds
+ * Returned status is valid if result != -EIO
+ *
+ * Should we allow to be killed by SIGINT? (^C)
+ * Would be nice at least for large timeouts.
+ */
+int ftape_ready_wait(unsigned int timeout, int *status)
+{
+ unsigned long t0;
+ unsigned int poll_delay;
+ int signal_retries;
+ TRACE_FUN(ft_t_any);
+
+ /* the following ** REALLY ** reduces the system load when
+ * e.g. one simply rewinds or retensions. The tape is slow
+ * anyway. It is really not necessary to detect error
+ * conditions with 1/10 seconds granularity
+ *
+ * On my AMD 133MHZ 486: 100 ms: 23% system load
+ * 1 sec: 5%
+ * 5 sec: 0.6%, yeah
+ */
+ if (timeout <= FT_SECOND) {
+ poll_delay = 100 * FT_MILLISECOND;
+ signal_retries = 20; /* two seconds */
+ } else if (timeout < 20 * FT_SECOND) {
+ TRACE(ft_t_flow, "setting poll delay to 1 second");
+ poll_delay = FT_SECOND;
+ signal_retries = 2; /* two seconds */
+ } else {
+ TRACE(ft_t_flow, "setting poll delay to 5 seconds");
+ poll_delay = 5 * FT_SECOND;
+ signal_retries = 1; /* five seconds */
+ }
+ for (;;) {
+ t0 = jiffies;
+ TRACE_CATCH(ftape_report_raw_drive_status(status),);
+ if (*status & QIC_STATUS_READY) {
+ TRACE_EXIT 0;
+ }
+ if (!signal_retries--) {
+ FT_SIGNAL_EXIT(_NEVER_BLOCK);
+ }
+ if ((int)timeout >= 0) {
+ /* this will fail when jiffies wraps around about
+ * once every year :-)
+ */
+ timeout -= ((jiffies - t0) * FT_SECOND) / HZ;
+ if (timeout <= 0) {
+ TRACE_ABORT(-ETIME, ft_t_err, "timeout");
+ }
+ ftape_sleep(poll_delay);
+ timeout -= poll_delay;
+ } else {
+ ftape_sleep(poll_delay);
+ }
+ }
+ TRACE_EXIT -ETIME;
+}
+
+/* Issue command and wait up to timeout milli seconds for drive ready
+ */
+int ftape_command_wait(qic117_cmd_t command, unsigned int timeout, int *status)
+{
+ int result;
+
+ /* Drive should be ready, issue command
+ */
+ result = ftape_command(command);
+ if (result >= 0) {
+ result = ftape_ready_wait(timeout, status);
+ }
+ return result;
+}
+
+static int ftape_parameter_wait(unsigned int parm, unsigned int timeout, int *status)
+{
+ int result;
+
+ /* Drive should be ready, issue command
+ */
+ result = ftape_parameter(parm);
+ if (result >= 0) {
+ result = ftape_ready_wait(timeout, status);
+ }
+ return result;
+}
+
+/*--------------------------------------------------------------------------
+ * Report operations
+ */
+
+/* Query the drive about its status. The command is sent and
+ result_length bits of status are returned (2 extra bits are read
+ for start and stop). */
+
+int ftape_report_operation(int *status,
+ qic117_cmd_t command,
+ int result_length)
+{
+ int i, st3;
+ unsigned int t0;
+ unsigned int dt;
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(ftape_command(command),);
+ t0 = ftape_timestamp();
+ i = 0;
+ do {
+ ++i;
+ ftape_sleep(3 * FT_MILLISECOND); /* see remark below */
+ TRACE_CATCH(fdc_sense_drive_status(&st3),);
+ dt = ftape_timediff(t0, ftape_timestamp());
+ /* Ack should be asserted within Ttimout + Tack = 6 msec.
+ * Looks like some drives fail to do this so extend this
+ * period to 300 msec.
+ */
+ } while (!(st3 & ST3_TRACK_0) && dt < 300000);
+ if (!(st3 & ST3_TRACK_0)) {
+ TRACE(ft_t_err,
+ "No acknowledge after %u msec. (%i iter)", dt / 1000, i);
+ TRACE_ABORT(-EIO, ft_t_err, "timeout on Acknowledge");
+ }
+ /* dt may be larger than expected because of other tasks
+ * scheduled while we were sleeping.
+ */
+ if (i > 1 && dt > 6000) {
+ TRACE(ft_t_err, "Acknowledge after %u msec. (%i iter)",
+ dt / 1000, i);
+ }
+ *status = 0;
+ for (i = 0; i < result_length + 1; i++) {
+ TRACE_CATCH(ftape_command(QIC_REPORT_NEXT_BIT),);
+ TRACE_CATCH(fdc_sense_drive_status(&st3),);
+ if (i < result_length) {
+ *status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i;
+ } else if ((st3 & ST3_TRACK_0) == 0) {
+ TRACE_ABORT(-EIO, ft_t_err, "missing status stop bit");
+ }
+ }
+ /* this command will put track zero and index back into normal state */
+ (void)ftape_command(QIC_REPORT_NEXT_BIT);
+ TRACE_EXIT 0;
+}
+
+/* Report the current drive status. */
+
+int ftape_report_raw_drive_status(int *status)
+{
+ int result;
+ int count = 0;
+ TRACE_FUN(ft_t_any);
+
+ do {
+ result = ftape_report_operation(status,
+ QIC_REPORT_DRIVE_STATUS, 8);
+ } while (result < 0 && ++count <= 3);
+ if (result < 0) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "report_operation failed after %d trials", count);
+ }
+ if ((*status & 0xff) == 0xff) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "impossible drive status 0xff");
+ }
+ if (*status & QIC_STATUS_READY) {
+ ftape_current_command = QIC_NO_COMMAND; /* completed */
+ }
+ ft_last_status.status.drive_status = (__u8)(*status & 0xff);
+ TRACE_EXIT 0;
+}
+
+int ftape_report_drive_status(int *status)
+{
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(ftape_report_raw_drive_status(status),);
+ if (*status & QIC_STATUS_NEW_CARTRIDGE ||
+ !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) {
+ ft_failure = 1; /* will inhibit further operations */
+ TRACE_EXIT -EIO;
+ }
+ if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) {
+ /* Let caller handle all errors */
+ TRACE_ABORT(1, ft_t_warn, "warning: error status set!");
+ }
+ TRACE_EXIT 0;
+}
+
+int ftape_report_error(unsigned int *error,
+ qic117_cmd_t *command, int report)
+{
+ static const ftape_error ftape_errors[] = QIC117_ERRORS;
+ int code;
+ TRACE_FUN(ft_t_any);
+
+ TRACE_CATCH(ftape_report_operation(&code, QIC_REPORT_ERROR_CODE, 16),);
+ *error = (unsigned int)(code & 0xff);
+ *command = (qic117_cmd_t)((code>>8)&0xff);
+ /* remember hardware status, maybe useful for status ioctls
+ */
+ ft_last_error.error.command = (__u8)*command;
+ ft_last_error.error.error = (__u8)*error;
+ if (!report) {
+ TRACE_EXIT 0;
+ }
+ if (*error == 0) {
+ TRACE_ABORT(0, ft_t_info, "No error");
+ }
+ TRACE(ft_t_info, "errorcode: %d", *error);
+ if (*error < NR_ITEMS(ftape_errors)) {
+ TRACE(ft_t_noise, "%sFatal ERROR:",
+ (ftape_errors[*error].fatal ? "" : "Non-"));
+ TRACE(ft_t_noise, "%s ...", ftape_errors[*error].message);
+ } else {
+ TRACE(ft_t_noise, "Unknown ERROR !");
+ }
+ if ((unsigned int)*command < NR_ITEMS(qic117_cmds) &&
+ qic117_cmds[*command].name != NULL) {
+ TRACE(ft_t_noise, "... caused by command \'%s\'",
+ qic117_cmds[*command].name);
+ } else {
+ TRACE(ft_t_noise, "... caused by unknown command %d",
+ *command);
+ }
+ TRACE_EXIT 0;
+}
+
+int ftape_report_configuration(qic_model *model,
+ unsigned int *rate,
+ int *qic_std,
+ int *tape_len)
+{
+ int result;
+ int config;
+ int status;
+ static const unsigned int qic_rates[ 4] = { 250, 2000, 500, 1000 };
+ TRACE_FUN(ft_t_any);
+
+ result = ftape_report_operation(&config,
+ QIC_REPORT_DRIVE_CONFIGURATION, 8);
+ if (result < 0) {
+ ft_last_status.status.drive_config = (__u8)0x00;
+ *model = prehistoric;
+ *rate = 500;
+ *qic_std = QIC_TAPE_QIC40;
+ *tape_len = 205;
+ TRACE_EXIT 0;
+ } else {
+ ft_last_status.status.drive_config = (__u8)(config & 0xff);
+ }
+ *rate = qic_rates[(config & QIC_CONFIG_RATE_MASK) >> QIC_CONFIG_RATE_SHIFT];
+ result = ftape_report_operation(&status, QIC_REPORT_TAPE_STATUS, 8);
+ if (result < 0) {
+ ft_last_status.status.tape_status = (__u8)0x00;
+ /* pre- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is valid.
+ */
+ *qic_std = (config & QIC_CONFIG_80) ?
+ QIC_TAPE_QIC80 : QIC_TAPE_QIC40;
+ /* ?? how's about 425ft tapes? */
+ *tape_len = (config & QIC_CONFIG_LONG) ? 307 : 0;
+ *model = pre_qic117c;
+ result = 0;
+ } else {
+ ft_last_status.status.tape_status = (__u8)(status & 0xff);
+ *model = post_qic117b;
+ TRACE(ft_t_any, "report tape status result = %02x", status);
+ /* post- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is
+ * invalid.
+ */
+ switch (status & QIC_TAPE_STD_MASK) {
+ case QIC_TAPE_QIC40:
+ case QIC_TAPE_QIC80:
+ case QIC_TAPE_QIC3020:
+ case QIC_TAPE_QIC3010:
+ *qic_std = status & QIC_TAPE_STD_MASK;
+ break;
+ default:
+ *qic_std = -1;
+ break;
+ }
+ switch (status & QIC_TAPE_LEN_MASK) {
+ case QIC_TAPE_205FT:
+ /* 205 or 425+ ft 550 Oe tape */
+ *tape_len = 0;
+ break;
+ case QIC_TAPE_307FT:
+ /* 307.5 ft 550 Oe Extended Length (XL) tape */
+ *tape_len = 307;
+ break;
+ case QIC_TAPE_VARIABLE:
+ /* Variable length 550 Oe tape */
+ *tape_len = 0;
+ break;
+ case QIC_TAPE_1100FT:
+ /* 1100 ft 550 Oe tape */
+ *tape_len = 1100;
+ break;
+ case QIC_TAPE_FLEX:
+ /* Variable length 900 Oe tape */
+ *tape_len = 0;
+ break;
+ default:
+ *tape_len = -1;
+ break;
+ }
+ if (*qic_std == -1 || *tape_len == -1) {
+ TRACE(ft_t_any,
+ "post qic-117b spec drive with unknown tape");
+ }
+ result = *tape_len == -1 ? -EIO : 0;
+ if (status & QIC_TAPE_WIDE) {
+ switch (*qic_std) {
+ case QIC_TAPE_QIC80:
+ TRACE(ft_t_info, "TR-1 tape detected");
+ break;
+ case QIC_TAPE_QIC3010:
+ TRACE(ft_t_info, "TR-2 tape detected");
+ break;
+ case QIC_TAPE_QIC3020:
+ TRACE(ft_t_info, "TR-3 tape detected");
+ break;
+ default:
+ TRACE(ft_t_warn,
+ "Unknown Travan tape type detected");
+ break;
+ }
+ }
+ }
+ TRACE_EXIT (result < 0) ? -EIO : 0;
+}
+
+static int ftape_report_rom_version(int *version)
+{
+
+ if (ftape_report_operation(version, QIC_REPORT_ROM_VERSION, 8) < 0) {
+ return -EIO;
+ } else {
+ return 0;
+ }
+}
+
+void ftape_report_vendor_id(unsigned int *id)
+{
+ int result;
+ TRACE_FUN(ft_t_any);
+
+ /* We'll try to get a vendor id from the drive. First
+ * according to the QIC-117 spec, a 16-bit id is requested.
+ * If that fails we'll try an 8-bit version, otherwise we'll
+ * try an undocumented query.
+ */
+ result = ftape_report_operation((int *) id, QIC_REPORT_VENDOR_ID, 16);
+ if (result < 0) {
+ result = ftape_report_operation((int *) id,
+ QIC_REPORT_VENDOR_ID, 8);
+ if (result < 0) {
+ /* The following is an undocumented call found
+ * in the CMS code.
+ */
+ result = ftape_report_operation((int *) id, 24, 8);
+ if (result < 0) {
+ *id = UNKNOWN_VENDOR;
+ } else {
+ TRACE(ft_t_noise, "got old 8 bit id: %04x",
+ *id);
+ *id |= 0x20000;
+ }
+ } else {
+ TRACE(ft_t_noise, "got 8 bit id: %04x", *id);
+ *id |= 0x10000;
+ }
+ } else {
+ TRACE(ft_t_noise, "got 16 bit id: %04x", *id);
+ }
+ if (*id == 0x0047) {
+ int version;
+ int sign;
+
+ if (ftape_report_rom_version(&version) < 0) {
+ TRACE(ft_t_bug, "report rom version failed");
+ TRACE_EXIT;
+ }
+ TRACE(ft_t_noise, "CMS rom version: %d", version);
+ ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+ ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+ diagnostic_mode = 1;
+ if (ftape_report_operation(&sign, 9, 8) < 0) {
+ unsigned int error;
+ qic117_cmd_t command;
+
+ ftape_report_error(&error, &command, 1);
+ ftape_command(QIC_ENTER_PRIMARY_MODE);
+ diagnostic_mode = 0;
+ TRACE_EXIT; /* failure ! */
+ } else {
+ TRACE(ft_t_noise, "CMS signature: %02x", sign);
+ }
+ if (sign == 0xa5) {
+ result = ftape_report_operation(&sign, 37, 8);
+ if (result < 0) {
+ if (version >= 63) {
+ *id = 0x8880;
+ TRACE(ft_t_noise,
+ "This is an Iomega drive !");
+ } else {
+ *id = 0x0047;
+ TRACE(ft_t_noise,
+ "This is a real CMS drive !");
+ }
+ } else {
+ *id = 0x0047;
+ TRACE(ft_t_noise, "CMS status: %d", sign);
+ }
+ } else {
+ *id = UNKNOWN_VENDOR;
+ }
+ ftape_command(QIC_ENTER_PRIMARY_MODE);
+ diagnostic_mode = 0;
+ }
+ TRACE_EXIT;
+}
+
+static int qic_rate_code(unsigned int rate)
+{
+ switch (rate) {
+ case 250:
+ return QIC_CONFIG_RATE_250;
+ case 500:
+ return QIC_CONFIG_RATE_500;
+ case 1000:
+ return QIC_CONFIG_RATE_1000;
+ case 2000:
+ return QIC_CONFIG_RATE_2000;
+ default:
+ return QIC_CONFIG_RATE_500;
+ }
+}
+
+static int ftape_set_rate_test(unsigned int *max_rate)
+{
+ unsigned int error;
+ qic117_cmd_t command;
+ int status;
+ int supported = 0;
+ TRACE_FUN(ft_t_any);
+
+ /* Check if the drive does support the select rate command
+ * by testing all different settings. If any one is accepted
+ * we assume the command is supported, else not.
+ */
+ for (*max_rate = 2000; *max_rate >= 250; *max_rate /= 2) {
+ if (ftape_command(QIC_SELECT_RATE) < 0) {
+ continue;
+ }
+ if (ftape_parameter_wait(qic_rate_code(*max_rate),
+ 1 * FT_SECOND, &status) < 0) {
+ continue;
+ }
+ if (status & QIC_STATUS_ERROR) {
+ ftape_report_error(&error, &command, 0);
+ continue;
+ }
+ supported = 1; /* did accept a request */
+ break;
+ }
+ TRACE(ft_t_noise, "Select Rate command is%s supported",
+ supported ? "" : " not");
+ TRACE_EXIT supported;
+}
+
+int ftape_set_data_rate(unsigned int new_rate /* Kbps */, unsigned int qic_std)
+{
+ int status;
+ int result = 0;
+ unsigned int data_rate = new_rate;
+ static int supported;
+ int rate_changed = 0;
+ qic_model dummy_model;
+ unsigned int dummy_qic_std, dummy_tape_len;
+ TRACE_FUN(ft_t_any);
+
+ if (ft_drive_max_rate == 0) { /* first time */
+ supported = ftape_set_rate_test(&ft_drive_max_rate);
+ }
+ if (supported) {
+ ftape_command(QIC_SELECT_RATE);
+ result = ftape_parameter_wait(qic_rate_code(new_rate),
+ 1 * FT_SECOND, &status);
+ if (result >= 0 && !(status & QIC_STATUS_ERROR)) {
+ rate_changed = 1;
+ }
+ }
+ TRACE_CATCH(result = ftape_report_configuration(&dummy_model,
+ &data_rate,
+ &dummy_qic_std,
+ &dummy_tape_len),);
+ if (data_rate != new_rate) {
+ if (!supported) {
+ TRACE(ft_t_warn, "Rate change not supported!");
+ } else if (rate_changed) {
+ TRACE(ft_t_warn, "Requested: %d, got %d",
+ new_rate, data_rate);
+ } else {
+ TRACE(ft_t_warn, "Rate change failed!");
+ }
+ result = -EINVAL;
+ }
+ /*
+ * Set data rate and write precompensation as specified:
+ *
+ * | QIC-40/80 | QIC-3010/3020
+ * rate | precomp | precomp
+ * ----------+-------------+--------------
+ * 250 Kbps. | 250 ns. | 0 ns.
+ * 500 Kbps. | 125 ns. | 0 ns.
+ * 1 Mbps. | 42 ns. | 0 ns.
+ * 2 Mbps | N/A | 0 ns.
+ */
+ if ((qic_std == QIC_TAPE_QIC40 && data_rate > 500) ||
+ (qic_std == QIC_TAPE_QIC80 && data_rate > 1000)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_warn, "Datarate too high for QIC-mode");
+ }
+ TRACE_CATCH(fdc_set_data_rate(data_rate),_res = -EINVAL);
+ ft_data_rate = data_rate;
+ if (qic_std == QIC_TAPE_QIC40 || qic_std == QIC_TAPE_QIC80) {
+ switch (data_rate) {
+ case 250:
+ fdc_set_write_precomp(250);
+ break;
+ default:
+ case 500:
+ fdc_set_write_precomp(125);
+ break;
+ case 1000:
+ fdc_set_write_precomp(42);
+ break;
+ }
+ } else {
+ fdc_set_write_precomp(0);
+ }
+ TRACE_EXIT result;
+}
+
+/* The next two functions are used to cope with excessive overrun errors
+ */
+int ftape_increase_threshold(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (fdc.type < i82077 || ft_fdc_threshold >= 12) {
+ TRACE_ABORT(-EIO, ft_t_err, "cannot increase fifo threshold");
+ }
+ if (fdc_fifo_threshold(++ft_fdc_threshold, NULL, NULL, NULL) < 0) {
+ TRACE(ft_t_err, "cannot increase fifo threshold");
+ ft_fdc_threshold --;
+ fdc_reset();
+ }
+ TRACE(ft_t_info, "New FIFO threshold: %d", ft_fdc_threshold);
+ TRACE_EXIT 0;
+}
+
+int ftape_half_data_rate(void)
+{
+ if (ft_data_rate < 500) {
+ return -1;
+ }
+ if (ftape_set_data_rate(ft_data_rate / 2, ft_qic_std) < 0) {
+ return -EIO;
+ }
+ ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+ return 0;
+}
+
+/* Seek the head to the specified track.
+ */
+int ftape_seek_head_to_track(unsigned int track)
+{
+ int status;
+ TRACE_FUN(ft_t_any);
+
+ ft_location.track = -1; /* remains set in case of error */
+ if (track >= ft_tracks_per_tape) {
+ TRACE_ABORT(-EINVAL, ft_t_bug, "track out of bounds");
+ }
+ TRACE(ft_t_flow, "seeking track %d", track);
+ TRACE_CATCH(ftape_command(QIC_SEEK_HEAD_TO_TRACK),);
+ TRACE_CATCH(ftape_parameter_wait(track, ftape_timeout.head_seek,
+ &status),);
+ ft_location.track = track;
+ ftape_might_be_off_track = 0;
+ TRACE_EXIT 0;
+}
+
+int ftape_wakeup_drive(wake_up_types method)
+{
+ int status;
+ int motor_on = 0;
+ TRACE_FUN(ft_t_any);
+
+ switch (method) {
+ case wake_up_colorado:
+ TRACE_CATCH(ftape_command(QIC_PHANTOM_SELECT),);
+ TRACE_CATCH(ftape_parameter(0 /* ft_drive_sel ?? */),);
+ break;
+ case wake_up_mountain:
+ TRACE_CATCH(ftape_command(QIC_SOFT_SELECT),);
+ ftape_sleep(FT_MILLISECOND); /* NEEDED */
+ TRACE_CATCH(ftape_parameter(18),);
+ break;
+ case wake_up_insight:
+ ftape_sleep(100 * FT_MILLISECOND);
+ motor_on = 1;
+ fdc_motor(motor_on); /* enable is done by motor-on */
+ case no_wake_up:
+ break;
+ default:
+ TRACE_EXIT -ENODEV; /* unknown wakeup method */
+ break;
+ }
+ /* If wakeup succeeded we shouldn't get an error here..
+ */
+ TRACE_CATCH(ftape_report_raw_drive_status(&status),
+ if (motor_on) {
+ fdc_motor(0);
+ });
+ TRACE_EXIT 0;
+}
+
+int ftape_put_drive_to_sleep(wake_up_types method)
+{
+ TRACE_FUN(ft_t_any);
+
+ switch (method) {
+ case wake_up_colorado:
+ TRACE_CATCH(ftape_command(QIC_PHANTOM_DESELECT),);
+ break;
+ case wake_up_mountain:
+ TRACE_CATCH(ftape_command(QIC_SOFT_DESELECT),);
+ break;
+ case wake_up_insight:
+ fdc_motor(0); /* enable is done by motor-on */
+ case no_wake_up: /* no wakeup / no sleep ! */
+ break;
+ default:
+ TRACE_EXIT -ENODEV; /* unknown wakeup method */
+ }
+ TRACE_EXIT 0;
+}
+
+int ftape_reset_drive(void)
+{
+ int result = 0;
+ int status;
+ unsigned int err_code;
+ qic117_cmd_t err_command;
+ int i;
+ TRACE_FUN(ft_t_any);
+
+ /* We want to re-establish contact with our drive. Fire a
+ * number of reset commands (single step pulses) and pray for
+ * success.
+ */
+ for (i = 0; i < 2; ++i) {
+ TRACE(ft_t_flow, "Resetting fdc");
+ fdc_reset();
+ ftape_sleep(10 * FT_MILLISECOND);
+ TRACE(ft_t_flow, "Reset command to drive");
+ result = ftape_command(QIC_RESET);
+ if (result == 0) {
+ ftape_sleep(1 * FT_SECOND); /* drive not
+ * accessible
+ * during 1 second
+ */
+ TRACE(ft_t_flow, "Re-selecting drive");
+
+ /* Strange, the QIC-117 specs don't mention
+ * this but the drive gets deselected after a
+ * soft reset ! So we need to enable it
+ * again.
+ */
+ if (ftape_wakeup_drive(ft_drive_type.wake_up) < 0) {
+ TRACE(ft_t_err, "Wakeup failed !");
+ }
+ TRACE(ft_t_flow, "Waiting until drive gets ready");
+ result= ftape_ready_wait(ftape_timeout.reset, &status);
+ if (result == 0 && (status & QIC_STATUS_ERROR)) {
+ result = ftape_report_error(&err_code,
+ &err_command, 1);
+ if (result == 0 && err_code == 27) {
+ /* Okay, drive saw reset
+ * command and responded as it
+ * should
+ */
+ break;
+ } else {
+ result = -EIO;
+ }
+ } else {
+ result = -EIO;
+ }
+ }
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ }
+ if (result != 0) {
+ TRACE(ft_t_err, "General failure to reset tape drive");
+ } else {
+ /* Restore correct settings: keep original rate
+ */
+ ftape_set_data_rate(ft_data_rate, ft_qic_std);
+ }
+ ftape_init_drive_needed = 1;
+ TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-io.h b/drivers/char/ftape/lowlevel/ftape-io.h
new file mode 100644
index 000000000000..26a7baad8717
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-io.h
@@ -0,0 +1,90 @@
+#ifndef _FTAPE_IO_H
+#define _FTAPE_IO_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:18 $
+ *
+ * This file contains definitions for the glue part of the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/qic117.h>
+#include <linux/ftape-vendors.h>
+
+typedef struct {
+ unsigned int seek;
+ unsigned int reset;
+ unsigned int rewind;
+ unsigned int head_seek;
+ unsigned int stop;
+ unsigned int pause;
+} ft_timeout_table;
+
+typedef enum {
+ prehistoric, pre_qic117c, post_qic117b, post_qic117d
+} qic_model;
+
+/*
+ * ftape-io.c defined global vars.
+ */
+extern ft_timeout_table ftape_timeout;
+extern unsigned int ftape_tape_len;
+extern volatile qic117_cmd_t ftape_current_command;
+extern const struct qic117_command_table qic117_cmds[];
+extern int ftape_might_be_off_track;
+
+/*
+ * ftape-io.c defined global functions.
+ */
+extern void ftape_udelay(unsigned int usecs);
+extern void ftape_udelay_calibrate(void);
+extern void ftape_sleep(unsigned int time);
+extern void ftape_report_vendor_id(unsigned int *id);
+extern int ftape_command(qic117_cmd_t command);
+extern int ftape_command_wait(qic117_cmd_t command,
+ unsigned int timeout,
+ int *status);
+extern int ftape_parameter(unsigned int parameter);
+extern int ftape_report_operation(int *status,
+ qic117_cmd_t command,
+ int result_length);
+extern int ftape_report_configuration(qic_model *model,
+ unsigned int *rate,
+ int *qic_std,
+ int *tape_len);
+extern int ftape_report_drive_status(int *status);
+extern int ftape_report_raw_drive_status(int *status);
+extern int ftape_report_status(int *status);
+extern int ftape_ready_wait(unsigned int timeout, int *status);
+extern int ftape_seek_head_to_track(unsigned int track);
+extern int ftape_set_data_rate(unsigned int new_rate, unsigned int qic_std);
+extern int ftape_report_error(unsigned int *error,
+ qic117_cmd_t *command,
+ int report);
+extern int ftape_reset_drive(void);
+extern int ftape_put_drive_to_sleep(wake_up_types method);
+extern int ftape_wakeup_drive(wake_up_types method);
+extern int ftape_increase_threshold(void);
+extern int ftape_half_data_rate(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-proc.c b/drivers/char/ftape/lowlevel/ftape-proc.c
new file mode 100644
index 000000000000..c66251e997ed
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-proc.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.c,v $
+ * $Revision: 1.11 $
+ * $Date: 1997/10/24 14:47:37 $
+ *
+ * This file contains the procfs interface for the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+
+ * Old code removed, switched to dynamic proc entry.
+ */
+
+#include <linux/config.h>
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+
+#include <linux/proc_fs.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include <linux/qic117.h>
+
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-proc.h"
+#include "../lowlevel/ftape-tracing.h"
+
+static size_t get_driver_info(char *buf)
+{
+ const char *debug_level[] = { "bugs" ,
+ "errors",
+ "warnings",
+ "informational",
+ "noisy",
+ "program flow",
+ "fdc and dma",
+ "data flow",
+ "anything" };
+
+ return sprintf(buf,
+ "version : %s\n"
+ "used data rate: %d kbit/sec\n"
+ "dma memory : %d kb\n"
+ "debug messages: %s\n",
+ FTAPE_VERSION,
+ ft_data_rate,
+ FT_BUFF_SIZE * ft_nr_buffers >> 10,
+ debug_level[TRACE_LEVEL]);
+}
+
+static size_t get_tapedrive_info(char *buf)
+{
+ return sprintf(buf,
+ "vendor id : 0x%04x\n"
+ "drive name: %s\n"
+ "wind speed: %d ips\n"
+ "wakeup : %s\n"
+ "max. rate : %d kbit/sec\n",
+ ft_drive_type.vendor_id,
+ ft_drive_type.name,
+ ft_drive_type.speed,
+ ((ft_drive_type.wake_up == no_wake_up)
+ ? "No wakeup needed" :
+ ((ft_drive_type.wake_up == wake_up_colorado)
+ ? "Colorado" :
+ ((ft_drive_type.wake_up == wake_up_mountain)
+ ? "Mountain" :
+ ((ft_drive_type.wake_up == wake_up_insight)
+ ? "Motor on" :
+ "Unknown")))),
+ ft_drive_max_rate);
+}
+
+static size_t get_cartridge_info(char *buf)
+{
+ if (ftape_init_drive_needed) {
+ return sprintf(buf, "uninitialized\n");
+ }
+ if (ft_no_tape) {
+ return sprintf(buf, "no cartridge inserted\n");
+ }
+ return sprintf(buf,
+ "segments : %5d\n"
+ "tracks : %5d\n"
+ "length : %5dft\n"
+ "formatted : %3s\n"
+ "writable : %3s\n"
+ "QIC spec. : QIC-%s\n"
+ "fmt-code : %1d\n",
+ ft_segments_per_track,
+ ft_tracks_per_tape,
+ ftape_tape_len,
+ (ft_formatted == 1) ? "yes" : "no",
+ (ft_write_protected == 1) ? "no" : "yes",
+ ((ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+ ((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+ ((ft_qic_std == QIC_TAPE_QIC3010) ? "3010" :
+ ((ft_qic_std == QIC_TAPE_QIC3020) ? "3020" :
+ "???")))),
+ ft_format_code);
+}
+
+static size_t get_controller_info(char *buf)
+{
+ const char *fdc_name[] = { "no fdc",
+ "i8272",
+ "i82077",
+ "i82077AA",
+ "Colorado FC-10 or FC-20",
+ "i82078",
+ "i82078_1" };
+
+ return sprintf(buf,
+ "FDC type : %s\n"
+ "FDC base : 0x%03x\n"
+ "FDC irq : %d\n"
+ "FDC dma : %d\n"
+ "FDC thr. : %d\n"
+ "max. rate : %d kbit/sec\n",
+ ft_mach2 ? "Mountain MACH-2" : fdc_name[fdc.type],
+ fdc.sra, fdc.irq, fdc.dma,
+ ft_fdc_threshold, ft_fdc_max_rate);
+}
+
+static size_t get_history_info(char *buf)
+{
+ size_t len;
+
+ len = sprintf(buf,
+ "\nFDC isr statistics\n"
+ " id_am_errors : %3d\n"
+ " id_crc_errors : %3d\n"
+ " data_am_errors : %3d\n"
+ " data_crc_errors : %3d\n"
+ " overrun_errors : %3d\n"
+ " no_data_errors : %3d\n"
+ " retries : %3d\n",
+ ft_history.id_am_errors, ft_history.id_crc_errors,
+ ft_history.data_am_errors, ft_history.data_crc_errors,
+ ft_history.overrun_errors, ft_history.no_data_errors,
+ ft_history.retries);
+ len += sprintf(buf + len,
+ "\nECC statistics\n"
+ " crc_errors : %3d\n"
+ " crc_failures : %3d\n"
+ " ecc_failures : %3d\n"
+ " sectors corrected: %3d\n",
+ ft_history.crc_errors, ft_history.crc_failures,
+ ft_history.ecc_failures, ft_history.corrected);
+ len += sprintf(buf + len,
+ "\ntape quality statistics\n"
+ " media defects : %3d\n",
+ ft_history.defects);
+ len += sprintf(buf + len,
+ "\ntape motion statistics\n"
+ " repositions : %3d\n",
+ ft_history.rewinds);
+ return len;
+}
+
+static int ftape_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *ptr = page;
+ size_t len;
+
+ ptr += sprintf(ptr, "Kernel Driver\n\n");
+ ptr += get_driver_info(ptr);
+ ptr += sprintf(ptr, "\nTape Drive\n\n");
+ ptr += get_tapedrive_info(ptr);
+ ptr += sprintf(ptr, "\nFDC Controller\n\n");
+ ptr += get_controller_info(ptr);
+ ptr += sprintf(ptr, "\nTape Cartridge\n\n");
+ ptr += get_cartridge_info(ptr);
+ ptr += sprintf(ptr, "\nHistory Record\n\n");
+ ptr += get_history_info(ptr);
+
+ len = strlen(page);
+ *start = NULL;
+ if (off+count >= len) {
+ *eof = 1;
+ } else {
+ *eof = 0;
+ }
+ return len;
+}
+
+int __init ftape_proc_init(void)
+{
+ return create_proc_read_entry("ftape", 0, &proc_root,
+ ftape_read_proc, NULL) != NULL;
+}
+
+void ftape_proc_destroy(void)
+{
+ remove_proc_entry("ftape", &proc_root);
+}
+
+#endif /* defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) */
diff --git a/drivers/char/ftape/lowlevel/ftape-proc.h b/drivers/char/ftape/lowlevel/ftape-proc.h
new file mode 100644
index 000000000000..264dfcc1d22d
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-proc.h
@@ -0,0 +1,35 @@
+#ifndef _FTAPE_PROC_H
+#define _FTAPE_PROC_H
+
+/*
+ * Copyright (C) 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:20 $
+ *
+ * This file contains definitions for the procfs interface of the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/proc_fs.h>
+
+extern int ftape_proc_init(void);
+extern void ftape_proc_destroy(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-read.c b/drivers/char/ftape/lowlevel/ftape-read.c
new file mode 100644
index 000000000000..d967d8cd86dc
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-read.c
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.c,v $
+ * $Revision: 1.6 $
+ * $Date: 1997/10/21 14:39:22 $
+ *
+ * This file contains the reading code
+ * for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/* Global vars.
+ */
+
+/* Local vars.
+ */
+
+void ftape_zap_read_buffers(void)
+{
+ int i;
+
+ for (i = 0; i < ft_nr_buffers; ++i) {
+/* changed to "fit" with dynamic allocation of tape_buffer. --khp */
+ ft_buffer[i]->status = waiting;
+ ft_buffer[i]->bytes = 0;
+ ft_buffer[i]->skip = 0;
+ ft_buffer[i]->retry = 0;
+ }
+/* ftape_reset_buffer(); */
+}
+
+static SectorMap convert_sector_map(buffer_struct * buff)
+{
+ int i = 0;
+ SectorMap bad_map = ftape_get_bad_sector_entry(buff->segment_id);
+ SectorMap src_map = buff->soft_error_map | buff->hard_error_map;
+ SectorMap dst_map = 0;
+ TRACE_FUN(ft_t_any);
+
+ if (bad_map || src_map) {
+ TRACE(ft_t_flow, "bad_map = 0x%08lx", (long) bad_map);
+ TRACE(ft_t_flow, "src_map = 0x%08lx", (long) src_map);
+ }
+ while (bad_map) {
+ while ((bad_map & 1) == 0) {
+ if (src_map & 1) {
+ dst_map |= (1 << i);
+ }
+ src_map >>= 1;
+ bad_map >>= 1;
+ ++i;
+ }
+ /* (bad_map & 1) == 1 */
+ src_map >>= 1;
+ bad_map >>= 1;
+ }
+ if (src_map) {
+ dst_map |= (src_map << i);
+ }
+ if (dst_map) {
+ TRACE(ft_t_flow, "dst_map = 0x%08lx", (long) dst_map);
+ }
+ TRACE_EXIT dst_map;
+}
+
+static int correct_and_copy_fraction(buffer_struct *buff, __u8 * destination,
+ int start, int size)
+{
+ struct memory_segment mseg;
+ int result;
+ SectorMap read_bad;
+ TRACE_FUN(ft_t_any);
+
+ mseg.read_bad = convert_sector_map(buff);
+ mseg.marked_bad = 0; /* not used... */
+ mseg.blocks = buff->bytes / FT_SECTOR_SIZE;
+ mseg.data = buff->address;
+ /* If there are no data sectors we can skip this segment.
+ */
+ if (mseg.blocks <= 3) {
+ TRACE_ABORT(0, ft_t_noise, "empty segment");
+ }
+ read_bad = mseg.read_bad;
+ ft_history.crc_errors += count_ones(read_bad);
+ result = ftape_ecc_correct_data(&mseg);
+ if (read_bad != 0 || mseg.corrected != 0) {
+ TRACE(ft_t_noise, "crc error map: 0x%08lx", (unsigned long)read_bad);
+ TRACE(ft_t_noise, "corrected map: 0x%08lx", (unsigned long)mseg.corrected);
+ ft_history.corrected += count_ones(mseg.corrected);
+ }
+ if (result == ECC_CORRECTED || result == ECC_OK) {
+ if (result == ECC_CORRECTED) {
+ TRACE(ft_t_info, "ecc corrected segment: %d", buff->segment_id);
+ }
+ if(start < 0) {
+ start= 0;
+ }
+ if((start+size) > ((mseg.blocks - 3) * FT_SECTOR_SIZE)) {
+ size = (mseg.blocks - 3) * FT_SECTOR_SIZE - start;
+ }
+ if (size < 0) {
+ size= 0;
+ }
+ if(size > 0) {
+ memcpy(destination + start, mseg.data + start, size);
+ }
+ if ((read_bad ^ mseg.corrected) & mseg.corrected) {
+ /* sectors corrected without crc errors set */
+ ft_history.crc_failures++;
+ }
+ TRACE_EXIT size; /* (mseg.blocks - 3) * FT_SECTOR_SIZE; */
+ } else {
+ ft_history.ecc_failures++;
+ TRACE_ABORT(-EAGAIN,
+ ft_t_err, "ecc failure on segment %d",
+ buff->segment_id);
+ }
+ TRACE_EXIT 0;
+}
+
+/* Read given segment into buffer at address.
+ */
+int ftape_read_segment_fraction(const int segment_id,
+ void *address,
+ const ft_read_mode_t read_mode,
+ const int start,
+ const int size)
+{
+ int result = 0;
+ int retry = 0;
+ int bytes_read = 0;
+ int read_done = 0;
+ TRACE_FUN(ft_t_flow);
+
+ ft_history.used |= 1;
+ TRACE(ft_t_data_flow, "segment_id = %d", segment_id);
+ if (ft_driver_state != reading) {
+ TRACE(ft_t_noise, "calling ftape_abort_operation");
+ TRACE_CATCH(ftape_abort_operation(),);
+ ftape_set_state(reading);
+ }
+ for(;;) {
+ buffer_struct *tail;
+ /* Allow escape from this loop on signal !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ /* Search all full buffers for the first matching the
+ * wanted segment. Clear other buffers on the fly.
+ */
+ tail = ftape_get_buffer(ft_queue_tail);
+ while (!read_done && tail->status == done) {
+ /* Allow escape from this loop on signal !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ if (tail->segment_id == segment_id) {
+ /* If out buffer is already full,
+ * return its contents.
+ */
+ TRACE(ft_t_flow, "found segment in cache: %d",
+ segment_id);
+ if (tail->deleted) {
+ /* Return a value that
+ * read_header_segment
+ * understands. As this
+ * should only occur when
+ * searching for the header
+ * segments it shouldn't be
+ * misinterpreted elsewhere.
+ */
+ TRACE_EXIT 0;
+ }
+ result = correct_and_copy_fraction(
+ tail,
+ address,
+ start,
+ size);
+ TRACE(ft_t_flow, "segment contains (bytes): %d",
+ result);
+ if (result < 0) {
+ if (result != -EAGAIN) {
+ TRACE_EXIT result;
+ }
+ /* keep read_done == 0, will
+ * trigger
+ * ftape_abort_operation
+ * because reading wrong
+ * segment.
+ */
+ TRACE(ft_t_err, "ecc failed, retry");
+ ++retry;
+ } else {
+ read_done = 1;
+ bytes_read = result;
+ }
+ } else {
+ TRACE(ft_t_flow,"zapping segment in cache: %d",
+ tail->segment_id);
+ }
+ tail->status = waiting;
+ tail = ftape_next_buffer(ft_queue_tail);
+ }
+ if (!read_done && tail->status == reading) {
+ if (tail->segment_id == segment_id) {
+ switch(ftape_wait_segment(reading)) {
+ case 0:
+ break;
+ case -EINTR:
+ TRACE_ABORT(-EINTR, ft_t_warn,
+ "interrupted by "
+ "non-blockable signal");
+ break;
+ default:
+ TRACE(ft_t_noise,
+ "wait_segment failed");
+ ftape_abort_operation();
+ ftape_set_state(reading);
+ break;
+ }
+ } else {
+ /* We're reading the wrong segment,
+ * stop runner.
+ */
+ TRACE(ft_t_noise, "reading wrong segment");
+ ftape_abort_operation();
+ ftape_set_state(reading);
+ }
+ }
+ /* should runner stop ?
+ */
+ if (ft_runner_status == aborting) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+ switch(head->status) {
+ case error:
+ ft_history.defects +=
+ count_ones(head->hard_error_map);
+ case reading:
+ head->status = waiting;
+ break;
+ default:
+ break;
+ }
+ TRACE_CATCH(ftape_dumb_stop(),);
+ } else {
+ /* If just passed last segment on tape: wait
+ * for BOT or EOT mark. Sets ft_runner_status to
+ * idle if at lEOT and successful
+ */
+ TRACE_CATCH(ftape_handle_logical_eot(),);
+ }
+ /* If we got a segment: quit, or else retry up to limit.
+ *
+ * If segment to read is empty, do not start runner for it,
+ * but wait for next read call.
+ */
+ if (read_done ||
+ ftape_get_bad_sector_entry(segment_id) == EMPTY_SEGMENT ) {
+ /* bytes_read = 0; should still be zero */
+ TRACE_EXIT bytes_read;
+
+ }
+ if (retry > FT_RETRIES_ON_ECC_ERROR) {
+ ft_history.defects++;
+ TRACE_ABORT(-ENODATA, ft_t_err,
+ "too many retries on ecc failure");
+ }
+ /* Now at least one buffer is empty !
+ * Restart runner & tape if needed.
+ */
+ TRACE(ft_t_any, "head: %d, tail: %d, ft_runner_status: %d",
+ ftape_buffer_id(ft_queue_head),
+ ftape_buffer_id(ft_queue_tail),
+ ft_runner_status);
+ TRACE(ft_t_any, "buffer[].status, [head]: %d, [tail]: %d",
+ ftape_get_buffer(ft_queue_head)->status,
+ ftape_get_buffer(ft_queue_tail)->status);
+ tail = ftape_get_buffer(ft_queue_tail);
+ if (tail->status == waiting) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+
+ ftape_setup_new_segment(head, segment_id, -1);
+ if (read_mode == FT_RD_SINGLE) {
+ /* disable read-ahead */
+ head->next_segment = 0;
+ }
+ ftape_calc_next_cluster(head);
+ if (ft_runner_status == idle) {
+ result = ftape_start_tape(segment_id,
+ head->sector_offset);
+ if (result < 0) {
+ TRACE_ABORT(result, ft_t_err, "Error: "
+ "segment %d unreachable",
+ segment_id);
+ }
+ }
+ head->status = reading;
+ fdc_setup_read_write(head, FDC_READ);
+ }
+ }
+ /* not reached */
+ TRACE_EXIT -EIO;
+}
+
+int ftape_read_header_segment(__u8 *address)
+{
+ int result;
+ int header_segment;
+ int first_failed = 0;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ ft_used_header_segment = -1;
+ TRACE_CATCH(ftape_report_drive_status(&status),);
+ TRACE(ft_t_flow, "reading...");
+ /* We're looking for the first header segment.
+ * A header segment cannot contain bad sectors, therefor at the
+ * tape start, segments with bad sectors are (according to QIC-40/80)
+ * written with deleted data marks and must be skipped.
+ */
+ memset(address, '\0', (FT_SECTORS_PER_SEGMENT - 3) * FT_SECTOR_SIZE);
+ result = 0;
+#define HEADER_SEGMENT_BOUNDARY 68 /* why not 42? */
+ for (header_segment = 0;
+ header_segment < HEADER_SEGMENT_BOUNDARY && result == 0;
+ ++header_segment) {
+ /* Set no read-ahead, the isr will force read-ahead whenever
+ * it encounters deleted data !
+ */
+ result = ftape_read_segment(header_segment,
+ address,
+ FT_RD_SINGLE);
+ if (result < 0 && !first_failed) {
+ TRACE(ft_t_err, "header segment damaged, trying backup");
+ first_failed = 1;
+ result = 0; /* force read of next (backup) segment */
+ }
+ }
+ if (result < 0 || header_segment >= HEADER_SEGMENT_BOUNDARY) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "no readable header segment found");
+ }
+ TRACE_CATCH(ftape_abort_operation(),);
+ ft_used_header_segment = header_segment;
+ result = ftape_decode_header_segment(address);
+ TRACE_EXIT result;
+}
+
+int ftape_decode_header_segment(__u8 *address)
+{
+ unsigned int max_floppy_side;
+ unsigned int max_floppy_track;
+ unsigned int max_floppy_sector;
+ unsigned int new_tape_len;
+ TRACE_FUN(ft_t_flow);
+
+ if (GET4(address, FT_SIGNATURE) == FT_D2G_MAGIC) {
+ /* Ditto 2GB header segment. They encrypt the bad sector map.
+ * We decrypt it and store them in normal format.
+ * I hope this is correct.
+ */
+ int i;
+ TRACE(ft_t_warn,
+ "Found Ditto 2GB tape, "
+ "trying to decrypt bad sector map");
+ for (i=256; i < 29 * FT_SECTOR_SIZE; i++) {
+ address[i] = ~(address[i] - (i&0xff));
+ }
+ PUT4(address, 0,FT_HSEG_MAGIC);
+ } else if (GET4(address, FT_SIGNATURE) != FT_HSEG_MAGIC) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "wrong signature in header segment");
+ }
+ ft_format_code = (ft_format_type) address[FT_FMT_CODE];
+ if (ft_format_code != fmt_big) {
+ ft_header_segment_1 = GET2(address, FT_HSEG_1);
+ ft_header_segment_2 = GET2(address, FT_HSEG_2);
+ ft_first_data_segment = GET2(address, FT_FRST_SEG);
+ ft_last_data_segment = GET2(address, FT_LAST_SEG);
+ } else {
+ ft_header_segment_1 = GET4(address, FT_6_HSEG_1);
+ ft_header_segment_2 = GET4(address, FT_6_HSEG_2);
+ ft_first_data_segment = GET4(address, FT_6_FRST_SEG);
+ ft_last_data_segment = GET4(address, FT_6_LAST_SEG);
+ }
+ TRACE(ft_t_noise, "first data segment: %d", ft_first_data_segment);
+ TRACE(ft_t_noise, "last data segment: %d", ft_last_data_segment);
+ TRACE(ft_t_noise, "header segments are %d and %d",
+ ft_header_segment_1, ft_header_segment_2);
+
+ /* Verify tape parameters...
+ * QIC-40/80 spec: tape_parameters:
+ *
+ * segments-per-track segments_per_track
+ * tracks-per-cartridge tracks_per_tape
+ * max-floppy-side (segments_per_track *
+ * tracks_per_tape - 1) /
+ * ftape_segments_per_head
+ * max-floppy-track ftape_segments_per_head /
+ * ftape_segments_per_cylinder - 1
+ * max-floppy-sector ftape_segments_per_cylinder *
+ * FT_SECTORS_PER_SEGMENT
+ */
+ ft_segments_per_track = GET2(address, FT_SPT);
+ ft_tracks_per_tape = address[FT_TPC];
+ max_floppy_side = address[FT_FHM];
+ max_floppy_track = address[FT_FTM];
+ max_floppy_sector = address[FT_FSM];
+ TRACE(ft_t_noise, "(fmt/spt/tpc/fhm/ftm/fsm) = %d/%d/%d/%d/%d/%d",
+ ft_format_code, ft_segments_per_track, ft_tracks_per_tape,
+ max_floppy_side, max_floppy_track, max_floppy_sector);
+ new_tape_len = ftape_tape_len;
+ switch (ft_format_code) {
+ case fmt_425ft:
+ new_tape_len = 425;
+ break;
+ case fmt_normal:
+ if (ftape_tape_len == 0) { /* otherwise 307 ft */
+ new_tape_len = 205;
+ }
+ break;
+ case fmt_1100ft:
+ new_tape_len = 1100;
+ break;
+ case fmt_var:{
+ int segments_per_1000_inch = 1; /* non-zero default for switch */
+ switch (ft_qic_std) {
+ case QIC_TAPE_QIC40:
+ segments_per_1000_inch = 332;
+ break;
+ case QIC_TAPE_QIC80:
+ segments_per_1000_inch = 488;
+ break;
+ case QIC_TAPE_QIC3010:
+ segments_per_1000_inch = 730;
+ break;
+ case QIC_TAPE_QIC3020:
+ segments_per_1000_inch = 1430;
+ break;
+ }
+ new_tape_len = (1000 * ft_segments_per_track +
+ (segments_per_1000_inch - 1)) / segments_per_1000_inch;
+ break;
+ }
+ case fmt_big:{
+ int segments_per_1000_inch = 1; /* non-zero default for switch */
+ switch (ft_qic_std) {
+ case QIC_TAPE_QIC40:
+ segments_per_1000_inch = 332;
+ break;
+ case QIC_TAPE_QIC80:
+ segments_per_1000_inch = 488;
+ break;
+ case QIC_TAPE_QIC3010:
+ segments_per_1000_inch = 730;
+ break;
+ case QIC_TAPE_QIC3020:
+ segments_per_1000_inch = 1430;
+ break;
+ default:
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "%x QIC-standard with fmt-code %d, please report",
+ ft_qic_std, ft_format_code);
+ }
+ new_tape_len = ((1000 * ft_segments_per_track +
+ (segments_per_1000_inch - 1)) /
+ segments_per_1000_inch);
+ break;
+ }
+ default:
+ TRACE_ABORT(-EIO, ft_t_err,
+ "unknown tape format, please report !");
+ }
+ if (new_tape_len != ftape_tape_len) {
+ ftape_tape_len = new_tape_len;
+ TRACE(ft_t_info, "calculated tape length is %d ft",
+ ftape_tape_len);
+ ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+ }
+ if (ft_segments_per_track == 0 && ft_tracks_per_tape == 0 &&
+ max_floppy_side == 0 && max_floppy_track == 0 &&
+ max_floppy_sector == 0) {
+ /* QIC-40 Rev E and earlier has no values in the header.
+ */
+ ft_segments_per_track = 68;
+ ft_tracks_per_tape = 20;
+ max_floppy_side = 1;
+ max_floppy_track = 169;
+ max_floppy_sector = 128;
+ }
+ /* This test will compensate for the wrong parameter on tapes
+ * formatted by Conner software.
+ */
+ if (ft_segments_per_track == 150 &&
+ ft_tracks_per_tape == 28 &&
+ max_floppy_side == 7 &&
+ max_floppy_track == 149 &&
+ max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous CONNER bug: max_floppy_side off by one !");
+ max_floppy_side = 6;
+ }
+ /* These tests will compensate for the wrong parameter on tapes
+ * formatted by ComByte Windows software.
+ *
+ * First, for 205 foot tapes
+ */
+ if (ft_segments_per_track == 100 &&
+ ft_tracks_per_tape == 28 &&
+ max_floppy_side == 9 &&
+ max_floppy_track == 149 &&
+ max_floppy_sector == 128) {
+TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!");
+ max_floppy_side = 4;
+ }
+ /* Next, for 307 foot tapes. */
+ if (ft_segments_per_track == 150 &&
+ ft_tracks_per_tape == 28 &&
+ max_floppy_side == 9 &&
+ max_floppy_track == 149 &&
+ max_floppy_sector == 128) {
+TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!");
+ max_floppy_side = 6;
+ }
+ /* This test will compensate for the wrong parameter on tapes
+ * formatted by Colorado Windows software.
+ */
+ if (ft_segments_per_track == 150 &&
+ ft_tracks_per_tape == 28 &&
+ max_floppy_side == 6 &&
+ max_floppy_track == 150 &&
+ max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous Colorado bug: max_floppy_track off by one !");
+ max_floppy_track = 149;
+ }
+ ftape_segments_per_head = ((max_floppy_sector/FT_SECTORS_PER_SEGMENT) *
+ (max_floppy_track + 1));
+ /* This test will compensate for some bug reported by Dima
+ * Brodsky. Seems to be a Colorado bug, either. (freebee
+ * Imation tape shipped together with Colorado T3000
+ */
+ if ((ft_format_code == fmt_var || ft_format_code == fmt_big) &&
+ ft_tracks_per_tape == 50 &&
+ max_floppy_side == 54 &&
+ max_floppy_track == 255 &&
+ max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous ??? bug: max_floppy_track off by one !");
+ max_floppy_track = 254;
+ }
+ /*
+ * Verify drive_configuration with tape parameters
+ */
+ if (ftape_segments_per_head == 0 || ftape_segments_per_cylinder == 0 ||
+ ((ft_segments_per_track * ft_tracks_per_tape - 1) / ftape_segments_per_head
+ != max_floppy_side) ||
+ (ftape_segments_per_head / ftape_segments_per_cylinder - 1 != max_floppy_track) ||
+ (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT != max_floppy_sector)
+#ifdef TESTING
+ || ((ft_format_code == fmt_var || ft_format_code == fmt_big) &&
+ (max_floppy_track != 254 || max_floppy_sector != 128))
+#endif
+ ) {
+ char segperheadz = ftape_segments_per_head ? ' ' : '?';
+ char segpercylz = ftape_segments_per_cylinder ? ' ' : '?';
+ TRACE(ft_t_err,"Tape parameters inconsistency, please report");
+ TRACE(ft_t_err, "reported = %d/%d/%d/%d/%d/%d",
+ ft_format_code,
+ ft_segments_per_track,
+ ft_tracks_per_tape,
+ max_floppy_side,
+ max_floppy_track,
+ max_floppy_sector);
+ TRACE(ft_t_err, "required = %d/%d/%d/%d%c/%d%c/%d",
+ ft_format_code,
+ ft_segments_per_track,
+ ft_tracks_per_tape,
+ ftape_segments_per_head ?
+ ((ft_segments_per_track * ft_tracks_per_tape -1) /
+ ftape_segments_per_head ) :
+ (ft_segments_per_track * ft_tracks_per_tape -1),
+ segperheadz,
+ ftape_segments_per_cylinder ?
+ (ftape_segments_per_head /
+ ftape_segments_per_cylinder - 1 ) :
+ ftape_segments_per_head - 1,
+ segpercylz,
+ (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT));
+ TRACE_EXIT -EIO;
+ }
+ ftape_extract_bad_sector_map(address);
+ TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-read.h b/drivers/char/ftape/lowlevel/ftape-read.h
new file mode 100644
index 000000000000..069f99f2a984
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-read.h
@@ -0,0 +1,51 @@
+#ifndef _FTAPE_READ_H
+#define _FTAPE_READ_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:22 $
+ *
+ * This file contains the definitions for the read functions
+ * for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+/* ftape-read.c defined global functions.
+ */
+typedef enum {
+ FT_RD_SINGLE = 0,
+ FT_RD_AHEAD = 1,
+} ft_read_mode_t;
+
+extern int ftape_read_header_segment(__u8 *address);
+extern int ftape_decode_header_segment(__u8 *address);
+extern int ftape_read_segment_fraction(const int segment,
+ void *address,
+ const ft_read_mode_t read_mode,
+ const int start,
+ const int size);
+#define ftape_read_segment(segment, address, read_mode) \
+ ftape_read_segment_fraction(segment, address, read_mode, \
+ 0, FT_SEGMENT_SIZE)
+extern void ftape_zap_read_buffers(void);
+
+#endif /* _FTAPE_READ_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-rw.c b/drivers/char/ftape/lowlevel/ftape-rw.c
new file mode 100644
index 000000000000..c0d6dc2cbfd3
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-rw.c
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.c,v $
+ * $Revision: 1.7 $
+ * $Date: 1997/10/28 14:26:49 $
+ *
+ * This file contains some common code for the segment read and
+ * segment write routines for the QIC-117 floppy-tape driver for
+ * Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/* Global vars.
+ */
+int ft_nr_buffers;
+buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS];
+static volatile int ft_head;
+static volatile int ft_tail; /* not volatile but need same type as head */
+int fdc_setup_error;
+location_record ft_location = {-1, 0};
+volatile int ftape_tape_running;
+
+/* Local vars.
+ */
+static int overrun_count_offset;
+static int inhibit_correction;
+
+/* maxmimal allowed overshoot when fast seeking
+ */
+#define OVERSHOOT_LIMIT 10
+
+/* Increment cyclic buffer nr.
+ */
+buffer_struct *ftape_next_buffer(ft_buffer_queue_t pos)
+{
+ switch (pos) {
+ case ft_queue_head:
+ if (++ft_head >= ft_nr_buffers) {
+ ft_head = 0;
+ }
+ return ft_buffer[ft_head];
+ case ft_queue_tail:
+ if (++ft_tail >= ft_nr_buffers) {
+ ft_tail = 0;
+ }
+ return ft_buffer[ft_tail];
+ default:
+ return NULL;
+ }
+}
+int ftape_buffer_id(ft_buffer_queue_t pos)
+{
+ switch(pos) {
+ case ft_queue_head: return ft_head;
+ case ft_queue_tail: return ft_tail;
+ default: return -1;
+ }
+}
+buffer_struct *ftape_get_buffer(ft_buffer_queue_t pos)
+{
+ switch(pos) {
+ case ft_queue_head: return ft_buffer[ft_head];
+ case ft_queue_tail: return ft_buffer[ft_tail];
+ default: return NULL;
+ }
+}
+void ftape_reset_buffer(void)
+{
+ ft_head = ft_tail = 0;
+}
+
+buffer_state_enum ftape_set_state(buffer_state_enum new_state)
+{
+ buffer_state_enum old_state = ft_driver_state;
+
+ ft_driver_state = new_state;
+ return old_state;
+}
+/* Calculate Floppy Disk Controller and DMA parameters for a segment.
+ * head: selects buffer struct in array.
+ * offset: number of physical sectors to skip (including bad ones).
+ * count: number of physical sectors to handle (including bad ones).
+ */
+static int setup_segment(buffer_struct * buff,
+ int segment_id,
+ unsigned int sector_offset,
+ unsigned int sector_count,
+ int retry)
+{
+ SectorMap offset_mask;
+ SectorMap mask;
+ TRACE_FUN(ft_t_any);
+
+ buff->segment_id = segment_id;
+ buff->sector_offset = sector_offset;
+ buff->remaining = sector_count;
+ buff->head = segment_id / ftape_segments_per_head;
+ buff->cyl = (segment_id % ftape_segments_per_head) / ftape_segments_per_cylinder;
+ buff->sect = (segment_id % ftape_segments_per_cylinder) * FT_SECTORS_PER_SEGMENT + 1;
+ buff->deleted = 0;
+ offset_mask = (1 << buff->sector_offset) - 1;
+ mask = ftape_get_bad_sector_entry(segment_id) & offset_mask;
+ while (mask) {
+ if (mask & 1) {
+ offset_mask >>= 1; /* don't count bad sector */
+ }
+ mask >>= 1;
+ }
+ buff->data_offset = count_ones(offset_mask); /* good sectors to skip */
+ buff->ptr = buff->address + buff->data_offset * FT_SECTOR_SIZE;
+ TRACE(ft_t_flow, "data offset = %d sectors", buff->data_offset);
+ if (retry) {
+ buff->soft_error_map &= offset_mask; /* keep skipped part */
+ } else {
+ buff->hard_error_map = buff->soft_error_map = 0;
+ }
+ buff->bad_sector_map = ftape_get_bad_sector_entry(buff->segment_id);
+ if (buff->bad_sector_map != 0) {
+ TRACE(ft_t_noise, "segment: %d, bad sector map: %08lx",
+ buff->segment_id, (long)buff->bad_sector_map);
+ } else {
+ TRACE(ft_t_flow, "segment: %d", buff->segment_id);
+ }
+ if (buff->sector_offset > 0) {
+ buff->bad_sector_map >>= buff->sector_offset;
+ }
+ if (buff->sector_offset != 0 || buff->remaining != FT_SECTORS_PER_SEGMENT) {
+ TRACE(ft_t_flow, "sector offset = %d, count = %d",
+ buff->sector_offset, buff->remaining);
+ }
+ /* Segments with 3 or less sectors are not written with valid
+ * data because there is no space left for the ecc. The
+ * data written is whatever happens to be in the buffer.
+ * Reading such a segment will return a zero byte-count.
+ * To allow us to read/write segments with all bad sectors
+ * we fake one readable sector in the segment. This
+ * prevents having to handle these segments in a very
+ * special way. It is not important if the reading of this
+ * bad sector fails or not (the data is ignored). It is
+ * only read to keep the driver running.
+ *
+ * The QIC-40/80 spec. has no information on how to handle
+ * this case, so this is my interpretation.
+ */
+ if (buff->bad_sector_map == EMPTY_SEGMENT) {
+ TRACE(ft_t_flow, "empty segment %d, fake first sector good",
+ buff->segment_id);
+ if (buff->ptr != buff->address) {
+ TRACE(ft_t_bug, "This is a bug: %p/%p",
+ buff->ptr, buff->address);
+ }
+ buff->bad_sector_map = FAKE_SEGMENT;
+ }
+ fdc_setup_error = 0;
+ buff->next_segment = segment_id + 1;
+ TRACE_EXIT 0;
+}
+
+/* Calculate Floppy Disk Controller and DMA parameters for a new segment.
+ */
+int ftape_setup_new_segment(buffer_struct * buff, int segment_id, int skip)
+{
+ int result = 0;
+ static int old_segment_id = -1;
+ static buffer_state_enum old_ft_driver_state = idle;
+ int retry = 0;
+ unsigned offset = 0;
+ int count = FT_SECTORS_PER_SEGMENT;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_flow, "%s segment %d (old = %d)",
+ (ft_driver_state == reading || ft_driver_state == verifying)
+ ? "reading" : "writing",
+ segment_id, old_segment_id);
+ if (ft_driver_state != old_ft_driver_state) { /* when verifying */
+ old_segment_id = -1;
+ old_ft_driver_state = ft_driver_state;
+ }
+ if (segment_id == old_segment_id) {
+ ++buff->retry;
+ ++ft_history.retries;
+ TRACE(ft_t_flow, "setting up for retry nr %d", buff->retry);
+ retry = 1;
+ if (skip && buff->skip > 0) { /* allow skip on retry */
+ offset = buff->skip;
+ count -= offset;
+ TRACE(ft_t_flow, "skipping %d sectors", offset);
+ }
+ } else {
+ buff->retry = 0;
+ buff->skip = 0;
+ old_segment_id = segment_id;
+ }
+ result = setup_segment(buff, segment_id, offset, count, retry);
+ TRACE_EXIT result;
+}
+
+/* Determine size of next cluster of good sectors.
+ */
+int ftape_calc_next_cluster(buffer_struct * buff)
+{
+ /* Skip bad sectors.
+ */
+ while (buff->remaining > 0 && (buff->bad_sector_map & 1) != 0) {
+ buff->bad_sector_map >>= 1;
+ ++buff->sector_offset;
+ --buff->remaining;
+ }
+ /* Find next cluster of good sectors
+ */
+ if (buff->bad_sector_map == 0) { /* speed up */
+ buff->sector_count = buff->remaining;
+ } else {
+ SectorMap map = buff->bad_sector_map;
+
+ buff->sector_count = 0;
+ while (buff->sector_count < buff->remaining && (map & 1) == 0) {
+ ++buff->sector_count;
+ map >>= 1;
+ }
+ }
+ return buff->sector_count;
+}
+
+/* if just passed the last segment on a track, wait for BOT
+ * or EOT mark.
+ */
+int ftape_handle_logical_eot(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_runner_status == logical_eot) {
+ int status;
+
+ TRACE(ft_t_noise, "tape at logical EOT");
+ TRACE_CATCH(ftape_ready_wait(ftape_timeout.seek, &status),);
+ if ((status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) {
+ TRACE_ABORT(-EIO, ft_t_err, "eot/bot not reached");
+ }
+ ft_runner_status = end_of_tape;
+ }
+ if (ft_runner_status == end_of_tape) {
+ TRACE(ft_t_noise, "runner stopped because of logical EOT");
+ ft_runner_status = idle;
+ }
+ TRACE_EXIT 0;
+}
+
+static int check_bot_eot(int status)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) {
+ ft_location.bot = ((ft_location.track & 1) == 0 ?
+ (status & QIC_STATUS_AT_BOT) != 0:
+ (status & QIC_STATUS_AT_EOT) != 0);
+ ft_location.eot = !ft_location.bot;
+ ft_location.segment = (ft_location.track +
+ (ft_location.bot ? 0 : 1)) * ft_segments_per_track - 1;
+ ft_location.sector = -1;
+ ft_location.known = 1;
+ TRACE(ft_t_flow, "tape at logical %s",
+ ft_location.bot ? "bot" : "eot");
+ TRACE(ft_t_flow, "segment = %d", ft_location.segment);
+ } else {
+ ft_location.known = 0;
+ }
+ TRACE_EXIT ft_location.known;
+}
+
+/* Read Id of first sector passing tape head.
+ */
+static int ftape_read_id(void)
+{
+ int status;
+ __u8 out[2];
+ TRACE_FUN(ft_t_any);
+
+ /* Assume tape is running on entry, be able to handle
+ * situation where it stopped or is stopping.
+ */
+ ft_location.known = 0; /* default is location not known */
+ out[0] = FDC_READID;
+ out[1] = ft_drive_sel;
+ TRACE_CATCH(fdc_command(out, 2),);
+ switch (fdc_interrupt_wait(20 * FT_SECOND)) {
+ case 0:
+ if (fdc_sect == 0) {
+ if (ftape_report_drive_status(&status) >= 0 &&
+ (status & QIC_STATUS_READY)) {
+ ftape_tape_running = 0;
+ TRACE(ft_t_flow, "tape has stopped");
+ check_bot_eot(status);
+ }
+ } else {
+ ft_location.known = 1;
+ ft_location.segment = (ftape_segments_per_head
+ * fdc_head
+ + ftape_segments_per_cylinder
+ * fdc_cyl
+ + (fdc_sect - 1)
+ / FT_SECTORS_PER_SEGMENT);
+ ft_location.sector = ((fdc_sect - 1)
+ % FT_SECTORS_PER_SEGMENT);
+ ft_location.eot = ft_location.bot = 0;
+ }
+ break;
+ case -ETIME:
+ /* Didn't find id on tape, must be near end: Wait
+ * until stopped.
+ */
+ if (ftape_ready_wait(FT_FOREVER, &status) >= 0) {
+ ftape_tape_running = 0;
+ TRACE(ft_t_flow, "tape has stopped");
+ check_bot_eot(status);
+ }
+ break;
+ default:
+ /* Interrupted or otherwise failing
+ * fdc_interrupt_wait()
+ */
+ TRACE(ft_t_err, "fdc_interrupt_wait failed");
+ break;
+ }
+ if (!ft_location.known) {
+ TRACE_ABORT(-EIO, ft_t_flow, "no id found");
+ }
+ if (ft_location.sector == 0) {
+ TRACE(ft_t_flow, "passing segment %d/%d",
+ ft_location.segment, ft_location.sector);
+ } else {
+ TRACE(ft_t_fdc_dma, "passing segment %d/%d",
+ ft_location.segment, ft_location.sector);
+ }
+ TRACE_EXIT 0;
+}
+
+static int logical_forward(void)
+{
+ ftape_tape_running = 1;
+ return ftape_command(QIC_LOGICAL_FORWARD);
+}
+
+int ftape_stop_tape(int *pstatus)
+{
+ int retry = 0;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ do {
+ result = ftape_command_wait(QIC_STOP_TAPE,
+ ftape_timeout.stop, pstatus);
+ if (result == 0) {
+ if ((*pstatus & QIC_STATUS_READY) == 0) {
+ result = -EIO;
+ } else {
+ ftape_tape_running = 0;
+ }
+ }
+ } while (result < 0 && ++retry <= 3);
+ if (result < 0) {
+ TRACE(ft_t_err, "failed ! (fatal)");
+ }
+ TRACE_EXIT result;
+}
+
+int ftape_dumb_stop(void)
+{
+ int result;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ /* Abort current fdc operation if it's busy (probably read
+ * or write operation pending) with a reset.
+ */
+ if (fdc_ready_wait(100 /* usec */) < 0) {
+ TRACE(ft_t_noise, "aborting fdc operation");
+ fdc_reset();
+ }
+ /* Reading id's after the last segment on a track may fail
+ * but eventually the drive will become ready (logical eot).
+ */
+ result = ftape_report_drive_status(&status);
+ ft_location.known = 0;
+ do {
+ if (result == 0 && status & QIC_STATUS_READY) {
+ /* Tape is not running any more.
+ */
+ TRACE(ft_t_noise, "tape already halted");
+ check_bot_eot(status);
+ ftape_tape_running = 0;
+ } else if (ftape_tape_running) {
+ /* Tape is (was) still moving.
+ */
+#ifdef TESTING
+ ftape_read_id();
+#endif
+ result = ftape_stop_tape(&status);
+ } else {
+ /* Tape not yet ready but stopped.
+ */
+ result = ftape_ready_wait(ftape_timeout.pause,&status);
+ }
+ } while (ftape_tape_running
+ && !(sigtestsetmask(&current->pending.signal, _NEVER_BLOCK)));
+#ifndef TESTING
+ ft_location.known = 0;
+#endif
+ if (ft_runner_status == aborting || ft_runner_status == do_abort) {
+ ft_runner_status = idle;
+ }
+ TRACE_EXIT result;
+}
+
+/* Wait until runner has finished tail buffer.
+ *
+ */
+int ftape_wait_segment(buffer_state_enum state)
+{
+ int status;
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ while (ft_buffer[ft_tail]->status == state) {
+ TRACE(ft_t_flow, "state: %d", ft_buffer[ft_tail]->status);
+ /* First buffer still being worked on, wait up to timeout.
+ *
+ * Note: we check two times for being killed. 50
+ * seconds are quite long. Note that
+ * fdc_interrupt_wait() is not killable by any
+ * means. ftape_read_segment() wants us to return
+ * -EINTR in case of a signal.
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ result = fdc_interrupt_wait(50 * FT_SECOND);
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ if (result < 0) {
+ TRACE_ABORT(result,
+ ft_t_err, "fdc_interrupt_wait failed");
+ }
+ if (fdc_setup_error) {
+ /* recover... FIXME */
+ TRACE_ABORT(-EIO, ft_t_err, "setup error");
+ }
+ }
+ if (ft_buffer[ft_tail]->status != error) {
+ TRACE_EXIT 0;
+ }
+ TRACE_CATCH(ftape_report_drive_status(&status),);
+ TRACE(ft_t_noise, "ftape_report_drive_status: 0x%02x", status);
+ if ((status & QIC_STATUS_READY) &&
+ (status & QIC_STATUS_ERROR)) {
+ unsigned int error;
+ qic117_cmd_t command;
+
+ /* Report and clear error state.
+ * In case the drive can't operate at the selected
+ * rate, select the next lower data rate.
+ */
+ ftape_report_error(&error, &command, 1);
+ if (error == 31 && command == QIC_LOGICAL_FORWARD) {
+ /* drive does not accept this data rate */
+ if (ft_data_rate > 250) {
+ TRACE(ft_t_info,
+ "Probable data rate conflict");
+ TRACE(ft_t_info,
+ "Lowering data rate to %d Kbps",
+ ft_data_rate / 2);
+ ftape_half_data_rate();
+ if (ft_buffer[ft_tail]->retry > 0) {
+ /* give it a chance */
+ --ft_buffer[ft_tail]->retry;
+ }
+ } else {
+ /* no rate is accepted... */
+ TRACE(ft_t_err, "We're dead :(");
+ }
+ } else {
+ TRACE(ft_t_err, "Unknown error");
+ }
+ TRACE_EXIT -EIO; /* g.p. error */
+ }
+ TRACE_EXIT 0;
+}
+
+/* forward */ static int seek_forward(int segment_id, int fast);
+
+static int fast_seek(int count, int reverse)
+{
+ int result = 0;
+ int status;
+ TRACE_FUN(ft_t_flow);
+
+ if (count > 0) {
+ /* If positioned at begin or end of tape, fast seeking needs
+ * special treatment.
+ * Starting from logical bot needs a (slow) seek to the first
+ * segment before the high speed seek. Most drives do this
+ * automatically but some older don't, so we treat them
+ * all the same.
+ * Starting from logical eot is even more difficult because
+ * we cannot (slow) reverse seek to the last segment.
+ * TO BE IMPLEMENTED.
+ */
+ inhibit_correction = 0;
+ if (ft_location.known &&
+ ((ft_location.bot && !reverse) ||
+ (ft_location.eot && reverse))) {
+ if (!reverse) {
+ /* (slow) skip to first segment on a track
+ */
+ seek_forward(ft_location.track * ft_segments_per_track, 0);
+ --count;
+ } else {
+ /* When seeking backwards from
+ * end-of-tape the number of erased
+ * gaps found seems to be higher than
+ * expected. Therefor the drive must
+ * skip some more segments than
+ * calculated, but we don't know how
+ * many. Thus we will prevent the
+ * re-calculation of offset and
+ * overshoot when seeking backwards.
+ */
+ inhibit_correction = 1;
+ count += 3; /* best guess */
+ }
+ }
+ } else {
+ TRACE(ft_t_flow, "warning: zero or negative count: %d", count);
+ }
+ if (count > 0) {
+ int i;
+ int nibbles = count > 255 ? 3 : 2;
+
+ if (count > 4095) {
+ TRACE(ft_t_noise, "skipping clipped at 4095 segment");
+ count = 4095;
+ }
+ /* Issue this tape command first. */
+ if (!reverse) {
+ TRACE(ft_t_noise, "skipping %d segment(s)", count);
+ result = ftape_command(nibbles == 3 ?
+ QIC_SKIP_EXTENDED_FORWARD : QIC_SKIP_FORWARD);
+ } else {
+ TRACE(ft_t_noise, "backing up %d segment(s)", count);
+ result = ftape_command(nibbles == 3 ?
+ QIC_SKIP_EXTENDED_REVERSE : QIC_SKIP_REVERSE);
+ }
+ if (result < 0) {
+ TRACE(ft_t_noise, "Skip command failed");
+ } else {
+ --count; /* 0 means one gap etc. */
+ for (i = 0; i < nibbles; ++i) {
+ if (result >= 0) {
+ result = ftape_parameter(count & 15);
+ count /= 16;
+ }
+ }
+ result = ftape_ready_wait(ftape_timeout.rewind, &status);
+ if (result >= 0) {
+ ftape_tape_running = 0;
+ }
+ }
+ }
+ TRACE_EXIT result;
+}
+
+static int validate(int id)
+{
+ /* Check to see if position found is off-track as reported
+ * once. Because all tracks in one direction lie next to
+ * each other, if off-track the error will be approximately
+ * 2 * ft_segments_per_track.
+ */
+ if (ft_location.track == -1) {
+ return 1; /* unforseen situation, don't generate error */
+ } else {
+ /* Use margin of ft_segments_per_track on both sides
+ * because ftape needs some margin and the error we're
+ * looking for is much larger !
+ */
+ int lo = (ft_location.track - 1) * ft_segments_per_track;
+ int hi = (ft_location.track + 2) * ft_segments_per_track;
+
+ return (id >= lo && id < hi);
+ }
+}
+
+static int seek_forward(int segment_id, int fast)
+{
+ int failures = 0;
+ int count;
+ static int margin = 1; /* fixed: stop this before target */
+ static int overshoot = 1;
+ static int min_count = 8;
+ int expected = -1;
+ int target = segment_id - margin;
+ int fast_seeking;
+ int prev_segment = ft_location.segment;
+ TRACE_FUN(ft_t_flow);
+
+ if (!ft_location.known) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "fatal: cannot seek from unknown location");
+ }
+ if (!validate(segment_id)) {
+ ftape_sleep(1 * FT_SECOND);
+ ft_failure = 1;
+ TRACE_ABORT(-EIO, ft_t_err,
+ "fatal: head off track (bad hardware?)");
+ }
+ TRACE(ft_t_noise, "from %d/%d to %d/0 - %d",
+ ft_location.segment, ft_location.sector,segment_id,margin);
+ count = target - ft_location.segment - overshoot;
+ fast_seeking = (fast &&
+ count > (min_count + (ft_location.bot ? 1 : 0)));
+ if (fast_seeking) {
+ TRACE(ft_t_noise, "fast skipping %d segments", count);
+ expected = segment_id - margin;
+ fast_seek(count, 0);
+ }
+ if (!ftape_tape_running) {
+ logical_forward();
+ }
+ while (ft_location.segment < segment_id) {
+ /* This requires at least one sector in a (bad) segment to
+ * have a valid and readable sector id !
+ * It looks like this is not guaranteed, so we must try
+ * to find a way to skip an EMPTY_SEGMENT. !!! FIXME !!!
+ */
+ if (ftape_read_id() < 0 || !ft_location.known ||
+ sigtestsetmask(&current->pending.signal, _DONT_BLOCK)) {
+ ft_location.known = 0;
+ if (!ftape_tape_running ||
+ ++failures > FT_SECTORS_PER_SEGMENT) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "read_id failed completely");
+ }
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ TRACE(ft_t_flow, "read_id failed, retry (%d)",
+ failures);
+ continue;
+ }
+ if (fast_seeking) {
+ TRACE(ft_t_noise, "ended at %d/%d (%d,%d)",
+ ft_location.segment, ft_location.sector,
+ overshoot, inhibit_correction);
+ if (!inhibit_correction &&
+ (ft_location.segment < expected ||
+ ft_location.segment > expected + margin)) {
+ int error = ft_location.segment - expected;
+ TRACE(ft_t_noise,
+ "adjusting overshoot from %d to %d",
+ overshoot, overshoot + error);
+ overshoot += error;
+ /* All overshoots have the same
+ * direction, so it should never
+ * become negative, but who knows.
+ */
+ if (overshoot < -5 ||
+ overshoot > OVERSHOOT_LIMIT) {
+ if (overshoot < 0) {
+ /* keep sane value */
+ overshoot = -5;
+ } else {
+ /* keep sane value */
+ overshoot = OVERSHOOT_LIMIT;
+ }
+ TRACE(ft_t_noise,
+ "clipped overshoot to %d",
+ overshoot);
+ }
+ }
+ fast_seeking = 0;
+ }
+ if (ft_location.known) {
+ if (ft_location.segment > prev_segment + 1) {
+ TRACE(ft_t_noise,
+ "missed segment %d while skipping",
+ prev_segment + 1);
+ }
+ prev_segment = ft_location.segment;
+ }
+ }
+ if (ft_location.segment > segment_id) {
+ TRACE_ABORT(-EIO,
+ ft_t_noise, "failed: skip ended at segment %d/%d",
+ ft_location.segment, ft_location.sector);
+ }
+ TRACE_EXIT 0;
+}
+
+static int skip_reverse(int segment_id, int *pstatus)
+{
+ int failures = 0;
+ static int overshoot = 1;
+ static int min_rewind = 2; /* 1 + overshoot */
+ static const int margin = 1; /* stop this before target */
+ int expected = 0;
+ int count = 1;
+ int short_seek;
+ int target = segment_id - margin;
+ TRACE_FUN(ft_t_flow);
+
+ if (ft_location.known && !validate(segment_id)) {
+ ftape_sleep(1 * FT_SECOND);
+ ft_failure = 1;
+ TRACE_ABORT(-EIO, ft_t_err,
+ "fatal: head off track (bad hardware?)");
+ }
+ do {
+ if (!ft_location.known) {
+ TRACE(ft_t_warn, "warning: location not known");
+ }
+ TRACE(ft_t_noise, "from %d/%d to %d/0 - %d",
+ ft_location.segment, ft_location.sector,
+ segment_id, margin);
+ /* min_rewind == 1 + overshoot_when_doing_minimum_rewind
+ * overshoot == overshoot_when_doing_larger_rewind
+ * Initially min_rewind == 1 + overshoot, optimization
+ * of both values will be done separately.
+ * overshoot and min_rewind can be negative as both are
+ * sums of three components:
+ * any_overshoot == rewind_overshoot -
+ * stop_overshoot -
+ * start_overshoot
+ */
+ if (ft_location.segment - target - (min_rewind - 1) < 1) {
+ short_seek = 1;
+ } else {
+ count = ft_location.segment - target - overshoot;
+ short_seek = (count < 1);
+ }
+ if (short_seek) {
+ count = 1; /* do shortest rewind */
+ expected = ft_location.segment - min_rewind;
+ if (expected/ft_segments_per_track != ft_location.track) {
+ expected = (ft_location.track *
+ ft_segments_per_track);
+ }
+ } else {
+ expected = target;
+ }
+ fast_seek(count, 1);
+ logical_forward();
+ if (ftape_read_id() < 0 || !ft_location.known ||
+ (sigtestsetmask(&current->pending.signal, _DONT_BLOCK))) {
+ if ((!ftape_tape_running && !ft_location.known) ||
+ ++failures > FT_SECTORS_PER_SEGMENT) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "read_id failed completely");
+ }
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ TRACE_CATCH(ftape_report_drive_status(pstatus),);
+ TRACE(ft_t_noise, "ftape_read_id failed, retry (%d)",
+ failures);
+ continue;
+ }
+ TRACE(ft_t_noise, "ended at %d/%d (%d,%d,%d)",
+ ft_location.segment, ft_location.sector,
+ min_rewind, overshoot, inhibit_correction);
+ if (!inhibit_correction &&
+ (ft_location.segment < expected ||
+ ft_location.segment > expected + margin)) {
+ int error = expected - ft_location.segment;
+ if (short_seek) {
+ TRACE(ft_t_noise,
+ "adjusting min_rewind from %d to %d",
+ min_rewind, min_rewind + error);
+ min_rewind += error;
+ if (min_rewind < -5) {
+ /* is this right ? FIXME ! */
+ /* keep sane value */
+ min_rewind = -5;
+ TRACE(ft_t_noise,
+ "clipped min_rewind to %d",
+ min_rewind);
+ }
+ } else {
+ TRACE(ft_t_noise,
+ "adjusting overshoot from %d to %d",
+ overshoot, overshoot + error);
+ overshoot += error;
+ if (overshoot < -5 ||
+ overshoot > OVERSHOOT_LIMIT) {
+ if (overshoot < 0) {
+ /* keep sane value */
+ overshoot = -5;
+ } else {
+ /* keep sane value */
+ overshoot = OVERSHOOT_LIMIT;
+ }
+ TRACE(ft_t_noise,
+ "clipped overshoot to %d",
+ overshoot);
+ }
+ }
+ }
+ } while (ft_location.segment > segment_id);
+ if (ft_location.known) {
+ TRACE(ft_t_noise, "current location: %d/%d",
+ ft_location.segment, ft_location.sector);
+ }
+ TRACE_EXIT 0;
+}
+
+static int determine_position(void)
+{
+ int retry = 0;
+ int status;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if (!ftape_tape_running) {
+ /* This should only happen if tape is stopped by isr.
+ */
+ TRACE(ft_t_flow, "waiting for tape stop");
+ if (ftape_ready_wait(ftape_timeout.pause, &status) < 0) {
+ TRACE(ft_t_flow, "drive still running (fatal)");
+ ftape_tape_running = 1; /* ? */
+ }
+ } else {
+ ftape_report_drive_status(&status);
+ }
+ if (status & QIC_STATUS_READY) {
+ /* Drive must be ready to check error state !
+ */
+ TRACE(ft_t_flow, "drive is ready");
+ if (status & QIC_STATUS_ERROR) {
+ unsigned int error;
+ qic117_cmd_t command;
+
+ /* Report and clear error state, try to continue.
+ */
+ TRACE(ft_t_flow, "error status set");
+ ftape_report_error(&error, &command, 1);
+ ftape_ready_wait(ftape_timeout.reset, &status);
+ ftape_tape_running = 0; /* ? */
+ }
+ if (check_bot_eot(status)) {
+ if (ft_location.bot) {
+ if ((status & QIC_STATUS_READY) == 0) {
+ /* tape moving away from
+ * bot/eot, let's see if we
+ * can catch up with the first
+ * segment on this track.
+ */
+ } else {
+ TRACE(ft_t_flow,
+ "start tape from logical bot");
+ logical_forward(); /* start moving */
+ }
+ } else {
+ if ((status & QIC_STATUS_READY) == 0) {
+ TRACE(ft_t_noise, "waiting for logical end of track");
+ result = ftape_ready_wait(ftape_timeout.reset, &status);
+ /* error handling needed ? */
+ } else {
+ TRACE(ft_t_noise,
+ "tape at logical end of track");
+ }
+ }
+ } else {
+ TRACE(ft_t_flow, "start tape");
+ logical_forward(); /* start moving */
+ ft_location.known = 0; /* not cleared by logical forward ! */
+ }
+ }
+ /* tape should be moving now, start reading id's
+ */
+ while (!ft_location.known &&
+ retry++ < FT_SECTORS_PER_SEGMENT &&
+ (result = ftape_read_id()) < 0) {
+
+ TRACE(ft_t_flow, "location unknown");
+
+ /* exit on signal
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+
+ /* read-id somehow failed, tape may
+ * have reached end or some other
+ * error happened.
+ */
+ TRACE(ft_t_flow, "read-id failed");
+ TRACE_CATCH(ftape_report_drive_status(&status),);
+ TRACE(ft_t_err, "ftape_report_drive_status: 0x%02x", status);
+ if (status & QIC_STATUS_READY) {
+ ftape_tape_running = 0;
+ TRACE(ft_t_noise, "tape stopped for unknown reason! "
+ "status = 0x%02x", status);
+ if (status & QIC_STATUS_ERROR ||
+ !check_bot_eot(status)) {
+ /* oops, tape stopped but not at end!
+ */
+ TRACE_EXIT -EIO;
+ }
+ }
+ }
+ TRACE(ft_t_flow,
+ "tape is positioned at segment %d", ft_location.segment);
+ TRACE_EXIT ft_location.known ? 0 : -EIO;
+}
+
+/* Get the tape running and position it just before the
+ * requested segment.
+ * Seek tape-track and reposition as needed.
+ */
+int ftape_start_tape(int segment_id, int sector_offset)
+{
+ int track = segment_id / ft_segments_per_track;
+ int result = -EIO;
+ int status;
+ static int last_segment = -1;
+ static int bad_bus_timing = 0;
+ /* number of segments passing the head between starting the tape
+ * and being able to access the first sector.
+ */
+ static int start_offset = 1;
+ int retry;
+ TRACE_FUN(ft_t_flow);
+
+ /* If sector_offset > 0, seek into wanted segment instead of
+ * into previous.
+ * This allows error recovery if a part of the segment is bad
+ * (erased) causing the tape drive to generate an index pulse
+ * thus causing a no-data error before the requested sector
+ * is reached.
+ */
+ ftape_tape_running = 0;
+ TRACE(ft_t_noise, "target segment: %d/%d%s", segment_id, sector_offset,
+ ft_buffer[ft_head]->retry > 0 ? " retry" : "");
+ if (ft_buffer[ft_head]->retry > 0) { /* this is a retry */
+ int dist = segment_id - last_segment;
+
+ if ((int)ft_history.overrun_errors < overrun_count_offset) {
+ overrun_count_offset = ft_history.overrun_errors;
+ } else if (dist < 0 || dist > 50) {
+ overrun_count_offset = ft_history.overrun_errors;
+ } else if ((ft_history.overrun_errors -
+ overrun_count_offset) >= 8) {
+ if (ftape_increase_threshold() >= 0) {
+ --ft_buffer[ft_head]->retry;
+ overrun_count_offset =
+ ft_history.overrun_errors;
+ TRACE(ft_t_warn, "increased threshold because "
+ "of excessive overrun errors");
+ } else if (!bad_bus_timing && ft_data_rate >= 1000) {
+ ftape_half_data_rate();
+ --ft_buffer[ft_head]->retry;
+ bad_bus_timing = 1;
+ overrun_count_offset =
+ ft_history.overrun_errors;
+ TRACE(ft_t_warn, "reduced datarate because "
+ "of excessive overrun errors");
+ }
+ }
+ }
+ last_segment = segment_id;
+ if (ft_location.track != track ||
+ (ftape_might_be_off_track && ft_buffer[ft_head]->retry== 0)) {
+ /* current track unknown or not equal to destination
+ */
+ ftape_ready_wait(ftape_timeout.seek, &status);
+ ftape_seek_head_to_track(track);
+ /* overrun_count_offset = ft_history.overrun_errors; */
+ }
+ result = -EIO;
+ retry = 0;
+ while (result < 0 &&
+ retry++ <= 5 &&
+ !ft_failure &&
+ !(sigtestsetmask(&current->pending.signal, _DONT_BLOCK))) {
+
+ if (retry && start_offset < 5) {
+ start_offset ++;
+ }
+ /* Check if we are able to catch the requested
+ * segment in time.
+ */
+ if ((ft_location.known || (determine_position() == 0)) &&
+ ft_location.segment >=
+ (segment_id -
+ ((ftape_tape_running || ft_location.bot)
+ ? 0 : start_offset))) {
+ /* Too far ahead (in or past target segment).
+ */
+ if (ftape_tape_running) {
+ if ((result = ftape_stop_tape(&status)) < 0) {
+ TRACE(ft_t_err,
+ "stop tape failed with code %d",
+ result);
+ break;
+ }
+ TRACE(ft_t_noise, "tape stopped");
+ ftape_tape_running = 0;
+ }
+ TRACE(ft_t_noise, "repositioning");
+ ++ft_history.rewinds;
+ if (segment_id % ft_segments_per_track < start_offset){
+ TRACE(ft_t_noise, "end of track condition\n"
+ KERN_INFO "segment_id : %d\n"
+ KERN_INFO "ft_segments_per_track: %d\n"
+ KERN_INFO "start_offset : %d",
+ segment_id, ft_segments_per_track,
+ start_offset);
+
+ /* If seeking to first segments on
+ * track better do a complete rewind
+ * to logical begin of track to get a
+ * more steady tape motion.
+ */
+ result = ftape_command_wait(
+ (ft_location.track & 1)
+ ? QIC_PHYSICAL_FORWARD
+ : QIC_PHYSICAL_REVERSE,
+ ftape_timeout.rewind, &status);
+ check_bot_eot(status); /* update location */
+ } else {
+ result= skip_reverse(segment_id - start_offset,
+ &status);
+ }
+ }
+ if (!ft_location.known) {
+ TRACE(ft_t_bug, "panic: location not known");
+ result = -EIO;
+ continue; /* while() will check for failure */
+ }
+ TRACE(ft_t_noise, "current segment: %d/%d",
+ ft_location.segment, ft_location.sector);
+ /* We're on the right track somewhere before the
+ * wanted segment. Start tape movement if needed and
+ * skip to just before or inside the requested
+ * segment. Keep tape running.
+ */
+ result = 0;
+ if (ft_location.segment <
+ (segment_id - ((ftape_tape_running || ft_location.bot)
+ ? 0 : start_offset))) {
+ if (sector_offset > 0) {
+ result = seek_forward(segment_id,
+ retry <= 3);
+ } else {
+ result = seek_forward(segment_id - 1,
+ retry <= 3);
+ }
+ }
+ if (result == 0 &&
+ ft_location.segment !=
+ (segment_id - (sector_offset > 0 ? 0 : 1))) {
+ result = -EIO;
+ }
+ }
+ if (result < 0) {
+ TRACE(ft_t_err, "failed to reposition");
+ } else {
+ ft_runner_status = running;
+ }
+ TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-rw.h b/drivers/char/ftape/lowlevel/ftape-rw.h
new file mode 100644
index 000000000000..32f4feeb887c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-rw.h
@@ -0,0 +1,111 @@
+#ifndef _FTAPE_RW_H
+#define _FTAPE_RW_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:25 $
+ *
+ * This file contains the definitions for the read and write
+ * functions for the QIC-117 floppy-tape driver for Linux.
+ *
+ * Claus-Justus Heine (1996/09/20): Add definition of format code 6
+ * Claus-Justus Heine (1996/10/04): Changed GET/PUT macros to cast to (__u8 *)
+ *
+ */
+
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-bsm.h"
+
+#include <asm/unaligned.h>
+
+#define GET2(address, offset) get_unaligned((__u16*)((__u8 *)address + offset))
+#define GET4(address, offset) get_unaligned((__u32*)((__u8 *)address + offset))
+#define GET8(address, offset) get_unaligned((__u64*)((__u8 *)address + offset))
+#define PUT2(address, offset , value) put_unaligned((value), (__u16*)((__u8 *)address + offset))
+#define PUT4(address, offset , value) put_unaligned((value), (__u32*)((__u8 *)address + offset))
+#define PUT8(address, offset , value) put_unaligned((value), (__u64*)((__u8 *)address + offset))
+
+enum runner_status_enum {
+ idle = 0,
+ running,
+ do_abort,
+ aborting,
+ logical_eot,
+ end_of_tape,
+};
+
+typedef enum ft_buffer_queue {
+ ft_queue_head = 0,
+ ft_queue_tail = 1
+} ft_buffer_queue_t;
+
+
+typedef struct {
+ int track; /* tape head position */
+ volatile int segment; /* current segment */
+ volatile int sector; /* sector offset within current segment */
+ volatile unsigned int bot; /* logical begin of track */
+ volatile unsigned int eot; /* logical end of track */
+ volatile unsigned int known; /* validates bot, segment, sector */
+} location_record;
+
+/* Count nr of 1's in pattern.
+ */
+static inline int count_ones(unsigned long mask)
+{
+ int bits;
+
+ for (bits = 0; mask != 0; mask >>= 1) {
+ if (mask & 1) {
+ ++bits;
+ }
+ }
+ return bits;
+}
+
+#define FT_MAX_NR_BUFFERS 16 /* arbitrary value */
+/* ftape-rw.c defined global vars.
+ */
+extern buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS];
+extern int ft_nr_buffers;
+extern location_record ft_location;
+extern volatile int ftape_tape_running;
+
+/* ftape-rw.c defined global functions.
+ */
+extern int ftape_setup_new_segment(buffer_struct * buff,
+ int segment_id,
+ int offset);
+extern int ftape_calc_next_cluster(buffer_struct * buff);
+extern buffer_struct *ftape_next_buffer (ft_buffer_queue_t pos);
+extern buffer_struct *ftape_get_buffer (ft_buffer_queue_t pos);
+extern int ftape_buffer_id (ft_buffer_queue_t pos);
+extern void ftape_reset_buffer(void);
+extern void ftape_tape_parameters(__u8 drive_configuration);
+extern int ftape_wait_segment(buffer_state_enum state);
+extern int ftape_dumb_stop(void);
+extern int ftape_start_tape(int segment_id, int offset);
+extern int ftape_stop_tape(int *pstatus);
+extern int ftape_handle_logical_eot(void);
+extern buffer_state_enum ftape_set_state(buffer_state_enum new_state);
+#endif /* _FTAPE_RW_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-setup.c b/drivers/char/ftape/lowlevel/ftape-setup.c
new file mode 100644
index 000000000000..280a1a55d87e
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-setup.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-setup.c,v $
+ * $Revision: 1.7 $
+ * $Date: 1997/10/10 09:57:06 $
+ *
+ * This file contains the code for processing the kernel command
+ * line options for the QIC-40/80/3010/3020 floppy-tape driver
+ * "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+
+static struct param_table {
+ const char *name;
+ int *var;
+ int def_param;
+ int min;
+ int max;
+} config_params[] __initdata = {
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+ { "tracing", &ftape_tracing, 3, ft_t_bug, ft_t_any},
+#endif
+ { "ioport", &ft_fdc_base, CONFIG_FT_FDC_BASE, 0x0, 0xfff},
+ { "irq", &ft_fdc_irq, CONFIG_FT_FDC_IRQ, 2, 15},
+ { "dma", &ft_fdc_dma, CONFIG_FT_FDC_DMA, 0, 3},
+ { "threshold", &ft_fdc_threshold, CONFIG_FT_FDC_THR, 1, 16},
+ { "datarate", &ft_fdc_rate_limit, CONFIG_FT_FDC_MAX_RATE, 500, 2000},
+ { "fc10", &ft_probe_fc10, CONFIG_FT_PROBE_FC10, 0, 1},
+ { "mach2", &ft_mach2, CONFIG_FT_MACH2, 0, 1}
+};
+
+static int __init ftape_setup(char *str)
+{
+ int i;
+ int param;
+ int ints[2];
+
+ TRACE_FUN(ft_t_flow);
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+ if (str) {
+ for (i=0; i < NR_ITEMS(config_params); i++) {
+ if (strcmp(str,config_params[i].name) == 0){
+ if (ints[0]) {
+ param = ints[1];
+ } else {
+ param = config_params[i].def_param;
+ }
+ if (param < config_params[i].min ||
+ param > config_params[i].max) {
+ TRACE(ft_t_err,
+ "parameter %s out of range %d ... %d",
+ config_params[i].name,
+ config_params[i].min,
+ config_params[i].max);
+ goto out;
+ }
+ if(config_params[i].var) {
+ TRACE(ft_t_info, "%s=%d", str, param);
+ *config_params[i].var = param;
+ }
+ goto out;
+ }
+ }
+ }
+ if (str) {
+ TRACE(ft_t_err, "unknown ftape option [%s]", str);
+
+ TRACE(ft_t_err, "allowed options are:");
+ for (i=0; i < NR_ITEMS(config_params); i++) {
+ TRACE(ft_t_err, " %s",config_params[i].name);
+ }
+ } else {
+ TRACE(ft_t_err, "botched ftape option");
+ }
+ out:
+ TRACE_EXIT 1;
+}
+
+__setup("ftape=", ftape_setup);
diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.c b/drivers/char/ftape/lowlevel/ftape-tracing.c
new file mode 100644
index 000000000000..7fdc6567440b
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-tracing.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:27 $
+ *
+ * This file contains the reading code
+ * for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+
+/* Global vars.
+ */
+/* tracing
+ * set it to: to log :
+ * 0 bugs
+ * 1 + errors
+ * 2 + warnings
+ * 3 + information
+ * 4 + more information
+ * 5 + program flow
+ * 6 + fdc/dma info
+ * 7 + data flow
+ * 8 + everything else
+ */
+ft_trace_t ftape_tracing = ft_t_info; /* Default level: information and up */
+int ftape_function_nest_level;
+
+/* Local vars.
+ */
+static __u8 trace_id;
+static char spacing[] = "* ";
+
+void ftape_trace_call(const char *file, const char *name)
+{
+ char *indent;
+
+ /* Since printk seems not to work with "%*s" format
+ * we'll use this work-around.
+ */
+ if (ftape_function_nest_level < 0) {
+ printk(KERN_INFO "function nest level (%d) < 0\n",
+ ftape_function_nest_level);
+ ftape_function_nest_level = 0;
+ }
+ if (ftape_function_nest_level < sizeof(spacing)) {
+ indent = (spacing +
+ sizeof(spacing) - 1 -
+ ftape_function_nest_level);
+ } else {
+ indent = spacing;
+ }
+ printk(KERN_INFO "[%03d]%s+%s (%s)\n",
+ (int) trace_id++, indent, file, name);
+}
+
+void ftape_trace_exit(const char *file, const char *name)
+{
+ char *indent;
+
+ /* Since printk seems not to work with "%*s" format
+ * we'll use this work-around.
+ */
+ if (ftape_function_nest_level < 0) {
+ printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level);
+ ftape_function_nest_level = 0;
+ }
+ if (ftape_function_nest_level < sizeof(spacing)) {
+ indent = (spacing +
+ sizeof(spacing) - 1 -
+ ftape_function_nest_level);
+ } else {
+ indent = spacing;
+ }
+ printk(KERN_INFO "[%03d]%s-%s (%s)\n",
+ (int) trace_id++, indent, file, name);
+}
+
+void ftape_trace_log(const char *file, const char *function)
+{
+ char *indent;
+
+ /* Since printk seems not to work with "%*s" format
+ * we'll use this work-around.
+ */
+ if (ftape_function_nest_level < 0) {
+ printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level);
+ ftape_function_nest_level = 0;
+ }
+ if (ftape_function_nest_level < sizeof(spacing)) {
+ indent = (spacing +
+ sizeof(spacing) - 1 -
+ ftape_function_nest_level);
+ } else {
+ indent = spacing;
+ }
+ printk(KERN_INFO "[%03d]%s%s (%s) - ",
+ (int) trace_id++, indent, file, function);
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.h b/drivers/char/ftape/lowlevel/ftape-tracing.h
new file mode 100644
index 000000000000..fa7cd20ee66c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-tracing.h
@@ -0,0 +1,180 @@
+#ifndef _FTAPE_TRACING_H
+#define _FTAPE_TRACING_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:28 $
+ *
+ * This file contains definitions that eases the debugging of the
+ * QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+
+/*
+ * Be very careful with TRACE_EXIT and TRACE_ABORT.
+ *
+ * if (something) TRACE_EXIT error;
+ *
+ * will NOT work. Use
+ *
+ * if (something) {
+ * TRACE_EXIT error;
+ * }
+ *
+ * instead. Maybe a bit dangerous, but save lots of lines of code.
+ */
+
+#define LL_X "%d/%d KB"
+#define LL(x) (unsigned int)((__u64)(x)>>10), (unsigned int)((x)&1023)
+
+typedef enum {
+ ft_t_nil = -1,
+ ft_t_bug,
+ ft_t_err,
+ ft_t_warn,
+ ft_t_info,
+ ft_t_noise,
+ ft_t_flow,
+ ft_t_fdc_dma,
+ ft_t_data_flow,
+ ft_t_any
+} ft_trace_t;
+
+#ifdef CONFIG_FT_NO_TRACE_AT_ALL
+/* the compiler will optimize away most TRACE() macros
+ */
+#define FT_TRACE_TOP_LEVEL ft_t_bug
+#define TRACE_FUN(level) do {} while(0)
+#define TRACE_EXIT return
+#define TRACE(l, m, i...) \
+{ \
+ if ((ft_trace_t)(l) == FT_TRACE_TOP_LEVEL) { \
+ printk(KERN_INFO"ftape%s(%s):\n" \
+ KERN_INFO m".\n" ,__FILE__, __FUNCTION__ , ##i); \
+ } \
+}
+#define SET_TRACE_LEVEL(l) if ((l) == (l)) do {} while(0)
+#define TRACE_LEVEL FT_TRACE_TOP_LEVEL
+
+#else
+
+#ifdef CONFIG_FT_NO_TRACE
+/* the compiler will optimize away many TRACE() macros
+ * the ftape_simple_trace_call() function simply increments
+ * the function nest level.
+ */
+#define FT_TRACE_TOP_LEVEL ft_t_warn
+#define TRACE_FUN(level) ftape_function_nest_level++
+#define TRACE_EXIT ftape_function_nest_level--; return
+
+#else
+#ifdef CONFIG_FT_FULL_DEBUG
+#define FT_TRACE_TOP_LEVEL ft_t_any
+#else
+#define FT_TRACE_TOP_LEVEL ft_t_flow
+#endif
+#define TRACE_FUN(level) \
+ const ft_trace_t _tracing = level; \
+ if (ftape_tracing >= (ft_trace_t)(level) && \
+ (ft_trace_t)(level) <= FT_TRACE_TOP_LEVEL) \
+ ftape_trace_call(__FILE__, __FUNCTION__); \
+ ftape_function_nest_level ++;
+
+#define TRACE_EXIT \
+ --ftape_function_nest_level; \
+ if (ftape_tracing >= (ft_trace_t)(_tracing) && \
+ (ft_trace_t)(_tracing) <= FT_TRACE_TOP_LEVEL) \
+ ftape_trace_exit(__FILE__, __FUNCTION__); \
+ return
+
+#endif
+
+#define TRACE(l, m, i...) \
+{ \
+ if (ftape_tracing >= (ft_trace_t)(l) && \
+ (ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) { \
+ ftape_trace_log(__FILE__, __FUNCTION__); \
+ printk(m".\n" ,##i); \
+ } \
+}
+
+#define SET_TRACE_LEVEL(l) \
+{ \
+ if ((ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) { \
+ ftape_tracing = (ft_trace_t)(l); \
+ } else { \
+ ftape_tracing = FT_TRACE_TOP_LEVEL; \
+ } \
+}
+#define TRACE_LEVEL \
+((ftape_tracing <= FT_TRACE_TOP_LEVEL) ? ftape_tracing : FT_TRACE_TOP_LEVEL)
+
+
+/* Global variables declared in tracing.c
+ */
+extern ft_trace_t ftape_tracing; /* sets default level */
+extern int ftape_function_nest_level;
+
+/* Global functions declared in tracing.c
+ */
+extern void ftape_trace_call(const char *file, const char *name);
+extern void ftape_trace_exit(const char *file, const char *name);
+extern void ftape_trace_log (const char *file, const char *name);
+
+#endif /* !defined(CONFIG_FT_NO_TRACE_AT_ALL) */
+
+/*
+ * Abort with a message.
+ */
+#define TRACE_ABORT(res, i...) \
+{ \
+ TRACE(i); \
+ TRACE_EXIT res; \
+}
+
+/* The following transforms the common "if(result < 0) ... " into a
+ * one-liner.
+ */
+#define _TRACE_CATCH(level, fun, action) \
+{ \
+ int _res = (fun); \
+ if (_res < 0) { \
+ do { action /* */ ; } while(0); \
+ TRACE_ABORT(_res, level, "%s failed: %d", #fun, _res); \
+ } \
+}
+
+#define TRACE_CATCH(fun, fail) _TRACE_CATCH(ft_t_err, fun, fail)
+
+/* Abort the current function when signalled. This doesn't belong here,
+ * but rather into ftape-rw.h (maybe)
+ */
+#define FT_SIGNAL_EXIT(sig_mask) \
+ if (sigtestsetmask(&current->pending.signal, sig_mask)) { \
+ TRACE_ABORT(-EINTR, \
+ ft_t_warn, \
+ "interrupted by non-blockable signal"); \
+ }
+
+#endif /* _FTAPE_TRACING_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-write.c b/drivers/char/ftape/lowlevel/ftape-write.c
new file mode 100644
index 000000000000..45601ec801ee
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-write.c
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 1993-1995 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.c,v $
+ * $Revision: 1.3.4.1 $
+ * $Date: 1997/11/14 18:07:04 $
+ *
+ * This file contains the writing code
+ * for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/fdc-isr.h"
+
+/* Global vars.
+ */
+
+/* Local vars.
+ */
+static int last_write_failed;
+
+void ftape_zap_write_buffers(void)
+{
+ int i;
+
+ for (i = 0; i < ft_nr_buffers; ++i) {
+ ft_buffer[i]->status = done;
+ }
+ ftape_reset_buffer();
+}
+
+static int copy_and_gen_ecc(void *destination,
+ const void *source,
+ const SectorMap bad_sector_map)
+{
+ int result;
+ struct memory_segment mseg;
+ int bads = count_ones(bad_sector_map);
+ TRACE_FUN(ft_t_any);
+
+ if (bads > 0) {
+ TRACE(ft_t_noise, "bad sectors in map: %d", bads);
+ }
+ if (bads + 3 >= FT_SECTORS_PER_SEGMENT) {
+ TRACE(ft_t_noise, "empty segment");
+ mseg.blocks = 0; /* skip entire segment */
+ result = 0; /* nothing written */
+ } else {
+ mseg.blocks = FT_SECTORS_PER_SEGMENT - bads;
+ mseg.data = destination;
+ memcpy(mseg.data, source, (mseg.blocks - 3) * FT_SECTOR_SIZE);
+ result = ftape_ecc_set_segment_parity(&mseg);
+ if (result < 0) {
+ TRACE(ft_t_err, "ecc_set_segment_parity failed");
+ } else {
+ result = (mseg.blocks - 3) * FT_SECTOR_SIZE;
+ }
+ }
+ TRACE_EXIT result;
+}
+
+
+int ftape_start_writing(const ft_write_mode_t mode)
+{
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+ int segment_id = head->segment_id;
+ int result;
+ buffer_state_enum wanted_state = (mode == FT_WR_DELETE
+ ? deleting
+ : writing);
+ TRACE_FUN(ft_t_flow);
+
+ if ((ft_driver_state != wanted_state) || head->status != waiting) {
+ TRACE_EXIT 0;
+ }
+ ftape_setup_new_segment(head, segment_id, 1);
+ if (mode == FT_WR_SINGLE) {
+ /* stop tape instead of pause */
+ head->next_segment = 0;
+ }
+ ftape_calc_next_cluster(head); /* prepare */
+ head->status = ft_driver_state; /* either writing or deleting */
+ if (ft_runner_status == idle) {
+ TRACE(ft_t_noise,
+ "starting runner for segment %d", segment_id);
+ TRACE_CATCH(ftape_start_tape(segment_id,head->sector_offset),);
+ } else {
+ TRACE(ft_t_noise, "runner not idle, not starting tape");
+ }
+ /* go */
+ result = fdc_setup_read_write(head, (mode == FT_WR_DELETE
+ ? FDC_WRITE_DELETED : FDC_WRITE));
+ ftape_set_state(wanted_state); /* should not be necessary */
+ TRACE_EXIT result;
+}
+
+/* Wait until all data is actually written to tape.
+ *
+ * There is a problem: when the tape runs into logical EOT, then this
+ * failes. We need to restart the runner in this case.
+ */
+int ftape_loop_until_writes_done(void)
+{
+ buffer_struct *head;
+ TRACE_FUN(ft_t_flow);
+
+ while ((ft_driver_state == writing || ft_driver_state == deleting) &&
+ ftape_get_buffer(ft_queue_head)->status != done) {
+ /* set the runner status to idle if at lEOT */
+ TRACE_CATCH(ftape_handle_logical_eot(), last_write_failed = 1);
+ /* restart the tape if necessary */
+ if (ft_runner_status == idle) {
+ TRACE(ft_t_noise, "runner is idle, restarting");
+ if (ft_driver_state == deleting) {
+ TRACE_CATCH(ftape_start_writing(FT_WR_DELETE),
+ last_write_failed = 1);
+ } else {
+ TRACE_CATCH(ftape_start_writing(FT_WR_MULTI),
+ last_write_failed = 1);
+ }
+ }
+ TRACE(ft_t_noise, "tail: %d, head: %d",
+ ftape_buffer_id(ft_queue_tail),
+ ftape_buffer_id(ft_queue_head));
+ TRACE_CATCH(fdc_interrupt_wait(5 * FT_SECOND),
+ last_write_failed = 1);
+ head = ftape_get_buffer(ft_queue_head);
+ if (head->status == error) {
+ /* Allow escape from loop when signaled !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ if (head->hard_error_map != 0) {
+ /* Implement hard write error recovery here
+ */
+ }
+ /* retry this one */
+ head->status = waiting;
+ if (ft_runner_status == aborting) {
+ ftape_dumb_stop();
+ }
+ if (ft_runner_status != idle) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "unexpected state: "
+ "ft_runner_status != idle");
+ }
+ ftape_start_writing(ft_driver_state == deleting
+ ? FT_WR_MULTI : FT_WR_DELETE);
+ }
+ TRACE(ft_t_noise, "looping until writes done");
+ }
+ ftape_set_state(idle);
+ TRACE_EXIT 0;
+}
+
+/* Write given segment from buffer at address to tape.
+ */
+static int write_segment(const int segment_id,
+ const void *address,
+ const ft_write_mode_t write_mode)
+{
+ int bytes_written = 0;
+ buffer_struct *tail;
+ buffer_state_enum wanted_state = (write_mode == FT_WR_DELETE
+ ? deleting : writing);
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "segment_id = %d", segment_id);
+ if (ft_driver_state != wanted_state) {
+ if (ft_driver_state == deleting ||
+ wanted_state == deleting) {
+ TRACE_CATCH(ftape_loop_until_writes_done(),);
+ }
+ TRACE(ft_t_noise, "calling ftape_abort_operation");
+ TRACE_CATCH(ftape_abort_operation(),);
+ ftape_zap_write_buffers();
+ ftape_set_state(wanted_state);
+ }
+ /* if all buffers full we'll have to wait...
+ */
+ ftape_wait_segment(wanted_state);
+ tail = ftape_get_buffer(ft_queue_tail);
+ switch(tail->status) {
+ case done:
+ ft_history.defects += count_ones(tail->hard_error_map);
+ break;
+ case waiting:
+ /* this could happen with multiple EMPTY_SEGMENTs, but
+ * shouldn't happen any more as we re-start the runner even
+ * with an empty segment.
+ */
+ bytes_written = -EAGAIN;
+ break;
+ case error:
+ /* setup for a retry
+ */
+ tail->status = waiting;
+ bytes_written = -EAGAIN; /* force retry */
+ if (tail->hard_error_map != 0) {
+ TRACE(ft_t_warn,
+ "warning: %d hard error(s) in written segment",
+ count_ones(tail->hard_error_map));
+ TRACE(ft_t_noise, "hard_error_map = 0x%08lx",
+ (long)tail->hard_error_map);
+ /* Implement hard write error recovery here
+ */
+ }
+ break;
+ default:
+ TRACE_ABORT(-EIO, ft_t_err,
+ "wait for empty segment failed, tail status: %d",
+ tail->status);
+ }
+ /* should runner stop ?
+ */
+ if (ft_runner_status == aborting) {
+ buffer_struct *head = ftape_get_buffer(ft_queue_head);
+ if (head->status == wanted_state) {
+ head->status = done; /* ???? */
+ }
+ /* don't call abort_operation(), we don't want to zap
+ * the dma buffers
+ */
+ TRACE_CATCH(ftape_dumb_stop(),);
+ } else {
+ /* If just passed last segment on tape: wait for BOT
+ * or EOT mark. Sets ft_runner_status to idle if at lEOT
+ * and successful
+ */
+ TRACE_CATCH(ftape_handle_logical_eot(),);
+ }
+ if (tail->status == done) {
+ /* now at least one buffer is empty, fill it with our
+ * data. skip bad sectors and generate ecc.
+ * copy_and_gen_ecc return nr of bytes written, range
+ * 0..29 Kb inclusive!
+ *
+ * Empty segments are handled inside coyp_and_gen_ecc()
+ */
+ if (write_mode != FT_WR_DELETE) {
+ TRACE_CATCH(bytes_written = copy_and_gen_ecc(
+ tail->address, address,
+ ftape_get_bad_sector_entry(segment_id)),);
+ }
+ tail->segment_id = segment_id;
+ tail->status = waiting;
+ tail = ftape_next_buffer(ft_queue_tail);
+ }
+ /* Start tape only if all buffers full or flush mode.
+ * This will give higher probability of streaming.
+ */
+ if (ft_runner_status != running &&
+ ((tail->status == waiting &&
+ ftape_get_buffer(ft_queue_head) == tail) ||
+ write_mode != FT_WR_ASYNC)) {
+ TRACE_CATCH(ftape_start_writing(write_mode),);
+ }
+ TRACE_EXIT bytes_written;
+}
+
+/* Write as much as fits from buffer to the given segment on tape
+ * and handle retries.
+ * Return the number of bytes written (>= 0), or:
+ * -EIO write failed
+ * -EINTR interrupted by signal
+ * -ENOSPC device full
+ */
+int ftape_write_segment(const int segment_id,
+ const void *buffer,
+ const ft_write_mode_t flush)
+{
+ int retry = 0;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ ft_history.used |= 2;
+ if (segment_id >= ft_tracks_per_tape*ft_segments_per_track) {
+ /* tape full */
+ TRACE_ABORT(-ENOSPC, ft_t_err,
+ "invalid segment id: %d (max %d)",
+ segment_id,
+ ft_tracks_per_tape * ft_segments_per_track -1);
+ }
+ for (;;) {
+ if ((result = write_segment(segment_id, buffer, flush)) >= 0) {
+ if (result == 0) { /* empty segment */
+ TRACE(ft_t_noise,
+ "empty segment, nothing written");
+ }
+ TRACE_EXIT result;
+ }
+ if (result == -EAGAIN) {
+ if (++retry > 100) { /* give up */
+ TRACE_ABORT(-EIO, ft_t_err,
+ "write failed, >100 retries in segment");
+ }
+ TRACE(ft_t_warn, "write error, retry %d (%d)",
+ retry,
+ ftape_get_buffer(ft_queue_tail)->segment_id);
+ } else {
+ TRACE_ABORT(result, ft_t_err,
+ "write_segment failed, error: %d", result);
+ }
+ /* Allow escape from loop when signaled !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ }
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-write.h b/drivers/char/ftape/lowlevel/ftape-write.h
new file mode 100644
index 000000000000..0e7f898b7af9
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-write.h
@@ -0,0 +1,53 @@
+#ifndef _FTAPE_WRITE_H
+#define _FTAPE_WRITE_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven,
+ * (C) 1996-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.h,v $
+ $Author: claus $
+ *
+ $Revision: 1.2 $
+ $Date: 1997/10/05 19:18:30 $
+ $State: Exp $
+ *
+ * This file contains the definitions for the write functions
+ * for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+
+/* ftape-write.c defined global functions.
+ */
+typedef enum {
+ FT_WR_ASYNC = 0, /* start tape only when all buffers are full */
+ FT_WR_MULTI = 1, /* start tape, but don't necessarily stop */
+ FT_WR_SINGLE = 2, /* write a single segment and stop afterwards */
+ FT_WR_DELETE = 3 /* write deleted data marks */
+} ft_write_mode_t;
+
+extern int ftape_start_writing(const ft_write_mode_t mode);
+extern int ftape_write_segment(const int segment,
+ const void *address,
+ const ft_write_mode_t flushing);
+extern void ftape_zap_write_buffers(void);
+extern int ftape_loop_until_writes_done(void);
+
+#endif /* _FTAPE_WRITE_H */
+
diff --git a/drivers/char/ftape/lowlevel/ftape_syms.c b/drivers/char/ftape/lowlevel/ftape_syms.c
new file mode 100644
index 000000000000..5dc3a380c9bf
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape_syms.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 1996-1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape_syms.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/10/17 00:03:51 $
+ *
+ * This file contains the symbols that the ftape low level
+ * part of the QIC-40/80/3010/3020 floppy-tape driver "ftape"
+ * exports to its high level clients
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-format.h"
+
+/* bad sector handling from ftape-bsm.c */
+EXPORT_SYMBOL(ftape_get_bad_sector_entry);
+EXPORT_SYMBOL(ftape_find_end_of_bsm_list);
+/* from ftape-rw.c */
+EXPORT_SYMBOL(ftape_set_state);
+/* from ftape-ctl.c */
+EXPORT_SYMBOL(ftape_seek_to_bot);
+EXPORT_SYMBOL(ftape_seek_to_eot);
+EXPORT_SYMBOL(ftape_abort_operation);
+EXPORT_SYMBOL(ftape_get_status);
+EXPORT_SYMBOL(ftape_enable);
+EXPORT_SYMBOL(ftape_disable);
+EXPORT_SYMBOL(ftape_mmap);
+EXPORT_SYMBOL(ftape_calibrate_data_rate);
+/* from ftape-io.c */
+EXPORT_SYMBOL(ftape_reset_drive);
+EXPORT_SYMBOL(ftape_command);
+EXPORT_SYMBOL(ftape_parameter);
+EXPORT_SYMBOL(ftape_ready_wait);
+EXPORT_SYMBOL(ftape_report_operation);
+EXPORT_SYMBOL(ftape_report_error);
+/* from ftape-read.c */
+EXPORT_SYMBOL(ftape_read_segment_fraction);
+EXPORT_SYMBOL(ftape_zap_read_buffers);
+EXPORT_SYMBOL(ftape_read_header_segment);
+EXPORT_SYMBOL(ftape_decode_header_segment);
+/* from ftape-write.c */
+EXPORT_SYMBOL(ftape_write_segment);
+EXPORT_SYMBOL(ftape_start_writing);
+EXPORT_SYMBOL(ftape_loop_until_writes_done);
+/* from ftape-buffer.h */
+EXPORT_SYMBOL(ftape_set_nr_buffers);
+/* from ftape-format.h */
+EXPORT_SYMBOL(ftape_format_track);
+EXPORT_SYMBOL(ftape_format_status);
+EXPORT_SYMBOL(ftape_verify_segment);
+/* from tracing.c */
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+EXPORT_SYMBOL(ftape_tracing);
+EXPORT_SYMBOL(ftape_function_nest_level);
+EXPORT_SYMBOL(ftape_trace_call);
+EXPORT_SYMBOL(ftape_trace_exit);
+EXPORT_SYMBOL(ftape_trace_log);
+#endif
+
diff --git a/drivers/char/ftape/zftape/Makefile b/drivers/char/ftape/zftape/Makefile
new file mode 100644
index 000000000000..6d91c1f77c05
--- /dev/null
+++ b/drivers/char/ftape/zftape/Makefile
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 1996, 1997 Claus-Justus Heine.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; see the file COPYING. If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/zftape/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/05 19:18:58 $
+#
+# Makefile for the QIC-40/80/3010/3020 zftape interface VFS to
+# ftape
+#
+
+
+# ZFT_OBSOLETE - enable the MTIOC_ZFTAPE_GETBLKSZ ioctl. You should
+# leave this enabled for compatibility with taper.
+
+obj-$(CONFIG_ZFTAPE) += zftape.o
+
+zftape-objs := zftape-rw.o zftape-ctl.o zftape-read.o \
+ zftape-write.o zftape-vtbl.o zftape-eof.o \
+ zftape-init.o zftape-buffers.o zftape_syms.o
+
+EXTRA_CFLAGS := -DZFT_OBSOLETE
diff --git a/drivers/char/ftape/zftape/zftape-buffers.c b/drivers/char/ftape/zftape/zftape-buffers.c
new file mode 100644
index 000000000000..da06f138334e
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-buffers.c
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 1995-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:59 $
+ *
+ * This file contains the dynamic buffer allocation routines
+ * of zftape
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/zftape.h>
+
+#include <linux/vmalloc.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/* global variables
+ */
+
+/* local varibales
+ */
+static unsigned int used_memory;
+static unsigned int peak_memory;
+
+void zft_memory_stats(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Memory usage (vmalloc allocations):\n"
+ KERN_INFO "total allocated: %d\n"
+ KERN_INFO "peak allocation: %d",
+ used_memory, peak_memory);
+ peak_memory = used_memory;
+ TRACE_EXIT;
+}
+
+int zft_vcalloc_once(void *new, size_t size)
+{
+ TRACE_FUN(ft_t_flow);
+ if (zft_vmalloc_once(new, size) < 0) {
+ TRACE_EXIT -ENOMEM;
+ }
+ memset(*(void **)new, '\0', size);
+ TRACE_EXIT 0;
+}
+int zft_vmalloc_once(void *new, size_t size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (*(void **)new != NULL || size == 0) {
+ TRACE_EXIT 0;
+ }
+ if ((*(void **)new = vmalloc(size)) == NULL) {
+ TRACE_EXIT -ENOMEM;
+ }
+ used_memory += size;
+ if (peak_memory < used_memory) {
+ peak_memory = used_memory;
+ }
+ TRACE_ABORT(0, ft_t_noise,
+ "allocated buffer @ %p, %d bytes", *(void **)new, size);
+}
+int zft_vmalloc_always(void *new, size_t size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_vfree(new, size);
+ TRACE_EXIT zft_vmalloc_once(new, size);
+}
+void zft_vfree(void *old, size_t size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (*(void **)old) {
+ vfree(*(void **)old);
+ used_memory -= size;
+ TRACE(ft_t_noise, "released buffer @ %p, %d bytes",
+ *(void **)old, size);
+ *(void **)old = NULL;
+ }
+ TRACE_EXIT;
+}
+
+void *zft_kmalloc(size_t size)
+{
+ void *new;
+
+ while ((new = kmalloc(size, GFP_KERNEL)) == NULL) {
+ msleep_interruptible(100);
+ }
+ memset(new, 0, size);
+ used_memory += size;
+ if (peak_memory < used_memory) {
+ peak_memory = used_memory;
+ }
+ return new;
+}
+
+void zft_kfree(void *old, size_t size)
+{
+ kfree(old);
+ used_memory -= size;
+}
+
+/* there are some more buffers that are allocated on demand.
+ * cleanup_module() calles this function to be sure to have released
+ * them
+ */
+void zft_uninit_mem(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_vfree(&zft_hseg_buf, FT_SEGMENT_SIZE);
+ zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); zft_deblock_segment = -1;
+ zft_free_vtbl();
+ if (zft_cmpr_lock(0 /* don't load */) == 0) {
+ (*zft_cmpr_ops->cleanup)();
+ (*zft_cmpr_ops->reset)(); /* unlock it again */
+ }
+ zft_memory_stats();
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-buffers.h b/drivers/char/ftape/zftape/zftape-buffers.h
new file mode 100644
index 000000000000..798e3128c682
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-buffers.h
@@ -0,0 +1,55 @@
+#ifndef _FTAPE_DYNMEM_H
+#define _FTAPE_DYNMEM_H
+
+/*
+ * Copyright (C) 1995-1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:59 $
+ *
+ * memory allocation routines.
+ *
+ */
+
+/* we do not allocate all of the really large buffer memory before
+ * someone tries to open the drive. ftape_open() may fail with
+ * -ENOMEM, but that's better having 200k of vmalloced memory which
+ * cannot be swapped out.
+ */
+
+extern void zft_memory_stats(void);
+extern int zft_vmalloc_once(void *new, size_t size);
+extern int zft_vcalloc_once(void *new, size_t size);
+extern int zft_vmalloc_always(void *new, size_t size);
+extern void zft_vfree(void *old, size_t size);
+extern void *zft_kmalloc(size_t size);
+extern void zft_kfree(void *old, size_t size);
+
+/* called by cleanup_module()
+ */
+extern void zft_uninit_mem(void);
+
+#endif
+
+
+
+
+
+
+
diff --git a/drivers/char/ftape/zftape/zftape-ctl.c b/drivers/char/ftape/zftape/zftape-ctl.c
new file mode 100644
index 000000000000..6c7874e5c199
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-ctl.c
@@ -0,0 +1,1418 @@
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.c,v $
+ * $Revision: 1.2.6.2 $
+ * $Date: 1997/11/14 18:07:33 $
+ *
+ * This file contains the non-read/write zftape functions
+ * for the QIC-40/80/3010/3020 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/fcntl.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/* Global vars.
+ */
+int zft_write_protected; /* this is when cartridge rdonly or O_RDONLY */
+int zft_header_read;
+int zft_offline;
+unsigned int zft_unit;
+int zft_resid;
+int zft_mt_compression;
+
+/* Local vars.
+ */
+static int going_offline;
+
+typedef int (mt_fun)(int *argptr);
+typedef int (*mt_funp)(int *argptr);
+typedef struct
+{
+ mt_funp function;
+ unsigned offline : 1; /* op permitted if offline or no_tape */
+ unsigned write_protected : 1; /* op permitted if write-protected */
+ unsigned not_formatted : 1; /* op permitted if tape not formatted */
+ unsigned raw_mode : 1; /* op permitted if zft_mode == 0 */
+ unsigned need_idle_state : 1; /* need to call def_idle_state */
+ char *name;
+} fun_entry;
+
+static mt_fun mt_dummy, mt_reset, mt_fsr, mt_bsr, mt_rew, mt_offl, mt_nop,
+ mt_weof, mt_erase, mt_ras2, mt_setblk, mt_setdensity,
+ mt_seek, mt_tell, mt_reten, mt_eom, mt_fsf, mt_bsf,
+ mt_fsfm, mt_bsfm, mt_setdrvbuffer, mt_compression;
+
+static fun_entry mt_funs[]=
+{
+ {mt_reset , 1, 1, 1, 1, 0, "MT_RESET" }, /* 0 */
+ {mt_fsf , 0, 1, 0, 0, 1, "MT_FSF" },
+ {mt_bsf , 0, 1, 0, 0, 1, "MT_BSF" },
+ {mt_fsr , 0, 1, 0, 1, 1, "MT_FSR" },
+ {mt_bsr , 0, 1, 0, 1, 1, "MT_BSR" },
+ {mt_weof , 0, 0, 0, 0, 0, "MT_WEOF" }, /* 5 */
+ {mt_rew , 0, 1, 1, 1, 0, "MT_REW" },
+ {mt_offl , 0, 1, 1, 1, 0, "MT_OFFL" },
+ {mt_nop , 1, 1, 1, 1, 0, "MT_NOP" },
+ {mt_reten , 0, 1, 1, 1, 0, "MT_RETEN" },
+ {mt_bsfm , 0, 1, 0, 0, 1, "MT_BSFM" }, /* 10 */
+ {mt_fsfm , 0, 1, 0, 0, 1, "MT_FSFM" },
+ {mt_eom , 0, 1, 0, 0, 1, "MT_EOM" },
+ {mt_erase , 0, 0, 0, 1, 0, "MT_ERASE" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_RAS1" },
+ {mt_ras2 , 0, 0, 0, 1, 0, "MT_RAS2" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_RAS3" },
+ {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" },
+ {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" },
+ {mt_dummy , 1, 1, 1, 1, 0, "UNKNOWN" },
+ {mt_setblk , 1, 1, 1, 1, 1, "MT_SETBLK"}, /* 20 */
+ {mt_setdensity , 1, 1, 1, 1, 0, "MT_SETDENSITY"},
+ {mt_seek , 0, 1, 0, 1, 1, "MT_SEEK" },
+ {mt_dummy , 0, 1, 0, 1, 1, "MT_TELL" }, /* wr-only ?! */
+ {mt_setdrvbuffer, 1, 1, 1, 1, 0, "MT_SETDRVBUFFER" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_FSS" }, /* 25 */
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_BSS" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_WSM" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_LOCK" },
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_UNLOCK"},
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_LOAD" }, /* 30 */
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_UNLOAD"},
+ {mt_compression , 1, 1, 1, 0, 1, "MT_COMPRESSION"},
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_SETPART"},
+ {mt_dummy , 1, 1, 1, 1, 0, "MT_MKPART"}
+};
+
+#define NR_MT_CMDS NR_ITEMS(mt_funs)
+
+void zft_reset_position(zft_position *pos)
+{
+ TRACE_FUN(ft_t_flow);
+
+ pos->seg_byte_pos =
+ pos->volume_pos = 0;
+ if (zft_header_read) {
+ /* need to keep track of the volume table and
+ * compression map. We therefor simply
+ * position at the beginning of the first
+ * volume. This covers old ftape archives as
+ * well has various flavours of the
+ * compression map segments. The worst case is
+ * that the compression map shows up as a
+ * additional volume in front of all others.
+ */
+ pos->seg_pos = zft_find_volume(0)->start_seg;
+ pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+ } else {
+ pos->tape_pos = 0;
+ pos->seg_pos = -1;
+ }
+ zft_just_before_eof = 0;
+ zft_deblock_segment = -1;
+ zft_io_state = zft_idle;
+ zft_zap_read_buffers();
+ zft_prevent_flush();
+ /* unlock the compresison module if it is loaded.
+ * The zero arg means not to try to load the module.
+ */
+ if (zft_cmpr_lock(0) == 0) {
+ (*zft_cmpr_ops->reset)(); /* unlock */
+ }
+ TRACE_EXIT;
+}
+
+static void zft_init_driver(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_resid =
+ zft_header_read =
+ zft_old_ftape =
+ zft_offline =
+ zft_write_protected =
+ going_offline =
+ zft_mt_compression =
+ zft_header_changed =
+ zft_volume_table_changed =
+ zft_written_segments = 0;
+ zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ;
+ zft_reset_position(&zft_pos); /* does most of the stuff */
+ ftape_zap_read_buffers();
+ ftape_set_state(idle);
+ TRACE_EXIT;
+}
+
+int zft_def_idle_state(void)
+{
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (!zft_header_read) {
+ result = zft_read_header_segments();
+ } else if ((result = zft_flush_buffers()) >= 0 && zft_qic_mode) {
+ /* don't move past eof
+ */
+ (void)zft_close_volume(&zft_pos);
+ }
+ if (ftape_abort_operation() < 0) {
+ TRACE(ft_t_warn, "ftape_abort_operation() failed");
+ result = -EIO;
+ }
+ /* clear remaining read buffers */
+ zft_zap_read_buffers();
+ zft_io_state = zft_idle;
+ TRACE_EXIT result;
+}
+
+/*****************************************************************************
+ * *
+ * functions for the MTIOCTOP commands *
+ * *
+ *****************************************************************************/
+
+static int mt_dummy(int *dummy)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE_EXIT -ENOSYS;
+}
+
+static int mt_reset(int *dummy)
+{
+ TRACE_FUN(ft_t_flow);
+
+ (void)ftape_seek_to_bot();
+ TRACE_CATCH(ftape_reset_drive(),
+ zft_init_driver(); zft_uninit_mem(); zft_offline = 1);
+ /* fake a re-open of the device. This will set all flage and
+ * allocate buffers as appropriate. The new tape condition will
+ * force the open routine to do anything we need.
+ */
+ TRACE_CATCH(_zft_open(-1 /* fake reopen */, 0 /* dummy */),);
+ TRACE_EXIT 0;
+}
+
+static int mt_fsf(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = zft_skip_volumes(*arg, &zft_pos);
+ zft_just_before_eof = 0;
+ TRACE_EXIT result;
+}
+
+static int mt_bsf(int *arg)
+{
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (*arg != 0) {
+ result = zft_skip_volumes(-*arg + 1, &zft_pos);
+ }
+ TRACE_EXIT result;
+}
+
+static int seek_block(__s64 data_offset,
+ __s64 block_increment,
+ zft_position *pos)
+{
+ int result = 0;
+ __s64 new_block_pos;
+ __s64 vol_block_count;
+ const zft_volinfo *volume;
+ int exceed;
+ TRACE_FUN(ft_t_flow);
+
+ volume = zft_find_volume(pos->seg_pos);
+ if (volume->start_seg == 0 || volume->end_seg == 0) {
+ TRACE_EXIT -EIO;
+ }
+ new_block_pos = (zft_div_blksz(data_offset, volume->blk_sz)
+ + block_increment);
+ vol_block_count = zft_div_blksz(volume->size, volume->blk_sz);
+ if (new_block_pos < 0) {
+ TRACE(ft_t_noise,
+ "new_block_pos " LL_X " < 0", LL(new_block_pos));
+ zft_resid = (int)new_block_pos;
+ new_block_pos = 0;
+ exceed = 1;
+ } else if (new_block_pos > vol_block_count) {
+ TRACE(ft_t_noise,
+ "new_block_pos " LL_X " exceeds size of volume " LL_X,
+ LL(new_block_pos), LL(vol_block_count));
+ zft_resid = (int)(vol_block_count - new_block_pos);
+ new_block_pos = vol_block_count;
+ exceed = 1;
+ } else {
+ exceed = 0;
+ }
+ if (zft_use_compression && volume->use_compression) {
+ TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+ result = (*zft_cmpr_ops->seek)(new_block_pos, pos, volume,
+ zft_deblock_buf);
+ pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+ pos->tape_pos += pos->seg_byte_pos;
+ } else {
+ pos->volume_pos = zft_mul_blksz(new_block_pos, volume->blk_sz);
+ pos->tape_pos = zft_calc_tape_pos(volume->start_seg);
+ pos->tape_pos += pos->volume_pos;
+ pos->seg_pos = zft_calc_seg_byte_coord(&pos->seg_byte_pos,
+ pos->tape_pos);
+ }
+ zft_just_before_eof = volume->size == pos->volume_pos;
+ if (zft_just_before_eof) {
+ /* why this? because zft_file_no checks agains start
+ * and end segment of a volume. We do not want to
+ * advance to the next volume with this function.
+ */
+ TRACE(ft_t_noise, "set zft_just_before_eof");
+ zft_position_before_eof(pos, volume);
+ }
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "new_seg_pos : %d\n"
+ KERN_INFO "new_tape_pos: " LL_X "\n"
+ KERN_INFO "vol_size : " LL_X "\n"
+ KERN_INFO "seg_byte_pos: %d\n"
+ KERN_INFO "blk_sz : %d",
+ pos->seg_pos, LL(pos->tape_pos),
+ LL(volume->size), pos->seg_byte_pos,
+ volume->blk_sz);
+ if (!exceed) {
+ zft_resid = new_block_pos - zft_div_blksz(pos->volume_pos,
+ volume->blk_sz);
+ }
+ if (zft_resid < 0) {
+ zft_resid = -zft_resid;
+ }
+ TRACE_EXIT ((exceed || zft_resid != 0) && result >= 0) ? -EINVAL : result;
+}
+
+static int mt_fsr(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = seek_block(zft_pos.volume_pos, *arg, &zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_bsr(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = seek_block(zft_pos.volume_pos, -*arg, &zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_weof(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE_CATCH(zft_flush_buffers(),);
+ result = zft_weof(*arg, &zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_rew(int *dummy)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if(zft_header_read) {
+ (void)zft_def_idle_state();
+ }
+ result = ftape_seek_to_bot();
+ zft_reset_position(&zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_offl(int *dummy)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ going_offline= 1;
+ result = mt_rew(NULL);
+ TRACE_EXIT result;
+}
+
+static int mt_nop(int *dummy)
+{
+ TRACE_FUN(ft_t_flow);
+ /* should we set tape status?
+ */
+ if (!zft_offline) { /* offline includes no_tape */
+ (void)zft_def_idle_state();
+ }
+ TRACE_EXIT 0;
+}
+
+static int mt_reten(int *dummy)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if(zft_header_read) {
+ (void)zft_def_idle_state();
+ }
+ result = ftape_seek_to_eot();
+ if (result >= 0) {
+ result = ftape_seek_to_bot();
+ }
+ TRACE_EXIT(result);
+}
+
+static int fsfbsfm(int arg, zft_position *pos)
+{
+ const zft_volinfo *vtbl;
+ __s64 block_pos;
+ TRACE_FUN(ft_t_flow);
+
+ /* What to do? This should seek to the next file-mark and
+ * position BEFORE. That is, a next write would just extend
+ * the current file. Well. Let's just seek to the end of the
+ * current file, if count == 1. If count > 1, then do a
+ * "mt_fsf(count - 1)", and then seek to the end of that file.
+ * If count == 0, do nothing
+ */
+ if (arg == 0) {
+ TRACE_EXIT 0;
+ }
+ zft_just_before_eof = 0;
+ TRACE_CATCH(zft_skip_volumes(arg < 0 ? arg : arg-1, pos),
+ if (arg > 0) {
+ zft_resid ++;
+ });
+ vtbl = zft_find_volume(pos->seg_pos);
+ block_pos = zft_div_blksz(vtbl->size, vtbl->blk_sz);
+ (void)seek_block(0, block_pos, pos);
+ if (pos->volume_pos != vtbl->size) {
+ zft_just_before_eof = 0;
+ zft_resid = 1;
+ /* we didn't managed to go there */
+ TRACE_ABORT(-EIO, ft_t_err,
+ "wanted file position " LL_X ", arrived at " LL_X,
+ LL(vtbl->size), LL(pos->volume_pos));
+ }
+ zft_just_before_eof = 1;
+ TRACE_EXIT 0;
+}
+
+static int mt_bsfm(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = fsfbsfm(-*arg, &zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_fsfm(int *arg)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = fsfbsfm(*arg, &zft_pos);
+ TRACE_EXIT result;
+}
+
+static int mt_eom(int *dummy)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_skip_to_eom(&zft_pos);
+ TRACE_EXIT 0;
+}
+
+static int mt_erase(int *dummy)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = zft_erase();
+ TRACE_EXIT result;
+}
+
+static int mt_ras2(int *dummy)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ result = -ENOSYS;
+ TRACE_EXIT result;
+}
+
+/* Sets the new blocksize in BYTES
+ *
+ */
+static int mt_setblk(int *new_size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if((unsigned int)(*new_size) > ZFT_MAX_BLK_SZ) {
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "desired blk_sz (%d) should be <= %d bytes",
+ *new_size, ZFT_MAX_BLK_SZ);
+ }
+ if ((*new_size & (FT_SECTOR_SIZE-1)) != 0) {
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "desired blk_sz (%d) must be a multiple of %d bytes",
+ *new_size, FT_SECTOR_SIZE);
+ }
+ if (*new_size == 0) {
+ if (zft_use_compression) {
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "Variable block size not yet "
+ "supported with compression");
+ }
+ *new_size = 1;
+ }
+ zft_blk_sz = *new_size;
+ TRACE_EXIT 0;
+}
+
+static int mt_setdensity(int *arg)
+{
+ TRACE_FUN(ft_t_flow);
+
+ SET_TRACE_LEVEL(*arg);
+ TRACE(TRACE_LEVEL, "tracing set to %d", TRACE_LEVEL);
+ if ((int)TRACE_LEVEL != *arg) {
+ TRACE_EXIT -EINVAL;
+ }
+ TRACE_EXIT 0;
+}
+
+static int mt_seek(int *new_block_pos)
+{
+ int result= 0;
+ TRACE_FUN(ft_t_any);
+
+ result = seek_block(0, (__s64)*new_block_pos, &zft_pos);
+ TRACE_EXIT result;
+}
+
+/* OK, this is totally different from SCSI, but the worst thing that can
+ * happen is that there is not enough defragmentated memory that can be
+ * allocated. Also, there is a hardwired limit of 16 dma buffers in the
+ * stock ftape module. This shouldn't bring the system down.
+ *
+ * NOTE: the argument specifies the total number of dma buffers to use.
+ * The driver needs at least 3 buffers to function at all.
+ *
+ */
+static int mt_setdrvbuffer(int *cnt)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (*cnt < 3) {
+ TRACE_EXIT -EINVAL;
+ }
+ TRACE_CATCH(ftape_set_nr_buffers(*cnt),);
+ TRACE_EXIT 0;
+}
+/* return the block position from start of volume
+ */
+static int mt_tell(int *arg)
+{
+ TRACE_FUN(ft_t_flow);
+
+ *arg = zft_div_blksz(zft_pos.volume_pos,
+ zft_find_volume(zft_pos.seg_pos)->blk_sz);
+ TRACE_EXIT 0;
+}
+
+static int mt_compression(int *arg)
+{
+ TRACE_FUN(ft_t_flow);
+
+ /* Ok. We could also check whether compression is available at
+ * all by trying to load the compression module. We could
+ * also check for a block size of 1 byte which is illegal
+ * with compression. Instead of doing it here we rely on
+ * zftape_write() to do the proper checks.
+ */
+ if ((unsigned int)*arg > 1) {
+ TRACE_EXIT -EINVAL;
+ }
+ if (*arg != 0 && zft_blk_sz == 1) { /* variable block size */
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "Compression not yet supported "
+ "with variable block size");
+ }
+ zft_mt_compression = *arg;
+ if ((zft_unit & ZFT_ZIP_MODE) == 0) {
+ zft_use_compression = zft_mt_compression;
+ }
+ TRACE_EXIT 0;
+}
+
+/* check whether write access is allowed. Write access is denied when
+ * + zft_write_protected == 1 -- this accounts for either hard write
+ * protection of the cartridge or for
+ * O_RDONLY access mode of the tape device
+ * + zft_offline == 1 -- this meany that there is either no tape
+ * or that the MTOFFLINE ioctl has been
+ * previously issued (`soft eject')
+ * + ft_formatted == 0 -- this means that the cartridge is not
+ * formatted
+ * Then we distinuguish two cases. When zft_qic_mode is TRUE, then we try
+ * to emulate a `traditional' (aka SCSI like) UN*X tape device. Therefore we
+ * deny writes when
+ * + zft_qic_mode ==1 &&
+ * (!zft_tape_at_lbot() && -- tape no at logical BOT
+ * !(zft_tape_at_eom() || -- tape not at logical EOM (or EOD)
+ * (zft_tape_at_eom() &&
+ * zft_old_ftape()))) -- we can't add new volume to tapes
+ * written by old ftape because ftape
+ * don't use the volume table
+ *
+ * when the drive is in true raw mode (aka /dev/rawft0) then we don't
+ * care about LBOT and EOM conditions. This device is intended for a
+ * user level program that wants to truly implement the QIC-80 compliance
+ * at the logical data layout level of the cartridge, i.e. implement all
+ * that volume table and volume directory stuff etc.<
+ */
+int zft_check_write_access(zft_position *pos)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (zft_offline) { /* offline includes no_tape */
+ TRACE_ABORT(-ENXIO,
+ ft_t_info, "tape is offline or no cartridge");
+ }
+ if (!ft_formatted) {
+ TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+ }
+ if (zft_write_protected) {
+ TRACE_ABORT(-EACCES, ft_t_info, "cartridge write protected");
+ }
+ if (zft_qic_mode) {
+ /* check BOT condition */
+ if (!zft_tape_at_lbot(pos)) {
+ /* protect cartridges written by old ftape if
+ * not at BOT because they use the vtbl
+ * segment for storing data
+ */
+ if (zft_old_ftape) {
+ TRACE_ABORT(-EACCES, ft_t_warn,
+ "Cannot write to cartridges written by old ftape when not at BOT");
+ }
+ /* not at BOT, but allow writes at EOD, of course
+ */
+ if (!zft_tape_at_eod(pos)) {
+ TRACE_ABORT(-EACCES, ft_t_info,
+ "tape not at BOT and not at EOD");
+ }
+ }
+ /* fine. Now the tape is either at BOT or at EOD. */
+ }
+ /* or in raw mode in which case we don't care about BOT and EOD */
+ TRACE_EXIT 0;
+}
+
+/* OPEN routine called by kernel-interface code
+ *
+ * NOTE: this is also called by mt_reset() with dev_minor == -1
+ * to fake a reopen after a reset.
+ */
+int _zft_open(unsigned int dev_minor, unsigned int access_mode)
+{
+ static unsigned int tape_unit;
+ static unsigned int file_access_mode;
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if ((int)dev_minor == -1) {
+ /* fake reopen */
+ zft_unit = tape_unit;
+ access_mode = file_access_mode;
+ zft_init_driver(); /* reset all static data to defaults */
+ } else {
+ tape_unit = dev_minor;
+ file_access_mode = access_mode;
+ if ((result = ftape_enable(FTAPE_SEL(dev_minor))) < 0) {
+ TRACE_ABORT(-ENXIO, ft_t_err,
+ "ftape_enable failed: %d", result);
+ }
+ if (ft_new_tape || ft_no_tape || !ft_formatted ||
+ (FTAPE_SEL(zft_unit) != FTAPE_SEL(dev_minor)) ||
+ (zft_unit & ZFT_RAW_MODE) != (dev_minor & ZFT_RAW_MODE)) {
+ /* reset all static data to defaults,
+ */
+ zft_init_driver();
+ }
+ zft_unit = dev_minor;
+ }
+ zft_set_flags(zft_unit); /* decode the minor bits */
+ if (zft_blk_sz == 1 && zft_use_compression) {
+ ftape_disable(); /* resets ft_no_tape */
+ TRACE_ABORT(-ENODEV, ft_t_warn, "Variable block size not yet "
+ "supported with compression");
+ }
+ /* no need for most of the buffers when no tape or not
+ * formatted. for the read/write operations, it is the
+ * regardless whether there is no tape, a not-formatted tape
+ * or the whether the driver is soft offline.
+ * Nevertheless we allow some ioctls with non-formatted tapes,
+ * like rewind and reset.
+ */
+ if (ft_no_tape || !ft_formatted) {
+ zft_uninit_mem();
+ }
+ if (ft_no_tape) {
+ zft_offline = 1; /* so we need not test two variables */
+ }
+ if ((access_mode == O_WRONLY || access_mode == O_RDWR) &&
+ (ft_write_protected || ft_no_tape)) {
+ ftape_disable(); /* resets ft_no_tape */
+ TRACE_ABORT(ft_no_tape ? -ENXIO : -EROFS,
+ ft_t_warn, "wrong access mode %s cartridge",
+ ft_no_tape ? "without a" : "with write protected");
+ }
+ zft_write_protected = (access_mode == O_RDONLY ||
+ ft_write_protected != 0);
+ if (zft_write_protected) {
+ TRACE(ft_t_noise,
+ "read only access mode: %d, "
+ "drive write protected: %d",
+ access_mode == O_RDONLY,
+ ft_write_protected != 0);
+ }
+ if (!zft_offline) {
+ TRACE_CATCH(zft_vmalloc_once(&zft_deblock_buf,FT_SEGMENT_SIZE),
+ ftape_disable());
+ }
+ /* zft_seg_pos should be greater than the vtbl segpos but not
+ * if in compatibility mode and only after we read in the
+ * header segments
+ *
+ * might also be a problem if the user makes a backup with a
+ * *qft* device and rewinds it with a raw device.
+ */
+ if (zft_qic_mode &&
+ !zft_old_ftape &&
+ zft_pos.seg_pos >= 0 &&
+ zft_header_read &&
+ zft_pos.seg_pos <= ft_first_data_segment) {
+ TRACE(ft_t_noise, "you probably mixed up the zftape devices!");
+ zft_reset_position(&zft_pos);
+ }
+ TRACE_EXIT 0;
+}
+
+/* RELEASE routine called by kernel-interface code
+ */
+int _zft_close(void)
+{
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (zft_offline) {
+ /* call the hardware release routine. Puts the drive offline */
+ ftape_disable();
+ TRACE_EXIT 0;
+ }
+ if (!(ft_write_protected || zft_old_ftape)) {
+ result = zft_flush_buffers();
+ TRACE(ft_t_noise, "writing file mark at current position");
+ if (zft_qic_mode && zft_close_volume(&zft_pos) == 0) {
+ zft_move_past_eof(&zft_pos);
+ }
+ if ((zft_tape_at_lbot(&zft_pos) ||
+ !(zft_unit & FTAPE_NO_REWIND))) {
+ if (result >= 0) {
+ result = zft_update_header_segments();
+ } else {
+ TRACE(ft_t_err,
+ "Error: unable to update header segments");
+ }
+ }
+ }
+ ftape_abort_operation();
+ if (!(zft_unit & FTAPE_NO_REWIND)) {
+ TRACE(ft_t_noise, "rewinding tape");
+ if (ftape_seek_to_bot() < 0 && result >= 0) {
+ result = -EIO; /* keep old value */
+ }
+ zft_reset_position(&zft_pos);
+ }
+ zft_zap_read_buffers();
+ /* now free up memory as much as possible. We don't destroy
+ * the deblock buffer if it containes a valid segment.
+ */
+ if (zft_deblock_segment == -1) {
+ zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE);
+ }
+ /* high level driver status, forces creation of a new volume
+ * when calling ftape_write again and not zft_just_before_eof
+ */
+ zft_io_state = zft_idle;
+ if (going_offline) {
+ zft_init_driver();
+ zft_uninit_mem();
+ going_offline = 0;
+ zft_offline = 1;
+ } else if (zft_cmpr_lock(0 /* don't load */) == 0) {
+ (*zft_cmpr_ops->reset)(); /* unlock it again */
+ }
+ zft_memory_stats();
+ /* call the hardware release routine. Puts the drive offline */
+ ftape_disable();
+ TRACE_EXIT result;
+}
+
+/*
+ * the wrapper function around the wrapper MTIOCTOP ioctl
+ */
+static int mtioctop(struct mtop *mtop, int arg_size)
+{
+ int result = 0;
+ fun_entry *mt_fun_entry;
+ TRACE_FUN(ft_t_flow);
+
+ if (arg_size != sizeof(struct mtop) || mtop->mt_op >= NR_MT_CMDS) {
+ TRACE_EXIT -EINVAL;
+ }
+ TRACE(ft_t_noise, "calling MTIOCTOP command: %s",
+ mt_funs[mtop->mt_op].name);
+ mt_fun_entry= &mt_funs[mtop->mt_op];
+ zft_resid = mtop->mt_count;
+ if (!mt_fun_entry->offline && zft_offline) {
+ if (ft_no_tape) {
+ TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+ } else {
+ TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+ }
+ }
+ if (!mt_fun_entry->not_formatted && !ft_formatted) {
+ TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+ }
+ if (!mt_fun_entry->write_protected) {
+ TRACE_CATCH(zft_check_write_access(&zft_pos),);
+ }
+ if (mt_fun_entry->need_idle_state && !(zft_offline || !ft_formatted)) {
+ TRACE_CATCH(zft_def_idle_state(),);
+ }
+ if (!zft_qic_mode && !mt_fun_entry->raw_mode) {
+ TRACE_ABORT(-EACCES, ft_t_info,
+"Drive needs to be in QIC-80 compatibility mode for this command");
+ }
+ result = (mt_fun_entry->function)(&mtop->mt_count);
+ if (zft_tape_at_lbot(&zft_pos)) {
+ TRACE_CATCH(zft_update_header_segments(),);
+ }
+ if (result >= 0) {
+ zft_resid = 0;
+ }
+ TRACE_EXIT result;
+}
+
+/*
+ * standard MTIOCGET ioctl
+ */
+static int mtiocget(struct mtget *mtget, int arg_size)
+{
+ const zft_volinfo *volume;
+ __s64 max_tape_pos;
+ TRACE_FUN(ft_t_flow);
+
+ if (arg_size != sizeof(struct mtget)) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+ arg_size);
+ }
+ mtget->mt_type = ft_drive_type.vendor_id + 0x800000;
+ mtget->mt_dsreg = ft_last_status.space;
+ mtget->mt_erreg = ft_last_error.space; /* error register */
+ mtget->mt_resid = zft_resid; /* residuum of writes, reads and
+ * MTIOCTOP commands
+ */
+ if (!zft_offline) { /* neither no_tape nor soft offline */
+ mtget->mt_gstat = GMT_ONLINE(~0UL);
+ /* should rather return the status of the cartridge
+ * than the access mode of the file, therefor use
+ * ft_write_protected, not zft_write_protected
+ */
+ if (ft_write_protected) {
+ mtget->mt_gstat |= GMT_WR_PROT(~0UL);
+ }
+ if(zft_header_read) { /* this catches non-formatted */
+ volume = zft_find_volume(zft_pos.seg_pos);
+ mtget->mt_fileno = volume->count;
+ max_tape_pos = zft_capacity - zft_blk_sz;
+ if (zft_use_compression) {
+ max_tape_pos -= ZFT_CMPR_OVERHEAD;
+ }
+ if (zft_tape_at_eod(&zft_pos)) {
+ mtget->mt_gstat |= GMT_EOD(~0UL);
+ }
+ if (zft_pos.tape_pos > max_tape_pos) {
+ mtget->mt_gstat |= GMT_EOT(~0UL);
+ }
+ mtget->mt_blkno = zft_div_blksz(zft_pos.volume_pos,
+ volume->blk_sz);
+ if (zft_just_before_eof) {
+ mtget->mt_gstat |= GMT_EOF(~0UL);
+ }
+ if (zft_tape_at_lbot(&zft_pos)) {
+ mtget->mt_gstat |= GMT_BOT(~0UL);
+ }
+ } else {
+ mtget->mt_fileno = mtget->mt_blkno = -1;
+ if (mtget->mt_dsreg & QIC_STATUS_AT_BOT) {
+ mtget->mt_gstat |= GMT_BOT(~0UL);
+ }
+ }
+ } else {
+ if (ft_no_tape) {
+ mtget->mt_gstat = GMT_DR_OPEN(~0UL);
+ } else {
+ mtget->mt_gstat = 0UL;
+ }
+ mtget->mt_fileno = mtget->mt_blkno = -1;
+ }
+ TRACE_EXIT 0;
+}
+
+#ifdef MTIOCRDFTSEG
+/*
+ * Read a floppy tape segment. This is useful for manipulating the
+ * volume table, and read the old header segment before re-formatting
+ * the cartridge.
+ */
+static int mtiocrdftseg(struct mtftseg * mtftseg, int arg_size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCRDFTSEG");
+ if (zft_qic_mode) {
+ TRACE_ABORT(-EACCES, ft_t_info,
+ "driver needs to be in raw mode for this ioctl");
+ }
+ if (arg_size != sizeof(struct mtftseg)) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+ arg_size);
+ }
+ if (zft_offline) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (mtftseg->mt_mode != FT_RD_SINGLE &&
+ mtftseg->mt_mode != FT_RD_AHEAD) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "invalid read mode");
+ }
+ if (!ft_formatted) {
+ TRACE_EXIT -EACCES; /* -ENXIO ? */
+
+ }
+ if (!zft_header_read) {
+ TRACE_CATCH(zft_def_idle_state(),);
+ }
+ if (mtftseg->mt_segno > ft_last_data_segment) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "segment number is too large");
+ }
+ mtftseg->mt_result = ftape_read_segment(mtftseg->mt_segno,
+ zft_deblock_buf,
+ mtftseg->mt_mode);
+ if (mtftseg->mt_result < 0) {
+ /* a negativ result is not an ioctl error. if
+ * the user wants to read damaged tapes,
+ * it's up to her/him
+ */
+ TRACE_EXIT 0;
+ }
+ if (copy_to_user(mtftseg->mt_data,
+ zft_deblock_buf,
+ mtftseg->mt_result) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCWRFTSEG
+/*
+ * write a floppy tape segment. This version features writing of
+ * deleted address marks, and gracefully ignores the (software)
+ * ft_formatted flag to support writing of header segments after
+ * formatting.
+ */
+static int mtiocwrftseg(struct mtftseg * mtftseg, int arg_size)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCWRFTSEG");
+ if (zft_write_protected || zft_qic_mode) {
+ TRACE_EXIT -EACCES;
+ }
+ if (arg_size != sizeof(struct mtftseg)) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+ arg_size);
+ }
+ if (zft_offline) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (mtftseg->mt_mode != FT_WR_ASYNC &&
+ mtftseg->mt_mode != FT_WR_MULTI &&
+ mtftseg->mt_mode != FT_WR_SINGLE &&
+ mtftseg->mt_mode != FT_WR_DELETE) {
+ TRACE_ABORT(-EINVAL, ft_t_info, "invalid write mode");
+ }
+ /*
+ * We don't check for ft_formatted, because this gives
+ * only the software status of the driver.
+ *
+ * We assume that the user knows what it is
+ * doing. And rely on the low level stuff to fail
+ * when the tape isn't formatted. We only make sure
+ * that The header segment buffer is allocated,
+ * because it holds the bad sector map.
+ */
+ if (zft_hseg_buf == NULL) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (mtftseg->mt_mode != FT_WR_DELETE) {
+ if (copy_from_user(zft_deblock_buf,
+ mtftseg->mt_data,
+ FT_SEGMENT_SIZE) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ }
+ mtftseg->mt_result = ftape_write_segment(mtftseg->mt_segno,
+ zft_deblock_buf,
+ mtftseg->mt_mode);
+ if (mtftseg->mt_result >= 0 && mtftseg->mt_mode == FT_WR_SINGLE) {
+ /*
+ * a negativ result is not an ioctl error. if
+ * the user wants to write damaged tapes,
+ * it's up to her/him
+ */
+ if ((result = ftape_loop_until_writes_done()) < 0) {
+ mtftseg->mt_result = result;
+ }
+ }
+ TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCVOLINFO
+/*
+ * get information about volume positioned at.
+ */
+static int mtiocvolinfo(struct mtvolinfo *volinfo, int arg_size)
+{
+ const zft_volinfo *volume;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCVOLINFO");
+ if (arg_size != sizeof(struct mtvolinfo)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ if (zft_offline) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (!ft_formatted) {
+ TRACE_EXIT -EACCES;
+ }
+ TRACE_CATCH(zft_def_idle_state(),);
+ volume = zft_find_volume(zft_pos.seg_pos);
+ volinfo->mt_volno = volume->count;
+ volinfo->mt_blksz = volume->blk_sz == 1 ? 0 : volume->blk_sz;
+ volinfo->mt_size = volume->size >> 10;
+ volinfo->mt_rawsize = ((zft_calc_tape_pos(volume->end_seg + 1) >> 10) -
+ (zft_calc_tape_pos(volume->start_seg) >> 10));
+ volinfo->mt_cmpr = volume->use_compression;
+ TRACE_EXIT 0;
+}
+#endif
+
+#ifdef ZFT_OBSOLETE
+static int mtioc_zftape_getblksz(struct mtblksz *blksz, int arg_size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "Mag tape ioctl command: MTIOC_ZTAPE_GETBLKSZ\n"
+ KERN_INFO "This ioctl is here merely for compatibility.\n"
+ KERN_INFO "Please use MTIOCVOLINFO instead");
+ if (arg_size != sizeof(struct mtblksz)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ if (zft_offline) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (!ft_formatted) {
+ TRACE_EXIT -EACCES;
+ }
+ TRACE_CATCH(zft_def_idle_state(),);
+ blksz->mt_blksz = zft_find_volume(zft_pos.seg_pos)->blk_sz;
+ TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCGETSIZE
+/*
+ * get the capacity of the tape cartridge.
+ */
+static int mtiocgetsize(struct mttapesize *size, int arg_size)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOC_ZFTAPE_GETSIZE");
+ if (arg_size != sizeof(struct mttapesize)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ if (zft_offline) {
+ TRACE_EXIT -ENXIO;
+ }
+ if (!ft_formatted) {
+ TRACE_EXIT -EACCES;
+ }
+ TRACE_CATCH(zft_def_idle_state(),);
+ size->mt_capacity = (unsigned int)(zft_capacity>>10);
+ size->mt_used = (unsigned int)(zft_get_eom_pos()>>10);
+ TRACE_EXIT 0;
+}
+#endif
+
+static int mtiocpos(struct mtpos *mtpos, int arg_size)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCPOS");
+ if (arg_size != sizeof(struct mtpos)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ result = mt_tell((int *)&mtpos->mt_blkno);
+ TRACE_EXIT result;
+}
+
+#ifdef MTIOCFTFORMAT
+/*
+ * formatting of floppy tape cartridges. This is intended to be used
+ * together with the MTIOCFTCMD ioctl and the new mmap feature
+ */
+
+/*
+ * This function uses ftape_decode_header_segment() to inform the low
+ * level ftape module about the new parameters.
+ *
+ * It erases the hseg_buf. The calling process must specify all
+ * parameters to assure proper operation.
+ *
+ * return values: -EINVAL - wrong argument size
+ * -EINVAL - if ftape_decode_header_segment() failed.
+ */
+static int set_format_parms(struct ftfmtparms *p, __u8 *hseg_buf)
+{
+ ft_trace_t old_level = TRACE_LEVEL;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_SETPARMS");
+ memset(hseg_buf, 0, FT_SEGMENT_SIZE);
+ PUT4(hseg_buf, FT_SIGNATURE, FT_HSEG_MAGIC);
+
+ /* fill in user specified parameters
+ */
+ hseg_buf[FT_FMT_CODE] = (__u8)p->ft_fmtcode;
+ PUT2(hseg_buf, FT_SPT, p->ft_spt);
+ hseg_buf[FT_TPC] = (__u8)p->ft_tpc;
+ hseg_buf[FT_FHM] = (__u8)p->ft_fhm;
+ hseg_buf[FT_FTM] = (__u8)p->ft_ftm;
+
+ /* fill in sane defaults to make ftape happy.
+ */
+ hseg_buf[FT_FSM] = (__u8)128; /* 128 is hard wired all over ftape */
+ if (p->ft_fmtcode == fmt_big) {
+ PUT4(hseg_buf, FT_6_HSEG_1, 0);
+ PUT4(hseg_buf, FT_6_HSEG_2, 1);
+ PUT4(hseg_buf, FT_6_FRST_SEG, 2);
+ PUT4(hseg_buf, FT_6_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+ } else {
+ PUT2(hseg_buf, FT_HSEG_1, 0);
+ PUT2(hseg_buf, FT_HSEG_2, 1);
+ PUT2(hseg_buf, FT_FRST_SEG, 2);
+ PUT2(hseg_buf, FT_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+ }
+
+ /* Synchronize with the low level module. This is particularly
+ * needed for unformatted cartridges as the QIC std was previously
+ * unknown BUT is needed to set data rate and to calculate timeouts.
+ */
+ TRACE_CATCH(ftape_calibrate_data_rate(p->ft_qicstd&QIC_TAPE_STD_MASK),
+ _res = -EINVAL);
+
+ /* The following will also recalcualte the timeouts for the tape
+ * length and QIC std we want to format to.
+ * abort with -EINVAL rather than -EIO
+ */
+ SET_TRACE_LEVEL(ft_t_warn);
+ TRACE_CATCH(ftape_decode_header_segment(hseg_buf),
+ SET_TRACE_LEVEL(old_level); _res = -EINVAL);
+ SET_TRACE_LEVEL(old_level);
+ TRACE_EXIT 0;
+}
+
+/*
+ * Return the internal SOFTWARE status of the kernel driver. This does
+ * NOT query the tape drive about its status.
+ */
+static int get_format_parms(struct ftfmtparms *p, __u8 *hseg_buffer)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_GETPARMS");
+ p->ft_qicstd = ft_qic_std;
+ p->ft_fmtcode = ft_format_code;
+ p->ft_fhm = hseg_buffer[FT_FHM];
+ p->ft_ftm = hseg_buffer[FT_FTM];
+ p->ft_spt = ft_segments_per_track;
+ p->ft_tpc = ft_tracks_per_tape;
+ TRACE_EXIT 0;
+}
+
+static int mtiocftformat(struct mtftformat *mtftformat, int arg_size)
+{
+ int result;
+ union fmt_arg *arg = &mtftformat->fmt_arg;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTFORMAT");
+ if (zft_offline) {
+ if (ft_no_tape) {
+ TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+ } else {
+ TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+ }
+ }
+ if (zft_qic_mode) {
+ TRACE_ABORT(-EACCES, ft_t_info,
+ "driver needs to be in raw mode for this ioctl");
+ }
+ if (zft_hseg_buf == NULL) {
+ TRACE_CATCH(zft_vcalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),);
+ }
+ zft_header_read = 0;
+ switch(mtftformat->fmt_op) {
+ case FTFMT_SET_PARMS:
+ TRACE_CATCH(set_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+ TRACE_EXIT 0;
+ case FTFMT_GET_PARMS:
+ TRACE_CATCH(get_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+ TRACE_EXIT 0;
+ case FTFMT_FORMAT_TRACK:
+ if ((ft_formatted && zft_check_write_access(&zft_pos) < 0) ||
+ (!ft_formatted && zft_write_protected)) {
+ TRACE_ABORT(-EACCES, ft_t_info, "Write access denied");
+ }
+ TRACE_CATCH(ftape_format_track(arg->fmt_track.ft_track,
+ arg->fmt_track.ft_gap3),);
+ TRACE_EXIT 0;
+ case FTFMT_STATUS:
+ TRACE_CATCH(ftape_format_status(&arg->fmt_status.ft_segment),);
+ TRACE_EXIT 0;
+ case FTFMT_VERIFY:
+ TRACE_CATCH(ftape_verify_segment(arg->fmt_verify.ft_segment,
+ (SectorMap *)&arg->fmt_verify.ft_bsm),);
+ TRACE_EXIT 0;
+ default:
+ TRACE_ABORT(-EINVAL, ft_t_err, "Invalid format operation");
+ }
+ TRACE_EXIT result;
+}
+#endif
+
+#ifdef MTIOCFTCMD
+/*
+ * send a QIC-117 command to the drive, with optional timeouts,
+ * parameter and result bits. This is intended to be used together
+ * with the formatting ioctl.
+ */
+static int mtiocftcmd(struct mtftcmd *ftcmd, int arg_size)
+{
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTCMD");
+ if (!capable(CAP_SYS_ADMIN)) {
+ TRACE_ABORT(-EPERM, ft_t_info,
+ "need CAP_SYS_ADMIN capability to send raw qic-117 commands");
+ }
+ if (zft_qic_mode) {
+ TRACE_ABORT(-EACCES, ft_t_info,
+ "driver needs to be in raw mode for this ioctl");
+ }
+ if (arg_size != sizeof(struct mtftcmd)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ if (ftcmd->ft_wait_before) {
+ TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_before,
+ &ftcmd->ft_status),);
+ }
+ if (ftcmd->ft_status & QIC_STATUS_ERROR)
+ goto ftmtcmd_error;
+ if (ftcmd->ft_result_bits != 0) {
+ TRACE_CATCH(ftape_report_operation(&ftcmd->ft_result,
+ ftcmd->ft_cmd,
+ ftcmd->ft_result_bits),);
+ } else {
+ TRACE_CATCH(ftape_command(ftcmd->ft_cmd),);
+ if (ftcmd->ft_status & QIC_STATUS_ERROR)
+ goto ftmtcmd_error;
+ for (i = 0; i < ftcmd->ft_parm_cnt; i++) {
+ TRACE_CATCH(ftape_parameter(ftcmd->ft_parms[i]&0x0f),);
+ if (ftcmd->ft_status & QIC_STATUS_ERROR)
+ goto ftmtcmd_error;
+ }
+ }
+ if (ftcmd->ft_wait_after != 0) {
+ TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_after,
+ &ftcmd->ft_status),);
+ }
+ftmtcmd_error:
+ if (ftcmd->ft_status & QIC_STATUS_ERROR) {
+ TRACE(ft_t_noise, "error status set");
+ TRACE_CATCH(ftape_report_error(&ftcmd->ft_error,
+ &ftcmd->ft_cmd, 1),);
+ }
+ TRACE_EXIT 0; /* this is not an i/o error */
+}
+#endif
+
+/* IOCTL routine called by kernel-interface code
+ */
+int _zft_ioctl(unsigned int command, void __user * arg)
+{
+ int result;
+ union { struct mtop mtop;
+ struct mtget mtget;
+ struct mtpos mtpos;
+#ifdef MTIOCRDFTSEG
+ struct mtftseg mtftseg;
+#endif
+#ifdef MTIOCVOLINFO
+ struct mtvolinfo mtvolinfo;
+#endif
+#ifdef MTIOCGETSIZE
+ struct mttapesize mttapesize;
+#endif
+#ifdef MTIOCFTFORMAT
+ struct mtftformat mtftformat;
+#endif
+#ifdef ZFT_OBSOLETE
+ struct mtblksz mtblksz;
+#endif
+#ifdef MTIOCFTCMD
+ struct mtftcmd mtftcmd;
+#endif
+ } krnl_arg;
+ int arg_size = _IOC_SIZE(command);
+ int dir = _IOC_DIR(command);
+ TRACE_FUN(ft_t_flow);
+
+ /* This check will only catch arguments that are too large !
+ */
+ if (dir & (_IOC_READ | _IOC_WRITE) && arg_size > sizeof(krnl_arg)) {
+ TRACE_ABORT(-EINVAL,
+ ft_t_info, "bad argument size: %d", arg_size);
+ }
+ if (dir & _IOC_WRITE) {
+ if (copy_from_user(&krnl_arg, arg, arg_size) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ }
+ TRACE(ft_t_flow, "called with ioctl command: 0x%08x", command);
+ switch (command) {
+ case MTIOCTOP:
+ result = mtioctop(&krnl_arg.mtop, arg_size);
+ break;
+ case MTIOCGET:
+ result = mtiocget(&krnl_arg.mtget, arg_size);
+ break;
+ case MTIOCPOS:
+ result = mtiocpos(&krnl_arg.mtpos, arg_size);
+ break;
+#ifdef MTIOCVOLINFO
+ case MTIOCVOLINFO:
+ result = mtiocvolinfo(&krnl_arg.mtvolinfo, arg_size);
+ break;
+#endif
+#ifdef ZFT_OBSOLETE
+ case MTIOC_ZFTAPE_GETBLKSZ:
+ result = mtioc_zftape_getblksz(&krnl_arg.mtblksz, arg_size);
+ break;
+#endif
+#ifdef MTIOCRDFTSEG
+ case MTIOCRDFTSEG: /* read a segment via ioctl */
+ result = mtiocrdftseg(&krnl_arg.mtftseg, arg_size);
+ break;
+#endif
+#ifdef MTIOCWRFTSEG
+ case MTIOCWRFTSEG: /* write a segment via ioctl */
+ result = mtiocwrftseg(&krnl_arg.mtftseg, arg_size);
+ break;
+#endif
+#ifdef MTIOCGETSIZE
+ case MTIOCGETSIZE:
+ result = mtiocgetsize(&krnl_arg.mttapesize, arg_size);
+ break;
+#endif
+#ifdef MTIOCFTFORMAT
+ case MTIOCFTFORMAT:
+ result = mtiocftformat(&krnl_arg.mtftformat, arg_size);
+ break;
+#endif
+#ifdef MTIOCFTCMD
+ case MTIOCFTCMD:
+ result = mtiocftcmd(&krnl_arg.mtftcmd, arg_size);
+ break;
+#endif
+ default:
+ result = -EINVAL;
+ break;
+ }
+ if ((result >= 0) && (dir & _IOC_READ)) {
+ if (copy_to_user(arg, &krnl_arg, arg_size) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ }
+ TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/zftape/zftape-ctl.h b/drivers/char/ftape/zftape/zftape-ctl.h
new file mode 100644
index 000000000000..414159891990
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-ctl.h
@@ -0,0 +1,59 @@
+#ifndef _ZFTAPE_CTL_H
+#define _ZFTAPE_CTL_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:02 $
+ *
+ * This file contains the non-standard IOCTL related definitions
+ * for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+
+#include "../zftape/zftape-rw.h"
+
+#ifdef CONFIG_ZFTAPE_MODULE
+#define ftape_status (*zft_status)
+#endif
+
+extern int zft_offline;
+extern int zft_mt_compression;
+extern int zft_write_protected;
+extern int zft_header_read;
+extern unsigned int zft_unit;
+extern int zft_resid;
+
+extern void zft_reset_position(zft_position *pos);
+extern int zft_check_write_access(zft_position *pos);
+extern int zft_def_idle_state(void);
+
+/* hooks for the VFS interface
+ */
+extern int _zft_open(unsigned int dev_minor, unsigned int access_mode);
+extern int _zft_close(void);
+extern int _zft_ioctl(unsigned int command, void __user *arg);
+#endif
+
+
+
diff --git a/drivers/char/ftape/zftape/zftape-eof.c b/drivers/char/ftape/zftape/zftape-eof.c
new file mode 100644
index 000000000000..dcadcaee9ac1
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-eof.c
@@ -0,0 +1,199 @@
+/*
+ * I use these routines just to decide when I have to fake a
+ * volume-table to preserve compatibility to original ftape.
+ */
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+ *
+ * Modified for zftape 1996, 1997 Claus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:02 $
+ *
+ * This file contains the eof mark handling code
+ * for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/zftape.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-eof.h"
+
+/* Global vars.
+ */
+
+/* a copy of the failed sector log from the header segment.
+ */
+eof_mark_union *zft_eof_map;
+
+/* number of eof marks (entries in bad sector log) on tape.
+ */
+int zft_nr_eof_marks = -1;
+
+
+/* Local vars.
+ */
+
+static char linux_tape_label[] = "Linux raw format V";
+enum {
+ min_fmt_version = 1, max_fmt_version = 2
+};
+static unsigned ftape_fmt_version = 0;
+
+
+/* Ftape (mis)uses the bad sector log to record end-of-file marks.
+ * Initially (when the tape is erased) all entries in the bad sector
+ * log are added to the tape's bad sector map. The bad sector log then
+ * is cleared.
+ *
+ * The bad sector log normally contains entries of the form:
+ * even 16-bit word: segment number of bad sector
+ * odd 16-bit word: encoded date
+ * There can be a total of 448 entries (1792 bytes).
+ *
+ * My guess is that no program is using this bad sector log (the *
+ * format seems useless as there is no indication of the bad sector
+ * itself, only the segment) However, if any program does use the bad
+ * sector log, the format used by ftape will let the program think
+ * there are some bad sectors and no harm is done.
+ *
+ * The eof mark entries that ftape stores in the bad sector log: even
+ * 16-bit word: segment number of eof mark odd 16-bit word: sector
+ * number of eof mark [1..32]
+ *
+ * The zft_eof_map as maintained is a sorted list of eof mark entries.
+ *
+ *
+ * The tape name field in the header segments is used to store a linux
+ * tape identification string and a version number. This way the tape
+ * can be recognized as a Linux raw format tape when using tools under
+ * other OS's.
+ *
+ * 'Wide' QIC tapes (format code 4) don't have a failed sector list
+ * anymore. That space is used for the (longer) bad sector map that
+ * now is a variable length list too. We now store our end-of-file
+ * marker list after the bad-sector-map on tape. The list is delimited
+ * by a (__u32) 0 entry.
+ */
+
+int zft_ftape_validate_label(char *label)
+{
+ static char tmp_label[45];
+ int result = 0;
+ TRACE_FUN(ft_t_any);
+
+ memcpy(tmp_label, label, FT_LABEL_SZ);
+ tmp_label[FT_LABEL_SZ] = '\0';
+ TRACE(ft_t_noise, "tape label = `%s'", tmp_label);
+ ftape_fmt_version = 0;
+ if (memcmp(label, linux_tape_label, strlen(linux_tape_label)) == 0) {
+ int pos = strlen(linux_tape_label);
+ while (label[pos] >= '0' && label[pos] <= '9') {
+ ftape_fmt_version *= 10;
+ ftape_fmt_version = label[ pos++] - '0';
+ }
+ result = (ftape_fmt_version >= min_fmt_version &&
+ ftape_fmt_version <= max_fmt_version);
+ }
+ TRACE(ft_t_noise, "format version = %d", ftape_fmt_version);
+ TRACE_EXIT result;
+}
+
+static __u8 * find_end_of_eof_list(__u8 * ptr, __u8 * limit)
+{
+ while (ptr + 3 < limit) {
+
+ if (get_unaligned((__u32*)ptr)) {
+ ptr += sizeof(__u32);
+ } else {
+ return ptr;
+ }
+ }
+ return NULL;
+}
+
+void zft_ftape_extract_file_marks(__u8* address)
+{
+ int i;
+ TRACE_FUN(ft_t_any);
+
+ zft_eof_map = NULL;
+ if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+ __u8* end;
+ __u8* start = ftape_find_end_of_bsm_list(address);
+
+ zft_nr_eof_marks = 0;
+ if (start) {
+ start += 3; /* skip end of list mark */
+ end = find_end_of_eof_list(start,
+ address + FT_SEGMENT_SIZE);
+ if (end && end - start <= FT_FSL_SIZE) {
+ zft_nr_eof_marks = ((end - start) /
+ sizeof(eof_mark_union));
+ zft_eof_map = (eof_mark_union *)start;
+ } else {
+ TRACE(ft_t_err,
+ "EOF Mark List is too long or damaged!");
+ }
+ } else {
+ TRACE(ft_t_err,
+ "Bad Sector List is too long or damaged !");
+ }
+ } else {
+ zft_eof_map = (eof_mark_union *)&address[FT_FSL];
+ zft_nr_eof_marks = GET2(address, FT_FSL_CNT);
+ }
+ TRACE(ft_t_noise, "number of file marks: %d", zft_nr_eof_marks);
+ if (ftape_fmt_version == 1) {
+ TRACE(ft_t_info, "swapping version 1 fields");
+ /* version 1 format uses swapped sector and segment
+ * fields, correct that !
+ */
+ for (i = 0; i < zft_nr_eof_marks; ++i) {
+ __u16 tmp = GET2(&zft_eof_map[i].mark.segment,0);
+ PUT2(&zft_eof_map[i].mark.segment, 0,
+ GET2(&zft_eof_map[i].mark.date,0));
+ PUT2(&zft_eof_map[i].mark.date, 0, tmp);
+ }
+ }
+ for (i = 0; i < zft_nr_eof_marks; ++i) {
+ TRACE(ft_t_noise, "eof mark: %5d/%2d",
+ GET2(&zft_eof_map[i].mark.segment, 0),
+ GET2(&zft_eof_map[i].mark.date,0));
+ }
+ TRACE_EXIT;
+}
+
+void zft_clear_ftape_file_marks(void)
+{
+ TRACE_FUN(ft_t_flow);
+ /* Clear failed sector log: remove all tape marks. We
+ * don't use old ftape-style EOF-marks.
+ */
+ TRACE(ft_t_info, "Clearing old ftape's eof map");
+ memset(zft_eof_map, 0, zft_nr_eof_marks * sizeof(__u32));
+ zft_nr_eof_marks = 0;
+ PUT2(zft_hseg_buf, FT_FSL_CNT, 0); /* nr of eof-marks */
+ zft_header_changed = 1;
+ zft_update_label(zft_hseg_buf);
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-eof.h b/drivers/char/ftape/zftape/zftape-eof.h
new file mode 100644
index 000000000000..26568c26c518
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-eof.h
@@ -0,0 +1,52 @@
+#ifndef _ZFTAPE_EOF_H
+#define _ZFTAPE_EOF_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+ * adaptaed for zftape 1996, 1997 by Claus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:03 $
+ *
+ * Definitions and declarations for the end of file markers
+ * for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape-header-segment.h>
+#include "../zftape/zftape-buffers.h"
+/* failed sector log size (only used if format code != 4).
+ */
+
+typedef union {
+ ft_fsl_entry mark;
+ __u32 entry;
+} eof_mark_union;
+
+/* ftape-eof.c defined global vars.
+ */
+extern int zft_nr_eof_marks;
+extern eof_mark_union *zft_eof_map;
+
+/* ftape-eof.c defined global functions.
+ */
+extern void zft_ftape_extract_file_marks(__u8* address);
+extern int zft_ftape_validate_label(char* label);
+extern void zft_clear_ftape_file_marks(void);
+
+#endif
diff --git a/drivers/char/ftape/zftape/zftape-init.c b/drivers/char/ftape/zftape/zftape-init.c
new file mode 100644
index 000000000000..dbac7e54e8e0
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-init.c
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * This file contains the code that registers the zftape frontend
+ * to the ftape floppy tape driver for Linux
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+#include <linux/fcntl.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/zftape.h>
+#include <linux/init.h>
+#include <linux/device.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-buffers.h"
+
+MODULE_AUTHOR("(c) 1996, 1997 Claus-Justus Heine "
+ "(claus@momo.math.rwth-aachen.de)");
+MODULE_DESCRIPTION(ZFTAPE_VERSION " - "
+ "VFS interface for the Linux floppy tape driver. "
+ "Support for QIC-113 compatible volume table "
+ "and builtin compression (lzrw3 algorithm)");
+MODULE_SUPPORTED_DEVICE("char-major-27");
+MODULE_LICENSE("GPL");
+
+/* Global vars.
+ */
+struct zft_cmpr_ops *zft_cmpr_ops = NULL;
+const ftape_info *zft_status;
+
+/* Local vars.
+ */
+static unsigned long busy_flag;
+
+static sigset_t orig_sigmask;
+
+/* the interface to the kernel vfs layer
+ */
+
+/* Note about llseek():
+ *
+ * st.c and tpqic.c update fp->f_pos but don't implment llseek() and
+ * initialize the llseek component of the file_ops struct with NULL.
+ * This means that the user will get the default seek, but the tape
+ * device will not respect the new position, but happily read from the
+ * old position. Think a zftape specific llseek() function would be
+ * better, returning -ESPIPE. TODO.
+ */
+
+static int zft_open (struct inode *ino, struct file *filep);
+static int zft_close(struct inode *ino, struct file *filep);
+static int zft_ioctl(struct inode *ino, struct file *filep,
+ unsigned int command, unsigned long arg);
+static int zft_mmap(struct file *filep, struct vm_area_struct *vma);
+static ssize_t zft_read (struct file *fp, char __user *buff,
+ size_t req_len, loff_t *ppos);
+static ssize_t zft_write(struct file *fp, const char __user *buff,
+ size_t req_len, loff_t *ppos);
+
+static struct file_operations zft_cdev =
+{
+ .owner = THIS_MODULE,
+ .read = zft_read,
+ .write = zft_write,
+ .ioctl = zft_ioctl,
+ .mmap = zft_mmap,
+ .open = zft_open,
+ .release = zft_close,
+};
+
+static struct class_simple *zft_class;
+
+/* Open floppy tape device
+ */
+static int zft_open(struct inode *ino, struct file *filep)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ nonseekable_open(ino, filep);
+ TRACE(ft_t_flow, "called for minor %d", iminor(ino));
+ if ( test_and_set_bit(0,&busy_flag) ) {
+ TRACE_ABORT(-EBUSY, ft_t_warn, "failed: already busy");
+ }
+ if ((iminor(ino) & ~(ZFT_MINOR_OP_MASK | FTAPE_NO_REWIND))
+ >
+ FTAPE_SEL_D) {
+ clear_bit(0,&busy_flag);
+ TRACE_ABORT(-ENXIO, ft_t_err, "failed: invalid unit nr");
+ }
+ orig_sigmask = current->blocked;
+ sigfillset(&current->blocked);
+ result = _zft_open(iminor(ino), filep->f_flags & O_ACCMODE);
+ if (result < 0) {
+ current->blocked = orig_sigmask; /* restore mask */
+ clear_bit(0,&busy_flag);
+ TRACE_ABORT(result, ft_t_err, "_ftape_open failed");
+ } else {
+ /* Mask signals that will disturb proper operation of the
+ * program that is calling.
+ */
+ current->blocked = orig_sigmask;
+ sigaddsetmask (&current->blocked, _DO_BLOCK);
+ TRACE_EXIT 0;
+ }
+}
+
+/* Close floppy tape device
+ */
+static int zft_close(struct inode *ino, struct file *filep)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit) {
+ TRACE(ft_t_err, "failed: not busy or wrong unit");
+ TRACE_EXIT 0;
+ }
+ sigfillset(&current->blocked);
+ result = _zft_close();
+ if (result < 0) {
+ TRACE(ft_t_err, "_zft_close failed");
+ }
+ current->blocked = orig_sigmask; /* restore before open state */
+ clear_bit(0,&busy_flag);
+ TRACE_EXIT 0;
+}
+
+/* Ioctl for floppy tape device
+ */
+static int zft_ioctl(struct inode *ino, struct file *filep,
+ unsigned int command, unsigned long arg)
+{
+ int result = -EIO;
+ sigset_t old_sigmask;
+ TRACE_FUN(ft_t_flow);
+
+ if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "failed: not busy, failure or wrong unit");
+ }
+ old_sigmask = current->blocked; /* save mask */
+ sigfillset(&current->blocked);
+ /* This will work as long as sizeof(void *) == sizeof(long) */
+ result = _zft_ioctl(command, (void __user *) arg);
+ current->blocked = old_sigmask; /* restore mask */
+ TRACE_EXIT result;
+}
+
+/* Ioctl for floppy tape device
+ */
+static int zft_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ int result = -EIO;
+ sigset_t old_sigmask;
+ TRACE_FUN(ft_t_flow);
+
+ if ( !test_bit(0,&busy_flag) ||
+ iminor(filep->f_dentry->d_inode) != zft_unit ||
+ ft_failure)
+ {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "failed: not busy, failure or wrong unit");
+ }
+ old_sigmask = current->blocked; /* save mask */
+ sigfillset(&current->blocked);
+ if ((result = ftape_mmap(vma)) >= 0) {
+#ifndef MSYNC_BUG_WAS_FIXED
+ static struct vm_operations_struct dummy = { NULL, };
+ vma->vm_ops = &dummy;
+#endif
+ }
+ current->blocked = old_sigmask; /* restore mask */
+ TRACE_EXIT result;
+}
+
+/* Read from floppy tape device
+ */
+static ssize_t zft_read(struct file *fp, char __user *buff,
+ size_t req_len, loff_t *ppos)
+{
+ int result = -EIO;
+ sigset_t old_sigmask;
+ struct inode *ino = fp->f_dentry->d_inode;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_data_flow, "called with count: %ld", (unsigned long)req_len);
+ if (!test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "failed: not busy, failure or wrong unit");
+ }
+ old_sigmask = current->blocked; /* save mask */
+ sigfillset(&current->blocked);
+ result = _zft_read(buff, req_len);
+ current->blocked = old_sigmask; /* restore mask */
+ TRACE(ft_t_data_flow, "return with count: %d", result);
+ TRACE_EXIT result;
+}
+
+/* Write to tape device
+ */
+static ssize_t zft_write(struct file *fp, const char __user *buff,
+ size_t req_len, loff_t *ppos)
+{
+ int result = -EIO;
+ sigset_t old_sigmask;
+ struct inode *ino = fp->f_dentry->d_inode;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_flow, "called with count: %ld", (unsigned long)req_len);
+ if (!test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "failed: not busy, failure or wrong unit");
+ }
+ old_sigmask = current->blocked; /* save mask */
+ sigfillset(&current->blocked);
+ result = _zft_write(buff, req_len);
+ current->blocked = old_sigmask; /* restore mask */
+ TRACE(ft_t_data_flow, "return with count: %d", result);
+ TRACE_EXIT result;
+}
+
+/* END OF VFS INTERFACE
+ *
+ *****************************************************************************/
+
+/* driver/module initialization
+ */
+
+/* the compression module has to call this function to hook into the zftape
+ * code
+ */
+int zft_cmpr_register(struct zft_cmpr_ops *new_ops)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (zft_cmpr_ops != NULL) {
+ TRACE_EXIT -EBUSY;
+ } else {
+ zft_cmpr_ops = new_ops;
+ TRACE_EXIT 0;
+ }
+}
+
+/* lock the zft-compressor() module.
+ */
+int zft_cmpr_lock(int try_to_load)
+{
+ if (zft_cmpr_ops == NULL) {
+#ifdef CONFIG_KMOD
+ if (try_to_load) {
+ request_module("zft-compressor");
+ if (zft_cmpr_ops == NULL) {
+ return -ENOSYS;
+ }
+ } else {
+ return -ENOSYS;
+ }
+#else
+ return -ENOSYS;
+#endif
+ }
+ (*zft_cmpr_ops->lock)();
+ return 0;
+}
+
+#ifdef CONFIG_ZFT_COMPRESSOR
+extern int zft_compressor_init(void);
+#endif
+
+/* Called by modules package when installing the driver or by kernel
+ * during the initialization phase
+ */
+int __init zft_init(void)
+{
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+#ifdef MODULE
+ printk(KERN_INFO ZFTAPE_VERSION "\n");
+ if (TRACE_LEVEL >= ft_t_info) {
+ printk(
+KERN_INFO
+"(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO
+"vfs interface for ftape floppy tape driver.\n"
+KERN_INFO
+"Support for QIC-113 compatible volume table, dynamic memory allocation\n"
+KERN_INFO
+"and builtin compression (lzrw3 algorithm).\n");
+ }
+#else /* !MODULE */
+ /* print a short no-nonsense boot message */
+ printk(KERN_INFO ZFTAPE_VERSION "\n");
+#endif /* MODULE */
+ TRACE(ft_t_info, "zft_init @ 0x%p", zft_init);
+ TRACE(ft_t_info,
+ "installing zftape VFS interface for ftape driver ...");
+ TRACE_CATCH(register_chrdev(QIC117_TAPE_MAJOR, "zft", &zft_cdev),);
+
+ zft_class = class_simple_create(THIS_MODULE, "zft");
+ for (i = 0; i < 4; i++) {
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i), NULL, "qft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "qft%i", i);
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 4), NULL, "nqft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 4),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "nqft%i", i);
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 16), NULL, "zqft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 16),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "zqft%i", i);
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 20), NULL, "nzqft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 20),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "nzqft%i", i);
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 32), NULL, "rawqft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 32),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "rawqft%i", i);
+ class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 36), NULL, "nrawrawqft%i", i);
+ devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 36),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "nrawqft%i", i);
+ }
+
+#ifdef CONFIG_ZFT_COMPRESSOR
+ (void)zft_compressor_init();
+#endif
+ zft_status = ftape_get_status(); /* fetch global data of ftape
+ * hardware driver
+ */
+ TRACE_EXIT 0;
+}
+
+
+/* Called by modules package when removing the driver
+ */
+static void zft_exit(void)
+{
+ int i;
+ TRACE_FUN(ft_t_flow);
+
+ if (unregister_chrdev(QIC117_TAPE_MAJOR, "zft") != 0) {
+ TRACE(ft_t_warn, "failed");
+ } else {
+ TRACE(ft_t_info, "successful");
+ }
+ for (i = 0; i < 4; i++) {
+ devfs_remove("qft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i));
+ devfs_remove("nqft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 4));
+ devfs_remove("zqft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 16));
+ devfs_remove("nzqft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 20));
+ devfs_remove("rawqft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 32));
+ devfs_remove("nrawqft%i", i);
+ class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 36));
+ }
+ class_simple_destroy(zft_class);
+ zft_uninit_mem(); /* release remaining memory, if any */
+ printk(KERN_INFO "zftape successfully unloaded.\n");
+ TRACE_EXIT;
+}
+
+module_init(zft_init);
+module_exit(zft_exit);
diff --git a/drivers/char/ftape/zftape/zftape-init.h b/drivers/char/ftape/zftape/zftape-init.h
new file mode 100644
index 000000000000..937e5d48c20e
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-init.h
@@ -0,0 +1,77 @@
+#ifndef _ZFTAPE_INIT_H
+#define _ZFTAPE_INIT_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-init.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:05 $
+ *
+ * This file contains definitions and macro for the vfs
+ * interface defined by zftape
+ *
+ */
+
+#include <linux/ftape-header-segment.h>
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-format.h"
+
+#include "../zftape/zftape-rw.h"
+
+#ifdef MODULE
+#define ftape_status (*zft_status)
+#endif
+
+extern const ftape_info *zft_status; /* needed for zftape-vtbl.h */
+
+#include "../zftape/zftape-vtbl.h"
+
+struct zft_cmpr_ops {
+ int (*write)(int *write_cnt,
+ __u8 *dst_buf, const int seg_sz,
+ const __u8 __user *src_buf, const int req_len,
+ const zft_position *pos, const zft_volinfo *volume);
+ int (*read)(int *read_cnt,
+ __u8 __user *dst_buf, const int req_len,
+ const __u8 *src_buf, const int seg_sz,
+ const zft_position *pos, const zft_volinfo *volume);
+ int (*seek)(unsigned int new_block_pos,
+ zft_position *pos, const zft_volinfo *volume,
+ __u8 *buffer);
+ void (*lock) (void);
+ void (*reset) (void);
+ void (*cleanup)(void);
+};
+
+extern struct zft_cmpr_ops *zft_cmpr_ops;
+/* zftape-init.c defined global functions.
+ */
+extern int zft_cmpr_register(struct zft_cmpr_ops *new_ops);
+extern int zft_cmpr_lock(int try_to_load);
+
+#endif
+
+
diff --git a/drivers/char/ftape/zftape/zftape-read.c b/drivers/char/ftape/zftape/zftape-read.c
new file mode 100644
index 000000000000..214bf03dce68
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-read.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:06 $
+ *
+ * This file contains the high level reading code
+ * for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/* Global vars.
+ */
+int zft_just_before_eof;
+
+/* Local vars.
+ */
+static int buf_len_rd;
+
+void zft_zap_read_buffers(void)
+{
+ buf_len_rd = 0;
+}
+
+int zft_read_header_segments(void)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_header_read = 0;
+ TRACE_CATCH(zft_vmalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),);
+ TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),);
+ TRACE(ft_t_info, "Segments written since first format: %d",
+ (int)GET4(zft_hseg_buf, FT_SEG_CNT));
+ zft_qic113 = (ft_format_code != fmt_normal &&
+ ft_format_code != fmt_1100ft &&
+ ft_format_code != fmt_425ft);
+ TRACE(ft_t_info, "ft_first_data_segment: %d, ft_last_data_segment: %d",
+ ft_first_data_segment, ft_last_data_segment);
+ zft_capacity = zft_get_capacity();
+ zft_old_ftape = zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]);
+ if (zft_old_ftape) {
+ TRACE(ft_t_info,
+"Found old ftaped tape, emulating eof marks, entering read-only mode");
+ zft_ftape_extract_file_marks(zft_hseg_buf);
+ TRACE_CATCH(zft_fake_volume_headers(zft_eof_map,
+ zft_nr_eof_marks),);
+ } else {
+ /* the specs say that the volume table must be
+ * initialized with zeroes during formatting, so it
+ * MUST be readable, i.e. contain vaid ECC
+ * information.
+ */
+ TRACE_CATCH(ftape_read_segment(ft_first_data_segment,
+ zft_deblock_buf,
+ FT_RD_SINGLE),);
+ TRACE_CATCH(zft_extract_volume_headers(zft_deblock_buf),);
+ }
+ zft_header_read = 1;
+ zft_set_flags(zft_unit);
+ zft_reset_position(&zft_pos);
+ TRACE_EXIT 0;
+}
+
+int zft_fetch_segment_fraction(const unsigned int segment, void *buffer,
+ const ft_read_mode_t read_mode,
+ const unsigned int start,
+ const unsigned int size)
+{
+ int seg_sz;
+ TRACE_FUN(ft_t_flow);
+
+ if (segment == zft_deblock_segment) {
+ TRACE(ft_t_data_flow,
+ "re-using segment %d already in deblock buffer",
+ segment);
+ seg_sz = zft_get_seg_sz(segment);
+ if (start > seg_sz) {
+ TRACE_ABORT(-EINVAL, ft_t_bug,
+ "trying to read beyond end of segment:\n"
+ KERN_INFO "seg_sz : %d\n"
+ KERN_INFO "start : %d\n"
+ KERN_INFO "segment: %d",
+ seg_sz, start, segment);
+ }
+ if ((start + size) > seg_sz) {
+ TRACE_EXIT seg_sz - start;
+ }
+ TRACE_EXIT size;
+ }
+ seg_sz = ftape_read_segment_fraction(segment, buffer, read_mode,
+ start, size);
+ TRACE(ft_t_data_flow, "segment %d, result %d", segment, seg_sz);
+ if ((int)seg_sz >= 0 && start == 0 && size == FT_SEGMENT_SIZE) {
+ /* this implicitly assumes that we are always called with
+ * buffer == zft_deblock_buf
+ */
+ zft_deblock_segment = segment;
+ } else {
+ zft_deblock_segment = -1;
+ }
+ TRACE_EXIT seg_sz;
+}
+
+/*
+ * out:
+ *
+ * int *read_cnt: the number of bytes we removed from the
+ * zft_deblock_buf (result)
+ *
+ * int *to_do : the remaining size of the read-request. Is changed.
+ *
+ * in:
+ *
+ * char *buff : buff is the address of the upper part of the user
+ * buffer, that hasn't been filled with data yet.
+ * int buf_pos_read: copy of buf_pos_rd
+ * int buf_len_read: copy of buf_len_rd
+ * char *zft_deblock_buf: ftape_zft_deblock_buf
+ *
+ * returns the amount of data actually copied to the user-buffer
+ *
+ * to_do MUST NOT SHRINK except to indicate an EOT. In this case to_do
+ * has to be set to 0. We cannot return -ENOSPC, because we return the
+ * amount of data actually * copied to the user-buffer
+ */
+static int zft_simple_read (int *read_cnt,
+ __u8 __user *dst_buf,
+ const int to_do,
+ const __u8 *src_buf,
+ const int seg_sz,
+ const zft_position *pos,
+ const zft_volinfo *volume)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (seg_sz - pos->seg_byte_pos < to_do) {
+ *read_cnt = seg_sz - pos->seg_byte_pos;
+ } else {
+ *read_cnt = to_do;
+ }
+ if (copy_to_user(dst_buf,
+ src_buf + pos->seg_byte_pos, *read_cnt) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ TRACE(ft_t_noise, "nr bytes just read: %d", *read_cnt);
+ TRACE_EXIT *read_cnt;
+}
+
+/* req_len: gets clipped due to EOT of EOF.
+ * req_clipped: is a flag indicating whether req_len was clipped or not
+ * volume: contains information on current volume (blk_sz etc.)
+ */
+static int check_read_access(int *req_len,
+ const zft_volinfo **volume,
+ int *req_clipped,
+ const zft_position *pos)
+{
+ static __s64 remaining;
+ static int eod;
+ TRACE_FUN(ft_t_flow);
+
+ if (zft_io_state != zft_reading) {
+ if (zft_offline) { /* offline includes no_tape */
+ TRACE_ABORT(-ENXIO, ft_t_warn,
+ "tape is offline or no cartridge");
+ }
+ if (!ft_formatted) {
+ TRACE_ABORT(-EACCES,
+ ft_t_warn, "tape is not formatted");
+ }
+ /* now enter defined state, read header segment if not
+ * already done and flush write buffers
+ */
+ TRACE_CATCH(zft_def_idle_state(),);
+ zft_io_state = zft_reading;
+ if (zft_tape_at_eod(pos)) {
+ eod = 1;
+ TRACE_EXIT 1;
+ }
+ eod = 0;
+ *volume = zft_find_volume(pos->seg_pos);
+ /* get the space left until EOF */
+ remaining = zft_check_for_eof(*volume, pos);
+ buf_len_rd = 0;
+ TRACE(ft_t_noise, "remaining: " LL_X ", vol_no: %d",
+ LL(remaining), (*volume)->count);
+ } else if (zft_tape_at_eod(pos)) {
+ if (++eod > 2) {
+ TRACE_EXIT -EIO; /* st.c also returns -EIO */
+ } else {
+ TRACE_EXIT 1;
+ }
+ }
+ if ((*req_len % (*volume)->blk_sz) != 0) {
+ /* this message is informational only. The user gets the
+ * proper return value
+ */
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "req_len %d not a multiple of block size %d",
+ *req_len, (*volume)->blk_sz);
+ }
+ /* As GNU tar doesn't accept partial read counts when the
+ * multiple volume flag is set, we make sure to return the
+ * requested amount of data. Except, of course, at the end of
+ * the tape or file mark.
+ */
+ remaining -= *req_len;
+ if (remaining <= 0) {
+ TRACE(ft_t_noise,
+ "clipped request from %d to %d.",
+ *req_len, (int)(*req_len + remaining));
+ *req_len += remaining;
+ *req_clipped = 1;
+ } else {
+ *req_clipped = 0;
+ }
+ TRACE_EXIT 0;
+}
+
+/* this_segs_size: the current segment's size.
+ * buff: the USER-SPACE buffer provided by the calling function.
+ * req_len: how much data should be read at most.
+ * volume: contains information on current volume (blk_sz etc.)
+ */
+static int empty_deblock_buf(__u8 __user *usr_buf, const int req_len,
+ const __u8 *src_buf, const int seg_sz,
+ zft_position *pos,
+ const zft_volinfo *volume)
+{
+ int cnt;
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_data_flow, "this_segs_size: %d", seg_sz);
+ if (zft_use_compression && volume->use_compression) {
+ TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+ TRACE_CATCH(result= (*zft_cmpr_ops->read)(&cnt,
+ usr_buf, req_len,
+ src_buf, seg_sz,
+ pos, volume),);
+ } else {
+ TRACE_CATCH(result= zft_simple_read (&cnt,
+ usr_buf, req_len,
+ src_buf, seg_sz,
+ pos, volume),);
+ }
+ pos->volume_pos += result;
+ pos->tape_pos += cnt;
+ pos->seg_byte_pos += cnt;
+ buf_len_rd -= cnt; /* remaining bytes in buffer */
+ TRACE(ft_t_data_flow, "buf_len_rd: %d, cnt: %d", buf_len_rd, cnt);
+ if(pos->seg_byte_pos >= seg_sz) {
+ pos->seg_pos++;
+ pos->seg_byte_pos = 0;
+ }
+ TRACE(ft_t_data_flow, "bytes moved out of deblock-buffer: %d", cnt);
+ TRACE_EXIT result;
+}
+
+
+/* note: we store the segment id of the segment that is inside the
+ * deblock buffer. This spares a lot of ftape_read_segment()s when we
+ * use small block-sizes. The block-size may be 1kb (SECTOR_SIZE). In
+ * this case a MTFSR 28 maybe still inside the same segment.
+ */
+int _zft_read(char __user *buff, int req_len)
+{
+ int req_clipped;
+ int result = 0;
+ int bytes_read = 0;
+ static unsigned int seg_sz = 0;
+ static const zft_volinfo *volume = NULL;
+ TRACE_FUN(ft_t_flow);
+
+ zft_resid = req_len;
+ result = check_read_access(&req_len, &volume,
+ &req_clipped, &zft_pos);
+ switch(result) {
+ case 0:
+ break; /* nothing special */
+ case 1:
+ TRACE(ft_t_noise, "EOD reached");
+ TRACE_EXIT 0; /* EOD */
+ default:
+ TRACE_ABORT(result, ft_t_noise,
+ "check_read_access() failed with result %d",
+ result);
+ TRACE_EXIT result;
+ }
+ while (req_len > 0) {
+ /* Allow escape from this loop on signal !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ /* buf_len_rd == 0 means that we need to read a new
+ * segment.
+ */
+ if (buf_len_rd == 0) {
+ while((result = zft_fetch_segment(zft_pos.seg_pos,
+ zft_deblock_buf,
+ FT_RD_AHEAD)) == 0) {
+ zft_pos.seg_pos ++;
+ zft_pos.seg_byte_pos = 0;
+ }
+ if (result < 0) {
+ zft_resid -= bytes_read;
+ TRACE_ABORT(result, ft_t_noise,
+ "zft_fetch_segment(): %d",
+ result);
+ }
+ seg_sz = result;
+ buf_len_rd = seg_sz - zft_pos.seg_byte_pos;
+ }
+ TRACE_CATCH(result = empty_deblock_buf(buff,
+ req_len,
+ zft_deblock_buf,
+ seg_sz,
+ &zft_pos,
+ volume),
+ zft_resid -= bytes_read);
+ TRACE(ft_t_data_flow, "bytes just read: %d", result);
+ bytes_read += result; /* what we got so far */
+ buff += result; /* index in user-buffer */
+ req_len -= result; /* what's left from req_len */
+ } /* while (req_len > 0) */
+ if (req_clipped) {
+ TRACE(ft_t_data_flow,
+ "maybe partial count because of eof mark");
+ if (zft_just_before_eof && bytes_read == 0) {
+ /* req_len was > 0, but user didn't get
+ * anything the user has read in the eof-mark
+ */
+ zft_move_past_eof(&zft_pos);
+ ftape_abort_operation();
+ } else {
+ /* don't skip to the next file before the user
+ * tried to read a second time past EOF Just
+ * mark that we are at EOF and maybe decrement
+ * zft_seg_pos to stay in the same volume;
+ */
+ zft_just_before_eof = 1;
+ zft_position_before_eof(&zft_pos, volume);
+ TRACE(ft_t_noise, "just before eof");
+ }
+ }
+ zft_resid -= result; /* for MTSTATUS */
+ TRACE_EXIT bytes_read;
+}
diff --git a/drivers/char/ftape/zftape/zftape-read.h b/drivers/char/ftape/zftape/zftape-read.h
new file mode 100644
index 000000000000..42941de0c23a
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-read.h
@@ -0,0 +1,53 @@
+#ifndef _ZFTAPE_READ_H
+#define _ZFTAPE_READ_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:07 $
+ *
+ * This file contains the definitions for the read functions
+ * for the zftape driver for Linux.
+ *
+ */
+
+#include "../lowlevel/ftape-read.h"
+
+/* ftape-read.c defined global vars.
+ */
+extern int zft_just_before_eof;
+
+/* ftape-read.c defined global functions.
+ */
+extern void zft_zap_read_buffers(void);
+extern int zft_read_header_segments(void);
+extern int zft_fetch_segment_fraction(const unsigned int segment,
+ void *buffer,
+ const ft_read_mode_t read_mode,
+ const unsigned int start,
+ const unsigned int size);
+#define zft_fetch_segment(segment, address, read_mode) \
+ zft_fetch_segment_fraction(segment, address, read_mode, \
+ 0, FT_SEGMENT_SIZE)
+/* hook for the VFS interface
+ */
+extern int _zft_read(char __user *buff, int req_len);
+
+#endif /* _ZFTAPE_READ_H */
diff --git a/drivers/char/ftape/zftape/zftape-rw.c b/drivers/char/ftape/zftape/zftape-rw.c
new file mode 100644
index 000000000000..a61ef50f3dfc
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-rw.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:08 $
+ *
+ * This file contains some common code for the r/w code for
+ * zftape.
+ */
+
+#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/* Global vars.
+ */
+
+__u8 *zft_deblock_buf;
+__u8 *zft_hseg_buf;
+int zft_deblock_segment = -1;
+zft_status_enum zft_io_state = zft_idle;
+int zft_header_changed;
+int zft_qic113; /* conform to old specs. and old zftape */
+int zft_use_compression;
+zft_position zft_pos = {
+ -1, /* seg_pos */
+ 0, /* seg_byte_pos */
+ 0, /* tape_pos */
+ 0 /* volume_pos */
+};
+unsigned int zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ;
+__s64 zft_capacity;
+
+unsigned int zft_written_segments;
+int zft_label_changed;
+
+/* Local vars.
+ */
+
+unsigned int zft_get_seg_sz(unsigned int segment)
+{
+ int size;
+ TRACE_FUN(ft_t_any);
+
+ size = FT_SEGMENT_SIZE -
+ count_ones(ftape_get_bad_sector_entry(segment))*FT_SECTOR_SIZE;
+ if (size > 0) {
+ TRACE_EXIT (unsigned)size;
+ } else {
+ TRACE_EXIT 0;
+ }
+}
+
+/* ftape_set_flags(). Claus-Justus Heine, 1994/1995
+ */
+void zft_set_flags(unsigned minor_unit)
+{
+ TRACE_FUN(ft_t_flow);
+
+ zft_use_compression = zft_qic_mode = 0;
+ switch (minor_unit & ZFT_MINOR_OP_MASK) {
+ case (ZFT_Q80_MODE | ZFT_ZIP_MODE):
+ case ZFT_ZIP_MODE:
+ zft_use_compression = 1;
+ case 0:
+ case ZFT_Q80_MODE:
+ zft_qic_mode = 1;
+ if (zft_mt_compression) { /* override the default */
+ zft_use_compression = 1;
+ }
+ break;
+ case ZFT_RAW_MODE:
+ TRACE(ft_t_noise, "switching to raw mode");
+ break;
+ default:
+ TRACE(ft_t_warn, "Warning:\n"
+ KERN_INFO "Wrong combination of minor device bits.\n"
+ KERN_INFO "Switching to raw read-only mode.");
+ zft_write_protected = 1;
+ break;
+ }
+ TRACE_EXIT;
+}
+
+/* computes the segment and byte offset inside the segment
+ * corresponding to tape_pos.
+ *
+ * tape_pos gives the offset in bytes from the beginning of the
+ * ft_first_data_segment *seg_byte_pos is the offset in the current
+ * segment in bytes
+ *
+ * Of, if this routine was called often one should cache the last data
+ * pos it was called with, but actually this is only needed in
+ * ftape_seek_block(), that is, almost never.
+ */
+int zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos)
+{
+ int segment;
+ int seg_sz;
+ TRACE_FUN(ft_t_flow);
+
+ if (tape_pos == 0) {
+ *seg_byte_pos = 0;
+ segment = ft_first_data_segment;
+ } else {
+ seg_sz = 0;
+
+ for (segment = ft_first_data_segment;
+ ((tape_pos > 0) && (segment <= ft_last_data_segment));
+ segment++) {
+ seg_sz = zft_get_seg_sz(segment);
+ tape_pos -= seg_sz;
+ }
+ if(tape_pos >= 0) {
+ /* the case tape_pos > != 0 means that the
+ * argument tape_pos lies beyond the EOT.
+ */
+ *seg_byte_pos= 0;
+ } else { /* tape_pos < 0 */
+ segment--;
+ *seg_byte_pos= tape_pos + seg_sz;
+ }
+ }
+ TRACE_EXIT(segment);
+}
+
+/* ftape_calc_tape_pos().
+ *
+ * computes the offset in bytes from the beginning of the
+ * ft_first_data_segment inverse to ftape_calc_seg_byte_coord
+ *
+ * We should do some caching. But how:
+ *
+ * Each time the header segments are read in, this routine is called
+ * with ft_tracks_per_tape*segments_per_track argumnet. So this should be
+ * the time to reset the cache.
+ *
+ * Also, it might be in the future that the bad sector map gets
+ * changed. -> reset the cache
+ */
+static int seg_pos;
+static __s64 tape_pos;
+
+__s64 zft_get_capacity(void)
+{
+ seg_pos = ft_first_data_segment;
+ tape_pos = 0;
+
+ while (seg_pos <= ft_last_data_segment) {
+ tape_pos += zft_get_seg_sz(seg_pos ++);
+ }
+ return tape_pos;
+}
+
+__s64 zft_calc_tape_pos(int segment)
+{
+ int d1, d2, d3;
+ TRACE_FUN(ft_t_any);
+
+ if (segment > ft_last_data_segment) {
+ TRACE_EXIT zft_capacity;
+ }
+ if (segment < ft_first_data_segment) {
+ TRACE_EXIT 0;
+ }
+ d2 = segment - seg_pos;
+ if (-d2 > 10) {
+ d1 = segment - ft_first_data_segment;
+ if (-d2 > d1) {
+ tape_pos = 0;
+ seg_pos = ft_first_data_segment;
+ d2 = d1;
+ }
+ }
+ if (d2 > 10) {
+ d3 = ft_last_data_segment - segment;
+ if (d2 > d3) {
+ tape_pos = zft_capacity;
+ seg_pos = ft_last_data_segment + 1;
+ d2 = -d3;
+ }
+ }
+ if (d2 > 0) {
+ while (seg_pos < segment) {
+ tape_pos += zft_get_seg_sz(seg_pos++);
+ }
+ } else {
+ while (seg_pos > segment) {
+ tape_pos -= zft_get_seg_sz(--seg_pos);
+ }
+ }
+ TRACE(ft_t_noise, "new cached pos: %d", seg_pos);
+
+ TRACE_EXIT tape_pos;
+}
+
+/* copy Z-label string to buffer, keeps track of the correct offset in
+ * `buffer'
+ */
+void zft_update_label(__u8 *buffer)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (strncmp(&buffer[FT_LABEL], ZFTAPE_LABEL,
+ sizeof(ZFTAPE_LABEL)-1) != 0) {
+ TRACE(ft_t_info, "updating label from \"%s\" to \"%s\"",
+ &buffer[FT_LABEL], ZFTAPE_LABEL);
+ strcpy(&buffer[FT_LABEL], ZFTAPE_LABEL);
+ memset(&buffer[FT_LABEL] + sizeof(ZFTAPE_LABEL) - 1, ' ',
+ FT_LABEL_SZ - sizeof(ZFTAPE_LABEL + 1));
+ PUT4(buffer, FT_LABEL_DATE, 0);
+ zft_label_changed = zft_header_changed = 1; /* changed */
+ }
+ TRACE_EXIT;
+}
+
+int zft_verify_write_segments(unsigned int segment,
+ __u8 *data, size_t size,
+ __u8 *buffer)
+{
+ int result;
+ __u8 *write_buf;
+ __u8 *src_buf;
+ int single;
+ int seg_pos;
+ int seg_sz;
+ int remaining;
+ ft_write_mode_t write_mode;
+ TRACE_FUN(ft_t_flow);
+
+ seg_pos = segment;
+ seg_sz = zft_get_seg_sz(seg_pos);
+ src_buf = data;
+ single = size <= seg_sz;
+ remaining = size;
+ do {
+ TRACE(ft_t_noise, "\n"
+ KERN_INFO "remaining: %d\n"
+ KERN_INFO "seg_sz : %d\n"
+ KERN_INFO "segment : %d",
+ remaining, seg_sz, seg_pos);
+ if (remaining == seg_sz) {
+ write_buf = src_buf;
+ write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI;
+ remaining = 0;
+ } else if (remaining > seg_sz) {
+ write_buf = src_buf;
+ write_mode = FT_WR_ASYNC; /* don't start tape */
+ remaining -= seg_sz;
+ } else { /* remaining < seg_sz */
+ write_buf = buffer;
+ memcpy(write_buf, src_buf, remaining);
+ memset(&write_buf[remaining],'\0',seg_sz-remaining);
+ write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI;
+ remaining = 0;
+ }
+ if ((result = ftape_write_segment(seg_pos,
+ write_buf,
+ write_mode)) != seg_sz) {
+ TRACE(ft_t_err, "Error: "
+ "Couldn't write segment %d", seg_pos);
+ TRACE_EXIT result < 0 ? result : -EIO; /* bail out */
+ }
+ zft_written_segments ++;
+ seg_sz = zft_get_seg_sz(++seg_pos);
+ src_buf += result;
+ } while (remaining > 0);
+ if (ftape_get_status()->fti_state == writing) {
+ TRACE_CATCH(ftape_loop_until_writes_done(),);
+ TRACE_CATCH(ftape_abort_operation(),);
+ zft_prevent_flush();
+ }
+ seg_pos = segment;
+ src_buf = data;
+ remaining = size;
+ do {
+ TRACE_CATCH(result = ftape_read_segment(seg_pos, buffer,
+ single ? FT_RD_SINGLE
+ : FT_RD_AHEAD),);
+ if (memcmp(src_buf, buffer,
+ remaining > result ? result : remaining) != 0) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "Failed to verify written segment %d",
+ seg_pos);
+ }
+ remaining -= result;
+ TRACE(ft_t_noise, "verify successful:\n"
+ KERN_INFO "segment : %d\n"
+ KERN_INFO "segsize : %d\n"
+ KERN_INFO "remaining: %d",
+ seg_pos, result, remaining);
+ src_buf += seg_sz;
+ seg_pos++;
+ } while (remaining > 0);
+ TRACE_EXIT size;
+}
+
+
+/* zft_erase(). implemented compression-handling
+ *
+ * calculate the first data-segment when using/not using compression.
+ *
+ * update header-segment and compression-map-segment.
+ */
+int zft_erase(void)
+{
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (!zft_header_read) {
+ TRACE_CATCH(zft_vmalloc_once((void **)&zft_hseg_buf,
+ FT_SEGMENT_SIZE),);
+ /* no need to read the vtbl and compression map */
+ TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),);
+ if ((zft_old_ftape =
+ zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]))) {
+ zft_ftape_extract_file_marks(zft_hseg_buf);
+ }
+ TRACE(ft_t_noise,
+ "ft_first_data_segment: %d, ft_last_data_segment: %d",
+ ft_first_data_segment, ft_last_data_segment);
+ zft_qic113 = (ft_format_code != fmt_normal &&
+ ft_format_code != fmt_1100ft &&
+ ft_format_code != fmt_425ft);
+ }
+ if (zft_old_ftape) {
+ zft_clear_ftape_file_marks();
+ zft_old_ftape = 0; /* no longer old ftape */
+ }
+ PUT2(zft_hseg_buf, FT_CMAP_START, 0);
+ zft_volume_table_changed = 1;
+ zft_capacity = zft_get_capacity();
+ zft_init_vtbl();
+ /* the rest must be done in ftape_update_header_segments
+ */
+ zft_header_read = 1;
+ zft_header_changed = 1; /* force update of timestamp */
+ result = zft_update_header_segments();
+
+ ftape_abort_operation();
+
+ zft_reset_position(&zft_pos);
+ zft_set_flags (zft_unit);
+ TRACE_EXIT result;
+}
+
+unsigned int zft_get_time(void)
+{
+ unsigned int date = FT_TIME_STAMP(2097, 11, 30, 23, 59, 59); /* fun */
+ return date;
+}
diff --git a/drivers/char/ftape/zftape/zftape-rw.h b/drivers/char/ftape/zftape/zftape-rw.h
new file mode 100644
index 000000000000..14c07f086575
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-rw.h
@@ -0,0 +1,102 @@
+#ifndef _ZFTAPE_RW_H
+#define _ZFTAPE_RW_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:09 $
+ *
+ * This file contains the definitions for the read and write
+ * functions for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */
+#include "../zftape/zftape-buffers.h"
+
+#define SEGMENTS_PER_TAPE (ft_segments_per_track * ft_tracks_per_tape)
+
+/* QIC-113 Rev. G says that `a maximum of 63488 raw bytes may be
+ * compressed into a single frame'.
+ * Maybe we should stick to 32kb to make it more `beautiful'
+ */
+#define ZFT_MAX_BLK_SZ (62*1024) /* bytes */
+#if !defined(CONFIG_ZFT_DFLT_BLK_SZ)
+# define CONFIG_ZFT_DFLT_BLK_SZ (10*1024) /* bytes, default of gnu tar */
+#elif CONFIG_ZFT_DFLT_BLK_SZ == 0
+# undef CONFIG_ZFT_DFLT_BLK_SZ
+# define CONFIG_ZFT_DFLT_BLK_SZ 1
+#elif (CONFIG_ZFT_DFLT_BLK_SZ % 1024) != 0
+# error CONFIG_ZFT_DFLT_BLK_SZ must be 1 or a multiple of 1024
+#endif
+/* The *optional* compression routines need some overhead per tape
+ * block for their purposes. Instead of asking the actual compression
+ * implementation how much it needs, we restrict this overhead to be
+ * maximal of ZFT_CMPT_OVERHEAD size. We need this for EOT
+ * conditions. The tape is assumed to be logical at EOT when the
+ * distance from the physical EOT is less than
+ * one tape block + ZFT_CMPR_OVERHEAD
+ */
+#define ZFT_CMPR_OVERHEAD 16 /* bytes */
+
+typedef enum
+{
+ zft_idle = 0,
+ zft_reading,
+ zft_writing,
+} zft_status_enum;
+
+typedef struct /* all values measured in bytes */
+{
+ int seg_pos; /* segment currently positioned at */
+ int seg_byte_pos; /* offset in current segment */
+ __s64 tape_pos; /* real offset from BOT */
+ __s64 volume_pos; /* pos. in uncompressed data stream in
+ * current volume
+ */
+} zft_position;
+
+extern zft_position zft_pos;
+extern __u8 *zft_deblock_buf;
+extern __u8 *zft_hseg_buf;
+extern int zft_deblock_segment;
+extern zft_status_enum zft_io_state;
+extern int zft_header_changed;
+extern int zft_qic113; /* conform to old specs. and old zftape */
+extern int zft_use_compression;
+extern unsigned int zft_blk_sz;
+extern __s64 zft_capacity;
+extern unsigned int zft_written_segments;
+extern int zft_label_changed;
+
+/* zftape-rw.c exported functions
+ */
+extern unsigned int zft_get_seg_sz(unsigned int segment);
+extern void zft_set_flags(unsigned int minor_unit);
+extern int zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos);
+extern __s64 zft_calc_tape_pos(int segment);
+extern __s64 zft_get_capacity(void);
+extern void zft_update_label(__u8 *buffer);
+extern int zft_erase(void);
+extern int zft_verify_write_segments(unsigned int segment,
+ __u8 *data, size_t size, __u8 *buffer);
+extern unsigned int zft_get_time(void);
+#endif /* _ZFTAPE_RW_H */
+
diff --git a/drivers/char/ftape/zftape/zftape-vtbl.c b/drivers/char/ftape/zftape/zftape-vtbl.c
new file mode 100644
index 000000000000..ad7f8be6340b
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-vtbl.c
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 1995-1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.c,v $
+ * $Revision: 1.7.6.1 $
+ * $Date: 1997/11/24 13:48:31 $
+ *
+ * This file defines a volume table as defined in various QIC
+ * standards.
+ *
+ * This is a minimal implementation, just allowing ordinary DOS
+ * :( prgrams to identify the cartridge as used.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <linux/zftape.h>
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+#define ZFT_CMAP_HACK /* leave this defined to hide the compression map */
+
+/*
+ * global variables
+ */
+int zft_qic_mode = 1; /* use the vtbl */
+int zft_old_ftape; /* prevents old ftaped tapes to be overwritten */
+int zft_volume_table_changed; /* for write_header_segments() */
+
+/*
+ * private variables (only exported for inline functions)
+ */
+LIST_HEAD(zft_vtbl);
+
+/* We could also allocate these dynamically when extracting the volume table
+ * sizeof(zft_volinfo) is about 32 or something close to that
+ */
+static zft_volinfo tape_vtbl;
+static zft_volinfo eot_vtbl;
+static zft_volinfo *cur_vtbl;
+
+static inline void zft_new_vtbl_entry(void)
+{
+ struct list_head *tmp = &zft_last_vtbl->node;
+ zft_volinfo *new = zft_kmalloc(sizeof(zft_volinfo));
+
+ list_add(&new->node, tmp);
+ new->count = zft_eom_vtbl->count ++;
+}
+
+void zft_free_vtbl(void)
+{
+ for (;;) {
+ struct list_head *tmp = zft_vtbl.prev;
+ zft_volinfo *vtbl;
+
+ if (tmp == &zft_vtbl)
+ break;
+ list_del(tmp);
+ vtbl = list_entry(tmp, zft_volinfo, node);
+ zft_kfree(vtbl, sizeof(zft_volinfo));
+ }
+ INIT_LIST_HEAD(&zft_vtbl);
+ cur_vtbl = NULL;
+}
+
+/* initialize vtbl, called by ftape_new_cartridge()
+ */
+void zft_init_vtbl(void)
+{
+ zft_volinfo *new;
+
+ zft_free_vtbl();
+
+ /* Create the two dummy vtbl entries
+ */
+ new = zft_kmalloc(sizeof(zft_volinfo));
+ list_add(&new->node, &zft_vtbl);
+ new = zft_kmalloc(sizeof(zft_volinfo));
+ list_add(&new->node, &zft_vtbl);
+ zft_head_vtbl->end_seg = ft_first_data_segment;
+ zft_head_vtbl->blk_sz = zft_blk_sz;
+ zft_head_vtbl->count = -1;
+ zft_eom_vtbl->start_seg = ft_first_data_segment + 1;
+ zft_eom_vtbl->end_seg = ft_last_data_segment + 1;
+ zft_eom_vtbl->blk_sz = zft_blk_sz;
+ zft_eom_vtbl->count = 0;
+
+ /* Reset the pointer for zft_find_volume()
+ */
+ cur_vtbl = zft_eom_vtbl;
+
+ /* initialize the dummy vtbl entries for zft_qic_mode == 0
+ */
+ eot_vtbl.start_seg = ft_last_data_segment + 1;
+ eot_vtbl.end_seg = ft_last_data_segment + 1;
+ eot_vtbl.blk_sz = zft_blk_sz;
+ eot_vtbl.count = -1;
+ tape_vtbl.start_seg = ft_first_data_segment;
+ tape_vtbl.end_seg = ft_last_data_segment;
+ tape_vtbl.blk_sz = zft_blk_sz;
+ tape_vtbl.size = zft_capacity;
+ tape_vtbl.count = 0;
+}
+
+/* check for a valid VTBL signature.
+ */
+static int vtbl_signature_valid(__u8 signature[4])
+{
+ const char *vtbl_ids[] = VTBL_IDS; /* valid signatures */
+ int j;
+
+ for (j = 0;
+ (j < NR_ITEMS(vtbl_ids)) && (memcmp(signature, vtbl_ids[j], 4) != 0);
+ j++);
+ return j < NR_ITEMS(vtbl_ids);
+}
+
+/* We used to store the block-size of the volume in the volume-label,
+ * using the keyword "blocksize". The blocksize written to the
+ * volume-label is in bytes.
+ *
+ * We use this now only for compatibility with old zftape version. We
+ * store the blocksize directly as binary number in the vendor
+ * extension part of the volume entry.
+ */
+static int check_volume_label(const char *label, int *blk_sz)
+{
+ int valid_format;
+ char *blocksize;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "called with \"%s\" / \"%s\"", label, ZFT_VOL_NAME);
+ if (strncmp(label, ZFT_VOL_NAME, strlen(ZFT_VOL_NAME)) != 0) {
+ *blk_sz = 1; /* smallest block size that we allow */
+ valid_format = 0;
+ } else {
+ TRACE(ft_t_noise, "got old style zftape vtbl entry");
+ /* get the default blocksize */
+ /* use the kernel strstr() */
+ blocksize= strstr(label, " blocksize ");
+ if (blocksize) {
+ blocksize += strlen(" blocksize ");
+ for(*blk_sz= 0;
+ *blocksize >= '0' && *blocksize <= '9';
+ blocksize++) {
+ *blk_sz *= 10;
+ *blk_sz += *blocksize - '0';
+ }
+ if (*blk_sz > ZFT_MAX_BLK_SZ) {
+ *blk_sz= 1;
+ valid_format= 0;
+ } else {
+ valid_format = 1;
+ }
+ } else {
+ *blk_sz= 1;
+ valid_format= 0;
+ }
+ }
+ TRACE_EXIT valid_format;
+}
+
+/* check for a zftape volume
+ */
+static int check_volume(__u8 *entry, zft_volinfo *volume)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if(strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+ strlen(ZFTAPE_SIG)) == 0) {
+ TRACE(ft_t_noise, "got new style zftape vtbl entry");
+ volume->blk_sz = GET2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ);
+ volume->qic113 = entry[VTBL_EXT+EXT_ZFTAPE_QIC113];
+ TRACE_EXIT 1;
+ } else {
+ TRACE_EXIT check_volume_label(&entry[VTBL_DESC], &volume->blk_sz);
+ }
+}
+
+
+/* create zftape specific vtbl entry, the volume bounds are inserted
+ * in the calling function, zft_create_volume_headers()
+ */
+static void create_zft_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+ TRACE_FUN(ft_t_flow);
+
+ memset(entry, 0, VTBL_SIZE);
+ memcpy(&entry[VTBL_SIG], VTBL_ID, 4);
+ sprintf(&entry[VTBL_DESC], ZFT_VOL_NAME" %03d", vtbl->count);
+ entry[VTBL_FLAGS] = (VTBL_FL_NOT_VERIFIED | VTBL_FL_SEG_SPANNING);
+ entry[VTBL_M_NO] = 1; /* multi_cartridge_count */
+ strcpy(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG);
+ PUT2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ, vtbl->blk_sz);
+ if (zft_qic113) {
+ PUT8(entry, VTBL_DATA_SIZE, vtbl->size);
+ entry[VTBL_CMPR] = VTBL_CMPR_UNREG;
+ if (vtbl->use_compression) { /* use compression: */
+ entry[VTBL_CMPR] |= VTBL_CMPR_USED;
+ }
+ entry[VTBL_EXT+EXT_ZFTAPE_QIC113] = 1;
+ } else {
+ PUT4(entry, VTBL_DATA_SIZE, vtbl->size);
+ entry[VTBL_K_CMPR] = VTBL_CMPR_UNREG;
+ if (vtbl->use_compression) { /* use compression: */
+ entry[VTBL_K_CMPR] |= VTBL_CMPR_USED;
+ }
+ }
+ if (ft_format_code == fmt_big) {
+ /* SCSI like vtbl, store the number of used
+ * segments as 4 byte value
+ */
+ PUT4(entry, VTBL_SCSI_SEGS, vtbl->end_seg-vtbl->start_seg + 1);
+ } else {
+ /* normal, QIC-80MC like vtbl
+ */
+ PUT2(entry, VTBL_START, vtbl->start_seg);
+ PUT2(entry, VTBL_END, vtbl->end_seg);
+ }
+ TRACE_EXIT;
+}
+
+/* this one creates the volume headers for each volume. It is assumed
+ * that buffer already contains the old volume-table, so that vtbl
+ * entries without the zft_volume flag set can savely be ignored.
+ */
+static void zft_create_volume_headers(__u8 *buffer)
+{
+ __u8 *entry;
+ struct list_head *tmp;
+ zft_volinfo *vtbl;
+ TRACE_FUN(ft_t_flow);
+
+#ifdef ZFT_CMAP_HACK
+ if((strncmp(&buffer[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+ strlen(ZFTAPE_SIG)) == 0) &&
+ buffer[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) {
+ TRACE(ft_t_noise, "deleting cmap volume");
+ memmove(buffer, buffer + VTBL_SIZE,
+ FT_SEGMENT_SIZE - VTBL_SIZE);
+ }
+#endif
+ entry = buffer;
+ for (tmp = zft_head_vtbl->node.next;
+ tmp != &zft_eom_vtbl->node;
+ tmp = tmp->next) {
+ vtbl = list_entry(tmp, zft_volinfo, node);
+ /* we now fill in the values only for newly created volumes.
+ */
+ if (vtbl->new_volume) {
+ create_zft_volume(entry, vtbl);
+ vtbl->new_volume = 0; /* clear the flag */
+ }
+
+ DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], vtbl);
+ entry += VTBL_SIZE;
+ }
+ memset(entry, 0, FT_SEGMENT_SIZE - zft_eom_vtbl->count * VTBL_SIZE);
+ TRACE_EXIT;
+}
+
+/* write volume table to tape. Calls zft_create_volume_headers()
+ */
+int zft_update_volume_table(unsigned int segment)
+{
+ int result = 0;
+ __u8 *verify_buf = NULL;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE_CATCH(result = ftape_read_segment(ft_first_data_segment,
+ zft_deblock_buf,
+ FT_RD_SINGLE),);
+ zft_create_volume_headers(zft_deblock_buf);
+ TRACE(ft_t_noise, "writing volume table segment %d", segment);
+ if (zft_vmalloc_once(&verify_buf, FT_SEGMENT_SIZE) == 0) {
+ TRACE_CATCH(zft_verify_write_segments(segment,
+ zft_deblock_buf, result,
+ verify_buf),
+ zft_vfree(&verify_buf, FT_SEGMENT_SIZE));
+ zft_vfree(&verify_buf, FT_SEGMENT_SIZE);
+ } else {
+ TRACE_CATCH(ftape_write_segment(segment, zft_deblock_buf,
+ FT_WR_SINGLE),);
+ }
+ TRACE_EXIT 0;
+}
+
+/* non zftape volumes are handled in raw mode. Thus we need to
+ * calculate the raw amount of data contained in those segments.
+ */
+static void extract_alien_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+ TRACE_FUN(ft_t_flow);
+
+ vtbl->size = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1) -
+ zft_calc_tape_pos(zft_last_vtbl->start_seg));
+ vtbl->use_compression = 0;
+ vtbl->qic113 = zft_qic113;
+ if (vtbl->qic113) {
+ TRACE(ft_t_noise,
+ "Fake alien volume's size from " LL_X " to " LL_X,
+ LL(GET8(entry, VTBL_DATA_SIZE)), LL(vtbl->size));
+ } else {
+ TRACE(ft_t_noise,
+ "Fake alien volume's size from %d to " LL_X,
+ (int)GET4(entry, VTBL_DATA_SIZE), LL(vtbl->size));
+ }
+ TRACE_EXIT;
+}
+
+
+/* extract an zftape specific volume
+ */
+static void extract_zft_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (vtbl->qic113) {
+ vtbl->size = GET8(entry, VTBL_DATA_SIZE);
+ vtbl->use_compression =
+ (entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0;
+ } else {
+ vtbl->size = GET4(entry, VTBL_DATA_SIZE);
+ if (entry[VTBL_K_CMPR] & VTBL_CMPR_UNREG) {
+ vtbl->use_compression =
+ (entry[VTBL_K_CMPR] & VTBL_CMPR_USED) != 0;
+ } else if (entry[VTBL_CMPR] & VTBL_CMPR_UNREG) {
+ vtbl->use_compression =
+ (entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0;
+ } else {
+ TRACE(ft_t_warn, "Geeh! There is something wrong:\n"
+ KERN_INFO "QIC compression (Rev = K): %x\n"
+ KERN_INFO "QIC compression (Rev > K): %x",
+ entry[VTBL_K_CMPR], entry[VTBL_CMPR]);
+ }
+ }
+ TRACE_EXIT;
+}
+
+/* extract the volume table from buffer. "buffer" must already contain
+ * the vtbl-segment
+ */
+int zft_extract_volume_headers(__u8 *buffer)
+{
+ __u8 *entry;
+ TRACE_FUN(ft_t_flow);
+
+ zft_init_vtbl();
+ entry = buffer;
+#ifdef ZFT_CMAP_HACK
+ if ((strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+ strlen(ZFTAPE_SIG)) == 0) &&
+ entry[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) {
+ TRACE(ft_t_noise, "ignoring cmap volume");
+ entry += VTBL_SIZE;
+ }
+#endif
+ /* the end of the vtbl is indicated by an invalid signature
+ */
+ while (vtbl_signature_valid(&entry[VTBL_SIG]) &&
+ (entry - buffer) < FT_SEGMENT_SIZE) {
+ zft_new_vtbl_entry();
+ if (ft_format_code == fmt_big) {
+ /* SCSI like vtbl, stores only the number of
+ * segments used
+ */
+ unsigned int num_segments= GET4(entry, VTBL_SCSI_SEGS);
+ zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+ zft_last_vtbl->end_seg =
+ zft_last_vtbl->start_seg + num_segments - 1;
+ } else {
+ /* `normal', QIC-80 like vtbl
+ */
+ zft_last_vtbl->start_seg = GET2(entry, VTBL_START);
+ zft_last_vtbl->end_seg = GET2(entry, VTBL_END);
+ }
+ zft_eom_vtbl->start_seg = zft_last_vtbl->end_seg + 1;
+ /* check if we created this volume and get the
+ * blk_sz
+ */
+ zft_last_vtbl->zft_volume = check_volume(entry, zft_last_vtbl);
+ if (zft_last_vtbl->zft_volume == 0) {
+ extract_alien_volume(entry, zft_last_vtbl);
+ } else {
+ extract_zft_volume(entry, zft_last_vtbl);
+ }
+ DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], zft_last_vtbl);
+ entry +=VTBL_SIZE;
+ }
+#if 0
+/*
+ * undefine to test end of tape handling
+ */
+ zft_new_vtbl_entry();
+ zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+ zft_last_vtbl->end_seg = ft_last_data_segment - 10;
+ zft_last_vtbl->blk_sz = zft_blk_sz;
+ zft_last_vtbl->zft_volume = 1;
+ zft_last_vtbl->qic113 = zft_qic113;
+ zft_last_vtbl->size = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1)
+ - zft_calc_tape_pos(zft_last_vtbl->start_seg));
+#endif
+ TRACE_EXIT 0;
+}
+
+/* this functions translates the failed_sector_log, misused as
+ * EOF-marker list, into a virtual volume table. The table mustn't be
+ * written to tape, because this would occupy the first data segment,
+ * which should be the volume table, but is actually the first segment
+ * that is filled with data (when using standard ftape). We assume,
+ * that we get a non-empty failed_sector_log.
+ */
+int zft_fake_volume_headers (eof_mark_union *eof_map, int num_failed_sectors)
+{
+ unsigned int segment, sector;
+ int have_eom = 0;
+ int vol_no;
+ TRACE_FUN(ft_t_flow);
+
+ if ((num_failed_sectors >= 2) &&
+ (GET2(&eof_map[num_failed_sectors - 1].mark.segment, 0)
+ ==
+ GET2(&eof_map[num_failed_sectors - 2].mark.segment, 0) + 1) &&
+ (GET2(&eof_map[num_failed_sectors - 1].mark.date, 0) == 1)) {
+ /* this should be eom. We keep the remainder of the
+ * tape as another volume.
+ */
+ have_eom = 1;
+ }
+ zft_init_vtbl();
+ zft_eom_vtbl->start_seg = ft_first_data_segment;
+ for(vol_no = 0; vol_no < num_failed_sectors - have_eom; vol_no ++) {
+ zft_new_vtbl_entry();
+
+ segment = GET2(&eof_map[vol_no].mark.segment, 0);
+ sector = GET2(&eof_map[vol_no].mark.date, 0);
+
+ zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+ zft_last_vtbl->end_seg = segment;
+ zft_eom_vtbl->start_seg = segment + 1;
+ zft_last_vtbl->blk_sz = 1;
+ zft_last_vtbl->size =
+ (zft_calc_tape_pos(zft_last_vtbl->end_seg)
+ - zft_calc_tape_pos(zft_last_vtbl->start_seg)
+ + (sector-1) * FT_SECTOR_SIZE);
+ TRACE(ft_t_noise,
+ "failed sector log: segment: %d, sector: %d",
+ segment, sector);
+ DUMP_VOLINFO(ft_t_noise, "Faked volume", zft_last_vtbl);
+ }
+ if (!have_eom) {
+ zft_new_vtbl_entry();
+ zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+ zft_last_vtbl->end_seg = ft_last_data_segment;
+ zft_eom_vtbl->start_seg = ft_last_data_segment + 1;
+ zft_last_vtbl->size = zft_capacity;
+ zft_last_vtbl->size -= zft_calc_tape_pos(zft_last_vtbl->start_seg);
+ zft_last_vtbl->blk_sz = 1;
+ DUMP_VOLINFO(ft_t_noise, "Faked volume",zft_last_vtbl);
+ }
+ TRACE_EXIT 0;
+}
+
+/* update the internal volume table
+ *
+ * if before start of last volume: erase all following volumes if
+ * inside a volume: set end of volume to infinity
+ *
+ * this function is intended to be called every time _ftape_write() is
+ * called
+ *
+ * return: 0 if no new volume was created, 1 if a new volume was
+ * created
+ *
+ * NOTE: we don't need to check for zft_mode as ftape_write() does
+ * that already. This function gets never called without accessing
+ * zftape via the *qft* devices
+ */
+
+int zft_open_volume(zft_position *pos, int blk_sz, int use_compression)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (!zft_qic_mode) {
+ TRACE_EXIT 0;
+ }
+ if (zft_tape_at_lbot(pos)) {
+ zft_init_vtbl();
+ if(zft_old_ftape) {
+ /* clear old ftape's eof marks */
+ zft_clear_ftape_file_marks();
+ zft_old_ftape = 0; /* no longer old ftape */
+ }
+ zft_reset_position(pos);
+ }
+ if (pos->seg_pos != zft_last_vtbl->end_seg + 1) {
+ TRACE_ABORT(-EIO, ft_t_bug,
+ "BUG: seg_pos: %d, zft_last_vtbl->end_seg: %d",
+ pos->seg_pos, zft_last_vtbl->end_seg);
+ }
+ TRACE(ft_t_noise, "create new volume");
+ if (zft_eom_vtbl->count >= ZFT_MAX_VOLUMES) {
+ TRACE_ABORT(-ENOSPC, ft_t_err,
+ "Error: maxmimal number of volumes exhausted "
+ "(maxmimum is %d)", ZFT_MAX_VOLUMES);
+ }
+ zft_new_vtbl_entry();
+ pos->volume_pos = pos->seg_byte_pos = 0;
+ zft_last_vtbl->start_seg = pos->seg_pos;
+ zft_last_vtbl->end_seg = ft_last_data_segment; /* infinity */
+ zft_last_vtbl->blk_sz = blk_sz;
+ zft_last_vtbl->size = zft_capacity;
+ zft_last_vtbl->zft_volume = 1;
+ zft_last_vtbl->use_compression = use_compression;
+ zft_last_vtbl->qic113 = zft_qic113;
+ zft_last_vtbl->new_volume = 1;
+ zft_last_vtbl->open = 1;
+ zft_volume_table_changed = 1;
+ zft_eom_vtbl->start_seg = ft_last_data_segment + 1;
+ TRACE_EXIT 0;
+}
+
+/* perform mtfsf, mtbsf, not allowed without zft_qic_mode
+ */
+int zft_skip_volumes(int count, zft_position *pos)
+{
+ const zft_volinfo *vtbl;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "count: %d", count);
+
+ vtbl= zft_find_volume(pos->seg_pos);
+ while (count > 0 && vtbl != zft_eom_vtbl) {
+ vtbl = list_entry(vtbl->node.next, zft_volinfo, node);
+ count --;
+ }
+ while (count < 0 && vtbl != zft_first_vtbl) {
+ vtbl = list_entry(vtbl->node.prev, zft_volinfo, node);
+ count ++;
+ }
+ pos->seg_pos = vtbl->start_seg;
+ pos->seg_byte_pos = 0;
+ pos->volume_pos = 0;
+ pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+ zft_just_before_eof = vtbl->size == 0;
+ if (zft_cmpr_ops) {
+ (*zft_cmpr_ops->reset)();
+ }
+ zft_deblock_segment = -1; /* no need to keep cache */
+ TRACE(ft_t_noise, "repositioning to:\n"
+ KERN_INFO "zft_seg_pos : %d\n"
+ KERN_INFO "zft_seg_byte_pos : %d\n"
+ KERN_INFO "zft_tape_pos : " LL_X "\n"
+ KERN_INFO "zft_volume_pos : " LL_X "\n"
+ KERN_INFO "file number : %d",
+ pos->seg_pos, pos->seg_byte_pos,
+ LL(pos->tape_pos), LL(pos->volume_pos), vtbl->count);
+ zft_resid = count < 0 ? -count : count;
+ TRACE_EXIT zft_resid ? -EINVAL : 0;
+}
+
+/* the following simply returns the raw data position of the EOM
+ * marker, MTIOCSIZE ioctl
+ */
+__s64 zft_get_eom_pos(void)
+{
+ if (zft_qic_mode) {
+ return zft_calc_tape_pos(zft_eom_vtbl->start_seg);
+ } else {
+ /* there is only one volume in raw mode */
+ return zft_capacity;
+ }
+}
+
+/* skip to eom, used for MTEOM
+ */
+void zft_skip_to_eom(zft_position *pos)
+{
+ TRACE_FUN(ft_t_flow);
+ pos->seg_pos = zft_eom_vtbl->start_seg;
+ pos->seg_byte_pos =
+ pos->volume_pos =
+ zft_just_before_eof = 0;
+ pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+ TRACE(ft_t_noise, "ftape positioned to segment %d, data pos " LL_X,
+ pos->seg_pos, LL(pos->tape_pos));
+ TRACE_EXIT;
+}
+
+/* write an EOF-marker by setting zft_last_vtbl->end_seg to seg_pos.
+ * NOTE: this function assumes that zft_last_vtbl points to a valid
+ * vtbl entry
+ *
+ * NOTE: this routine always positions before the EOF marker
+ */
+int zft_close_volume(zft_position *pos)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (zft_vtbl_empty || !zft_last_vtbl->open) { /* should not happen */
+ TRACE(ft_t_noise, "There are no volumes to finish");
+ TRACE_EXIT -EIO;
+ }
+ if (pos->seg_byte_pos == 0 &&
+ pos->seg_pos != zft_last_vtbl->start_seg) {
+ pos->seg_pos --;
+ pos->seg_byte_pos = zft_get_seg_sz(pos->seg_pos);
+ }
+ zft_last_vtbl->end_seg = pos->seg_pos;
+ zft_last_vtbl->size = pos->volume_pos;
+ zft_volume_table_changed = 1;
+ zft_just_before_eof = 1;
+ zft_eom_vtbl->start_seg = zft_last_vtbl->end_seg + 1;
+ zft_last_vtbl->open = 0; /* closed */
+ TRACE_EXIT 0;
+}
+
+/* write count file-marks at current position.
+ *
+ * The tape is positioned after the eof-marker, that is at byte 0 of
+ * the segment following the eof-marker
+ *
+ * this function is only allowed in zft_qic_mode
+ *
+ * Only allowed when tape is at BOT or EOD.
+ */
+int zft_weof(unsigned int count, zft_position *pos)
+{
+
+ TRACE_FUN(ft_t_flow);
+
+ if (!count) { /* write zero EOF marks should be a real no-op */
+ TRACE_EXIT 0;
+ }
+ zft_volume_table_changed = 1;
+ if (zft_tape_at_lbot(pos)) {
+ zft_init_vtbl();
+ if(zft_old_ftape) {
+ /* clear old ftape's eof marks */
+ zft_clear_ftape_file_marks();
+ zft_old_ftape = 0; /* no longer old ftape */
+ }
+ }
+ if (zft_last_vtbl->open) {
+ zft_close_volume(pos);
+ zft_move_past_eof(pos);
+ count --;
+ }
+ /* now it's easy, just append eof-marks, that is empty
+ * volumes, to the end of the already recorded media.
+ */
+ while (count > 0 &&
+ pos->seg_pos <= ft_last_data_segment &&
+ zft_eom_vtbl->count < ZFT_MAX_VOLUMES) {
+ TRACE(ft_t_noise,
+ "Writing zero sized file at segment %d", pos->seg_pos);
+ zft_new_vtbl_entry();
+ zft_last_vtbl->start_seg = pos->seg_pos;
+ zft_last_vtbl->end_seg = pos->seg_pos;
+ zft_last_vtbl->size = 0;
+ zft_last_vtbl->blk_sz = zft_blk_sz;
+ zft_last_vtbl->zft_volume = 1;
+ zft_last_vtbl->use_compression = 0;
+ pos->tape_pos += zft_get_seg_sz(pos->seg_pos);
+ zft_eom_vtbl->start_seg = ++ pos->seg_pos;
+ count --;
+ }
+ if (count > 0) {
+ /* there are two possibilities: end of tape, or the
+ * maximum number of files is exhausted.
+ */
+ zft_resid = count;
+ TRACE(ft_t_noise,"Number of marks NOT written: %d", zft_resid);
+ if (zft_eom_vtbl->count == ZFT_MAX_VOLUMES) {
+ TRACE_ABORT(-EINVAL, ft_t_warn,
+ "maximum allowed number of files "
+ "exhausted: %d", ZFT_MAX_VOLUMES);
+ } else {
+ TRACE_ABORT(-ENOSPC,
+ ft_t_noise, "reached end of tape");
+ }
+ }
+ TRACE_EXIT 0;
+}
+
+const zft_volinfo *zft_find_volume(unsigned int seg_pos)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_any, "called with seg_pos %d",seg_pos);
+ if (!zft_qic_mode) {
+ if (seg_pos > ft_last_data_segment) {
+ TRACE_EXIT &eot_vtbl;
+ }
+ tape_vtbl.blk_sz = zft_blk_sz;
+ TRACE_EXIT &tape_vtbl;
+ }
+ if (seg_pos < zft_first_vtbl->start_seg) {
+ TRACE_EXIT (cur_vtbl = zft_first_vtbl);
+ }
+ while (seg_pos > cur_vtbl->end_seg) {
+ cur_vtbl = list_entry(cur_vtbl->node.next, zft_volinfo, node);
+ TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg);
+ }
+ while (seg_pos < cur_vtbl->start_seg) {
+ cur_vtbl = list_entry(cur_vtbl->node.prev, zft_volinfo, node);
+ TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg);
+ }
+ if (seg_pos > cur_vtbl->end_seg || seg_pos < cur_vtbl->start_seg) {
+ TRACE(ft_t_bug, "This cannot happen");
+ }
+ DUMP_VOLINFO(ft_t_noise, "", cur_vtbl);
+ TRACE_EXIT cur_vtbl;
+}
+
+/* this function really assumes that we are just before eof
+ */
+void zft_move_past_eof(zft_position *pos)
+{
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_noise, "old seg. pos: %d", pos->seg_pos);
+ pos->tape_pos += zft_get_seg_sz(pos->seg_pos++) - pos->seg_byte_pos;
+ pos->seg_byte_pos = 0;
+ pos->volume_pos = 0;
+ if (zft_cmpr_ops) {
+ (*zft_cmpr_ops->reset)();
+ }
+ zft_just_before_eof = 0;
+ zft_deblock_segment = -1; /* no need to cache it anymore */
+ TRACE(ft_t_noise, "new seg. pos: %d", pos->seg_pos);
+ TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-vtbl.h b/drivers/char/ftape/zftape/zftape-vtbl.h
new file mode 100644
index 000000000000..f31d196d1759
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-vtbl.h
@@ -0,0 +1,227 @@
+#ifndef _ZFTAPE_VTBL_H
+#define _ZFTAPE_VTBL_H
+
+/*
+ * Copyright (c) 1995-1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.h,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/28 14:30:09 $
+ *
+ * This file defines a volume table as defined in the QIC-80
+ * development standards.
+ */
+
+#include <linux/list.h>
+
+#include "../lowlevel/ftape-tracing.h"
+
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-rw.h"
+
+#define VTBL_SIZE 128 /* bytes */
+
+/* The following are offsets in the vtbl. */
+#define VTBL_SIG 0
+#define VTBL_START 4
+#define VTBL_END 6
+#define VTBL_DESC 8
+#define VTBL_DATE 52
+#define VTBL_FLAGS 56
+#define VTBL_FL_VENDOR_SPECIFIC (1<<0)
+#define VTBL_FL_MUTLI_CARTRIDGE (1<<1)
+#define VTBL_FL_NOT_VERIFIED (1<<2)
+#define VTBL_FL_REDIR_INHIBIT (1<<3)
+#define VTBL_FL_SEG_SPANNING (1<<4)
+#define VTBL_FL_DIRECTORY_LAST (1<<5)
+#define VTBL_FL_RESERVED_6 (1<<6)
+#define VTBL_FL_RESERVED_7 (1<<7)
+#define VTBL_M_NO 57
+#define VTBL_EXT 58
+#define EXT_ZFTAPE_SIG 0
+#define EXT_ZFTAPE_BLKSZ 10
+#define EXT_ZFTAPE_CMAP 12
+#define EXT_ZFTAPE_QIC113 13
+#define VTBL_PWD 84
+#define VTBL_DIR_SIZE 92
+#define VTBL_DATA_SIZE 96
+#define VTBL_OS_VERSION 104
+#define VTBL_SRC_DRIVE 106
+#define VTBL_DEV 122
+#define VTBL_RESERVED_1 123
+#define VTBL_CMPR 124
+#define VTBL_CMPR_UNREG 0x3f
+#define VTBL_CMPR_USED 0x80
+#define VTBL_FMT 125
+#define VTBL_RESERVED_2 126
+#define VTBL_RESERVED_3 127
+/* compatibility with pre revision K */
+#define VTBL_K_CMPR 120
+
+/* the next is used by QIC-3020 tapes with format code 6 (>2^16
+ * segments) It is specified in QIC-113, Rev. G, Section 5 (SCSI
+ * volume table). The difference is simply, that we only store the
+ * number of segments used, not the starting segment.
+ */
+#define VTBL_SCSI_SEGS 4 /* is a 4 byte value */
+
+/* one vtbl is 128 bytes, that results in a maximum number of
+ * 29*1024/128 = 232 volumes.
+ */
+#define ZFT_MAX_VOLUMES (FT_SEGMENT_SIZE/VTBL_SIZE)
+#define VTBL_ID "VTBL"
+#define VTBL_IDS { VTBL_ID, "XTBL", "UTID", "EXVT" } /* other valid ids */
+#define ZFT_VOL_NAME "zftape volume" /* volume label used by me */
+#define ZFTAPE_SIG "LINUX ZFT"
+
+/* global variables
+ */
+typedef struct zft_internal_vtbl
+{
+ struct list_head node;
+ int count;
+ unsigned int start_seg; /* 32 bits are enough for now */
+ unsigned int end_seg; /* 32 bits are enough for now */
+ __s64 size; /* uncompressed size */
+ unsigned int blk_sz; /* block size for this volume */
+ unsigned int zft_volume :1; /* zftape created this volume */
+ unsigned int use_compression:1; /* compressed volume */
+ unsigned int qic113 :1; /* layout of compressed block
+ * info and vtbl conforms to
+ * QIC-113, Rev. G
+ */
+ unsigned int new_volume :1; /* it was created by us, this
+ * run. this allows the
+ * fields that aren't really
+ * used by zftape to be filled
+ * in by some user level
+ * program.
+ */
+ unsigned int open :1; /* just in progress of being
+ * written
+ */
+} zft_volinfo;
+
+extern struct list_head zft_vtbl;
+#define zft_head_vtbl list_entry(zft_vtbl.next, zft_volinfo, node)
+#define zft_eom_vtbl list_entry(zft_vtbl.prev, zft_volinfo, node)
+#define zft_last_vtbl list_entry(zft_eom_vtbl->node.prev, zft_volinfo, node)
+#define zft_first_vtbl list_entry(zft_head_vtbl->node.next, zft_volinfo, node)
+#define zft_vtbl_empty (zft_eom_vtbl->node.prev == &zft_head_vtbl->node)
+
+#define DUMP_VOLINFO(level, desc, info) \
+{ \
+ char tmp[21]; \
+ strlcpy(tmp, desc, sizeof(tmp)); \
+ TRACE(level, "Volume %d:\n" \
+ KERN_INFO "description : %s\n" \
+ KERN_INFO "first segment: %d\n" \
+ KERN_INFO "last segment: %d\n" \
+ KERN_INFO "size : " LL_X "\n" \
+ KERN_INFO "block size : %d\n" \
+ KERN_INFO "compression : %d\n" \
+ KERN_INFO "zftape volume: %d\n" \
+ KERN_INFO "QIC-113 conf.: %d", \
+ (info)->count, tmp, (info)->start_seg, (info)->end_seg, \
+ LL((info)->size), (info)->blk_sz, \
+ (info)->use_compression != 0, (info)->zft_volume != 0, \
+ (info)->qic113 != 0); \
+}
+
+extern int zft_qic_mode;
+extern int zft_old_ftape;
+extern int zft_volume_table_changed;
+
+/* exported functions */
+extern void zft_init_vtbl (void);
+extern void zft_free_vtbl (void);
+extern int zft_extract_volume_headers(__u8 *buffer);
+extern int zft_update_volume_table (unsigned int segment);
+extern int zft_open_volume (zft_position *pos,
+ int blk_sz, int use_compression);
+extern int zft_close_volume (zft_position *pos);
+extern const zft_volinfo *zft_find_volume(unsigned int seg_pos);
+extern int zft_skip_volumes (int count, zft_position *pos);
+extern __s64 zft_get_eom_pos (void);
+extern void zft_skip_to_eom (zft_position *pos);
+extern int zft_fake_volume_headers (eof_mark_union *eof_map,
+ int num_failed_sectors);
+extern int zft_weof (unsigned int count, zft_position *pos);
+extern void zft_move_past_eof (zft_position *pos);
+
+static inline int zft_tape_at_eod (const zft_position *pos);
+static inline int zft_tape_at_lbot (const zft_position *pos);
+static inline void zft_position_before_eof (zft_position *pos,
+ const zft_volinfo *volume);
+static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl,
+ const zft_position *pos);
+
+/* this function decrements the zft_seg_pos counter if we are right
+ * at the beginning of a segment. This is to handle fsfm/bsfm -- we
+ * need to position before the eof mark. NOTE: zft_tape_pos is not
+ * changed
+ */
+static inline void zft_position_before_eof(zft_position *pos,
+ const zft_volinfo *volume)
+{
+ TRACE_FUN(ft_t_flow);
+
+ if (pos->seg_pos == volume->end_seg + 1 && pos->seg_byte_pos == 0) {
+ pos->seg_pos --;
+ pos->seg_byte_pos = zft_get_seg_sz(pos->seg_pos);
+ }
+ TRACE_EXIT;
+}
+
+/* Mmmh. Is the position at the end of the last volume, that is right
+ * before the last EOF mark also logical an EOD condition?
+ */
+static inline int zft_tape_at_eod(const zft_position *pos)
+{
+ TRACE_FUN(ft_t_any);
+
+ if (zft_qic_mode) {
+ TRACE_EXIT (pos->seg_pos >= zft_eom_vtbl->start_seg ||
+ zft_last_vtbl->open);
+ } else {
+ TRACE_EXIT pos->seg_pos > ft_last_data_segment;
+ }
+}
+
+static inline int zft_tape_at_lbot(const zft_position *pos)
+{
+ if (zft_qic_mode) {
+ return (pos->seg_pos <= zft_first_vtbl->start_seg &&
+ pos->volume_pos == 0);
+ } else {
+ return (pos->seg_pos <= ft_first_data_segment &&
+ pos->volume_pos == 0);
+ }
+}
+
+/* This one checks for EOF. return remaing space (may be negative)
+ */
+static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl,
+ const zft_position *pos)
+{
+ return (__s64)(vtbl->size - pos->volume_pos);
+}
+
+#endif /* _ZFTAPE_VTBL_H */
diff --git a/drivers/char/ftape/zftape/zftape-write.c b/drivers/char/ftape/zftape/zftape-write.c
new file mode 100644
index 000000000000..94327b8c97b9
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-write.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 1996, 1997 Claus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/11/06 00:50:29 $
+ *
+ * This file contains the writing code
+ * for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/* Global vars.
+ */
+
+/* Local vars.
+ */
+static int last_write_failed;
+static int need_flush;
+
+void zft_prevent_flush(void)
+{
+ need_flush = 0;
+}
+
+static int zft_write_header_segments(__u8* buffer)
+{
+ int header_1_ok = 0;
+ int header_2_ok = 0;
+ unsigned int time_stamp;
+ TRACE_FUN(ft_t_noise);
+
+ TRACE_CATCH(ftape_abort_operation(),);
+ ftape_seek_to_bot(); /* prevents extra rewind */
+ if (GET4(buffer, 0) != FT_HSEG_MAGIC) {
+ TRACE_ABORT(-EIO, ft_t_err,
+ "wrong header signature found, aborting");
+ }
+ /* Be optimistic: */
+ PUT4(buffer, FT_SEG_CNT,
+ zft_written_segments + GET4(buffer, FT_SEG_CNT) + 2);
+ if ((time_stamp = zft_get_time()) != 0) {
+ PUT4(buffer, FT_WR_DATE, time_stamp);
+ if (zft_label_changed) {
+ PUT4(buffer, FT_LABEL_DATE, time_stamp);
+ }
+ }
+ TRACE(ft_t_noise,
+ "writing first header segment %d", ft_header_segment_1);
+ header_1_ok = zft_verify_write_segments(ft_header_segment_1,
+ buffer, FT_SEGMENT_SIZE,
+ zft_deblock_buf) >= 0;
+ TRACE(ft_t_noise,
+ "writing second header segment %d", ft_header_segment_2);
+ header_2_ok = zft_verify_write_segments(ft_header_segment_2,
+ buffer, FT_SEGMENT_SIZE,
+ zft_deblock_buf) >= 0;
+ if (!header_1_ok) {
+ TRACE(ft_t_warn, "Warning: "
+ "update of first header segment failed");
+ }
+ if (!header_2_ok) {
+ TRACE(ft_t_warn, "Warning: "
+ "update of second header segment failed");
+ }
+ if (!header_1_ok && !header_2_ok) {
+ TRACE_ABORT(-EIO, ft_t_err, "Error: "
+ "update of both header segments failed.");
+ }
+ TRACE_EXIT 0;
+}
+
+int zft_update_header_segments(void)
+{
+ TRACE_FUN(ft_t_noise);
+
+ /* must NOT use zft_write_protected, as it also includes the
+ * file access mode. But we also want to update when soft
+ * write protection is enabled (O_RDONLY)
+ */
+ if (ft_write_protected || zft_old_ftape) {
+ TRACE_ABORT(0, ft_t_noise, "Tape set read-only: no update");
+ }
+ if (!zft_header_read) {
+ TRACE_ABORT(0, ft_t_noise, "Nothing to update");
+ }
+ if (!zft_header_changed) {
+ zft_header_changed = zft_written_segments > 0;
+ }
+ if (!zft_header_changed && !zft_volume_table_changed) {
+ TRACE_ABORT(0, ft_t_noise, "Nothing to update");
+ }
+ TRACE(ft_t_noise, "Updating header segments");
+ if (ftape_get_status()->fti_state == writing) {
+ TRACE_CATCH(ftape_loop_until_writes_done(),);
+ }
+ TRACE_CATCH(ftape_abort_operation(),);
+
+ zft_deblock_segment = -1; /* invalidate the cache */
+ if (zft_header_changed) {
+ TRACE_CATCH(zft_write_header_segments(zft_hseg_buf),);
+ }
+ if (zft_volume_table_changed) {
+ TRACE_CATCH(zft_update_volume_table(ft_first_data_segment),);
+ }
+ zft_header_changed =
+ zft_volume_table_changed =
+ zft_label_changed =
+ zft_written_segments = 0;
+ TRACE_CATCH(ftape_abort_operation(),);
+ ftape_seek_to_bot();
+ TRACE_EXIT 0;
+}
+
+static int read_merge_buffer(int seg_pos, __u8 *buffer, int offset, int seg_sz)
+{
+ int result = 0;
+ const ft_trace_t old_tracing = TRACE_LEVEL;
+ TRACE_FUN(ft_t_flow);
+
+ if (zft_qic_mode) {
+ /* writing in the middle of a volume is NOT allowed
+ *
+ */
+ TRACE(ft_t_noise, "No need to read a segment");
+ memset(buffer + offset, 0, seg_sz - offset);
+ TRACE_EXIT 0;
+ }
+ TRACE(ft_t_any, "waiting");
+ ftape_start_writing(FT_WR_MULTI);
+ TRACE_CATCH(ftape_loop_until_writes_done(),);
+
+ TRACE(ft_t_noise, "trying to read segment %d from offset %d",
+ seg_pos, offset);
+ SET_TRACE_LEVEL(ft_t_bug);
+ result = zft_fetch_segment_fraction(seg_pos, buffer,
+ FT_RD_SINGLE,
+ offset, seg_sz - offset);
+ SET_TRACE_LEVEL(old_tracing);
+ if (result != (seg_sz - offset)) {
+ TRACE(ft_t_noise, "Ignore error: read_segment() result: %d",
+ result);
+ memset(buffer + offset, 0, seg_sz - offset);
+ }
+ TRACE_EXIT 0;
+}
+
+/* flush the write buffer to tape and write an eof-marker at the
+ * current position if not in raw mode. This function always
+ * positions the tape before the eof-marker. _ftape_close() should
+ * then advance to the next segment.
+ *
+ * the parameter "finish_volume" describes whether to position before
+ * or after the possibly created file-mark. We always position after
+ * the file-mark when called from ftape_close() and a flush was needed
+ * (that is ftape_write() was the last tape operation before calling
+ * ftape_flush) But we always position before the file-mark when this
+ * function get's called from outside ftape_close()
+ */
+int zft_flush_buffers(void)
+{
+ int result;
+ int data_remaining;
+ int this_segs_size;
+ TRACE_FUN(ft_t_flow);
+
+ TRACE(ft_t_data_flow,
+ "entered, ftape_state = %d", ftape_get_status()->fti_state);
+ if (ftape_get_status()->fti_state != writing && !need_flush) {
+ TRACE_ABORT(0, ft_t_noise, "no need for flush");
+ }
+ zft_io_state = zft_idle; /* triggers some initializations for the
+ * read and write routines
+ */
+ if (last_write_failed) {
+ ftape_abort_operation();
+ TRACE_EXIT -EIO;
+ }
+ TRACE(ft_t_noise, "flushing write buffers");
+ this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
+ if (this_segs_size == zft_pos.seg_byte_pos) {
+ zft_pos.seg_pos ++;
+ data_remaining = zft_pos.seg_byte_pos = 0;
+ } else {
+ data_remaining = zft_pos.seg_byte_pos;
+ }
+ /* If there is any data not written to tape yet, append zero's
+ * up to the end of the sector (if using compression) or merge
+ * it with the data existing on the tape Then write the
+ * segment(s) to tape.
+ */
+ TRACE(ft_t_noise, "Position:\n"
+ KERN_INFO "seg_pos : %d\n"
+ KERN_INFO "byte pos : %d\n"
+ KERN_INFO "remaining: %d",
+ zft_pos.seg_pos, zft_pos.seg_byte_pos, data_remaining);
+ if (data_remaining > 0) {
+ do {
+ this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
+ if (this_segs_size > data_remaining) {
+ TRACE_CATCH(read_merge_buffer(zft_pos.seg_pos,
+ zft_deblock_buf,
+ data_remaining,
+ this_segs_size),
+ last_write_failed = 1);
+ }
+ result = ftape_write_segment(zft_pos.seg_pos,
+ zft_deblock_buf,
+ FT_WR_MULTI);
+ if (result != this_segs_size) {
+ TRACE(ft_t_err, "flush buffers failed");
+ zft_pos.tape_pos -= zft_pos.seg_byte_pos;
+ zft_pos.seg_byte_pos = 0;
+
+ last_write_failed = 1;
+ TRACE_EXIT result;
+ }
+ zft_written_segments ++;
+ TRACE(ft_t_data_flow,
+ "flush, moved out buffer: %d", result);
+ /* need next segment for more data (empty segments?)
+ */
+ if (result < data_remaining) {
+ if (result > 0) {
+ /* move remainder to buffer beginning
+ */
+ memmove(zft_deblock_buf,
+ zft_deblock_buf + result,
+ FT_SEGMENT_SIZE - result);
+ }
+ }
+ data_remaining -= result;
+ zft_pos.seg_pos ++;
+ } while (data_remaining > 0);
+ TRACE(ft_t_any, "result: %d", result);
+ zft_deblock_segment = --zft_pos.seg_pos;
+ if (data_remaining == 0) { /* first byte next segment */
+ zft_pos.seg_byte_pos = this_segs_size;
+ } else { /* after data previous segment, data_remaining < 0 */
+ zft_pos.seg_byte_pos = data_remaining + result;
+ }
+ } else {
+ TRACE(ft_t_noise, "zft_deblock_buf empty");
+ zft_pos.seg_pos --;
+ zft_pos.seg_byte_pos = zft_get_seg_sz (zft_pos.seg_pos);
+ ftape_start_writing(FT_WR_MULTI);
+ }
+ TRACE(ft_t_any, "waiting");
+ if ((result = ftape_loop_until_writes_done()) < 0) {
+ /* that's really bad. What to to with zft_tape_pos?
+ */
+ TRACE(ft_t_err, "flush buffers failed");
+ }
+ TRACE(ft_t_any, "zft_seg_pos: %d, zft_seg_byte_pos: %d",
+ zft_pos.seg_pos, zft_pos.seg_byte_pos);
+ last_write_failed =
+ need_flush = 0;
+ TRACE_EXIT result;
+}
+
+/* return-value: the number of bytes removed from the user-buffer
+ *
+ * out:
+ * int *write_cnt: how much actually has been moved to the
+ * zft_deblock_buf
+ * int req_len : MUST NOT BE CHANGED, except at EOT, in
+ * which case it may be adjusted
+ * in :
+ * char *buff : the user buffer
+ * int buf_pos_write : copy of buf_len_wr int
+ * this_segs_size : the size in bytes of the actual segment
+ * char
+ * *zft_deblock_buf : zft_deblock_buf
+ */
+static int zft_simple_write(int *cnt,
+ __u8 *dst_buf, const int seg_sz,
+ const __u8 __user *src_buf, const int req_len,
+ const zft_position *pos,const zft_volinfo *volume)
+{
+ int space_left;
+ TRACE_FUN(ft_t_flow);
+
+ /* volume->size holds the tape capacity while volume is open */
+ if (pos->tape_pos + volume->blk_sz > volume->size) {
+ TRACE_EXIT -ENOSPC;
+ }
+ /* remaining space in this segment, NOT zft_deblock_buf
+ */
+ space_left = seg_sz - pos->seg_byte_pos;
+ *cnt = req_len < space_left ? req_len : space_left;
+ if (copy_from_user(dst_buf + pos->seg_byte_pos, src_buf, *cnt) != 0) {
+ TRACE_EXIT -EFAULT;
+ }
+ TRACE_EXIT *cnt;
+}
+
+static int check_write_access(int req_len,
+ const zft_volinfo **volume,
+ zft_position *pos,
+ const unsigned int blk_sz)
+{
+ int result;
+ TRACE_FUN(ft_t_flow);
+
+ if ((req_len % zft_blk_sz) != 0) {
+ TRACE_ABORT(-EINVAL, ft_t_info,
+ "write-count %d must be multiple of block-size %d",
+ req_len, blk_sz);
+ }
+ if (zft_io_state == zft_writing) {
+ /* all other error conditions have been checked earlier
+ */
+ TRACE_EXIT 0;
+ }
+ zft_io_state = zft_idle;
+ TRACE_CATCH(zft_check_write_access(pos),);
+ /* If we haven't read the header segment yet, do it now.
+ * This will verify the configuration, get the bad sector
+ * table and read the volume table segment
+ */
+ if (!zft_header_read) {
+ TRACE_CATCH(zft_read_header_segments(),);
+ }
+ /* fine. Now the tape is either at BOT or at EOD,
+ * Write start of volume now
+ */
+ TRACE_CATCH(zft_open_volume(pos, blk_sz, zft_use_compression),);
+ *volume = zft_find_volume(pos->seg_pos);
+ DUMP_VOLINFO(ft_t_noise, "", *volume);
+ zft_just_before_eof = 0;
+ /* now merge with old data if necessary */
+ if (!zft_qic_mode && pos->seg_byte_pos != 0){
+ result = zft_fetch_segment(pos->seg_pos,
+ zft_deblock_buf,
+ FT_RD_SINGLE);
+ if (result < 0) {
+ if (result == -EINTR || result == -ENOSPC) {
+ TRACE_EXIT result;
+ }
+ TRACE(ft_t_noise,
+ "ftape_read_segment() result: %d. "
+ "This might be normal when using "
+ "a newly\nformatted tape", result);
+ memset(zft_deblock_buf, '\0', pos->seg_byte_pos);
+ }
+ }
+ zft_io_state = zft_writing;
+ TRACE_EXIT 0;
+}
+
+static int fill_deblock_buf(__u8 *dst_buf, const int seg_sz,
+ zft_position *pos, const zft_volinfo *volume,
+ const char __user *usr_buf, const int req_len)
+{
+ int cnt = 0;
+ int result = 0;
+ TRACE_FUN(ft_t_flow);
+
+ if (seg_sz == 0) {
+ TRACE_ABORT(0, ft_t_data_flow, "empty segment");
+ }
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "remaining req_len: %d\n"
+ KERN_INFO " buf_pos: %d",
+ req_len, pos->seg_byte_pos);
+ /* zft_deblock_buf will not contain a valid segment any longer */
+ zft_deblock_segment = -1;
+ if (zft_use_compression) {
+ TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+ TRACE_CATCH(result= (*zft_cmpr_ops->write)(&cnt,
+ dst_buf, seg_sz,
+ usr_buf, req_len,
+ pos, volume),);
+ } else {
+ TRACE_CATCH(result= zft_simple_write(&cnt,
+ dst_buf, seg_sz,
+ usr_buf, req_len,
+ pos, volume),);
+ }
+ pos->volume_pos += result;
+ pos->seg_byte_pos += cnt;
+ pos->tape_pos += cnt;
+ TRACE(ft_t_data_flow, "\n"
+ KERN_INFO "removed from user-buffer : %d bytes.\n"
+ KERN_INFO "copied to zft_deblock_buf: %d bytes.\n"
+ KERN_INFO "zft_tape_pos : " LL_X " bytes.",
+ result, cnt, LL(pos->tape_pos));
+ TRACE_EXIT result;
+}
+
+
+/* called by the kernel-interface routine "zft_write()"
+ */
+int _zft_write(const char __user *buff, int req_len)
+{
+ int result = 0;
+ int written = 0;
+ int write_cnt;
+ int seg_sz;
+ static const zft_volinfo *volume = NULL;
+ TRACE_FUN(ft_t_flow);
+
+ zft_resid = req_len;
+ last_write_failed = 1; /* reset to 0 when successful */
+ /* check if write is allowed
+ */
+ TRACE_CATCH(check_write_access(req_len, &volume,&zft_pos,zft_blk_sz),);
+ while (req_len > 0) {
+ /* Allow us to escape from this loop with a signal !
+ */
+ FT_SIGNAL_EXIT(_DONT_BLOCK);
+ seg_sz = zft_get_seg_sz(zft_pos.seg_pos);
+ if ((write_cnt = fill_deblock_buf(zft_deblock_buf,
+ seg_sz,
+ &zft_pos,
+ volume,
+ buff,
+ req_len)) < 0) {
+ zft_resid -= written;
+ if (write_cnt == -ENOSPC) {
+ /* leave the remainder to flush_buffers()
+ */
+ TRACE(ft_t_info, "No space left on device");
+ last_write_failed = 0;
+ if (!need_flush) {
+ need_flush = written > 0;
+ }
+ TRACE_EXIT written > 0 ? written : -ENOSPC;
+ } else {
+ TRACE_EXIT result;
+ }
+ }
+ if (zft_pos.seg_byte_pos == seg_sz) {
+ TRACE_CATCH(ftape_write_segment(zft_pos.seg_pos,
+ zft_deblock_buf,
+ FT_WR_ASYNC),
+ zft_resid -= written);
+ zft_written_segments ++;
+ zft_pos.seg_byte_pos = 0;
+ zft_deblock_segment = zft_pos.seg_pos;
+ ++zft_pos.seg_pos;
+ }
+ written += write_cnt;
+ buff += write_cnt;
+ req_len -= write_cnt;
+ } /* while (req_len > 0) */
+ TRACE(ft_t_data_flow, "remaining in blocking buffer: %d",
+ zft_pos.seg_byte_pos);
+ TRACE(ft_t_data_flow, "just written bytes: %d", written);
+ last_write_failed = 0;
+ zft_resid -= written;
+ need_flush = need_flush || written > 0;
+ TRACE_EXIT written; /* bytes written */
+}
diff --git a/drivers/char/ftape/zftape/zftape-write.h b/drivers/char/ftape/zftape/zftape-write.h
new file mode 100644
index 000000000000..ea887015b493
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-write.h
@@ -0,0 +1,38 @@
+#ifndef _ZFTAPE_WRITE_H
+#define _ZFTAPE_WRITE_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:13 $
+ *
+ * This file contains the definitions for the write functions
+ * for the zftape driver for Linux.
+ *
+ */
+
+extern int zft_flush_buffers(void);
+extern int zft_update_header_segments(void);
+extern void zft_prevent_flush(void);
+
+/* hook for the VFS interface
+ */
+extern int _zft_write(const char __user *buff, int req_len);
+#endif /* _ZFTAPE_WRITE_H */
diff --git a/drivers/char/ftape/zftape/zftape_syms.c b/drivers/char/ftape/zftape/zftape_syms.c
new file mode 100644
index 000000000000..2db1401682df
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape_syms.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 1997 Claus-Justus Heine
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape_syms.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:19:14 $
+ *
+ * This file contains the symbols that the zftape frontend to
+ * the ftape floppy tape driver exports
+ */
+
+#include <linux/module.h>
+
+#include <linux/zftape.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-buffers.h"
+#include "../zftape/zftape-ctl.h"
+
+/* zftape-init.c */
+EXPORT_SYMBOL(zft_cmpr_register);
+/* zftape-read.c */
+EXPORT_SYMBOL(zft_fetch_segment_fraction);
+/* zftape-buffers.c */
+EXPORT_SYMBOL(zft_vmalloc_once);
+EXPORT_SYMBOL(zft_vmalloc_always);
+EXPORT_SYMBOL(zft_vfree);
diff --git a/drivers/char/generic_nvram.c b/drivers/char/generic_nvram.c
new file mode 100644
index 000000000000..1b5e01e6e129
--- /dev/null
+++ b/drivers/char/generic_nvram.c
@@ -0,0 +1,145 @@
+/*
+ * Generic /dev/nvram driver for architectures providing some
+ * "generic" hooks, that is :
+ *
+ * nvram_read_byte, nvram_write_byte, nvram_sync
+ *
+ * Note that an additional hook is supported for PowerMac only
+ * for getting the nvram "partition" informations
+ *
+ */
+
+#define NVRAM_VERSION "1.1"
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include <asm/nvram.h>
+
+#define NVRAM_SIZE 8192
+
+static loff_t nvram_llseek(struct file *file, loff_t offset, int origin)
+{
+ lock_kernel();
+ switch (origin) {
+ case 1:
+ offset += file->f_pos;
+ break;
+ case 2:
+ offset += NVRAM_SIZE;
+ break;
+ }
+ if (offset < 0) {
+ unlock_kernel();
+ return -EINVAL;
+ }
+ file->f_pos = offset;
+ unlock_kernel();
+ return file->f_pos;
+}
+
+static ssize_t read_nvram(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int i;
+ char __user *p = buf;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+ if (*ppos >= NVRAM_SIZE)
+ return 0;
+ for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count)
+ if (__put_user(nvram_read_byte(i), p))
+ return -EFAULT;
+ *ppos = i;
+ return p - buf;
+}
+
+static ssize_t write_nvram(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int i;
+ const char __user *p = buf;
+ char c;
+
+ if (!access_ok(VERIFY_READ, buf, count))
+ return -EFAULT;
+ if (*ppos >= NVRAM_SIZE)
+ return 0;
+ for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) {
+ if (__get_user(c, p))
+ return -EFAULT;
+ nvram_write_byte(c, i);
+ }
+ *ppos = i;
+ return p - buf;
+}
+
+static int nvram_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch(cmd) {
+#ifdef CONFIG_PPC_PMAC
+ case OBSOLETE_PMAC_NVRAM_GET_OFFSET:
+ printk(KERN_WARNING "nvram: Using obsolete PMAC_NVRAM_GET_OFFSET ioctl\n");
+ case IOC_NVRAM_GET_OFFSET: {
+ int part, offset;
+
+ if (_machine != _MACH_Pmac)
+ return -EINVAL;
+ if (copy_from_user(&part, (void __user*)arg, sizeof(part)) != 0)
+ return -EFAULT;
+ if (part < pmac_nvram_OF || part > pmac_nvram_NR)
+ return -EINVAL;
+ offset = pmac_get_partition(part);
+ if (copy_to_user((void __user*)arg, &offset, sizeof(offset)) != 0)
+ return -EFAULT;
+ break;
+ }
+#endif /* CONFIG_PPC_PMAC */
+ case IOC_NVRAM_SYNC:
+ nvram_sync();
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct file_operations nvram_fops = {
+ .owner = THIS_MODULE,
+ .llseek = nvram_llseek,
+ .read = read_nvram,
+ .write = write_nvram,
+ .ioctl = nvram_ioctl,
+};
+
+static struct miscdevice nvram_dev = {
+ NVRAM_MINOR,
+ "nvram",
+ &nvram_fops
+};
+
+int __init nvram_init(void)
+{
+ printk(KERN_INFO "Macintosh non-volatile memory driver v%s\n",
+ NVRAM_VERSION);
+ return misc_register(&nvram_dev);
+}
+
+void __exit nvram_cleanup(void)
+{
+ misc_deregister( &nvram_dev );
+}
+
+module_init(nvram_init);
+module_exit(nvram_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/generic_serial.c b/drivers/char/generic_serial.c
new file mode 100644
index 000000000000..204a7302a4a9
--- /dev/null
+++ b/drivers/char/generic_serial.c
@@ -0,0 +1,1001 @@
+/*
+ * generic_serial.c
+ *
+ * Copyright (C) 1998/1999 R.E.Wolff@BitWizard.nl
+ *
+ * written for the SX serial driver.
+ * Contains the code that should be shared over all the serial drivers.
+ *
+ * Credit for the idea to do it this way might go to Alan Cox.
+ *
+ *
+ * Version 0.1 -- December, 1998. Initial version.
+ * Version 0.2 -- March, 1999. Some more routines. Bugfixes. Etc.
+ * Version 0.5 -- August, 1999. Some more fixes. Reformat for Linus.
+ *
+ * BitWizard is actively maintaining this file. We sometimes find
+ * that someone submitted changes to this file. We really appreciate
+ * your help, but please submit changes through us. We're doing our
+ * best to be responsive. -- REW
+ * */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/mm.h>
+#include <linux/generic_serial.h>
+#include <linux/interrupt.h>
+#include <linux/tty_flip.h>
+#include <linux/delay.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#define DEBUG
+
+static char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static int gs_debug;
+
+#ifdef DEBUG
+#define gs_dprintk(f, str...) if (gs_debug & f) printk (str)
+#else
+#define gs_dprintk(f, str...) /* nothing */
+#endif
+
+#define func_enter() gs_dprintk (GS_DEBUG_FLOW, "gs: enter %s\n", __FUNCTION__)
+#define func_exit() gs_dprintk (GS_DEBUG_FLOW, "gs: exit %s\n", __FUNCTION__)
+#define NEW_WRITE_LOCKING 1
+#if NEW_WRITE_LOCKING
+#define DECL /* Nothing */
+#define LOCKIT down (& port->port_write_sem);
+#define RELEASEIT up (&port->port_write_sem);
+#else
+#define DECL unsigned long flags;
+#define LOCKIT save_flags (flags);cli ()
+#define RELEASEIT restore_flags (flags)
+#endif
+
+#define RS_EVENT_WRITE_WAKEUP 1
+
+module_param(gs_debug, int, 0644);
+
+
+void gs_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct gs_port *port;
+ DECL
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+
+ if (! (port->flags & ASYNC_INITIALIZED)) return;
+
+ /* Take a lock on the serial tranmit buffer! */
+ LOCKIT;
+
+ if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+ /* Sorry, buffer is full, drop character. Update statistics???? -- REW */
+ RELEASEIT;
+ return;
+ }
+
+ port->xmit_buf[port->xmit_head++] = ch;
+ port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ port->xmit_cnt++; /* Characters in buffer */
+
+ RELEASEIT;
+ func_exit ();
+}
+
+
+#ifdef NEW_WRITE_LOCKING
+
+/*
+> Problems to take into account are:
+> -1- Interrupts that empty part of the buffer.
+> -2- page faults on the access to userspace.
+> -3- Other processes that are also trying to do a "write".
+*/
+
+int gs_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct gs_port *port;
+ int c, total = 0;
+ int t;
+
+ func_enter ();
+
+ if (!tty) return 0;
+
+ port = tty->driver_data;
+
+ if (!port) return 0;
+
+ if (! (port->flags & ASYNC_INITIALIZED))
+ return 0;
+
+ /* get exclusive "write" access to this port (problem 3) */
+ /* This is not a spinlock because we can have a disk access (page
+ fault) in copy_from_user */
+ down (& port->port_write_sem);
+
+ while (1) {
+
+ c = count;
+
+ /* This is safe because we "OWN" the "head". Noone else can
+ change the "head": we own the port_write_sem. */
+ /* Don't overrun the end of the buffer */
+ t = SERIAL_XMIT_SIZE - port->xmit_head;
+ if (t < c) c = t;
+
+ /* This is safe because the xmit_cnt can only decrease. This
+ would increase "t", so we might copy too little chars. */
+ /* Don't copy past the "head" of the buffer */
+ t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+ if (t < c) c = t;
+
+ /* Can't copy more? break out! */
+ if (c <= 0) break;
+
+ memcpy (port->xmit_buf + port->xmit_head, buf, c);
+
+ port -> xmit_cnt += c;
+ port -> xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE -1);
+ buf += c;
+ count -= c;
+ total += c;
+ }
+ up (& port->port_write_sem);
+
+ gs_dprintk (GS_DEBUG_WRITE, "write: interrupts are %s\n",
+ (port->flags & GS_TX_INTEN)?"enabled": "disabled");
+
+ if (port->xmit_cnt &&
+ !tty->stopped &&
+ !tty->hw_stopped &&
+ !(port->flags & GS_TX_INTEN)) {
+ port->flags |= GS_TX_INTEN;
+ port->rd->enable_tx_interrupts (port);
+ }
+ func_exit ();
+ return total;
+}
+#else
+/*
+> Problems to take into account are:
+> -1- Interrupts that empty part of the buffer.
+> -2- page faults on the access to userspace.
+> -3- Other processes that are also trying to do a "write".
+*/
+
+int gs_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct gs_port *port;
+ int c, total = 0;
+ int t;
+ unsigned long flags;
+
+ func_enter ();
+
+ /* The standard serial driver returns 0 in this case.
+ That sounds to me as "No error, I just didn't get to writing any
+ bytes. Feel free to try again."
+ The "official" way to write n bytes from buf is:
+
+ for (nwritten = 0;nwritten < n;nwritten += rv) {
+ rv = write (fd, buf+nwritten, n-nwritten);
+ if (rv < 0) break; // Error: bail out. //
+ }
+
+ which will loop endlessly in this case. The manual page for write
+ agrees with me. In practise almost everybody writes
+ "write (fd, buf,n);" but some people might have had to deal with
+ incomplete writes in the past and correctly implemented it by now...
+ */
+
+ if (!tty) return -EIO;
+
+ port = tty->driver_data;
+ if (!port || !port->xmit_buf || !tmp_buf)
+ return -EIO;
+
+ local_save_flags(flags);
+ while (1) {
+ cli();
+ c = count;
+
+ /* This is safe because we "OWN" the "head". Noone else can
+ change the "head": we own the port_write_sem. */
+ /* Don't overrun the end of the buffer */
+ t = SERIAL_XMIT_SIZE - port->xmit_head;
+ if (t < c) c = t;
+
+ /* This is safe because the xmit_cnt can only decrease. This
+ would increase "t", so we might copy too little chars. */
+ /* Don't copy past the "head" of the buffer */
+ t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+ if (t < c) c = t;
+
+ /* Can't copy more? break out! */
+ if (c <= 0) {
+ local_restore_flags(flags);
+ break;
+ }
+ memcpy(port->xmit_buf + port->xmit_head, buf, c);
+ port->xmit_head = ((port->xmit_head + c) &
+ (SERIAL_XMIT_SIZE-1));
+ port->xmit_cnt += c;
+ local_restore_flags(flags);
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ if (port->xmit_cnt &&
+ !tty->stopped &&
+ !tty->hw_stopped &&
+ !(port->flags & GS_TX_INTEN)) {
+ port->flags |= GS_TX_INTEN;
+ port->rd->enable_tx_interrupts (port);
+ }
+ func_exit ();
+ return total;
+}
+
+#endif
+
+
+
+int gs_write_room(struct tty_struct * tty)
+{
+ struct gs_port *port = tty->driver_data;
+ int ret;
+
+ func_enter ();
+ ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ func_exit ();
+ return ret;
+}
+
+
+int gs_chars_in_buffer(struct tty_struct *tty)
+{
+ struct gs_port *port = tty->driver_data;
+ func_enter ();
+
+ func_exit ();
+ return port->xmit_cnt;
+}
+
+
+static int gs_real_chars_in_buffer(struct tty_struct *tty)
+{
+ struct gs_port *port;
+ func_enter ();
+
+ if (!tty) return 0;
+ port = tty->driver_data;
+
+ if (!port->rd) return 0;
+ if (!port->rd->chars_in_buffer) return 0;
+
+ func_exit ();
+ return port->xmit_cnt + port->rd->chars_in_buffer (port);
+}
+
+
+static int gs_wait_tx_flushed (void * ptr, unsigned long timeout)
+{
+ struct gs_port *port = ptr;
+ unsigned long end_jiffies;
+ int jiffies_to_transmit, charsleft = 0, rv = 0;
+ int rcib;
+
+ func_enter();
+
+ gs_dprintk (GS_DEBUG_FLUSH, "port=%p.\n", port);
+ if (port) {
+ gs_dprintk (GS_DEBUG_FLUSH, "xmit_cnt=%x, xmit_buf=%p, tty=%p.\n",
+ port->xmit_cnt, port->xmit_buf, port->tty);
+ }
+
+ if (!port || port->xmit_cnt < 0 || !port->xmit_buf) {
+ gs_dprintk (GS_DEBUG_FLUSH, "ERROR: !port, !port->xmit_buf or prot->xmit_cnt < 0.\n");
+ func_exit();
+ return -EINVAL; /* This is an error which we don't know how to handle. */
+ }
+
+ rcib = gs_real_chars_in_buffer(port->tty);
+
+ if(rcib <= 0) {
+ gs_dprintk (GS_DEBUG_FLUSH, "nothing to wait for.\n");
+ func_exit();
+ return rv;
+ }
+ /* stop trying: now + twice the time it would normally take + seconds */
+ if (timeout == 0) timeout = MAX_SCHEDULE_TIMEOUT;
+ end_jiffies = jiffies;
+ if (timeout != MAX_SCHEDULE_TIMEOUT)
+ end_jiffies += port->baud?(2 * rcib * 10 * HZ / port->baud):0;
+ end_jiffies += timeout;
+
+ gs_dprintk (GS_DEBUG_FLUSH, "now=%lx, end=%lx (%ld).\n",
+ jiffies, end_jiffies, end_jiffies-jiffies);
+
+ /* the expression is actually jiffies < end_jiffies, but that won't
+ work around the wraparound. Tricky eh? */
+ while ((charsleft = gs_real_chars_in_buffer (port->tty)) &&
+ time_after (end_jiffies, jiffies)) {
+ /* Units check:
+ chars * (bits/char) * (jiffies /sec) / (bits/sec) = jiffies!
+ check! */
+
+ charsleft += 16; /* Allow 16 chars more to be transmitted ... */
+ jiffies_to_transmit = port->baud?(1 + charsleft * 10 * HZ / port->baud):0;
+ /* ^^^ Round up.... */
+ if (jiffies_to_transmit <= 0) jiffies_to_transmit = 1;
+
+ gs_dprintk (GS_DEBUG_FLUSH, "Expect to finish in %d jiffies "
+ "(%d chars).\n", jiffies_to_transmit, charsleft);
+
+ msleep_interruptible(jiffies_to_msecs(jiffies_to_transmit));
+ if (signal_pending (current)) {
+ gs_dprintk (GS_DEBUG_FLUSH, "Signal pending. Bombing out: ");
+ rv = -EINTR;
+ break;
+ }
+ }
+
+ gs_dprintk (GS_DEBUG_FLUSH, "charsleft = %d.\n", charsleft);
+ set_current_state (TASK_RUNNING);
+
+ func_exit();
+ return rv;
+}
+
+
+
+void gs_flush_buffer(struct tty_struct *tty)
+{
+ struct gs_port *port;
+ unsigned long flags;
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+
+ /* XXX Would the write semaphore do? */
+ spin_lock_irqsave (&port->driver_lock, flags);
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ spin_unlock_irqrestore (&port->driver_lock, flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+ func_exit ();
+}
+
+
+void gs_flush_chars(struct tty_struct * tty)
+{
+ struct gs_port *port;
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+
+ if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+ !port->xmit_buf) {
+ func_exit ();
+ return;
+ }
+
+ /* Beats me -- REW */
+ port->flags |= GS_TX_INTEN;
+ port->rd->enable_tx_interrupts (port);
+ func_exit ();
+}
+
+
+void gs_stop(struct tty_struct * tty)
+{
+ struct gs_port *port;
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+
+ if (port->xmit_cnt &&
+ port->xmit_buf &&
+ (port->flags & GS_TX_INTEN) ) {
+ port->flags &= ~GS_TX_INTEN;
+ port->rd->disable_tx_interrupts (port);
+ }
+ func_exit ();
+}
+
+
+void gs_start(struct tty_struct * tty)
+{
+ struct gs_port *port;
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+
+ if (port->xmit_cnt &&
+ port->xmit_buf &&
+ !(port->flags & GS_TX_INTEN) ) {
+ port->flags |= GS_TX_INTEN;
+ port->rd->enable_tx_interrupts (port);
+ }
+ func_exit ();
+}
+
+
+static void gs_shutdown_port (struct gs_port *port)
+{
+ unsigned long flags;
+
+ func_enter();
+
+ if (!port) return;
+
+ if (!(port->flags & ASYNC_INITIALIZED))
+ return;
+
+ spin_lock_irqsave(&port->driver_lock, flags);
+
+ if (port->xmit_buf) {
+ free_page((unsigned long) port->xmit_buf);
+ port->xmit_buf = NULL;
+ }
+
+ if (port->tty)
+ set_bit(TTY_IO_ERROR, &port->tty->flags);
+
+ port->rd->shutdown_port (port);
+
+ port->flags &= ~ASYNC_INITIALIZED;
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+
+ func_exit();
+}
+
+
+void gs_hangup(struct tty_struct *tty)
+{
+ struct gs_port *port;
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+ tty = port->tty;
+ if (!tty)
+ return;
+
+ gs_shutdown_port (port);
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE|GS_ACTIVE);
+ port->tty = NULL;
+ port->count = 0;
+
+ wake_up_interruptible(&port->open_wait);
+ func_exit ();
+}
+
+
+int gs_block_til_ready(void *port_, struct file * filp)
+{
+ struct gs_port *port = port_;
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0;
+ int CD;
+ struct tty_struct *tty;
+ unsigned long flags;
+
+ func_enter ();
+
+ if (!port) return 0;
+
+ tty = port->tty;
+
+ if (!tty) return 0;
+
+ gs_dprintk (GS_DEBUG_BTR, "Entering gs_block_till_ready.\n");
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ gs_dprintk (GS_DEBUG_BTR, "after hung up\n");
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ gs_dprintk (GS_DEBUG_BTR, "after nonblock\n");
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, port->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+
+ add_wait_queue(&port->open_wait, &wait);
+
+ gs_dprintk (GS_DEBUG_BTR, "after add waitq.\n");
+ spin_lock_irqsave(&port->driver_lock, flags);
+ if (!tty_hung_up_p(filp)) {
+ port->count--;
+ }
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ port->blocked_open++;
+ while (1) {
+ CD = port->rd->get_CD (port);
+ gs_dprintk (GS_DEBUG_BTR, "CD is now %d.\n", CD);
+ set_current_state (TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || CD))
+ break;
+ gs_dprintk (GS_DEBUG_BTR, "signal_pending is now: %d (%lx)\n",
+ (int)signal_pending (current), *(long*)(&current->blocked));
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ gs_dprintk (GS_DEBUG_BTR, "Got out of the loop. (%d)\n",
+ port->blocked_open);
+ set_current_state (TASK_RUNNING);
+ remove_wait_queue(&port->open_wait, &wait);
+ if (!tty_hung_up_p(filp)) {
+ port->count++;
+ }
+ port->blocked_open--;
+ if (retval)
+ return retval;
+
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ func_exit ();
+ return 0;
+}
+
+
+void gs_close(struct tty_struct * tty, struct file * filp)
+{
+ unsigned long flags;
+ struct gs_port *port;
+
+ func_enter ();
+
+ if (!tty) return;
+
+ port = (struct gs_port *) tty->driver_data;
+
+ if (!port) return;
+
+ if (!port->tty) {
+ /* This seems to happen when this is called from vhangup. */
+ gs_dprintk (GS_DEBUG_CLOSE, "gs: Odd: port->tty is NULL\n");
+ port->tty = tty;
+ }
+
+ spin_lock_irqsave(&port->driver_lock, flags);
+
+ if (tty_hung_up_p(filp)) {
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ if (port->rd->hungup)
+ port->rd->hungup (port);
+ func_exit ();
+ return;
+ }
+
+ if ((tty->count == 1) && (port->count != 1)) {
+ printk(KERN_ERR "gs: gs_close port %p: bad port count;"
+ " tty->count is 1, port count is %d\n", port, port->count);
+ port->count = 1;
+ }
+ if (--port->count < 0) {
+ printk(KERN_ERR "gs: gs_close port %p: bad port count: %d\n", port, port->count);
+ port->count = 0;
+ }
+
+ if (port->count) {
+ gs_dprintk(GS_DEBUG_CLOSE, "gs_close port %p: count: %d\n", port, port->count);
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ func_exit ();
+ return;
+ }
+ port->flags |= ASYNC_CLOSING;
+
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ /* if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, port->closing_wait); */
+
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+
+ port->rd->disable_rx_interrupts (port);
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+
+ /* close has no way of returning "EINTR", so discard return value */
+ if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ gs_wait_tx_flushed (port, port->closing_wait);
+
+ port->flags &= ~GS_ACTIVE;
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+ tty->closing = 0;
+
+ port->event = 0;
+ port->rd->close (port);
+ port->rd->shutdown_port (port);
+ port->tty = NULL;
+
+ if (port->blocked_open) {
+ if (port->close_delay) {
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ msleep_interruptible(jiffies_to_msecs(port->close_delay));
+ spin_lock_irqsave(&port->driver_lock, flags);
+ }
+ wake_up_interruptible(&port->open_wait);
+ }
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING | ASYNC_INITIALIZED);
+ wake_up_interruptible(&port->close_wait);
+
+ func_exit ();
+}
+
+
+static unsigned int gs_baudrates[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+
+void gs_set_termios (struct tty_struct * tty,
+ struct termios * old_termios)
+{
+ struct gs_port *port;
+ int baudrate, tmp, rv;
+ struct termios *tiosp;
+
+ func_enter();
+
+ if (!tty) return;
+
+ port = tty->driver_data;
+
+ if (!port) return;
+ if (!port->tty) {
+ /* This seems to happen when this is called after gs_close. */
+ gs_dprintk (GS_DEBUG_TERMIOS, "gs: Odd: port->tty is NULL\n");
+ port->tty = tty;
+ }
+
+
+ tiosp = tty->termios;
+
+ if (gs_debug & GS_DEBUG_TERMIOS) {
+ gs_dprintk (GS_DEBUG_TERMIOS, "termios structure (%p):\n", tiosp);
+ }
+
+#if 0
+ /* This is an optimization that is only allowed for dumb cards */
+ /* Smart cards require knowledge of iflags and oflags too: that
+ might change hardware cooking mode.... */
+#endif
+ if (old_termios) {
+ if( (tiosp->c_iflag == old_termios->c_iflag)
+ && (tiosp->c_oflag == old_termios->c_oflag)
+ && (tiosp->c_cflag == old_termios->c_cflag)
+ && (tiosp->c_lflag == old_termios->c_lflag)
+ && (tiosp->c_line == old_termios->c_line)
+ && (memcmp(tiosp->c_cc, old_termios->c_cc, NCC) == 0)) {
+ gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: optimized away\n");
+ return /* 0 */;
+ }
+ } else
+ gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: no old_termios: "
+ "no optimization\n");
+
+ if(old_termios && (gs_debug & GS_DEBUG_TERMIOS)) {
+ if(tiosp->c_iflag != old_termios->c_iflag) printk("c_iflag changed\n");
+ if(tiosp->c_oflag != old_termios->c_oflag) printk("c_oflag changed\n");
+ if(tiosp->c_cflag != old_termios->c_cflag) printk("c_cflag changed\n");
+ if(tiosp->c_lflag != old_termios->c_lflag) printk("c_lflag changed\n");
+ if(tiosp->c_line != old_termios->c_line) printk("c_line changed\n");
+ if(!memcmp(tiosp->c_cc, old_termios->c_cc, NCC)) printk("c_cc changed\n");
+ }
+
+ baudrate = tiosp->c_cflag & CBAUD;
+ if (baudrate & CBAUDEX) {
+ baudrate &= ~CBAUDEX;
+ if ((baudrate < 1) || (baudrate > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ baudrate += 15;
+ }
+
+ baudrate = gs_baudrates[baudrate];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ( (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baudrate = 57600;
+ else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baudrate = 115200;
+ else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ baudrate = 230400;
+ else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ baudrate = 460800;
+ else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudrate = (port->baud_base / port->custom_divisor);
+ }
+
+ /* I recommend using THIS instead of the mess in termios (and
+ duplicating the above code). Next we should create a clean
+ interface towards this variable. If your card supports arbitrary
+ baud rates, (e.g. CD1400 or 16550 based cards) then everything
+ will be very easy..... */
+ port->baud = baudrate;
+
+ /* Two timer ticks seems enough to wakeup something like SLIP driver */
+ /* Baudrate/10 is cps. Divide by HZ to get chars per tick. */
+ tmp = (baudrate / 10 / HZ) * 2;
+
+ if (tmp < 0) tmp = 0;
+ if (tmp >= SERIAL_XMIT_SIZE) tmp = SERIAL_XMIT_SIZE-1;
+
+ port->wakeup_chars = tmp;
+
+ /* We should really wait for the characters to be all sent before
+ changing the settings. -- CAL */
+ rv = gs_wait_tx_flushed (port, MAX_SCHEDULE_TIMEOUT);
+ if (rv < 0) return /* rv */;
+
+ rv = port->rd->set_real_termios(port);
+ if (rv < 0) return /* rv */;
+
+ if ((!old_termios ||
+ (old_termios->c_cflag & CRTSCTS)) &&
+ !( tiosp->c_cflag & CRTSCTS)) {
+ tty->stopped = 0;
+ gs_start(tty);
+ }
+
+#ifdef tytso_patch_94Nov25_1726
+ /* This "makes sense", Why is it commented out? */
+
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&port->gs.open_wait);
+#endif
+
+ func_exit();
+ return /* 0 */;
+}
+
+
+
+/* Must be called with interrupts enabled */
+int gs_init_port(struct gs_port *port)
+{
+ unsigned long flags;
+ unsigned long page;
+
+ func_enter ();
+
+ if (!tmp_buf) {
+ page = get_zeroed_page(GFP_KERNEL);
+ spin_lock_irqsave (&port->driver_lock, flags); /* Don't expect this to make a difference. */
+ if (tmp_buf)
+ free_page(page);
+ else
+ tmp_buf = (unsigned char *) page;
+ spin_unlock_irqrestore (&port->driver_lock, flags);
+ if (!tmp_buf) {
+ func_exit ();
+ return -ENOMEM;
+ }
+ }
+
+ if (port->flags & ASYNC_INITIALIZED) {
+ func_exit ();
+ return 0;
+ }
+ if (!port->xmit_buf) {
+ /* We may sleep in get_zeroed_page() */
+ unsigned long tmp;
+
+ tmp = get_zeroed_page(GFP_KERNEL);
+ spin_lock_irqsave (&port->driver_lock, flags);
+ if (port->xmit_buf)
+ free_page (tmp);
+ else
+ port->xmit_buf = (unsigned char *) tmp;
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ if (!port->xmit_buf) {
+ func_exit ();
+ return -ENOMEM;
+ }
+ }
+
+ spin_lock_irqsave (&port->driver_lock, flags);
+ if (port->tty)
+ clear_bit(TTY_IO_ERROR, &port->tty->flags);
+ init_MUTEX(&port->port_write_sem);
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ gs_set_termios(port->tty, NULL);
+ spin_lock_irqsave (&port->driver_lock, flags);
+ port->flags |= ASYNC_INITIALIZED;
+ port->flags &= ~GS_TX_INTEN;
+
+ spin_unlock_irqrestore(&port->driver_lock, flags);
+ func_exit ();
+ return 0;
+}
+
+
+int gs_setserial(struct gs_port *port, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+
+ if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+ return(-EFAULT);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((sio.baud_base != port->baud_base) ||
+ (sio.close_delay != port->close_delay) ||
+ ((sio.flags & ~ASYNC_USR_MASK) !=
+ (port->flags & ~ASYNC_USR_MASK)))
+ return(-EPERM);
+ }
+
+ port->flags = (port->flags & ~ASYNC_USR_MASK) |
+ (sio.flags & ASYNC_USR_MASK);
+
+ port->baud_base = sio.baud_base;
+ port->close_delay = sio.close_delay;
+ port->closing_wait = sio.closing_wait;
+ port->custom_divisor = sio.custom_divisor;
+
+ gs_set_termios (port->tty, NULL);
+
+ return 0;
+}
+
+
+/*****************************************************************************/
+
+/*
+ * Generate the serial struct info.
+ */
+
+int gs_getserial(struct gs_port *port, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+
+ memset(&sio, 0, sizeof(struct serial_struct));
+ sio.flags = port->flags;
+ sio.baud_base = port->baud_base;
+ sio.close_delay = port->close_delay;
+ sio.closing_wait = port->closing_wait;
+ sio.custom_divisor = port->custom_divisor;
+ sio.hub6 = 0;
+
+ /* If you want you can override these. */
+ sio.type = PORT_UNKNOWN;
+ sio.xmit_fifo_size = -1;
+ sio.line = -1;
+ sio.port = -1;
+ sio.irq = -1;
+
+ if (port->rd->getserial)
+ port->rd->getserial (port, &sio);
+
+ if (copy_to_user(sp, &sio, sizeof(struct serial_struct)))
+ return -EFAULT;
+ return 0;
+
+}
+
+
+void gs_got_break(struct gs_port *port)
+{
+ func_enter ();
+
+ tty_insert_flip_char(port->tty, 0, TTY_BREAK);
+ tty_schedule_flip(port->tty);
+ if (port->flags & ASYNC_SAK) {
+ do_SAK (port->tty);
+ }
+
+ func_exit ();
+}
+
+
+EXPORT_SYMBOL(gs_put_char);
+EXPORT_SYMBOL(gs_write);
+EXPORT_SYMBOL(gs_write_room);
+EXPORT_SYMBOL(gs_chars_in_buffer);
+EXPORT_SYMBOL(gs_flush_buffer);
+EXPORT_SYMBOL(gs_flush_chars);
+EXPORT_SYMBOL(gs_stop);
+EXPORT_SYMBOL(gs_start);
+EXPORT_SYMBOL(gs_hangup);
+EXPORT_SYMBOL(gs_block_til_ready);
+EXPORT_SYMBOL(gs_close);
+EXPORT_SYMBOL(gs_set_termios);
+EXPORT_SYMBOL(gs_init_port);
+EXPORT_SYMBOL(gs_setserial);
+EXPORT_SYMBOL(gs_getserial);
+EXPORT_SYMBOL(gs_got_break);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/genrtc.c b/drivers/char/genrtc.c
new file mode 100644
index 000000000000..d3a2bc36129b
--- /dev/null
+++ b/drivers/char/genrtc.c
@@ -0,0 +1,535 @@
+/*
+ * Real Time Clock interface for
+ * - q40 and other m68k machines,
+ * - HP PARISC machines
+ * - PowerPC machines
+ * emulate some RTC irq capabilities in software
+ *
+ * Copyright (C) 1999 Richard Zidlicky
+ *
+ * based on Paul Gortmaker's rtc.c device and
+ * Sam Creasey Generic rtc driver
+ *
+ * This driver allows use of the real time clock (built into
+ * nearly all computers) from user space. It exports the /dev/rtc
+ * interface supporting various ioctl() and also the /proc/dev/rtc
+ * pseudo-file for status information.
+ *
+ * The ioctls can be used to set the interrupt behaviour where
+ * supported.
+ *
+ * The /dev/rtc interface will block on reads until an interrupt
+ * has been received. If a RTC interrupt has already happened,
+ * it will output an unsigned long and then block. The output value
+ * contains the interrupt status in the low byte and the number of
+ * interrupts since the last read in the remaining high bytes. The
+ * /dev/rtc interface can also be used with the select(2) call.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+
+ * 1.01 fix for 2.3.X rz@linux-m68k.org
+ * 1.02 merged with code from genrtc.c rz@linux-m68k.org
+ * 1.03 make it more portable zippel@linux-m68k.org
+ * 1.04 removed useless timer code rz@linux-m68k.org
+ * 1.05 portable RTC_UIE emulation rz@linux-m68k.org
+ * 1.06 set_rtc_time can return an error trini@kernel.crashing.org
+ * 1.07 ported to HP PARISC (hppa) Helge Deller <deller@gmx.de>
+ */
+
+#define RTC_VERSION "1.07"
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+
+#include <linux/rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/workqueue.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/rtc.h>
+
+/*
+ * We sponge a minor off of the misc major. No need slurping
+ * up another valuable major dev number for this. If you add
+ * an ioctl, make sure you don't conflict with SPARC's RTC
+ * ioctls.
+ */
+
+static DECLARE_WAIT_QUEUE_HEAD(gen_rtc_wait);
+
+/*
+ * Bits in gen_rtc_status.
+ */
+
+#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
+
+static unsigned char gen_rtc_status; /* bitmapped status byte. */
+static unsigned long gen_rtc_irq_data; /* our output to the world */
+
+/* months start at 0 now */
+static unsigned char days_in_mo[] =
+{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static int irq_active;
+
+#ifdef CONFIG_GEN_RTC_X
+static struct work_struct genrtc_task;
+static struct timer_list timer_task;
+
+static unsigned int oldsecs;
+static int lostint;
+static unsigned long tt_exp;
+
+static void gen_rtc_timer(unsigned long data);
+
+static volatile int stask_active; /* schedule_work */
+static volatile int ttask_active; /* timer_task */
+static int stop_rtc_timers; /* don't requeue tasks */
+static DEFINE_SPINLOCK(gen_rtc_lock);
+
+static void gen_rtc_interrupt(unsigned long arg);
+
+/*
+ * Routine to poll RTC seconds field for change as often as possible,
+ * after first RTC_UIE use timer to reduce polling
+ */
+static void genrtc_troutine(void *data)
+{
+ unsigned int tmp = get_rtc_ss();
+
+ if (stop_rtc_timers) {
+ stask_active = 0;
+ return;
+ }
+
+ if (oldsecs != tmp){
+ oldsecs = tmp;
+
+ timer_task.function = gen_rtc_timer;
+ timer_task.expires = jiffies + HZ - (HZ/10);
+ tt_exp=timer_task.expires;
+ ttask_active=1;
+ stask_active=0;
+ add_timer(&timer_task);
+
+ gen_rtc_interrupt(0);
+ } else if (schedule_work(&genrtc_task) == 0)
+ stask_active = 0;
+}
+
+static void gen_rtc_timer(unsigned long data)
+{
+ lostint = get_rtc_ss() - oldsecs ;
+ if (lostint<0)
+ lostint = 60 - lostint;
+ if (time_after(jiffies, tt_exp))
+ printk(KERN_INFO "genrtc: timer task delayed by %ld jiffies\n",
+ jiffies-tt_exp);
+ ttask_active=0;
+ stask_active=1;
+ if ((schedule_work(&genrtc_task) == 0))
+ stask_active = 0;
+}
+
+/*
+ * call gen_rtc_interrupt function to signal an RTC_UIE,
+ * arg is unused.
+ * Could be invoked either from a real interrupt handler or
+ * from some routine that periodically (eg 100HZ) monitors
+ * whether RTC_SECS changed
+ */
+static void gen_rtc_interrupt(unsigned long arg)
+{
+ /* We store the status in the low byte and the number of
+ * interrupts received since the last read in the remainder
+ * of rtc_irq_data. */
+
+ gen_rtc_irq_data += 0x100;
+ gen_rtc_irq_data &= ~0xff;
+ gen_rtc_irq_data |= RTC_UIE;
+
+ if (lostint){
+ printk("genrtc: system delaying clock ticks?\n");
+ /* increment count so that userspace knows something is wrong */
+ gen_rtc_irq_data += ((lostint-1)<<8);
+ lostint = 0;
+ }
+
+ wake_up_interruptible(&gen_rtc_wait);
+}
+
+/*
+ * Now all the various file operations that we export.
+ */
+static ssize_t gen_rtc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long data;
+ ssize_t retval;
+
+ if (count != sizeof (unsigned int) && count != sizeof (unsigned long))
+ return -EINVAL;
+
+ if (file->f_flags & O_NONBLOCK && !gen_rtc_irq_data)
+ return -EAGAIN;
+
+ add_wait_queue(&gen_rtc_wait, &wait);
+ retval = -ERESTARTSYS;
+
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ data = xchg(&gen_rtc_irq_data, 0);
+ if (data)
+ break;
+ if (signal_pending(current))
+ goto out;
+ schedule();
+ }
+
+ /* first test allows optimizer to nuke this case for 32-bit machines */
+ if (sizeof (int) != sizeof (long) && count == sizeof (unsigned int)) {
+ unsigned int uidata = data;
+ retval = put_user(uidata, (unsigned long __user *)buf);
+ }
+ else {
+ retval = put_user(data, (unsigned long __user *)buf);
+ }
+ if (!retval)
+ retval = sizeof(unsigned long);
+ out:
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&gen_rtc_wait, &wait);
+
+ return retval;
+}
+
+static unsigned int gen_rtc_poll(struct file *file,
+ struct poll_table_struct *wait)
+{
+ poll_wait(file, &gen_rtc_wait, wait);
+ if (gen_rtc_irq_data != 0)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+#endif
+
+/*
+ * Used to disable/enable interrupts, only RTC_UIE supported
+ * We also clear out any old irq data after an ioctl() that
+ * meddles with the interrupt enable/disable bits.
+ */
+
+static inline void gen_clear_rtc_irq_bit(unsigned char bit)
+{
+#ifdef CONFIG_GEN_RTC_X
+ stop_rtc_timers = 1;
+ if (ttask_active){
+ del_timer_sync(&timer_task);
+ ttask_active = 0;
+ }
+ while (stask_active)
+ schedule();
+
+ spin_lock(&gen_rtc_lock);
+ irq_active = 0;
+ spin_unlock(&gen_rtc_lock);
+#endif
+}
+
+static inline int gen_set_rtc_irq_bit(unsigned char bit)
+{
+#ifdef CONFIG_GEN_RTC_X
+ spin_lock(&gen_rtc_lock);
+ if ( !irq_active ) {
+ irq_active = 1;
+ stop_rtc_timers = 0;
+ lostint = 0;
+ INIT_WORK(&genrtc_task, genrtc_troutine, NULL);
+ oldsecs = get_rtc_ss();
+ init_timer(&timer_task);
+
+ stask_active = 1;
+ if (schedule_work(&genrtc_task) == 0){
+ stask_active = 0;
+ }
+ }
+ spin_unlock(&gen_rtc_lock);
+ gen_rtc_irq_data = 0;
+ return 0;
+#else
+ return -EINVAL;
+#endif
+}
+
+static int gen_rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct rtc_time wtime;
+ struct rtc_pll_info pll;
+ void __user *argp = (void __user *)arg;
+
+ switch (cmd) {
+
+ case RTC_PLL_GET:
+ if (get_rtc_pll(&pll))
+ return -EINVAL;
+ else
+ return copy_to_user(argp, &pll, sizeof pll) ? -EFAULT : 0;
+
+ case RTC_PLL_SET:
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+ if (copy_from_user(&pll, argp, sizeof(pll)))
+ return -EFAULT;
+ return set_rtc_pll(&pll);
+
+ case RTC_UIE_OFF: /* disable ints from RTC updates. */
+ gen_clear_rtc_irq_bit(RTC_UIE);
+ return 0;
+
+ case RTC_UIE_ON: /* enable ints for RTC updates. */
+ return gen_set_rtc_irq_bit(RTC_UIE);
+
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ /* this doesn't get week-day, who cares */
+ memset(&wtime, 0, sizeof(wtime));
+ get_rtc_time(&wtime);
+
+ return copy_to_user(argp, &wtime, sizeof(wtime)) ? -EFAULT : 0;
+
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ int year;
+ unsigned char leap_yr;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&wtime, argp, sizeof(wtime)))
+ return -EFAULT;
+
+ year = wtime.tm_year + 1900;
+ leap_yr = ((!(year % 4) && (year % 100)) ||
+ !(year % 400));
+
+ if ((wtime.tm_mon < 0 || wtime.tm_mon > 11) || (wtime.tm_mday < 1))
+ return -EINVAL;
+
+ if (wtime.tm_mday < 0 || wtime.tm_mday >
+ (days_in_mo[wtime.tm_mon] + ((wtime.tm_mon == 1) && leap_yr)))
+ return -EINVAL;
+
+ if (wtime.tm_hour < 0 || wtime.tm_hour >= 24 ||
+ wtime.tm_min < 0 || wtime.tm_min >= 60 ||
+ wtime.tm_sec < 0 || wtime.tm_sec >= 60)
+ return -EINVAL;
+
+ return set_rtc_time(&wtime);
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+static int gen_rtc_open(struct inode *inode, struct file *file)
+{
+ if (gen_rtc_status & RTC_IS_OPEN)
+ return -EBUSY;
+
+ gen_rtc_status |= RTC_IS_OPEN;
+ gen_rtc_irq_data = 0;
+ irq_active = 0;
+
+ return 0;
+}
+
+static int gen_rtc_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Turn off all interrupts once the device is no longer
+ * in use and clear the data.
+ */
+
+ gen_clear_rtc_irq_bit(RTC_PIE|RTC_AIE|RTC_UIE);
+
+ gen_rtc_status &= ~RTC_IS_OPEN;
+ return 0;
+}
+
+
+#ifdef CONFIG_PROC_FS
+
+/*
+ * Info exported via "/proc/rtc".
+ */
+
+static int gen_rtc_proc_output(char *buf)
+{
+ char *p;
+ struct rtc_time tm;
+ unsigned int flags;
+ struct rtc_pll_info pll;
+
+ p = buf;
+
+ flags = get_rtc_time(&tm);
+
+ p += sprintf(p,
+ "rtc_time\t: %02d:%02d:%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n"
+ "rtc_epoch\t: %04u\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 1900);
+
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+
+ p += sprintf(p, "alarm\t\t: ");
+ if (tm.tm_hour <= 24)
+ p += sprintf(p, "%02d:", tm.tm_hour);
+ else
+ p += sprintf(p, "**:");
+
+ if (tm.tm_min <= 59)
+ p += sprintf(p, "%02d:", tm.tm_min);
+ else
+ p += sprintf(p, "**:");
+
+ if (tm.tm_sec <= 59)
+ p += sprintf(p, "%02d\n", tm.tm_sec);
+ else
+ p += sprintf(p, "**\n");
+
+ p += sprintf(p,
+ "DST_enable\t: %s\n"
+ "BCD\t\t: %s\n"
+ "24hr\t\t: %s\n"
+ "square_wave\t: %s\n"
+ "alarm_IRQ\t: %s\n"
+ "update_IRQ\t: %s\n"
+ "periodic_IRQ\t: %s\n"
+ "periodic_freq\t: %ld\n"
+ "batt_status\t: %s\n",
+ (flags & RTC_DST_EN) ? "yes" : "no",
+ (flags & RTC_DM_BINARY) ? "no" : "yes",
+ (flags & RTC_24H) ? "yes" : "no",
+ (flags & RTC_SQWE) ? "yes" : "no",
+ (flags & RTC_AIE) ? "yes" : "no",
+ irq_active ? "yes" : "no",
+ (flags & RTC_PIE) ? "yes" : "no",
+ 0L /* freq */,
+ (flags & RTC_BATT_BAD) ? "bad" : "okay");
+ if (!get_rtc_pll(&pll))
+ p += sprintf(p,
+ "PLL adjustment\t: %d\n"
+ "PLL max +ve adjustment\t: %d\n"
+ "PLL max -ve adjustment\t: %d\n"
+ "PLL +ve adjustment factor\t: %d\n"
+ "PLL -ve adjustment factor\t: %d\n"
+ "PLL frequency\t: %ld\n",
+ pll.pll_value,
+ pll.pll_max,
+ pll.pll_min,
+ pll.pll_posmult,
+ pll.pll_negmult,
+ pll.pll_clock);
+ return p - buf;
+}
+
+static int gen_rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = gen_rtc_proc_output (page);
+ if (len <= off+count) *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len>count) len = count;
+ if (len<0) len = 0;
+ return len;
+}
+
+static int __init gen_rtc_proc_init(void)
+{
+ struct proc_dir_entry *r;
+
+ r = create_proc_read_entry("driver/rtc", 0, NULL, gen_rtc_read_proc, NULL);
+ if (!r)
+ return -ENOMEM;
+ return 0;
+}
+#else
+static inline int gen_rtc_proc_init(void) { return 0; }
+#endif /* CONFIG_PROC_FS */
+
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations gen_rtc_fops = {
+ .owner = THIS_MODULE,
+#ifdef CONFIG_GEN_RTC_X
+ .read = gen_rtc_read,
+ .poll = gen_rtc_poll,
+#endif
+ .ioctl = gen_rtc_ioctl,
+ .open = gen_rtc_open,
+ .release = gen_rtc_release,
+};
+
+static struct miscdevice rtc_gen_dev =
+{
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &gen_rtc_fops,
+};
+
+static int __init rtc_generic_init(void)
+{
+ int retval;
+
+ printk(KERN_INFO "Generic RTC Driver v%s\n", RTC_VERSION);
+
+ retval = misc_register(&rtc_gen_dev);
+ if (retval < 0)
+ return retval;
+
+ retval = gen_rtc_proc_init();
+ if (retval) {
+ misc_deregister(&rtc_gen_dev);
+ return retval;
+ }
+
+ return 0;
+}
+
+static void __exit rtc_generic_exit(void)
+{
+ remove_proc_entry ("driver/rtc", NULL);
+ misc_deregister(&rtc_gen_dev);
+}
+
+
+module_init(rtc_generic_init);
+module_exit(rtc_generic_exit);
+
+MODULE_AUTHOR("Richard Zidlicky");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/hangcheck-timer.c b/drivers/char/hangcheck-timer.c
new file mode 100644
index 000000000000..83d6b37b36cd
--- /dev/null
+++ b/drivers/char/hangcheck-timer.c
@@ -0,0 +1,129 @@
+/*
+ * hangcheck-timer.c
+ *
+ * Driver for a little io fencing timer.
+ *
+ * Copyright (C) 2002 Oracle Corporation. All rights reserved.
+ *
+ * Author: Joel Becker <joel.becker@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+/*
+ * The hangcheck-timer driver uses the TSC to catch delays that
+ * jiffies does not notice. A timer is set. When the timer fires, it
+ * checks whether it was delayed and if that delay exceeds a given
+ * margin of error. The hangcheck_tick module paramter takes the timer
+ * duration in seconds. The hangcheck_margin parameter defines the
+ * margin of error, in seconds. The defaults are 60 seconds for the
+ * timer and 180 seconds for the margin of error. IOW, a timer is set
+ * for 60 seconds. When the timer fires, the callback checks the
+ * actual duration that the timer waited. If the duration exceeds the
+ * alloted time and margin (here 60 + 180, or 240 seconds), the machine
+ * is restarted. A healthy machine will have the duration match the
+ * expected timeout very closely.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+
+#define VERSION_STR "0.5.0"
+
+#define DEFAULT_IOFENCE_MARGIN 60 /* Default fudge factor, in seconds */
+#define DEFAULT_IOFENCE_TICK 180 /* Default timer timeout, in seconds */
+
+static int hangcheck_tick = DEFAULT_IOFENCE_TICK;
+static int hangcheck_margin = DEFAULT_IOFENCE_MARGIN;
+static int hangcheck_reboot; /* Defaults to not reboot */
+
+/* Driver options */
+module_param(hangcheck_tick, int, 0);
+MODULE_PARM_DESC(hangcheck_tick, "Timer delay.");
+module_param(hangcheck_margin, int, 0);
+MODULE_PARM_DESC(hangcheck_margin, "If the hangcheck timer has been delayed more than hangcheck_margin seconds, the driver will fire.");
+module_param(hangcheck_reboot, int, 0);
+MODULE_PARM_DESC(hangcheck_reboot, "If nonzero, the machine will reboot when the timer margin is exceeded.");
+
+MODULE_AUTHOR("Joel Becker");
+MODULE_DESCRIPTION("Hangcheck-timer detects when the system has gone out to lunch past a certain margin.");
+MODULE_LICENSE("GPL");
+
+
+/* Last time scheduled */
+static unsigned long long hangcheck_tsc, hangcheck_tsc_margin;
+
+static void hangcheck_fire(unsigned long);
+
+static struct timer_list hangcheck_ticktock =
+ TIMER_INITIALIZER(hangcheck_fire, 0, 0);
+
+extern unsigned long long monotonic_clock(void);
+
+static void hangcheck_fire(unsigned long data)
+{
+ unsigned long long cur_tsc, tsc_diff;
+
+ cur_tsc = monotonic_clock();
+
+ if (cur_tsc > hangcheck_tsc)
+ tsc_diff = cur_tsc - hangcheck_tsc;
+ else
+ tsc_diff = (cur_tsc + (~0ULL - hangcheck_tsc)); /* or something */
+
+ if (tsc_diff > hangcheck_tsc_margin) {
+ if (hangcheck_reboot) {
+ printk(KERN_CRIT "Hangcheck: hangcheck is restarting the machine.\n");
+ machine_restart(NULL);
+ } else {
+ printk(KERN_CRIT "Hangcheck: hangcheck value past margin!\n");
+ }
+ }
+ mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
+ hangcheck_tsc = monotonic_clock();
+}
+
+
+static int __init hangcheck_init(void)
+{
+ printk("Hangcheck: starting hangcheck timer %s (tick is %d seconds, margin is %d seconds).\n",
+ VERSION_STR, hangcheck_tick, hangcheck_margin);
+
+ hangcheck_tsc_margin = hangcheck_margin + hangcheck_tick;
+ hangcheck_tsc_margin *= 1000000000;
+
+
+ hangcheck_tsc = monotonic_clock();
+ mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
+
+ return 0;
+}
+
+
+static void __exit hangcheck_exit(void)
+{
+ del_timer_sync(&hangcheck_ticktock);
+}
+
+module_init(hangcheck_init);
+module_exit(hangcheck_exit);
diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c
new file mode 100644
index 000000000000..5ec732e6ca92
--- /dev/null
+++ b/drivers/char/hpet.c
@@ -0,0 +1,994 @@
+/*
+ * Intel & MS High Precision Event Timer Implementation.
+ *
+ * Copyright (C) 2003 Intel Corporation
+ * Venki Pallipadi
+ * (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
+ * Bob Picco <robert.picco@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/major.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/sysctl.h>
+#include <linux/wait.h>
+#include <linux/bcd.h>
+#include <linux/seq_file.h>
+#include <linux/bitops.h>
+
+#include <asm/current.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/div64.h>
+
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <linux/hpet.h>
+
+/*
+ * The High Precision Event Timer driver.
+ * This driver is closely modelled after the rtc.c driver.
+ * http://www.intel.com/labs/platcomp/hpet/hpetspec.htm
+ */
+#define HPET_USER_FREQ (64)
+#define HPET_DRIFT (500)
+
+static u32 hpet_ntimer, hpet_nhpet, hpet_max_freq = HPET_USER_FREQ;
+
+/* A lock for concurrent access by app and isr hpet activity. */
+static DEFINE_SPINLOCK(hpet_lock);
+/* A lock for concurrent intermodule access to hpet and isr hpet activity. */
+static DEFINE_SPINLOCK(hpet_task_lock);
+
+#define HPET_DEV_NAME (7)
+
+struct hpet_dev {
+ struct hpets *hd_hpets;
+ struct hpet __iomem *hd_hpet;
+ struct hpet_timer __iomem *hd_timer;
+ unsigned long hd_ireqfreq;
+ unsigned long hd_irqdata;
+ wait_queue_head_t hd_waitqueue;
+ struct fasync_struct *hd_async_queue;
+ struct hpet_task *hd_task;
+ unsigned int hd_flags;
+ unsigned int hd_irq;
+ unsigned int hd_hdwirq;
+ char hd_name[HPET_DEV_NAME];
+};
+
+struct hpets {
+ struct hpets *hp_next;
+ struct hpet __iomem *hp_hpet;
+ unsigned long hp_hpet_phys;
+ struct time_interpolator *hp_interpolator;
+ unsigned long hp_period;
+ unsigned long hp_delta;
+ unsigned int hp_ntimer;
+ unsigned int hp_which;
+ struct hpet_dev hp_dev[1];
+};
+
+static struct hpets *hpets;
+
+#define HPET_OPEN 0x0001
+#define HPET_IE 0x0002 /* interrupt enabled */
+#define HPET_PERIODIC 0x0004
+
+#if BITS_PER_LONG == 64
+#define write_counter(V, MC) writeq(V, MC)
+#define read_counter(MC) readq(MC)
+#else
+#define write_counter(V, MC) writel(V, MC)
+#define read_counter(MC) readl(MC)
+#endif
+
+#ifndef readq
+static unsigned long long __inline readq(void __iomem *addr)
+{
+ return readl(addr) | (((unsigned long long)readl(addr + 4)) << 32LL);
+}
+#endif
+
+#ifndef writeq
+static void __inline writeq(unsigned long long v, void __iomem *addr)
+{
+ writel(v & 0xffffffff, addr);
+ writel(v >> 32, addr + 4);
+}
+#endif
+
+static irqreturn_t hpet_interrupt(int irq, void *data, struct pt_regs *regs)
+{
+ struct hpet_dev *devp;
+ unsigned long isr;
+
+ devp = data;
+
+ spin_lock(&hpet_lock);
+ devp->hd_irqdata++;
+
+ /*
+ * For non-periodic timers, increment the accumulator.
+ * This has the effect of treating non-periodic like periodic.
+ */
+ if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) {
+ unsigned long m, t;
+
+ t = devp->hd_ireqfreq;
+ m = read_counter(&devp->hd_hpet->hpet_mc);
+ write_counter(t + m + devp->hd_hpets->hp_delta,
+ &devp->hd_timer->hpet_compare);
+ }
+
+ isr = (1 << (devp - devp->hd_hpets->hp_dev));
+ writeq(isr, &devp->hd_hpet->hpet_isr);
+ spin_unlock(&hpet_lock);
+
+ spin_lock(&hpet_task_lock);
+ if (devp->hd_task)
+ devp->hd_task->ht_func(devp->hd_task->ht_data);
+ spin_unlock(&hpet_task_lock);
+
+ wake_up_interruptible(&devp->hd_waitqueue);
+
+ kill_fasync(&devp->hd_async_queue, SIGIO, POLL_IN);
+
+ return IRQ_HANDLED;
+}
+
+static int hpet_open(struct inode *inode, struct file *file)
+{
+ struct hpet_dev *devp;
+ struct hpets *hpetp;
+ int i;
+
+ if (file->f_mode & FMODE_WRITE)
+ return -EINVAL;
+
+ spin_lock_irq(&hpet_lock);
+
+ for (devp = NULL, hpetp = hpets; hpetp && !devp; hpetp = hpetp->hp_next)
+ for (i = 0; i < hpetp->hp_ntimer; i++)
+ if (hpetp->hp_dev[i].hd_flags & HPET_OPEN
+ || hpetp->hp_dev[i].hd_task)
+ continue;
+ else {
+ devp = &hpetp->hp_dev[i];
+ break;
+ }
+
+ if (!devp) {
+ spin_unlock_irq(&hpet_lock);
+ return -EBUSY;
+ }
+
+ file->private_data = devp;
+ devp->hd_irqdata = 0;
+ devp->hd_flags |= HPET_OPEN;
+ spin_unlock_irq(&hpet_lock);
+
+ return 0;
+}
+
+static ssize_t
+hpet_read(struct file *file, char __user *buf, size_t count, loff_t * ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long data;
+ ssize_t retval;
+ struct hpet_dev *devp;
+
+ devp = file->private_data;
+ if (!devp->hd_ireqfreq)
+ return -EIO;
+
+ if (count < sizeof(unsigned long))
+ return -EINVAL;
+
+ add_wait_queue(&devp->hd_waitqueue, &wait);
+
+ for ( ; ; ) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_irq(&hpet_lock);
+ data = devp->hd_irqdata;
+ devp->hd_irqdata = 0;
+ spin_unlock_irq(&hpet_lock);
+
+ if (data)
+ break;
+ else if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ } else if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+ schedule();
+ }
+
+ retval = put_user(data, (unsigned long __user *)buf);
+ if (!retval)
+ retval = sizeof(unsigned long);
+out:
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&devp->hd_waitqueue, &wait);
+
+ return retval;
+}
+
+static unsigned int hpet_poll(struct file *file, poll_table * wait)
+{
+ unsigned long v;
+ struct hpet_dev *devp;
+
+ devp = file->private_data;
+
+ if (!devp->hd_ireqfreq)
+ return 0;
+
+ poll_wait(file, &devp->hd_waitqueue, wait);
+
+ spin_lock_irq(&hpet_lock);
+ v = devp->hd_irqdata;
+ spin_unlock_irq(&hpet_lock);
+
+ if (v != 0)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static int hpet_mmap(struct file *file, struct vm_area_struct *vma)
+{
+#ifdef CONFIG_HPET_MMAP
+ struct hpet_dev *devp;
+ unsigned long addr;
+
+ if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff)
+ return -EINVAL;
+
+ devp = file->private_data;
+ addr = devp->hd_hpets->hp_hpet_phys;
+
+ if (addr & (PAGE_SIZE - 1))
+ return -ENOSYS;
+
+ vma->vm_flags |= VM_IO;
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ addr = __pa(addr);
+
+ if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
+ PAGE_SIZE, vma->vm_page_prot)) {
+ printk(KERN_ERR "remap_pfn_range failed in hpet.c\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+#else
+ return -ENOSYS;
+#endif
+}
+
+static int hpet_fasync(int fd, struct file *file, int on)
+{
+ struct hpet_dev *devp;
+
+ devp = file->private_data;
+
+ if (fasync_helper(fd, file, on, &devp->hd_async_queue) >= 0)
+ return 0;
+ else
+ return -EIO;
+}
+
+static int hpet_release(struct inode *inode, struct file *file)
+{
+ struct hpet_dev *devp;
+ struct hpet_timer __iomem *timer;
+ int irq = 0;
+
+ devp = file->private_data;
+ timer = devp->hd_timer;
+
+ spin_lock_irq(&hpet_lock);
+
+ writeq((readq(&timer->hpet_config) & ~Tn_INT_ENB_CNF_MASK),
+ &timer->hpet_config);
+
+ irq = devp->hd_irq;
+ devp->hd_irq = 0;
+
+ devp->hd_ireqfreq = 0;
+
+ if (devp->hd_flags & HPET_PERIODIC
+ && readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
+ unsigned long v;
+
+ v = readq(&timer->hpet_config);
+ v ^= Tn_TYPE_CNF_MASK;
+ writeq(v, &timer->hpet_config);
+ }
+
+ devp->hd_flags &= ~(HPET_OPEN | HPET_IE | HPET_PERIODIC);
+ spin_unlock_irq(&hpet_lock);
+
+ if (irq)
+ free_irq(irq, devp);
+
+ if (file->f_flags & FASYNC)
+ hpet_fasync(-1, file, 0);
+
+ file->private_data = NULL;
+ return 0;
+}
+
+static int hpet_ioctl_common(struct hpet_dev *, int, unsigned long, int);
+
+static int
+hpet_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct hpet_dev *devp;
+
+ devp = file->private_data;
+ return hpet_ioctl_common(devp, cmd, arg, 0);
+}
+
+static int hpet_ioctl_ieon(struct hpet_dev *devp)
+{
+ struct hpet_timer __iomem *timer;
+ struct hpet __iomem *hpet;
+ struct hpets *hpetp;
+ int irq;
+ unsigned long g, v, t, m;
+ unsigned long flags, isr;
+
+ timer = devp->hd_timer;
+ hpet = devp->hd_hpet;
+ hpetp = devp->hd_hpets;
+
+ v = readq(&timer->hpet_config);
+ spin_lock_irq(&hpet_lock);
+
+ if (devp->hd_flags & HPET_IE) {
+ spin_unlock_irq(&hpet_lock);
+ return -EBUSY;
+ }
+
+ devp->hd_flags |= HPET_IE;
+ spin_unlock_irq(&hpet_lock);
+
+ t = readq(&timer->hpet_config);
+ irq = devp->hd_hdwirq;
+
+ if (irq) {
+ sprintf(devp->hd_name, "hpet%d", (int)(devp - hpetp->hp_dev));
+
+ if (request_irq
+ (irq, hpet_interrupt, SA_INTERRUPT, devp->hd_name, (void *)devp)) {
+ printk(KERN_ERR "hpet: IRQ %d is not free\n", irq);
+ irq = 0;
+ }
+ }
+
+ if (irq == 0) {
+ spin_lock_irq(&hpet_lock);
+ devp->hd_flags ^= HPET_IE;
+ spin_unlock_irq(&hpet_lock);
+ return -EIO;
+ }
+
+ devp->hd_irq = irq;
+ t = devp->hd_ireqfreq;
+ v = readq(&timer->hpet_config);
+ g = v | Tn_INT_ENB_CNF_MASK;
+
+ if (devp->hd_flags & HPET_PERIODIC) {
+ write_counter(t, &timer->hpet_compare);
+ g |= Tn_TYPE_CNF_MASK;
+ v |= Tn_TYPE_CNF_MASK;
+ writeq(v, &timer->hpet_config);
+ v |= Tn_VAL_SET_CNF_MASK;
+ writeq(v, &timer->hpet_config);
+ local_irq_save(flags);
+ m = read_counter(&hpet->hpet_mc);
+ write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+ } else {
+ local_irq_save(flags);
+ m = read_counter(&hpet->hpet_mc);
+ write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+ }
+
+ isr = (1 << (devp - hpets->hp_dev));
+ writeq(isr, &hpet->hpet_isr);
+ writeq(g, &timer->hpet_config);
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static inline unsigned long hpet_time_div(unsigned long dis)
+{
+ unsigned long long m = 1000000000000000ULL;
+
+ do_div(m, dis);
+
+ return (unsigned long)m;
+}
+
+static int
+hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, int kernel)
+{
+ struct hpet_timer __iomem *timer;
+ struct hpet __iomem *hpet;
+ struct hpets *hpetp;
+ int err;
+ unsigned long v;
+
+ switch (cmd) {
+ case HPET_IE_OFF:
+ case HPET_INFO:
+ case HPET_EPI:
+ case HPET_DPI:
+ case HPET_IRQFREQ:
+ timer = devp->hd_timer;
+ hpet = devp->hd_hpet;
+ hpetp = devp->hd_hpets;
+ break;
+ case HPET_IE_ON:
+ return hpet_ioctl_ieon(devp);
+ default:
+ return -EINVAL;
+ }
+
+ err = 0;
+
+ switch (cmd) {
+ case HPET_IE_OFF:
+ if ((devp->hd_flags & HPET_IE) == 0)
+ break;
+ v = readq(&timer->hpet_config);
+ v &= ~Tn_INT_ENB_CNF_MASK;
+ writeq(v, &timer->hpet_config);
+ if (devp->hd_irq) {
+ free_irq(devp->hd_irq, devp);
+ devp->hd_irq = 0;
+ }
+ devp->hd_flags ^= HPET_IE;
+ break;
+ case HPET_INFO:
+ {
+ struct hpet_info info;
+
+ info.hi_ireqfreq = hpet_time_div(hpetp->hp_period *
+ devp->hd_ireqfreq);
+ info.hi_flags =
+ readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK;
+ info.hi_hpet = devp->hd_hpets->hp_which;
+ info.hi_timer = devp - devp->hd_hpets->hp_dev;
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ err = -EFAULT;
+ break;
+ }
+ case HPET_EPI:
+ v = readq(&timer->hpet_config);
+ if ((v & Tn_PER_INT_CAP_MASK) == 0) {
+ err = -ENXIO;
+ break;
+ }
+ devp->hd_flags |= HPET_PERIODIC;
+ break;
+ case HPET_DPI:
+ v = readq(&timer->hpet_config);
+ if ((v & Tn_PER_INT_CAP_MASK) == 0) {
+ err = -ENXIO;
+ break;
+ }
+ if (devp->hd_flags & HPET_PERIODIC &&
+ readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
+ v = readq(&timer->hpet_config);
+ v ^= Tn_TYPE_CNF_MASK;
+ writeq(v, &timer->hpet_config);
+ }
+ devp->hd_flags &= ~HPET_PERIODIC;
+ break;
+ case HPET_IRQFREQ:
+ if (!kernel && (arg > hpet_max_freq) &&
+ !capable(CAP_SYS_RESOURCE)) {
+ err = -EACCES;
+ break;
+ }
+
+ if (arg & (arg - 1)) {
+ err = -EINVAL;
+ break;
+ }
+
+ devp->hd_ireqfreq = hpet_time_div(hpetp->hp_period * arg);
+ }
+
+ return err;
+}
+
+static struct file_operations hpet_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = hpet_read,
+ .poll = hpet_poll,
+ .ioctl = hpet_ioctl,
+ .open = hpet_open,
+ .release = hpet_release,
+ .fasync = hpet_fasync,
+ .mmap = hpet_mmap,
+};
+
+EXPORT_SYMBOL(hpet_alloc);
+EXPORT_SYMBOL(hpet_register);
+EXPORT_SYMBOL(hpet_unregister);
+EXPORT_SYMBOL(hpet_control);
+
+int hpet_register(struct hpet_task *tp, int periodic)
+{
+ unsigned int i;
+ u64 mask;
+ struct hpet_timer __iomem *timer;
+ struct hpet_dev *devp;
+ struct hpets *hpetp;
+
+ switch (periodic) {
+ case 1:
+ mask = Tn_PER_INT_CAP_MASK;
+ break;
+ case 0:
+ mask = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ spin_lock_irq(&hpet_task_lock);
+ spin_lock(&hpet_lock);
+
+ for (devp = NULL, hpetp = hpets; hpetp && !devp; hpetp = hpetp->hp_next)
+ for (timer = hpetp->hp_hpet->hpet_timers, i = 0;
+ i < hpetp->hp_ntimer; i++, timer++) {
+ if ((readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK)
+ != mask)
+ continue;
+
+ devp = &hpetp->hp_dev[i];
+
+ if (devp->hd_flags & HPET_OPEN || devp->hd_task) {
+ devp = NULL;
+ continue;
+ }
+
+ tp->ht_opaque = devp;
+ devp->hd_task = tp;
+ break;
+ }
+
+ spin_unlock(&hpet_lock);
+ spin_unlock_irq(&hpet_task_lock);
+
+ if (tp->ht_opaque)
+ return 0;
+ else
+ return -EBUSY;
+}
+
+static inline int hpet_tpcheck(struct hpet_task *tp)
+{
+ struct hpet_dev *devp;
+ struct hpets *hpetp;
+
+ devp = tp->ht_opaque;
+
+ if (!devp)
+ return -ENXIO;
+
+ for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+ if (devp >= hpetp->hp_dev
+ && devp < (hpetp->hp_dev + hpetp->hp_ntimer)
+ && devp->hd_hpet == hpetp->hp_hpet)
+ return 0;
+
+ return -ENXIO;
+}
+
+int hpet_unregister(struct hpet_task *tp)
+{
+ struct hpet_dev *devp;
+ struct hpet_timer __iomem *timer;
+ int err;
+
+ if ((err = hpet_tpcheck(tp)))
+ return err;
+
+ spin_lock_irq(&hpet_task_lock);
+ spin_lock(&hpet_lock);
+
+ devp = tp->ht_opaque;
+ if (devp->hd_task != tp) {
+ spin_unlock(&hpet_lock);
+ spin_unlock_irq(&hpet_task_lock);
+ return -ENXIO;
+ }
+
+ timer = devp->hd_timer;
+ writeq((readq(&timer->hpet_config) & ~Tn_INT_ENB_CNF_MASK),
+ &timer->hpet_config);
+ devp->hd_flags &= ~(HPET_IE | HPET_PERIODIC);
+ devp->hd_task = NULL;
+ spin_unlock(&hpet_lock);
+ spin_unlock_irq(&hpet_task_lock);
+
+ return 0;
+}
+
+int hpet_control(struct hpet_task *tp, unsigned int cmd, unsigned long arg)
+{
+ struct hpet_dev *devp;
+ int err;
+
+ if ((err = hpet_tpcheck(tp)))
+ return err;
+
+ spin_lock_irq(&hpet_lock);
+ devp = tp->ht_opaque;
+ if (devp->hd_task != tp) {
+ spin_unlock_irq(&hpet_lock);
+ return -ENXIO;
+ }
+ spin_unlock_irq(&hpet_lock);
+ return hpet_ioctl_common(devp, cmd, arg, 1);
+}
+
+static ctl_table hpet_table[] = {
+ {
+ .ctl_name = 1,
+ .procname = "max-user-freq",
+ .data = &hpet_max_freq,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {.ctl_name = 0}
+};
+
+static ctl_table hpet_root[] = {
+ {
+ .ctl_name = 1,
+ .procname = "hpet",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = hpet_table,
+ },
+ {.ctl_name = 0}
+};
+
+static ctl_table dev_root[] = {
+ {
+ .ctl_name = CTL_DEV,
+ .procname = "dev",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = hpet_root,
+ },
+ {.ctl_name = 0}
+};
+
+static struct ctl_table_header *sysctl_header;
+
+static void hpet_register_interpolator(struct hpets *hpetp)
+{
+#ifdef CONFIG_TIME_INTERPOLATION
+ struct time_interpolator *ti;
+
+ ti = kmalloc(sizeof(*ti), GFP_KERNEL);
+ if (!ti)
+ return;
+
+ memset(ti, 0, sizeof(*ti));
+ ti->source = TIME_SOURCE_MMIO64;
+ ti->shift = 10;
+ ti->addr = &hpetp->hp_hpet->hpet_mc;
+ ti->frequency = hpet_time_div(hpets->hp_period);
+ ti->drift = ti->frequency * HPET_DRIFT / 1000000;
+ ti->mask = -1;
+
+ hpetp->hp_interpolator = ti;
+ register_time_interpolator(ti);
+#endif
+}
+
+/*
+ * Adjustment for when arming the timer with
+ * initial conditions. That is, main counter
+ * ticks expired before interrupts are enabled.
+ */
+#define TICK_CALIBRATE (1000UL)
+
+static unsigned long hpet_calibrate(struct hpets *hpetp)
+{
+ struct hpet_timer __iomem *timer = NULL;
+ unsigned long t, m, count, i, flags, start;
+ struct hpet_dev *devp;
+ int j;
+ struct hpet __iomem *hpet;
+
+ for (j = 0, devp = hpetp->hp_dev; j < hpetp->hp_ntimer; j++, devp++)
+ if ((devp->hd_flags & HPET_OPEN) == 0) {
+ timer = devp->hd_timer;
+ break;
+ }
+
+ if (!timer)
+ return 0;
+
+ hpet = hpets->hp_hpet;
+ t = read_counter(&timer->hpet_compare);
+
+ i = 0;
+ count = hpet_time_div(hpetp->hp_period * TICK_CALIBRATE);
+
+ local_irq_save(flags);
+
+ start = read_counter(&hpet->hpet_mc);
+
+ do {
+ m = read_counter(&hpet->hpet_mc);
+ write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+ } while (i++, (m - start) < count);
+
+ local_irq_restore(flags);
+
+ return (m - start) / i;
+}
+
+int hpet_alloc(struct hpet_data *hdp)
+{
+ u64 cap, mcfg;
+ struct hpet_dev *devp;
+ u32 i, ntimer;
+ struct hpets *hpetp;
+ size_t siz;
+ struct hpet __iomem *hpet;
+ static struct hpets *last = (struct hpets *)0;
+ unsigned long ns;
+
+ /*
+ * hpet_alloc can be called by platform dependent code.
+ * if platform dependent code has allocated the hpet
+ * ACPI also reports hpet, then we catch it here.
+ */
+ for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+ if (hpetp->hp_hpet == hdp->hd_address)
+ return 0;
+
+ siz = sizeof(struct hpets) + ((hdp->hd_nirqs - 1) *
+ sizeof(struct hpet_dev));
+
+ hpetp = kmalloc(siz, GFP_KERNEL);
+
+ if (!hpetp)
+ return -ENOMEM;
+
+ memset(hpetp, 0, siz);
+
+ hpetp->hp_which = hpet_nhpet++;
+ hpetp->hp_hpet = hdp->hd_address;
+ hpetp->hp_hpet_phys = hdp->hd_phys_address;
+
+ hpetp->hp_ntimer = hdp->hd_nirqs;
+
+ for (i = 0; i < hdp->hd_nirqs; i++)
+ hpetp->hp_dev[i].hd_hdwirq = hdp->hd_irq[i];
+
+ hpet = hpetp->hp_hpet;
+
+ cap = readq(&hpet->hpet_cap);
+
+ ntimer = ((cap & HPET_NUM_TIM_CAP_MASK) >> HPET_NUM_TIM_CAP_SHIFT) + 1;
+
+ if (hpetp->hp_ntimer != ntimer) {
+ printk(KERN_WARNING "hpet: number irqs doesn't agree"
+ " with number of timers\n");
+ kfree(hpetp);
+ return -ENODEV;
+ }
+
+ if (last)
+ last->hp_next = hpetp;
+ else
+ hpets = hpetp;
+
+ last = hpetp;
+
+ hpetp->hp_period = (cap & HPET_COUNTER_CLK_PERIOD_MASK) >>
+ HPET_COUNTER_CLK_PERIOD_SHIFT;
+
+ printk(KERN_INFO "hpet%d: at MMIO 0x%lx, IRQ%s",
+ hpetp->hp_which, hdp->hd_phys_address,
+ hpetp->hp_ntimer > 1 ? "s" : "");
+ for (i = 0; i < hpetp->hp_ntimer; i++)
+ printk("%s %d", i > 0 ? "," : "", hdp->hd_irq[i]);
+ printk("\n");
+
+ ns = hpetp->hp_period; /* femptoseconds, 10^-15 */
+ do_div(ns, 1000000); /* convert to nanoseconds, 10^-9 */
+ printk(KERN_INFO "hpet%d: %ldns tick, %d %d-bit timers\n",
+ hpetp->hp_which, ns, hpetp->hp_ntimer,
+ cap & HPET_COUNTER_SIZE_MASK ? 64 : 32);
+
+ mcfg = readq(&hpet->hpet_config);
+ if ((mcfg & HPET_ENABLE_CNF_MASK) == 0) {
+ write_counter(0L, &hpet->hpet_mc);
+ mcfg |= HPET_ENABLE_CNF_MASK;
+ writeq(mcfg, &hpet->hpet_config);
+ }
+
+ for (i = 0, devp = hpetp->hp_dev; i < hpetp->hp_ntimer;
+ i++, hpet_ntimer++, devp++) {
+ unsigned long v;
+ struct hpet_timer __iomem *timer;
+
+ timer = &hpet->hpet_timers[devp - hpetp->hp_dev];
+ v = readq(&timer->hpet_config);
+
+ devp->hd_hpets = hpetp;
+ devp->hd_hpet = hpet;
+ devp->hd_timer = timer;
+
+ /*
+ * If the timer was reserved by platform code,
+ * then make timer unavailable for opens.
+ */
+ if (hdp->hd_state & (1 << i)) {
+ devp->hd_flags = HPET_OPEN;
+ continue;
+ }
+
+ init_waitqueue_head(&devp->hd_waitqueue);
+ }
+
+ hpetp->hp_delta = hpet_calibrate(hpetp);
+ hpet_register_interpolator(hpetp);
+
+ return 0;
+}
+
+static acpi_status hpet_resources(struct acpi_resource *res, void *data)
+{
+ struct hpet_data *hdp;
+ acpi_status status;
+ struct acpi_resource_address64 addr;
+ struct hpets *hpetp;
+
+ hdp = data;
+
+ status = acpi_resource_to_address64(res, &addr);
+
+ if (ACPI_SUCCESS(status)) {
+ unsigned long size;
+
+ size = addr.max_address_range - addr.min_address_range + 1;
+ hdp->hd_phys_address = addr.min_address_range;
+ hdp->hd_address = ioremap(addr.min_address_range, size);
+
+ for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+ if (hpetp->hp_hpet == hdp->hd_address)
+ return -EBUSY;
+ } else if (res->id == ACPI_RSTYPE_EXT_IRQ) {
+ struct acpi_resource_ext_irq *irqp;
+ int i;
+
+ irqp = &res->data.extended_irq;
+
+ if (irqp->number_of_interrupts > 0) {
+ hdp->hd_nirqs = irqp->number_of_interrupts;
+
+ for (i = 0; i < hdp->hd_nirqs; i++)
+ hdp->hd_irq[i] =
+ acpi_register_gsi(irqp->interrupts[i],
+ irqp->edge_level,
+ irqp->active_high_low);
+ }
+ }
+
+ return AE_OK;
+}
+
+static int hpet_acpi_add(struct acpi_device *device)
+{
+ acpi_status result;
+ struct hpet_data data;
+
+ memset(&data, 0, sizeof(data));
+
+ result =
+ acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+ hpet_resources, &data);
+
+ if (ACPI_FAILURE(result))
+ return -ENODEV;
+
+ if (!data.hd_address || !data.hd_nirqs) {
+ printk("%s: no address or irqs in _CRS\n", __FUNCTION__);
+ return -ENODEV;
+ }
+
+ return hpet_alloc(&data);
+}
+
+static int hpet_acpi_remove(struct acpi_device *device, int type)
+{
+ /* XXX need to unregister interpolator, dealloc mem, etc */
+ return -EINVAL;
+}
+
+static struct acpi_driver hpet_acpi_driver = {
+ .name = "hpet",
+ .ids = "PNP0103",
+ .ops = {
+ .add = hpet_acpi_add,
+ .remove = hpet_acpi_remove,
+ },
+};
+
+static struct miscdevice hpet_misc = { HPET_MINOR, "hpet", &hpet_fops };
+
+static int __init hpet_init(void)
+{
+ int result;
+
+ result = misc_register(&hpet_misc);
+ if (result < 0)
+ return -ENODEV;
+
+ sysctl_header = register_sysctl_table(dev_root, 0);
+
+ result = acpi_bus_register_driver(&hpet_acpi_driver);
+ if (result < 0) {
+ if (sysctl_header)
+ unregister_sysctl_table(sysctl_header);
+ misc_deregister(&hpet_misc);
+ return result;
+ }
+
+ return 0;
+}
+
+static void __exit hpet_exit(void)
+{
+ acpi_bus_unregister_driver(&hpet_acpi_driver);
+
+ if (sysctl_header)
+ unregister_sysctl_table(sysctl_header);
+ misc_deregister(&hpet_misc);
+
+ return;
+}
+
+module_init(hpet_init);
+module_exit(hpet_exit);
+MODULE_AUTHOR("Bob Picco <Robert.Picco@hp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/hvc_console.c b/drivers/char/hvc_console.c
new file mode 100644
index 000000000000..88cd858f74d0
--- /dev/null
+++ b/drivers/char/hvc_console.c
@@ -0,0 +1,831 @@
+/*
+ * Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
+ * Copyright (C) 2001 Paul Mackerras <paulus@au.ibm.com>, IBM
+ * Copyright (C) 2004 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Additional Author(s):
+ * Ryan S. Arnold <rsa@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/console.h>
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/kbd_kern.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/major.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <asm/hvconsole.h>
+#include <asm/vio.h>
+
+#define HVC_MAJOR 229
+#define HVC_MINOR 0
+
+#define TIMEOUT (10)
+
+/*
+ * Wait this long per iteration while trying to push buffered data to the
+ * hypervisor before allowing the tty to complete a close operation.
+ */
+#define HVC_CLOSE_WAIT (HZ/100) /* 1/10 of a second */
+
+/*
+ * The Linux TTY code does not support dynamic addition of tty derived devices
+ * so we need to know how many tty devices we might need when space is allocated
+ * for the tty device. Since this driver supports hotplug of vty adapters we
+ * need to make sure we have enough allocated.
+ */
+#define HVC_ALLOC_TTY_ADAPTERS 8
+
+static struct tty_driver *hvc_driver;
+#ifdef CONFIG_MAGIC_SYSRQ
+static int sysrq_pressed;
+#endif
+
+#define N_OUTBUF 16
+#define N_INBUF 16
+
+#define __ALIGNED__ __attribute__((__aligned__(8)))
+
+struct hvc_struct {
+ spinlock_t lock;
+ int index;
+ struct tty_struct *tty;
+ unsigned int count;
+ int do_wakeup;
+ char outbuf[N_OUTBUF] __ALIGNED__;
+ int n_outbuf;
+ uint32_t vtermno;
+ int irq_requested;
+ int irq;
+ struct list_head next;
+ struct kobject kobj; /* ref count & hvc_struct lifetime */
+ struct vio_dev *vdev;
+};
+
+/* dynamic list of hvc_struct instances */
+static struct list_head hvc_structs = LIST_HEAD_INIT(hvc_structs);
+
+/*
+ * Protect the list of hvc_struct instances from inserts and removals during
+ * list traversal.
+ */
+static DEFINE_SPINLOCK(hvc_structs_lock);
+
+/*
+ * Initial console vtermnos for console API usage prior to full console
+ * initialization. Any vty adapter outside this range will not have usable
+ * console interfaces but can still be used as a tty device. This has to be
+ * static because kmalloc will not work during early console init.
+ */
+static uint32_t vtermnos[MAX_NR_HVC_CONSOLES];
+
+/* Used for accounting purposes */
+static int num_vterms = 0;
+
+static struct task_struct *hvc_task;
+
+/*
+ * This value is used to associate a tty->index value to a hvc_struct based
+ * upon order of exposure via hvc_probe().
+ */
+static int hvc_count = -1;
+
+/* Picks up late kicks after list walk but before schedule() */
+static int hvc_kicked;
+
+/* Wake the sleeping khvcd */
+static void hvc_kick(void)
+{
+ hvc_kicked = 1;
+ wake_up_process(hvc_task);
+}
+
+/*
+ * NOTE: This API isn't used if the console adapter doesn't support interrupts.
+ * In this case the console is poll driven.
+ */
+static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
+{
+ hvc_kick();
+ return IRQ_HANDLED;
+}
+
+static void hvc_unthrottle(struct tty_struct *tty)
+{
+ hvc_kick();
+}
+
+/*
+ * Do not call this function with either the hvc_strucst_lock or the hvc_struct
+ * lock held. If successful, this function increments the kobject reference
+ * count against the target hvc_struct so it should be released when finished.
+ */
+struct hvc_struct *hvc_get_by_index(int index)
+{
+ struct hvc_struct *hp;
+ unsigned long flags;
+
+ spin_lock(&hvc_structs_lock);
+
+ list_for_each_entry(hp, &hvc_structs, next) {
+ spin_lock_irqsave(&hp->lock, flags);
+ if (hp->index == index) {
+ kobject_get(&hp->kobj);
+ spin_unlock_irqrestore(&hp->lock, flags);
+ spin_unlock(&hvc_structs_lock);
+ return hp;
+ }
+ spin_unlock_irqrestore(&hp->lock, flags);
+ }
+ hp = NULL;
+
+ spin_unlock(&hvc_structs_lock);
+ return hp;
+}
+
+/*
+ * The TTY interface won't be used until after the vio layer has exposed the vty
+ * adapter to the kernel.
+ */
+static int hvc_open(struct tty_struct *tty, struct file * filp)
+{
+ struct hvc_struct *hp;
+ unsigned long flags;
+ int irq = NO_IRQ;
+ int rc = 0;
+ struct kobject *kobjp;
+
+ /* Auto increments kobject reference if found. */
+ if (!(hp = hvc_get_by_index(tty->index))) {
+ printk(KERN_WARNING "hvc_console: tty open failed, no vty associated with tty.\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&hp->lock, flags);
+ /* Check and then increment for fast path open. */
+ if (hp->count++ > 0) {
+ spin_unlock_irqrestore(&hp->lock, flags);
+ hvc_kick();
+ return 0;
+ } /* else count == 0 */
+
+ tty->driver_data = hp;
+ hp->tty = tty;
+ /* Save for request_irq outside of spin_lock. */
+ irq = hp->irq;
+ if (irq != NO_IRQ)
+ hp->irq_requested = 1;
+
+ kobjp = &hp->kobj;
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+ /* check error, fallback to non-irq */
+ if (irq != NO_IRQ)
+ rc = request_irq(irq, hvc_handle_interrupt, SA_INTERRUPT, "hvc_console", hp);
+
+ /*
+ * If the request_irq() fails and we return an error. The tty layer
+ * will call hvc_close() after a failed open but we don't want to clean
+ * up there so we'll clean up here and clear out the previously set
+ * tty fields and return the kobject reference.
+ */
+ if (rc) {
+ spin_lock_irqsave(&hp->lock, flags);
+ hp->tty = NULL;
+ hp->irq_requested = 0;
+ spin_unlock_irqrestore(&hp->lock, flags);
+ tty->driver_data = NULL;
+ kobject_put(kobjp);
+ printk(KERN_ERR "hvc_open: request_irq failed with rc %d.\n", rc);
+ }
+ /* Force wakeup of the polling thread */
+ hvc_kick();
+
+ return rc;
+}
+
+static void hvc_close(struct tty_struct *tty, struct file * filp)
+{
+ struct hvc_struct *hp;
+ struct kobject *kobjp;
+ int irq = NO_IRQ;
+ unsigned long flags;
+
+ if (tty_hung_up_p(filp))
+ return;
+
+ /*
+ * No driver_data means that this close was issued after a failed
+ * hvc_open by the tty layer's release_dev() function and we can just
+ * exit cleanly because the kobject reference wasn't made.
+ */
+ if (!tty->driver_data)
+ return;
+
+ hp = tty->driver_data;
+ spin_lock_irqsave(&hp->lock, flags);
+
+ kobjp = &hp->kobj;
+ if (--hp->count == 0) {
+ if (hp->irq_requested)
+ irq = hp->irq;
+ hp->irq_requested = 0;
+
+ /* We are done with the tty pointer now. */
+ hp->tty = NULL;
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ /*
+ * Chain calls chars_in_buffer() and returns immediately if
+ * there is no buffered data otherwise sleeps on a wait queue
+ * waking periodically to check chars_in_buffer().
+ */
+ tty_wait_until_sent(tty, HVC_CLOSE_WAIT);
+
+ if (irq != NO_IRQ)
+ free_irq(irq, hp);
+
+ } else {
+ if (hp->count < 0)
+ printk(KERN_ERR "hvc_close %X: oops, count is %d\n",
+ hp->vtermno, hp->count);
+ spin_unlock_irqrestore(&hp->lock, flags);
+ }
+
+ kobject_put(kobjp);
+}
+
+static void hvc_hangup(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+ unsigned long flags;
+ int irq = NO_IRQ;
+ int temp_open_count;
+ struct kobject *kobjp;
+
+ if (!hp)
+ return;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ /*
+ * The N_TTY line discipline has problems such that in a close vs
+ * open->hangup case this can be called after the final close so prevent
+ * that from happening for now.
+ */
+ if (hp->count <= 0) {
+ spin_unlock_irqrestore(&hp->lock, flags);
+ return;
+ }
+
+ kobjp = &hp->kobj;
+ temp_open_count = hp->count;
+ hp->count = 0;
+ hp->n_outbuf = 0;
+ hp->tty = NULL;
+ if (hp->irq_requested)
+ /* Saved for use outside of spin_lock. */
+ irq = hp->irq;
+ hp->irq_requested = 0;
+ spin_unlock_irqrestore(&hp->lock, flags);
+ if (irq != NO_IRQ)
+ free_irq(irq, hp);
+ while(temp_open_count) {
+ --temp_open_count;
+ kobject_put(kobjp);
+ }
+}
+
+/*
+ * Push buffered characters whether they were just recently buffered or waiting
+ * on a blocked hypervisor. Call this function with hp->lock held.
+ */
+static void hvc_push(struct hvc_struct *hp)
+{
+ int n;
+
+ n = hvc_put_chars(hp->vtermno, hp->outbuf, hp->n_outbuf);
+ if (n <= 0) {
+ if (n == 0)
+ return;
+ /* throw away output on error; this happens when
+ there is no session connected to the vterm. */
+ hp->n_outbuf = 0;
+ } else
+ hp->n_outbuf -= n;
+ if (hp->n_outbuf > 0)
+ memmove(hp->outbuf, hp->outbuf + n, hp->n_outbuf);
+ else
+ hp->do_wakeup = 1;
+}
+
+static inline int __hvc_write_kernel(struct hvc_struct *hp,
+ const unsigned char *buf, int count)
+{
+ unsigned long flags;
+ int rsize, written = 0;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ /* Push pending writes */
+ if (hp->n_outbuf > 0)
+ hvc_push(hp);
+
+ while (count > 0 && (rsize = N_OUTBUF - hp->n_outbuf) > 0) {
+ if (rsize > count)
+ rsize = count;
+ memcpy(hp->outbuf + hp->n_outbuf, buf, rsize);
+ count -= rsize;
+ buf += rsize;
+ hp->n_outbuf += rsize;
+ written += rsize;
+ hvc_push(hp);
+ }
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ return written;
+}
+static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct hvc_struct *hp = tty->driver_data;
+ int written;
+
+ /* This write was probably executed during a tty close. */
+ if (!hp)
+ return -EPIPE;
+
+ if (hp->count <= 0)
+ return -EIO;
+
+ written = __hvc_write_kernel(hp, buf, count);
+
+ /*
+ * Racy, but harmless, kick thread if there is still pending data.
+ * There really is nothing wrong with kicking the thread, even if there
+ * is no buffered data.
+ */
+ if (hp->n_outbuf)
+ hvc_kick();
+
+ return written;
+}
+
+/*
+ * This is actually a contract between the driver and the tty layer outlining
+ * how much write room the driver can guarentee will be sent OR BUFFERED. This
+ * driver MUST honor the return value.
+ */
+static int hvc_write_room(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+
+ if (!hp)
+ return -1;
+
+ return N_OUTBUF - hp->n_outbuf;
+}
+
+static int hvc_chars_in_buffer(struct tty_struct *tty)
+{
+ struct hvc_struct *hp = tty->driver_data;
+
+ if (!hp)
+ return -1;
+ return hp->n_outbuf;
+}
+
+#define HVC_POLL_READ 0x00000001
+#define HVC_POLL_WRITE 0x00000002
+#define HVC_POLL_QUICK 0x00000004
+
+static int hvc_poll(struct hvc_struct *hp)
+{
+ struct tty_struct *tty;
+ int i, n, poll_mask = 0;
+ char buf[N_INBUF] __ALIGNED__;
+ unsigned long flags;
+ int read_total = 0;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ /* Push pending writes */
+ if (hp->n_outbuf > 0)
+ hvc_push(hp);
+ /* Reschedule us if still some write pending */
+ if (hp->n_outbuf > 0)
+ poll_mask |= HVC_POLL_WRITE;
+
+ /* No tty attached, just skip */
+ tty = hp->tty;
+ if (tty == NULL)
+ goto bail;
+
+ /* Now check if we can get data (are we throttled ?) */
+ if (test_bit(TTY_THROTTLED, &tty->flags))
+ goto throttled;
+
+ /* If we aren't interrupt driven and aren't throttled, we always
+ * request a reschedule
+ */
+ if (hp->irq == NO_IRQ)
+ poll_mask |= HVC_POLL_READ;
+
+ /* Read data if any */
+ for (;;) {
+ int count = N_INBUF;
+ if (count > (TTY_FLIPBUF_SIZE - tty->flip.count))
+ count = TTY_FLIPBUF_SIZE - tty->flip.count;
+
+ /* If flip is full, just reschedule a later read */
+ if (count == 0) {
+ poll_mask |= HVC_POLL_READ;
+ break;
+ }
+
+ n = hvc_get_chars(hp->vtermno, buf, count);
+ if (n <= 0) {
+ /* Hangup the tty when disconnected from host */
+ if (n == -EPIPE) {
+ spin_unlock_irqrestore(&hp->lock, flags);
+ tty_hangup(tty);
+ spin_lock_irqsave(&hp->lock, flags);
+ }
+ break;
+ }
+ for (i = 0; i < n; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+ /* Handle the SysRq Hack */
+ if (buf[i] == '\x0f') { /* ^O -- should support a sequence */
+ sysrq_pressed = 1;
+ continue;
+ } else if (sysrq_pressed) {
+ handle_sysrq(buf[i], NULL, tty);
+ sysrq_pressed = 0;
+ continue;
+ }
+#endif /* CONFIG_MAGIC_SYSRQ */
+ tty_insert_flip_char(tty, buf[i], 0);
+ }
+
+ if (tty->flip.count)
+ tty_schedule_flip(tty);
+
+ /*
+ * Account for the total amount read in one loop, and if above
+ * 64 bytes, we do a quick schedule loop to let the tty grok the
+ * data and eventually throttle us.
+ */
+ read_total += n;
+ if (read_total >= 64) {
+ poll_mask |= HVC_POLL_QUICK;
+ break;
+ }
+ }
+ throttled:
+ /* Wakeup write queue if necessary */
+ if (hp->do_wakeup) {
+ hp->do_wakeup = 0;
+ tty_wakeup(tty);
+ }
+ bail:
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ return poll_mask;
+}
+
+#if defined(CONFIG_XMON) && defined(CONFIG_SMP)
+extern cpumask_t cpus_in_xmon;
+#else
+static const cpumask_t cpus_in_xmon = CPU_MASK_NONE;
+#endif
+
+/*
+ * This kthread is either polling or interrupt driven. This is determined by
+ * calling hvc_poll() who determines whether a console adapter support
+ * interrupts.
+ */
+int khvcd(void *unused)
+{
+ int poll_mask;
+ struct hvc_struct *hp;
+
+ __set_current_state(TASK_RUNNING);
+ do {
+ poll_mask = 0;
+ hvc_kicked = 0;
+ wmb();
+ if (cpus_empty(cpus_in_xmon)) {
+ spin_lock(&hvc_structs_lock);
+ list_for_each_entry(hp, &hvc_structs, next) {
+ /*hp = list_entry(node, struct hvc_struct, * next); */
+ poll_mask |= hvc_poll(hp);
+ }
+ spin_unlock(&hvc_structs_lock);
+ } else
+ poll_mask |= HVC_POLL_READ;
+ if (hvc_kicked)
+ continue;
+ if (poll_mask & HVC_POLL_QUICK) {
+ yield();
+ continue;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!hvc_kicked) {
+ if (poll_mask == 0)
+ schedule();
+ else
+ msleep_interruptible(TIMEOUT);
+ }
+ __set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+static struct tty_operations hvc_ops = {
+ .open = hvc_open,
+ .close = hvc_close,
+ .write = hvc_write,
+ .hangup = hvc_hangup,
+ .unthrottle = hvc_unthrottle,
+ .write_room = hvc_write_room,
+ .chars_in_buffer = hvc_chars_in_buffer,
+};
+
+char hvc_driver_name[] = "hvc_console";
+
+static struct vio_device_id hvc_driver_table[] __devinitdata= {
+ {"serial", "hvterm1"},
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(vio, hvc_driver_table);
+
+/* callback when the kboject ref count reaches zero. */
+static void destroy_hvc_struct(struct kobject *kobj)
+{
+ struct hvc_struct *hp = container_of(kobj, struct hvc_struct, kobj);
+ unsigned long flags;
+
+ spin_lock(&hvc_structs_lock);
+
+ spin_lock_irqsave(&hp->lock, flags);
+ list_del(&(hp->next));
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ spin_unlock(&hvc_structs_lock);
+
+ kfree(hp);
+}
+
+static struct kobj_type hvc_kobj_type = {
+ .release = destroy_hvc_struct,
+};
+
+static int __devinit hvc_probe(
+ struct vio_dev *dev,
+ const struct vio_device_id *id)
+{
+ struct hvc_struct *hp;
+
+ /* probed with invalid parameters. */
+ if (!dev || !id)
+ return -EPERM;
+
+ hp = kmalloc(sizeof(*hp), GFP_KERNEL);
+ if (!hp)
+ return -ENOMEM;
+
+ memset(hp, 0x00, sizeof(*hp));
+ hp->vtermno = dev->unit_address;
+ hp->vdev = dev;
+ hp->vdev->dev.driver_data = hp;
+ hp->irq = dev->irq;
+
+ kobject_init(&hp->kobj);
+ hp->kobj.ktype = &hvc_kobj_type;
+
+ spin_lock_init(&hp->lock);
+ spin_lock(&hvc_structs_lock);
+ hp->index = ++hvc_count;
+ list_add_tail(&(hp->next), &hvc_structs);
+ spin_unlock(&hvc_structs_lock);
+
+ return 0;
+}
+
+static int __devexit hvc_remove(struct vio_dev *dev)
+{
+ struct hvc_struct *hp = dev->dev.driver_data;
+ unsigned long flags;
+ struct kobject *kobjp;
+ struct tty_struct *tty;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ tty = hp->tty;
+ kobjp = &hp->kobj;
+
+ if (hp->index < MAX_NR_HVC_CONSOLES)
+ vtermnos[hp->index] = -1;
+
+ /* Don't whack hp->irq because tty_hangup() will need to free the irq. */
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ /*
+ * We 'put' the instance that was grabbed when the kobject instance
+ * was intialized using kobject_init(). Let the last holder of this
+ * kobject cause it to be removed, which will probably be the tty_hangup
+ * below.
+ */
+ kobject_put(kobjp);
+
+ /*
+ * This function call will auto chain call hvc_hangup. The tty should
+ * always be valid at this time unless a simultaneous tty close already
+ * cleaned up the hvc_struct.
+ */
+ if (tty)
+ tty_hangup(tty);
+ return 0;
+}
+
+static struct vio_driver hvc_vio_driver = {
+ .name = hvc_driver_name,
+ .id_table = hvc_driver_table,
+ .probe = hvc_probe,
+ .remove = hvc_remove,
+};
+
+/* Driver initialization. Follow console initialization. This is where the TTY
+ * interfaces start to become available. */
+int __init hvc_init(void)
+{
+ int rc;
+
+ /* We need more than num_vterms adapters due to hotplug additions. */
+ hvc_driver = alloc_tty_driver(HVC_ALLOC_TTY_ADAPTERS);
+ /* hvc_driver = alloc_tty_driver(num_vterms); */
+ if (!hvc_driver)
+ return -ENOMEM;
+
+ hvc_driver->owner = THIS_MODULE;
+ hvc_driver->devfs_name = "hvc/";
+ hvc_driver->driver_name = "hvc";
+ hvc_driver->name = "hvc";
+ hvc_driver->major = HVC_MAJOR;
+ hvc_driver->minor_start = HVC_MINOR;
+ hvc_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ hvc_driver->init_termios = tty_std_termios;
+ hvc_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(hvc_driver, &hvc_ops);
+
+ if (tty_register_driver(hvc_driver))
+ panic("Couldn't register hvc console driver\n");
+
+ /* Always start the kthread because there can be hotplug vty adapters
+ * added later. */
+ hvc_task = kthread_run(khvcd, NULL, "khvcd");
+ if (IS_ERR(hvc_task)) {
+ panic("Couldn't create kthread for console.\n");
+ put_tty_driver(hvc_driver);
+ return -EIO;
+ }
+
+ /* Register as a vio device to receive callbacks */
+ rc = vio_register_driver(&hvc_vio_driver);
+
+ return rc;
+}
+
+/* This isn't particularily necessary due to this being a console driver but it
+ * is nice to be thorough */
+static void __exit hvc_exit(void)
+{
+ kthread_stop(hvc_task);
+
+ vio_unregister_driver(&hvc_vio_driver);
+ tty_unregister_driver(hvc_driver);
+ /* return tty_struct instances allocated in hvc_init(). */
+ put_tty_driver(hvc_driver);
+}
+
+/*
+ * Console APIs, NOT TTY. These APIs are available immediately when
+ * hvc_console_setup() finds adapters.
+ */
+
+/*
+ * hvc_instantiate() is an early console discovery method which locates consoles
+ * prior to the vio subsystem discovering them. Hotplugged vty adapters do NOT
+ * get an hvc_instantiate() callback since the appear after early console init.
+ */
+int hvc_instantiate(uint32_t vtermno, int index)
+{
+ if (index < 0 || index >= MAX_NR_HVC_CONSOLES)
+ return -1;
+
+ if (vtermnos[index] != -1)
+ return -1;
+
+ vtermnos[index] = vtermno;
+ return 0;
+}
+
+void hvc_console_print(struct console *co, const char *b, unsigned count)
+{
+ char c[16] __ALIGNED__;
+ unsigned i = 0, n = 0;
+ int r, donecr = 0;
+
+ /* Console access attempt outside of acceptable console range. */
+ if (co->index >= MAX_NR_HVC_CONSOLES)
+ return;
+
+ /* This console adapter was removed so it is not useable. */
+ if (vtermnos[co->index] < 0)
+ return;
+
+ while (count > 0 || i > 0) {
+ if (count > 0 && i < sizeof(c)) {
+ if (b[n] == '\n' && !donecr) {
+ c[i++] = '\r';
+ donecr = 1;
+ } else {
+ c[i++] = b[n++];
+ donecr = 0;
+ --count;
+ }
+ } else {
+ r = hvc_put_chars(vtermnos[co->index], c, i);
+ if (r < 0) {
+ /* throw away chars on error */
+ i = 0;
+ } else if (r > 0) {
+ i -= r;
+ if (i > 0)
+ memmove(c, c+r, i);
+ }
+ }
+ }
+}
+
+static struct tty_driver *hvc_console_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return hvc_driver;
+}
+
+static int __init hvc_console_setup(struct console *co, char *options)
+{
+ return 0;
+}
+
+struct console hvc_con_driver = {
+ .name = "hvc",
+ .write = hvc_console_print,
+ .device = hvc_console_device,
+ .setup = hvc_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+/* Early console initialization. Preceeds driver initialization. */
+static int __init hvc_console_init(void)
+{
+ int i;
+
+ for (i=0; i<MAX_NR_HVC_CONSOLES; i++)
+ vtermnos[i] = -1;
+ num_vterms = hvc_find_vtys();
+ register_console(&hvc_con_driver);
+ return 0;
+}
+console_initcall(hvc_console_init);
+
+module_init(hvc_init);
+module_exit(hvc_exit);
diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c
new file mode 100644
index 000000000000..abfbdcfd4e72
--- /dev/null
+++ b/drivers/char/hvcs.c
@@ -0,0 +1,1649 @@
+/*
+ * IBM eServer Hypervisor Virtual Console Server Device Driver
+ * Copyright (C) 2003, 2004 IBM Corp.
+ * Ryan S. Arnold (rsa@us.ibm.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author(s) : Ryan S. Arnold <rsa@us.ibm.com>
+ *
+ * This is the device driver for the IBM Hypervisor Virtual Console Server,
+ * "hvcs". The IBM hvcs provides a tty driver interface to allow Linux
+ * user space applications access to the system consoles of logically
+ * partitioned operating systems, e.g. Linux, running on the same partitioned
+ * Power5 ppc64 system. Physical hardware consoles per partition are not
+ * practical on this hardware so system consoles are accessed by this driver
+ * using inter-partition firmware interfaces to virtual terminal devices.
+ *
+ * A vty is known to the HMC as a "virtual serial server adapter". It is a
+ * virtual terminal device that is created by firmware upon partition creation
+ * to act as a partitioned OS's console device.
+ *
+ * Firmware dynamically (via hotplug) exposes vty-servers to a running ppc64
+ * Linux system upon their creation by the HMC or their exposure during boot.
+ * The non-user interactive backend of this driver is implemented as a vio
+ * device driver so that it can receive notification of vty-server lifetimes
+ * after it registers with the vio bus to handle vty-server probe and remove
+ * callbacks.
+ *
+ * Many vty-servers can be configured to connect to one vty, but a vty can
+ * only be actively connected to by a single vty-server, in any manner, at one
+ * time. If the HMC is currently hosting the console for a target Linux
+ * partition; attempts to open the tty device to the partition's console using
+ * the hvcs on any partition will return -EBUSY with every open attempt until
+ * the HMC frees the connection between its vty-server and the desired
+ * partition's vty device. Conversely, a vty-server may only be connected to
+ * a single vty at one time even though it may have several configured vty
+ * partner possibilities.
+ *
+ * Firmware does not provide notification of vty partner changes to this
+ * driver. This means that an HMC Super Admin may add or remove partner vtys
+ * from a vty-server's partner list but the changes will not be signaled to
+ * the vty-server. Firmware only notifies the driver when a vty-server is
+ * added or removed from the system. To compensate for this deficiency, this
+ * driver implements a sysfs update attribute which provides a method for
+ * rescanning partner information upon a user's request.
+ *
+ * Each vty-server, prior to being exposed to this driver is reference counted
+ * using the 2.6 Linux kernel kobject construct. This kobject is also used by
+ * the vio bus to provide a vio device sysfs entry that this driver attaches
+ * device specific attributes to, including partner information. The vio bus
+ * framework also provides a sysfs entry for each vio driver. The hvcs driver
+ * provides driver attributes in this entry.
+ *
+ * For direction on installation and usage of this driver please reference
+ * Documentation/powerpc/hvcs.txt.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <asm/hvconsole.h>
+#include <asm/hvcserver.h>
+#include <asm/uaccess.h>
+#include <asm/vio.h>
+
+/*
+ * 1.3.0 -> 1.3.1 In hvcs_open memset(..,0x00,..) instead of memset(..,0x3F,00).
+ * Removed braces around single statements following conditionals. Removed '=
+ * 0' after static int declarations since these default to zero. Removed
+ * list_for_each_safe() and replaced with list_for_each_entry() in
+ * hvcs_get_by_index(). The 'safe' version is un-needed now that the driver is
+ * using spinlocks. Changed spin_lock_irqsave() to spin_lock() when locking
+ * hvcs_structs_lock and hvcs_pi_lock since these are not touched in an int
+ * handler. Initialized hvcs_structs_lock and hvcs_pi_lock to
+ * SPIN_LOCK_UNLOCKED at declaration time rather than in hvcs_module_init().
+ * Added spin_lock around list_del() in destroy_hvcs_struct() to protect the
+ * list traversals from a deletion. Removed '= NULL' from pointer declaration
+ * statements since they are initialized NULL by default. Removed wmb()
+ * instances from hvcs_try_write(). They probably aren't needed with locking in
+ * place. Added check and cleanup for hvcs_pi_buff = kmalloc() in
+ * hvcs_module_init(). Exposed hvcs_struct.index via a sysfs attribute so that
+ * the coupling between /dev/hvcs* and a vty-server can be automatically
+ * determined. Moved kobject_put() in hvcs_open outside of the
+ * spin_unlock_irqrestore().
+ *
+ * 1.3.1 -> 1.3.2 Changed method for determining hvcs_struct->index and had it
+ * align with how the tty layer always assigns the lowest index available. This
+ * change resulted in a list of ints that denotes which indexes are available.
+ * Device additions and removals use the new hvcs_get_index() and
+ * hvcs_return_index() helper functions. The list is created with
+ * hvsc_alloc_index_list() and it is destroyed with hvcs_free_index_list().
+ * Without these fixes hotplug vty-server adapter support goes crazy with this
+ * driver if the user removes a vty-server adapter. Moved free_irq() outside of
+ * the hvcs_final_close() function in order to get it out of the spinlock.
+ * Rearranged hvcs_close(). Cleaned up some printks and did some housekeeping
+ * on the changelog. Removed local CLC_LENGTH and used HVCS_CLC_LENGTH from
+ * arch/ppc64/hvcserver.h.
+ *
+ * 1.3.2 -> 1.3.3 Replaced yield() in hvcs_close() with tty_wait_until_sent() to
+ * prevent possible lockup with realtime scheduling as similarily pointed out by
+ * akpm in hvc_console. Changed resulted in the removal of hvcs_final_close()
+ * to reorder cleanup operations and prevent discarding of pending data during
+ * an hvcs_close(). Removed spinlock protection of hvcs_struct data members in
+ * hvcs_write_room() and hvcs_chars_in_buffer() because they aren't needed.
+ */
+
+#define HVCS_DRIVER_VERSION "1.3.3"
+
+MODULE_AUTHOR("Ryan S. Arnold <rsa@us.ibm.com>");
+MODULE_DESCRIPTION("IBM hvcs (Hypervisor Virtual Console Server) Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(HVCS_DRIVER_VERSION);
+
+/*
+ * Wait this long per iteration while trying to push buffered data to the
+ * hypervisor before allowing the tty to complete a close operation.
+ */
+#define HVCS_CLOSE_WAIT (HZ/100) /* 1/10 of a second */
+
+/*
+ * Since the Linux TTY code does not currently (2-04-2004) support dynamic
+ * addition of tty derived devices and we shouldn't allocate thousands of
+ * tty_device pointers when the number of vty-server & vty partner connections
+ * will most often be much lower than this, we'll arbitrarily allocate
+ * HVCS_DEFAULT_SERVER_ADAPTERS tty_structs and cdev's by default when we
+ * register the tty_driver. This can be overridden using an insmod parameter.
+ */
+#define HVCS_DEFAULT_SERVER_ADAPTERS 64
+
+/*
+ * The user can't insmod with more than HVCS_MAX_SERVER_ADAPTERS hvcs device
+ * nodes as a sanity check. Theoretically there can be over 1 Billion
+ * vty-server & vty partner connections.
+ */
+#define HVCS_MAX_SERVER_ADAPTERS 1024
+
+/*
+ * We let Linux assign us a major number and we start the minors at zero. There
+ * is no intuitive mapping between minor number and the target vty-server
+ * adapter except that each new vty-server adapter is always assigned to the
+ * smallest minor number available.
+ */
+#define HVCS_MINOR_START 0
+
+/*
+ * The hcall interface involves putting 8 chars into each of two registers.
+ * We load up those 2 registers (in arch/ppc64/hvconsole.c) by casting char[16]
+ * to long[2]. It would work without __ALIGNED__, but a little (tiny) bit
+ * slower because an unaligned load is slower than aligned load.
+ */
+#define __ALIGNED__ __attribute__((__aligned__(8)))
+
+/*
+ * How much data can firmware send with each hvc_put_chars()? Maybe this
+ * should be moved into an architecture specific area.
+ */
+#define HVCS_BUFF_LEN 16
+
+/*
+ * This is the maximum amount of data we'll let the user send us (hvcs_write) at
+ * once in a chunk as a sanity check.
+ */
+#define HVCS_MAX_FROM_USER 4096
+
+/*
+ * Be careful when adding flags to this line discipline. Don't add anything
+ * that will cause echoing or we'll go into recursive loop echoing chars back
+ * and forth with the console drivers.
+ */
+static struct termios hvcs_tty_termios = {
+ .c_iflag = IGNBRK | IGNPAR,
+ .c_oflag = OPOST,
+ .c_cflag = B38400 | CS8 | CREAD | HUPCL,
+ .c_cc = INIT_C_CC
+};
+
+/*
+ * This value is used to take the place of a command line parameter when the
+ * module is inserted. It starts as -1 and stays as such if the user doesn't
+ * specify a module insmod parameter. If they DO specify one then it is set to
+ * the value of the integer passed in.
+ */
+static int hvcs_parm_num_devs = -1;
+module_param(hvcs_parm_num_devs, int, 0);
+
+char hvcs_driver_name[] = "hvcs";
+char hvcs_device_node[] = "hvcs";
+char hvcs_driver_string[]
+ = "IBM hvcs (Hypervisor Virtual Console Server) Driver";
+
+/* Status of partner info rescan triggered via sysfs. */
+static int hvcs_rescan_status;
+
+static struct tty_driver *hvcs_tty_driver;
+
+/*
+ * In order to be somewhat sane this driver always associates the hvcs_struct
+ * index element with the numerically equal tty->index. This means that a
+ * hotplugged vty-server adapter will always map to the lowest index valued
+ * device node. If vty-servers were hotplug removed from the system and then
+ * new ones added the new vty-server may have the largest slot number of all
+ * the vty-server adapters in the partition but it may have the lowest dev node
+ * index of all the adapters due to the hole left by the hotplug removed
+ * adapter. There are a set of functions provided to get the lowest index for
+ * a new device as well as return the index to the list. This list is allocated
+ * with a number of elements equal to the number of device nodes requested when
+ * the module was inserted.
+ */
+static int *hvcs_index_list;
+
+/*
+ * How large is the list? This is kept for traversal since the list is
+ * dynamically created.
+ */
+static int hvcs_index_count;
+
+/*
+ * Used by the khvcsd to pick up I/O operations when the kernel_thread is
+ * already awake but potentially shifted to TASK_INTERRUPTIBLE state.
+ */
+static int hvcs_kicked;
+
+/*
+ * Use by the kthread construct for task operations like waking the sleeping
+ * thread and stopping the kthread.
+ */
+static struct task_struct *hvcs_task;
+
+/*
+ * We allocate this for the use of all of the hvcs_structs when they fetch
+ * partner info.
+ */
+static unsigned long *hvcs_pi_buff;
+
+/* Only allow one hvcs_struct to use the hvcs_pi_buff at a time. */
+static DEFINE_SPINLOCK(hvcs_pi_lock);
+
+/* One vty-server per hvcs_struct */
+struct hvcs_struct {
+ spinlock_t lock;
+
+ /*
+ * This index identifies this hvcs device as the complement to a
+ * specific tty index.
+ */
+ unsigned int index;
+
+ struct tty_struct *tty;
+ unsigned int open_count;
+
+ /*
+ * Used to tell the driver kernel_thread what operations need to take
+ * place upon this hvcs_struct instance.
+ */
+ int todo_mask;
+
+ /*
+ * This buffer is required so that when hvcs_write_room() reports that
+ * it can send HVCS_BUFF_LEN characters that it will buffer the full
+ * HVCS_BUFF_LEN characters if need be. This is essential for opost
+ * writes since they do not do high level buffering and expect to be
+ * able to send what the driver commits to sending buffering
+ * [e.g. tab to space conversions in n_tty.c opost()].
+ */
+ char buffer[HVCS_BUFF_LEN];
+ int chars_in_buffer;
+
+ /*
+ * Any variable below the kobject is valid before a tty is connected and
+ * stays valid after the tty is disconnected. These shouldn't be
+ * whacked until the koject refcount reaches zero though some entries
+ * may be changed via sysfs initiatives.
+ */
+ struct kobject kobj; /* ref count & hvcs_struct lifetime */
+ int connected; /* is the vty-server currently connected to a vty? */
+ uint32_t p_unit_address; /* partner unit address */
+ uint32_t p_partition_ID; /* partner partition ID */
+ char p_location_code[HVCS_CLC_LENGTH + 1]; /* CLC + Null Term */
+ struct list_head next; /* list management */
+ struct vio_dev *vdev;
+};
+
+/* Required to back map a kobject to its containing object */
+#define from_kobj(kobj) container_of(kobj, struct hvcs_struct, kobj)
+
+static struct list_head hvcs_structs = LIST_HEAD_INIT(hvcs_structs);
+static DEFINE_SPINLOCK(hvcs_structs_lock);
+
+static void hvcs_unthrottle(struct tty_struct *tty);
+static void hvcs_throttle(struct tty_struct *tty);
+static irqreturn_t hvcs_handle_interrupt(int irq, void *dev_instance,
+ struct pt_regs *regs);
+
+static int hvcs_write(struct tty_struct *tty,
+ const unsigned char *buf, int count);
+static int hvcs_write_room(struct tty_struct *tty);
+static int hvcs_chars_in_buffer(struct tty_struct *tty);
+
+static int hvcs_has_pi(struct hvcs_struct *hvcsd);
+static void hvcs_set_pi(struct hvcs_partner_info *pi,
+ struct hvcs_struct *hvcsd);
+static int hvcs_get_pi(struct hvcs_struct *hvcsd);
+static int hvcs_rescan_devices_list(void);
+
+static int hvcs_partner_connect(struct hvcs_struct *hvcsd);
+static void hvcs_partner_free(struct hvcs_struct *hvcsd);
+
+static int hvcs_enable_device(struct hvcs_struct *hvcsd,
+ uint32_t unit_address, unsigned int irq, struct vio_dev *dev);
+
+static void destroy_hvcs_struct(struct kobject *kobj);
+static int hvcs_open(struct tty_struct *tty, struct file *filp);
+static void hvcs_close(struct tty_struct *tty, struct file *filp);
+static void hvcs_hangup(struct tty_struct * tty);
+
+static void hvcs_create_device_attrs(struct hvcs_struct *hvcsd);
+static void hvcs_remove_device_attrs(struct vio_dev *vdev);
+static void hvcs_create_driver_attrs(void);
+static void hvcs_remove_driver_attrs(void);
+
+static int __devinit hvcs_probe(struct vio_dev *dev,
+ const struct vio_device_id *id);
+static int __devexit hvcs_remove(struct vio_dev *dev);
+static int __init hvcs_module_init(void);
+static void __exit hvcs_module_exit(void);
+
+#define HVCS_SCHED_READ 0x00000001
+#define HVCS_QUICK_READ 0x00000002
+#define HVCS_TRY_WRITE 0x00000004
+#define HVCS_READ_MASK (HVCS_SCHED_READ | HVCS_QUICK_READ)
+
+static void hvcs_kick(void)
+{
+ hvcs_kicked = 1;
+ wmb();
+ wake_up_process(hvcs_task);
+}
+
+static void hvcs_unthrottle(struct tty_struct *tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ hvcsd->todo_mask |= HVCS_SCHED_READ;
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ hvcs_kick();
+}
+
+static void hvcs_throttle(struct tty_struct *tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ vio_disable_interrupts(hvcsd->vdev);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+}
+
+/*
+ * If the device is being removed we don't have to worry about this interrupt
+ * handler taking any further interrupts because they are disabled which means
+ * the hvcs_struct will always be valid in this handler.
+ */
+static irqreturn_t hvcs_handle_interrupt(int irq, void *dev_instance,
+ struct pt_regs *regs)
+{
+ struct hvcs_struct *hvcsd = dev_instance;
+
+ spin_lock(&hvcsd->lock);
+ vio_disable_interrupts(hvcsd->vdev);
+ hvcsd->todo_mask |= HVCS_SCHED_READ;
+ spin_unlock(&hvcsd->lock);
+ hvcs_kick();
+
+ return IRQ_HANDLED;
+}
+
+/* This function must be called with the hvcsd->lock held */
+static void hvcs_try_write(struct hvcs_struct *hvcsd)
+{
+ uint32_t unit_address = hvcsd->vdev->unit_address;
+ struct tty_struct *tty = hvcsd->tty;
+ int sent;
+
+ if (hvcsd->todo_mask & HVCS_TRY_WRITE) {
+ /* won't send partial writes */
+ sent = hvc_put_chars(unit_address,
+ &hvcsd->buffer[0],
+ hvcsd->chars_in_buffer );
+ if (sent > 0) {
+ hvcsd->chars_in_buffer = 0;
+ /* wmb(); */
+ hvcsd->todo_mask &= ~(HVCS_TRY_WRITE);
+ /* wmb(); */
+
+ /*
+ * We are still obligated to deliver the data to the
+ * hypervisor even if the tty has been closed because
+ * we commited to delivering it. But don't try to wake
+ * a non-existent tty.
+ */
+ if (tty) {
+ tty_wakeup(tty);
+ }
+ }
+ }
+}
+
+static int hvcs_io(struct hvcs_struct *hvcsd)
+{
+ uint32_t unit_address;
+ struct tty_struct *tty;
+ char buf[HVCS_BUFF_LEN] __ALIGNED__;
+ unsigned long flags;
+ int got = 0;
+ int i;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ unit_address = hvcsd->vdev->unit_address;
+ tty = hvcsd->tty;
+
+ hvcs_try_write(hvcsd);
+
+ if (!tty || test_bit(TTY_THROTTLED, &tty->flags)) {
+ hvcsd->todo_mask &= ~(HVCS_READ_MASK);
+ goto bail;
+ } else if (!(hvcsd->todo_mask & (HVCS_READ_MASK)))
+ goto bail;
+
+ /* remove the read masks */
+ hvcsd->todo_mask &= ~(HVCS_READ_MASK);
+
+ if ((tty->flip.count + HVCS_BUFF_LEN) < TTY_FLIPBUF_SIZE) {
+ got = hvc_get_chars(unit_address,
+ &buf[0],
+ HVCS_BUFF_LEN);
+ for (i=0;got && i<got;i++)
+ tty_insert_flip_char(tty, buf[i], TTY_NORMAL);
+ }
+
+ /* Give the TTY time to process the data we just sent. */
+ if (got)
+ hvcsd->todo_mask |= HVCS_QUICK_READ;
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ if (tty->flip.count) {
+ /* This is synch because tty->low_latency == 1 */
+ tty_flip_buffer_push(tty);
+ }
+
+ if (!got) {
+ /* Do this _after_ the flip_buffer_push */
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ vio_enable_interrupts(hvcsd->vdev);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ }
+
+ return hvcsd->todo_mask;
+
+ bail:
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return hvcsd->todo_mask;
+}
+
+static int khvcsd(void *unused)
+{
+ struct hvcs_struct *hvcsd;
+ int hvcs_todo_mask;
+
+ __set_current_state(TASK_RUNNING);
+
+ do {
+ hvcs_todo_mask = 0;
+ hvcs_kicked = 0;
+ wmb();
+
+ spin_lock(&hvcs_structs_lock);
+ list_for_each_entry(hvcsd, &hvcs_structs, next) {
+ hvcs_todo_mask |= hvcs_io(hvcsd);
+ }
+ spin_unlock(&hvcs_structs_lock);
+
+ /*
+ * If any of the hvcs adapters want to try a write or quick read
+ * don't schedule(), yield a smidgen then execute the hvcs_io
+ * thread again for those that want the write.
+ */
+ if (hvcs_todo_mask & (HVCS_TRY_WRITE | HVCS_QUICK_READ)) {
+ yield();
+ continue;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!hvcs_kicked)
+ schedule();
+ __set_current_state(TASK_RUNNING);
+ } while (!kthread_should_stop());
+
+ return 0;
+}
+
+static struct vio_device_id hvcs_driver_table[] __devinitdata= {
+ {"serial-server", "hvterm2"},
+ { NULL, }
+};
+MODULE_DEVICE_TABLE(vio, hvcs_driver_table);
+
+static void hvcs_return_index(int index)
+{
+ /* Paranoia check */
+ if (!hvcs_index_list)
+ return;
+ if (index < 0 || index >= hvcs_index_count)
+ return;
+ if (hvcs_index_list[index] == -1)
+ return;
+ else
+ hvcs_index_list[index] = -1;
+}
+
+/* callback when the kboject ref count reaches zero */
+static void destroy_hvcs_struct(struct kobject *kobj)
+{
+ struct hvcs_struct *hvcsd = from_kobj(kobj);
+ struct vio_dev *vdev;
+ unsigned long flags;
+
+ spin_lock(&hvcs_structs_lock);
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ /* the list_del poisons the pointers */
+ list_del(&(hvcsd->next));
+
+ if (hvcsd->connected == 1) {
+ hvcs_partner_free(hvcsd);
+ printk(KERN_INFO "HVCS: Closed vty-server@%X and"
+ " partner vty@%X:%d connection.\n",
+ hvcsd->vdev->unit_address,
+ hvcsd->p_unit_address,
+ (uint32_t)hvcsd->p_partition_ID);
+ }
+ printk(KERN_INFO "HVCS: Destroyed hvcs_struct for vty-server@%X.\n",
+ hvcsd->vdev->unit_address);
+
+ vdev = hvcsd->vdev;
+ hvcsd->vdev = NULL;
+
+ hvcsd->p_unit_address = 0;
+ hvcsd->p_partition_ID = 0;
+ hvcs_return_index(hvcsd->index);
+ memset(&hvcsd->p_location_code[0], 0x00, HVCS_CLC_LENGTH + 1);
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ spin_unlock(&hvcs_structs_lock);
+
+ hvcs_remove_device_attrs(vdev);
+
+ kfree(hvcsd);
+}
+
+static struct kobj_type hvcs_kobj_type = {
+ .release = destroy_hvcs_struct,
+};
+
+static int hvcs_get_index(void)
+{
+ int i;
+ /* Paranoia check */
+ if (!hvcs_index_list) {
+ printk(KERN_ERR "HVCS: hvcs_index_list NOT valid!.\n");
+ return -EFAULT;
+ }
+ /* Find the numerically lowest first free index. */
+ for(i = 0; i < hvcs_index_count; i++) {
+ if (hvcs_index_list[i] == -1) {
+ hvcs_index_list[i] = 0;
+ return i;
+ }
+ }
+ return -1;
+}
+
+static int __devinit hvcs_probe(
+ struct vio_dev *dev,
+ const struct vio_device_id *id)
+{
+ struct hvcs_struct *hvcsd;
+ int index;
+
+ if (!dev || !id) {
+ printk(KERN_ERR "HVCS: probed with invalid parameter.\n");
+ return -EPERM;
+ }
+
+ /* early to avoid cleanup on failure */
+ index = hvcs_get_index();
+ if (index < 0) {
+ return -EFAULT;
+ }
+
+ hvcsd = kmalloc(sizeof(*hvcsd), GFP_KERNEL);
+ if (!hvcsd)
+ return -ENODEV;
+
+ /* hvcsd->tty is zeroed out with the memset */
+ memset(hvcsd, 0x00, sizeof(*hvcsd));
+
+ spin_lock_init(&hvcsd->lock);
+ /* Automatically incs the refcount the first time */
+ kobject_init(&hvcsd->kobj);
+ /* Set up the callback for terminating the hvcs_struct's life */
+ hvcsd->kobj.ktype = &hvcs_kobj_type;
+
+ hvcsd->vdev = dev;
+ dev->dev.driver_data = hvcsd;
+
+ hvcsd->index = index;
+
+ /* hvcsd->index = ++hvcs_struct_count; */
+ hvcsd->chars_in_buffer = 0;
+ hvcsd->todo_mask = 0;
+ hvcsd->connected = 0;
+
+ /*
+ * This will populate the hvcs_struct's partner info fields for the
+ * first time.
+ */
+ if (hvcs_get_pi(hvcsd)) {
+ printk(KERN_ERR "HVCS: Failed to fetch partner"
+ " info for vty-server@%X on device probe.\n",
+ hvcsd->vdev->unit_address);
+ }
+
+ /*
+ * If a user app opens a tty that corresponds to this vty-server before
+ * the hvcs_struct has been added to the devices list then the user app
+ * will get -ENODEV.
+ */
+
+ spin_lock(&hvcs_structs_lock);
+
+ list_add_tail(&(hvcsd->next), &hvcs_structs);
+
+ spin_unlock(&hvcs_structs_lock);
+
+ hvcs_create_device_attrs(hvcsd);
+
+ printk(KERN_INFO "HVCS: vty-server@%X added to the vio bus.\n", dev->unit_address);
+
+ /*
+ * DON'T enable interrupts here because there is no user to receive the
+ * data.
+ */
+ return 0;
+}
+
+static int __devexit hvcs_remove(struct vio_dev *dev)
+{
+ struct hvcs_struct *hvcsd = dev->dev.driver_data;
+ unsigned long flags;
+ struct kobject *kobjp;
+ struct tty_struct *tty;
+
+ if (!hvcsd)
+ return -ENODEV;
+
+ /* By this time the vty-server won't be getting any more interrups */
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ tty = hvcsd->tty;
+
+ kobjp = &hvcsd->kobj;
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ /*
+ * Let the last holder of this object cause it to be removed, which
+ * would probably be tty_hangup below.
+ */
+ kobject_put (kobjp);
+
+ /*
+ * The hangup is a scheduled function which will auto chain call
+ * hvcs_hangup. The tty should always be valid at this time unless a
+ * simultaneous tty close already cleaned up the hvcs_struct.
+ */
+ if (tty)
+ tty_hangup(tty);
+
+ printk(KERN_INFO "HVCS: vty-server@%X removed from the"
+ " vio bus.\n", dev->unit_address);
+ return 0;
+};
+
+static struct vio_driver hvcs_vio_driver = {
+ .name = hvcs_driver_name,
+ .id_table = hvcs_driver_table,
+ .probe = hvcs_probe,
+ .remove = hvcs_remove,
+};
+
+/* Only called from hvcs_get_pi please */
+static void hvcs_set_pi(struct hvcs_partner_info *pi, struct hvcs_struct *hvcsd)
+{
+ int clclength;
+
+ hvcsd->p_unit_address = pi->unit_address;
+ hvcsd->p_partition_ID = pi->partition_ID;
+ clclength = strlen(&pi->location_code[0]);
+ if (clclength > HVCS_CLC_LENGTH)
+ clclength = HVCS_CLC_LENGTH;
+
+ /* copy the null-term char too */
+ strncpy(&hvcsd->p_location_code[0],
+ &pi->location_code[0], clclength + 1);
+}
+
+/*
+ * Traverse the list and add the partner info that is found to the hvcs_struct
+ * struct entry. NOTE: At this time I know that partner info will return a
+ * single entry but in the future there may be multiple partner info entries per
+ * vty-server and you'll want to zero out that list and reset it. If for some
+ * reason you have an old version of this driver but there IS more than one
+ * partner info then hvcsd->p_* will hold the last partner info data from the
+ * firmware query. A good way to update this code would be to replace the three
+ * partner info fields in hvcs_struct with a list of hvcs_partner_info
+ * instances.
+ *
+ * This function must be called with the hvcsd->lock held.
+ */
+static int hvcs_get_pi(struct hvcs_struct *hvcsd)
+{
+ struct hvcs_partner_info *pi;
+ uint32_t unit_address = hvcsd->vdev->unit_address;
+ struct list_head head;
+ int retval;
+
+ spin_lock(&hvcs_pi_lock);
+ if (!hvcs_pi_buff) {
+ spin_unlock(&hvcs_pi_lock);
+ return -EFAULT;
+ }
+ retval = hvcs_get_partner_info(unit_address, &head, hvcs_pi_buff);
+ spin_unlock(&hvcs_pi_lock);
+ if (retval) {
+ printk(KERN_ERR "HVCS: Failed to fetch partner"
+ " info for vty-server@%x.\n", unit_address);
+ return retval;
+ }
+
+ /* nixes the values if the partner vty went away */
+ hvcsd->p_unit_address = 0;
+ hvcsd->p_partition_ID = 0;
+
+ list_for_each_entry(pi, &head, node)
+ hvcs_set_pi(pi, hvcsd);
+
+ hvcs_free_partner_info(&head);
+ return 0;
+}
+
+/*
+ * This function is executed by the driver "rescan" sysfs entry. It shouldn't
+ * be executed elsewhere, in order to prevent deadlock issues.
+ */
+static int hvcs_rescan_devices_list(void)
+{
+ struct hvcs_struct *hvcsd;
+ unsigned long flags;
+
+ spin_lock(&hvcs_structs_lock);
+
+ list_for_each_entry(hvcsd, &hvcs_structs, next) {
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ hvcs_get_pi(hvcsd);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ }
+
+ spin_unlock(&hvcs_structs_lock);
+
+ return 0;
+}
+
+/*
+ * Farm this off into its own function because it could be more complex once
+ * multiple partners support is added. This function should be called with
+ * the hvcsd->lock held.
+ */
+static int hvcs_has_pi(struct hvcs_struct *hvcsd)
+{
+ if ((!hvcsd->p_unit_address) || (!hvcsd->p_partition_ID))
+ return 0;
+ return 1;
+}
+
+/*
+ * NOTE: It is possible that the super admin removed a partner vty and then
+ * added a different vty as the new partner.
+ *
+ * This function must be called with the hvcsd->lock held.
+ */
+static int hvcs_partner_connect(struct hvcs_struct *hvcsd)
+{
+ int retval;
+ unsigned int unit_address = hvcsd->vdev->unit_address;
+
+ /*
+ * If there wasn't any pi when the device was added it doesn't meant
+ * there isn't any now. This driver isn't notified when a new partner
+ * vty is added to a vty-server so we discover changes on our own.
+ * Please see comments in hvcs_register_connection() for justification
+ * of this bizarre code.
+ */
+ retval = hvcs_register_connection(unit_address,
+ hvcsd->p_partition_ID,
+ hvcsd->p_unit_address);
+ if (!retval) {
+ hvcsd->connected = 1;
+ return 0;
+ } else if (retval != -EINVAL)
+ return retval;
+
+ /*
+ * As per the spec re-get the pi and try again if -EINVAL after the
+ * first connection attempt.
+ */
+ if (hvcs_get_pi(hvcsd))
+ return -ENOMEM;
+
+ if (!hvcs_has_pi(hvcsd))
+ return -ENODEV;
+
+ retval = hvcs_register_connection(unit_address,
+ hvcsd->p_partition_ID,
+ hvcsd->p_unit_address);
+ if (retval != -EINVAL) {
+ hvcsd->connected = 1;
+ return retval;
+ }
+
+ /*
+ * EBUSY is the most likely scenario though the vty could have been
+ * removed or there really could be an hcall error due to the parameter
+ * data but thanks to ambiguous firmware return codes we can't really
+ * tell.
+ */
+ printk(KERN_INFO "HVCS: vty-server or partner"
+ " vty is busy. Try again later.\n");
+ return -EBUSY;
+}
+
+/* This function must be called with the hvcsd->lock held */
+static void hvcs_partner_free(struct hvcs_struct *hvcsd)
+{
+ int retval;
+ do {
+ retval = hvcs_free_connection(hvcsd->vdev->unit_address);
+ } while (retval == -EBUSY);
+ hvcsd->connected = 0;
+}
+
+/* This helper function must be called WITHOUT the hvcsd->lock held */
+static int hvcs_enable_device(struct hvcs_struct *hvcsd, uint32_t unit_address,
+ unsigned int irq, struct vio_dev *vdev)
+{
+ unsigned long flags;
+ int rc;
+
+ /*
+ * It is possible that the vty-server was removed between the time that
+ * the conn was registered and now.
+ */
+ if (!(rc = request_irq(irq, &hvcs_handle_interrupt,
+ SA_INTERRUPT, "ibmhvcs", hvcsd))) {
+ /*
+ * It is possible the vty-server was removed after the irq was
+ * requested but before we have time to enable interrupts.
+ */
+ if (vio_enable_interrupts(vdev) == H_Success)
+ return 0;
+ else {
+ printk(KERN_ERR "HVCS: int enable failed for"
+ " vty-server@%X.\n", unit_address);
+ free_irq(irq, hvcsd);
+ }
+ } else
+ printk(KERN_ERR "HVCS: irq req failed for"
+ " vty-server@%X.\n", unit_address);
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ hvcs_partner_free(hvcsd);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ return rc;
+
+}
+
+/*
+ * This always increments the kobject ref count if the call is successful.
+ * Please remember to dec when you are done with the instance.
+ *
+ * NOTICE: Do NOT hold either the hvcs_struct.lock or hvcs_structs_lock when
+ * calling this function or you will get deadlock.
+ */
+struct hvcs_struct *hvcs_get_by_index(int index)
+{
+ struct hvcs_struct *hvcsd = NULL;
+ unsigned long flags;
+
+ spin_lock(&hvcs_structs_lock);
+ /* We can immediately discard OOB requests */
+ if (index >= 0 && index < HVCS_MAX_SERVER_ADAPTERS) {
+ list_for_each_entry(hvcsd, &hvcs_structs, next) {
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ if (hvcsd->index == index) {
+ kobject_get(&hvcsd->kobj);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ spin_unlock(&hvcs_structs_lock);
+ return hvcsd;
+ }
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ }
+ hvcsd = NULL;
+ }
+
+ spin_unlock(&hvcs_structs_lock);
+ return hvcsd;
+}
+
+/*
+ * This is invoked via the tty_open interface when a user app connects to the
+ * /dev node.
+ */
+static int hvcs_open(struct tty_struct *tty, struct file *filp)
+{
+ struct hvcs_struct *hvcsd;
+ int rc, retval = 0;
+ unsigned long flags;
+ unsigned int irq;
+ struct vio_dev *vdev;
+ unsigned long unit_address;
+ struct kobject *kobjp;
+
+ if (tty->driver_data)
+ goto fast_open;
+
+ /*
+ * Is there a vty-server that shares the same index?
+ * This function increments the kobject index.
+ */
+ if (!(hvcsd = hvcs_get_by_index(tty->index))) {
+ printk(KERN_WARNING "HVCS: open failed, no device associated"
+ " with tty->index %d.\n", tty->index);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ if (hvcsd->connected == 0)
+ if ((retval = hvcs_partner_connect(hvcsd)))
+ goto error_release;
+
+ hvcsd->open_count = 1;
+ hvcsd->tty = tty;
+ tty->driver_data = hvcsd;
+
+ /*
+ * Set this driver to low latency so that we actually have a chance at
+ * catching a throttled TTY after we flip_buffer_push. Otherwise the
+ * flush_to_async may not execute until after the kernel_thread has
+ * yielded and resumed the next flip_buffer_push resulting in data
+ * loss.
+ */
+ tty->low_latency = 1;
+
+ memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);
+
+ /*
+ * Save these in the spinlock for the enable operations that need them
+ * outside of the spinlock.
+ */
+ irq = hvcsd->vdev->irq;
+ vdev = hvcsd->vdev;
+ unit_address = hvcsd->vdev->unit_address;
+
+ hvcsd->todo_mask |= HVCS_SCHED_READ;
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ /*
+ * This must be done outside of the spinlock because it requests irqs
+ * and will grab the spinlock and free the connection if it fails.
+ */
+ if (((rc = hvcs_enable_device(hvcsd, unit_address, irq, vdev)))) {
+ kobject_put(&hvcsd->kobj);
+ printk(KERN_WARNING "HVCS: enable device failed.\n");
+ return rc;
+ }
+
+ goto open_success;
+
+fast_open:
+ hvcsd = tty->driver_data;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ if (!kobject_get(&hvcsd->kobj)) {
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ printk(KERN_ERR "HVCS: Kobject of open"
+ " hvcs doesn't exist.\n");
+ return -EFAULT; /* Is this the right return value? */
+ }
+
+ hvcsd->open_count++;
+
+ hvcsd->todo_mask |= HVCS_SCHED_READ;
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+open_success:
+ hvcs_kick();
+
+ printk(KERN_INFO "HVCS: vty-server@%X connection opened.\n",
+ hvcsd->vdev->unit_address );
+
+ return 0;
+
+error_release:
+ kobjp = &hvcsd->kobj;
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ kobject_put(&hvcsd->kobj);
+
+ printk(KERN_WARNING "HVCS: partner connect failed.\n");
+ return retval;
+}
+
+static void hvcs_close(struct tty_struct *tty, struct file *filp)
+{
+ struct hvcs_struct *hvcsd;
+ unsigned long flags;
+ struct kobject *kobjp;
+ int irq = NO_IRQ;
+
+ /*
+ * Is someone trying to close the file associated with this device after
+ * we have hung up? If so tty->driver_data wouldn't be valid.
+ */
+ if (tty_hung_up_p(filp))
+ return;
+
+ /*
+ * No driver_data means that this close was probably issued after a
+ * failed hvcs_open by the tty layer's release_dev() api and we can just
+ * exit cleanly.
+ */
+ if (!tty->driver_data)
+ return;
+
+ hvcsd = tty->driver_data;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ kobjp = &hvcsd->kobj;
+ if (--hvcsd->open_count == 0) {
+
+ vio_disable_interrupts(hvcsd->vdev);
+
+ /*
+ * NULL this early so that the kernel_thread doesn't try to
+ * execute any operations on the TTY even though it is obligated
+ * to deliver any pending I/O to the hypervisor.
+ */
+ hvcsd->tty = NULL;
+
+ irq = hvcsd->vdev->irq;
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ tty_wait_until_sent(tty, HVCS_CLOSE_WAIT);
+
+ /*
+ * This line is important because it tells hvcs_open that this
+ * device needs to be re-configured the next time hvcs_open is
+ * called.
+ */
+ tty->driver_data = NULL;
+
+ free_irq(irq, hvcsd);
+ kobject_put(kobjp);
+ return;
+ } else if (hvcsd->open_count < 0) {
+ printk(KERN_ERR "HVCS: vty-server@%X open_count: %d"
+ " is missmanaged.\n",
+ hvcsd->vdev->unit_address, hvcsd->open_count);
+ }
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ kobject_put(kobjp);
+}
+
+static void hvcs_hangup(struct tty_struct * tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+ unsigned long flags;
+ int temp_open_count;
+ struct kobject *kobjp;
+ int irq = NO_IRQ;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ /* Preserve this so that we know how many kobject refs to put */
+ temp_open_count = hvcsd->open_count;
+
+ /*
+ * Don't kobject put inside the spinlock because the destruction
+ * callback may use the spinlock and it may get called before the
+ * spinlock has been released. Get a pointer to the kobject and
+ * kobject_put on that after releasing the spinlock.
+ */
+ kobjp = &hvcsd->kobj;
+
+ vio_disable_interrupts(hvcsd->vdev);
+
+ hvcsd->todo_mask = 0;
+
+ /* I don't think the tty needs the hvcs_struct pointer after a hangup */
+ hvcsd->tty->driver_data = NULL;
+ hvcsd->tty = NULL;
+
+ hvcsd->open_count = 0;
+
+ /* This will drop any buffered data on the floor which is OK in a hangup
+ * scenario. */
+ memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);
+ hvcsd->chars_in_buffer = 0;
+
+ irq = hvcsd->vdev->irq;
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ free_irq(irq, hvcsd);
+
+ /*
+ * We need to kobject_put() for every open_count we have since the
+ * tty_hangup() function doesn't invoke a close per open connection on a
+ * non-console device.
+ */
+ while(temp_open_count) {
+ --temp_open_count;
+ /*
+ * The final put will trigger destruction of the hvcs_struct.
+ * NOTE: If this hangup was signaled from user space then the
+ * final put will never happen.
+ */
+ kobject_put(kobjp);
+ }
+}
+
+/*
+ * NOTE: This is almost always from_user since user level apps interact with the
+ * /dev nodes. I'm trusting that if hvcs_write gets called and interrupted by
+ * hvcs_remove (which removes the target device and executes tty_hangup()) that
+ * tty_hangup will allow hvcs_write time to complete execution before it
+ * terminates our device.
+ */
+static int hvcs_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+ unsigned int unit_address;
+ const unsigned char *charbuf;
+ unsigned long flags;
+ int total_sent = 0;
+ int tosend = 0;
+ int result = 0;
+
+ /*
+ * If they don't check the return code off of their open they may
+ * attempt this even if there is no connected device.
+ */
+ if (!hvcsd)
+ return -ENODEV;
+
+ /* Reasonable size to prevent user level flooding */
+ if (count > HVCS_MAX_FROM_USER) {
+ printk(KERN_WARNING "HVCS write: count being truncated to"
+ " HVCS_MAX_FROM_USER.\n");
+ count = HVCS_MAX_FROM_USER;
+ }
+
+ charbuf = buf;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ /*
+ * Somehow an open succedded but the device was removed or the
+ * connection terminated between the vty-server and partner vty during
+ * the middle of a write operation? This is a crummy place to do this
+ * but we want to keep it all in the spinlock.
+ */
+ if (hvcsd->open_count <= 0) {
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return -ENODEV;
+ }
+
+ unit_address = hvcsd->vdev->unit_address;
+
+ while (count > 0) {
+ tosend = min(count, (HVCS_BUFF_LEN - hvcsd->chars_in_buffer));
+ /*
+ * No more space, this probably means that the last call to
+ * hvcs_write() didn't succeed and the buffer was filled up.
+ */
+ if (!tosend)
+ break;
+
+ memcpy(&hvcsd->buffer[hvcsd->chars_in_buffer],
+ &charbuf[total_sent],
+ tosend);
+
+ hvcsd->chars_in_buffer += tosend;
+
+ result = 0;
+
+ /*
+ * If this is true then we don't want to try writing to the
+ * hypervisor because that is the kernel_threads job now. We'll
+ * just add to the buffer.
+ */
+ if (!(hvcsd->todo_mask & HVCS_TRY_WRITE))
+ /* won't send partial writes */
+ result = hvc_put_chars(unit_address,
+ &hvcsd->buffer[0],
+ hvcsd->chars_in_buffer);
+
+ /*
+ * Since we know we have enough room in hvcsd->buffer for
+ * tosend we record that it was sent regardless of whether the
+ * hypervisor actually took it because we have it buffered.
+ */
+ total_sent+=tosend;
+ count-=tosend;
+ if (result == 0) {
+ hvcsd->todo_mask |= HVCS_TRY_WRITE;
+ hvcs_kick();
+ break;
+ }
+
+ hvcsd->chars_in_buffer = 0;
+ /*
+ * Test after the chars_in_buffer reset otherwise this could
+ * deadlock our writes if hvc_put_chars fails.
+ */
+ if (result < 0)
+ break;
+ }
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+ if (result == -1)
+ return -EIO;
+ else
+ return total_sent;
+}
+
+/*
+ * This is really asking how much can we guarentee that we can send or that we
+ * absolutely WILL BUFFER if we can't send it. This driver MUST honor the
+ * return value, hence the reason for hvcs_struct buffering.
+ */
+static int hvcs_write_room(struct tty_struct *tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+
+ if (!hvcsd || hvcsd->open_count <= 0)
+ return 0;
+
+ return HVCS_BUFF_LEN - hvcsd->chars_in_buffer;
+}
+
+static int hvcs_chars_in_buffer(struct tty_struct *tty)
+{
+ struct hvcs_struct *hvcsd = tty->driver_data;
+
+ return hvcsd->chars_in_buffer;
+}
+
+static struct tty_operations hvcs_ops = {
+ .open = hvcs_open,
+ .close = hvcs_close,
+ .hangup = hvcs_hangup,
+ .write = hvcs_write,
+ .write_room = hvcs_write_room,
+ .chars_in_buffer = hvcs_chars_in_buffer,
+ .unthrottle = hvcs_unthrottle,
+ .throttle = hvcs_throttle,
+};
+
+static int hvcs_alloc_index_list(int n)
+{
+ int i;
+ hvcs_index_list = kmalloc(n * sizeof(hvcs_index_count),GFP_KERNEL);
+ if (!hvcs_index_list)
+ return -ENOMEM;
+ hvcs_index_count = n;
+ for(i = 0; i < hvcs_index_count; i++)
+ hvcs_index_list[i] = -1;
+ return 0;
+}
+
+static void hvcs_free_index_list(void)
+{
+ /* Paranoia check to be thorough. */
+ if (hvcs_index_list) {
+ kfree(hvcs_index_list);
+ hvcs_index_list = NULL;
+ hvcs_index_count = 0;
+ }
+}
+
+static int __init hvcs_module_init(void)
+{
+ int rc;
+ int num_ttys_to_alloc;
+
+ printk(KERN_INFO "Initializing %s\n", hvcs_driver_string);
+
+ /* Has the user specified an overload with an insmod param? */
+ if (hvcs_parm_num_devs <= 0 ||
+ (hvcs_parm_num_devs > HVCS_MAX_SERVER_ADAPTERS)) {
+ num_ttys_to_alloc = HVCS_DEFAULT_SERVER_ADAPTERS;
+ } else
+ num_ttys_to_alloc = hvcs_parm_num_devs;
+
+ hvcs_tty_driver = alloc_tty_driver(num_ttys_to_alloc);
+ if (!hvcs_tty_driver)
+ return -ENOMEM;
+
+ if (hvcs_alloc_index_list(num_ttys_to_alloc))
+ return -ENOMEM;
+
+ hvcs_tty_driver->owner = THIS_MODULE;
+
+ hvcs_tty_driver->driver_name = hvcs_driver_name;
+ hvcs_tty_driver->name = hvcs_device_node;
+ hvcs_tty_driver->devfs_name = hvcs_device_node;
+
+ /*
+ * We'll let the system assign us a major number, indicated by leaving
+ * it blank.
+ */
+
+ hvcs_tty_driver->minor_start = HVCS_MINOR_START;
+ hvcs_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+
+ /*
+ * We role our own so that we DONT ECHO. We can't echo because the
+ * device we are connecting to already echoes by default and this would
+ * throw us into a horrible recursive echo-echo-echo loop.
+ */
+ hvcs_tty_driver->init_termios = hvcs_tty_termios;
+ hvcs_tty_driver->flags = TTY_DRIVER_REAL_RAW;
+
+ tty_set_operations(hvcs_tty_driver, &hvcs_ops);
+
+ /*
+ * The following call will result in sysfs entries that denote the
+ * dynamically assigned major and minor numbers for our devices.
+ */
+ if (tty_register_driver(hvcs_tty_driver)) {
+ printk(KERN_ERR "HVCS: registration "
+ " as a tty driver failed.\n");
+ hvcs_free_index_list();
+ put_tty_driver(hvcs_tty_driver);
+ return -EIO;
+ }
+
+ hvcs_pi_buff = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!hvcs_pi_buff) {
+ tty_unregister_driver(hvcs_tty_driver);
+ hvcs_free_index_list();
+ put_tty_driver(hvcs_tty_driver);
+ return -ENOMEM;
+ }
+
+ hvcs_task = kthread_run(khvcsd, NULL, "khvcsd");
+ if (IS_ERR(hvcs_task)) {
+ printk(KERN_ERR "HVCS: khvcsd creation failed. Driver not loaded.\n");
+ kfree(hvcs_pi_buff);
+ tty_unregister_driver(hvcs_tty_driver);
+ hvcs_free_index_list();
+ put_tty_driver(hvcs_tty_driver);
+ return -EIO;
+ }
+
+ rc = vio_register_driver(&hvcs_vio_driver);
+
+ /*
+ * This needs to be done AFTER the vio_register_driver() call or else
+ * the kobjects won't be initialized properly.
+ */
+ hvcs_create_driver_attrs();
+
+ printk(KERN_INFO "HVCS: driver module inserted.\n");
+
+ return rc;
+}
+
+static void __exit hvcs_module_exit(void)
+{
+ /*
+ * This driver receives hvcs_remove callbacks for each device upon
+ * module removal.
+ */
+
+ /*
+ * This synchronous operation will wake the khvcsd kthread if it is
+ * asleep and will return when khvcsd has terminated.
+ */
+ kthread_stop(hvcs_task);
+
+ spin_lock(&hvcs_pi_lock);
+ kfree(hvcs_pi_buff);
+ hvcs_pi_buff = NULL;
+ spin_unlock(&hvcs_pi_lock);
+
+ hvcs_remove_driver_attrs();
+
+ vio_unregister_driver(&hvcs_vio_driver);
+
+ tty_unregister_driver(hvcs_tty_driver);
+
+ hvcs_free_index_list();
+
+ put_tty_driver(hvcs_tty_driver);
+
+ printk(KERN_INFO "HVCS: driver module removed.\n");
+}
+
+module_init(hvcs_module_init);
+module_exit(hvcs_module_exit);
+
+static inline struct hvcs_struct *from_vio_dev(struct vio_dev *viod)
+{
+ return viod->dev.driver_data;
+}
+/* The sysfs interface for the driver and devices */
+
+static ssize_t hvcs_partner_vtys_show(struct device *dev, char *buf)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ retval = sprintf(buf, "%X\n", hvcsd->p_unit_address);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return retval;
+}
+static DEVICE_ATTR(partner_vtys, S_IRUGO, hvcs_partner_vtys_show, NULL);
+
+static ssize_t hvcs_partner_clcs_show(struct device *dev, char *buf)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ retval = sprintf(buf, "%s\n", &hvcsd->p_location_code[0]);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return retval;
+}
+static DEVICE_ATTR(partner_clcs, S_IRUGO, hvcs_partner_clcs_show, NULL);
+
+static ssize_t hvcs_current_vty_store(struct device *dev, const char * buf,
+ size_t count)
+{
+ /*
+ * Don't need this feature at the present time because firmware doesn't
+ * yet support multiple partners.
+ */
+ printk(KERN_INFO "HVCS: Denied current_vty change: -EPERM.\n");
+ return -EPERM;
+}
+
+static ssize_t hvcs_current_vty_show(struct device *dev, char *buf)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ retval = sprintf(buf, "%s\n", &hvcsd->p_location_code[0]);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return retval;
+}
+
+static DEVICE_ATTR(current_vty,
+ S_IRUGO | S_IWUSR, hvcs_current_vty_show, hvcs_current_vty_store);
+
+static ssize_t hvcs_vterm_state_store(struct device *dev, const char *buf,
+ size_t count)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+
+ /* writing a '0' to this sysfs entry will result in the disconnect. */
+ if (simple_strtol(buf, NULL, 0) != 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+
+ if (hvcsd->open_count > 0) {
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ printk(KERN_INFO "HVCS: vterm state unchanged. "
+ "The hvcs device node is still in use.\n");
+ return -EPERM;
+ }
+
+ if (hvcsd->connected == 0) {
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ printk(KERN_INFO "HVCS: vterm state unchanged. The"
+ " vty-server is not connected to a vty.\n");
+ return -EPERM;
+ }
+
+ hvcs_partner_free(hvcsd);
+ printk(KERN_INFO "HVCS: Closed vty-server@%X and"
+ " partner vty@%X:%d connection.\n",
+ hvcsd->vdev->unit_address,
+ hvcsd->p_unit_address,
+ (uint32_t)hvcsd->p_partition_ID);
+
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return count;
+}
+
+static ssize_t hvcs_vterm_state_show(struct device *dev, char *buf)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ retval = sprintf(buf, "%d\n", hvcsd->connected);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return retval;
+}
+static DEVICE_ATTR(vterm_state, S_IRUGO | S_IWUSR,
+ hvcs_vterm_state_show, hvcs_vterm_state_store);
+
+static ssize_t hvcs_index_show(struct device *dev, char *buf)
+{
+ struct vio_dev *viod = to_vio_dev(dev);
+ struct hvcs_struct *hvcsd = from_vio_dev(viod);
+ unsigned long flags;
+ int retval;
+
+ spin_lock_irqsave(&hvcsd->lock, flags);
+ retval = sprintf(buf, "%d\n", hvcsd->index);
+ spin_unlock_irqrestore(&hvcsd->lock, flags);
+ return retval;
+}
+
+static DEVICE_ATTR(index, S_IRUGO, hvcs_index_show, NULL);
+
+static struct attribute *hvcs_attrs[] = {
+ &dev_attr_partner_vtys.attr,
+ &dev_attr_partner_clcs.attr,
+ &dev_attr_current_vty.attr,
+ &dev_attr_vterm_state.attr,
+ &dev_attr_index.attr,
+ NULL,
+};
+
+static struct attribute_group hvcs_attr_group = {
+ .attrs = hvcs_attrs,
+};
+
+static void hvcs_create_device_attrs(struct hvcs_struct *hvcsd)
+{
+ struct vio_dev *vdev = hvcsd->vdev;
+ sysfs_create_group(&vdev->dev.kobj, &hvcs_attr_group);
+}
+
+static void hvcs_remove_device_attrs(struct vio_dev *vdev)
+{
+ sysfs_remove_group(&vdev->dev.kobj, &hvcs_attr_group);
+}
+
+static ssize_t hvcs_rescan_show(struct device_driver *ddp, char *buf)
+{
+ /* A 1 means it is updating, a 0 means it is done updating */
+ return snprintf(buf, PAGE_SIZE, "%d\n", hvcs_rescan_status);
+}
+
+static ssize_t hvcs_rescan_store(struct device_driver *ddp, const char * buf,
+ size_t count)
+{
+ if ((simple_strtol(buf, NULL, 0) != 1)
+ && (hvcs_rescan_status != 0))
+ return -EINVAL;
+
+ hvcs_rescan_status = 1;
+ printk(KERN_INFO "HVCS: rescanning partner info for all"
+ " vty-servers.\n");
+ hvcs_rescan_devices_list();
+ hvcs_rescan_status = 0;
+ return count;
+}
+static DRIVER_ATTR(rescan,
+ S_IRUGO | S_IWUSR, hvcs_rescan_show, hvcs_rescan_store);
+
+static void hvcs_create_driver_attrs(void)
+{
+ struct device_driver *driverfs = &(hvcs_vio_driver.driver);
+ driver_create_file(driverfs, &driver_attr_rescan);
+}
+
+static void hvcs_remove_driver_attrs(void)
+{
+ struct device_driver *driverfs = &(hvcs_vio_driver.driver);
+ driver_remove_file(driverfs, &driver_attr_rescan);
+}
diff --git a/drivers/char/hvsi.c b/drivers/char/hvsi.c
new file mode 100644
index 000000000000..f1f1192ba2b5
--- /dev/null
+++ b/drivers/char/hvsi.c
@@ -0,0 +1,1320 @@
+/*
+ * Copyright (C) 2004 Hollis Blanchard <hollisb@us.ibm.com>, IBM
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* Host Virtual Serial Interface (HVSI) is a protocol between the hosted OS
+ * and the service processor on IBM pSeries servers. On these servers, there
+ * are no serial ports under the OS's control, and sometimes there is no other
+ * console available either. However, the service processor has two standard
+ * serial ports, so this over-complicated protocol allows the OS to control
+ * those ports by proxy.
+ *
+ * Besides data, the procotol supports the reading/writing of the serial
+ * port's DTR line, and the reading of the CD line. This is to allow the OS to
+ * control a modem attached to the service processor's serial port. Note that
+ * the OS cannot change the speed of the port through this protocol.
+ */
+
+#undef DEBUG
+
+#include <linux/console.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/major.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <asm/hvcall.h>
+#include <asm/hvconsole.h>
+#include <asm/prom.h>
+#include <asm/uaccess.h>
+#include <asm/vio.h>
+#include <asm/param.h>
+
+#define HVSI_MAJOR 229
+#define HVSI_MINOR 128
+#define MAX_NR_HVSI_CONSOLES 4
+
+#define HVSI_TIMEOUT (5*HZ)
+#define HVSI_VERSION 1
+#define HVSI_MAX_PACKET 256
+#define HVSI_MAX_READ 16
+#define HVSI_MAX_OUTGOING_DATA 12
+#define N_OUTBUF 12
+
+/*
+ * we pass data via two 8-byte registers, so we would like our char arrays
+ * properly aligned for those loads.
+ */
+#define __ALIGNED__ __attribute__((__aligned__(sizeof(long))))
+
+struct hvsi_struct {
+ struct work_struct writer;
+ struct work_struct handshaker;
+ wait_queue_head_t emptyq; /* woken when outbuf is emptied */
+ wait_queue_head_t stateq; /* woken when HVSI state changes */
+ spinlock_t lock;
+ int index;
+ struct tty_struct *tty;
+ unsigned int count;
+ uint8_t throttle_buf[128];
+ uint8_t outbuf[N_OUTBUF]; /* to implement write_room and chars_in_buffer */
+ /* inbuf is for packet reassembly. leave a little room for leftovers. */
+ uint8_t inbuf[HVSI_MAX_PACKET + HVSI_MAX_READ];
+ uint8_t *inbuf_end;
+ int n_throttle;
+ int n_outbuf;
+ uint32_t vtermno;
+ uint32_t virq;
+ atomic_t seqno; /* HVSI packet sequence number */
+ uint16_t mctrl;
+ uint8_t state; /* HVSI protocol state */
+ uint8_t flags;
+#ifdef CONFIG_MAGIC_SYSRQ
+ uint8_t sysrq;
+#endif /* CONFIG_MAGIC_SYSRQ */
+};
+static struct hvsi_struct hvsi_ports[MAX_NR_HVSI_CONSOLES];
+
+static struct tty_driver *hvsi_driver;
+static int hvsi_count;
+static int (*hvsi_wait)(struct hvsi_struct *hp, int state);
+
+enum HVSI_PROTOCOL_STATE {
+ HVSI_CLOSED,
+ HVSI_WAIT_FOR_VER_RESPONSE,
+ HVSI_WAIT_FOR_VER_QUERY,
+ HVSI_OPEN,
+ HVSI_WAIT_FOR_MCTRL_RESPONSE,
+ HVSI_FSP_DIED,
+};
+#define HVSI_CONSOLE 0x1
+
+#define VS_DATA_PACKET_HEADER 0xff
+#define VS_CONTROL_PACKET_HEADER 0xfe
+#define VS_QUERY_PACKET_HEADER 0xfd
+#define VS_QUERY_RESPONSE_PACKET_HEADER 0xfc
+
+/* control verbs */
+#define VSV_SET_MODEM_CTL 1 /* to service processor only */
+#define VSV_MODEM_CTL_UPDATE 2 /* from service processor only */
+#define VSV_CLOSE_PROTOCOL 3
+
+/* query verbs */
+#define VSV_SEND_VERSION_NUMBER 1
+#define VSV_SEND_MODEM_CTL_STATUS 2
+
+/* yes, these masks are not consecutive. */
+#define HVSI_TSDTR 0x01
+#define HVSI_TSCD 0x20
+
+struct hvsi_header {
+ uint8_t type;
+ uint8_t len;
+ uint16_t seqno;
+} __attribute__((packed));
+
+struct hvsi_data {
+ uint8_t type;
+ uint8_t len;
+ uint16_t seqno;
+ uint8_t data[HVSI_MAX_OUTGOING_DATA];
+} __attribute__((packed));
+
+struct hvsi_control {
+ uint8_t type;
+ uint8_t len;
+ uint16_t seqno;
+ uint16_t verb;
+ /* optional depending on verb: */
+ uint32_t word;
+ uint32_t mask;
+} __attribute__((packed));
+
+struct hvsi_query {
+ uint8_t type;
+ uint8_t len;
+ uint16_t seqno;
+ uint16_t verb;
+} __attribute__((packed));
+
+struct hvsi_query_response {
+ uint8_t type;
+ uint8_t len;
+ uint16_t seqno;
+ uint16_t verb;
+ uint16_t query_seqno;
+ union {
+ uint8_t version;
+ uint32_t mctrl_word;
+ } u;
+} __attribute__((packed));
+
+
+
+static inline int is_console(struct hvsi_struct *hp)
+{
+ return hp->flags & HVSI_CONSOLE;
+}
+
+static inline int is_open(struct hvsi_struct *hp)
+{
+ /* if we're waiting for an mctrl then we're already open */
+ return (hp->state == HVSI_OPEN)
+ || (hp->state == HVSI_WAIT_FOR_MCTRL_RESPONSE);
+}
+
+static inline void print_state(struct hvsi_struct *hp)
+{
+#ifdef DEBUG
+ static const char *state_names[] = {
+ "HVSI_CLOSED",
+ "HVSI_WAIT_FOR_VER_RESPONSE",
+ "HVSI_WAIT_FOR_VER_QUERY",
+ "HVSI_OPEN",
+ "HVSI_WAIT_FOR_MCTRL_RESPONSE",
+ "HVSI_FSP_DIED",
+ };
+ const char *name = state_names[hp->state];
+
+ if (hp->state > (sizeof(state_names)/sizeof(char*)))
+ name = "UNKNOWN";
+
+ pr_debug("hvsi%i: state = %s\n", hp->index, name);
+#endif /* DEBUG */
+}
+
+static inline void __set_state(struct hvsi_struct *hp, int state)
+{
+ hp->state = state;
+ print_state(hp);
+ wake_up_all(&hp->stateq);
+}
+
+static inline void set_state(struct hvsi_struct *hp, int state)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ __set_state(hp, state);
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static inline int len_packet(const uint8_t *packet)
+{
+ return (int)((struct hvsi_header *)packet)->len;
+}
+
+static inline int is_header(const uint8_t *packet)
+{
+ struct hvsi_header *header = (struct hvsi_header *)packet;
+ return header->type >= VS_QUERY_RESPONSE_PACKET_HEADER;
+}
+
+static inline int got_packet(const struct hvsi_struct *hp, uint8_t *packet)
+{
+ if (hp->inbuf_end < packet + sizeof(struct hvsi_header))
+ return 0; /* don't even have the packet header */
+
+ if (hp->inbuf_end < (packet + len_packet(packet)))
+ return 0; /* don't have the rest of the packet */
+
+ return 1;
+}
+
+/* shift remaining bytes in packetbuf down */
+static void compact_inbuf(struct hvsi_struct *hp, uint8_t *read_to)
+{
+ int remaining = (int)(hp->inbuf_end - read_to);
+
+ pr_debug("%s: %i chars remain\n", __FUNCTION__, remaining);
+
+ if (read_to != hp->inbuf)
+ memmove(hp->inbuf, read_to, remaining);
+
+ hp->inbuf_end = hp->inbuf + remaining;
+}
+
+#ifdef DEBUG
+#define dbg_dump_packet(packet) dump_packet(packet)
+#define dbg_dump_hex(data, len) dump_hex(data, len)
+#else
+#define dbg_dump_packet(packet) do { } while (0)
+#define dbg_dump_hex(data, len) do { } while (0)
+#endif
+
+static void dump_hex(const uint8_t *data, int len)
+{
+ int i;
+
+ printk(" ");
+ for (i=0; i < len; i++)
+ printk("%.2x", data[i]);
+
+ printk("\n ");
+ for (i=0; i < len; i++) {
+ if (isprint(data[i]))
+ printk("%c", data[i]);
+ else
+ printk(".");
+ }
+ printk("\n");
+}
+
+static void dump_packet(uint8_t *packet)
+{
+ struct hvsi_header *header = (struct hvsi_header *)packet;
+
+ printk("type 0x%x, len %i, seqno %i:\n", header->type, header->len,
+ header->seqno);
+
+ dump_hex(packet, header->len);
+}
+
+/* can't use hvc_get_chars because that strips CRs */
+static int hvsi_read(struct hvsi_struct *hp, char *buf, int count)
+{
+ unsigned long got;
+
+ if (plpar_hcall(H_GET_TERM_CHAR, hp->vtermno, 0, 0, 0, &got,
+ (unsigned long *)buf, (unsigned long *)buf+1) == H_Success)
+ return got;
+ return 0;
+}
+
+static void hvsi_recv_control(struct hvsi_struct *hp, uint8_t *packet,
+ struct tty_struct **to_hangup, struct hvsi_struct **to_handshake)
+{
+ struct hvsi_control *header = (struct hvsi_control *)packet;
+
+ switch (header->verb) {
+ case VSV_MODEM_CTL_UPDATE:
+ if ((header->word & HVSI_TSCD) == 0) {
+ /* CD went away; no more connection */
+ pr_debug("hvsi%i: CD dropped\n", hp->index);
+ hp->mctrl &= TIOCM_CD;
+ if (!(hp->tty->flags & CLOCAL))
+ *to_hangup = hp->tty;
+ }
+ break;
+ case VSV_CLOSE_PROTOCOL:
+ pr_debug("hvsi%i: service processor came back\n", hp->index);
+ if (hp->state != HVSI_CLOSED) {
+ *to_handshake = hp;
+ }
+ break;
+ default:
+ printk(KERN_WARNING "hvsi%i: unknown HVSI control packet: ",
+ hp->index);
+ dump_packet(packet);
+ break;
+ }
+}
+
+static void hvsi_recv_response(struct hvsi_struct *hp, uint8_t *packet)
+{
+ struct hvsi_query_response *resp = (struct hvsi_query_response *)packet;
+
+ switch (hp->state) {
+ case HVSI_WAIT_FOR_VER_RESPONSE:
+ __set_state(hp, HVSI_WAIT_FOR_VER_QUERY);
+ break;
+ case HVSI_WAIT_FOR_MCTRL_RESPONSE:
+ hp->mctrl = 0;
+ if (resp->u.mctrl_word & HVSI_TSDTR)
+ hp->mctrl |= TIOCM_DTR;
+ if (resp->u.mctrl_word & HVSI_TSCD)
+ hp->mctrl |= TIOCM_CD;
+ __set_state(hp, HVSI_OPEN);
+ break;
+ default:
+ printk(KERN_ERR "hvsi%i: unexpected query response: ", hp->index);
+ dump_packet(packet);
+ break;
+ }
+}
+
+/* respond to service processor's version query */
+static int hvsi_version_respond(struct hvsi_struct *hp, uint16_t query_seqno)
+{
+ struct hvsi_query_response packet __ALIGNED__;
+ int wrote;
+
+ packet.type = VS_QUERY_RESPONSE_PACKET_HEADER;
+ packet.len = sizeof(struct hvsi_query_response);
+ packet.seqno = atomic_inc_return(&hp->seqno);
+ packet.verb = VSV_SEND_VERSION_NUMBER;
+ packet.u.version = HVSI_VERSION;
+ packet.query_seqno = query_seqno+1;
+
+ pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+ if (wrote != packet.len) {
+ printk(KERN_ERR "hvsi%i: couldn't send query response!\n",
+ hp->index);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void hvsi_recv_query(struct hvsi_struct *hp, uint8_t *packet)
+{
+ struct hvsi_query *query = (struct hvsi_query *)packet;
+
+ switch (hp->state) {
+ case HVSI_WAIT_FOR_VER_QUERY:
+ hvsi_version_respond(hp, query->seqno);
+ __set_state(hp, HVSI_OPEN);
+ break;
+ default:
+ printk(KERN_ERR "hvsi%i: unexpected query: ", hp->index);
+ dump_packet(packet);
+ break;
+ }
+}
+
+static void hvsi_insert_chars(struct hvsi_struct *hp, const char *buf, int len)
+{
+ int i;
+
+ for (i=0; i < len; i++) {
+ char c = buf[i];
+#ifdef CONFIG_MAGIC_SYSRQ
+ if (c == '\0') {
+ hp->sysrq = 1;
+ continue;
+ } else if (hp->sysrq) {
+ handle_sysrq(c, NULL, hp->tty);
+ hp->sysrq = 0;
+ continue;
+ }
+#endif /* CONFIG_MAGIC_SYSRQ */
+ tty_insert_flip_char(hp->tty, c, 0);
+ }
+}
+
+/*
+ * We could get 252 bytes of data at once here. But the tty layer only
+ * throttles us at TTY_THRESHOLD_THROTTLE (128) bytes, so we could overflow
+ * it. Accordingly we won't send more than 128 bytes at a time to the flip
+ * buffer, which will give the tty buffer a chance to throttle us. Should the
+ * value of TTY_THRESHOLD_THROTTLE change in n_tty.c, this code should be
+ * revisited.
+ */
+#define TTY_THRESHOLD_THROTTLE 128
+static struct tty_struct *hvsi_recv_data(struct hvsi_struct *hp,
+ const uint8_t *packet)
+{
+ const struct hvsi_header *header = (const struct hvsi_header *)packet;
+ const uint8_t *data = packet + sizeof(struct hvsi_header);
+ int datalen = header->len - sizeof(struct hvsi_header);
+ int overflow = datalen - TTY_THRESHOLD_THROTTLE;
+
+ pr_debug("queueing %i chars '%.*s'\n", datalen, datalen, data);
+
+ if (datalen == 0)
+ return NULL;
+
+ if (overflow > 0) {
+ pr_debug("%s: got >TTY_THRESHOLD_THROTTLE bytes\n", __FUNCTION__);
+ datalen = TTY_THRESHOLD_THROTTLE;
+ }
+
+ hvsi_insert_chars(hp, data, datalen);
+
+ if (overflow > 0) {
+ /*
+ * we still have more data to deliver, so we need to save off the
+ * overflow and send it later
+ */
+ pr_debug("%s: deferring overflow\n", __FUNCTION__);
+ memcpy(hp->throttle_buf, data + TTY_THRESHOLD_THROTTLE, overflow);
+ hp->n_throttle = overflow;
+ }
+
+ return hp->tty;
+}
+
+/*
+ * Returns true/false indicating data successfully read from hypervisor.
+ * Used both to get packets for tty connections and to advance the state
+ * machine during console handshaking (in which case tty = NULL and we ignore
+ * incoming data).
+ */
+static int hvsi_load_chunk(struct hvsi_struct *hp, struct tty_struct **flip,
+ struct tty_struct **hangup, struct hvsi_struct **handshake)
+{
+ uint8_t *packet = hp->inbuf;
+ int chunklen;
+
+ *flip = NULL;
+ *hangup = NULL;
+ *handshake = NULL;
+
+ chunklen = hvsi_read(hp, hp->inbuf_end, HVSI_MAX_READ);
+ if (chunklen == 0) {
+ pr_debug("%s: 0-length read\n", __FUNCTION__);
+ return 0;
+ }
+
+ pr_debug("%s: got %i bytes\n", __FUNCTION__, chunklen);
+ dbg_dump_hex(hp->inbuf_end, chunklen);
+
+ hp->inbuf_end += chunklen;
+
+ /* handle all completed packets */
+ while ((packet < hp->inbuf_end) && got_packet(hp, packet)) {
+ struct hvsi_header *header = (struct hvsi_header *)packet;
+
+ if (!is_header(packet)) {
+ printk(KERN_ERR "hvsi%i: got malformed packet\n", hp->index);
+ /* skip bytes until we find a header or run out of data */
+ while ((packet < hp->inbuf_end) && (!is_header(packet)))
+ packet++;
+ continue;
+ }
+
+ pr_debug("%s: handling %i-byte packet\n", __FUNCTION__,
+ len_packet(packet));
+ dbg_dump_packet(packet);
+
+ switch (header->type) {
+ case VS_DATA_PACKET_HEADER:
+ if (!is_open(hp))
+ break;
+ if (hp->tty == NULL)
+ break; /* no tty buffer to put data in */
+ *flip = hvsi_recv_data(hp, packet);
+ break;
+ case VS_CONTROL_PACKET_HEADER:
+ hvsi_recv_control(hp, packet, hangup, handshake);
+ break;
+ case VS_QUERY_RESPONSE_PACKET_HEADER:
+ hvsi_recv_response(hp, packet);
+ break;
+ case VS_QUERY_PACKET_HEADER:
+ hvsi_recv_query(hp, packet);
+ break;
+ default:
+ printk(KERN_ERR "hvsi%i: unknown HVSI packet type 0x%x\n",
+ hp->index, header->type);
+ dump_packet(packet);
+ break;
+ }
+
+ packet += len_packet(packet);
+
+ if (*hangup || *handshake) {
+ pr_debug("%s: hangup or handshake\n", __FUNCTION__);
+ /*
+ * we need to send the hangup now before receiving any more data.
+ * If we get "data, hangup, data", we can't deliver the second
+ * data before the hangup.
+ */
+ break;
+ }
+ }
+
+ compact_inbuf(hp, packet);
+
+ return 1;
+}
+
+static void hvsi_send_overflow(struct hvsi_struct *hp)
+{
+ pr_debug("%s: delivering %i bytes overflow\n", __FUNCTION__,
+ hp->n_throttle);
+
+ hvsi_insert_chars(hp, hp->throttle_buf, hp->n_throttle);
+ hp->n_throttle = 0;
+}
+
+/*
+ * must get all pending data because we only get an irq on empty->non-empty
+ * transition
+ */
+static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+ struct tty_struct *flip;
+ struct tty_struct *hangup;
+ struct hvsi_struct *handshake;
+ unsigned long flags;
+ int again = 1;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ while (again) {
+ spin_lock_irqsave(&hp->lock, flags);
+ again = hvsi_load_chunk(hp, &flip, &hangup, &handshake);
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ /*
+ * we have to call tty_flip_buffer_push() and tty_hangup() outside our
+ * spinlock. But we also have to keep going until we've read all the
+ * available data.
+ */
+
+ if (flip) {
+ /* there was data put in the tty flip buffer */
+ tty_flip_buffer_push(flip);
+ flip = NULL;
+ }
+
+ if (hangup) {
+ tty_hangup(hangup);
+ }
+
+ if (handshake) {
+ pr_debug("hvsi%i: attempting re-handshake\n", handshake->index);
+ schedule_work(&handshake->handshaker);
+ }
+ }
+
+ spin_lock_irqsave(&hp->lock, flags);
+ if (hp->tty && hp->n_throttle
+ && (!test_bit(TTY_THROTTLED, &hp->tty->flags))) {
+ /* we weren't hung up and we weren't throttled, so we can deliver the
+ * rest now */
+ flip = hp->tty;
+ hvsi_send_overflow(hp);
+ }
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (flip) {
+ tty_flip_buffer_push(flip);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/* for boot console, before the irq handler is running */
+static int __init poll_for_state(struct hvsi_struct *hp, int state)
+{
+ unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
+
+ for (;;) {
+ hvsi_interrupt(hp->virq, (void *)hp, NULL); /* get pending data */
+
+ if (hp->state == state)
+ return 0;
+
+ mdelay(5);
+ if (time_after(jiffies, end_jiffies))
+ return -EIO;
+ }
+}
+
+/* wait for irq handler to change our state */
+static int wait_for_state(struct hvsi_struct *hp, int state)
+{
+ int ret = 0;
+
+ if (!wait_event_timeout(hp->stateq, (hp->state == state), HVSI_TIMEOUT))
+ ret = -EIO;
+
+ return ret;
+}
+
+static int hvsi_query(struct hvsi_struct *hp, uint16_t verb)
+{
+ struct hvsi_query packet __ALIGNED__;
+ int wrote;
+
+ packet.type = VS_QUERY_PACKET_HEADER;
+ packet.len = sizeof(struct hvsi_query);
+ packet.seqno = atomic_inc_return(&hp->seqno);
+ packet.verb = verb;
+
+ pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+ if (wrote != packet.len) {
+ printk(KERN_ERR "hvsi%i: couldn't send query (%i)!\n", hp->index,
+ wrote);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int hvsi_get_mctrl(struct hvsi_struct *hp)
+{
+ int ret;
+
+ set_state(hp, HVSI_WAIT_FOR_MCTRL_RESPONSE);
+ hvsi_query(hp, VSV_SEND_MODEM_CTL_STATUS);
+
+ ret = hvsi_wait(hp, HVSI_OPEN);
+ if (ret < 0) {
+ printk(KERN_ERR "hvsi%i: didn't get modem flags\n", hp->index);
+ set_state(hp, HVSI_OPEN);
+ return ret;
+ }
+
+ pr_debug("%s: mctrl 0x%x\n", __FUNCTION__, hp->mctrl);
+
+ return 0;
+}
+
+/* note that we can only set DTR */
+static int hvsi_set_mctrl(struct hvsi_struct *hp, uint16_t mctrl)
+{
+ struct hvsi_control packet __ALIGNED__;
+ int wrote;
+
+ packet.type = VS_CONTROL_PACKET_HEADER,
+ packet.seqno = atomic_inc_return(&hp->seqno);
+ packet.len = sizeof(struct hvsi_control);
+ packet.verb = VSV_SET_MODEM_CTL;
+ packet.mask = HVSI_TSDTR;
+
+ if (mctrl & TIOCM_DTR)
+ packet.word = HVSI_TSDTR;
+
+ pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+ wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+ if (wrote != packet.len) {
+ printk(KERN_ERR "hvsi%i: couldn't set DTR!\n", hp->index);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void hvsi_drain_input(struct hvsi_struct *hp)
+{
+ uint8_t buf[HVSI_MAX_READ] __ALIGNED__;
+ unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
+
+ while (time_before(end_jiffies, jiffies))
+ if (0 == hvsi_read(hp, buf, HVSI_MAX_READ))
+ break;
+}
+
+static int hvsi_handshake(struct hvsi_struct *hp)
+{
+ int ret;
+
+ /*
+ * We could have a CLOSE or other data waiting for us before we even try
+ * to open; try to throw it all away so we don't get confused. (CLOSE
+ * is the first message sent up the pipe when the FSP comes online. We
+ * need to distinguish between "it came up a while ago and we're the first
+ * user" and "it was just reset before it saw our handshake packet".)
+ */
+ hvsi_drain_input(hp);
+
+ set_state(hp, HVSI_WAIT_FOR_VER_RESPONSE);
+ ret = hvsi_query(hp, VSV_SEND_VERSION_NUMBER);
+ if (ret < 0) {
+ printk(KERN_ERR "hvsi%i: couldn't send version query\n", hp->index);
+ return ret;
+ }
+
+ ret = hvsi_wait(hp, HVSI_OPEN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void hvsi_handshaker(void *arg)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+
+ if (hvsi_handshake(hp) >= 0)
+ return;
+
+ printk(KERN_ERR "hvsi%i: re-handshaking failed\n", hp->index);
+ if (is_console(hp)) {
+ /*
+ * ttys will re-attempt the handshake via hvsi_open, but
+ * the console will not.
+ */
+ printk(KERN_ERR "hvsi%i: lost console!\n", hp->index);
+ }
+}
+
+static int hvsi_put_chars(struct hvsi_struct *hp, const char *buf, int count)
+{
+ struct hvsi_data packet __ALIGNED__;
+ int ret;
+
+ BUG_ON(count > HVSI_MAX_OUTGOING_DATA);
+
+ packet.type = VS_DATA_PACKET_HEADER;
+ packet.seqno = atomic_inc_return(&hp->seqno);
+ packet.len = count + sizeof(struct hvsi_header);
+ memcpy(&packet.data, buf, count);
+
+ ret = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+ if (ret == packet.len) {
+ /* return the number of chars written, not the packet length */
+ return count;
+ }
+ return ret; /* return any errors */
+}
+
+static void hvsi_close_protocol(struct hvsi_struct *hp)
+{
+ struct hvsi_control packet __ALIGNED__;
+
+ packet.type = VS_CONTROL_PACKET_HEADER;
+ packet.seqno = atomic_inc_return(&hp->seqno);
+ packet.len = 6;
+ packet.verb = VSV_CLOSE_PROTOCOL;
+
+ pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+ dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+ hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+}
+
+static int hvsi_open(struct tty_struct *tty, struct file *filp)
+{
+ struct hvsi_struct *hp;
+ unsigned long flags;
+ int line = tty->index;
+ int ret;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ if (line < 0 || line >= hvsi_count)
+ return -ENODEV;
+ hp = &hvsi_ports[line];
+
+ tty->driver_data = hp;
+ tty->low_latency = 1; /* avoid throttle/tty_flip_buffer_push race */
+
+ mb();
+ if (hp->state == HVSI_FSP_DIED)
+ return -EIO;
+
+ spin_lock_irqsave(&hp->lock, flags);
+ hp->tty = tty;
+ hp->count++;
+ atomic_set(&hp->seqno, 0);
+ h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (is_console(hp))
+ return 0; /* this has already been handshaked as the console */
+
+ ret = hvsi_handshake(hp);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: HVSI handshaking failed\n", tty->name);
+ return ret;
+ }
+
+ ret = hvsi_get_mctrl(hp);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: couldn't get initial modem flags\n", tty->name);
+ return ret;
+ }
+
+ ret = hvsi_set_mctrl(hp, hp->mctrl | TIOCM_DTR);
+ if (ret < 0) {
+ printk(KERN_ERR "%s: couldn't set DTR\n", tty->name);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* wait for hvsi_write_worker to empty hp->outbuf */
+static void hvsi_flush_output(struct hvsi_struct *hp)
+{
+ wait_event_timeout(hp->emptyq, (hp->n_outbuf <= 0), HVSI_TIMEOUT);
+
+ /* 'writer' could still be pending if it didn't see n_outbuf = 0 yet */
+ cancel_delayed_work(&hp->writer);
+ flush_scheduled_work();
+
+ /*
+ * it's also possible that our timeout expired and hvsi_write_worker
+ * didn't manage to push outbuf. poof.
+ */
+ hp->n_outbuf = 0;
+}
+
+static void hvsi_close(struct tty_struct *tty, struct file *filp)
+{
+ struct hvsi_struct *hp = tty->driver_data;
+ unsigned long flags;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ if (tty_hung_up_p(filp))
+ return;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ if (--hp->count == 0) {
+ hp->tty = NULL;
+ hp->inbuf_end = hp->inbuf; /* discard remaining partial packets */
+
+ /* only close down connection if it is not the console */
+ if (!is_console(hp)) {
+ h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE); /* no more irqs */
+ __set_state(hp, HVSI_CLOSED);
+ /*
+ * any data delivered to the tty layer after this will be
+ * discarded (except for XON/XOFF)
+ */
+ tty->closing = 1;
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ /* let any existing irq handlers finish. no more will start. */
+ synchronize_irq(hp->virq);
+
+ /* hvsi_write_worker will re-schedule until outbuf is empty. */
+ hvsi_flush_output(hp);
+
+ /* tell FSP to stop sending data */
+ hvsi_close_protocol(hp);
+
+ /*
+ * drain anything FSP is still in the middle of sending, and let
+ * hvsi_handshake drain the rest on the next open.
+ */
+ hvsi_drain_input(hp);
+
+ spin_lock_irqsave(&hp->lock, flags);
+ }
+ } else if (hp->count < 0)
+ printk(KERN_ERR "hvsi_close %lu: oops, count is %d\n",
+ hp - hvsi_ports, hp->count);
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static void hvsi_hangup(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = tty->driver_data;
+ unsigned long flags;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ hp->count = 0;
+ hp->n_outbuf = 0;
+ hp->tty = NULL;
+
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+/* called with hp->lock held */
+static void hvsi_push(struct hvsi_struct *hp)
+{
+ int n;
+
+ if (hp->n_outbuf <= 0)
+ return;
+
+ n = hvsi_put_chars(hp, hp->outbuf, hp->n_outbuf);
+ if (n > 0) {
+ /* success */
+ pr_debug("%s: wrote %i chars\n", __FUNCTION__, n);
+ hp->n_outbuf = 0;
+ } else if (n == -EIO) {
+ __set_state(hp, HVSI_FSP_DIED);
+ printk(KERN_ERR "hvsi%i: service processor died\n", hp->index);
+ }
+}
+
+/* hvsi_write_worker will keep rescheduling itself until outbuf is empty */
+static void hvsi_write_worker(void *arg)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+ unsigned long flags;
+#ifdef DEBUG
+ static long start_j = 0;
+
+ if (start_j == 0)
+ start_j = jiffies;
+#endif /* DEBUG */
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
+ if (!is_open(hp)) {
+ /*
+ * We could have a non-open connection if the service processor died
+ * while we were busily scheduling ourselves. In that case, it could
+ * be minutes before the service processor comes back, so only try
+ * again once a second.
+ */
+ schedule_delayed_work(&hp->writer, HZ);
+ goto out;
+ }
+
+ hvsi_push(hp);
+ if (hp->n_outbuf > 0)
+ schedule_delayed_work(&hp->writer, 10);
+ else {
+#ifdef DEBUG
+ pr_debug("%s: outbuf emptied after %li jiffies\n", __FUNCTION__,
+ jiffies - start_j);
+ start_j = 0;
+#endif /* DEBUG */
+ wake_up_all(&hp->emptyq);
+ if (test_bit(TTY_DO_WRITE_WAKEUP, &hp->tty->flags)
+ && hp->tty->ldisc.write_wakeup)
+ hp->tty->ldisc.write_wakeup(hp->tty);
+ wake_up_interruptible(&hp->tty->write_wait);
+ }
+
+out:
+ spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static int hvsi_write_room(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+ return N_OUTBUF - hp->n_outbuf;
+}
+
+static int hvsi_chars_in_buffer(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+ return hp->n_outbuf;
+}
+
+static int hvsi_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct hvsi_struct *hp = tty->driver_data;
+ const char *source = buf;
+ unsigned long flags;
+ int total = 0;
+ int origcount = count;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
+ if (!is_open(hp)) {
+ /* we're either closing or not yet open; don't accept data */
+ pr_debug("%s: not open\n", __FUNCTION__);
+ goto out;
+ }
+
+ /*
+ * when the hypervisor buffer (16K) fills, data will stay in hp->outbuf
+ * and hvsi_write_worker will be scheduled. subsequent hvsi_write() calls
+ * will see there is no room in outbuf and return.
+ */
+ while ((count > 0) && (hvsi_write_room(hp->tty) > 0)) {
+ int chunksize = min(count, hvsi_write_room(hp->tty));
+
+ BUG_ON(hp->n_outbuf < 0);
+ memcpy(hp->outbuf + hp->n_outbuf, source, chunksize);
+ hp->n_outbuf += chunksize;
+
+ total += chunksize;
+ source += chunksize;
+ count -= chunksize;
+ hvsi_push(hp);
+ }
+
+ if (hp->n_outbuf > 0) {
+ /*
+ * we weren't able to write it all to the hypervisor.
+ * schedule another push attempt.
+ */
+ schedule_delayed_work(&hp->writer, 10);
+ }
+
+out:
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (total != origcount)
+ pr_debug("%s: wanted %i, only wrote %i\n", __FUNCTION__, origcount,
+ total);
+
+ return total;
+}
+
+/*
+ * I have never seen throttle or unthrottle called, so this little throttle
+ * buffering scheme may or may not work.
+ */
+static void hvsi_throttle(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE);
+}
+
+static void hvsi_unthrottle(struct tty_struct *tty)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+ unsigned long flags;
+ int shouldflip = 0;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ spin_lock_irqsave(&hp->lock, flags);
+ if (hp->n_throttle) {
+ hvsi_send_overflow(hp);
+ shouldflip = 1;
+ }
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ if (shouldflip)
+ tty_flip_buffer_push(hp->tty);
+
+ h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
+}
+
+static int hvsi_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+ hvsi_get_mctrl(hp);
+ return hp->mctrl;
+}
+
+static int hvsi_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+ unsigned long flags;
+ uint16_t new_mctrl;
+
+ /* we can only alter DTR */
+ clear &= TIOCM_DTR;
+ set &= TIOCM_DTR;
+
+ spin_lock_irqsave(&hp->lock, flags);
+
+ new_mctrl = (hp->mctrl & ~clear) | set;
+
+ if (hp->mctrl != new_mctrl) {
+ hvsi_set_mctrl(hp, new_mctrl);
+ hp->mctrl = new_mctrl;
+ }
+ spin_unlock_irqrestore(&hp->lock, flags);
+
+ return 0;
+}
+
+
+static struct tty_operations hvsi_ops = {
+ .open = hvsi_open,
+ .close = hvsi_close,
+ .write = hvsi_write,
+ .hangup = hvsi_hangup,
+ .write_room = hvsi_write_room,
+ .chars_in_buffer = hvsi_chars_in_buffer,
+ .throttle = hvsi_throttle,
+ .unthrottle = hvsi_unthrottle,
+ .tiocmget = hvsi_tiocmget,
+ .tiocmset = hvsi_tiocmset,
+};
+
+static int __init hvsi_init(void)
+{
+ int i;
+
+ hvsi_driver = alloc_tty_driver(hvsi_count);
+ if (!hvsi_driver)
+ return -ENOMEM;
+
+ hvsi_driver->owner = THIS_MODULE;
+ hvsi_driver->devfs_name = "hvsi/";
+ hvsi_driver->driver_name = "hvsi";
+ hvsi_driver->name = "hvsi";
+ hvsi_driver->major = HVSI_MAJOR;
+ hvsi_driver->minor_start = HVSI_MINOR;
+ hvsi_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+ hvsi_driver->init_termios = tty_std_termios;
+ hvsi_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+ hvsi_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(hvsi_driver, &hvsi_ops);
+
+ for (i=0; i < hvsi_count; i++) {
+ struct hvsi_struct *hp = &hvsi_ports[i];
+ int ret = 1;
+
+ ret = request_irq(hp->virq, hvsi_interrupt, SA_INTERRUPT, "hvsi", hp);
+ if (ret)
+ printk(KERN_ERR "HVSI: couldn't reserve irq 0x%x (error %i)\n",
+ hp->virq, ret);
+ }
+ hvsi_wait = wait_for_state; /* irqs active now */
+
+ if (tty_register_driver(hvsi_driver))
+ panic("Couldn't register hvsi console driver\n");
+
+ printk(KERN_INFO "HVSI: registered %i devices\n", hvsi_count);
+
+ return 0;
+}
+device_initcall(hvsi_init);
+
+/***** console (not tty) code: *****/
+
+static void hvsi_console_print(struct console *console, const char *buf,
+ unsigned int count)
+{
+ struct hvsi_struct *hp = &hvsi_ports[console->index];
+ char c[HVSI_MAX_OUTGOING_DATA] __ALIGNED__;
+ unsigned int i = 0, n = 0;
+ int ret, donecr = 0;
+
+ mb();
+ if (!is_open(hp))
+ return;
+
+ /*
+ * ugh, we have to translate LF -> CRLF ourselves, in place.
+ * copied from hvc_console.c:
+ */
+ while (count > 0 || i > 0) {
+ if (count > 0 && i < sizeof(c)) {
+ if (buf[n] == '\n' && !donecr) {
+ c[i++] = '\r';
+ donecr = 1;
+ } else {
+ c[i++] = buf[n++];
+ donecr = 0;
+ --count;
+ }
+ } else {
+ ret = hvsi_put_chars(hp, c, i);
+ if (ret < 0)
+ i = 0;
+ i -= ret;
+ }
+ }
+}
+
+static struct tty_driver *hvsi_console_device(struct console *console,
+ int *index)
+{
+ *index = console->index;
+ return hvsi_driver;
+}
+
+static int __init hvsi_console_setup(struct console *console, char *options)
+{
+ struct hvsi_struct *hp = &hvsi_ports[console->index];
+ int ret;
+
+ if (console->index < 0 || console->index >= hvsi_count)
+ return -1;
+
+ /* give the FSP a chance to change the baud rate when we re-open */
+ hvsi_close_protocol(hp);
+
+ ret = hvsi_handshake(hp);
+ if (ret < 0)
+ return ret;
+
+ ret = hvsi_get_mctrl(hp);
+ if (ret < 0)
+ return ret;
+
+ ret = hvsi_set_mctrl(hp, hp->mctrl | TIOCM_DTR);
+ if (ret < 0)
+ return ret;
+
+ hp->flags |= HVSI_CONSOLE;
+
+ return 0;
+}
+
+static struct console hvsi_con_driver = {
+ .name = "hvsi",
+ .write = hvsi_console_print,
+ .device = hvsi_console_device,
+ .setup = hvsi_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+static int __init hvsi_console_init(void)
+{
+ struct device_node *vty;
+
+ hvsi_wait = poll_for_state; /* no irqs yet; must poll */
+
+ /* search device tree for vty nodes */
+ for (vty = of_find_compatible_node(NULL, "serial", "hvterm-protocol");
+ vty != NULL;
+ vty = of_find_compatible_node(vty, "serial", "hvterm-protocol")) {
+ struct hvsi_struct *hp;
+ uint32_t *vtermno;
+ uint32_t *irq;
+
+ vtermno = (uint32_t *)get_property(vty, "reg", NULL);
+ irq = (uint32_t *)get_property(vty, "interrupts", NULL);
+ if (!vtermno || !irq)
+ continue;
+
+ if (hvsi_count >= MAX_NR_HVSI_CONSOLES) {
+ of_node_put(vty);
+ break;
+ }
+
+ hp = &hvsi_ports[hvsi_count];
+ INIT_WORK(&hp->writer, hvsi_write_worker, hp);
+ INIT_WORK(&hp->handshaker, hvsi_handshaker, hp);
+ init_waitqueue_head(&hp->emptyq);
+ init_waitqueue_head(&hp->stateq);
+ spin_lock_init(&hp->lock);
+ hp->index = hvsi_count;
+ hp->inbuf_end = hp->inbuf;
+ hp->state = HVSI_CLOSED;
+ hp->vtermno = *vtermno;
+ hp->virq = virt_irq_create_mapping(irq[0]);
+ if (hp->virq == NO_IRQ) {
+ printk(KERN_ERR "%s: couldn't create irq mapping for 0x%x\n",
+ __FUNCTION__, hp->virq);
+ continue;
+ } else
+ hp->virq = irq_offset_up(hp->virq);
+
+ hvsi_count++;
+ }
+
+ if (hvsi_count)
+ register_console(&hvsi_con_driver);
+ return 0;
+}
+console_initcall(hvsi_console_init);
diff --git a/drivers/char/hw_random.c b/drivers/char/hw_random.c
new file mode 100644
index 000000000000..7e6ac14c2450
--- /dev/null
+++ b/drivers/char/hw_random.c
@@ -0,0 +1,630 @@
+/*
+ Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
+ (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
+
+ derived from
+
+ Hardware driver for the AMD 768 Random Number Generator (RNG)
+ (c) Copyright 2001 Red Hat Inc <alan@redhat.com>
+
+ derived from
+
+ Hardware driver for Intel i810 Random Number Generator (RNG)
+ Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
+ Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
+
+ Please read Documentation/hw_random.txt for details on use.
+
+ ----------------------------------------------------------
+ This software may be used and distributed according to the terms
+ of the GNU General Public License, incorporated herein by reference.
+
+ */
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <linux/miscdevice.h>
+#include <linux/smp_lock.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+
+#ifdef __i386__
+#include <asm/msr.h>
+#include <asm/cpufeature.h>
+#endif
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+
+/*
+ * core module and version information
+ */
+#define RNG_VERSION "1.0.0"
+#define RNG_MODULE_NAME "hw_random"
+#define RNG_DRIVER_NAME RNG_MODULE_NAME " hardware driver " RNG_VERSION
+#define PFX RNG_MODULE_NAME ": "
+
+
+/*
+ * debugging macros
+ */
+
+/* pr_debug() collapses to a no-op if DEBUG is not defined */
+#define DPRINTK(fmt, args...) pr_debug(PFX "%s: " fmt, __FUNCTION__ , ## args)
+
+
+#undef RNG_NDEBUG /* define to enable lightweight runtime checks */
+#ifdef RNG_NDEBUG
+#define assert(expr) \
+ if(!(expr)) { \
+ printk(KERN_DEBUG PFX "Assertion failed! %s,%s,%s," \
+ "line=%d\n", #expr, __FILE__, __FUNCTION__, __LINE__); \
+ }
+#else
+#define assert(expr)
+#endif
+
+#define RNG_MISCDEV_MINOR 183 /* official */
+
+static int rng_dev_open (struct inode *inode, struct file *filp);
+static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size,
+ loff_t * offp);
+
+static int __init intel_init (struct pci_dev *dev);
+static void intel_cleanup(void);
+static unsigned int intel_data_present (void);
+static u32 intel_data_read (void);
+
+static int __init amd_init (struct pci_dev *dev);
+static void amd_cleanup(void);
+static unsigned int amd_data_present (void);
+static u32 amd_data_read (void);
+
+#ifdef __i386__
+static int __init via_init(struct pci_dev *dev);
+static void via_cleanup(void);
+static unsigned int via_data_present (void);
+static u32 via_data_read (void);
+#endif
+
+struct rng_operations {
+ int (*init) (struct pci_dev *dev);
+ void (*cleanup) (void);
+ unsigned int (*data_present) (void);
+ u32 (*data_read) (void);
+ unsigned int n_bytes; /* number of bytes per ->data_read */
+};
+static struct rng_operations *rng_ops;
+
+static struct file_operations rng_chrdev_ops = {
+ .owner = THIS_MODULE,
+ .open = rng_dev_open,
+ .read = rng_dev_read,
+};
+
+
+static struct miscdevice rng_miscdev = {
+ RNG_MISCDEV_MINOR,
+ RNG_MODULE_NAME,
+ &rng_chrdev_ops,
+};
+
+enum {
+ rng_hw_none,
+ rng_hw_intel,
+ rng_hw_amd,
+ rng_hw_via,
+};
+
+static struct rng_operations rng_vendor_ops[] = {
+ /* rng_hw_none */
+ { },
+
+ /* rng_hw_intel */
+ { intel_init, intel_cleanup, intel_data_present,
+ intel_data_read, 1 },
+
+ /* rng_hw_amd */
+ { amd_init, amd_cleanup, amd_data_present, amd_data_read, 4 },
+
+#ifdef __i386__
+ /* rng_hw_via */
+ { via_init, via_cleanup, via_data_present, via_data_read, 1 },
+#endif
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id rng_pci_tbl[] = {
+ { 0x1022, 0x7443, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd },
+ { 0x1022, 0x746b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd },
+
+ { 0x8086, 0x2418, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+ { 0x8086, 0x2428, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+ { 0x8086, 0x2448, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+ { 0x8086, 0x244e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+ { 0x8086, 0x245e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE (pci, rng_pci_tbl);
+
+
+/***********************************************************************
+ *
+ * Intel RNG operations
+ *
+ */
+
+/*
+ * RNG registers (offsets from rng_mem)
+ */
+#define INTEL_RNG_HW_STATUS 0
+#define INTEL_RNG_PRESENT 0x40
+#define INTEL_RNG_ENABLED 0x01
+#define INTEL_RNG_STATUS 1
+#define INTEL_RNG_DATA_PRESENT 0x01
+#define INTEL_RNG_DATA 2
+
+/*
+ * Magic address at which Intel PCI bridges locate the RNG
+ */
+#define INTEL_RNG_ADDR 0xFFBC015F
+#define INTEL_RNG_ADDR_LEN 3
+
+/* token to our ioremap'd RNG register area */
+static void __iomem *rng_mem;
+
+static inline u8 intel_hwstatus (void)
+{
+ assert (rng_mem != NULL);
+ return readb (rng_mem + INTEL_RNG_HW_STATUS);
+}
+
+static inline u8 intel_hwstatus_set (u8 hw_status)
+{
+ assert (rng_mem != NULL);
+ writeb (hw_status, rng_mem + INTEL_RNG_HW_STATUS);
+ return intel_hwstatus ();
+}
+
+static unsigned int intel_data_present(void)
+{
+ assert (rng_mem != NULL);
+
+ return (readb (rng_mem + INTEL_RNG_STATUS) & INTEL_RNG_DATA_PRESENT) ?
+ 1 : 0;
+}
+
+static u32 intel_data_read(void)
+{
+ assert (rng_mem != NULL);
+
+ return readb (rng_mem + INTEL_RNG_DATA);
+}
+
+static int __init intel_init (struct pci_dev *dev)
+{
+ int rc;
+ u8 hw_status;
+
+ DPRINTK ("ENTER\n");
+
+ rng_mem = ioremap (INTEL_RNG_ADDR, INTEL_RNG_ADDR_LEN);
+ if (rng_mem == NULL) {
+ printk (KERN_ERR PFX "cannot ioremap RNG Memory\n");
+ rc = -EBUSY;
+ goto err_out;
+ }
+
+ /* Check for Intel 82802 */
+ hw_status = intel_hwstatus ();
+ if ((hw_status & INTEL_RNG_PRESENT) == 0) {
+ printk (KERN_ERR PFX "RNG not detected\n");
+ rc = -ENODEV;
+ goto err_out_free_map;
+ }
+
+ /* turn RNG h/w on, if it's off */
+ if ((hw_status & INTEL_RNG_ENABLED) == 0)
+ hw_status = intel_hwstatus_set (hw_status | INTEL_RNG_ENABLED);
+ if ((hw_status & INTEL_RNG_ENABLED) == 0) {
+ printk (KERN_ERR PFX "cannot enable RNG, aborting\n");
+ rc = -EIO;
+ goto err_out_free_map;
+ }
+
+ DPRINTK ("EXIT, returning 0\n");
+ return 0;
+
+err_out_free_map:
+ iounmap (rng_mem);
+ rng_mem = NULL;
+err_out:
+ DPRINTK ("EXIT, returning %d\n", rc);
+ return rc;
+}
+
+static void intel_cleanup(void)
+{
+ u8 hw_status;
+
+ hw_status = intel_hwstatus ();
+ if (hw_status & INTEL_RNG_ENABLED)
+ intel_hwstatus_set (hw_status & ~INTEL_RNG_ENABLED);
+ else
+ printk(KERN_WARNING PFX "unusual: RNG already disabled\n");
+ iounmap(rng_mem);
+ rng_mem = NULL;
+}
+
+/***********************************************************************
+ *
+ * AMD RNG operations
+ *
+ */
+
+static u32 pmbase; /* PMxx I/O base */
+static struct pci_dev *amd_dev;
+
+static unsigned int amd_data_present (void)
+{
+ return inl(pmbase + 0xF4) & 1;
+}
+
+
+static u32 amd_data_read (void)
+{
+ return inl(pmbase + 0xF0);
+}
+
+static int __init amd_init (struct pci_dev *dev)
+{
+ int rc;
+ u8 rnen;
+
+ DPRINTK ("ENTER\n");
+
+ pci_read_config_dword(dev, 0x58, &pmbase);
+
+ pmbase &= 0x0000FF00;
+
+ if (pmbase == 0)
+ {
+ printk (KERN_ERR PFX "power management base not set\n");
+ rc = -EIO;
+ goto err_out;
+ }
+
+ pci_read_config_byte(dev, 0x40, &rnen);
+ rnen |= (1 << 7); /* RNG on */
+ pci_write_config_byte(dev, 0x40, rnen);
+
+ pci_read_config_byte(dev, 0x41, &rnen);
+ rnen |= (1 << 7); /* PMIO enable */
+ pci_write_config_byte(dev, 0x41, rnen);
+
+ pr_info( PFX "AMD768 system management I/O registers at 0x%X.\n",
+ pmbase);
+
+ amd_dev = dev;
+
+ DPRINTK ("EXIT, returning 0\n");
+ return 0;
+
+err_out:
+ DPRINTK ("EXIT, returning %d\n", rc);
+ return rc;
+}
+
+static void amd_cleanup(void)
+{
+ u8 rnen;
+
+ pci_read_config_byte(amd_dev, 0x40, &rnen);
+ rnen &= ~(1 << 7); /* RNG off */
+ pci_write_config_byte(amd_dev, 0x40, rnen);
+
+ /* FIXME: twiddle pmio, also? */
+}
+
+#ifdef __i386__
+/***********************************************************************
+ *
+ * VIA RNG operations
+ *
+ */
+
+enum {
+ VIA_STRFILT_CNT_SHIFT = 16,
+ VIA_STRFILT_FAIL = (1 << 15),
+ VIA_STRFILT_ENABLE = (1 << 14),
+ VIA_RAWBITS_ENABLE = (1 << 13),
+ VIA_RNG_ENABLE = (1 << 6),
+ VIA_XSTORE_CNT_MASK = 0x0F,
+
+ VIA_RNG_CHUNK_8 = 0x00, /* 64 rand bits, 64 stored bits */
+ VIA_RNG_CHUNK_4 = 0x01, /* 32 rand bits, 32 stored bits */
+ VIA_RNG_CHUNK_4_MASK = 0xFFFFFFFF,
+ VIA_RNG_CHUNK_2 = 0x02, /* 16 rand bits, 32 stored bits */
+ VIA_RNG_CHUNK_2_MASK = 0xFFFF,
+ VIA_RNG_CHUNK_1 = 0x03, /* 8 rand bits, 32 stored bits */
+ VIA_RNG_CHUNK_1_MASK = 0xFF,
+};
+
+static u32 via_rng_datum;
+
+/*
+ * Investigate using the 'rep' prefix to obtain 32 bits of random data
+ * in one insn. The upside is potentially better performance. The
+ * downside is that the instruction becomes no longer atomic. Due to
+ * this, just like familiar issues with /dev/random itself, the worst
+ * case of a 'rep xstore' could potentially pause a cpu for an
+ * unreasonably long time. In practice, this condition would likely
+ * only occur when the hardware is failing. (or so we hope :))
+ *
+ * Another possible performance boost may come from simply buffering
+ * until we have 4 bytes, thus returning a u32 at a time,
+ * instead of the current u8-at-a-time.
+ */
+
+static inline u32 xstore(u32 *addr, u32 edx_in)
+{
+ u32 eax_out;
+
+ asm(".byte 0x0F,0xA7,0xC0 /* xstore %%edi (addr=%0) */"
+ :"=m"(*addr), "=a"(eax_out)
+ :"D"(addr), "d"(edx_in));
+
+ return eax_out;
+}
+
+static unsigned int via_data_present(void)
+{
+ u32 bytes_out;
+
+ /* We choose the recommended 1-byte-per-instruction RNG rate,
+ * for greater randomness at the expense of speed. Larger
+ * values 2, 4, or 8 bytes-per-instruction yield greater
+ * speed at lesser randomness.
+ *
+ * If you change this to another VIA_CHUNK_n, you must also
+ * change the ->n_bytes values in rng_vendor_ops[] tables.
+ * VIA_CHUNK_8 requires further code changes.
+ *
+ * A copy of MSR_VIA_RNG is placed in eax_out when xstore
+ * completes.
+ */
+ via_rng_datum = 0; /* paranoia, not really necessary */
+ bytes_out = xstore(&via_rng_datum, VIA_RNG_CHUNK_1) & VIA_XSTORE_CNT_MASK;
+ if (bytes_out == 0)
+ return 0;
+
+ return 1;
+}
+
+static u32 via_data_read(void)
+{
+ return via_rng_datum;
+}
+
+static int __init via_init(struct pci_dev *dev)
+{
+ u32 lo, hi, old_lo;
+
+ /* Control the RNG via MSR. Tread lightly and pay very close
+ * close attention to values written, as the reserved fields
+ * are documented to be "undefined and unpredictable"; but it
+ * does not say to write them as zero, so I make a guess that
+ * we restore the values we find in the register.
+ */
+ rdmsr(MSR_VIA_RNG, lo, hi);
+
+ old_lo = lo;
+ lo &= ~(0x7f << VIA_STRFILT_CNT_SHIFT);
+ lo &= ~VIA_XSTORE_CNT_MASK;
+ lo &= ~(VIA_STRFILT_ENABLE | VIA_STRFILT_FAIL | VIA_RAWBITS_ENABLE);
+ lo |= VIA_RNG_ENABLE;
+
+ if (lo != old_lo)
+ wrmsr(MSR_VIA_RNG, lo, hi);
+
+ /* perhaps-unnecessary sanity check; remove after testing if
+ unneeded */
+ rdmsr(MSR_VIA_RNG, lo, hi);
+ if ((lo & VIA_RNG_ENABLE) == 0) {
+ printk(KERN_ERR PFX "cannot enable VIA C3 RNG, aborting\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void via_cleanup(void)
+{
+ /* do nothing */
+}
+#endif
+
+
+/***********************************************************************
+ *
+ * /dev/hwrandom character device handling (major 10, minor 183)
+ *
+ */
+
+static int rng_dev_open (struct inode *inode, struct file *filp)
+{
+ /* enforce read-only access to this chrdev */
+ if ((filp->f_mode & FMODE_READ) == 0)
+ return -EINVAL;
+ if (filp->f_mode & FMODE_WRITE)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size,
+ loff_t * offp)
+{
+ static DEFINE_SPINLOCK(rng_lock);
+ unsigned int have_data;
+ u32 data = 0;
+ ssize_t ret = 0;
+
+ while (size) {
+ spin_lock(&rng_lock);
+
+ have_data = 0;
+ if (rng_ops->data_present()) {
+ data = rng_ops->data_read();
+ have_data = rng_ops->n_bytes;
+ }
+
+ spin_unlock (&rng_lock);
+
+ while (have_data && size) {
+ if (put_user((u8)data, buf++)) {
+ ret = ret ? : -EFAULT;
+ break;
+ }
+ size--;
+ ret++;
+ have_data--;
+ data>>=8;
+ }
+
+ if (filp->f_flags & O_NONBLOCK)
+ return ret ? : -EAGAIN;
+
+ if(need_resched())
+ {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+ }
+ else
+ udelay(200); /* FIXME: We could poll for 250uS ?? */
+
+ if (signal_pending (current))
+ return ret ? : -ERESTARTSYS;
+ }
+ return ret;
+}
+
+
+
+/*
+ * rng_init_one - look for and attempt to init a single RNG
+ */
+static int __init rng_init_one (struct pci_dev *dev)
+{
+ int rc;
+
+ DPRINTK ("ENTER\n");
+
+ assert(rng_ops != NULL);
+
+ rc = rng_ops->init(dev);
+ if (rc)
+ goto err_out;
+
+ rc = misc_register (&rng_miscdev);
+ if (rc) {
+ printk (KERN_ERR PFX "misc device register failed\n");
+ goto err_out_cleanup_hw;
+ }
+
+ DPRINTK ("EXIT, returning 0\n");
+ return 0;
+
+err_out_cleanup_hw:
+ rng_ops->cleanup();
+err_out:
+ DPRINTK ("EXIT, returning %d\n", rc);
+ return rc;
+}
+
+
+
+MODULE_AUTHOR("The Linux Kernel team");
+MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * rng_init - initialize RNG module
+ */
+static int __init rng_init (void)
+{
+ int rc;
+ struct pci_dev *pdev = NULL;
+ const struct pci_device_id *ent;
+
+ DPRINTK ("ENTER\n");
+
+ /* Probe for Intel, AMD RNGs */
+ for_each_pci_dev(pdev) {
+ ent = pci_match_device (rng_pci_tbl, pdev);
+ if (ent) {
+ rng_ops = &rng_vendor_ops[ent->driver_data];
+ goto match;
+ }
+ }
+
+#ifdef __i386__
+ /* Probe for VIA RNG */
+ if (cpu_has_xstore) {
+ rng_ops = &rng_vendor_ops[rng_hw_via];
+ pdev = NULL;
+ goto match;
+ }
+#endif
+
+ DPRINTK ("EXIT, returning -ENODEV\n");
+ return -ENODEV;
+
+match:
+ rc = rng_init_one (pdev);
+ if (rc)
+ return rc;
+
+ pr_info( RNG_DRIVER_NAME " loaded\n");
+
+ DPRINTK ("EXIT, returning 0\n");
+ return 0;
+}
+
+
+/*
+ * rng_init - shutdown RNG module
+ */
+static void __exit rng_cleanup (void)
+{
+ DPRINTK ("ENTER\n");
+
+ misc_deregister (&rng_miscdev);
+
+ if (rng_ops->cleanup)
+ rng_ops->cleanup();
+
+ DPRINTK ("EXIT\n");
+}
+
+
+module_init (rng_init);
+module_exit (rng_cleanup);
diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
new file mode 100644
index 000000000000..a81197640283
--- /dev/null
+++ b/drivers/char/i8k.c
@@ -0,0 +1,788 @@
+/*
+ * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ * See http://www.debian.org/~dz/i8k/ for more information
+ * and for latest version of this driver.
+ *
+ * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/apm_bios.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/i8k.h>
+
+#define I8K_VERSION "1.13 14/05/2002"
+
+#define I8K_SMM_FN_STATUS 0x0025
+#define I8K_SMM_POWER_STATUS 0x0069
+#define I8K_SMM_SET_FAN 0x01a3
+#define I8K_SMM_GET_FAN 0x00a3
+#define I8K_SMM_GET_SPEED 0x02a3
+#define I8K_SMM_GET_TEMP 0x10a3
+#define I8K_SMM_GET_DELL_SIG 0xffa3
+#define I8K_SMM_BIOS_VERSION 0x00a6
+
+#define I8K_FAN_MULT 30
+#define I8K_MAX_TEMP 127
+
+#define I8K_FN_NONE 0x00
+#define I8K_FN_UP 0x01
+#define I8K_FN_DOWN 0x02
+#define I8K_FN_MUTE 0x04
+#define I8K_FN_MASK 0x07
+#define I8K_FN_SHIFT 8
+
+#define I8K_POWER_AC 0x05
+#define I8K_POWER_BATTERY 0x01
+
+#define I8K_TEMPERATURE_BUG 1
+
+#define DELL_SIGNATURE "Dell Computer"
+
+static char *supported_models[] = {
+ "Inspiron",
+ "Latitude",
+ NULL
+};
+
+static char system_vendor[48] = "?";
+static char product_name [48] = "?";
+static char bios_version [4] = "?";
+static char serial_number[16] = "?";
+
+MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_LICENSE("GPL");
+
+static int force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+
+static int restricted;
+module_param(restricted, bool, 0);
+MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
+
+static int power_status;
+module_param(power_status, bool, 0600);
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+
+static ssize_t i8k_read(struct file *, char __user *, size_t, loff_t *);
+static int i8k_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+
+static struct file_operations i8k_fops = {
+ .read = i8k_read,
+ .ioctl = i8k_ioctl,
+};
+
+typedef struct {
+ unsigned int eax;
+ unsigned int ebx __attribute__ ((packed));
+ unsigned int ecx __attribute__ ((packed));
+ unsigned int edx __attribute__ ((packed));
+ unsigned int esi __attribute__ ((packed));
+ unsigned int edi __attribute__ ((packed));
+} SMMRegisters;
+
+typedef struct {
+ u8 type;
+ u8 length;
+ u16 handle;
+} DMIHeader;
+
+/*
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
+ */
+static int i8k_smm(SMMRegisters *regs)
+{
+ int rc;
+ int eax = regs->eax;
+
+ asm("pushl %%eax\n\t" \
+ "movl 0(%%eax),%%edx\n\t" \
+ "push %%edx\n\t" \
+ "movl 4(%%eax),%%ebx\n\t" \
+ "movl 8(%%eax),%%ecx\n\t" \
+ "movl 12(%%eax),%%edx\n\t" \
+ "movl 16(%%eax),%%esi\n\t" \
+ "movl 20(%%eax),%%edi\n\t" \
+ "popl %%eax\n\t" \
+ "out %%al,$0xb2\n\t" \
+ "out %%al,$0x84\n\t" \
+ "xchgl %%eax,(%%esp)\n\t"
+ "movl %%ebx,4(%%eax)\n\t" \
+ "movl %%ecx,8(%%eax)\n\t" \
+ "movl %%edx,12(%%eax)\n\t" \
+ "movl %%esi,16(%%eax)\n\t" \
+ "movl %%edi,20(%%eax)\n\t" \
+ "popl %%edx\n\t" \
+ "movl %%edx,0(%%eax)\n\t" \
+ "lahf\n\t" \
+ "shrl $8,%%eax\n\t" \
+ "andl $1,%%eax\n" \
+ : "=a" (rc)
+ : "a" (regs)
+ : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+
+ if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff) || (regs->eax == eax)) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Read the bios version. Return the version as an integer corresponding
+ * to the ascii value, for example "A17" is returned as 0x00413137.
+ */
+static int i8k_get_bios_version(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_BIOS_VERSION;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ return regs.eax;
+}
+
+/*
+ * Read the machine id.
+ */
+static int i8k_get_serial_number(unsigned char *buff)
+{
+ strlcpy(buff, serial_number, sizeof(serial_number));
+ return 0;
+}
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_FN_STATUS;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+ case I8K_FN_UP:
+ return I8K_VOL_UP;
+ case I8K_FN_DOWN:
+ return I8K_VOL_DOWN;
+ case I8K_FN_MUTE:
+ return I8K_VOL_MUTE;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_POWER_STATUS;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ switch (regs.eax & 0xff) {
+ case I8K_POWER_AC:
+ return I8K_AC;
+ default:
+ return I8K_BATTERY;
+ }
+}
+
+/*
+ * Read the fan status.
+ */
+static int i8k_get_fan_status(int fan)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_GET_FAN;
+ regs.ebx = fan & 0xff;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ return (regs.eax & 0xff);
+}
+
+/*
+ * Read the fan speed in RPM.
+ */
+static int i8k_get_fan_speed(int fan)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_GET_SPEED;
+ regs.ebx = fan & 0xff;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ return (regs.eax & 0xffff) * I8K_FAN_MULT;
+}
+
+/*
+ * Set the fan speed (off, low, high). Returns the new fan status.
+ */
+static int i8k_set_fan(int fan, int speed)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
+
+ regs.eax = I8K_SMM_SET_FAN;
+ regs.ebx = (fan & 0xff) | (speed << 8);
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ return (i8k_get_fan_status(fan));
+}
+
+/*
+ * Read the cpu temperature.
+ */
+static int i8k_get_cpu_temp(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+ int temp;
+
+#ifdef I8K_TEMPERATURE_BUG
+ static int prev = 0;
+#endif
+
+ regs.eax = I8K_SMM_GET_TEMP;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+ temp = regs.eax & 0xff;
+
+#ifdef I8K_TEMPERATURE_BUG
+ /*
+ * Sometimes the temperature sensor returns 0x99, which is out of range.
+ * In this case we return (once) the previous cached value. For example:
+ # 1003655137 00000058 00005a4b
+ # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
+ # 1003655139 00000054 00005c52
+ */
+ if (temp > I8K_MAX_TEMP) {
+ temp = prev;
+ prev = I8K_MAX_TEMP;
+ } else {
+ prev = temp;
+ }
+#endif
+
+ return temp;
+}
+
+static int i8k_get_dell_signature(void)
+{
+ SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+ int rc;
+
+ regs.eax = I8K_SMM_GET_DELL_SIG;
+ if ((rc=i8k_smm(&regs)) < 0) {
+ return rc;
+ }
+
+ if ((regs.eax == 1145651527) && (regs.edx == 1145392204)) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ int val;
+ int speed;
+ unsigned char buff[16];
+ int __user *argp = (int __user *)arg;
+
+ if (!argp)
+ return -EINVAL;
+
+ switch (cmd) {
+ case I8K_BIOS_VERSION:
+ val = i8k_get_bios_version();
+ break;
+
+ case I8K_MACHINE_ID:
+ memset(buff, 0, 16);
+ val = i8k_get_serial_number(buff);
+ break;
+
+ case I8K_FN_STATUS:
+ val = i8k_get_fn_status();
+ break;
+
+ case I8K_POWER_STATUS:
+ val = i8k_get_power_status();
+ break;
+
+ case I8K_GET_TEMP:
+ val = i8k_get_cpu_temp();
+ break;
+
+ case I8K_GET_SPEED:
+ if (copy_from_user(&val, argp, sizeof(int))) {
+ return -EFAULT;
+ }
+ val = i8k_get_fan_speed(val);
+ break;
+
+ case I8K_GET_FAN:
+ if (copy_from_user(&val, argp, sizeof(int))) {
+ return -EFAULT;
+ }
+ val = i8k_get_fan_status(val);
+ break;
+
+ case I8K_SET_FAN:
+ if (restricted && !capable(CAP_SYS_ADMIN)) {
+ return -EPERM;
+ }
+ if (copy_from_user(&val, argp, sizeof(int))) {
+ return -EFAULT;
+ }
+ if (copy_from_user(&speed, argp+1, sizeof(int))) {
+ return -EFAULT;
+ }
+ val = i8k_set_fan(val, speed);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (val < 0) {
+ return val;
+ }
+
+ switch (cmd) {
+ case I8K_BIOS_VERSION:
+ if (copy_to_user(argp, &val, 4)) {
+ return -EFAULT;
+ }
+ break;
+ case I8K_MACHINE_ID:
+ if (copy_to_user(argp, buff, 16)) {
+ return -EFAULT;
+ }
+ break;
+ default:
+ if (copy_to_user(argp, &val, sizeof(int))) {
+ return -EFAULT;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Print the information for /proc/i8k.
+ */
+static int i8k_get_info(char *buffer, char **start, off_t fpos, int length)
+{
+ int n, fn_key, cpu_temp, ac_power;
+ int left_fan, right_fan, left_speed, right_speed;
+
+ cpu_temp = i8k_get_cpu_temp(); /* 11100 µs */
+ left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */
+ right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */
+ left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */
+ right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */
+ fn_key = i8k_get_fn_status(); /* 750 µs */
+ if (power_status) {
+ ac_power = i8k_get_power_status(); /* 14700 µs */
+ } else {
+ ac_power = -1;
+ }
+
+ /*
+ * Info:
+ *
+ * 1) Format version (this will change if format changes)
+ * 2) BIOS version
+ * 3) BIOS machine ID
+ * 4) Cpu temperature
+ * 5) Left fan status
+ * 6) Right fan status
+ * 7) Left fan speed
+ * 8) Right fan speed
+ * 9) AC power
+ * 10) Fn Key status
+ */
+ n = sprintf(buffer, "%s %s %s %d %d %d %d %d %d %d\n",
+ I8K_PROC_FMT,
+ bios_version,
+ serial_number,
+ cpu_temp,
+ left_fan,
+ right_fan,
+ left_speed,
+ right_speed,
+ ac_power,
+ fn_key);
+
+ return n;
+}
+
+static ssize_t i8k_read(struct file *f, char __user *buffer, size_t len, loff_t *fpos)
+{
+ int n;
+ char info[128];
+
+ n = i8k_get_info(info, NULL, 0, 128);
+ if (n <= 0) {
+ return n;
+ }
+
+ if (*fpos >= n) {
+ return 0;
+ }
+
+ if ((*fpos + len) >= n) {
+ len = n - *fpos;
+ }
+
+ if (copy_to_user(buffer, info, len) != 0) {
+ return -EFAULT;
+ }
+
+ *fpos += len;
+ return len;
+}
+
+static char* __init string_trim(char *s, int size)
+{
+ int len;
+ char *p;
+
+ if ((len = strlen(s)) > size) {
+ len = size;
+ }
+
+ for (p=s+len-1; len && (*p==' '); len--,p--) {
+ *p = '\0';
+ }
+
+ return s;
+}
+
+/* DMI code, stolen from arch/i386/kernel/dmi_scan.c */
+
+/*
+ * |<-- dmi->length -->|
+ * | |
+ * |dmi header s=N | string1,\0, ..., stringN,\0, ..., \0
+ * | |
+ * +-----------------------+
+ */
+static char* __init dmi_string(DMIHeader *dmi, u8 s)
+{
+ u8 *p;
+
+ if (!s) {
+ return "";
+ }
+ s--;
+
+ p = (u8 *)dmi + dmi->length;
+ while (s > 0) {
+ p += strlen(p);
+ p++;
+ s--;
+ }
+
+ return p;
+}
+
+static void __init dmi_decode(DMIHeader *dmi)
+{
+ u8 *data = (u8 *) dmi;
+ char *p;
+
+#ifdef I8K_DEBUG
+ int i;
+ printk("%08x ", (int)data);
+ for (i=0; i<data[1] && i<64; i++) {
+ printk("%02x ", data[i]);
+ }
+ printk("\n");
+#endif
+
+ switch (dmi->type) {
+ case 0: /* BIOS Information */
+ p = dmi_string(dmi,data[5]);
+ if (*p) {
+ strlcpy(bios_version, p, sizeof(bios_version));
+ string_trim(bios_version, sizeof(bios_version));
+ }
+ break;
+ case 1: /* System Information */
+ p = dmi_string(dmi,data[4]);
+ if (*p) {
+ strlcpy(system_vendor, p, sizeof(system_vendor));
+ string_trim(system_vendor, sizeof(system_vendor));
+ }
+ p = dmi_string(dmi,data[5]);
+ if (*p) {
+ strlcpy(product_name, p, sizeof(product_name));
+ string_trim(product_name, sizeof(product_name));
+ }
+ p = dmi_string(dmi,data[7]);
+ if (*p) {
+ strlcpy(serial_number, p, sizeof(serial_number));
+ string_trim(serial_number, sizeof(serial_number));
+ }
+ break;
+ }
+}
+
+static int __init dmi_table(u32 base, int len, int num, void (*fn)(DMIHeader*))
+{
+ u8 *buf;
+ u8 *data;
+ DMIHeader *dmi;
+ int i = 1;
+
+ buf = ioremap(base, len);
+ if (buf == NULL) {
+ return -1;
+ }
+ data = buf;
+
+ /*
+ * Stop when we see al the items the table claimed to have
+ * or we run off the end of the table (also happens)
+ */
+ while ((i<num) && ((data-buf) < len)) {
+ dmi = (DMIHeader *)data;
+ /*
+ * Avoid misparsing crud if the length of the last
+ * record is crap
+ */
+ if ((data-buf+dmi->length) >= len) {
+ break;
+ }
+ fn(dmi);
+ data += dmi->length;
+ /*
+ * Don't go off the end of the data if there is
+ * stuff looking like string fill past the end
+ */
+ while (((data-buf) < len) && (*data || data[1])) {
+ data++;
+ }
+ data += 2;
+ i++;
+ }
+ iounmap(buf);
+
+ return 0;
+}
+
+static int __init dmi_iterate(void (*decode)(DMIHeader *))
+{
+ unsigned char buf[20];
+ void __iomem *p = ioremap(0xe0000, 0x20000), *q;
+
+ if (!p)
+ return -1;
+
+ for (q = p; q < p + 0x20000; q += 16) {
+ memcpy_fromio(buf, q, 20);
+ if (memcmp(buf, "_DMI_", 5)==0) {
+ u16 num = buf[13]<<8 | buf[12];
+ u16 len = buf [7]<<8 | buf [6];
+ u32 base = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8];
+#ifdef I8K_DEBUG
+ printk(KERN_INFO "DMI %d.%d present.\n",
+ buf[14]>>4, buf[14]&0x0F);
+ printk(KERN_INFO "%d structures occupying %d bytes.\n",
+ buf[13]<<8 | buf[12],
+ buf [7]<<8 | buf[6]);
+ printk(KERN_INFO "DMI table at 0x%08X.\n",
+ buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]);
+#endif
+ if (dmi_table(base, len, num, decode)==0) {
+ iounmap(p);
+ return 0;
+ }
+ }
+ }
+ iounmap(p);
+ return -1;
+}
+/* end of DMI code */
+
+/*
+ * Get DMI information.
+ */
+static int __init i8k_dmi_probe(void)
+{
+ char **p;
+
+ if (dmi_iterate(dmi_decode) != 0) {
+ printk(KERN_INFO "i8k: unable to get DMI information\n");
+ return -ENODEV;
+ }
+
+ if (strncmp(system_vendor,DELL_SIGNATURE,strlen(DELL_SIGNATURE)) != 0) {
+ printk(KERN_INFO "i8k: not running on a Dell system\n");
+ return -ENODEV;
+ }
+
+ for (p=supported_models; ; p++) {
+ if (!*p) {
+ printk(KERN_INFO "i8k: unsupported model: %s\n", product_name);
+ return -ENODEV;
+ }
+ if (strncmp(product_name,*p,strlen(*p)) == 0) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+ char buff[4];
+ int version;
+ int smm_found = 0;
+
+ /*
+ * Get DMI information
+ */
+ if (i8k_dmi_probe() != 0) {
+ printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%s\n",
+ system_vendor, product_name, bios_version);
+ }
+
+ /*
+ * Get SMM Dell signature
+ */
+ if (i8k_get_dell_signature() != 0) {
+ printk(KERN_INFO "i8k: unable to get SMM Dell signature\n");
+ } else {
+ smm_found = 1;
+ }
+
+ /*
+ * Get SMM BIOS version.
+ */
+ version = i8k_get_bios_version();
+ if (version <= 0) {
+ printk(KERN_INFO "i8k: unable to get SMM BIOS version\n");
+ } else {
+ smm_found = 1;
+ buff[0] = (version >> 16) & 0xff;
+ buff[1] = (version >> 8) & 0xff;
+ buff[2] = (version) & 0xff;
+ buff[3] = '\0';
+ /*
+ * If DMI BIOS version is unknown use SMM BIOS version.
+ */
+ if (bios_version[0] == '?') {
+ strcpy(bios_version, buff);
+ }
+ /*
+ * Check if the two versions match.
+ */
+ if (strncmp(buff,bios_version,sizeof(bios_version)) != 0) {
+ printk(KERN_INFO "i8k: BIOS version mismatch: %s != %s\n",
+ buff, bios_version);
+ }
+ }
+
+ if (!smm_found && !force) {
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+#ifdef MODULE
+static
+#endif
+int __init i8k_init(void)
+{
+ struct proc_dir_entry *proc_i8k;
+
+ /* Are we running on an supported laptop? */
+ if (i8k_probe() != 0) {
+ return -ENODEV;
+ }
+
+ /* Register the proc entry */
+ proc_i8k = create_proc_info_entry("i8k", 0, NULL, i8k_get_info);
+ if (!proc_i8k) {
+ return -ENOENT;
+ }
+ proc_i8k->proc_fops = &i8k_fops;
+ proc_i8k->owner = THIS_MODULE;
+
+ printk(KERN_INFO
+ "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
+ I8K_VERSION);
+
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return i8k_init();
+}
+
+void cleanup_module(void)
+{
+ /* Remove the proc entry */
+ remove_proc_entry("i8k", NULL);
+
+ printk(KERN_INFO "i8k: module unloaded\n");
+}
+#endif
+
+/* end of file */
diff --git a/drivers/char/ip2.c b/drivers/char/ip2.c
new file mode 100644
index 000000000000..6cd12f23aa58
--- /dev/null
+++ b/drivers/char/ip2.c
@@ -0,0 +1,110 @@
+// ip2.c
+// This is a dummy module to make the firmware available when needed
+// and allows it to be unloaded when not. Rumor is the __initdata
+// macro doesn't always works on all platforms so we use this kludge.
+// If not compiled as a module it just makes fip_firm avaliable then
+// __initdata should work as advertized
+//
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+
+#ifndef __init
+#define __init
+#endif
+#ifndef __initfunc
+#define __initfunc(a) a
+#endif
+#ifndef __initdata
+#define __initdata
+#endif
+
+#include "./ip2/ip2types.h"
+#include "./ip2/fip_firm.h" // the meat
+
+int
+ip2_loadmain(int *, int *, unsigned char *, int ); // ref into ip2main.c
+
+/* Note: Add compiled in defaults to these arrays, not to the structure
+ in ip2/ip2.h any longer. That structure WILL get overridden
+ by these values, or command line values, or insmod values!!! =mhw=
+*/
+static int io[IP2_MAX_BOARDS]= { 0, 0, 0, 0 };
+static int irq[IP2_MAX_BOARDS] = { -1, -1, -1, -1 };
+
+static int poll_only = 0;
+
+MODULE_AUTHOR("Doug McNash");
+MODULE_DESCRIPTION("Computone IntelliPort Plus Driver");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq,"Interrupts for IntelliPort Cards");
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io,"I/O ports for IntelliPort Cards");
+module_param(poll_only, bool, 0);
+MODULE_PARM_DESC(poll_only,"Do not use card interrupts");
+
+
+static int __init ip2_init(void)
+{
+ if( poll_only ) {
+ /* Hard lock the interrupts to zero */
+ irq[0] = irq[1] = irq[2] = irq[3] = 0;
+ }
+
+ return ip2_loadmain(io,irq,(unsigned char *)fip_firm,sizeof(fip_firm));
+}
+module_init(ip2_init);
+
+MODULE_LICENSE("GPL");
+
+#ifndef MODULE
+/******************************************************************************
+ * ip2_setup:
+ * str: kernel command line string
+ *
+ * Can't autoprobe the boards so user must specify configuration on
+ * kernel command line. Sane people build it modular but the others
+ * come here.
+ *
+ * Alternating pairs of io,irq for up to 4 boards.
+ * ip2=io0,irq0,io1,irq1,io2,irq2,io3,irq3
+ *
+ * io=0 => No board
+ * io=1 => PCI
+ * io=2 => EISA
+ * else => ISA I/O address
+ *
+ * irq=0 or invalid for ISA will revert to polling mode
+ *
+ * Any value = -1, do not overwrite compiled in value.
+ *
+ ******************************************************************************/
+static int __init ip2_setup(char *str)
+{
+ int ints[10]; /* 4 boards, 2 parameters + 2 */
+ int i, j;
+
+ str = get_options (str, ARRAY_SIZE(ints), ints);
+
+ for( i = 0, j = 1; i < 4; i++ ) {
+ if( j > ints[0] ) {
+ break;
+ }
+ if( ints[j] >= 0 ) {
+ io[i] = ints[j];
+ }
+ j++;
+ if( j > ints[0] ) {
+ break;
+ }
+ if( ints[j] >= 0 ) {
+ irq[i] = ints[j];
+ }
+ j++;
+ }
+ return 1;
+}
+__setup("ip2=", ip2_setup);
+#endif /* !MODULE */
diff --git a/drivers/char/ip2/fip_firm.h b/drivers/char/ip2/fip_firm.h
new file mode 100644
index 000000000000..4c525fa4929f
--- /dev/null
+++ b/drivers/char/ip2/fip_firm.h
@@ -0,0 +1,2149 @@
+/* fip_firm.h - Intelliport II loadware */
+/* -31232 bytes read from ff.lod */
+
+static unsigned char fip_firm[] __initdata = {
+0x3C,0x42,0x37,0x18,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x57,0x65,0x64,0x20,0x44,0x65,0x63,0x20,0x30,0x31,0x20,0x31,0x32,0x3A,0x32,0x34,
+0x3A,0x33,0x30,0x20,0x31,0x39,0x39,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xE9,0x6C,0x0F,0x42,0x65,0x47,0x69,0x4E,0x6E,0x49,0x6E,0x47,0x20,0x6F,0x46,0x20,
+0x63,0x4F,0x64,0x45,0xCC,0x13,0x5A,0x15,0xE8,0x16,0x76,0x18,0x04,0x1A,0x92,0x1B,
+0x20,0x1D,0xAE,0x1E,0x3C,0x20,0xCA,0x21,0x58,0x23,0xE6,0x24,0x74,0x26,0x02,0x28,
+0x90,0x29,0x1E,0x2B,0xAC,0x2C,0x3A,0x2E,0xC8,0x2F,0x56,0x31,0xE4,0x32,0x72,0x34,
+0x00,0x36,0x8E,0x37,0x1C,0x39,0xAA,0x3A,0x38,0x3C,0xC6,0x3D,0x54,0x3F,0xE2,0x40,
+0x70,0x42,0xFE,0x43,0x8C,0x45,0x1A,0x47,0xA8,0x48,0x36,0x4A,0xC4,0x4B,0x52,0x4D,
+0xE0,0x4E,0x6E,0x50,0xFC,0x51,0x8A,0x53,0x18,0x55,0xA6,0x56,0x34,0x58,0xC2,0x59,
+0x50,0x5B,0xDE,0x5C,0x6C,0x5E,0xFA,0x5F,0x88,0x61,0x16,0x63,0xA4,0x64,0x32,0x66,
+0xC0,0x67,0x4E,0x69,0xDC,0x6A,0x6A,0x6C,0xF8,0x6D,0x86,0x6F,0x14,0x71,0xA2,0x72,
+0x30,0x74,0xBE,0x75,0x4C,0x77,0x6C,0x77,0x8C,0x77,0xAC,0x77,0x33,0xDB,0x8A,0xDC,
+0x53,0x33,0xDB,0x25,0x07,0x00,0x75,0x0A,0x8A,0x1E,0x08,0x01,0x83,0xE3,0x0C,0xEB,
+0x20,0x90,0x3C,0x01,0x75,0x0A,0x8A,0x1E,0x08,0x01,0x80,0xE3,0xC0,0xEB,0x12,0x90,
+0x8A,0x1E,0x0D,0x01,0x3C,0x02,0x75,0x06,0x80,0xE3,0x0C,0xEB,0x04,0x90,0x80,0xE3,
+0xC0,0x53,0x50,0x8B,0x1E,0xBA,0x13,0x8E,0xDB,0xE8,0x6A,0x65,0x55,0x8B,0xEC,0x53,
+0x1E,0x2B,0xC0,0x8E,0xD8,0x8B,0x5E,0x04,0xC1,0xE3,0x04,0x03,0x5E,0x06,0xD1,0xE3,
+0x2E,0x8B,0x9F,0x44,0x00,0x8D,0x47,0x2A,0x1E,0x5A,0x1F,0x5B,0x5D,0xC3,0x55,0x8B,
+0xEC,0x53,0x1E,0x2B,0xC0,0x8E,0xD8,0x8B,0x5E,0x04,0xC1,0xE3,0x04,0x03,0x5E,0x06,
+0xD1,0xE3,0x2E,0x8B,0x9F,0x44,0x00,0x8D,0x47,0x34,0x1E,0x5A,0x1F,0x5B,0x5D,0xC3,
+0xFB,0x55,0x8B,0xEC,0x53,0x51,0x52,0x56,0x57,0x1E,0x06,0x1E,0x07,0x33,0xC0,0x8E,
+0xD8,0x8B,0x5E,0x04,0x26,0x8A,0x47,0x59,0x25,0x03,0x00,0x8B,0xF0,0xD1,0xE6,0x2E,
+0x8B,0xB4,0xC4,0x00,0xC1,0xE0,0x04,0x26,0x02,0x47,0x1A,0xD1,0xE0,0x8B,0xE8,0x2E,
+0x8B,0xAE,0x44,0x00,0x89,0x2C,0x26,0x8A,0x47,0x1C,0x88,0x44,0x0F,0x26,0x8A,0x47,
+0x1D,0x88,0x44,0x10,0x26,0x8A,0x47,0x1E,0x88,0x44,0x11,0x26,0x8A,0x47,0x1F,0x88,
+0x44,0x12,0x26,0x8A,0x47,0x20,0x88,0x44,0x13,0x26,0x8A,0x47,0x23,0x88,0x44,0x14,
+0x26,0x8A,0x47,0x24,0x88,0x44,0x15,0x26,0x8A,0x47,0x5A,0x88,0x44,0x0E,0x33,0xC0,
+0x89,0x44,0x06,0x89,0x44,0x08,0x88,0x44,0x0B,0x88,0x44,0x0A,0xB0,0x21,0xB4,0x64,
+0x89,0x44,0x04,0x89,0x44,0x02,0xB0,0x55,0x88,0x44,0x0D,0x88,0x44,0x0C,0xE8,0x6A,
+0x00,0x72,0x5B,0xE8,0xC9,0x00,0xE8,0xC1,0x10,0x89,0x44,0x08,0x80,0x7C,0x0F,0x01,
+0x74,0x29,0xE8,0x2B,0x02,0xE8,0x7F,0x02,0x80,0x7C,0x0F,0x03,0x74,0x1D,0xE8,0xA9,
+0x10,0x8B,0xF8,0x2B,0x44,0x08,0x3D,0xA0,0x0F,0x72,0x10,0x89,0x7C,0x08,0x33,0xC0,
+0x87,0x44,0x06,0x85,0xC0,0x75,0x04,0xC6,0x44,0x0A,0xFF,0x8A,0x44,0x0A,0x84,0xC0,
+0x75,0x0B,0xB8,0x08,0x00,0xE8,0x6A,0x4A,0xE8,0xA9,0x01,0x73,0xBF,0xE8,0x4F,0x01,
+0x81,0x66,0x48,0x7F,0xFF,0x83,0x66,0x7A,0xBF,0xB0,0x02,0xE8,0x04,0x0E,0x8A,0x44,
+0x0A,0x98,0x07,0x1F,0x5F,0x5E,0x5A,0x59,0x5B,0x5D,0xC3,0x81,0x4E,0x48,0x80,0x00,
+0xB0,0x40,0xE8,0x3D,0x4A,0xE8,0x89,0x40,0x73,0x2A,0xE8,0x4D,0x10,0x8B,0xD8,0xB0,
+0x05,0xE8,0x2E,0x4A,0xF6,0x46,0x27,0x02,0x75,0x1A,0xE8,0x3D,0x10,0x2B,0xC3,0x3D,
+0x58,0x1B,0x72,0xEB,0x81,0x66,0x48,0x7F,0xFF,0xB0,0x02,0xE8,0xC4,0x0D,0xC6,0x44,
+0x0A,0x01,0xF9,0xC3,0x83,0x4E,0x7A,0x40,0xF8,0xC3,0xFB,0xB0,0x01,0xE8,0x02,0x4A,
+0xFA,0xE8,0x99,0x1E,0xE4,0x0A,0x84,0xC0,0x75,0xF0,0xB0,0x4E,0xE6,0x0A,0xFB,0xB0,
+0x01,0xE8,0xEE,0x49,0xFA,0xE8,0x85,0x1E,0xE4,0x0A,0x84,0xC0,0x75,0xF0,0xC3,0xFA,
+0xE8,0x7A,0x1E,0xE4,0xEC,0x88,0x44,0x16,0xE4,0xE4,0x88,0x44,0x17,0xE4,0xF8,0x88,
+0x44,0x18,0xE4,0xF0,0x88,0x44,0x19,0xE4,0x10,0x88,0x44,0x1A,0xE4,0x12,0x88,0x44,
+0x1B,0xE4,0x14,0x88,0x44,0x1C,0xE4,0x34,0x88,0x44,0x1D,0xE4,0x36,0x88,0x44,0x1E,
+0xE4,0xD8,0x24,0x01,0x8A,0xE0,0xE4,0xDA,0x24,0x02,0x0A,0xC4,0x88,0x44,0x1F,0x8A,
+0x44,0x10,0xE8,0xCD,0x1F,0x8A,0x44,0x11,0xE8,0x35,0x21,0x8A,0x44,0x12,0xE8,0x89,
+0x21,0x8A,0x44,0x13,0xE8,0x43,0x21,0xC6,0x86,0xA1,0x00,0x00,0xE4,0x14,0x24,0x10,
+0xE6,0x14,0xE4,0x12,0x24,0x3D,0xE6,0x12,0x8A,0x44,0x15,0x3C,0x01,0x72,0x1E,0x77,
+0x16,0xB0,0x11,0xE6,0x34,0xB0,0x13,0xE6,0x36,0xE4,0x14,0x0C,0x10,0xE6,0x14,0xE4,
+0x12,0x0C,0x40,0xE6,0x12,0xEB,0x06,0xE4,0x12,0x0C,0x02,0xE6,0x12,0x8A,0x44,0x0F,
+0x3C,0x01,0x74,0x06,0x3C,0x02,0x74,0x0A,0xEB,0x0E,0xE4,0x12,0x0C,0x08,0xE6,0x12,
+0xEB,0x06,0xE4,0x12,0x0C,0x10,0xE6,0x12,0xE8,0x2F,0xFF,0x8A,0x44,0x14,0x3C,0x02,
+0x75,0x08,0xB0,0x55,0x88,0x44,0x0C,0x88,0x44,0x0D,0xB0,0x21,0xB4,0x64,0x89,0x44,
+0x04,0x89,0x44,0x02,0xE4,0x0C,0x0C,0x10,0xE6,0x0C,0xE8,0xED,0x39,0xFB,0xC3,0xE8,
+0x5F,0x3F,0x73,0x08,0xFB,0xB0,0x0A,0xE8,0x08,0x49,0xEB,0xF3,0xFA,0xE8,0x9D,0x1D,
+0x8A,0x64,0x16,0x8A,0x44,0x17,0x89,0x86,0x94,0x00,0xE6,0xE4,0x8A,0xC4,0xE6,0xEC,
+0x8A,0x64,0x18,0x8A,0x44,0x19,0x89,0x86,0x96,0x00,0xE6,0xF0,0x8A,0xC4,0xE6,0xF8,
+0x8A,0x44,0x1A,0xE6,0x10,0x8A,0x44,0x1B,0xE6,0x12,0x8A,0x44,0x1C,0xE6,0x14,0x8A,
+0x44,0x1D,0xE6,0x34,0x8A,0x44,0x1E,0xE6,0x36,0x8A,0x44,0x1F,0xE6,0xD8,0xE6,0xDA,
+0xE9,0xB7,0xFE,0x90,0xFA,0x8A,0x44,0x0E,0xE6,0xFE,0xE4,0x02,0xA8,0x01,0x75,0x05,
+0x33,0xC0,0xFB,0xF8,0xC3,0x33,0xC0,0xE4,0x00,0xFB,0xF9,0xC3,0x8A,0x64,0x14,0x80,
+0xFC,0x02,0x74,0x2B,0xFE,0xC0,0xFE,0xC7,0x80,0xFF,0x4E,0x72,0x1C,0x74,0x09,0x80,
+0xFF,0x50,0x73,0x08,0xB0,0x0A,0xEB,0x17,0xB0,0x0D,0xEB,0x13,0x02,0xDC,0x32,0xFF,
+0x80,0xFB,0x7F,0x7C,0x02,0xB3,0x21,0x8A,0xC3,0x3C,0x7F,0x7C,0x02,0xB0,0x21,0xC3,
+0xFA,0x80,0x7C,0x0B,0x04,0x76,0x02,0xFB,0xC3,0x8B,0x46,0x24,0x3D,0x08,0x00,0x72,
+0xF6,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x8A,0x44,0x0C,0x8B,0x5C,0x02,0xAA,0xE8,0xAB,
+0xFF,0xAA,0xE8,0xA7,0xFF,0xAA,0xE8,0xA3,0xFF,0xAA,0xE8,0x9F,0xFF,0x88,0x44,0x0C,
+0x89,0x5C,0x02,0x80,0x44,0x0B,0x04,0x89,0x7E,0x22,0x83,0x6E,0x24,0x04,0x83,0x46,
+0x1A,0x04,0x80,0x7E,0x26,0x02,0x74,0x06,0x80,0x66,0x26,0xFD,0xFB,0xC3,0x60,0xB0,
+0xFD,0xE8,0x02,0x3F,0x61,0xFB,0xC3,0xFA,0x80,0x7C,0x0F,0x03,0x75,0x09,0xC6,0x44,
+0x0B,0x00,0xE8,0xE5,0x38,0xFB,0xC3,0xC4,0x7E,0x14,0x8B,0x4E,0x3A,0x85,0xC9,0x75,
+0x35,0x26,0x8B,0x0D,0x47,0x47,0xE3,0xEA,0x3B,0x7E,0x04,0x76,0x22,0xB8,0x02,0x00,
+0x39,0x46,0x2E,0x77,0x07,0xC7,0x46,0x2E,0x00,0x00,0xEB,0x13,0x8B,0x5E,0x2C,0x89,
+0x5E,0x04,0x26,0xC7,0x07,0x00,0x00,0x43,0x43,0x89,0x5E,0x2C,0x29,0x46,0x2E,0x85,
+0xC9,0x78,0xCE,0x89,0x4E,0x3A,0x8A,0x44,0x0D,0x8B,0x5C,0x04,0x26,0x8A,0x25,0x47,
+0x3A,0xC4,0x75,0x16,0xFE,0x4C,0x0B,0xFF,0x44,0x06,0xE8,0x0F,0xFF,0xE2,0xED,0x88,
+0x44,0x0D,0x89,0x5C,0x04,0x89,0x4E,0x3A,0xEB,0xA7,0xC6,0x44,0x0A,0xFE,0xE8,0x79,
+0x38,0xFB,0xC3,0x90,0xE8,0xB3,0x0D,0x8A,0xE8,0x8A,0x0E,0xCB,0x13,0xB3,0x07,0x8A,
+0xC1,0xEE,0xEB,0x00,0xEC,0x3A,0xC1,0x75,0x09,0x02,0xCD,0xFE,0xCB,0x75,0xF0,0xEB,
+0x0C,0x90,0x88,0x0E,0xCB,0x13,0x8A,0xE8,0xBB,0xFF,0xFF,0xF9,0xC3,0x88,0x0E,0xCB,
+0x13,0xF8,0xC3,0x90,0xBB,0x3F,0x3F,0x8A,0x8E,0x9E,0x00,0xBA,0xFE,0x00,0xEC,0x8A,
+0xE8,0x32,0xC1,0x22,0xC3,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x90,0xE8,0xE5,0xFF,0x73,
+0x01,0xC3,0xBA,0xD0,0x00,0xBB,0x03,0x03,0x8A,0x8E,0x9F,0x00,0xEC,0x8A,0xE8,0x32,
+0xC1,0x22,0xC3,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x90,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,
+0x80,0x3E,0xC8,0x13,0x00,0x75,0x07,0xB0,0x0A,0xE8,0x26,0x47,0xEB,0xF2,0xFB,0x33,
+0xDB,0x8A,0x1E,0xC9,0x13,0x43,0x43,0x83,0xFB,0x7E,0x76,0x07,0x33,0xDB,0xB0,0x02,
+0xE8,0x0F,0x47,0x2E,0x8B,0xAF,0x44,0x00,0x83,0x7E,0x08,0x00,0x74,0xE7,0x88,0x1E,
+0xC9,0x13,0xB0,0x02,0xE8,0xFB,0x46,0xFA,0xF7,0x46,0x38,0x40,0x00,0x74,0x14,0xE8,
+0x96,0x1B,0xE8,0x7F,0xFF,0x72,0x1C,0x33,0xD2,0x8A,0x96,0x9F,0x00,0x83,0xC2,0x0E,
+0xEB,0x0C,0x90,0xE8,0x77,0x1B,0xE8,0x83,0xFF,0x72,0x08,0xBA,0x48,0x00,0xE8,0x33,
+0xFF,0x73,0xAB,0x23,0xCB,0x89,0x8E,0x9A,0x00,0x89,0x96,0x9C,0x00,0xFE,0x86,0xB5,
+0x00,0xC6,0x06,0xC8,0x13,0x00,0xB0,0x0A,0xE8,0x67,0x0A,0xFB,0xEB,0x89,0x10,0x18,
+0x08,0x28,0x33,0xC0,0xA0,0x05,0x01,0x8A,0xC8,0x24,0x40,0x75,0x24,0xC7,0x06,0x7C,
+0x12,0x8E,0x45,0xC7,0x06,0x42,0x12,0x01,0x00,0xC6,0x06,0x54,0x12,0x02,0xB0,0x08,
+0xF6,0xC1,0x01,0x74,0x02,0xB0,0x04,0xA3,0x46,0x12,0xA2,0x4C,0x12,0xA2,0x94,0x12,
+0xC3,0xC7,0x06,0x7C,0x12,0xB6,0x45,0xA0,0x0F,0x01,0x84,0xC0,0x75,0x0E,0x6A,0x00,
+0x1F,0xC6,0x06,0x93,0x12,0x1E,0x9C,0x0E,0xE8,0xB1,0x0C,0x90,0xC7,0x06,0x44,0x12,
+0x01,0x00,0xA3,0x42,0x12,0x8B,0xD8,0xC1,0xE3,0x04,0x88,0x1E,0x94,0x12,0xBE,0xE2,
+0x05,0x2B,0xF0,0x8B,0xC8,0x33,0xDB,0x8B,0xFB,0x2E,0xAC,0x88,0x85,0x48,0x12,0x8A,
+0xD8,0x0C,0x05,0xE6,0xFE,0x8A,0xE0,0xEB,0x00,0xE4,0xFE,0x32,0xC4,0xA8,0x3F,0x74,
+0x03,0xE9,0x9E,0x00,0xE4,0x00,0x88,0x85,0x50,0x12,0x8A,0xE0,0x24,0x30,0xBA,0x10,
+0xFF,0x3C,0x30,0x74,0x12,0x80,0xFC,0x04,0x74,0x0A,0xBA,0x04,0x03,0xF6,0x06,0x08,
+0x01,0xFE,0x74,0x03,0xBA,0x08,0x0F,0x88,0x95,0x4C,0x12,0x02,0xFA,0x32,0xC0,0xF6,
+0xC4,0x08,0x74,0x02,0xB0,0x01,0x88,0x85,0x58,0x12,0x8A,0xC4,0x3C,0x35,0x74,0x5B,
+0x3C,0x36,0x74,0x57,0x3C,0x34,0x74,0x53,0x3C,0x04,0x74,0x4F,0x3C,0x14,0x74,0x4B,
+0x3C,0x15,0x74,0x47,0xA8,0x40,0x74,0x25,0xC6,0x85,0x54,0x12,0x04,0xD1,0xE7,0xB4,
+0x03,0x8A,0xC3,0x89,0x85,0x5C,0x12,0x8A,0xC3,0x8A,0xE3,0x80,0xCC,0x01,0x89,0x85,
+0x64,0x12,0xD1,0xEF,0x47,0xE2,0x03,0xEB,0x1A,0x90,0xE9,0x6C,0xFF,0xC6,0x85,0x54,
+0x12,0x02,0xD1,0xE7,0x8A,0xE6,0x8A,0xC3,0x0C,0x04,0x89,0x85,0x5C,0x12,0xD1,0xEF,
+0x47,0xE2,0xE7,0x33,0xC0,0x8A,0xC7,0xA3,0x46,0x12,0xC3,0xC6,0x85,0x54,0x12,0x06,
+0xEB,0xBB,0xC6,0x85,0x54,0x12,0x00,0x33,0xC0,0x88,0x85,0x50,0x12,0x88,0x85,0x4C,
+0x12,0x88,0x85,0x58,0x12,0xEB,0xA6,0xC7,0x46,0x26,0x02,0x12,0x8B,0x46,0x1E,0x89,
+0x46,0x00,0x89,0x46,0x22,0x8B,0x46,0x20,0x89,0x46,0x24,0xC7,0x46,0x1A,0x00,0x00,
+0xC3,0xC7,0x46,0x3C,0x80,0x00,0xC7,0x46,0x38,0x01,0x00,0x1E,0x56,0x8B,0x76,0x30,
+0x89,0x76,0x04,0x89,0x76,0x14,0x8E,0x5E,0x06,0x33,0xC0,0x89,0x04,0x46,0x46,0x89,
+0x76,0x2C,0x89,0x46,0x3A,0x8B,0x46,0x32,0x48,0x48,0x89,0x46,0x2E,0x5E,0x1F,0xC3,
+0x33,0xC0,0x89,0x46,0x48,0x89,0x46,0x4A,0xC7,0x46,0x46,0xAE,0x01,0x89,0x46,0x4E,
+0x8B,0x46,0x44,0x89,0x46,0x50,0x8B,0x46,0x42,0x89,0x46,0x40,0x89,0x46,0x08,0xC3,
+0x33,0xC0,0x89,0x46,0x76,0x89,0x46,0x78,0xC7,0x46,0x7A,0x10,0x00,0x56,0x1E,0x8B,
+0x76,0x70,0x89,0x76,0x10,0x89,0x76,0x0C,0x8E,0x5E,0x12,0xC7,0x04,0x00,0x00,0x8B,
+0x46,0x72,0x89,0x46,0x74,0x1F,0x5E,0xC3,0x89,0x56,0x18,0x89,0x56,0x02,0x89,0x56,
+0x06,0x89,0x56,0x0A,0x89,0x56,0x0E,0x89,0x56,0x12,0x89,0x56,0x16,0x8B,0xD8,0x4B,
+0x4B,0xC1,0xE3,0x02,0xBF,0x02,0x00,0x89,0x7E,0x1E,0x03,0xFB,0x89,0x7E,0x30,0x03,
+0xFB,0x89,0x7E,0x42,0x03,0xFB,0x89,0x7E,0x70,0x83,0xEB,0x08,0x89,0x5E,0x20,0x89,
+0x5E,0x32,0x89,0x5E,0x44,0x89,0x5E,0x72,0x50,0xE8,0x2B,0xFF,0xE8,0x71,0xFF,0xE8,
+0x3F,0xFF,0xE8,0x8B,0xFF,0x58,0xC3,0xB8,0x30,0x75,0xC1,0xE8,0x04,0x0E,0x5B,0x03,
+0xC3,0xA3,0xBA,0x13,0x83,0x3E,0x42,0x12,0x00,0x74,0x07,0x80,0x3E,0x94,0x12,0x00,
+0x75,0x0E,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x1E,0x9C,0x0E,0xE8,0xBD,0x0A,0x90,
+0xB8,0x30,0x7A,0xC1,0xE8,0x04,0x40,0xA3,0xC0,0x13,0x2B,0x06,0x12,0x01,0xF7,0xD8,
+0x33,0xD2,0x8B,0xCA,0x8A,0x0E,0x94,0x12,0xF7,0xF1,0x3D,0x80,0x00,0x77,0x0E,0x6A,
+0x00,0x1F,0xC6,0x06,0x93,0x12,0x25,0x9C,0x0E,0xE8,0x90,0x0A,0x90,0x48,0x3D,0xFF,
+0x07,0x72,0x03,0xB8,0xFF,0x07,0xA3,0xC2,0x13,0x33,0xC9,0x8A,0x0E,0x94,0x12,0x33,
+0xF6,0xB8,0x00,0x09,0x2E,0x8B,0xAC,0x44,0x00,0x89,0x46,0x4C,0x40,0x46,0x46,0xE2,
+0xF3,0x8A,0x0E,0x94,0x12,0x33,0xF6,0x8B,0x16,0xC0,0x13,0xA1,0xC2,0x13,0x2E,0x8B,
+0xAC,0x44,0x00,0xE8,0x22,0xFF,0x03,0xD0,0x46,0x46,0xE2,0xF2,0xC3,0x33,0xC0,0x2E,
+0x8B,0xAD,0x44,0x00,0x89,0x46,0x08,0x47,0x47,0xE2,0xF4,0xC3,0x51,0x33,0xC0,0x0A,
+0xC2,0x2E,0x8B,0xAD,0x44,0x00,0x89,0x86,0x9E,0x00,0x81,0x4E,0x38,0x00,0x20,0x47,
+0x47,0xFE,0xC4,0x80,0xFC,0x04,0x72,0x04,0x32,0xE4,0xFE,0xC0,0xE2,0xE3,0x59,0x83,
+0xE9,0x10,0x74,0x05,0xF7,0xD9,0xE8,0xC4,0xFF,0xC3,0x51,0x33,0xC0,0x0A,0xC2,0x2E,
+0x8B,0xAD,0x44,0x00,0x89,0x86,0x9E,0x00,0x83,0x4E,0x38,0x40,0x47,0x47,0x80,0xC4,
+0x10,0x79,0x04,0x32,0xE4,0xFE,0xC0,0xE2,0xE6,0x59,0x83,0xE9,0x10,0x74,0x05,0xF7,
+0xD9,0xE8,0x99,0xFF,0xC3,0xE8,0xD2,0xFF,0xC3,0x8D,0x08,0x9C,0x08,0xCA,0x08,0xF5,
+0x08,0x8B,0x0E,0x42,0x12,0x33,0xF6,0x51,0x56,0x33,0xDB,0x8B,0xCB,0x8A,0x94,0x48,
+0x12,0x8A,0x8C,0x4C,0x12,0x8A,0x9C,0x54,0x12,0x8B,0xFE,0xC1,0xE7,0x05,0x85,0xDB,
+0x75,0x02,0xB1,0x10,0x2E,0xFF,0x97,0xF9,0x08,0x5E,0x59,0x46,0xE2,0xD9,0xC3,0x01,
+0xCC,0x03,0xD0,0x00,0xE8,0x02,0xD0,0x00,0xE8,0x01,0xD0,0x00,0xE8,0x00,0xD0,0x00,
+0xE8,0x04,0xD0,0xA8,0xDA,0x00,0xDC,0x00,0xDE,0x01,0xD8,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x04,0xD0,0xA8,0xDA,0x20,0xDC,0x00,0xDE,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x00,
+0xD8,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x04,0xD0,0x00,0xDA,0x20,0xDC,0x03,0xDE,0x01,0xD8,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x03,0xCC,0x00,0xD8,0x00,0xCC,0x00,0xD0,0x00,0x00,0x56,0x52,0x1E,0x0E,0x1F,
+0xBE,0x2F,0x09,0x33,0xD2,0xFC,0xAD,0x85,0xC0,0x74,0x0D,0x8A,0xD4,0xEE,0xAD,0x85,
+0xC0,0x74,0x05,0x8A,0xD4,0xEE,0xEB,0xEE,0x1F,0x5A,0x5E,0xC3,0xE4,0x80,0x84,0xC0,
+0x74,0x16,0x78,0x14,0xB0,0x27,0xE6,0xFC,0xB0,0x11,0xE6,0x34,0xE4,0xFC,0x3C,0x27,
+0x75,0x06,0xE4,0x11,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x83,0xC2,0x06,0xB0,0xBF,0xEE,
+0x83,0xEA,0x02,0xB0,0x10,0xEE,0x88,0x86,0xAF,0x00,0xB0,0x11,0x83,0xC2,0x04,0xEE,
+0x83,0xC2,0x02,0xEE,0xB0,0x13,0x83,0xC2,0x02,0xEE,0x83,0xC2,0x02,0xEE,0x2E,0xA1,
+0x4C,0x2D,0x89,0x86,0x94,0x00,0x83,0xEA,0x0E,0xEE,0x83,0xC2,0x02,0x8A,0xC4,0xEE,
+0x83,0xC2,0x04,0xB0,0x03,0xEE,0x88,0x86,0xA8,0x00,0x83,0xEA,0x04,0x32,0xC0,0xEE,
+0x83,0xC2,0x02,0xB0,0x89,0xEE,0x88,0x86,0xA6,0x00,0x0C,0x06,0xEE,0xB0,0x40,0xB4,
+0x38,0x89,0x46,0x1C,0xC7,0x46,0x36,0x38,0x00,0x83,0xC2,0x04,0x32,0xC0,0xEE,0x88,
+0x86,0xA7,0x00,0xC3,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x83,0xEA,0x02,0xEC,0x3A,0x86,
+0xAF,0x00,0x75,0x24,0x83,0xC2,0x04,0xEC,0x3C,0x11,0x75,0x1C,0x83,0xC2,0x06,0xEC,
+0x3C,0x13,0x75,0x14,0x83,0xEA,0x08,0x8A,0x86,0xA8,0x00,0xEE,0x83,0xEA,0x02,0xEC,
+0x24,0xC0,0x3C,0xC0,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x33,0xC9,0x8B,0xD1,0x8B,0xF1,
+0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,
+0x20,0x74,0x0E,0x8A,0x86,0x9E,0x00,0xE6,0xFE,0x32,0xC0,0xE6,0x80,0x42,0xE8,0xFA,
+0xFE,0x83,0xC6,0x08,0xE2,0xE1,0x85,0xD2,0x74,0x03,0xE8,0x05,0x08,0xC3,0x33,0xC9,
+0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x40,0x00,
+0x74,0x06,0xE8,0x73,0x16,0xE8,0x12,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x33,0xC9,0x8B,
+0xF1,0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,
+0x00,0x20,0x74,0x16,0xE8,0x46,0x16,0xE8,0xD2,0xFE,0x73,0x0E,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x1C,0x9C,0x0E,0xE8,0xE3,0x07,0x90,0x83,0xC6,0x08,0xE2,0xD9,0xC3,
+0x33,0xC9,0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,
+0x40,0x00,0x74,0x16,0xE8,0x21,0x16,0xE8,0x2A,0xFF,0x73,0x0E,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x1C,0x9C,0x0E,0xE8,0xB3,0x07,0x90,0x46,0x46,0xE2,0xDA,0xC3,0x0C,
+0x00,0x00,0x10,0x00,0x13,0x12,0x00,0x00,0x14,0x00,0x28,0x3C,0x00,0x1B,0x3E,0x00,
+0x00,0x2A,0x00,0x00,0x2C,0x00,0x00,0x42,0x00,0x14,0xD8,0x00,0x00,0xDA,0x00,0x00,
+0x34,0x00,0x11,0x36,0x00,0x13,0x38,0x00,0x11,0x3A,0x00,0x13,0x00,0x00,0x56,0x50,
+0x52,0xBE,0x2F,0x0B,0x2E,0xAD,0x85,0xC0,0x74,0x06,0x92,0x2E,0xAC,0xEE,0xEB,0xF4,
+0x5A,0x58,0x5E,0xC3,0x53,0x2E,0xA1,0x60,0x22,0xE6,0xE4,0xE6,0xF0,0x8A,0xC4,0xE6,
+0xEC,0xE6,0xF8,0xE8,0xD8,0xFF,0xB0,0x4B,0xE6,0x10,0xB0,0x50,0xE6,0x12,0xB0,0x38,
+0xE6,0x14,0xE8,0xAE,0x15,0xB0,0x46,0xE6,0x0A,0xE8,0xA7,0x15,0xB0,0x1A,0xE6,0x0A,
+0xE8,0xA0,0x15,0xB0,0x22,0xE6,0x0A,0xE8,0x99,0x15,0xE8,0xFD,0x06,0x8B,0xD8,0xE4,
+0x16,0xA8,0x04,0x75,0x18,0xE8,0xF2,0x06,0x2B,0xC3,0x3D,0x32,0x00,0x72,0xF0,0x6A,
+0x00,0x1F,0xC6,0x06,0x93,0x12,0x23,0x9C,0x0E,0xE8,0x10,0x07,0x90,0xE8,0xDA,0x06,
+0x2B,0xC3,0x3D,0x24,0x00,0x77,0x1B,0xB0,0x31,0xE6,0xFC,0x56,0x51,0x55,0xB9,0x10,
+0x00,0x2E,0x8B,0xAC,0x44,0x00,0x81,0x4E,0x38,0x80,0x00,0x46,0x46,0xE2,0xF2,0x5D,
+0x59,0x5E,0xE8,0x69,0xFF,0xE8,0x4B,0x15,0xB0,0x46,0xE6,0x0A,0xE8,0x44,0x15,0x5B,
+0xC3,0x33,0xF6,0x8B,0x0E,0x42,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,
+0x20,0x74,0x06,0xE8,0x17,0x15,0xE8,0x5B,0xFF,0x83,0xC6,0x20,0xE2,0xE9,0xC3,0x8B,
+0xC2,0x05,0x04,0x00,0x89,0x46,0x28,0x2E,0xA1,0x4C,0x2D,0x89,0x86,0x8E,0x00,0x89,
+0x86,0x90,0x00,0x89,0x86,0x92,0x00,0xC6,0x86,0xA3,0x00,0x0A,0xC6,0x86,0xC3,0x00,
+0x03,0x52,0x83,0xC2,0x04,0x8A,0x86,0xA6,0x00,0x0C,0x06,0xEE,0x5A,0x83,0xC2,0x02,
+0xB0,0x05,0xEE,0x88,0x86,0xA5,0x00,0xC3,0xE8,0x03,0xFF,0xE8,0xE5,0x14,0xB0,0x42,
+0xE6,0x0A,0xF7,0x46,0x38,0x80,0x00,0x74,0x06,0x2E,0xA1,0x9C,0x22,0xEB,0x04,0x2E,
+0xA1,0x6C,0x22,0xC7,0x46,0x1C,0x0C,0x00,0x89,0x86,0x94,0x00,0x89,0x86,0x96,0x00,
+0x89,0x86,0x8E,0x00,0x89,0x86,0x90,0x00,0x89,0x86,0x92,0x00,0xE6,0xF0,0xE6,0xE4,
+0x8A,0xC4,0xE6,0xF8,0xE6,0xEC,0xC6,0x86,0xC3,0x00,0x03,0xE8,0xA5,0x14,0xB0,0x1A,
+0xE6,0x0A,0xB0,0x10,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0x33,0xC9,0x8B,0xF1,0x8A,
+0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x40,0x00,0x74,0x06,0xE8,
+0x76,0x14,0xE8,0x5A,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x33,0xC9,0x8B,0xF1,0x8A,0x0E,
+0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,0x20,0x74,0x06,0xE8,0x4C,
+0x14,0xE8,0x74,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x90,0x83,0x3E,0x44,0x12,0x00,0x75,
+0x14,0xB0,0x01,0xBA,0x06,0x01,0xEE,0x2A,0xC0,0xEE,0xB0,0x02,0xEE,0xB0,0x04,0xEE,
+0xB8,0x00,0x02,0xEB,0x0F,0xBA,0x06,0x01,0xB0,0x40,0xEE,0xB8,0x01,0x00,0x8A,0x0E,
+0x0E,0x01,0xD3,0xE0,0xA3,0x88,0x12,0xC3,0xA1,0x88,0x12,0xA3,0x84,0x12,0x2D,0x20,
+0x00,0xA3,0x8A,0x12,0x2D,0x20,0x00,0xA3,0x82,0x12,0xC7,0x06,0x86,0x12,0x20,0x00,
+0xC7,0x06,0x80,0x12,0x32,0x00,0xC3,0x83,0x3E,0x44,0x12,0x00,0x74,0x76,0x8B,0x0E,
+0x42,0x12,0x33,0xF6,0x8A,0xA4,0x54,0x12,0x84,0xE4,0x74,0x5F,0x8A,0x84,0x48,0x12,
+0x0C,0x04,0xE6,0xFE,0xF6,0xC4,0x04,0x74,0x25,0xB0,0x1B,0xBA,0x00,0x00,0xEE,0xEB,
+0x00,0x2A,0xC0,0xBA,0x02,0x00,0xEE,0xEB,0x00,0xB0,0x03,0xEE,0xEB,0x00,0x32,0xC0,
+0xBA,0x02,0x00,0xEE,0xEB,0x00,0xBA,0x00,0x00,0xB0,0x00,0xEE,0xEB,0x2D,0xB0,0x1F,
+0xBA,0x00,0x00,0xEE,0xEB,0x00,0x2A,0xC0,0xBA,0x02,0x00,0xEE,0xEB,0x00,0xB0,0x03,
+0xEE,0xEB,0x00,0xD1,0xE6,0x8A,0x84,0x5D,0x12,0xD1,0xEE,0xF6,0xD0,0xBA,0x02,0x00,
+0xEE,0xEB,0x00,0xBA,0x00,0x00,0xB0,0x0A,0xEE,0xEB,0x00,0xE4,0x04,0xEB,0x00,0xE4,
+0x04,0x46,0xE2,0x90,0xC3,0x90,0xB8,0x14,0x00,0xBA,0x3E,0xFF,0xEF,0xB8,0x06,0x00,
+0xBA,0x32,0xFF,0xEF,0xB8,0x0F,0x00,0xBA,0x34,0xFF,0xEF,0xBA,0x36,0xFF,0xEF,0x83,
+0x3E,0x44,0x12,0x00,0x75,0x16,0xB8,0x11,0x00,0xBA,0x38,0xFF,0xEF,0xB8,0x12,0x00,
+0xBA,0x3A,0xFF,0xEF,0xB8,0x1B,0x00,0xBA,0x3C,0xFF,0xEF,0xC3,0xB8,0x11,0x00,0xBA,
+0x38,0xFF,0xEF,0xB8,0x12,0x00,0xBA,0x3A,0xFF,0xEF,0xB8,0x1B,0x00,0xBA,0x3C,0xFF,
+0xEF,0xC3,0xB8,0xFC,0x00,0xBA,0x28,0xFF,0xEF,0xFB,0x83,0x3E,0x44,0x12,0x00,0x74,
+0x07,0xB8,0xCC,0x00,0xBA,0x28,0xFF,0xEF,0xC3,0x00,0xFF,0xFF,0x20,0x24,0x28,0xFF,
+0x2C,0xFF,0xFF,0x30,0x34,0x38,0xFF,0xFF,0x3C,0x90,0x3C,0x0F,0x77,0x0E,0xBB,0x19,
+0x0E,0x2E,0xD7,0x3C,0xFF,0x74,0x05,0x8A,0xD8,0xF8,0xC3,0x90,0x2A,0xDB,0xF9,0xC3,
+0x83,0x3E,0x44,0x12,0x00,0x74,0x27,0xA0,0x06,0x01,0x80,0x26,0x06,0x01,0x30,0x80,
+0x3E,0x06,0x01,0x30,0x75,0x18,0xB9,0x02,0x00,0xBF,0xC4,0x13,0xBA,0x06,0x01,0xEC,
+0xA8,0x20,0x75,0xF8,0xBA,0x04,0x01,0xED,0xAB,0xE2,0xF1,0xEB,0x16,0x90,0xB9,0x04,
+0x00,0xBF,0xC4,0x13,0xBA,0x06,0x01,0xEC,0xA8,0x20,0x75,0xF8,0xBA,0x04,0x01,0xEC,
+0xAA,0xE2,0xF1,0xFA,0x90,0xBE,0xC4,0x13,0xAD,0x80,0xE4,0x3F,0x80,0xFC,0x02,0x74,
+0x0E,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x0A,0x9C,0x0E,0xE8,0x3E,0x04,0x90,0xAD,
+0x3C,0x0F,0x75,0xED,0x8A,0xC4,0xE8,0x81,0xFF,0x72,0xE6,0x88,0x1E,0x1A,0x01,0xC6,
+0x06,0x8E,0x12,0x00,0xB0,0x00,0x0A,0x06,0x1A,0x01,0xBA,0x00,0x01,0xEE,0xC6,0x06,
+0x8F,0x12,0x40,0x83,0x3E,0x44,0x12,0x00,0x75,0x06,0xB8,0x0C,0x00,0xEB,0x04,0x90,
+0xB8,0x4C,0x00,0xBA,0x28,0xFF,0xEF,0xC3,0x83,0x3E,0x44,0x12,0x00,0x75,0x01,0xC3,
+0xA1,0x50,0x12,0x0B,0x06,0x52,0x12,0x0A,0xC4,0xA8,0x08,0x74,0xF2,0xA0,0x0F,0x01,
+0x2A,0xE4,0x50,0xFF,0x36,0xBA,0x13,0x1F,0xE8,0x50,0x56,0x83,0xC4,0x02,0x6A,0x00,
+0x1F,0x33,0xC0,0xA3,0xBC,0x13,0xA0,0x0F,0x01,0xA3,0xBE,0x13,0x8B,0x1E,0xBC,0x13,
+0x8A,0x87,0x50,0x12,0xF6,0x87,0x50,0x12,0x08,0x74,0x0D,0x24,0x07,0x8A,0xE0,0xBE,
+0xCC,0x00,0xA0,0xBC,0x13,0xE8,0x94,0x3D,0xFF,0x06,0xBC,0x13,0xFF,0x0E,0xBE,0x13,
+0x75,0xDA,0xC3,0x90,0x1E,0x33,0xC0,0x8E,0xD8,0xB0,0x01,0xE8,0x54,0x3D,0x1F,0xC3,
+0x33,0xC9,0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xC7,0x46,0x62,
+0x38,0x44,0xC7,0x46,0x7C,0xFC,0x3B,0xC7,0x46,0x7E,0xE2,0x3B,0xC7,0x86,0x80,0x00,
+0xEC,0x3C,0xE8,0xAB,0x16,0xC6,0x86,0xC0,0x00,0x11,0x83,0x7E,0x08,0x00,0x74,0x07,
+0x51,0x56,0xE8,0x33,0x33,0x5E,0x59,0x46,0x46,0xE2,0xCD,0xC3,0x33,0xC9,0x8B,0xF1,
+0x8B,0xF9,0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0xE3,0x13,0x2E,0x8B,0xAC,0x44,0x00,
+0x8A,0x86,0x9E,0x00,0x88,0x85,0x6C,0x12,0x83,0xC6,0x08,0x47,0xE2,0xED,0xC3,0xFA,
+0xFC,0xB0,0xC0,0xBA,0x00,0x01,0xEE,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0x8E,0xD0,0xBF,
+0x16,0x01,0xB9,0xCC,0x77,0x2B,0xCF,0xD1,0xE9,0xF3,0xAB,0xBC,0x40,0x12,0xE8,0xD9,
+0x02,0xE8,0x70,0x3C,0xBE,0xCC,0x0F,0xE8,0xF2,0x3C,0xF4,0x90,0x33,0xC0,0x8E,0xD8,
+0x8E,0xC0,0x8E,0xD0,0xF6,0x06,0x0A,0x01,0x80,0x74,0x0B,0xBE,0x35,0x55,0xE8,0xDB,
+0x3C,0xB0,0x01,0xE8,0xAC,0x3C,0xE8,0xB3,0x00,0xE8,0xF6,0xF5,0xE8,0x08,0xF8,0xE8,
+0x0F,0xF9,0xE8,0x85,0xFA,0xE8,0xB6,0xFA,0xE8,0xEF,0xFC,0xE8,0xC2,0x10,0xE8,0x03,
+0x3C,0xE8,0xB2,0xFD,0xE8,0x30,0xFD,0xE8,0x54,0x02,0xC6,0x06,0x8F,0x12,0xC0,0xE8,
+0xBB,0xFA,0xE8,0xEB,0xFA,0xE8,0xE9,0xFB,0xE8,0xAF,0xFC,0xE8,0x8D,0xFC,0xE8,0x1F,
+0xFF,0xE8,0x58,0xFF,0xE8,0xDB,0xFD,0xE8,0x16,0xFE,0x33,0xC0,0xBE,0x5A,0x05,0xE8,
+0x8A,0x3C,0xE8,0xA3,0xFE,0xE8,0xE0,0xFC,0xFB,0xBE,0xA4,0x44,0xE8,0x7D,0x3C,0xE9,
+0xCA,0x2D,0x56,0x98,0x8B,0xF0,0x8B,0x42,0x52,0x85,0xC0,0x75,0x27,0xC7,0x42,0x52,
+0x01,0x00,0x53,0x36,0x8B,0x9C,0x2C,0x01,0xF6,0xC3,0x01,0x75,0x0C,0x36,0x89,0x68,
+0x52,0x36,0x89,0xAC,0x2C,0x01,0x5B,0x5E,0xC3,0x36,0x89,0xAC,0x2C,0x01,0x36,0x89,
+0xAC,0x1C,0x01,0x5B,0x5E,0xC3,0x56,0x98,0x8B,0xF0,0x33,0xED,0x36,0x8B,0x84,0x1C,
+0x01,0xA8,0x01,0x75,0x15,0x8B,0xE8,0x33,0xC0,0x87,0x42,0x52,0x36,0x89,0x84,0x1C,
+0x01,0xA8,0x01,0x74,0x05,0x36,0x89,0x84,0x2C,0x01,0x5E,0xC3,0x56,0x51,0x33,0xF6,
+0xB8,0x01,0x00,0xB9,0x08,0x00,0x89,0x84,0x1C,0x01,0x89,0x84,0x2C,0x01,0x46,0x46,
+0xE2,0xF4,0x59,0x5E,0xC3,0x90,0xBB,0x01,0x00,0x8B,0xE8,0xFF,0x4E,0x6E,0x74,0x0A,
+0x8B,0xDD,0x8B,0x46,0x58,0xA8,0x01,0x74,0xF0,0xC3,0x8B,0x46,0x48,0xA9,0x08,0x00,
+0x74,0x45,0xF7,0x46,0x38,0x40,0x00,0x74,0x27,0xE8,0x5C,0x10,0x80,0xC2,0x06,0x8A,
+0x86,0xA8,0x00,0x24,0xBF,0x88,0x86,0xA8,0x00,0xEE,0x60,0xB0,0xFE,0xE8,0x86,0x32,
+0x61,0xB0,0x02,0xE8,0x4C,0xFF,0x8B,0x46,0x48,0x24,0xF7,0x89,0x46,0x48,0xEB,0x17,
+0xE8,0x2A,0x10,0x81,0x4E,0x26,0x00,0x40,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,
+0xA5,0x00,0xE6,0x0C,0x8B,0x46,0x48,0xA9,0x04,0x00,0x74,0x14,0xB0,0x02,0xE8,0x21,
+0xFF,0x8B,0x46,0x48,0x24,0xFB,0x89,0x46,0x48,0x60,0xB0,0xDF,0xE8,0x47,0x32,0x61,
+0x33,0xC0,0x87,0x46,0x58,0xF6,0xC3,0x01,0x75,0x0B,0x36,0x89,0x47,0x58,0xA8,0x01,
+0x75,0x0D,0xE9,0x74,0xFF,0xA3,0x22,0x01,0xA8,0x01,0x75,0x03,0xE9,0x6A,0xFF,0x89,
+0x1E,0x32,0x01,0xC3,0xBB,0x01,0x00,0x8B,0xE8,0xF7,0x46,0x38,0x40,0x00,0x74,0x15,
+0xE8,0xD5,0x0F,0x80,0xC2,0x0A,0xEC,0xA8,0x40,0x75,0x0A,0x8B,0xDD,0x8B,0x46,0x56,
+0xA8,0x01,0x74,0xE3,0xC3,0x8B,0x46,0x26,0x80,0xE4,0xFE,0x80,0xCC,0x02,0x89,0x46,
+0x26,0xB0,0x02,0xE8,0xBC,0xFE,0x33,0xC0,0x87,0x46,0x56,0xF6,0xC3,0x01,0x75,0x0A,
+0x36,0x89,0x47,0x56,0xA8,0x01,0x75,0x0B,0xEB,0xBD,0xA3,0x20,0x01,0xA8,0x01,0x75,
+0x02,0xEB,0xB4,0x89,0x1E,0x30,0x01,0xC3,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA0,
+0x90,0x12,0x84,0xC0,0x75,0x49,0xA1,0x22,0x01,0xA8,0x01,0x75,0x03,0xE8,0xF6,0xFE,
+0xA1,0x20,0x01,0xA8,0x01,0x75,0x03,0xE8,0x8A,0xFF,0xA1,0xAC,0x13,0x48,0x78,0x05,
+0x74,0x45,0xA3,0xAC,0x13,0xA1,0xAE,0x13,0x48,0x78,0x05,0x74,0x51,0xA3,0xAE,0x13,
+0xA1,0xB0,0x13,0x48,0x78,0x05,0x74,0x63,0xA3,0xB0,0x13,0xA1,0x7E,0x12,0x40,0x78,
+0x03,0xA3,0x7E,0x12,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0xA0,
+0x91,0x12,0x40,0x3C,0x02,0x72,0x0B,0x33,0xC0,0xA2,0x91,0x12,0xFF,0x16,0x7C,0x12,
+0xEB,0xA4,0xA2,0x91,0x12,0xEB,0x9F,0xA0,0x8E,0x12,0x32,0x06,0x8F,0x12,0xA2,0x8E,
+0x12,0x0A,0x06,0x1A,0x01,0xBA,0x00,0x01,0xEE,0xB8,0x2C,0x01,0xEB,0xA4,0x83,0x3E,
+0x84,0x12,0x10,0x72,0x11,0xBA,0x28,0xFF,0xED,0x0C,0x81,0xEF,0xE8,0x53,0x37,0xBA,
+0x28,0xFF,0xED,0x24,0x7E,0xEF,0xB8,0x04,0x00,0xEB,0x92,0xC6,0x06,0x8D,0x12,0x01,
+0xE8,0x3F,0x37,0xC6,0x06,0x8D,0x12,0x00,0xA1,0xB2,0x13,0xEB,0x8B,0x90,0x8A,0x1E,
+0x0B,0x01,0x2A,0xFF,0x6B,0xC3,0x19,0xBA,0x62,0xFF,0xEF,0xB8,0x0A,0x00,0xBA,0x60,
+0xFF,0xEF,0xB8,0x01,0xE0,0xBA,0x66,0xFF,0xEF,0xB8,0xFF,0xFF,0xBA,0x52,0xFF,0xEF,
+0xB8,0x09,0xC0,0xBA,0x56,0xFF,0xEF,0xC7,0x06,0xAC,0x13,0x2C,0x01,0xC7,0x06,0xAE,
+0x13,0x04,0x00,0xC6,0x06,0x91,0x12,0x00,0xC3,0x90,0x8A,0x1E,0x0B,0x01,0x2A,0xFF,
+0x6B,0xC3,0x05,0xD1,0xE8,0xA3,0x18,0x01,0xC3,0x90,0x52,0xBA,0x50,0xFF,0xED,0x5A,
+0xC3,0x90,0x53,0x51,0x8B,0x1E,0x18,0x01,0xB9,0x32,0x05,0x90,0xE2,0xFE,0x4B,0x75,
+0xF7,0x59,0x5B,0xC3,0xB0,0x80,0xBA,0x00,0x01,0x0A,0x06,0x1A,0x01,0xEE,0xC3,0x90,
+0xB0,0x40,0xEB,0xF2,0xB0,0xC0,0xEB,0xEE,0xB0,0x00,0xEB,0xEA,0xFA,0x60,0x06,0x1E,
+0x16,0x2B,0xDB,0x8E,0xDB,0x2E,0xA1,0xBA,0x4C,0x2E,0xA3,0x92,0x4C,0xA0,0x93,0x12,
+0x98,0x8B,0xE8,0x89,0x26,0x2D,0x7A,0x80,0x3E,0xCA,0x13,0x00,0x74,0x03,0xE9,0x6B,
+0x42,0xE8,0xC0,0xFF,0xE8,0xAB,0xFF,0xE8,0xA8,0xFF,0xB0,0x20,0xC6,0x06,0x90,0x12,
+0x00,0xFF,0x16,0x7C,0x12,0x8B,0xFD,0x83,0xFF,0x0A,0x72,0x11,0xE8,0xB9,0xFF,0xE8,
+0x90,0xFF,0xE8,0xAB,0xFF,0xE8,0x8A,0xFF,0x83,0xEF,0x0A,0xEB,0xEA,0x0B,0xFF,0x74,
+0x0F,0xE8,0xA4,0xFF,0xE8,0x7B,0xFF,0xE8,0x9A,0xFF,0xE8,0x75,0xFF,0x4F,0x75,0xF1,
+0xE8,0x95,0xFF,0xE8,0x6C,0xFF,0xEB,0xB9,0x8A,0x86,0xA5,0x00,0x24,0xFD,0xEE,0x88,
+0x86,0xA5,0x00,0xC3,0x8A,0x86,0xA6,0x00,0x0C,0x02,0xEE,0xC3,0x8B,0x76,0x38,0xF7,
+0xC6,0x01,0x00,0x74,0xEF,0x8B,0x4E,0x36,0x8B,0x46,0x2E,0x3B,0xC1,0x73,0x02,0x8B,
+0xC8,0x2B,0xC1,0x89,0x46,0x2E,0x01,0x4E,0x34,0xC4,0x7E,0x04,0x26,0x01,0x0D,0x8B,
+0x7E,0x2C,0x83,0xEA,0x04,0xF3,0x6C,0x8E,0xC1,0x89,0x7E,0x2C,0x3B,0x46,0x3C,0x72,
+0x12,0xF7,0xC6,0x20,0x00,0x75,0x0B,0x83,0xCE,0x20,0x89,0x76,0x38,0xB0,0x00,0xE8,
+0xA0,0xFC,0xC3,0xF7,0xC6,0x04,0x00,0x74,0x1B,0x8B,0xD8,0x83,0xCE,0x10,0x89,0x76,
+0x38,0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0x83,0xC2,0x08,0xEE,0x83,
+0xEA,0x08,0x8B,0xC3,0x3D,0x40,0x00,0x72,0x01,0xC3,0x81,0x4E,0x38,0x00,0x04,0x83,
+0xC2,0x02,0x8A,0x86,0xA5,0x00,0x24,0xFA,0x88,0x86,0xA5,0x00,0xEE,0xC3,0x8A,0x86,
+0xA6,0x00,0x0C,0x02,0xEE,0xC3,0xF7,0x46,0x38,0x01,0x00,0x74,0xF1,0x8B,0x4E,0x2E,
+0x32,0xDB,0x8A,0xBE,0xA3,0x00,0x83,0xC2,0x06,0xC4,0x76,0x04,0x8B,0x7E,0x2C,0x83,
+0xF9,0x08,0x72,0x2C,0xEC,0xA8,0x01,0x74,0x16,0x8A,0xE0,0x83,0xEA,0x0A,0xEC,0x83,
+0xC2,0x0A,0x84,0xE7,0x75,0x51,0xAA,0xFE,0xC3,0x49,0x83,0xF9,0x08,0x73,0xE5,0x32,
+0xFF,0x26,0x01,0x1C,0x01,0x5E,0x34,0x89,0x76,0x04,0x89,0x4E,0x2E,0x89,0x7E,0x2C,
+0x3B,0x4E,0x3C,0x72,0x11,0xF6,0x46,0x38,0x20,0x74,0x01,0xC3,0x83,0x4E,0x38,0x20,
+0xB0,0x00,0xE8,0xFD,0xFB,0xC3,0xF6,0x46,0x38,0x04,0x74,0x15,0x83,0x4E,0x38,0x10,
+0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0x83,0xEA,0x02,0xEE,0x83,0xC2,
+0x02,0x3D,0x40,0x00,0x72,0x5D,0xC3,0x32,0xFF,0x26,0x03,0x1C,0x85,0xDB,0x74,0x09,
+0x26,0x89,0x1C,0x8B,0xF7,0x47,0x47,0x49,0x49,0x80,0xE4,0x1E,0x80,0xCC,0xC0,0x26,
+0x89,0x04,0xF6,0xC4,0x10,0x74,0x27,0x8B,0x76,0x38,0xF7,0xC6,0x00,0x10,0x74,0x0B,
+0x50,0xFE,0x86,0xB2,0x00,0xB0,0x0A,0xE8,0xA8,0xFB,0x58,0xF7,0xC6,0x00,0x01,0x74,
+0x0D,0xE8,0x82,0x26,0x8B,0x76,0x38,0x8B,0x4E,0x2E,0x8B,0x7E,0x04,0xAB,0x8B,0xF7,
+0x33,0xC0,0xAB,0x32,0xDB,0x8A,0xBE,0xA3,0x00,0x49,0x49,0x83,0xF9,0x08,0x72,0x17,
+0xE9,0x41,0xFF,0x81,0x4E,0x38,0x00,0x04,0x83,0xC2,0xF8,0x8A,0x86,0xA5,0x00,0x24,
+0xFA,0x88,0x86,0xA5,0x00,0xEE,0xC3,0xE9,0x45,0xFF,0x83,0xC2,0x08,0xEC,0x88,0x86,
+0xAA,0x00,0xC0,0xE8,0x04,0x8A,0xE0,0x8A,0xC8,0x86,0x86,0xA9,0x00,0x32,0xE0,0x8B,
+0x5E,0x3E,0x84,0xE3,0x74,0x4F,0x8A,0xC1,0x8B,0x4E,0x26,0xF6,0xC5,0x04,0x74,0x0C,
+0xA8,0x08,0x74,0x05,0x80,0xE1,0xBF,0xEB,0x03,0x80,0xC9,0x40,0xF6,0xC5,0x08,0x74,
+0x0C,0xA8,0x02,0x74,0x05,0x80,0xE1,0x7F,0xEB,0x03,0x80,0xC9,0x80,0x88,0x4E,0x26,
+0x8B,0xF0,0x8A,0x86,0xA5,0x00,0x84,0xC9,0x74,0x08,0xA8,0x02,0x74,0x15,0x24,0xFD,
+0xEB,0x06,0xA8,0x02,0x75,0x0D,0x0C,0x02,0x88,0x86,0xA5,0x00,0x83,0xEA,0x0A,0xEE,
+0x83,0xC2,0x0A,0x8B,0xC6,0x84,0xE7,0x75,0x01,0xC3,0xC6,0x86,0xBA,0x00,0x01,0xB0,
+0x0E,0xE8,0xEE,0xFA,0xF7,0x46,0x38,0x00,0x02,0x74,0xEE,0x83,0x7E,0x2E,0x06,0x72,
+0xE8,0x8A,0xA6,0xAA,0x00,0xC4,0x5E,0x04,0x8B,0x7E,0x2C,0xB0,0xFF,0xAA,0xB0,0x02,
+0xAB,0x26,0x83,0x07,0x03,0x83,0x6E,0x2E,0x03,0x89,0x7E,0x2C,0xF6,0x46,0x38,0x20,
+0x74,0x01,0xC3,0x83,0x4E,0x38,0x20,0xB0,0x00,0xE8,0xB6,0xFA,0xC3,0x90,0x83,0xEA,
+0x08,0xE9,0xB4,0xFD,0x83,0xC2,0x06,0x8B,0x5E,0x26,0xF6,0xC3,0xC0,0x75,0xEF,0x8B,
+0x4E,0x1C,0xEC,0x88,0x86,0xA4,0x00,0x83,0xEA,0x0A,0xA8,0x20,0x75,0x02,0x8A,0xCD,
+0x32,0xED,0x8B,0x46,0x1A,0x3B,0xC8,0x73,0x18,0x01,0x4E,0x2A,0x2B,0xC1,0x89,0x46,
+0x1A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x89,0x76,0x00,0x3D,0x20,0x00,0x72,0x30,
+0xC3,0x85,0xC0,0x74,0x31,0x8B,0xC8,0x01,0x46,0x2A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,
+0xD9,0x80,0xCB,0x02,0x89,0x5E,0x26,0xE8,0x32,0xF1,0xF6,0xC7,0x01,0x75,0x16,0x83,
+0xC2,0x02,0xE8,0x53,0xFD,0xF6,0xC7,0x10,0x75,0x0B,0xB0,0x02,0xE8,0x43,0xFA,0xC3,
+0xF6,0xC7,0x01,0x74,0xF0,0xC3,0x80,0xCB,0x02,0x89,0x5E,0x26,0xF6,0xC7,0x01,0x74,
+0xDE,0x83,0xC2,0x02,0xE8,0x31,0xFD,0xF6,0x86,0xA4,0x00,0x40,0x74,0x0B,0x80,0xE7,
+0xFE,0x80,0xCF,0x02,0x89,0x5E,0x26,0xEB,0xCC,0xB0,0x04,0xE8,0x14,0xFA,0xC3,0xC0,
+0xC2,0xC8,0xCA,0xC4,0xC6,0xCC,0xCE,0xD0,0xD2,0xD8,0xDA,0xD4,0xD6,0xDC,0xDE,0x90,
+0xE9,0x0E,0x01,0xE4,0xC4,0x8A,0xE0,0xE4,0xC4,0x8B,0xD0,0x83,0xF9,0x08,0x72,0xF0,
+0x26,0x83,0x3F,0x00,0x74,0x04,0x8B,0xDF,0x49,0x49,0x8B,0xFB,0x8A,0xDE,0x83,0xE3,
+0x0F,0x2E,0x8A,0xA7,0x2F,0x16,0xAB,0xF6,0xC4,0x10,0x74,0x24,0xF7,0xC6,0x00,0x10,
+0x74,0x0B,0x50,0xFE,0x86,0xB2,0x00,0xB0,0x0A,0xE8,0xC6,0xF9,0x58,0xF7,0xC6,0x00,
+0x01,0x74,0x0D,0xE8,0xA0,0x24,0x8B,0x76,0x38,0x8B,0x4E,0x2E,0x8B,0x7E,0x04,0xAB,
+0x89,0x7E,0x04,0x33,0xC0,0xAB,0x49,0x49,0x89,0x4E,0x2E,0x89,0x7E,0x2C,0x8B,0xC1,
+0xEB,0x4E,0x90,0xEB,0x9E,0x90,0xE4,0xD6,0x84,0xC0,0x79,0x63,0xE6,0xD0,0x8A,0xC8,
+0x25,0x03,0x00,0x03,0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,0x44,0x00,0x88,0x8E,0xAE,0x00,
+0x8B,0x4E,0x2E,0xC4,0x5E,0x04,0x8B,0x7E,0x2C,0x8B,0x76,0x38,0xE4,0x86,0x24,0x07,
+0x3C,0x03,0x75,0xCF,0xE4,0x1C,0x91,0x3B,0xC1,0x73,0x02,0x8B,0xC8,0x2B,0xC1,0x89,
+0x46,0x2E,0x01,0x4E,0x34,0x26,0x01,0x0F,0xBA,0xC4,0x00,0xF3,0x6C,0x89,0x7E,0x2C,
+0x3B,0x46,0x3C,0x72,0x1C,0xF7,0xC6,0x20,0x00,0x75,0x0B,0x83,0xCE,0x20,0x89,0x76,
+0x38,0xB0,0x00,0xE8,0x3C,0xF9,0x8A,0x86,0xAE,0x00,0x24,0x3F,0xE6,0xD6,0xC3,0xF9,
+0xC3,0xF7,0xC6,0x0A,0x00,0x74,0x35,0xF7,0xC6,0x10,0x00,0x75,0x2F,0x83,0xCE,0x10,
+0x89,0x76,0x38,0xF7,0xC6,0x02,0x00,0x74,0x0E,0x50,0xE4,0xD8,0x24,0xFE,0xE6,0xD8,
+0x58,0xF7,0xC6,0x08,0x00,0x74,0x15,0x50,0x51,0xB9,0xE8,0x03,0xE4,0x0A,0x84,0xC0,
+0xE0,0xFA,0x84,0xC0,0x75,0x04,0xB0,0x24,0xE6,0x0A,0x59,0x58,0x3D,0x40,0x00,0x73,
+0xB5,0x8A,0x86,0xA5,0x00,0x24,0xEF,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0xCE,0x10,
+0x04,0x89,0x76,0x38,0xEB,0xA0,0x00,0x08,0x04,0x0C,0x01,0x09,0x05,0x0D,0x02,0x0A,
+0x06,0x0E,0x03,0x0B,0x07,0x0F,0x00,0x40,0x80,0xC0,0x20,0x60,0xA0,0xE0,0x10,0x50,
+0x90,0xD0,0x30,0x70,0xB0,0xF0,0xE4,0xD2,0xE6,0xD0,0x8A,0xC8,0x25,0x03,0x00,0x03,
+0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,0x44,0x00,0x88,0x8E,0xAE,0x00,0xE4,0xD8,0xC0,0xE8,
+0x04,0x8B,0xD8,0x2E,0x8A,0x87,0x66,0x17,0x8A,0xE0,0x8A,0xC8,0x86,0x86,0xA9,0x00,
+0x32,0xE0,0xE4,0x98,0x8B,0x5E,0x3E,0x84,0xE3,0x74,0x54,0x8A,0xC1,0x8B,0x4E,0x26,
+0xF6,0xC5,0x04,0x74,0x0C,0xA8,0x08,0x74,0x05,0x80,0xE1,0xBF,0xEB,0x03,0x80,0xC9,
+0x40,0xF6,0xC5,0x08,0x74,0x0C,0xA8,0x02,0x74,0x05,0x80,0xE1,0x7F,0xEB,0x03,0x80,
+0xC9,0x80,0x88,0x4E,0x26,0x8B,0xF0,0x8A,0x86,0xA5,0x00,0xF6,0xC1,0xFD,0x74,0x08,
+0xA8,0x06,0x74,0x19,0x24,0xF9,0xEB,0x0F,0xA8,0x06,0x75,0x11,0xF6,0xC5,0x01,0x75,
+0x04,0x0C,0x04,0xEB,0x02,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x8B,0xC6,0x84,
+0xE7,0x75,0x09,0x8A,0x86,0xAE,0x00,0x24,0x3F,0xE6,0xD2,0xC3,0xC6,0x86,0xBA,0x00,
+0x01,0xB0,0x0E,0xE8,0x1C,0xF8,0xF7,0x46,0x38,0x00,0x02,0x74,0xE6,0x83,0x7E,0x2E,
+0x06,0x72,0xE0,0x8A,0x86,0xA9,0x00,0x8A,0xE0,0x86,0x86,0xAA,0x00,0x8A,0xC8,0x32,
+0xC4,0x80,0xC9,0x0B,0x22,0xC1,0xC0,0xE4,0x04,0x0A,0xE0,0xC4,0x5E,0x04,0x8B,0x7E,
+0x2C,0xB0,0xFF,0xAA,0xB0,0x02,0xAB,0x26,0x83,0x07,0x03,0x83,0x6E,0x2E,0x03,0x89,
+0x7E,0x2C,0xF6,0x46,0x38,0x20,0x75,0xAB,0x83,0x4E,0x38,0x20,0xB0,0x00,0xE8,0xD1,
+0xF7,0xEB,0xA0,0x90,0xE4,0x12,0x24,0xDF,0xE6,0x12,0x81,0xE3,0xFE,0x9F,0x89,0x5E,
+0x26,0x83,0x66,0x48,0xF7,0xEB,0x73,0x90,0xF6,0xC7,0x20,0x75,0xE7,0xE4,0x12,0x0C,
+0x20,0xE6,0x12,0x32,0xC0,0xE6,0xC6,0xB0,0x83,0xE6,0xC6,0x80,0xCF,0x20,0x89,0x5E,
+0x26,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xEB,0x74,0x90,
+0xF6,0xC7,0x40,0x75,0xD3,0xE4,0x12,0x0C,0x20,0xE6,0x12,0x32,0xC0,0xE6,0xC6,0xB0,
+0x81,0xE6,0xC6,0x80,0xE7,0xDF,0x80,0xCB,0x01,0x89,0x5E,0x26,0xB0,0x06,0xE8,0x71,
+0xF7,0x90,0x8A,0x86,0xA5,0x00,0x24,0xF9,0xE6,0x0C,0x88,0x86,0xA5,0x00,0xEB,0x43,
+0xE4,0xD4,0xE6,0xD0,0x8B,0xF8,0x25,0x03,0x00,0x03,0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,
+0x44,0x00,0x8B,0x5E,0x26,0xF6,0xC7,0x60,0x75,0xB6,0xF6,0xC3,0xC0,0x75,0xD3,0xBA,
+0xC6,0x00,0x8B,0x4E,0x1C,0x8B,0x46,0x1A,0x3B,0xC8,0x73,0x1E,0x01,0x4E,0x2A,0x2B,
+0xC1,0x89,0x46,0x1A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x89,0x76,0x00,0x3D,0x20,
+0x00,0x72,0x3D,0x8B,0xC7,0x24,0x3F,0xE6,0xD4,0xC3,0x85,0xC0,0x74,0x39,0x8B,0xC8,
+0x01,0x46,0x2A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x83,0xCB,0x02,0x89,0x5E,0x26,
+0xE8,0xD9,0xED,0xF6,0xC7,0x01,0x75,0x39,0x8A,0x86,0xA5,0x00,0x24,0xF9,0xE6,0x0C,
+0x88,0x86,0xA5,0x00,0xF6,0xC7,0x10,0x75,0xCA,0xB0,0x02,0xE8,0xE4,0xF6,0xEB,0xC3,
+0xF6,0xC7,0x01,0x74,0xEF,0xEB,0xBC,0xF6,0xC7,0x01,0x74,0xDC,0x8A,0x86,0xA5,0x00,
+0xA8,0x02,0x74,0x11,0x81,0xE3,0xFF,0xFE,0x81,0xCB,0x00,0x02,0x89,0x5E,0x26,0xEB,
+0xC7,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0xE6,0x0C,0x88,0x86,0xA5,0x00,0xEB,
+0x92,0x90,0xFD,0xF7,0xDF,0x7F,0xFE,0xFB,0xEF,0xBF,0x00,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x02,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x03,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x02,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x33,0xDB,0x8A,0xD8,0x8A,0x87,
+0x6C,0x12,0xE6,0xFE,0xC1,0xE3,0x02,0xE4,0xCE,0xA8,0x04,0x75,0x09,0xA8,0x02,0x74,
+0x03,0xE9,0x2C,0xFE,0xF9,0xC3,0x50,0x53,0xE8,0xCB,0xFC,0x5B,0x58,0xA8,0x02,0x74,
+0x03,0xE9,0x1C,0xFE,0xF8,0xC3,0x33,0xDB,0x8A,0xD8,0x8A,0x87,0x6C,0x12,0xE6,0xFE,
+0xC1,0xE3,0x02,0xE9,0xD0,0xFB,0x9A,0x1A,0xC6,0x1A,0x00,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0C,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0E,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0C,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0xC3,0x90,0xDA,0x14,0x94,0x15,
+0x5C,0x13,0xE6,0x13,0xDA,0x1B,0xDA,0x1B,0xE6,0x13,0xDA,0x1B,0x8B,0x94,0x64,0x12,
+0xC1,0xE6,0x04,0xA8,0x01,0x74,0x35,0x50,0x33,0xC0,0x8A,0xC2,0xE6,0xFE,0xE4,0xA0,
+0x85,0xC0,0x74,0x27,0x8B,0xD8,0x2E,0x8A,0x9F,0xDA,0x1A,0x52,0x56,0x2E,0x8B,0xA8,
+0x44,0x00,0x8B,0x56,0x28,0xEC,0xA8,0x01,0x75,0x0D,0x88,0x86,0xAD,0x00,0x24,0x0E,
+0x8A,0xD8,0x2E,0xFF,0x97,0xDC,0x1B,0x5E,0x5A,0xEB,0xCD,0x58,0xA8,0x02,0x74,0x36,
+0x83,0xC6,0x10,0x33,0xC0,0x8A,0xC6,0xE6,0xFE,0xE4,0xA0,0x85,0xC0,0x74,0x27,0x8B,
+0xD8,0x2E,0x8A,0x9F,0xDA,0x1A,0x52,0x56,0x2E,0x8B,0xA8,0x44,0x00,0x8B,0x56,0x28,
+0xEC,0xA8,0x01,0x75,0x0D,0x88,0x86,0xAD,0x00,0x24,0x0E,0x8A,0xD8,0x2E,0xFF,0x97,
+0xDC,0x1B,0x5E,0x5A,0xEB,0xCD,0xC3,0x90,0x32,0xE4,0x8B,0xD8,0x8B,0xD0,0x2E,0x8A,
+0x9F,0x9A,0x19,0x2E,0x22,0x97,0x92,0x19,0x56,0x52,0x8A,0xC3,0x24,0x03,0x03,0xC6,
+0x80,0xE3,0x04,0xD0,0xEB,0x2E,0xFF,0x97,0xD6,0x1A,0x58,0x5E,0xA9,0x55,0x00,0x75,
+0xD9,0xC3,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x00,
+0x22,0xC4,0x74,0x08,0x33,0xF6,0xE8,0xBF,0xFF,0xEB,0xEE,0x90,0xE4,0x04,0x07,0xE4,
+0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,
+0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x08,0xBE,0x04,
+0x00,0xE8,0x94,0xFF,0xEB,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x6B,0xFF,0xA1,0x60,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE5,0xBE,0x08,0x00,0xE8,0x5A,0xFF,0xEB,0xDD,
+0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,
+0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x04,0x1F,0xE4,0x04,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,
+0xE4,0x00,0x22,0xC4,0x74,0x19,0xBE,0x04,0x00,0xE8,0x1C,0xFF,0xA1,0x62,0x12,0xE6,
+0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE4,0xBE,0x0C,0x00,0xE8,0x0B,0xFF,0xEB,0xDC,0xA1,
+0x62,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x04,0x1F,0xE4,0x04,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,
+0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x80,
+0x84,0xC4,0x74,0x08,0x33,0xF6,0xE8,0x53,0xFE,0xEB,0xEE,0x90,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x08,0xBE,0x02,0x00,0xE8,0x2C,0xFE,
+0xEB,0xED,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x08,
+0xBE,0x04,0x00,0xE8,0x06,0xFE,0xEB,0xED,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,
+0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x62,0x12,0xE6,0xFE,
+0xE4,0x80,0x84,0xC4,0x74,0x08,0xBE,0x06,0x00,0xE8,0xE0,0xFD,0xEB,0xED,0xB8,0x00,
+0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x37,
+0xFE,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE5,0xBE,0x04,0x00,0xE8,
+0xAA,0xFD,0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0xA1,
+0x5C,0x12,0xE6,0xFE,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,
+0xE4,0x00,0x22,0xC4,0x74,0x19,0xBE,0x04,0x00,0xE8,0xEC,0xFD,0xA1,0x62,0x12,0xE6,
+0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE4,0xBE,0x06,0x00,0xE8,0x5F,0xFD,0xEB,0xDC,0xA1,
+0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,
+0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x18,
+0x33,0xF6,0xE8,0x27,0xFD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE5,
+0xBE,0x08,0x00,0xE8,0x92,0xFD,0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,
+0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,
+0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,0x80,
+0x84,0xC4,0x74,0x19,0xBE,0x02,0x00,0xE8,0xE2,0xFC,0xA1,0x62,0x12,0xE6,0xFE,0xE4,
+0x00,0x22,0xC4,0x74,0xE4,0xBE,0x0C,0x00,0xE8,0x4D,0xFD,0xEB,0xDC,0xA1,0x62,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,
+0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5C,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x9D,0xFC,0xA1,
+0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE5,0xBE,0x04,0x00,0xE8,0x8C,0xFC,
+0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0x07,0x1F,0xB8,
+0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x19,0xBE,0x02,0x00,0xE8,0x5C,0xFC,
+0xA1,0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE4,0xBE,0x06,0x00,0xE8,0x4B,
+0xFC,0xEB,0xDC,0xA1,0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0x07,0x1F,
+0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0x90,0x2A,0xC0,0xE6,0xFE,0xE4,0xCE,0xA8,0x01,0x74,0x14,0x33,0xDB,0xE8,0xD5,
+0xF6,0xEB,0xEF,0x90,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,
+0xF6,0x06,0x05,0x01,0x01,0x75,0xED,0xB0,0x01,0xE6,0xFE,0xE4,0xCE,0xA8,0x01,0x74,
+0xE3,0xBB,0x04,0x00,0xE8,0xAF,0xF6,0xEB,0xC9,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0x90,0xFB,0x90,0xFA,0x2A,0xC0,0xE6,0xFE,0xE4,0xCE,0xA8,0x02,0x74,0x13,0x33,
+0xDB,0xE8,0xCC,0xF8,0xEB,0xEC,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,
+0xCF,0x90,0xA8,0x04,0x74,0xF0,0x33,0xDB,0xE8,0x5B,0xF7,0xEB,0xD5,0x90,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0x90,0xFB,0x90,0xFA,0xB0,0x01,0xE6,0xFE,0xE4,0xCE,0xA8,
+0x02,0x74,0x15,0xBB,0x04,0x00,0xE8,0x97,0xF8,0xEB,0xEB,0x90,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0xA8,0x04,0x74,0xF0,0xBB,0x04,0x00,0xE8,
+0x24,0xF7,0xEB,0xD2,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x09,0x9C,0x0E,0xE8,0x6B,
+0xF2,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x29,0x9C,0x0E,0xE8,0x5D,0xF2,0x90,
+0x72,0x20,0x72,0x20,0x72,0x20,0xCE,0x1D,0x92,0x1C,0xE6,0x1C,0x1A,0x1E,0x72,0x20,
+0x82,0x1D,0xAE,0x1E,0x38,0x1F,0x72,0x20,0x82,0x1D,0x72,0x20,0x72,0x20,0x38,0x1F,
+0x72,0x20,0x72,0x20,0x72,0x20,0xF4,0x1D,0xBC,0x1C,0x34,0x1D,0x64,0x1E,0x72,0x20,
+0xA8,0x1D,0xF2,0x1E,0x78,0x1F,0x72,0x20,0xA8,0x1D,0x72,0x20,0x72,0x20,0x78,0x1F,
+0xFC,0xB9,0x40,0x00,0x8C,0xCB,0xB8,0x64,0x20,0x2B,0xFF,0xAB,0x93,0xAB,0x93,0xE2,
+0xFA,0xC7,0x06,0x4C,0x00,0xA8,0x11,0x83,0x3E,0x44,0x12,0x00,0x75,0x20,0xC7,0x06,
+0x3C,0x00,0x08,0x4B,0xC7,0x06,0x30,0x00,0xBA,0x1F,0xC7,0x06,0x34,0x00,0xFA,0x1F,
+0xF6,0x06,0x05,0x01,0x01,0x75,0x06,0xC7,0x06,0x38,0x00,0x2E,0x20,0xC3,0xC7,0x06,
+0x3C,0x00,0x56,0x4B,0x33,0xDB,0x8A,0x1E,0x54,0x12,0xC1,0xE3,0x02,0x02,0x1E,0x56,
+0x12,0x2E,0x8B,0x87,0x80,0x20,0xA3,0x30,0x00,0x8A,0x1E,0x55,0x12,0xC1,0xE3,0x02,
+0x02,0x1E,0x57,0x12,0x2E,0x8B,0x87,0xA0,0x20,0xA3,0x34,0x00,0xC3,0x8B,0x86,0x9E,
+0x00,0xE6,0xFE,0x86,0xC4,0xE6,0xD0,0xC3,0x8B,0x86,0x9E,0x00,0xE6,0xFE,0x33,0xD2,
+0x8A,0xD4,0xC3,0x51,0xB9,0x10,0x27,0xE4,0x0A,0x90,0x90,0x84,0xC0,0x74,0x05,0xE2,
+0xF6,0x59,0xF9,0xC3,0x59,0xF8,0xC3,0x84,0xC0,0x78,0x1E,0x51,0x8A,0xE8,0x8A,0xC8,
+0xB8,0x01,0x00,0xD3,0xE0,0x09,0x86,0x98,0x00,0x3A,0xAE,0xA0,0x00,0x59,0x75,0x10,
+0xE8,0xA9,0xE5,0x83,0x4E,0x26,0x02,0xF9,0xC3,0x98,0x89,0x86,0x98,0x00,0xEB,0xF0,
+0xF8,0xC3,0x84,0xC0,0x78,0x12,0x51,0x8A,0xE0,0x8A,0xC8,0xB8,0x01,0x00,0xD3,0xE0,
+0x59,0xF7,0xD0,0x21,0x86,0x98,0x00,0xC3,0xC7,0x86,0x98,0x00,0x00,0x00,0xC3,0x83,
+0xC2,0x04,0x8A,0x86,0xA6,0x00,0x0C,0x04,0xEE,0x83,0xEA,0x04,0xC3,0xE8,0x93,0xFF,
+0x72,0x04,0xB0,0x82,0xE6,0x0A,0xC3,0x8B,0x46,0x26,0xA8,0xFD,0x74,0x11,0x8A,0x86,
+0xA5,0x00,0xA8,0x06,0x74,0x08,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xF6,
+0xC4,0x01,0x74,0x0A,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0xEB,0x0C,0xA8,0x02,
+0x75,0x0F,0x8A,0x86,0xA5,0x00,0x24,0xFD,0x0C,0x04,0x3A,0x86,0xA5,0x00,0x75,0xD8,
+0xC3,0x8A,0x86,0xA5,0x00,0xEB,0xCF,0xE4,0xD8,0x33,0xDB,0x8A,0xD8,0xC0,0xEB,0x04,
+0x2E,0x8A,0x9F,0x66,0x17,0x88,0x9E,0xA9,0x00,0x8B,0x5E,0x26,0x80,0xE3,0x3F,0xF6,
+0xC7,0x04,0x74,0x07,0xA8,0x10,0x75,0x03,0x80,0xCB,0x40,0xF6,0xC7,0x08,0x74,0x07,
+0xA8,0x80,0x75,0x03,0x80,0xCB,0x40,0x88,0x5E,0x26,0x8A,0x86,0xA5,0x00,0xF6,0xC3,
+0xFD,0x74,0x0D,0xA8,0x06,0x74,0x08,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,
+0xF6,0xC7,0x01,0x74,0x04,0x0C,0x02,0xEB,0xF0,0xF6,0xC3,0x02,0x75,0xE9,0x0C,0x04,
+0xEB,0xE7,0xC4,0x04,0xC4,0x04,0x85,0x04,0x59,0x04,0x48,0x04,0x41,0x04,0xC3,0x03,
+0x82,0x03,0x41,0x03,0x82,0x02,0x57,0x02,0x41,0x02,0x82,0x01,0x41,0x01,0x82,0x00,
+0x41,0x00,0x4E,0x02,0xAD,0x01,0x57,0x01,0x2D,0x00,0x2B,0x00,0x27,0x00,0x21,0x00,
+0x16,0x00,0xF4,0x04,0xF4,0x04,0xA3,0x04,0x6F,0x04,0x5B,0x04,0x51,0x04,0xF4,0x03,
+0xA3,0x03,0x51,0x03,0xA3,0x02,0x6D,0x02,0x51,0x02,0xA3,0x01,0x51,0x01,0xA3,0x00,
+0x51,0x00,0x62,0x02,0xD9,0x01,0x6D,0x01,0x38,0x00,0x36,0x00,0x31,0x00,0x29,0x00,
+0x1B,0x00,0x51,0x57,0xBF,0x02,0x00,0xEB,0x0F,0x90,0x51,0x56,0xBF,0x01,0x00,0xEB,
+0x07,0x90,0x51,0x56,0xBF,0x03,0x00,0x90,0x3C,0x19,0x76,0x02,0xB0,0x17,0x98,0x8B,
+0xF0,0x8A,0x82,0xC4,0x00,0x2A,0xE4,0x8B,0xF0,0x83,0xFE,0x18,0x73,0x46,0xD1,0xE6,
+0x2E,0x8B,0x8C,0x52,0x22,0xF7,0x46,0x38,0x80,0x00,0x74,0x05,0x2E,0x8B,0x8C,0x82,
+0x22,0xF7,0xC7,0x02,0x00,0x74,0x12,0x3B,0x8E,0x94,0x00,0x74,0x0C,0x89,0x8E,0x94,
+0x00,0x8A,0xC5,0xE6,0xEC,0x8A,0xC1,0xE6,0xE4,0xF7,0xC7,0x01,0x00,0x74,0x12,0x3B,
+0x8E,0x96,0x00,0x74,0x0C,0x89,0x8E,0x96,0x00,0x8A,0xC5,0xE6,0xF8,0x8A,0xC1,0xE6,
+0xF0,0x5E,0x59,0xC3,0x77,0x06,0x8B,0x8E,0x8E,0x00,0xEB,0xC5,0x8B,0x8E,0x90,0x00,
+0xEB,0xBF,0xD5,0x03,0xF6,0x00,0x3E,0x00,0x10,0x00,0x04,0x00,0xCA,0x04,0x33,0x01,
+0x4D,0x00,0x14,0x00,0x05,0x00,0x01,0x03,0x05,0x07,0x09,0x00,0x01,0x02,0x03,0x04,
+0x80,0x84,0x1E,0x00,0xA0,0x25,0x26,0x00,0x00,0x00,0x60,0x8B,0xF0,0x33,0xFF,0x2E,
+0xA1,0x50,0x23,0x2E,0x8B,0x16,0x52,0x23,0xBB,0x32,0x23,0xF7,0x46,0x38,0x80,0x00,
+0x74,0x0C,0x2E,0xA1,0x54,0x23,0x2E,0x8B,0x16,0x56,0x23,0xBB,0x3C,0x23,0xB9,0x05,
+0x00,0x2E,0x3B,0x31,0x73,0x0A,0x47,0x47,0xE2,0xF7,0xB8,0xFF,0xFF,0xEB,0x1D,0x90,
+0xD1,0xEF,0x2E,0x8A,0x8D,0x46,0x23,0x2A,0xED,0xD1,0xEA,0xD1,0xD8,0xE2,0xFA,0xF7,
+0xF6,0x05,0x02,0x00,0xC1,0xE8,0x02,0x2E,0x8A,0xA5,0x4B,0x23,0x2E,0xA3,0x58,0x23,
+0x61,0x2E,0xA1,0x58,0x23,0xC3,0x08,0x00,0x20,0x00,0x80,0x00,0x00,0x02,0x60,0x09,
+0x08,0x00,0x20,0x00,0x80,0x00,0x00,0x02,0x00,0x08,0x00,0x00,0x01,0x00,0x02,0x00,
+0x03,0x00,0x04,0x00,0x52,0x56,0x57,0x85,0xC0,0x74,0x05,0x3D,0x01,0x09,0x76,0x03,
+0xB8,0x01,0x09,0xBF,0x5B,0x01,0xF7,0x46,0x38,0x80,0x00,0x74,0x03,0xBF,0xB2,0x01,
+0x33,0xF6,0x2E,0x3B,0x84,0xB6,0x23,0x76,0x04,0x46,0x46,0xEB,0xF5,0xF7,0xE7,0x2E,
+0x8B,0xBC,0xC0,0x23,0x03,0xC7,0x83,0xD2,0x00,0xD1,0xE7,0xF7,0xF7,0x2E,0x8A,0xA4,
+0xCA,0x23,0x5F,0x5E,0x5A,0xC3,0xE4,0x3E,0x80,0xBE,0xC3,0x00,0x03,0x75,0x0C,0xF7,
+0x46,0x7A,0x20,0x00,0x74,0x05,0x0C,0x80,0xE6,0x3E,0xC3,0x24,0x7F,0xE6,0x3E,0xC3,
+0x24,0x03,0x88,0x86,0xC3,0x00,0x8A,0xE0,0xE4,0x10,0x24,0xFC,0x0A,0xC4,0xE6,0x10,
+0x80,0x8E,0xA1,0x00,0x42,0xE8,0xCE,0xFF,0xC3,0x90,0x56,0x8B,0xF0,0x83,0xE6,0x07,
+0xD1,0xE6,0x2E,0xFF,0xA4,0x58,0x24,0x90,0x68,0x24,0x6C,0x24,0x70,0x24,0x74,0x24,
+0x78,0x24,0x87,0x24,0x87,0x24,0x87,0x24,0xB4,0x00,0xEB,0x0E,0xB4,0xC0,0xEB,0x0A,
+0xB4,0x40,0xEB,0x06,0xB4,0x20,0xEB,0x02,0xB4,0xA0,0xE4,0x10,0x24,0x1F,0x0A,0xC4,
+0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,0x5E,0xC3,0x90,0x3C,0x02,0x77,0x12,0x8A,0xE0,
+0xE4,0x10,0x24,0xF3,0xC0,0xE4,0x02,0x0A,0xC4,0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,
+0xC3,0x90,0x8B,0x5E,0x38,0x84,0xC0,0x74,0x1F,0x3C,0x02,0x74,0x20,0x83,0xCB,0x08,
+0x8B,0x46,0x2E,0x3B,0x46,0x3C,0x77,0x0C,0xE8,0x88,0xFC,0x72,0x07,0xB0,0x24,0xE6,
+0x0A,0x83,0xCB,0x10,0x89,0x5E,0x38,0xC3,0x83,0xE3,0xF7,0xEB,0xF7,0xF7,0xC3,0x10,
+0x00,0x74,0xF5,0xE8,0x6D,0xFC,0x72,0xEC,0x8A,0x86,0xC0,0x00,0xE6,0x38,0xB0,0x23,
+0xE6,0x0A,0xEB,0xE0,0x8B,0x5E,0x38,0x8B,0x46,0x2E,0x3B,0x46,0x3C,0xE4,0xD8,0x77,
+0x0B,0x24,0xFE,0x80,0xCB,0x12,0xE6,0xD8,0x89,0x5E,0x38,0xC3,0x0C,0x01,0x80,0xCB,
+0x02,0xEB,0xF3,0x50,0x33,0xDB,0xC1,0xE8,0x04,0x25,0x0F,0x0F,0x8A,0xD8,0x2E,0x8A,
+0x87,0x66,0x17,0x8A,0xDC,0x2E,0x8A,0xA7,0x66,0x17,0x09,0x46,0x3E,0x58,0xC3,0x50,
+0x33,0xDB,0xC1,0xE8,0x04,0x25,0x0F,0x0F,0x8A,0xD8,0x2E,0x8A,0x87,0x66,0x17,0x8A,
+0xDC,0x2E,0x8A,0xA7,0x66,0x17,0xF7,0xD0,0x21,0x46,0x3E,0x58,0xC3,0x8B,0x46,0x3E,
+0x33,0xDB,0x8A,0xD8,0x0A,0xDC,0x2E,0x8A,0x87,0x76,0x17,0xE6,0x2C,0x8A,0xE0,0xE4,
+0x2A,0x24,0x0F,0x0A,0xC4,0xE6,0x2A,0x8A,0x86,0xA5,0x00,0x84,0xE4,0x75,0x0D,0xA8,
+0x80,0x74,0x11,0x24,0x7F,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xA8,0x80,0x75,0x04,
+0x0C,0x80,0xEB,0xF1,0xC3,0x1E,0x60,0x33,0xC9,0x33,0xD2,0x33,0xF6,0x8E,0xD9,0x8D,
+0xBE,0xFD,0x00,0x57,0x8B,0x05,0x84,0xC0,0x74,0x16,0x8B,0xD1,0x42,0x8B,0xFE,0x4F,
+0x78,0x09,0x38,0xA3,0xE4,0x00,0x74,0x08,0x4F,0x79,0xF7,0x88,0xA2,0xE4,0x00,0x46,
+0x5F,0x83,0xC7,0x09,0x41,0x83,0xF9,0x10,0x72,0xD9,0x89,0xB6,0x86,0x00,0x89,0x96,
+0x84,0x00,0x61,0x1F,0xC3,0x53,0xC7,0x46,0x66,0x00,0x00,0x8B,0x46,0x64,0xA9,0x40,
+0x00,0x74,0x0D,0xB3,0x00,0xA9,0x80,0x00,0x74,0x02,0xB3,0x7F,0x88,0x9E,0xC1,0x00,
+0x32,0xDB,0xA9,0x02,0x00,0x74,0x03,0x80,0xCB,0x40,0xA9,0x00,0x40,0x74,0x03,0x80,
+0xCB,0x02,0xA9,0x00,0x80,0x74,0x03,0x80,0xCB,0x01,0xA9,0x30,0x1E,0x74,0x03,0x80,
+0xCB,0xBC,0xA9,0x00,0x20,0x74,0x03,0x80,0xCB,0x08,0xA9,0x04,0x01,0x74,0x03,0x80,
+0xCB,0x10,0xA9,0x08,0x00,0x74,0x03,0x80,0xCB,0x20,0x88,0x9E,0xC2,0x00,0x5B,0xC3,
+0x06,0x51,0x57,0x50,0x16,0x07,0x8D,0xBE,0xC4,0x00,0xB9,0x1F,0x00,0x33,0xC0,0xAA,
+0x40,0xE2,0xFC,0x8B,0x86,0x92,0x00,0x89,0x86,0x8E,0x00,0x89,0x86,0x90,0x00,0x58,
+0x5F,0x59,0x07,0xC3,0xE4,0xD8,0xC0,0xE8,0x04,0x53,0x25,0x0F,0x00,0x8B,0xD8,0x2E,
+0x8A,0x87,0x66,0x17,0x88,0x86,0xA9,0x00,0x5A,0xC3,0x08,0x86,0xAC,0x00,0xC6,0x86,
+0xBA,0x00,0x01,0xB0,0x0E,0xE8,0xEA,0xE9,0xC3,0xAD,0x36,0xA3,0xB4,0x13,0xAD,0x36,
+0xA3,0xB6,0x13,0xAD,0x36,0xA3,0xB8,0x13,0x83,0xE9,0x06,0x36,0xF7,0x06,0xB6,0x13,
+0x0F,0x00,0xC3,0x8A,0x46,0x26,0xF7,0x46,0x48,0x80,0x00,0x74,0x02,0x0C,0x10,0x88,
+0x86,0xBD,0x00,0x32,0xC0,0x83,0x7E,0x1A,0x00,0x75,0x0E,0x8B,0x5E,0x40,0x43,0x80,
+0xE3,0xFE,0x3B,0x5E,0x08,0x75,0x02,0x0C,0x01,0x83,0x7E,0x3A,0x00,0x75,0x0D,0x1E,
+0xC5,0x5E,0x14,0x8B,0x1F,0x1F,0x85,0xDB,0x75,0x02,0x0C,0x02,0xF7,0x46,0x38,0x10,
+0x00,0x74,0x02,0x0C,0x04,0x8B,0x5E,0x7A,0xF7,0xC3,0x02,0x00,0x74,0x02,0x0C,0x08,
+0xF7,0xC3,0x04,0x00,0x74,0x02,0x0C,0x10,0xF7,0xC3,0x08,0x00,0x74,0x02,0x0C,0x20,
+0xF7,0xC3,0x40,0x00,0x74,0x02,0x0C,0x40,0x88,0x86,0xBF,0x00,0xC3,0x90,0x6A,0x00,
+0x1F,0xC6,0x06,0x93,0x12,0x0D,0x9C,0x0E,0xE8,0xF1,0xEB,0x90,0xB0,0x02,0xE6,0xDA,
+0xF8,0xC3,0x33,0xC0,0xE6,0xDA,0xF8,0xC3,0xB0,0x01,0xE6,0xD8,0xF8,0xC3,0x33,0xC0,
+0xE6,0xD8,0xF8,0xC3,0xB0,0xFF,0xE8,0x4E,0xFA,0xE8,0xA1,0xFA,0xF8,0xC3,0xAC,0x49,
+0xE8,0xAF,0xFB,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x15,0xFD,0xF8,0xC3,0x90,0xAC,0x49,
+0xE8,0x67,0xFD,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x1F,0xFD,0xF8,0xC3,0x90,0xAC,0x49,
+0xE6,0x34,0xF8,0xC3,0xAC,0x49,0xE6,0x36,0xF8,0xC3,0xAC,0x49,0x3C,0x02,0x77,0x1F,
+0x84,0xC0,0x75,0x1D,0xE4,0x14,0x24,0xEF,0xE6,0x14,0xE4,0x12,0x24,0x3F,0xE6,0x12,
+0xE4,0x16,0xA8,0x04,0x74,0x09,0xE8,0xEA,0xF9,0x72,0x04,0xB0,0x18,0xE6,0x0A,0xF8,
+0xC3,0x8A,0xE0,0xE4,0x14,0x0C,0x10,0xE6,0x14,0xE4,0x12,0x0C,0xC0,0xF6,0xC4,0x01,
+0x74,0x02,0x24,0x7F,0xE6,0x12,0xF8,0xC3,0xAC,0x49,0xE8,0x25,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x40,0xE8,0x7D,0xFD,0xE8,0xB4,0xFD,0xE8,0xA8,0xFE,0xB0,0x01,0xE8,0xB9,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x85,0xFD,0xE8,0xA0,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x10,0xE8,0x5D,0xFD,0xE8,0x94,0xFD,0xE8,0x88,0xFE,0xB0,0x08,0xE8,0x99,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x65,0xFD,0xE8,0x80,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x80,0xE8,0x3D,0xFD,0xE8,0x74,0xFD,0xE8,0x68,0xFE,0xB0,0x02,0xE8,0x79,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x45,0xFD,0xE8,0x60,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x20,0xE8,0x1D,0xFD,0xE8,0x54,0xFD,0xE8,0x48,0xFE,0xB0,0x04,0xE8,0x59,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x25,0xFD,0xE8,0x40,0xFD,0xF8,0xC3,0x90,
+0xAC,0x49,0xE8,0x48,0x14,0xE4,0x3C,0x24,0xE7,0x0A,0xC4,0xE6,0x3C,0xF8,0xC3,0x90,
+0xB8,0xFC,0x3B,0x89,0x46,0x7C,0xE4,0x3C,0x0C,0x18,0xE6,0x3C,0xF8,0xC3,0xE4,0x12,
+0x0C,0x02,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFD,0xEB,0xF6,0xE8,0xB5,0xFC,0xF8,
+0xC3,0x90,0x83,0x66,0x38,0xFD,0xF8,0xC3,0xAC,0x49,0xA8,0x01,0x74,0x06,0x83,0x4E,
+0x7A,0x20,0xEB,0x04,0x83,0x66,0x7A,0xDF,0xE8,0xCB,0xFB,0xF8,0xC3,0x90,0x8A,0x86,
+0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0x4E,0x26,0x01,
+0x20,0xAC,0x49,0x32,0xE4,0x89,0x46,0x6E,0x83,0x4E,0x48,0x08,0x49,0x46,0xF9,0xC3,
+0x8A,0x86,0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0x4E,
+0x26,0x01,0x20,0xAC,0xB4,0x0A,0xF6,0xE4,0xEB,0xD8,0xE8,0xFA,0x13,0xE4,0x3C,0x24,
+0xF8,0x0A,0xC4,0xE6,0x3C,0xF8,0xC3,0x90,0xAD,0x49,0x49,0x89,0x46,0x64,0xA9,0x01,
+0x00,0x74,0x1B,0x8B,0xD8,0x83,0xE3,0xFA,0x75,0x1A,0xA9,0x04,0x00,0x74,0x0F,0xE4,
+0x3E,0x0C,0x02,0xE6,0x3E,0xB8,0x38,0x44,0x89,0x46,0x62,0xF8,0xC3,0x90,0xE4,0x3E,
+0x24,0xFC,0xEB,0xEF,0xE4,0x3E,0x24,0xFC,0xE6,0x3E,0xE8,0xE8,0xFC,0xB8,0xAA,0x40,
+0xEB,0xE6,0xE8,0x6E,0xF8,0x72,0x05,0xB0,0x18,0xE6,0x0A,0xF8,0xC3,0x90,0xAC,0x49,
+0xE8,0xCF,0xF9,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0xCF,0xF9,0xF8,0xC3,0x90,0xE8,0x68,
+0xFD,0x75,0x06,0x32,0xC0,0xE6,0xDA,0xF8,0xC3,0xB0,0x02,0xE6,0xDA,0x36,0xA0,0xB4,
+0x13,0x24,0x10,0x34,0x10,0xE8,0x16,0x01,0x36,0xA1,0xB4,0x13,0xA9,0x01,0x00,0x74,
+0x05,0xE8,0xFC,0xFE,0xEB,0x0E,0xA9,0x02,0x00,0x74,0x04,0x32,0xC0,0xEB,0x02,0xB0,
+0x01,0xE8,0xDE,0xFE,0x36,0xA1,0xB4,0x13,0xE8,0xB5,0x13,0xE4,0x3C,0x24,0xF8,0x0A,
+0xC4,0xE6,0x3C,0x36,0xA1,0xB4,0x13,0xC1,0xE8,0x05,0x25,0x01,0x00,0xE8,0xFA,0xFE,
+0x36,0xA0,0xB5,0x13,0x24,0x10,0xE8,0x59,0xFB,0x32,0xC0,0x36,0x8A,0x26,0xB5,0x13,
+0xF6,0xC4,0x04,0x74,0x09,0xFE,0xC0,0xF6,0xC4,0x08,0x74,0x02,0xFE,0xC0,0xE8,0xDB,
+0xFD,0x36,0xA1,0xB6,0x13,0x25,0x0F,0x00,0xE8,0x57,0xF9,0x36,0xA1,0xB6,0x13,0xC1,
+0xE8,0x04,0x25,0x03,0x00,0xE8,0xB8,0xFA,0x36,0xA1,0xB6,0x13,0xC1,0xE8,0x05,0x25,
+0x02,0x00,0xE8,0x05,0xFB,0x36,0xA1,0xB6,0x13,0xF6,0xC4,0x01,0x75,0x04,0x32,0xC0,
+0xEB,0x09,0x80,0xE4,0x02,0xD0,0xEC,0xB0,0x02,0x2A,0xC4,0xE8,0xAC,0xFA,0x36,0xF6,
+0x06,0xB7,0x13,0x40,0x74,0x05,0xE8,0x83,0xFE,0xEB,0x03,0xE8,0x84,0xFE,0x36,0xF6,
+0x06,0xB7,0x13,0x20,0x74,0x05,0xE8,0x65,0xFE,0xEB,0x03,0xE8,0x68,0xFE,0xF8,0xC3,
+0xE4,0x12,0x0C,0x01,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFE,0xEB,0xF6,0xE4,0x14,
+0x24,0xF0,0x0C,0x05,0xE6,0x14,0xE4,0x2A,0x24,0xF0,0x0C,0x06,0xE6,0x2A,0xF8,0xC3,
+0xE4,0x2A,0x24,0xF0,0xE6,0x2A,0xE4,0x14,0x24,0xF0,0x0C,0x07,0xE6,0x14,0xF8,0xC3,
+0xAD,0x49,0x49,0xE8,0x64,0xF9,0x89,0x86,0x8E,0x00,0xF8,0xC3,0xAD,0x49,0x49,0xE8,
+0x58,0xF9,0x89,0x86,0x90,0x00,0xF8,0xC3,0x83,0x4E,0x26,0x04,0xE8,0xA8,0xF7,0xF8,
+0xC3,0x90,0x83,0x66,0x26,0xFB,0xE8,0x9E,0xF7,0xF8,0xC3,0x90,0xAC,0x49,0x84,0xC0,
+0x75,0x0D,0xE4,0x10,0x24,0xEF,0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,0xF8,0xC3,0xE4,
+0x10,0x0C,0x10,0xEB,0xF1,0x90,0xAC,0x49,0x3C,0x02,0x76,0x02,0x32,0xC0,0xC0,0xE0,
+0x04,0xA8,0x20,0x74,0x02,0x0C,0x08,0x24,0x18,0x8A,0xE0,0xE4,0x12,0x24,0xE7,0x0A,
+0xC4,0xE6,0x12,0x80,0x8E,0xA1,0x00,0x44,0xF8,0xC3,0xAC,0x49,0x88,0x86,0xC0,0x00,
+0xF8,0xC3,0xAC,0x49,0xE6,0x3A,0xF8,0xC3,0xAC,0x49,0x84,0xC0,0x74,0x08,0xE4,0x12,
+0x0C,0x04,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFB,0xEB,0xF6,0xAC,0x49,0xE8,0xD6,
+0xF6,0x73,0x03,0xE8,0x27,0xF7,0xF8,0xC3,0xE4,0x12,0xA8,0x02,0x74,0x04,0x24,0xFD,
+0xE6,0x12,0xB8,0xF0,0x00,0xE8,0x87,0xFA,0x81,0x66,0x26,0xFF,0xF3,0xE8,0x57,0xF7,
+0xE8,0x9A,0xFA,0xF8,0xC3,0x90,0xB8,0x80,0x00,0xE8,0x57,0xFA,0x80,0x4E,0x27,0x08,
+0xE8,0x44,0xF7,0xE8,0x87,0xFA,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x61,0xFA,0x81,0x66,
+0x26,0xFF,0xF7,0xE8,0x31,0xF7,0xE8,0x74,0xFA,0xF8,0xC3,0x90,0xB8,0x10,0x00,0xE8,
+0x31,0xFA,0x80,0x4E,0x27,0x04,0xE8,0x1E,0xF7,0xE8,0x61,0xFA,0xF8,0xC3,0xB8,0x10,
+0x00,0xE8,0x3B,0xFA,0x81,0x66,0x26,0xFF,0xFB,0xE8,0x0B,0xF7,0xE8,0x4E,0xFA,0xF8,
+0xC3,0x90,0x33,0xC0,0xAC,0x49,0x3C,0x01,0x73,0x04,0xB0,0x01,0xEB,0x06,0x3C,0x0C,
+0x76,0x02,0xB0,0x0C,0x89,0x46,0x1C,0xF8,0xC3,0x90,0x81,0x4E,0x26,0x00,0x20,0x8A,
+0x86,0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x83,0x4E,0x26,
+0x01,0xF8,0xC3,0x90,0x81,0x4E,0x26,0x00,0x40,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,
+0x86,0xA5,0x00,0xE6,0x0C,0xF8,0xC3,0x90,0xAC,0x49,0x50,0xE8,0x05,0xF6,0x58,0x72,
+0x08,0xE6,0x38,0xB0,0x23,0xE6,0x0A,0xF8,0xC3,0xF9,0xC3,0x90,0xAC,0x50,0xAD,0xE8,
+0x82,0xF8,0x5A,0xF6,0xC2,0x01,0x74,0x12,0x39,0x86,0x96,0x00,0x74,0x0C,0x89,0x86,
+0x96,0x00,0xE6,0xF0,0x86,0xE0,0xE6,0xF8,0x86,0xE0,0xF6,0xC2,0x02,0x74,0x10,0x39,
+0x86,0x94,0x00,0x74,0x0A,0x89,0x86,0x94,0x00,0xE6,0xE4,0x86,0xE0,0xE6,0xEC,0x83,
+0xE9,0x03,0xC3,0x90,0xE4,0x16,0x88,0x86,0xBC,0x00,0xE8,0xE6,0xFA,0x33,0xDB,0xE4,
+0x0C,0xA8,0x06,0x74,0x03,0x80,0xCB,0x01,0xA8,0x10,0x74,0x03,0x80,0xCB,0x02,0xA8,
+0x80,0x74,0x03,0x80,0xCB,0x04,0xE4,0x12,0x8A,0xE0,0x24,0x18,0x0A,0xD8,0xE4,0xDA,
+0xF6,0xC4,0x02,0x74,0x07,0xA8,0x40,0x75,0x03,0x80,0xCB,0x20,0xA8,0x02,0x75,0x09,
+0xE4,0x2A,0xA8,0x0F,0x74,0x03,0x80,0xCB,0x40,0xF7,0x46,0x38,0x02,0x00,0x74,0x09,
+0xE4,0xD8,0xA8,0x01,0x75,0x03,0x80,0xCB,0x80,0x88,0x9E,0xBE,0x00,0xFE,0x86,0xB4,
+0x00,0xB0,0x0A,0xE8,0x5C,0xE4,0xF8,0xC3,0xAC,0x49,0x3C,0x02,0x74,0x41,0x77,0x1F,
+0x50,0xE8,0x4F,0xF5,0x58,0x72,0x0C,0x84,0xC0,0x74,0x0A,0xB0,0x12,0xE6,0x0A,0x80,
+0x4E,0x38,0x01,0xF8,0xC3,0xB0,0x11,0xE6,0x0A,0x80,0x66,0x38,0xFE,0xF8,0xC3,0x8B,
+0x46,0x38,0x25,0xFF,0xF7,0x89,0x46,0x38,0xA9,0x00,0x04,0x75,0xE6,0x8A,0x86,0xA5,
+0x00,0xA8,0x10,0x75,0xDE,0x0C,0x10,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xF8,0xC3,0x81,
+0x4E,0x38,0x00,0x08,0x8A,0x86,0xA5,0x00,0xA8,0x10,0x74,0xC7,0x24,0xEF,0xEB,0xE7,
+0xAD,0x49,0x49,0x3C,0x01,0x72,0x11,0x3C,0x0C,0x77,0x0D,0x50,0x8A,0xE0,0xE4,0x14,
+0x25,0xF0,0x0F,0x0A,0xC4,0xE6,0x14,0x58,0x8A,0xC4,0x84,0xC0,0x74,0x02,0xE6,0x42,
+0xF8,0xC3,0xE8,0xCF,0xF9,0xFE,0x86,0xB9,0x00,0xB0,0x0E,0xE8,0xD4,0xE3,0xF8,0xC3,
+0x3A,0x86,0xAF,0x00,0x74,0x1F,0x88,0x86,0xAF,0x00,0x8A,0xE0,0x80,0xC2,0x06,0xB0,
+0xBF,0xEE,0x80,0xEA,0x02,0x8A,0xC4,0xEE,0x8A,0x86,0xA8,0x00,0x80,0xC2,0x02,0xEE,
+0x80,0xEA,0x06,0x8A,0xC4,0xC3,0x8B,0x46,0x3E,0x85,0xC0,0x8A,0x86,0xA5,0x00,0x74,
+0x12,0xA8,0x08,0x75,0x0D,0x0C,0x08,0x88,0x86,0xA5,0x00,0x80,0xC2,0x02,0xEE,0x80,
+0xEA,0x02,0xC3,0xA8,0x08,0x74,0xFB,0x24,0xF7,0xEB,0xEC,0x8B,0x46,0x26,0x84,0xC0,
+0x74,0x16,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x74,0x0D,0x24,0xFD,0x88,0x86,0xA5,0x00,
+0x83,0xC2,0x02,0xEE,0x83,0xEA,0x02,0xC3,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x75,0xF7,
+0x0C,0x02,0xEB,0xE8,0x52,0x83,0xC2,0x0C,0xEC,0xC0,0xE8,0x04,0x88,0x86,0xA9,0x00,
+0x8B,0x5E,0x26,0x80,0xE3,0x3F,0xF6,0xC7,0x04,0x74,0x07,0xA8,0x08,0x75,0x03,0x80,
+0xCB,0x40,0xF6,0xC7,0x08,0x74,0x07,0xA8,0x02,0x75,0x03,0x80,0xCB,0x80,0x88,0x5E,
+0x26,0x8A,0x86,0xA5,0x00,0x84,0xDB,0x74,0x10,0xA8,0x02,0x74,0x0A,0x24,0xFD,0x88,
+0x86,0xA5,0x00,0x83,0xEA,0x0A,0xEE,0x5A,0xC3,0xA8,0x02,0x75,0xFA,0x0C,0x02,0xEB,
+0xEE,0x90,0xFF,0xFF,0x00,0x48,0x00,0x30,0xBA,0x20,0xC4,0x1A,0x00,0x18,0x00,0x12,
+0x00,0x0C,0x00,0x06,0x00,0x03,0x00,0x02,0x80,0x01,0xC0,0x00,0x60,0x00,0x30,0x00,
+0x18,0x00,0xCD,0x01,0x00,0x01,0x80,0x00,0x10,0x00,0x10,0x00,0x0E,0x00,0x0C,0x00,
+0x08,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x04,0x00,0x03,0x00,0x02,0x00,0x01,0x00,
+0x52,0x51,0x56,0x3C,0x1E,0x77,0x47,0x98,0x8B,0xF0,0x8A,0x82,0xC4,0x00,0x32,0xE4,
+0x83,0xFE,0x18,0x74,0x3D,0x83,0xFE,0x19,0x74,0x3E,0x83,0xFE,0x1E,0x77,0x2F,0xD1,
+0xE6,0x2E,0x8B,0x8C,0x32,0x2D,0x3B,0x8E,0x94,0x00,0x74,0x22,0x89,0x8E,0x94,0x00,
+0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x8A,0xE0,0x0C,0x80,0xEE,0x83,0xEA,0x06,0x8A,
+0xC1,0xEE,0x83,0xC2,0x02,0x8A,0xC5,0xEE,0x83,0xC2,0x04,0x8A,0xC4,0xEE,0x5E,0x59,
+0x5A,0xC3,0x8B,0x8E,0x8E,0x00,0xEB,0xCE,0x8B,0x8E,0x90,0x00,0xEB,0xC8,0x52,0x51,
+0x3D,0x05,0x00,0x77,0x03,0xB8,0x05,0x00,0x8B,0xC8,0xBA,0x02,0x00,0xB8,0x00,0xD0,
+0xF7,0xF1,0x05,0x01,0x00,0xD1,0xE8,0x59,0x5A,0xC3,0x8B,0x46,0x7A,0xA8,0x20,0x74,
+0x0B,0x80,0xBE,0xC3,0x00,0x03,0x75,0x04,0x0C,0x01,0xEB,0x02,0x24,0xFE,0x89,0x46,
+0x7A,0xC3,0x24,0x03,0x88,0x86,0xC3,0x00,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,0x80,0xE4,
+0xFC,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,0xEE,0x83,
+0xEA,0x06,0xE8,0xC5,0xFF,0xC3,0x00,0x08,0x18,0x38,0x28,0x90,0x3C,0x04,0x77,0x23,
+0x32,0xE4,0x8B,0xD8,0x2E,0x8A,0x87,0x26,0x2E,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,0x80,
+0xE4,0xC7,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,0xEE,
+0x83,0xEA,0x06,0xC3,0x84,0xC0,0x74,0x02,0xB0,0x04,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,
+0x80,0xE4,0xFB,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,
+0xEE,0x83,0xEA,0x06,0xC3,0x90,0x8B,0x5E,0x38,0x84,0xC0,0x74,0x34,0x3C,0x02,0x74,
+0x3B,0x8A,0x86,0xAF,0x00,0x0C,0x04,0xE8,0xE6,0xFD,0x8B,0x46,0x2E,0x3B,0x46,0x3C,
+0x77,0x1B,0xF7,0xC3,0x00,0x04,0x75,0x15,0x81,0xCB,0x00,0x04,0x83,0xC2,0x02,0x8A,
+0x86,0xA5,0x00,0x24,0xFA,0x88,0x86,0xA5,0x00,0xEE,0x83,0xEA,0x02,0x89,0x5E,0x38,
+0xC3,0x8A,0x86,0xAF,0x00,0x24,0xFB,0xE8,0xB6,0xFD,0xEB,0xF1,0xF7,0xC3,0x10,0x00,
+0x74,0xEF,0xEB,0xED,0x83,0xC2,0x0C,0xEC,0x83,0xEA,0x0C,0xC0,0xE8,0x04,0x88,0x86,
+0xA9,0x00,0xC3,0x90,0x8A,0x86,0xA7,0x00,0x0C,0x01,0x88,0x86,0xA7,0x00,0x8B,0xDA,
+0x80,0xC2,0x08,0xEE,0x8B,0xD3,0xF8,0xC3,0x8A,0x86,0xA7,0x00,0x24,0xFE,0xEB,0xEA,
+0x8A,0x86,0xA7,0x00,0x0C,0x02,0xEB,0xE2,0x8A,0x86,0xA7,0x00,0x24,0xFD,0xEB,0xDA,
+0xB0,0xFF,0xE8,0x52,0xF2,0xE8,0x97,0xF2,0xF8,0xC3,0xAC,0x49,0xE8,0x61,0xFE,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0xEB,0xFE,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x35,0xFF,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x05,0xFF,0xF8,0xC3,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,
+0xEE,0x52,0x83,0xC2,0x02,0xAC,0x49,0xEE,0x5A,0x8A,0x86,0xA8,0x00,0xEE,0x5A,0xF8,
+0xC3,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x06,0xEB,0xE6,0x90,
+0xAC,0x49,0x3C,0x02,0x77,0x0D,0x84,0xC0,0x75,0x0B,0x8A,0x86,0xAF,0x00,0x24,0xFD,
+0xE8,0x0D,0xFD,0xF8,0xC3,0x50,0x8A,0x86,0xAF,0x00,0x0C,0x02,0xE8,0x01,0xFD,0x5B,
+0x83,0xC2,0x08,0x8A,0x86,0xA7,0x00,0xF6,0xC3,0x01,0x74,0x0C,0x24,0xDF,0x88,0x86,
+0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,0xC3,0x0C,0x20,0xEB,0xF2,0xAC,0x49,0xE8,0xE5,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x69,0xF5,0xE8,0xF9,0xFC,0xE8,0x24,0xFF,
+0xB0,0x01,0xE8,0xA5,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x71,0xF5,0xE8,0xE5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x49,0xF5,0xE8,0xD9,0xFC,0xE8,0x04,0xFF,
+0xB0,0x08,0xE8,0x85,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x51,0xF5,0xE8,0xC5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x29,0xF5,0xE8,0xB9,0xFC,0xE8,0xE4,0xFE,
+0xB0,0x02,0xE8,0x65,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x31,0xF5,0xE8,0xA5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x09,0xF5,0xE8,0x99,0xFC,0xE8,0xC4,0xFE,
+0xB0,0x04,0xE8,0x45,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x11,0xF5,0xE8,0x85,
+0xFC,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x34,0x0C,0xF8,0xC3,0x90,0xB8,0xFC,0x3B,0x89,
+0x46,0x7C,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0x0C,0x80,0xE8,0x43,0xFC,0xF8,0xC3,0x90,
+0x8A,0x86,0xAF,0x00,0x24,0x7F,0xEB,0xF2,0x8A,0x86,0xAF,0x00,0x0C,0x40,0xE8,0x2F,
+0xFC,0xF8,0xC3,0x90,0x8A,0x86,0xAF,0x00,0x24,0xBF,0xEB,0xF2,0xAC,0x49,0xA8,0x01,
+0x74,0x07,0x83,0x4E,0x7A,0x20,0xEB,0x05,0x90,0x83,0x66,0x7A,0xDF,0xE8,0x8A,0xFD,
+0xF8,0xC3,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,
+0x83,0xEA,0x06,0xAC,0x49,0x32,0xE4,0x89,0x46,0x6E,0x83,0x4E,0x26,0x01,0x83,0x4E,
+0x48,0x08,0xB0,0x06,0xE8,0xBB,0xDF,0x49,0x46,0xF9,0xC3,0x90,0x83,0xC2,0x06,0x8A,
+0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,0x83,0xEA,0x06,0xAC,0xB4,0x0A,
+0xF6,0xE4,0xEB,0xD0,0xE8,0xE0,0x0B,0xF8,0xC3,0x90,0xAD,0x49,0x49,0x89,0x46,0x64,
+0xA9,0x01,0x00,0x74,0x19,0x8B,0xD8,0x83,0xE3,0xFA,0x75,0x0A,0xA9,0x04,0x00,0x74,
+0x0D,0xB8,0xE2,0x3F,0xEB,0x0B,0xE8,0xEC,0xF4,0xB8,0xAA,0x40,0xEB,0x03,0xB8,0x38,
+0x44,0x89,0x46,0x62,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0xA8,0x02,0x74,0x0A,0x24,0xFD,
+0xE8,0x8D,0xFB,0x0C,0x02,0xE8,0x88,0xFB,0xF8,0xC3,0xAC,0x49,0xE8,0x81,0xFC,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x79,0xFC,0xF8,0xC3,0x90,0xE8,0x5C,0xF5,0x75,0x05,0xE8,
+0xE6,0xFD,0xF8,0xC3,0xE8,0xCD,0xFD,0x36,0xA0,0xB4,0x13,0x24,0x10,0x34,0x10,0xE8,
+0x26,0x01,0x36,0xA1,0xB4,0x13,0xA9,0x01,0x00,0x74,0x05,0xE8,0xFE,0xFE,0xEB,0x0E,
+0xA9,0x02,0x00,0x74,0x04,0x32,0xC0,0xEB,0x02,0xB0,0x01,0xE8,0xE8,0xFE,0x36,0xA1,
+0xB4,0x13,0xE8,0xAB,0x0B,0x36,0xA1,0xB4,0x13,0xC1,0xE8,0x05,0x25,0x01,0x00,0xE8,
+0x0C,0xFF,0x36,0xA0,0xB5,0x13,0x24,0x10,0xE8,0x2B,0xFD,0x32,0xC0,0x36,0x8A,0x26,
+0xB5,0x13,0xF6,0xC4,0x04,0x74,0x09,0xFE,0xC0,0xF6,0xC4,0x08,0x74,0x02,0xFE,0xC0,
+0xE8,0xEF,0xFD,0x36,0xA1,0xB6,0x13,0x25,0x0F,0x00,0xE8,0x03,0xFC,0x36,0xA1,0xB6,
+0x13,0xC1,0xE8,0x04,0x25,0x03,0x00,0xE8,0x88,0xFC,0x36,0xA1,0xB6,0x13,0xC1,0xE8,
+0x05,0x25,0x02,0x00,0xE8,0xCD,0xFC,0x36,0xA1,0xB6,0x13,0xF6,0xC4,0x01,0x75,0x04,
+0x32,0xC0,0xEB,0x09,0x80,0xE4,0x02,0xD0,0xEC,0xB0,0x02,0x2A,0xC4,0xE8,0x8C,0xFC,
+0x36,0xF6,0x06,0xB7,0x13,0x40,0x74,0x05,0xE8,0x8D,0xFE,0xEB,0x03,0xE8,0x94,0xFE,
+0x36,0xF6,0x06,0xB7,0x13,0x20,0x74,0x05,0xE8,0x69,0xFE,0xEB,0x03,0xE8,0x70,0xFE,
+0xF8,0xC3,0xF8,0xC3,0x8B,0x46,0x38,0xA9,0x04,0x00,0x75,0x23,0x0D,0x04,0x00,0x89,
+0x46,0x38,0x83,0xC2,0x08,0x8B,0x46,0x2E,0x3B,0x46,0x3C,0x73,0x14,0x83,0x4E,0x38,
+0x10,0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,
+0xC3,0x8A,0x86,0xA7,0x00,0x0C,0x01,0xEB,0xEE,0x90,0x8B,0x46,0x38,0xA9,0x04,0x00,
+0x74,0x06,0x25,0xFB,0xFF,0x89,0x46,0x38,0xF8,0xC3,0xAD,0x49,0x49,0xE8,0xBE,0xFB,
+0x89,0x86,0x8E,0x00,0xF8,0xC3,0xAD,0x49,0x49,0xE8,0xB2,0xFB,0x89,0x86,0x90,0x00,
+0xF8,0xC3,0x83,0x4E,0x26,0x04,0xE8,0x92,0xFA,0xF8,0xC3,0x90,0x83,0x66,0x26,0xFB,
+0xE8,0x88,0xFA,0xF8,0xC3,0x90,0xAC,0x49,0x84,0xC0,0x75,0x07,0x80,0x8E,0xA3,0x00,
+0x04,0xF8,0xC3,0x80,0xA6,0xA3,0x00,0xFB,0xF8,0xC3,0xAC,0x49,0x83,0xC2,0x08,0x3C,
+0x02,0x76,0x02,0x32,0xC0,0x3C,0x01,0x74,0x12,0x77,0x0B,0x8A,0x86,0xA7,0x00,0x24,
+0xEF,0x88,0x86,0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,0xC3,0x8A,0x86,0xA7,0x00,0x0C,
+0x10,0xEB,0xEE,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x04,0xAC,
+0x49,0xEE,0x5A,0x8A,0x86,0xA8,0x00,0xEE,0x5A,0xF8,0xC3,0x90,0x52,0x83,0xC2,0x06,
+0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x08,0xEB,0xE6,0x90,0xAC,0x49,0xF8,0xC3,0xAC,0x49,
+0xE8,0xB4,0xEE,0x73,0x03,0xE8,0xF7,0xEE,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0x24,0x7F,
+0xE8,0xBD,0xF9,0xB8,0xF0,0x00,0xE8,0x66,0xF2,0x81,0x66,0x26,0xFF,0xF3,0xE8,0x23,
+0xFA,0xE8,0xD2,0xF9,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x37,0xF2,0x80,0x4E,0x27,0x08,
+0xE8,0x11,0xFA,0xE8,0xC0,0xF9,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x41,0xF2,0x81,0x66,
+0x26,0xFF,0xF7,0xE8,0xFE,0xF9,0xE8,0xAD,0xF9,0xF8,0xC3,0x90,0xB8,0x10,0x00,0xE8,
+0x11,0xF2,0x80,0x4E,0x27,0x04,0xE8,0xEB,0xF9,0xE8,0x9A,0xF9,0xF8,0xC3,0xB8,0x10,
+0x00,0xE8,0xFF,0xF1,0x81,0x66,0x26,0xFF,0xFB,0xE8,0xD8,0xF9,0xF8,0xC3,0xAC,0x49,
+0xF8,0xC3,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,
+0x83,0xEA,0x06,0xF8,0xC3,0x90,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x24,0xBF,0xEB,
+0xEA,0x90,0xAC,0x49,0x8A,0xE0,0x80,0xC2,0x0A,0xEC,0x80,0xEA,0x0A,0xA8,0x20,0x74,
+0x05,0x8A,0xC4,0xEE,0xF8,0xC3,0x06,0x51,0x57,0x8B,0x4E,0x24,0xE3,0x34,0x49,0x89,
+0x4E,0x24,0xFF,0x46,0x1A,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x8A,0xC4,0xAA,0x89,0x7E,
+0x22,0x8B,0x46,0x26,0x24,0xFD,0x89,0x46,0x26,0x75,0x29,0x8A,0x86,0xA5,0x00,0xA8,
+0x02,0x75,0x21,0x80,0xC2,0x02,0x0C,0x02,0x88,0x86,0xA5,0x00,0xEE,0x80,0xEA,0x02,
+0xEB,0x12,0xC4,0x7E,0x00,0x3B,0x7E,0x1E,0x76,0x0A,0x4F,0x26,0x88,0x25,0x89,0x7E,
+0x00,0xFF,0x46,0x1A,0x5F,0x59,0x07,0xF8,0xC3,0x90,0xAC,0xAD,0x83,0xE9,0x03,0x85,
+0xC0,0x74,0x05,0x3D,0x00,0x20,0x72,0x05,0xB8,0xFF,0xFF,0xEB,0x03,0xC1,0xE0,0x03,
+0x3B,0x86,0x94,0x00,0x74,0x26,0x89,0x86,0x94,0x00,0x8B,0xD8,0x52,0x83,0xC2,0x06,
+0x8A,0x86,0xA8,0x00,0x8A,0xE0,0x0C,0x80,0xEE,0x83,0xEA,0x06,0x8A,0xC3,0xEE,0x83,
+0xC2,0x02,0x8A,0xC7,0xEE,0x83,0xC2,0x04,0x8A,0xC4,0xEE,0x5A,0xF8,0xC3,0xB0,0x88,
+0x88,0x86,0xBC,0x00,0xE8,0x8C,0xF2,0x33,0xDB,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x74,
+0x03,0x80,0xCB,0x01,0xA8,0x05,0x74,0x03,0x80,0xCB,0x02,0xA8,0x08,0x74,0x03,0x80,
+0xCB,0x04,0xF6,0x86,0xA7,0x00,0x10,0x74,0x03,0x80,0xCB,0x10,0x8A,0x86,0xA9,0x00,
+0xF6,0xC3,0x04,0x75,0x0A,0x83,0xC2,0x0C,0xEC,0x83,0xEA,0x0C,0xC0,0xE8,0x04,0x8A,
+0xE0,0x8A,0x86,0xAF,0x00,0xA8,0x80,0x74,0x08,0xF6,0xC4,0x01,0x75,0x03,0x80,0xCB,
+0x20,0xF6,0x86,0xA7,0x00,0x02,0x75,0x0A,0xF7,0x46,0x38,0x04,0x00,0x74,0x03,0x80,
+0xCB,0x40,0x88,0x9E,0xBE,0x00,0xFE,0x86,0xB4,0x00,0xB0,0x0A,0xE8,0xF3,0xDB,0xF8,
+0xC3,0xFE,0x86,0xB4,0x00,0xB0,0x0A,0xE8,0xE8,0xDB,0xF8,0xC3,0xAC,0x49,0x3C,0x02,
+0x74,0x37,0x77,0x10,0x84,0xC0,0x74,0x06,0x80,0x4E,0x38,0x01,0xF8,0xC3,0x80,0x66,
+0x38,0xFE,0xF8,0xC3,0x8B,0x46,0x38,0x25,0xFF,0xF7,0x89,0x46,0x38,0xA9,0x00,0x04,
+0x75,0xEA,0x8A,0x86,0xA5,0x00,0xA8,0x01,0x75,0xE2,0x0C,0x05,0x83,0xC2,0x02,0x88,
+0x86,0xA5,0x00,0xEE,0x83,0xEA,0x02,0xF8,0xC3,0x81,0x4E,0x38,0x00,0x08,0x8A,0x86,
+0xA5,0x00,0xA8,0x01,0x74,0xC6,0x24,0xFA,0xEB,0xE2,0xAD,0x49,0x49,0xF8,0xC3,0x90,
+0xE8,0x11,0xFA,0xFE,0x86,0xB9,0x00,0xB0,0x0E,0xE8,0x86,0xDB,0xF8,0xC3,0xB0,0xFF,
+0xE8,0xBF,0xEC,0xF8,0xC3,0x90,0x83,0x66,0x7A,0xFB,0xB0,0x00,0xE8,0x73,0xDB,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x53,0xD9,0x72,0x11,0x36,0x88,0x1E,0x1A,0x01,0x36,0xA0,
+0x8E,0x12,0x0A,0xC3,0x52,0xBA,0x00,0x01,0xEE,0x5A,0xF8,0xC3,0xAC,0x49,0x32,0xE4,
+0x36,0xA3,0x86,0x12,0x05,0x06,0x00,0x36,0x8B,0x1E,0x88,0x12,0x2B,0xD8,0x36,0x89,
+0x1E,0x8A,0x12,0xF8,0xC3,0x90,0xAD,0x8B,0xD8,0xAD,0x83,0xE9,0x04,0x03,0xC3,0x2B,
+0x46,0x76,0x89,0x46,0x78,0xF7,0x46,0x7A,0x02,0x00,0x74,0x0A,0x83,0x66,0x7A,0xFD,
+0xB8,0x00,0x00,0xE8,0x1C,0xDB,0xF8,0xC3,0x06,0x16,0x07,0xAC,0x49,0x25,0x0F,0x00,
+0x6B,0xC0,0x09,0x8D,0xBE,0xFD,0x00,0x03,0xF8,0xAC,0x49,0x25,0x0F,0x00,0xAA,0x85,
+0xC0,0x74,0x08,0x2B,0xC8,0x51,0x8B,0xC8,0xF3,0xA4,0x59,0xE8,0x27,0xF0,0xE8,0x44,
+0x03,0x07,0xF8,0xC3,0x33,0xC0,0xAC,0x49,0x36,0xA3,0xB2,0x13,0x36,0xA3,0xB0,0x13,
+0xF8,0xC3,0x83,0x66,0x7A,0xEF,0xE8,0x2C,0x03,0xF8,0xC3,0x90,0x83,0x4E,0x7A,0x10,
+0xEB,0xF4,0xE8,0x9B,0xF0,0xF8,0xC3,0x90,0xAD,0x3C,0x19,0x77,0x0E,0x3C,0x19,0x77,
+0x0A,0x8B,0xF8,0x81,0xE7,0xFF,0x00,0x88,0xA6,0xC4,0x00,0xF8,0xC3,0x90,0x83,0x4E,
+0x26,0x20,0xAC,0x49,0x32,0xE4,0xD1,0xE0,0x8B,0xD8,0xC1,0xE3,0x02,0x03,0xC3,0x89,
+0x46,0x6E,0x83,0x4E,0x48,0x04,0xB0,0x06,0xE8,0x97,0xDA,0x49,0x46,0xF9,0xC3,0x90,
+0xFE,0x86,0xB3,0x00,0xB0,0x0A,0xE8,0x89,0xDA,0xF8,0xC3,0x90,0x33,0xC0,0xAC,0x49,
+0x6B,0xC0,0x0A,0x89,0x86,0x8A,0x00,0xF8,0xC3,0x90,0xAC,0x49,0x32,0xE4,0x3D,0x0A,
+0x00,0x77,0x05,0xB8,0x0A,0x00,0xEB,0x08,0x3D,0x5A,0x00,0x72,0x03,0xB8,0x5A,0x00,
+0x51,0xF7,0xD8,0x05,0x64,0x00,0x8B,0xC8,0x8B,0x46,0x44,0xF7,0xE1,0xB9,0x64,0x00,
+0xF7,0xF1,0x89,0x46,0x46,0x59,0xF8,0xC3,0xAC,0x49,0xE8,0x85,0xEB,0xF8,0xC3,0x90,
+0xAC,0x49,0x84,0xC0,0x75,0x07,0x81,0x66,0x38,0xFF,0xFD,0xF8,0xC3,0x81,0x4E,0x38,
+0x00,0x02,0xF7,0x46,0x38,0x40,0x00,0x75,0x08,0x8A,0x86,0xA9,0x00,0x88,0x86,0xAA,
+0x00,0xF8,0xC3,0x90,0x51,0x56,0xE8,0x7F,0x0C,0x5E,0x59,0xF8,0xC3,0x90,0xFE,0x86,
+0xB6,0x00,0xB0,0x0A,0xE8,0x0B,0xDA,0xF8,0xC3,0x90,0xFE,0x86,0xB7,0x00,0xB0,0x0A,
+0xE8,0xFF,0xD9,0xF8,0xC3,0x90,0xFE,0x86,0xB8,0x00,0xB0,0x0A,0xE8,0xF3,0xD9,0xF8,
+0xC3,0x90,0x00,0x90,0x51,0x55,0xAC,0x2E,0xA2,0x52,0x36,0x33,0xC9,0xAD,0x8B,0xF9,
+0xC1,0xE7,0x05,0xA9,0x01,0x00,0x74,0x23,0x2E,0x8B,0xAD,0x44,0x00,0x83,0x7E,0x08,
+0x00,0x74,0x18,0x2E,0x80,0x3E,0x52,0x36,0x01,0x74,0x09,0x60,0xB0,0x04,0xE8,0xBB,
+0x0C,0x61,0xEB,0x07,0x60,0xB0,0xFB,0xE8,0xEC,0x0C,0x61,0x47,0x47,0xD1,0xE8,0x75,
+0xD2,0x41,0x83,0xF9,0x04,0x72,0xC6,0x5D,0x59,0x83,0xE9,0x05,0xF7,0x46,0x38,0x40,
+0x00,0x74,0x05,0xE8,0x87,0xEA,0xF8,0xC3,0xE8,0x8D,0xEA,0xF8,0xC3,0x90,0x36,0xC6,
+0x06,0xC8,0x13,0x01,0xF8,0xC3,0x33,0xC0,0xAC,0x49,0x36,0xA3,0x80,0x12,0xAC,0x49,
+0x36,0x2B,0x06,0x88,0x12,0xF7,0xD8,0x36,0xA3,0x82,0x12,0xF8,0xC3,0x90,0xDE,0x26,
+0xDE,0x26,0xEC,0x26,0xF2,0x26,0xF8,0x26,0xFE,0x26,0x04,0x27,0x0E,0x27,0x16,0x27,
+0x1E,0x27,0x26,0x27,0x2E,0x27,0x34,0x27,0xBE,0x34,0xC6,0x34,0xD2,0x34,0x3A,0x27,
+0x78,0x27,0x80,0x27,0x94,0x27,0xA0,0x27,0xB4,0x27,0xC0,0x27,0xD4,0x27,0xE0,0x27,
+0xF4,0x27,0x00,0x28,0x10,0x28,0xEC,0x34,0xDE,0x26,0x1E,0x28,0x26,0x28,0x2C,0x28,
+0x32,0x28,0x38,0x28,0x4E,0x28,0x8A,0x28,0x06,0x35,0x28,0x35,0x98,0x28,0xBE,0x28,
+0xD2,0x28,0xDE,0x28,0xE6,0x28,0x54,0x35,0x62,0x35,0x6C,0x35,0xEE,0x28,0xC0,0x29,
+0xC8,0x29,0xCE,0x29,0xE0,0x29,0x72,0x35,0x78,0x35,0xF0,0x29,0xFC,0x29,0x8E,0x35,
+0x08,0x2A,0x12,0x2A,0x1C,0x2A,0xB0,0x35,0x36,0x2A,0xBC,0x35,0x5A,0x2A,0x62,0x2A,
+0x68,0x2A,0xCA,0x35,0x7C,0x2A,0xF8,0x35,0x88,0x2A,0xA6,0x2A,0xB8,0x2A,0xCC,0x2A,
+0xDE,0x2A,0xF2,0x2A,0x00,0x36,0x0A,0x2B,0x24,0x2B,0x24,0x36,0x38,0x2B,0x4C,0x2B,
+0x84,0x2B,0x2E,0x36,0x3A,0x36,0x46,0x36,0x54,0x36,0xE8,0x2B,0xAE,0x36,0x40,0x2C,
+0x62,0x2C,0xB6,0x36,0x70,0x28,0xDE,0x26,0xDE,0x26,0xD4,0x2E,0xE8,0x2E,0xF0,0x2E,
+0xF8,0x2E,0x00,0x2F,0x0A,0x2F,0x12,0x2F,0x1A,0x2F,0x22,0x2F,0x2A,0x2F,0x42,0x2F,
+0xBE,0x34,0xC6,0x34,0xD2,0x34,0x50,0x2F,0x8C,0x2F,0x94,0x2F,0xA8,0x2F,0xB4,0x2F,
+0xC8,0x2F,0xD4,0x2F,0xE8,0x2F,0xF4,0x2F,0x08,0x30,0x14,0x30,0x1C,0x30,0xEC,0x34,
+0xDE,0x26,0x24,0x30,0x30,0x30,0x38,0x30,0x44,0x30,0x4C,0x30,0x62,0x30,0xA4,0x30,
+0x06,0x35,0x28,0x35,0xAA,0x30,0xCE,0x30,0xD6,0x30,0xEA,0x30,0xF2,0x30,0x54,0x35,
+0x62,0x35,0x6C,0x35,0xFA,0x30,0xC2,0x31,0xC2,0x31,0xC4,0x31,0xFA,0x31,0x72,0x35,
+0x78,0x35,0x0A,0x32,0x16,0x32,0x8E,0x35,0x22,0x32,0x2C,0x32,0x36,0x32,0xB0,0x35,
+0x4A,0x32,0xBC,0x35,0x74,0x32,0x8C,0x32,0x9A,0x32,0xCA,0x35,0x9E,0x32,0xF8,0x35,
+0xAA,0x32,0xC6,0x32,0xD8,0x32,0xEC,0x32,0xFE,0x32,0x0E,0x33,0x00,0x36,0x12,0x33,
+0x26,0x33,0x24,0x36,0x32,0x33,0x9A,0x33,0xDE,0x33,0x2E,0x36,0x3A,0x36,0x46,0x36,
+0x54,0x36,0x5C,0x34,0xAE,0x36,0xAA,0x34,0xB0,0x34,0xB6,0x36,0x8C,0x30,0xE3,0x28,
+0xF7,0x46,0x38,0x40,0x00,0x75,0x32,0xE8,0xE3,0xE8,0x33,0xC0,0xAC,0x49,0x3D,0x5B,
+0x00,0x77,0x19,0x8B,0xD8,0xD1,0xE3,0x2E,0xFF,0x97,0xCE,0x36,0x72,0x0B,0x85,0xC9,
+0x75,0xE8,0x8B,0x46,0x48,0xE8,0x1A,0x0C,0xC3,0x4E,0x41,0xC3,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x0C,0x9C,0x0E,0xE8,0x63,0xDA,0xE8,0xBC,0xE8,0x33,0xC0,0xAC,0x49,
+0x3D,0x5B,0x00,0x77,0xE7,0x8B,0xD8,0xD1,0xE3,0x2E,0xFF,0x97,0x86,0x37,0x72,0xD9,
+0x85,0xC9,0x75,0xE8,0xC3,0xF7,0x46,0x7A,0x10,0x00,0x75,0x0F,0x83,0xBE,0x84,0x00,
+0x00,0x74,0x08,0xB8,0x48,0x3A,0x89,0x86,0x80,0x00,0xC3,0x81,0xBE,0x80,0x00,0xEC,
+0x3C,0x74,0xF7,0x83,0xBE,0x88,0x00,0x00,0x75,0x05,0xB8,0xEC,0x3C,0xEB,0xE7,0xF7,
+0x46,0x7A,0x08,0x00,0x75,0x40,0x1E,0x60,0x8B,0x8E,0x88,0x00,0x3B,0x4E,0x74,0x77,
+0x33,0x3B,0x4E,0x78,0x77,0x2E,0xC4,0x7E,0x10,0x8B,0xDF,0x26,0x03,0x3D,0x47,0x47,
+0x33,0xC0,0x8E,0xD8,0x8D,0xB6,0xF4,0x00,0x8B,0xC1,0xF7,0x46,0x7A,0x01,0x00,0x75,
+0x1D,0xF3,0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xB0,
+0x0C,0xE8,0x3E,0xD7,0x61,0x1F,0xC7,0x86,0x88,0x00,0x00,0x00,0xEB,0xAC,0xE3,0xE3,
+0x50,0x90,0xAC,0x24,0x7F,0xAA,0xE2,0xFA,0x58,0xEB,0xD8,0x90,0x8B,0x8E,0x88,0x00,
+0xE3,0x46,0x8B,0x9E,0x8A,0x00,0x85,0xDB,0x74,0x3E,0xBA,0x50,0xFF,0xED,0x2B,0x86,
+0x82,0x00,0x3B,0xC3,0x72,0x37,0x8D,0xB6,0xF4,0x00,0xC4,0x7E,0x10,0x8B,0xDF,0x26,
+0x03,0x3D,0x47,0x47,0x8B,0xC1,0x16,0x1F,0xF7,0x46,0x7A,0x01,0x00,0x75,0x24,0xF3,
+0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC7,0x86,0x88,
+0x00,0x00,0x00,0xB0,0x0C,0xE8,0xDA,0xD6,0x83,0x66,0x7A,0xF7,0xC3,0xB0,0x00,0xE8,
+0xD0,0xD6,0xC3,0xE3,0xDC,0x50,0xAC,0x24,0x7F,0xAA,0xE2,0xFA,0x58,0xEB,0xD2,0x90,
+0x1E,0x60,0x33,0xC0,0x8E,0xD8,0x8D,0xB6,0xFD,0x00,0x8B,0x86,0x88,0x00,0x8B,0x96,
+0x84,0x00,0x3A,0x04,0x75,0x10,0x8B,0xDE,0x46,0x8B,0xC8,0x8D,0xBE,0xF4,0x00,0xF3,
+0xA6,0x74,0x66,0x8B,0xF3,0x90,0x83,0xC6,0x09,0x4A,0x75,0xE6,0x8D,0xB6,0xFD,0x00,
+0x8B,0x96,0x84,0x00,0x3A,0x04,0x73,0x10,0x8B,0xDE,0x46,0x8B,0xC8,0x8D,0xBE,0xF4,
+0x00,0xF3,0xA6,0x74,0x76,0x8B,0xF3,0x90,0x83,0xC6,0x09,0x4A,0x75,0xE6,0x8D,0xB6,
+0xF4,0x00,0xAC,0xF7,0x46,0x7A,0x01,0x00,0x74,0x02,0x24,0x7F,0x1E,0xC5,0x5E,0x10,
+0x8B,0x37,0x88,0x40,0x02,0x46,0x89,0x37,0xFF,0x4E,0x78,0xFF,0x46,0x76,0xFF,0x4E,
+0x74,0x1F,0x8B,0x8E,0x88,0x00,0x49,0x89,0x8E,0x88,0x00,0xE3,0x43,0x8D,0xB6,0xF4,
+0x00,0x8B,0xFE,0x46,0xF3,0xA4,0xE9,0x7D,0xFF,0xC5,0x76,0x10,0x8B,0x1C,0x85,0xDB,
+0x74,0x08,0x03,0xF3,0x83,0xC6,0x03,0x83,0xE6,0xFE,0x8B,0x86,0x84,0x00,0x2B,0xC2,
+0xB4,0x80,0x89,0x04,0x46,0x46,0xC7,0x04,0x00,0x00,0x89,0x76,0x10,0x83,0x4E,0x7A,
+0x04,0xC7,0x86,0x88,0x00,0x00,0x00,0x61,0x1F,0xF9,0xC3,0x33,0xC0,0x61,0x1F,0xC3,
+0xB0,0x80,0x84,0xC0,0x61,0x1F,0xC3,0x90,0x8B,0x4E,0x78,0x2B,0x8E,0x88,0x00,0x76,
+0x27,0x89,0xB6,0x8C,0x00,0x8B,0x5E,0x74,0x3B,0xCB,0x72,0x02,0x8B,0xCB,0x3B,0xC8,
+0x72,0x02,0x8B,0xC8,0x8B,0xC1,0xE3,0x44,0x33,0xD2,0x8E,0xC2,0x8B,0xD1,0x83,0xBE,
+0x88,0x00,0x00,0x74,0x06,0xE9,0x8E,0x00,0x33,0xC0,0xC3,0x8B,0x5E,0x10,0x03,0x1F,
+0x43,0x43,0x52,0xF7,0x46,0x7A,0x01,0x00,0x75,0x2A,0xAC,0x8D,0xBE,0xE4,0x00,0x8B,
+0x8E,0x86,0x00,0xF2,0xAE,0x74,0x34,0x88,0x07,0x43,0x4A,0x75,0xED,0x58,0x8B,0x5E,
+0x10,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0x8B,0xC6,0x2B,0x86,
+0x8C,0x00,0xC3,0x90,0xAC,0x8D,0xBE,0xE4,0x00,0x8B,0x8E,0x86,0x00,0xF2,0xAE,0x74,
+0x0A,0x24,0x7F,0x88,0x07,0x43,0x4A,0x75,0xEB,0xEB,0xD2,0x88,0x86,0xF4,0x00,0xC7,
+0x86,0x88,0x00,0x01,0x00,0x58,0x2B,0xC2,0x74,0x0E,0x8B,0x5E,0x10,0x01,0x07,0x29,
+0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0x40,0xE8,0x94,0xFE,0x72,0xBE,0x4A,0x75,
+0x15,0x83,0xBE,0x8A,0x00,0x00,0x74,0xB4,0xBA,0x50,0xFF,0xED,0x89,0x86,0x82,0x00,
+0x83,0x4E,0x7A,0x08,0xEB,0xA6,0x8D,0xBE,0xF4,0x00,0x03,0xBE,0x88,0x00,0xA4,0xFF,
+0x86,0x88,0x00,0xE8,0x6A,0xFE,0x72,0x94,0x79,0x06,0x4A,0x74,0x8F,0xE9,0x5B,0xFF,
+0x4A,0x74,0xCE,0xEB,0xE1,0x90,0x50,0xE8,0x11,0xCC,0x8B,0x46,0x74,0x39,0x46,0x72,
+0x74,0x27,0x1E,0x56,0x51,0x33,0xC9,0xC5,0x76,0x0C,0xAD,0x74,0x10,0x78,0x09,0x03,
+0xC8,0x05,0x01,0x00,0x24,0xFE,0x03,0xF0,0x3B,0x76,0x10,0x76,0xED,0x29,0x4E,0x76,
+0x01,0x4E,0x78,0xE8,0x37,0xCC,0x59,0x5E,0x1F,0x58,0xC3,0x90,0xC4,0x7E,0x10,0x26,
+0x8B,0x1D,0x83,0xC3,0x03,0x26,0x89,0x1D,0x4B,0x03,0xFB,0xAB,0x91,0xAA,0xB8,0x03,
+0x00,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC3,0x90,0xC4,0x7E,0x10,0x26,
+0x8B,0x1D,0x43,0x26,0x89,0x1D,0x43,0x03,0xFB,0xAA,0xFF,0x4E,0x78,0xFF,0x46,0x76,
+0xFF,0x4E,0x74,0xC3,0xE8,0xE5,0xFF,0xC3,0x80,0x81,0x84,0x85,0x82,0x83,0x86,0x87,
+0x50,0x53,0x8A,0xDC,0x83,0xE3,0x0E,0xD1,0xEB,0x2E,0x8A,0x87,0x98,0x3B,0x08,0x86,
+0xB0,0x00,0xFE,0x86,0xB1,0x00,0xB0,0x0A,0xE8,0x87,0xD4,0x5B,0x58,0xC3,0x50,0x8A,
+0xC8,0xB8,0xFF,0x00,0xE8,0x95,0xFF,0x58,0xC3,0x90,0x8A,0x86,0xBB,0x00,0xE8,0xAB,
+0xFF,0xC3,0xE8,0xCB,0xFF,0xE8,0xF2,0xFF,0xC3,0x90,0xE8,0xC3,0xFF,0xE8,0xB4,0xFF,
+0xC3,0x90,0x33,0xC0,0xE8,0x95,0xFF,0xC3,0xB8,0xFF,0x00,0x33,0xC9,0xE8,0x6C,0xFF,
+0xC3,0x90,0xB8,0xFF,0x01,0xB1,0x10,0xE8,0x62,0xFF,0xC3,0x90,0xC3,0xFC,0x3B,0xE2,
+0x3B,0xF2,0x3B,0xF2,0x3B,0xFC,0x3B,0xE2,0x3B,0xE8,0x3B,0xE8,0x3B,0xFC,0x3B,0xE2,
+0x3B,0xE8,0x3B,0xE8,0x3B,0xFC,0x3B,0xE2,0x3B,0xE2,0x3B,0xE2,0x3B,0x00,0x10,0x00,
+0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,
+0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x08,0x00,
+0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x51,0x53,0x8B,
+0x4E,0x38,0x81,0xE1,0xFF,0xEE,0xA8,0x04,0x74,0x04,0x81,0xC9,0x00,0x01,0x8A,0xE0,
+0x80,0xE4,0x03,0x24,0x18,0xD0,0xE4,0x0A,0xC4,0x33,0xDB,0x8A,0xD8,0x2E,0x8B,0x87,
+0xFD,0x3B,0x89,0x46,0x7C,0x2E,0x0B,0x8F,0x1D,0x3C,0x89,0x4E,0x38,0xD1,0xEB,0x2E,
+0x8A,0xA7,0x3D,0x3C,0x5B,0x59,0xC3,0xAC,0x49,0x3C,0x01,0x72,0x1D,0x74,0x20,0x3C,
+0x03,0x72,0x23,0x74,0x28,0x3C,0x08,0x72,0x2B,0x74,0x30,0x3C,0x20,0x72,0x37,0x74,
+0x3A,0xBB,0xDA,0x3B,0x32,0xE4,0x89,0x5E,0x7E,0xC3,0xBB,0xA0,0x3B,0xEB,0xF5,0xBB,
+0x94,0x3B,0xB4,0x01,0xEB,0xF0,0xBB,0xFC,0x3B,0xB4,0x02,0xEB,0xE9,0xBB,0xE2,0x3B,
+0xB4,0x03,0xEB,0xE2,0xBB,0xBE,0x3B,0xB4,0x04,0xEB,0xDB,0xBB,0xCA,0x3B,0xAC,0x49,
+0x88,0x86,0xBB,0x00,0xEB,0xCE,0xBB,0xD2,0x3B,0xEB,0xF3,0xBB,0xFC,0x3B,0xEB,0xC4,
+0xA9,0x04,0x00,0x75,0xD1,0xA9,0x08,0x00,0x75,0xDA,0xEB,0xD1,0x8B,0x5E,0x74,0x8B,
+0x4E,0x78,0x3B,0xCB,0x72,0x02,0x8B,0xCB,0x3B,0xC8,0x72,0x02,0x8B,0xC8,0x8B,0xC1,
+0xE3,0x2C,0xC4,0x7E,0x10,0x8B,0xDF,0x26,0x03,0x3D,0x47,0x47,0xF7,0x46,0x7A,0x01,
+0x00,0x75,0x1C,0xF7,0xC7,0x01,0x00,0x74,0x02,0x49,0xA4,0xD1,0xE9,0xF3,0xA5,0x73,
+0x01,0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC3,0x50,
+0x53,0xBB,0x7F,0x7F,0xF7,0xC7,0x01,0x00,0x74,0x05,0x49,0xAC,0x22,0xC3,0xAA,0xD1,
+0xE9,0xE3,0x1D,0x9C,0xAD,0x23,0xC3,0xAB,0x49,0x74,0x14,0xAD,0x23,0xC3,0xAB,0x49,
+0x74,0x0D,0xAD,0x23,0xC3,0xAB,0x49,0x74,0x06,0xAD,0x23,0xC3,0xAB,0xE2,0xE5,0x9D,
+0x73,0x04,0xAC,0x22,0xC3,0xAB,0x5B,0x58,0xEB,0xB8,0xE8,0xCE,0xC9,0x8B,0x5E,0x38,
+0xF7,0xC3,0x10,0x04,0x75,0x01,0xC3,0xF7,0xC3,0x40,0x00,0x74,0x05,0xE8,0xB8,0xE3,
+0xEB,0x03,0xE8,0xA8,0xE3,0x81,0x66,0x38,0xEF,0xFB,0xF6,0xC3,0x10,0x74,0x3C,0xF6,
+0xC3,0x02,0x74,0x06,0xE4,0xD8,0x0C,0x01,0xE6,0xD8,0xF6,0xC3,0x04,0x74,0x11,0x83,
+0xC2,0x08,0x8A,0x86,0xA7,0x00,0x0C,0x01,0xEE,0x88,0x86,0xA7,0x00,0x83,0xEA,0x08,
+0xF6,0xC3,0x08,0x74,0x0F,0xE8,0x8B,0xE3,0x72,0x0A,0x8A,0x86,0xC0,0x00,0xE6,0x38,
+0xB0,0x23,0xE6,0x0A,0xF7,0xC3,0x00,0x04,0x75,0x01,0xC3,0xF7,0xC3,0x00,0x08,0x75,
+0xF9,0x8A,0x86,0xA5,0x00,0xF6,0xC3,0x40,0x75,0x0D,0xA8,0x10,0x75,0xEC,0x0C,0x10,
+0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xA8,0x01,0x75,0xDF,0x83,0xC2,0x02,0x0C,0x05,
+0xEE,0x88,0x86,0xA5,0x00,0xC3,0xB0,0x00,0xE8,0x47,0xD2,0xEB,0x0F,0xB0,0x02,0xE8,
+0x90,0x0E,0xEB,0x08,0x83,0x66,0x38,0xDF,0x83,0x4E,0x7A,0x02,0x33,0xC0,0x8E,0xD8,
+0xFA,0xA0,0x92,0x12,0x40,0xA2,0x92,0x12,0x3C,0x05,0x72,0x1E,0xC6,0x06,0x92,0x12,
+0x00,0xFB,0xB0,0x01,0xE8,0x6B,0x0E,0xFA,0xA1,0x26,0x01,0x23,0x06,0x2A,0x01,0xA8,
+0x01,0x75,0x07,0xE8,0xE2,0x07,0xE8,0x61,0x09,0x90,0xB0,0x00,0xE8,0x37,0xD2,0xFB,
+0x85,0xED,0x74,0xB9,0xFA,0xF7,0x46,0x7A,0x46,0x00,0x75,0xC0,0x8B,0x46,0x78,0x3D,
+0x0A,0x00,0x72,0xB0,0x8B,0x4E,0x74,0x83,0xF9,0x50,0x72,0x9A,0x83,0x66,0x38,0xDF,
+0xC5,0x76,0x14,0x8B,0x46,0x3A,0x85,0xC0,0x75,0x58,0xAD,0x85,0xC0,0x75,0x0F,0xE8,
+0xF8,0xFE,0xF7,0x46,0x7A,0x08,0x00,0x74,0x93,0xE8,0xA0,0xFA,0xEB,0x8E,0x3B,0x76,
+0x04,0x76,0x21,0xB9,0x02,0x00,0x39,0x4E,0x2E,0x77,0x05,0xC7,0x46,0x2E,0x00,0x00,
+0x56,0x8B,0x76,0x2C,0x89,0x76,0x04,0xC7,0x04,0x00,0x00,0x46,0x46,0x89,0x76,0x2C,
+0x29,0x4E,0x2E,0x5E,0x85,0xC0,0x79,0x17,0xF6,0xC4,0x10,0x74,0x05,0xFF,0x56,0x7C,
+0xEB,0x03,0xFF,0x56,0x7E,0x89,0x76,0x14,0xB0,0x0C,0xE8,0x85,0xD1,0xEB,0x86,0x89,
+0x46,0x3A,0xFF,0x96,0x80,0x00,0x29,0x46,0x3A,0x89,0x76,0x14,0xB0,0x0C,0xE8,0x71,
+0xD1,0xE9,0x71,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x04,0x10,0x02,
+0x01,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
+0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,
+0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x4E,0x41,0x78,0x41,0xD0,0x41,0xF4,0x41,0x06,0x42,0x18,0x42,
+0xC3,0x90,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x89,0x7E,0x6C,0x80,0x66,0x27,0xFD,0x8B,
+0x56,0x24,0x83,0xFA,0x04,0x72,0xE9,0x83,0xEA,0x02,0x8B,0xD9,0x3B,0xCA,0x76,0x02,
+0x8B,0xCA,0xB0,0x0A,0x57,0x51,0x8B,0xFE,0xF2,0xAE,0x8B,0xC1,0x59,0x5F,0x75,0x1E,
+0x50,0x40,0x2B,0xC8,0x74,0x06,0x2B,0xD1,0x2B,0xD9,0xF3,0xA4,0x59,0x4B,0x4A,0x4A,
+0xB0,0x0D,0xAA,0xA4,0x3B,0xCA,0x76,0x02,0x8B,0xCA,0xE3,0x13,0xEB,0xD4,0x2B,0xD9,
+0xF7,0xC6,0x01,0x00,0x74,0x02,0xA4,0x49,0xD1,0xE9,0xF3,0xA5,0x73,0x01,0xA4,0x89,
+0x7E,0x22,0x2B,0x7E,0x6C,0x29,0x7E,0x24,0x01,0x7E,0x1A,0x8B,0xCB,0x80,0x7E,0x26,
+0x02,0x74,0x05,0x80,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0x18,0x03,0x61,0xC3,
+0xC3,0x90,0xE8,0x7C,0x02,0x72,0xF9,0x90,0x83,0x4E,0x26,0x20,0x8B,0x46,0x6A,0x89,
+0x46,0x6E,0x8B,0x46,0x48,0x0D,0x04,0x00,0x25,0xBF,0xFF,0x89,0x46,0x48,0xB0,0x06,
+0xE8,0xBF,0xCF,0xC3,0x89,0x7E,0x22,0x2B,0x7E,0x6C,0x01,0x7E,0x1A,0x29,0x7E,0x24,
+0x80,0x7E,0x26,0x02,0x74,0x05,0x83,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0xD5,
+0x02,0x61,0xC3,0x90,0x8A,0xBE,0xC2,0x00,0xEB,0x24,0xF7,0x46,0x48,0x40,0x00,0x75,
+0xB1,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x89,0x7E,0x6C,0x8B,0x56,0x24,0x83,0xEA,0x0A,
+0x78,0x9E,0x03,0xD7,0x80,0x66,0x27,0xFD,0x33,0xC0,0x8A,0xBE,0xC2,0x00,0xE3,0xB4,
+0x3B,0xFA,0x77,0xB0,0xAC,0x49,0x93,0x2E,0x8A,0x87,0xD4,0x3E,0x93,0x22,0xDF,0x75,
+0x17,0xAA,0xE3,0xA0,0x3B,0xFA,0x77,0x9C,0xAC,0x49,0x93,0x2E,0x8A,0x87,0xD4,0x3E,
+0x93,0x22,0xDF,0x75,0x03,0xAA,0xEB,0xD6,0xF6,0xC3,0x7F,0x75,0x05,0xFF,0x46,0x66,
+0xEB,0xDF,0xF6,0xC3,0x40,0x75,0x0C,0x8B,0xD8,0x83,0xEB,0x08,0xD1,0xE3,0x2E,0xFF,
+0xA7,0xD4,0x3F,0xFF,0x46,0x66,0x2C,0x20,0xEB,0xC7,0x85,0xC0,0x74,0x2C,0x89,0x46,
+0x6A,0x83,0x4E,0x48,0x40,0x89,0x7E,0x22,0x2B,0x7E,0x6C,0x01,0x7E,0x1A,0x29,0x7E,
+0x24,0x80,0x7E,0x26,0x02,0x74,0x08,0x83,0x66,0x26,0xFD,0xE8,0xA3,0x01,0xC3,0x60,
+0xB0,0xFD,0xE8,0x31,0x02,0x61,0xE8,0x98,0x01,0xC3,0xE9,0x57,0xFF,0x90,0x8B,0x5E,
+0x66,0x4B,0x78,0x03,0x89,0x5E,0x66,0xAA,0x8B,0x5E,0x64,0xF7,0xC3,0x00,0x20,0x75,
+0x03,0xE9,0x40,0xFF,0xF7,0xC3,0x40,0x00,0x74,0x08,0x8A,0x86,0xC1,0x00,0xAA,0xE9,
+0x32,0xFF,0xB8,0x32,0x00,0xEB,0xA3,0x90,0x8B,0x5E,0x66,0x89,0x5E,0x68,0x83,0xC3,
+0x08,0x80,0xE3,0xF8,0x89,0x5E,0x66,0x8B,0x5E,0x64,0x81,0xE3,0x00,0x18,0x81,0xFB,
+0x00,0x18,0x74,0x2D,0xAA,0x85,0xDB,0x74,0x25,0xF7,0x46,0x64,0x40,0x00,0x75,0x18,
+0x81,0xFB,0x00,0x10,0x74,0x0C,0x8B,0x46,0x66,0x2B,0x46,0x68,0xC1,0xE0,0x04,0xE9,
+0x68,0xFF,0xB8,0x64,0x00,0xE9,0x62,0xFF,0x8A,0x86,0xC1,0x00,0xAA,0xAA,0xE9,0xE3,
+0xFE,0x51,0x8B,0x4E,0x66,0x2B,0x4E,0x68,0xB0,0x20,0xF3,0xAA,0x59,0xE9,0xD4,0xFE,
+0x8B,0x5E,0x66,0x89,0x5E,0x68,0x8B,0x5E,0x64,0xF7,0xC3,0x24,0x00,0x74,0x10,0xC7,
+0x46,0x66,0x00,0x00,0xF7,0xC3,0x04,0x00,0x74,0x05,0xB0,0x0D,0xAA,0xB0,0x0A,0xAA,
+0xEB,0x48,0x90,0x90,0xAA,0xF7,0x46,0x64,0x00,0x40,0x74,0x06,0xB8,0xD0,0x07,0xE9,
+0x18,0xFF,0xE9,0x9F,0xFE,0x90,0xAA,0xF7,0x46,0x64,0x00,0x80,0x74,0x06,0xB8,0xD0,
+0x07,0xE9,0x06,0xFF,0xE9,0x8D,0xFE,0x90,0x8B,0x5E,0x66,0x89,0x5E,0x68,0x85,0xDB,
+0x75,0x0C,0x8B,0x5E,0x64,0xF7,0xC3,0x10,0x00,0x74,0x06,0xE9,0x76,0xFE,0x8B,0x5E,
+0x64,0xF7,0xC3,0x08,0x00,0x74,0x27,0xB0,0x0A,0xAA,0xF7,0xC3,0x20,0x00,0x75,0x1F,
+0xF7,0xC3,0x00,0x01,0x75,0x03,0xE9,0x5B,0xFE,0xF7,0xC3,0x40,0x00,0x75,0x06,0xB8,
+0x64,0x00,0xE9,0xC5,0xFE,0x8A,0x86,0xC1,0x00,0xAA,0xAA,0xE9,0x46,0xFE,0xAA,0xC7,
+0x46,0x66,0x00,0x00,0xF7,0xC3,0x00,0x06,0x74,0xF1,0xF7,0xC3,0x40,0x00,0x74,0x19,
+0x8A,0x86,0xC1,0x00,0x81,0xE3,0x00,0x06,0x81,0xFB,0x00,0x04,0x72,0x06,0x76,0x02,
+0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xE9,0x1B,0xFE,0x81,0xE3,0x00,0x06,0x81,0xFB,0x00,
+0x04,0x72,0x0E,0x76,0x06,0xB8,0x96,0x00,0xE9,0x7F,0xFE,0xB8,0x64,0x00,0xE9,0x79,
+0xFE,0x8B,0x46,0x68,0xE9,0x73,0xFE,0x90,0x36,0x8B,0x0E,0xDA,0x12,0x83,0xF9,0x32,
+0x73,0x1D,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0x8D,0x76,0x4C,0xBF,0xDC,0x12,
+0x03,0xF9,0xA5,0xA5,0xA5,0x83,0xC1,0x06,0x89,0x0E,0xDA,0x12,0x07,0x1F,0xC3,0xB0,
+0x08,0xE8,0x6E,0xCD,0xC3,0x90,0x83,0x66,0x48,0xFE,0xE8,0x93,0xC4,0xE8,0xC8,0xFF,
+0xC3,0xF6,0x46,0x27,0x02,0x75,0x0F,0x9C,0xFA,0x83,0x7E,0x1A,0x00,0x74,0x09,0x80,
+0x4E,0x27,0x01,0x9D,0xF9,0xC3,0xF8,0xC3,0x50,0x52,0xF7,0x46,0x38,0x40,0x00,0x74,
+0x1D,0xE8,0x34,0xDE,0x83,0xC2,0x0A,0xEC,0xA8,0x40,0x75,0x27,0x83,0xEA,0x08,0x8A,
+0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,0xA5,0x00,0xEE,0x5A,0x58,0xEB,0xD1,0xE8,0x0C,
+0xDE,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x5A,
+0x58,0xEB,0xBC,0x80,0x4E,0x27,0x02,0x5A,0x58,0x9D,0xF8,0xC3,0x08,0x46,0x26,0x9C,
+0xFA,0x8A,0x8E,0xA5,0x00,0xF7,0x46,0x38,0x40,0x00,0x75,0x14,0xF6,0xC1,0x06,0x74,
+0x23,0xE8,0xD9,0xDD,0x8A,0xC1,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x9D,0xC3,
+0xF6,0xC1,0x02,0x74,0x0F,0xE8,0xD0,0xDD,0x83,0xC2,0x02,0x8A,0xC1,0x24,0xFD,0x88,
+0x86,0xA5,0x00,0xEE,0x9D,0xC3,0x8B,0x5E,0x26,0x22,0xC3,0x88,0x46,0x26,0x74,0x01,
+0xC3,0x80,0x66,0x27,0xFD,0x9C,0xFA,0x8A,0x8E,0xA5,0x00,0xF7,0x46,0x38,0x40,0x00,
+0x75,0x16,0xF6,0xC1,0x04,0x75,0x0F,0xE8,0x93,0xDD,0x8A,0xC1,0x24,0xFD,0x0C,0x04,
+0x88,0x86,0xA5,0x00,0xE6,0x0C,0x9D,0xC3,0xF6,0xC1,0x02,0x75,0xF9,0xE8,0x88,0xDD,
+0x83,0xC2,0x0A,0xEC,0xA8,0x20,0x75,0x0E,0x83,0xEA,0x08,0x8A,0xC1,0x0C,0x02,0x88,
+0x86,0xA5,0x00,0xEE,0x9D,0xC3,0x83,0xEA,0x0A,0x33,0xC9,0x8A,0x4E,0x1C,0x8B,0x46,
+0x1A,0x3B,0xC8,0x73,0x1B,0x01,0x4E,0x2A,0x2B,0xC1,0x89,0x46,0x1A,0x1E,0xC5,0x76,
+0x00,0xF3,0x6E,0x1F,0x89,0x76,0x00,0x83,0xC2,0x02,0x8A,0x86,0xA5,0x00,0xEB,0xCD,
+0x85,0xC0,0x74,0x12,0x01,0x46,0x2A,0x8B,0xC8,0x1E,0xC5,0x76,0x00,0xF3,0x6E,0x1F,
+0x89,0x76,0x00,0x89,0x4E,0x1A,0xF6,0xC7,0x01,0x75,0x23,0x80,0xCB,0x02,0x89,0x5E,
+0x26,0xE8,0x08,0xC3,0x83,0xC2,0x02,0x8A,0x86,0xA5,0x00,0x24,0xFD,0xEE,0x88,0x86,
+0xA5,0x00,0xF6,0xC7,0x10,0x75,0x05,0xB0,0x02,0xE8,0x16,0xCC,0x9D,0xC3,0x83,0xC2,
+0x02,0x8A,0x86,0xA5,0x00,0xEB,0x86,0x90,0x8B,0xD1,0x8B,0x46,0x24,0x3B,0xC8,0x76,
+0x02,0x8B,0xC8,0x2B,0xD1,0x2B,0xC1,0x8B,0xD9,0xE3,0x22,0x80,0x66,0x27,0xFD,0x8E,
+0x46,0x02,0x8B,0x7E,0x22,0xF7,0xC6,0x01,0x00,0x74,0x02,0xA4,0x49,0xD1,0xE9,0xF3,
+0xA5,0x73,0x01,0xA4,0x89,0x7E,0x22,0x89,0x46,0x24,0x01,0x5E,0x1A,0x8B,0xCA,0x80,
+0x7E,0x26,0x02,0x74,0x05,0x80,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0xF6,0xFE,
+0x61,0xC3,0x50,0xE4,0x0A,0x84,0xC0,0x75,0x0A,0x86,0x86,0xA1,0x00,0x84,0xC0,0x74,
+0x0A,0xE6,0x0A,0x58,0x0C,0x20,0x89,0x46,0x48,0xF9,0xC3,0x58,0x24,0xDF,0x89,0x46,
+0x48,0xF8,0xC3,0x90,0xFB,0xB0,0x02,0xE8,0xE8,0x07,0xFA,0xE8,0x2E,0x01,0xFB,0xB0,
+0x01,0xE8,0xDE,0x07,0xFA,0xB0,0x02,0xE8,0xBC,0xCB,0xFB,0x85,0xED,0x74,0xE5,0xFA,
+0x8E,0x5E,0x0A,0xFB,0x90,0xFA,0x8B,0x46,0x48,0x8B,0x76,0x40,0xA8,0x8C,0x75,0xDE,
+0xA8,0x20,0x74,0x1A,0x50,0xE8,0x55,0xDC,0x58,0xE8,0xA6,0xFF,0x73,0x10,0xB0,0x02,
+0xE8,0x5F,0xCB,0xEB,0xC9,0x90,0x25,0xFF,0x00,0x8B,0xC8,0xEB,0x36,0x90,0xA8,0x01,
+0x75,0x22,0x46,0x83,0xE6,0xFE,0x3B,0x76,0x08,0x74,0x79,0xAD,0x8A,0xFC,0xB3,0xF0,
+0x22,0xFB,0x3A,0xFB,0x74,0xE0,0x3A,0xBE,0xA0,0x00,0x74,0x2E,0xE8,0xD2,0xFD,0x73,
+0x77,0xEB,0x9B,0x90,0x8A,0xE0,0x24,0xFC,0x88,0x46,0x48,0x8B,0x4E,0x4A,0xF6,0xC4,
+0x02,0x74,0x1D,0xE8,0xBB,0xFD,0x72,0x86,0xE8,0x13,0xF3,0x89,0x76,0x40,0xE3,0x93,
+0x83,0x4E,0x48,0x03,0x89,0x4E,0x4A,0xE9,0x74,0xFF,0x25,0xFF,0x0F,0x8B,0xC8,0x90,
+0x8B,0x86,0x98,0x00,0x85,0xC0,0x74,0x1A,0x51,0x8A,0x8E,0xA0,0x00,0xC0,0xE9,0x04,
+0xBA,0x01,0x00,0xD3,0xE2,0x59,0x23,0xC2,0x74,0x08,0x03,0xF1,0x89,0x76,0x40,0xE9,
+0x61,0xFF,0xFF,0x56,0x62,0xE3,0xF5,0x83,0x4E,0x48,0x01,0x89,0x4E,0x4A,0x89,0x76,
+0x40,0xE9,0x3A,0xFF,0x81,0x4E,0x26,0x00,0x10,0x8B,0x46,0x50,0x3B,0x46,0x46,0x77,
+0x03,0xE8,0x52,0xFD,0xE9,0x27,0xFF,0x90,0x88,0xBE,0xA0,0x00,0xEB,0xAC,0x0A,0x06,
+0x90,0x12,0x8A,0xE0,0xBA,0x06,0x01,0xB0,0x04,0xEE,0xEC,0x84,0xC0,0x75,0x12,0xB0,
+0x04,0xEE,0x8A,0xC4,0xEE,0x32,0xE4,0xA8,0x80,0x74,0x06,0xC7,0x06,0x84,0x12,0x00,
+0x00,0x88,0x26,0x90,0x12,0xC3,0x0A,0x06,0x90,0x12,0x8A,0xE0,0xBA,0x06,0x01,0xEC,
+0xA8,0x01,0x75,0xED,0xBA,0x08,0x01,0x8A,0xC4,0xEE,0x32,0xE4,0xA8,0x80,0x74,0xE1,
+0xC7,0x06,0x84,0x12,0x00,0x00,0x88,0x26,0x90,0x12,0xC3,0x90,0x36,0xF7,0x06,0x24,
+0x01,0x01,0x00,0x75,0x30,0x36,0x8B,0x0E,0xDA,0x12,0x80,0xF9,0x36,0x73,0x26,0x33,
+0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0xDC,0x12,0x03,0xF9,0xB0,0x08,0xE8,0x77,0xCA,0x85,
+0xED,0x74,0x0E,0x8D,0x76,0x4C,0xA5,0xA5,0xA5,0x80,0xC1,0x06,0x80,0xF9,0x36,0x72,
+0xE9,0x89,0x0E,0xDA,0x12,0xC3,0xC3,0x90,0xF7,0x06,0x26,0x01,0x01,0x00,0x75,0xF6,
+0x8B,0x0E,0x20,0x13,0x85,0xC9,0x75,0xEE,0x33,0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0x24,
+0x13,0xB9,0x36,0x00,0xB0,0x0A,0xE8,0x3D,0xCA,0x85,0xED,0x75,0x06,0xE9,0x12,0x01,
+0xE9,0x0A,0x01,0x33,0xDB,0x8A,0x46,0x4C,0x8A,0xA6,0xB3,0x00,0xFE,0xCC,0x78,0x0E,
+0x88,0xA6,0xB3,0x00,0x0A,0xDC,0xB4,0x0A,0xAB,0x83,0xE9,0x02,0x76,0xE2,0x8A,0xA6,
+0xB2,0x00,0xFE,0xCC,0x78,0x0E,0x88,0xA6,0xB2,0x00,0x0A,0xDC,0xB4,0x08,0xAB,0x83,
+0xE9,0x02,0x76,0xCC,0x8A,0xA6,0xB1,0x00,0xFE,0xCC,0x78,0x18,0x8A,0xBE,0xB0,0x00,
+0x75,0x04,0x88,0xA6,0xB0,0x00,0x88,0xA6,0xB1,0x00,0x0A,0xDC,0x8A,0xE7,0xAB,0x83,
+0xE9,0x02,0x76,0xAC,0x8A,0xA6,0xB4,0x00,0xFE,0xCC,0x78,0x1F,0x88,0xA6,0xB4,0x00,
+0x0A,0xDC,0xB4,0x0B,0xAB,0x8A,0x86,0xBC,0x00,0x8A,0xA6,0xBD,0x00,0xAB,0x8B,0x86,
+0xBE,0x00,0xAB,0x83,0xE9,0x06,0x76,0x88,0x8A,0x46,0x4C,0x8A,0xA6,0xB6,0x00,0xFE,
+0xCC,0x78,0x19,0x88,0xA6,0xB6,0x00,0x0A,0xDC,0xB4,0x0C,0xAB,0xE8,0xDB,0xCB,0xAB,
+0x8B,0x46,0x2A,0xAB,0x83,0xE9,0x06,0x76,0x74,0x8A,0x46,0x4C,0x8A,0xA6,0xB7,0x00,
+0xFE,0xCC,0x78,0x19,0x88,0xA6,0xB7,0x00,0x0A,0xDC,0xB4,0x0D,0xAB,0xE8,0xBA,0xCB,
+0xAB,0x8B,0x46,0x34,0xAB,0x83,0xE9,0x06,0x76,0x53,0x8A,0x46,0x4C,0x8A,0xA6,0xB8,
+0x00,0xFE,0xCC,0x78,0x19,0x88,0xA6,0xB8,0x00,0x0A,0xDC,0xB4,0x0E,0xAB,0xA1,0x50,
+0x12,0xAB,0xA1,0x52,0x12,0xAB,0x83,0xE9,0x06,0x76,0x32,0x8A,0x46,0x4C,0x8A,0xA6,
+0xB5,0x00,0xFE,0xCC,0x78,0x18,0x88,0xA6,0xB5,0x00,0x0A,0xDC,0xB4,0x0F,0xAB,0x8B,
+0x86,0x9A,0x00,0xAB,0x8B,0x86,0x9C,0x00,0xAB,0x83,0xE9,0x06,0x76,0x0F,0x84,0xDB,
+0x75,0x03,0xE9,0xEF,0xFE,0xB0,0x0A,0xE8,0xF8,0xC8,0xE9,0xE7,0xFE,0xB0,0x0A,0xE8,
+0xF0,0xC8,0xF7,0xD9,0x83,0xC1,0x36,0x8B,0xC1,0x0D,0x80,0x00,0x86,0xC4,0xA3,0x22,
+0x13,0x41,0x41,0x89,0x0E,0x20,0x13,0xC3,0xA1,0x84,0x12,0x2B,0xC1,0x72,0x11,0xA3,
+0x84,0x12,0xBE,0x22,0x13,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0x20,0x13,0xF8,0xC3,
+0xF9,0xC3,0xC3,0x81,0xEF,0x6A,0x13,0x74,0xF9,0x8B,0xC7,0x0D,0x80,0x00,0x86,0xC4,
+0xA3,0x68,0x13,0x47,0x47,0x89,0x3E,0x66,0x13,0xC3,0xF7,0x06,0x2A,0x01,0x01,0x00,
+0x75,0xE0,0x8B,0x0E,0x66,0x13,0xE3,0x07,0x80,0xF9,0x20,0x77,0xD5,0x49,0x49,0x33,
+0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0x6A,0x13,0x8B,0xF7,0x03,0xF9,0x83,0xC6,0x34,0x3B,
+0xFE,0x77,0xC0,0xB0,0x0E,0xE8,0xAE,0xC8,0x85,0xED,0x74,0xB7,0x8A,0x46,0x4C,0x8A,
+0xB6,0xB9,0x00,0xFE,0xCE,0x78,0x15,0x88,0xB6,0xB9,0x00,0x8A,0xA6,0xA9,0x00,0x80,
+0xCC,0xC0,0xAB,0x84,0xF6,0x74,0x05,0xB0,0x0E,0xE8,0x56,0xC8,0x8A,0xB6,0xBA,0x00,
+0xFE,0xCE,0x78,0xCB,0x8A,0x9E,0xA9,0x00,0x8A,0xBE,0xAB,0x00,0x8A,0x56,0x3F,0x8A,
+0xF3,0x32,0xF7,0x0A,0xB6,0xAC,0x00,0xC6,0x86,0xAC,0x00,0x00,0x22,0xF2,0x74,0x4B,
+0xF6,0xC6,0x08,0x74,0x0F,0xB4,0x02,0xF6,0xC3,0x08,0x75,0x02,0xB4,0x03,0xAB,0x80,
+0xE6,0xF7,0x74,0x37,0xF6,0xC6,0x01,0x74,0x0F,0xB4,0x00,0xF6,0xC3,0x01,0x75,0x02,
+0xB4,0x01,0xAB,0x80,0xE6,0xFE,0x74,0x23,0xF6,0xC6,0x02,0x74,0x0F,0xB4,0x04,0xF6,
+0xC3,0x02,0x75,0x02,0xB4,0x05,0xAB,0x80,0xE6,0xFD,0x74,0x0F,0xF6,0xC6,0x04,0x74,
+0x0A,0xB4,0x06,0xF6,0xC3,0x04,0x75,0x02,0xB4,0x07,0xAB,0xC6,0x86,0xBA,0x00,0x00,
+0x88,0x9E,0xAB,0x00,0xE9,0x58,0xFF,0x90,0xA1,0x84,0x12,0x2B,0xC1,0x72,0x11,0xA3,
+0x84,0x12,0xBE,0x68,0x13,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0x66,0x13,0xF8,0xC3,
+0xF9,0xC3,0xA1,0x84,0x12,0x41,0x41,0x2B,0xC1,0x72,0x23,0xA3,0x84,0x12,0x8B,0xC1,
+0x48,0x48,0x32,0xE4,0x0C,0x80,0x86,0xC4,0xEF,0x90,0x90,0x90,0x90,0x90,0xBE,0xDC,
+0x12,0x49,0x49,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0xDA,0x12,0xF8,0xC3,0xF9,0xC3,
+0x8A,0xC8,0x8A,0x46,0x4C,0xB4,0x01,0x83,0xEB,0x06,0xEF,0x90,0x90,0x90,0x90,0x90,
+0xB8,0x01,0x00,0xEF,0x90,0x90,0x90,0x90,0x90,0x8A,0xC1,0xEF,0x90,0x90,0x90,0x90,
+0x90,0xE9,0x97,0x00,0xE9,0xAC,0x00,0x33,0xC0,0x8E,0xD8,0x89,0x1E,0x84,0x12,0xC3,
+0x36,0x8B,0x1E,0x84,0x12,0xFB,0x90,0xFA,0xB0,0x0C,0xE8,0x89,0xC7,0x85,0xED,0x74,
+0xE6,0xC5,0x76,0x0C,0x83,0xFB,0x14,0x72,0xDB,0xFB,0x90,0xFA,0xAD,0x85,0xC0,0x78,
+0xAF,0x74,0xE2,0x8B,0xFE,0x03,0xF8,0x36,0x8B,0x0E,0x86,0x12,0x3B,0xC1,0x77,0x02,
+0x8B,0xC8,0x83,0xEB,0x04,0x3B,0xD9,0x77,0x02,0x8B,0xCB,0x33,0xC0,0x8A,0x46,0x4C,
+0xEF,0x90,0x90,0x90,0x90,0x90,0x8B,0xC1,0xEF,0x90,0x90,0x90,0x90,0x90,0x41,0x80,
+0xE1,0xFE,0x2B,0xD9,0x51,0xD1,0xE9,0xF3,0x6F,0x90,0x59,0x8B,0xC7,0x40,0x24,0xFE,
+0x3B,0xC6,0x74,0x27,0x2B,0xFE,0x4E,0x4E,0x53,0x8B,0x5E,0x10,0x3B,0xF3,0x72,0x13,
+0x03,0x1F,0x83,0xC3,0x03,0x80,0xE3,0xFE,0xC7,0x07,0x00,0x00,0x83,0x6E,0x74,0x02,
+0x89,0x5E,0x10,0x5B,0x89,0x3C,0x89,0x76,0x0C,0xEB,0x89,0x89,0x76,0x0C,0x39,0x76,
+0x10,0x77,0x81,0x72,0x08,0x83,0x3C,0x00,0x74,0x03,0xE9,0x77,0xFF,0xE8,0x0D,0xBE,
+0xE9,0x62,0xFF,0x36,0x89,0x1E,0x84,0x12,0xB0,0x0C,0xE8,0xB5,0xC6,0x33,0xC0,0x8E,
+0xD8,0xC3,0xA1,0x84,0x12,0x3D,0x10,0x00,0x72,0x77,0xBA,0x04,0x01,0x3B,0x06,0x88,
+0x12,0x75,0x06,0xC7,0x06,0x7E,0x12,0x00,0x00,0x8B,0x0E,0xDA,0x12,0xE3,0x0B,0xE8,
+0xD0,0xFE,0x72,0x57,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0x8B,0x0E,0x20,0x13,0xE3,0x0B,
+0xE8,0xA5,0xFD,0x72,0x46,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0x8B,0x0E,0x66,0x13,0xE3,
+0x0B,0xE8,0x94,0xFE,0x72,0x35,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0xA1,0x28,0x01,0xA9,
+0x01,0x00,0x75,0x03,0xE8,0xF9,0xFE,0x80,0x3E,0x8D,0x12,0x00,0x75,0x1D,0xA1,0x84,
+0x12,0x3D,0x20,0x00,0x76,0x15,0x3B,0x06,0x82,0x12,0x76,0x09,0xA1,0x7E,0x12,0x3B,
+0x06,0x80,0x12,0x72,0x0C,0x80,0x0E,0x90,0x12,0x80,0xC3,0xB0,0x80,0xFF,0x16,0x7C,
+0x12,0xC3,0x80,0x0E,0x90,0x12,0x40,0xC3,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x17,
+0x9C,0x0E,0xE8,0xB7,0xC8,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x20,0x9C,0x0E,0xE8,
+0xAA,0xC8,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x16,0x9C,0x0E,0xE8,0x9D,0xC8,0x90,
+0xBA,0x06,0x01,0xEC,0xA8,0x20,0x75,0xCA,0xFB,0x90,0xFA,0xBA,0x04,0x01,0xED,0x90,
+0x90,0x90,0x90,0x90,0x3A,0x06,0x94,0x12,0x77,0xBE,0x33,0xDB,0x8A,0xD8,0xD1,0xE3,
+0x2E,0x8B,0xAF,0x44,0x00,0xC4,0x7E,0x08,0x85,0xFF,0x74,0xB9,0xF6,0xC4,0xC0,0x75,
+0x55,0x32,0xC0,0xC1,0xE0,0x02,0x80,0xE4,0xF0,0x8B,0xF0,0xED,0x90,0x90,0x90,0x90,
+0x90,0x85,0xC0,0x74,0xBB,0x8B,0xC8,0x41,0x80,0xE1,0xFE,0x0B,0xC6,0x8B,0x5E,0x50,
+0x4B,0x4B,0x2B,0xD9,0x78,0x9C,0xAB,0x8B,0xC1,0x40,0x40,0x01,0x46,0x4E,0xD1,0xE9,
+0xF3,0x6D,0x90,0x89,0x5E,0x50,0x89,0x7E,0x08,0x8B,0x46,0x26,0x80,0xE4,0xEF,0x89,
+0x46,0x26,0xF6,0xC4,0x01,0x75,0x0C,0xF7,0x46,0x48,0x0C,0x00,0x75,0x05,0xB0,0x02,
+0xE8,0x7F,0xC5,0xE9,0x7A,0xFF,0x86,0xC4,0x8B,0xC8,0x83,0xE1,0x3F,0x41,0x80,0xE1,
+0xFE,0xE3,0x0A,0x3C,0x80,0x72,0x09,0x24,0x3F,0xB4,0xF0,0xEB,0xB0,0xE9,0x60,0xFF,
+0x25,0x3F,0x00,0x33,0xFF,0x8E,0xC7,0xBF,0x96,0x12,0x8B,0xF7,0xD1,0xE9,0xF3,0x6D,
+0x90,0x8B,0xC8,0xE8,0x48,0xED,0xE9,0x47,0xFF,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,
+0x12,0x1B,0x9C,0x0E,0xE8,0xD5,0xC7,0x90,0x60,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,
+0xC0,0xBA,0x06,0x01,0xEC,0xA8,0x04,0x74,0xE1,0xB0,0x06,0xEE,0xEC,0xA2,0x8C,0x12,
+0xA8,0x40,0x74,0x11,0xA1,0x88,0x12,0xA3,0x84,0x12,0xC6,0x06,0x8D,0x12,0x00,0xE8,
+0x60,0xFE,0xA0,0x8C,0x12,0xA8,0x80,0x74,0x03,0xE8,0x04,0xFF,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x1B,
+0x9C,0x0E,0xE8,0x87,0xC7,0x90,0x60,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0xBA,
+0x06,0x01,0xEC,0xA8,0x04,0x74,0xE1,0xBA,0x08,0x01,0xEC,0xA2,0x8C,0x12,0xA8,0x40,
+0x74,0x11,0xA1,0x88,0x12,0xA3,0x84,0x12,0xC6,0x06,0x8D,0x12,0x00,0xE8,0x12,0xFE,
+0xA0,0x8C,0x12,0xA8,0x80,0x74,0x03,0xE8,0xB6,0xFE,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x07,0x1F,0x61,0xCF,0x90,0xEE,0x86,0xE0,0xEE,0x86,0xE0,0xEC,0x86,0xE0,0xEC,
+0x86,0xE0,0x80,0xE1,0xFE,0xF3,0x6C,0x90,0x80,0xE1,0xFE,0xF3,0x6E,0x90,0x05,0x00,
+0x75,0x47,0xA8,0x4B,0x05,0x00,0x75,0x48,0xA8,0x4B,0x05,0x00,0xA3,0x48,0xA8,0x4B,
+0x05,0x00,0x35,0x49,0xA8,0x4B,0x06,0x00,0x98,0x48,0x96,0x4B,0x06,0x00,0xBA,0x48,
+0x96,0x4B,0x06,0x00,0xC3,0x48,0x96,0x4B,0x06,0x00,0xCB,0x48,0x96,0x4B,0x06,0x00,
+0x20,0x49,0x96,0x4B,0x06,0x00,0x28,0x49,0x96,0x4B,0x06,0x00,0x4E,0x4A,0x9C,0x4B,
+0x06,0x00,0x7B,0x4A,0x9C,0x4B,0x05,0x00,0x9E,0x4A,0xA2,0x4B,0x05,0x00,0xEC,0x4A,
+0xA2,0x4B,0x00,0x00,0x1E,0x06,0x83,0x3E,0x44,0x12,0x00,0x74,0x09,0xA0,0x06,0x01,
+0x24,0x30,0x3C,0x30,0x74,0x1A,0x8C,0xC8,0x8E,0xD8,0x8E,0xC0,0xBB,0xAE,0x4B,0x8B,
+0x0F,0xE3,0x0D,0x8B,0x7F,0x02,0x8B,0x77,0x04,0xF3,0xA4,0x83,0xC3,0x06,0xEB,0xEF,
+0x07,0x1F,0xC3,0x90,0x33,0xC0,0xA3,0x3E,0x01,0xB9,0x0C,0x01,0xBE,0x40,0x01,0x8B,
+0xFE,0x81,0xC6,0xB4,0x0F,0x89,0x04,0x8B,0xC6,0x2B,0xF1,0x3B,0xC7,0x77,0xF6,0xA3,
+0x3C,0x01,0xC3,0x90,0x1E,0x06,0x60,0x36,0x8B,0x2E,0x3E,0x01,0x8B,0x5E,0x00,0x3B,
+0xEB,0x74,0x2B,0x8B,0x76,0x02,0x89,0x1C,0x89,0x77,0x02,0x36,0xA1,0x3C,0x01,0x89,
+0x46,0x00,0x36,0x89,0x2E,0x3C,0x01,0x8B,0xEB,0xFF,0x4E,0x06,0x74,0x08,0x8B,0x6E,
+0x00,0xFF,0x4E,0x06,0x75,0xF8,0x36,0x89,0x2E,0x3E,0x01,0x8B,0x66,0x04,0x61,0x07,
+0x1F,0xC3,0x1E,0x06,0x60,0x36,0x8B,0x2E,0x3E,0x01,0x98,0x89,0x46,0x06,0x89,0x66,
+0x04,0x3B,0x6E,0x00,0x74,0x10,0x8B,0x6E,0x00,0xFF,0x4E,0x06,0x75,0xF8,0x36,0x89,
+0x2E,0x3E,0x01,0x8B,0x66,0x04,0x61,0x07,0x1F,0xC3,0xC3,0x90,0x1E,0x06,0x60,0x9C,
+0xFA,0x33,0xED,0x8E,0xDD,0x8B,0x2E,0x3C,0x01,0x85,0xED,0x74,0x3D,0x8B,0x4E,0x00,
+0x89,0x0E,0x3C,0x01,0x8B,0xCC,0x8D,0xA6,0x0A,0x01,0x56,0x1E,0x06,0x60,0x89,0x66,
+0x04,0xC7,0x46,0x08,0x0F,0x1A,0xC7,0x46,0x06,0x01,0x00,0x8B,0x1E,0x3E,0x01,0x85,
+0xDB,0x74,0x1D,0x8B,0xC5,0x87,0x07,0x89,0x46,0x00,0x89,0x5E,0x02,0x8B,0xD8,0x89,
+0x6F,0x02,0x8B,0xE1,0x9D,0x61,0x07,0x1F,0xF8,0xC3,0x9D,0x61,0x07,0x1F,0xF9,0xC3,
+0x89,0x2E,0x3E,0x01,0x89,0x6E,0x00,0x89,0x6E,0x02,0x87,0xE1,0x9D,0x8B,0xE1,0xEB,
+0xE4,0x00,0x0D,0x0A,0x54,0x65,0x72,0x6D,0x69,0x6E,0x61,0x6C,0x73,0x20,0x73,0x75,
+0x70,0x70,0x6F,0x72,0x74,0x65,0x64,0x3A,0x0D,0x0A,0x31,0x29,0x20,0x41,0x4E,0x53,
+0x49,0x20,0x63,0x6F,0x6D,0x70,0x61,0x74,0x69,0x62,0x6C,0x65,0x0D,0x0A,0x32,0x29,
+0x20,0x57,0x79,0x73,0x65,0x20,0x33,0x30,0x0D,0x0A,0x50,0x6C,0x65,0x61,0x73,0x65,
+0x20,0x73,0x65,0x6C,0x65,0x63,0x74,0x3A,0x20,0x00,0x0D,0x0A,0x63,0x6F,0x64,0x65,
+0x20,0x73,0x65,0x67,0x6D,0x65,0x6E,0x74,0x3D,0x00,0x0D,0x0A,0x4D,0x6F,0x6E,0x69,
+0x74,0x6F,0x72,0x20,0x76,0x32,0x2E,0x35,0x0A,0x0D,0x0A,0x3E,0x00,0x0D,0x0A,0x50,
+0x61,0x72,0x64,0x6F,0x6E,0x3F,0x00,0x0D,0x0A,0x4E,0x6F,0x20,0x61,0x64,0x64,0x72,
+0x65,0x73,0x73,0x20,0x73,0x70,0x65,0x63,0x69,0x66,0x69,0x65,0x64,0x00,0x0D,0x0A,
+0x3A,0x00,0x0D,0x0A,0x00,0x4C,0x6F,0x63,0x3D,0x00,0x0D,0x0A,0x46,0x41,0x54,0x41,
+0x4C,0x20,0x45,0x52,0x52,0x4F,0x52,0x3D,0x00,0x0D,0x0A,0x4D,0x6F,0x6E,0x69,0x74,
+0x6F,0x72,0x20,0x63,0x6F,0x6D,0x6D,0x61,0x6E,0x64,0x73,0x3A,0x2D,0x0D,0x0A,0x20,
+0x20,0x20,0x44,0x2C,0x64,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,
+0x78,0x5D,0x20,0x2D,0x20,0x64,0x75,0x6D,0x70,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,
+0x0D,0x0A,0x20,0x20,0x20,0x4C,0x2C,0x6C,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,
+0x78,0x78,0x78,0x78,0x5D,0x20,0x2D,0x20,0x64,0x75,0x6D,0x70,0x20,0x73,0x69,0x6E,
+0x67,0x6C,0x65,0x20,0x6C,0x69,0x6E,0x65,0x0D,0x0A,0x20,0x20,0x20,0x45,0x2C,0x65,
+0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x2D,0x20,
+0x65,0x64,0x69,0x74,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,0x0D,0x0A,0x20,0x20,0x20,
+0x46,0x2C,0x66,0x5B,0x5B,0x78,0x78,0x78,0x78,0x20,0x5D,0x78,0x78,0x78,0x78,0x5D,
+0x20,0x2D,0x20,0x66,0x69,0x6C,0x6C,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,0x20,0x70,
+0x61,0x72,0x61,0x67,0x72,0x61,0x70,0x68,0x73,0x0D,0x0A,0x20,0x20,0x20,0x49,0x5B,
+0x78,0x78,0x78,0x78,0x5D,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,
+0x20,0x77,0x6F,0x72,0x64,0x20,0x69,0x6E,0x70,0x75,0x74,0x20,0x66,0x72,0x6F,0x6D,
+0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,0x20,0x69,0x5B,0x78,0x78,0x78,0x78,
+0x5D,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x62,0x79,0x74,
+0x65,0x20,0x69,0x6E,0x70,0x75,0x74,0x20,0x66,0x72,0x6F,0x6D,0x20,0x70,0x6F,0x72,
+0x74,0x0D,0x0A,0x20,0x20,0x20,0x4F,0x78,0x78,0x78,0x78,0x20,0x78,0x78,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x6F,0x75,0x74,0x70,0x75,0x74,0x20,
+0x77,0x6F,0x72,0x64,0x20,0x74,0x6F,0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,
+0x20,0x6F,0x78,0x78,0x78,0x78,0x20,0x78,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x2D,0x20,0x6F,0x75,0x74,0x70,0x75,0x74,0x20,0x62,0x79,0x74,0x65,0x20,
+0x74,0x6F,0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,0x20,0x47,0x5B,0x5B,0x78,
+0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x20,0x20,0x2D,0x20,0x67,
+0x6F,0x74,0x6F,0x20,0x61,0x64,0x64,0x72,0x65,0x73,0x73,0x0D,0x0A,0x20,0x20,0x20,
+0x57,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x20,
+0x20,0x2D,0x20,0x77,0x61,0x74,0x63,0x68,0x20,0x61,0x20,0x77,0x6F,0x72,0x64,0x0D,
+0x0A,0x20,0x20,0x20,0x43,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x69,0x6E,0x74,0x65,0x72,0x72,0x75,0x70,0x74,
+0x73,0x20,0x6F,0x66,0x66,0x0D,0x0A,0x20,0x20,0x20,0x53,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x69,0x6E,0x74,
+0x65,0x72,0x72,0x75,0x70,0x74,0x73,0x20,0x6F,0x6E,0x0D,0x0A,0x20,0x20,0x20,0x73,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x2D,0x20,0x73,0x69,0x6E,0x67,0x6C,0x65,0x20,0x73,0x74,0x65,0x70,0x0D,0x0A,0x20,
+0x20,0x20,0x42,0x78,0x78,0x78,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x2D,0x20,0x62,0x72,0x65,0x61,0x6B,0x70,0x6F,0x69,0x6E,0x74,0x20,
+0x73,0x65,0x74,0x0D,0x0A,0x20,0x20,0x20,0x62,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x62,0x72,0x65,0x61,0x6B,
+0x70,0x6F,0x69,0x6E,0x74,0x20,0x63,0x6C,0x65,0x61,0x72,0x0D,0x0A,0x20,0x20,0x20,
+0x52,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x2D,0x20,0x72,0x65,0x73,0x74,0x61,0x72,0x74,0x20,0x62,0x72,0x65,0x61,0x6B,
+0x70,0x6F,0x69,0x6E,0x74,0x0D,0x0A,0x20,0x20,0x20,0x72,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x72,0x65,0x67,
+0x69,0x73,0x74,0x65,0x72,0x73,0x20,0x61,0x74,0x20,0x62,0x72,0x6B,0x70,0x74,0x0D,
+0x0A,0x20,0x20,0x20,0x58,0x2C,0x78,0x20,0x6E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x65,0x78,0x61,0x6D,0x69,0x6E,0x65,0x20,0x63,
+0x68,0x61,0x6E,0x6E,0x65,0x6C,0x20,0x6E,0x0D,0x0A,0x20,0x20,0x20,0x48,0x2C,0x3F,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,
+0x74,0x68,0x69,0x73,0x20,0x6D,0x65,0x73,0x73,0x61,0x67,0x65,0x00,0x1B,0x5B,0x32,
+0x4A,0x1B,0x5B,0x31,0x3B,0x31,0x48,0x41,0x4E,0x53,0x49,0x20,0x54,0x65,0x72,0x6D,
+0x69,0x6E,0x61,0x6C,0x0D,0x0A,0x0A,0x00,0x1B,0x5B,0x4B,0x00,0x1B,0x5B,0x4A,0x00,
+0x1B,0x5B,0x32,0x4A,0x1B,0x5B,0x31,0x3B,0x31,0x48,0x00,0x1B,0x5B,0x44,0x20,0x1B,
+0x5B,0x44,0x00,0x1B,0x5B,0x31,0x3B,0x37,0x32,0x48,0x00,0x1B,0x5B,0x00,0x3B,0x00,
+0x48,0x00,0x1B,0x5B,0x73,0x00,0x1B,0x5B,0x75,0x00,0x1B,0x7A,0x2B,0x0B,0x7F,0x1B,
+0x7A,0x2E,0x0C,0x7F,0x1B,0x7A,0x2D,0x08,0x7F,0x1B,0x7A,0x2C,0x0A,0x7F,0x1B,0x7A,
+0x22,0x08,0x7F,0x1A,0x57,0x79,0x73,0x65,0x20,0x33,0x30,0x20,0x54,0x65,0x72,0x6D,
+0x69,0x6E,0x61,0x6C,0x0D,0x0A,0x00,0x1B,0x54,0x00,0x1B,0x59,0x00,0x1A,0x00,0x1E,
+0x00,0x08,0x20,0x08,0x00,0x00,0x1B,0x3D,0x00,0x00,0x00,0x1B,0x46,0x00,0x0D,0x00,
+0x3F,0x44,0x64,0x45,0x65,0x46,0x66,0x47,0x67,0x48,0x68,0x49,0x69,0x4F,0x6F,0x43,
+0x63,0x53,0x73,0x42,0x62,0x52,0x72,0x57,0x77,0x58,0x78,0x4C,0x6C,0x3C,0x60,0xD4,
+0x57,0xD4,0x57,0x50,0x58,0x50,0x58,0xD6,0x59,0xD6,0x59,0xB4,0x59,0xB4,0x59,0x3C,
+0x60,0x3C,0x60,0x6C,0x57,0x48,0x57,0x26,0x57,0x06,0x57,0x90,0x57,0x90,0x57,0x98,
+0x57,0x48,0x5F,0x0C,0x5F,0x58,0x5F,0x33,0x5F,0x40,0x5F,0xA0,0x57,0xA0,0x57,0xFE,
+0x59,0xFE,0x59,0xDC,0x57,0xDC,0x57,0x88,0x61,0x98,0x61,0xC0,0x61,0xCC,0x61,0xD8,
+0x61,0xF6,0x61,0x02,0x62,0x22,0x62,0xF8,0x56,0x4A,0x62,0x58,0x62,0x60,0x59,0x20,
+0x20,0x66,0x6C,0x61,0x67,0x73,0x3D,0x00,0x20,0x20,0x61,0x78,0x3D,0x00,0x20,0x20,
+0x62,0x78,0x3D,0x00,0x20,0x20,0x63,0x78,0x3D,0x00,0x20,0x20,0x64,0x78,0x3D,0x00,
+0x20,0x20,0x63,0x73,0x3D,0x00,0x20,0x20,0x64,0x73,0x3D,0x00,0x20,0x20,0x65,0x73,
+0x3D,0x00,0x20,0x20,0x73,0x73,0x3D,0x00,0x20,0x20,0x64,0x69,0x3D,0x00,0x20,0x20,
+0x73,0x69,0x3D,0x00,0x20,0x20,0x62,0x70,0x3D,0x00,0x20,0x20,0x73,0x70,0x3D,0x00,
+0x20,0x20,0x69,0x70,0x3D,0x00,0x20,0x63,0x68,0x61,0x6E,0x65,0x6C,0x3D,0x00,0x20,
+0x20,0x20,0x20,0x73,0x65,0x67,0x3D,0x00,0x20,0x74,0x69,0x5F,0x73,0x74,0x72,0x3D,
+0x00,0x20,0x74,0x69,0x5F,0x74,0x6F,0x73,0x3D,0x00,0x20,0x74,0x69,0x5F,0x6D,0x61,
+0x78,0x3D,0x00,0x20,0x74,0x69,0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x74,0x69,0x5F,
+0x73,0x69,0x7A,0x3D,0x00,0x20,0x74,0x69,0x5F,0x73,0x74,0x66,0x3D,0x00,0x20,0x74,
+0x69,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x74,0x69,0x5F,0x66,0x6C,0x67,0x3D,0x00,
+0x20,0x74,0x69,0x5F,0x74,0x6F,0x74,0x3D,0x00,0x20,0x72,0x69,0x5F,0x70,0x63,0x6E,
+0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,0x74,0x72,0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,
+0x74,0x66,0x3D,0x00,0x20,0x72,0x69,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x72,0x69,
+0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,0x69,0x7A,0x3D,0x00,0x20,
+0x72,0x69,0x5F,0x74,0x6F,0x74,0x3D,0x00,0x20,0x72,0x69,0x5F,0x6D,0x69,0x6E,0x3D,
+0x00,0x20,0x72,0x69,0x5F,0x66,0x6C,0x67,0x3D,0x00,0x20,0x72,0x69,0x5F,0x74,0x6F,
+0x73,0x3D,0x00,0x20,0x72,0x69,0x5F,0x74,0x68,0x72,0x3D,0x00,0x20,0x74,0x68,0x5F,
+0x73,0x74,0x66,0x3D,0x00,0x20,0x74,0x68,0x5F,0x73,0x74,0x72,0x3D,0x00,0x20,0x74,
+0x68,0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x74,0x68,0x5F,0x73,0x69,0x7A,0x3D,0x00,
+0x20,0x74,0x68,0x5F,0x74,0x72,0x67,0x3D,0x00,0x20,0x74,0x68,0x5F,0x66,0x6C,0x67,
+0x3D,0x00,0x20,0x74,0x68,0x5F,0x63,0x6E,0x74,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,
+0x74,0x72,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,0x74,0x66,0x3D,0x00,0x20,0x72,0x68,
+0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,0x69,0x7A,0x3D,0x00,0x20,
+0x72,0x68,0x5F,0x73,0x70,0x61,0x3D,0x00,0x20,0x72,0x68,0x5F,0x61,0x73,0x6F,0x3D,
+0x00,0x20,0x72,0x68,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x72,0x68,0x5F,0x66,0x6C,
+0x67,0x3D,0x00,0x20,0x6D,0x5F,0x63,0x61,0x72,0x65,0x3D,0x00,0x20,0x70,0x74,0x5F,
+0x66,0x6C,0x6F,0x3D,0x00,0x20,0x61,0x73,0x5F,0x66,0x6C,0x6F,0x3D,0x00,0x20,0x72,
+0x6D,0x5F,0x66,0x6C,0x6F,0x3D,0x00,0x20,0x20,0x20,0x71,0x5F,0x69,0x6E,0x3D,0x00,
+0x20,0x20,0x71,0x5F,0x6F,0x75,0x74,0x3D,0x00,0x20,0x71,0x5F,0x64,0x72,0x61,0x6E,
+0x3D,0x00,0x20,0x20,0x71,0x5F,0x74,0x69,0x6D,0x3D,0x00,0x20,0x20,0x20,0x71,0x5F,
+0x66,0x63,0x3D,0x00,0x20,0x71,0x5F,0x73,0x74,0x61,0x74,0x3D,0x00,0x20,0x71,0x5F,
+0x64,0x61,0x74,0x61,0x3D,0x00,0x20,0x71,0x5F,0x6D,0x6F,0x64,0x6D,0x3D,0x00,0x20,
+0x68,0x61,0x6E,0x64,0x5F,0x6F,0x3D,0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,0x62,0x3D,
+0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,0x65,0x3D,0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,
+0x69,0x3D,0x00,0x20,0x20,0x6F,0x70,0x6F,0x73,0x74,0x3D,0x00,0x20,0x20,0x74,0x69,
+0x6D,0x65,0x6F,0x3D,0x00,0x20,0x63,0x75,0x73,0x74,0x6D,0x31,0x3D,0x00,0x20,0x63,
+0x75,0x73,0x74,0x6D,0x32,0x3D,0x00,0x20,0x63,0x75,0x73,0x74,0x6D,0x64,0x3D,0x00,
+0x20,0x74,0x78,0x72,0x61,0x74,0x65,0x3D,0x00,0x20,0x72,0x78,0x72,0x61,0x74,0x65,
+0x3D,0x00,0x20,0x20,0x63,0x5F,0x6D,0x61,0x70,0x3D,0x00,0x20,0x63,0x5F,0x61,0x64,
+0x64,0x72,0x3D,0x00,0x20,0x63,0x5F,0x61,0x69,0x73,0x72,0x3D,0x00,0x20,0x63,0x5F,
+0x78,0x74,0x61,0x67,0x3D,0x00,0x20,0x63,0x5F,0x64,0x65,0x66,0x72,0x3D,0x00,0x20,
+0x63,0x5F,0x66,0x6C,0x73,0x68,0x3D,0x00,0x20,0x74,0x78,0x6D,0x61,0x78,0x73,0x3D,
+0x00,0x20,0x72,0x69,0x5F,0x65,0x6D,0x73,0x3D,0x00,0x20,0x20,0x63,0x5F,0x6C,0x73,
+0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x69,0x65,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,
+0x66,0x63,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x6D,0x63,0x72,0x3D,0x00,0x20,0x20,
+0x63,0x5F,0x6C,0x63,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x64,0x73,0x73,0x3D,0x00,
+0x20,0x63,0x5F,0x64,0x73,0x73,0x69,0x3D,0x00,0x20,0x63,0x5F,0x64,0x73,0x73,0x72,
+0x3D,0x00,0x20,0x20,0x63,0x5F,0x69,0x73,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x63,
+0x61,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x65,0x66,0x72,0x3D,0x00,0x20,0x63,0x5F,
+0x65,0x72,0x73,0x74,0x3D,0x00,0x20,0x63,0x5F,0x65,0x63,0x6E,0x74,0x3D,0x00,0x20,
+0x63,0x5F,0x62,0x72,0x6B,0x63,0x3D,0x00,0x20,0x63,0x5F,0x62,0x6F,0x6B,0x63,0x3D,
+0x00,0x20,0x63,0x5F,0x72,0x65,0x70,0x6C,0x3D,0x00,0x20,0x63,0x5F,0x63,0x63,0x73,
+0x72,0x3D,0x00,0x20,0x63,0x5F,0x73,0x74,0x74,0x31,0x3D,0x00,0x20,0x63,0x5F,0x73,
+0x74,0x74,0x32,0x3D,0x00,0x2B,0xC0,0x8E,0xD8,0x8E,0xC0,0xE8,0xC2,0x00,0xE8,0xE5,
+0x00,0xFA,0xBF,0x84,0x00,0xC7,0x05,0xDC,0x56,0x8C,0x4D,0x02,0xBF,0x0C,0x00,0xC7,
+0x05,0x6E,0x5E,0x8C,0x4D,0x02,0xBF,0x04,0x00,0xC7,0x05,0xBA,0x5E,0x8C,0x4D,0x02,
+0xE8,0xF1,0x00,0x90,0xE8,0x49,0x01,0xE8,0x16,0x00,0xF4,0x90,0xE8,0xE5,0x00,0xBE,
+0xBA,0x4D,0xE8,0x09,0x0C,0xA0,0x93,0x12,0xE8,0x5D,0x0C,0xE8,0xC2,0x09,0xEB,0xE4,
+0xE8,0xD5,0x0C,0xE8,0xC4,0x0C,0x0A,0xC0,0x74,0xF6,0x8B,0x1E,0xF8,0x79,0x3C,0x0D,
+0x74,0x2E,0x3C,0x08,0x74,0x17,0x3C,0x7F,0x74,0x13,0x83,0xFB,0x20,0x7F,0xE1,0x88,
+0x87,0xD6,0x79,0x43,0x89,0x1E,0xF8,0x79,0xE8,0x77,0x0C,0xEB,0xD3,0x0B,0xDB,0x74,
+0xCF,0x4B,0x89,0x1E,0xF8,0x79,0x8B,0x36,0x16,0x7A,0xE8,0xC1,0x0B,0xEB,0xC1,0x90,
+0xE8,0x02,0x00,0xEB,0xBB,0xC6,0x87,0xD6,0x79,0x00,0x0B,0xDB,0x74,0x1E,0xA0,0xD6,
+0x79,0xBF,0x60,0x51,0xB9,0x1D,0x00,0x8B,0xD9,0x06,0x0E,0x07,0xF2,0xAE,0x07,0x75,
+0x17,0x41,0x2B,0xD9,0xD1,0xE3,0x2E,0xFF,0x97,0x7D,0x51,0x90,0x33,0xC0,0xA3,0xF8,
+0x79,0xBE,0x89,0x4D,0xE8,0x87,0x0B,0xC3,0xBE,0x8D,0x4D,0xE8,0x80,0x0B,0xEB,0xEC,
+0xBA,0x00,0x02,0xB0,0x93,0xEE,0xB0,0x55,0xEE,0xBA,0x10,0x02,0xB0,0x93,0xEE,0xB0,
+0xAA,0xEE,0xBA,0x00,0x02,0xEC,0x3C,0x55,0x75,0x08,0xBA,0x10,0x02,0xEC,0x3C,0xAA,
+0x74,0x03,0xE8,0x2F,0xF6,0xC3,0xBA,0x04,0x02,0xB0,0x1A,0xEE,0xB0,0x20,0xEE,0xB0,
+0x30,0xEE,0xB0,0x40,0xEE,0xB0,0x80,0xEE,0xBA,0x00,0x02,0xB0,0x13,0xEE,0xB0,0x07,
+0xEE,0xBA,0x08,0x02,0xB0,0x80,0xEE,0xBA,0x02,0x02,0xB0,0xBB,0xEE,0xBA,0x04,0x02,
+0xB0,0x05,0xEE,0xC3,0xC6,0x06,0xCA,0x13,0x01,0xC7,0x06,0xF8,0x79,0x00,0x00,0xC6,
+0x06,0xF6,0x79,0x01,0xC7,0x06,0xD0,0x79,0x00,0x00,0xC7,0x06,0xD2,0x79,0x00,0x00,
+0xC7,0x06,0xD4,0x79,0x00,0x00,0xC7,0x06,0xFA,0x79,0x00,0x00,0xC7,0x06,0xFC,0x79,
+0x00,0x00,0xC7,0x06,0xFE,0x79,0x00,0x00,0xC7,0x06,0x00,0x7A,0x00,0x00,0xC7,0x06,
+0x02,0x7A,0xCE,0x59,0x8C,0x0E,0x04,0x7A,0xC7,0x06,0x06,0x7A,0x00,0x00,0xC7,0x06,
+0x27,0x7A,0x00,0x00,0xC6,0x06,0x29,0x7A,0x00,0xC6,0x06,0x2A,0x7A,0x00,0xC3,0x90,
+0xBE,0x22,0x4D,0xE8,0xC8,0x0A,0xE8,0x3F,0x00,0x2C,0x31,0x3C,0x01,0x77,0xF7,0xE8,
+0x81,0x09,0x8B,0x36,0x0C,0x7A,0xE8,0xB5,0x0A,0xBE,0x6A,0x4D,0xE8,0xAF,0x0A,0x0E,
+0x58,0xE8,0xF8,0x0A,0xBE,0x7A,0x4D,0xE8,0xA4,0x0A,0xC3,0x90,0x60,0xD1,0xE3,0x83,
+0xFB,0x18,0x73,0x11,0x1E,0xBA,0x00,0x00,0x8E,0xDA,0x2E,0xFF,0x97,0xB7,0x51,0x8B,
+0xEC,0x89,0x46,0x10,0x1F,0x61,0xCF,0x90,0xE8,0x4F,0x0B,0x0A,0xC0,0x75,0x05,0xE8,
+0x56,0x0B,0xEB,0xF4,0xC3,0x90,0x83,0x3E,0xF8,0x79,0x01,0x74,0x16,0xBE,0xD7,0x79,
+0xE8,0x31,0x0A,0x8B,0xD0,0xAC,0x3C,0x2C,0x74,0x04,0x3C,0x20,0x75,0x05,0xE8,0x23,
+0x0A,0xEE,0xC3,0xE9,0xD2,0xFE,0x83,0x3E,0xF8,0x79,0x01,0x74,0xF6,0xBE,0xD7,0x79,
+0xE8,0x11,0x0A,0x8B,0xD0,0xAC,0x3C,0x2C,0x74,0x08,0x3C,0x20,0x74,0x04,0xE9,0xB7,
+0xFE,0x90,0xE8,0xFF,0x09,0xEF,0xC3,0x90,0x8B,0x16,0x06,0x7A,0x83,0x3E,0xF8,0x79,
+0x01,0x74,0x0B,0xBE,0xD7,0x79,0xE8,0xEB,0x09,0x8B,0xD0,0xA3,0x06,0x7A,0xB0,0x20,
+0xE8,0x57,0x0B,0x8B,0x16,0x06,0x7A,0xEC,0xE8,0x6F,0x0B,0xC3,0x8B,0x16,0x06,0x7A,
+0x83,0x3E,0xF8,0x79,0x01,0x74,0x0B,0xBE,0xD7,0x79,0xE8,0xC7,0x09,0x8B,0xD0,0xA3,
+0x06,0x7A,0xB0,0x20,0xE8,0x33,0x0B,0x8B,0x16,0x06,0x7A,0xED,0xE8,0x67,0x0B,0xC3,
+0xFA,0xC6,0x06,0xF6,0x79,0x00,0xC3,0x90,0xC6,0x06,0xF6,0x79,0x01,0xFB,0xC3,0x90,
+0x06,0xE8,0x58,0x09,0xB0,0x20,0xE8,0x11,0x0B,0x26,0x8B,0x05,0xE8,0x47,0x0B,0xB0,
+0x08,0xE8,0x06,0x0B,0xE8,0x03,0x0B,0xE8,0x00,0x0B,0xE8,0xFD,0x0A,0xB8,0x01,0x00,
+0xE8,0xCF,0xF4,0xBA,0x02,0x02,0xEC,0x24,0x01,0x75,0x02,0xEB,0xDC,0xBA,0x06,0x02,
+0xEC,0x07,0xC3,0x90,0xC7,0x06,0x08,0x7A,0x10,0x00,0xEB,0x06,0xC7,0x06,0x08,0x7A,
+0x01,0x00,0x06,0x8E,0x06,0xFC,0x79,0x8B,0x3E,0xFA,0x79,0xE8,0x0E,0x09,0xE8,0x0B,
+0x00,0x89,0x3E,0xFA,0x79,0x8C,0x06,0xFC,0x79,0x07,0xC3,0x90,0xBE,0xB2,0x4D,0xE8,
+0x7C,0x09,0x8B,0x16,0x08,0x7A,0x52,0xE8,0x2A,0x09,0xE8,0x0F,0x0A,0xE8,0x0C,0x0A,
+0x33,0xDB,0xB9,0x10,0x00,0x90,0x26,0x8A,0x01,0xE8,0xBC,0x09,0xE8,0xFD,0x09,0x43,
+0xE2,0xF4,0xE8,0xF7,0x09,0xE8,0xF4,0x09,0x33,0xDB,0xB9,0x10,0x00,0x90,0x26,0x8A,
+0x01,0x3C,0x20,0x72,0x05,0x3C,0x7E,0x76,0x03,0x90,0xB0,0x2E,0xE8,0xE3,0x09,0x43,
+0xE2,0xEC,0xBE,0xB2,0x4D,0xE8,0x36,0x09,0x83,0xC7,0x10,0x5A,0x4A,0x75,0xB7,0xC3,
+0x06,0x8E,0x06,0x00,0x7A,0x8B,0x3E,0xFE,0x79,0xE8,0xA0,0x08,0x89,0x3E,0xFE,0x79,
+0x8C,0x06,0x00,0x7A,0x57,0x8B,0x36,0x0E,0x7A,0xE8,0x12,0x09,0xC7,0x06,0x08,0x7A,
+0x10,0x00,0xBA,0x00,0x02,0xE8,0xE8,0x00,0xE8,0x81,0xFF,0x5F,0xBA,0x00,0x00,0xE8,
+0xDE,0x00,0xBE,0xB5,0x4D,0xE8,0xF6,0x08,0x8C,0xC0,0xE8,0x3F,0x09,0xB0,0x3A,0xE8,
+0x90,0x09,0x8B,0xC7,0xE8,0x35,0x09,0xE8,0x7E,0x08,0xE8,0xC3,0x00,0x90,0xE8,0xB7,
+0x09,0xE8,0xA6,0x09,0x0A,0xC0,0x74,0xF6,0x3C,0x0B,0x75,0x06,0x83,0xEF,0x10,0xEB,
+0x19,0x90,0x3C,0x0A,0x75,0x06,0x83,0xC7,0x10,0xEB,0x0F,0x90,0x3C,0x0C,0x75,0x04,
+0x47,0xEB,0x07,0x90,0x3C,0x08,0x75,0x24,0x4F,0x90,0x8B,0x36,0xFE,0x79,0x8B,0xC7,
+0x2B,0xC6,0x3D,0x00,0x01,0x72,0xA5,0x3D,0x10,0x01,0x72,0x04,0x83,0xEE,0x20,0x90,
+0x83,0xC6,0x10,0x89,0x36,0xFE,0x79,0x57,0x8B,0xFE,0xEB,0x80,0x3C,0x2E,0x75,0x08,
+0xBA,0x01,0x13,0xE8,0x6A,0x00,0x07,0xC3,0xC6,0x06,0x0A,0x7A,0x02,0x32,0xC9,0x90,
+0x3C,0x30,0x72,0x4C,0x3C,0x39,0x76,0x0C,0x24,0x5F,0x3C,0x41,0x72,0x42,0x3C,0x46,
+0x77,0x3E,0x2C,0x07,0x2C,0x30,0x50,0xE8,0xCC,0x08,0x58,0x02,0xC8,0xFE,0x0E,0x0A,
+0x7A,0x74,0x0F,0xC0,0xE1,0x04,0xE8,0x2F,0x09,0xE8,0x1E,0x09,0x0A,0xC0,0x74,0xF6,
+0xEB,0xCE,0x26,0x88,0x0D,0xE8,0xE0,0x07,0x8A,0xD0,0xE8,0x23,0x00,0x8A,0xC1,0x3C,
+0x20,0x72,0x05,0x3C,0x7E,0x76,0x03,0x90,0xB0,0x2E,0xE8,0xD5,0x08,0xE9,0x70,0xFF,
+0xE8,0xC5,0x07,0xE8,0x0A,0x00,0x26,0x8A,0x05,0xE8,0x7C,0x08,0xE9,0x1D,0xFF,0x90,
+0xF6,0x06,0x26,0x7A,0x02,0x75,0x02,0x86,0xF2,0x52,0x8B,0x36,0x1A,0x7A,0xE8,0x0D,
+0x08,0x5A,0x52,0x8A,0xC6,0x02,0x06,0x24,0x7A,0xF6,0x06,0x26,0x7A,0x01,0x75,0x06,
+0xE8,0x9F,0x08,0xEB,0x0D,0x90,0x32,0xE4,0xE8,0x0D,0x08,0x8B,0x36,0x1C,0x7A,0xE8,
+0xEC,0x07,0x5A,0x8A,0xC2,0x02,0x06,0x25,0x7A,0xF6,0x06,0x26,0x7A,0x01,0x75,0x06,
+0xE8,0x7F,0x08,0xEB,0x06,0x90,0x32,0xE4,0xE8,0xED,0x07,0x8B,0x36,0x1E,0x7A,0xE8,
+0xCC,0x07,0xC3,0x90,0x06,0x8E,0x06,0x04,0x7A,0x8B,0x3E,0x02,0x7A,0xE8,0x3C,0x07,
+0x89,0x3E,0x02,0x7A,0x8C,0x06,0x04,0x7A,0x07,0xFF,0x1E,0x02,0x7A,0xC3,0xBE,0x97,
+0x4D,0xE8,0xAA,0x07,0xCB,0x90,0x06,0x57,0xBE,0xD7,0x79,0xE8,0x66,0x07,0x8B,0xD8,
+0xE8,0x61,0x07,0x8B,0xC8,0x2B,0xCB,0x78,0x11,0x8E,0xC3,0xBF,0x00,0x00,0xB8,0xFF,
+0xFF,0x51,0xB9,0x08,0x00,0xF3,0xAB,0x59,0xE2,0xF7,0x5F,0x07,0xC3,0x90,0x06,0xBE,
+0xD7,0x79,0xE8,0x3F,0x07,0x8B,0xD8,0xD1,0xE3,0x2E,0x8B,0x9F,0x44,0x00,0xBE,0x26,
+0x52,0xE8,0xF1,0x08,0x8B,0xC3,0xE8,0xDD,0x08,0xB8,0x01,0x00,0xE8,0x73,0xF2,0xE8,
+0xE0,0x08,0xBE,0x2F,0x52,0xE8,0xDD,0x08,0x8B,0x47,0x18,0xE8,0xC8,0x08,0xBE,0x77,
+0x52,0xE8,0xD1,0x08,0x8B,0x47,0x26,0xE8,0xBC,0x08,0xBE,0x53,0x52,0xE8,0xC5,0x08,
+0x8B,0x47,0x1E,0xE8,0xB0,0x08,0xBE,0x5C,0x52,0xE8,0xB9,0x08,0x8B,0x47,0x20,0xE8,
+0xA4,0x08,0xBE,0x6E,0x52,0xE8,0xAD,0x08,0x8B,0x47,0x24,0xE8,0x98,0x08,0xBE,0x80,
+0x52,0xE8,0xA1,0x08,0x8B,0x47,0x2A,0xE8,0x8C,0x08,0xE8,0x95,0x08,0xBE,0x38,0x52,
+0xE8,0x92,0x08,0x8B,0x07,0xE8,0x7E,0x08,0xBE,0x41,0x52,0xE8,0x87,0x08,0x8B,0x47,
+0x1A,0xE8,0x72,0x08,0xBE,0x4A,0x52,0xE8,0x7B,0x08,0x8B,0x47,0x1C,0xE8,0x66,0x08,
+0xBE,0x65,0x52,0xE8,0x6F,0x08,0x8B,0x47,0x22,0xE8,0x5A,0x08,0xE8,0x63,0x08,0xBE,
+0xD1,0x52,0xE8,0x60,0x08,0x8B,0x47,0x38,0xE8,0x4B,0x08,0xBE,0xAD,0x52,0xE8,0x54,
+0x08,0x8B,0x47,0x30,0xE8,0x3F,0x08,0xBE,0xB6,0x52,0xE8,0x48,0x08,0x8B,0x47,0x32,
+0xE8,0x33,0x08,0xBE,0xA4,0x52,0xE8,0x3C,0x08,0x8B,0x47,0x2E,0xE8,0x27,0x08,0xBE,
+0xBF,0x52,0xE8,0x30,0x08,0x8B,0x47,0x34,0xE8,0x1B,0x08,0xE8,0x24,0x08,0xBE,0x89,
+0x52,0xE8,0x21,0x08,0x8B,0x47,0x04,0xE8,0x0C,0x08,0xBE,0x92,0x52,0xE8,0x15,0x08,
+0x8B,0x47,0x14,0xE8,0x00,0x08,0xBE,0x9B,0x52,0xE8,0x09,0x08,0x8B,0x47,0x2C,0xE8,
+0xF4,0x07,0xBE,0xC8,0x52,0xE8,0xFD,0x07,0x8B,0x47,0x36,0xE8,0xE8,0x07,0xBE,0xDA,
+0x52,0xE8,0xF1,0x07,0x8B,0x47,0x3A,0xE8,0xDC,0x07,0xBE,0xE3,0x52,0xE8,0xE5,0x07,
+0x8B,0x47,0x3C,0xE8,0xD0,0x07,0xE8,0xD9,0x07,0xBE,0x19,0x53,0xE8,0xD6,0x07,0x8B,
+0x47,0x48,0xE8,0xC1,0x07,0xBE,0xFE,0x52,0xE8,0xCA,0x07,0x8B,0x47,0x42,0xE8,0xB5,
+0x07,0xBE,0x07,0x53,0xE8,0xBE,0x07,0x8B,0x47,0x44,0xE8,0xA9,0x07,0xBE,0x7C,0x53,
+0xE8,0xB2,0x07,0x8B,0x47,0x4C,0xE8,0x9D,0x07,0xBE,0x85,0x53,0xE8,0xA6,0x07,0x8B,
+0x47,0x4E,0xE8,0x91,0x07,0xBE,0x8E,0x53,0xE8,0x9A,0x07,0x8B,0x47,0x50,0xE8,0x85,
+0x07,0xE8,0x8E,0x07,0xBE,0x22,0x53,0xE8,0x8B,0x07,0x8B,0x47,0x4A,0xE8,0x76,0x07,
+0xBE,0xEC,0x52,0xE8,0x7F,0x07,0x8B,0x47,0x08,0xE8,0x6A,0x07,0xBE,0xF5,0x52,0xE8,
+0x73,0x07,0x8B,0x47,0x40,0xE8,0x5E,0x07,0xBE,0x10,0x53,0xE8,0x67,0x07,0x8B,0x47,
+0x46,0xE8,0x52,0x07,0xE8,0x5B,0x07,0xBE,0x6A,0x53,0xE8,0x58,0x07,0x8B,0x47,0x7A,
+0xE8,0x43,0x07,0xBE,0x3D,0x53,0xE8,0x4C,0x07,0x8B,0x47,0x70,0xE8,0x37,0x07,0xBE,
+0x46,0x53,0xE8,0x40,0x07,0x8B,0x47,0x72,0xE8,0x2B,0x07,0xBE,0x4F,0x53,0xE8,0x34,
+0x07,0x8B,0x47,0x74,0xE8,0x1F,0x07,0xE8,0x28,0x07,0xBE,0x2B,0x53,0xE8,0x25,0x07,
+0x8B,0x47,0x0C,0xE8,0x10,0x07,0xBE,0x34,0x53,0xE8,0x19,0x07,0x8B,0x47,0x10,0xE8,
+0x04,0x07,0xBE,0x58,0x53,0xE8,0x0D,0x07,0x8B,0x47,0x76,0xE8,0xF8,0x06,0xBE,0x61,
+0x53,0xE8,0x01,0x07,0x8B,0x47,0x78,0xE8,0xEC,0x06,0xBE,0x73,0x53,0xE8,0xF5,0x06,
+0x8B,0x47,0x3E,0xE8,0xE0,0x06,0xE8,0xE9,0x06,0xBE,0x97,0x53,0xE8,0xE6,0x06,0x8B,
+0x47,0x52,0xE8,0xD1,0x06,0xBE,0xA0,0x53,0xE8,0xDA,0x06,0x8B,0x47,0x54,0xE8,0xC5,
+0x06,0xBE,0xA9,0x53,0xE8,0xCE,0x06,0x8B,0x47,0x56,0xE8,0xB9,0x06,0xBE,0xB2,0x53,
+0xE8,0xC2,0x06,0x8B,0x47,0x58,0xE8,0xAD,0x06,0xBE,0xBB,0x53,0xE8,0xB6,0x06,0x8B,
+0x47,0x5A,0xE8,0xA1,0x06,0xBE,0xC4,0x53,0xE8,0xAA,0x06,0x8B,0x47,0x5C,0xE8,0x95,
+0x06,0xE8,0x9E,0x06,0xBE,0xCD,0x53,0xE8,0x9B,0x06,0x8B,0x47,0x5E,0xE8,0x86,0x06,
+0xBE,0xD6,0x53,0xE8,0x8F,0x06,0x8B,0x47,0x60,0xE8,0x7A,0x06,0xBE,0xDF,0x53,0xE8,
+0x83,0x06,0x8B,0x47,0x62,0xE8,0x6E,0x06,0xBE,0xE8,0x53,0xE8,0x77,0x06,0x8B,0x47,
+0x7C,0xE8,0x62,0x06,0xBE,0xF1,0x53,0xE8,0x6B,0x06,0x8B,0x47,0x7E,0xE8,0x56,0x06,
+0xBE,0xFA,0x53,0xE8,0x5F,0x06,0x8B,0x87,0x80,0x00,0xE8,0x49,0x06,0xE8,0x52,0x06,
+0xBE,0x42,0x54,0xE8,0x4F,0x06,0x8B,0x87,0x9E,0x00,0xE8,0x39,0x06,0xBE,0x03,0x54,
+0xE8,0x42,0x06,0x8B,0x47,0x64,0xE8,0x2D,0x06,0xBE,0x0C,0x54,0xE8,0x36,0x06,0x8B,
+0x47,0x6E,0xE8,0x21,0x06,0xBE,0x15,0x54,0xE8,0x2A,0x06,0x8B,0x87,0x8E,0x00,0xE8,
+0x14,0x06,0xBE,0x1E,0x54,0xE8,0x1D,0x06,0x8B,0x87,0x90,0x00,0xE8,0x07,0x06,0xBE,
+0x27,0x54,0xE8,0x10,0x06,0x8B,0x87,0x92,0x00,0xE8,0xFA,0x05,0xE8,0x03,0x06,0xBE,
+0x30,0x54,0xE8,0x00,0x06,0x8B,0x87,0x94,0x00,0xE8,0xEA,0x05,0xBE,0x39,0x54,0xE8,
+0xF3,0x05,0x8B,0x87,0x96,0x00,0xE8,0xDD,0x05,0xBE,0x6F,0x54,0xE8,0xE6,0x05,0x8B,
+0x87,0x98,0x00,0xE8,0xD0,0x05,0xBE,0x5D,0x54,0xE8,0xD9,0x05,0x8A,0x87,0xA0,0x00,
+0xE8,0xA7,0x05,0xBE,0x54,0x54,0xE8,0xCC,0x05,0x8A,0x47,0x28,0xE8,0x9B,0x05,0xBE,
+0x66,0x54,0xE8,0xC0,0x05,0x8A,0x87,0xA1,0x00,0xE8,0x8E,0x05,0xE8,0xB3,0x05,0xBE,
+0x78,0x54,0xE8,0xB0,0x05,0x8A,0x87,0xA2,0x00,0xE8,0x7E,0x05,0xBE,0x81,0x54,0xE8,
+0xA3,0x05,0x8A,0x87,0xA3,0x00,0xE8,0x71,0x05,0xBE,0x8A,0x54,0xE8,0x96,0x05,0x8A,
+0x87,0xA4,0x00,0xE8,0x64,0x05,0xBE,0x93,0x54,0xE8,0x89,0x05,0x8A,0x87,0xA5,0x00,
+0xE8,0x57,0x05,0xBE,0x9C,0x54,0xE8,0x7C,0x05,0x8A,0x87,0xA6,0x00,0xE8,0x4A,0x05,
+0xBE,0xA5,0x54,0xE8,0x6F,0x05,0x8A,0x87,0xA7,0x00,0xE8,0x3D,0x05,0xBE,0xAE,0x54,
+0xE8,0x62,0x05,0x8A,0x87,0xA8,0x00,0xE8,0x30,0x05,0xE8,0x55,0x05,0xBE,0xB7,0x54,
+0xE8,0x52,0x05,0x8A,0x87,0xA9,0x00,0xE8,0x20,0x05,0xBE,0xC0,0x54,0xE8,0x45,0x05,
+0x8A,0x87,0xAA,0x00,0xE8,0x13,0x05,0xBE,0xC9,0x54,0xE8,0x38,0x05,0x8A,0x87,0xAB,
+0x00,0xE8,0x06,0x05,0xBE,0xD2,0x54,0xE8,0x2B,0x05,0x8A,0x87,0xAD,0x00,0xE8,0xF9,
+0x04,0xBE,0xDB,0x54,0xE8,0x1E,0x05,0x8A,0x87,0xAE,0x00,0xE8,0xEC,0x04,0xBE,0xE4,
+0x54,0xE8,0x11,0x05,0x8A,0x87,0xAF,0x00,0xE8,0xDF,0x04,0xBE,0xED,0x54,0xE8,0x04,
+0x05,0x8A,0x87,0xB0,0x00,0xE8,0xD2,0x04,0xE8,0xF7,0x04,0xBE,0xF6,0x54,0xE8,0xF4,
+0x04,0x8A,0x87,0xB1,0x00,0xE8,0xC2,0x04,0xBE,0xFF,0x54,0xE8,0xE7,0x04,0x8A,0x87,
+0xB2,0x00,0xE8,0xB5,0x04,0xBE,0x08,0x55,0xE8,0xDA,0x04,0x8A,0x87,0xB3,0x00,0xE8,
+0xA8,0x04,0xBE,0x11,0x55,0xE8,0xCD,0x04,0x8A,0x87,0xBB,0x00,0xE8,0x9B,0x04,0xE8,
+0xC0,0x04,0xBE,0x1A,0x55,0xE8,0xBD,0x04,0x8A,0x87,0xBC,0x00,0xE8,0x8B,0x04,0xBE,
+0x23,0x55,0xE8,0xB0,0x04,0x8A,0x87,0xBE,0x00,0xE8,0x7E,0x04,0xBE,0x2C,0x55,0xE8,
+0xA3,0x04,0x8A,0x87,0xBF,0x00,0xE8,0x71,0x04,0xE8,0x96,0x04,0x07,0xC3,0x60,0x06,
+0x1E,0x16,0x8B,0xEC,0xFF,0x4E,0x16,0xF7,0x46,0x1A,0x00,0x02,0x74,0x01,0xFB,0xB8,
+0x00,0x00,0x8E,0xD8,0x8E,0xC0,0x89,0x2E,0x2D,0x7A,0xE8,0xCB,0x00,0x81,0x66,0x1A,
+0xFF,0xFE,0xC6,0x06,0x2A,0x7A,0x00,0xE8,0xD8,0x00,0xB8,0x00,0x5F,0xA3,0x2B,0x7A,
+0xE8,0x5D,0x00,0x80,0x3E,0x2A,0x7A,0x00,0x74,0x0A,0x81,0x4E,0x1A,0x00,0x01,0xC6,
+0x06,0x2A,0x7A,0x00,0x17,0x1F,0x07,0x61,0xCF,0x90,0x60,0x06,0x1E,0x16,0x8B,0xEC,
+0xF7,0x46,0x1A,0x00,0x02,0x74,0x01,0xFB,0xB8,0x00,0x00,0x8E,0xD8,0x8E,0xC0,0x89,
+0x2E,0x2D,0x7A,0x81,0x66,0x1A,0xFF,0xFE,0xC6,0x06,0x2A,0x7A,0x00,0xE8,0x92,0x00,
+0xB8,0x00,0x5F,0xA3,0x2B,0x7A,0xE8,0x17,0x00,0x80,0x3E,0x2A,0x7A,0x00,0x74,0x0A,
+0x81,0x4E,0x1A,0x00,0x01,0xC6,0x06,0x2A,0x7A,0x00,0x17,0x1F,0x07,0x61,0xCF,0x90,
+0xB8,0xF0,0x00,0xE8,0x8C,0xED,0xFF,0x26,0x2B,0x7A,0xC3,0x90,0x06,0x53,0x56,0x80,
+0x3E,0x29,0x7A,0x00,0x74,0x03,0xE8,0x3F,0x00,0xBE,0xD7,0x79,0xE8,0x25,0x02,0x8B,
+0xD8,0xA3,0x27,0x7A,0x2E,0x8A,0x07,0xA2,0x29,0x7A,0xB0,0xCC,0x2E,0x88,0x07,0x5E,
+0x5B,0x07,0xC3,0xC6,0x06,0x2A,0x7A,0x00,0xB8,0x0A,0x5F,0xA3,0x2B,0x7A,0xC3,0x90,
+0x8B,0x2E,0x2D,0x7A,0xE8,0x2B,0x00,0xC3,0xC6,0x06,0x2A,0x7A,0x01,0xE8,0x08,0x00,
+0xB8,0x0A,0x5F,0xA3,0x2B,0x7A,0xC3,0x90,0x57,0x80,0x3E,0x29,0x7A,0x00,0x74,0x0F,
+0x8B,0x3E,0x27,0x7A,0xA0,0x29,0x7A,0x2E,0x88,0x05,0xC6,0x06,0x29,0x7A,0x00,0x5F,
+0xC3,0x90,0xBE,0xB2,0x4D,0xE8,0x06,0x02,0xBE,0xD8,0x51,0xE8,0x00,0x02,0xFF,0x76,
+0x14,0x58,0xE8,0x47,0x02,0xBE,0xDE,0x51,0xE8,0xF3,0x01,0xFF,0x76,0x0E,0x58,0xE8,
+0x3A,0x02,0xBE,0xE4,0x51,0xE8,0xE6,0x01,0xFF,0x76,0x12,0x58,0xE8,0x2D,0x02,0xBE,
+0xEA,0x51,0xE8,0xD9,0x01,0xFF,0x76,0x10,0x58,0xE8,0x20,0x02,0xBE,0x14,0x52,0xE8,
+0xCC,0x01,0xFF,0x76,0x0A,0x58,0xE8,0x13,0x02,0xBE,0x1A,0x52,0xE8,0xBF,0x01,0xFF,
+0x76,0x0C,0x58,0xE8,0x06,0x02,0xBE,0xCF,0x51,0xE8,0xB2,0x01,0xFF,0x76,0x1A,0x58,
+0xE8,0xF9,0x01,0xBE,0xB2,0x4D,0xE8,0xA5,0x01,0xBE,0xF0,0x51,0xE8,0x9F,0x01,0xFF,
+0x76,0x18,0x58,0xE8,0xE6,0x01,0xBE,0xF6,0x51,0xE8,0x92,0x01,0xFF,0x76,0x02,0x58,
+0xE8,0xD9,0x01,0xBE,0xFC,0x51,0xE8,0x85,0x01,0xFF,0x76,0x04,0x58,0xE8,0xCC,0x01,
+0xBE,0x02,0x52,0xE8,0x78,0x01,0xFF,0x76,0x00,0x58,0xE8,0xBF,0x01,0xBE,0x08,0x52,
+0xE8,0x6B,0x01,0xFF,0x76,0x06,0x58,0xE8,0xB2,0x01,0xBE,0x0E,0x52,0xE8,0x5E,0x01,
+0xFF,0x76,0x08,0x58,0xE8,0xA5,0x01,0xBE,0x20,0x52,0xE8,0x51,0x01,0xFF,0x76,0x16,
+0x58,0xE8,0x98,0x01,0xBE,0x89,0x4D,0xE8,0x44,0x01,0xC3,0x90,0xBE,0xC9,0x4D,0xE8,
+0x3C,0x01,0xC3,0x3C,0x00,0x74,0x05,0x3C,0x01,0x74,0x59,0xC3,0xC7,0x06,0x0C,0x7A,
+0xCD,0x50,0xC7,0x06,0x0E,0x7A,0xF0,0x50,0xC7,0x06,0x10,0x7A,0xE8,0x50,0xC7,0x06,
+0x12,0x7A,0xEC,0x50,0xC7,0x06,0x14,0x7A,0xF4,0x50,0xC7,0x06,0x16,0x7A,0xFB,0x50,
+0xC7,0x06,0x18,0x7A,0x03,0x51,0xC7,0x06,0x1A,0x7A,0x0B,0x51,0xC7,0x06,0x1C,0x7A,
+0x0E,0x51,0xC7,0x06,0x1E,0x7A,0x10,0x51,0xC7,0x06,0x20,0x7A,0x12,0x51,0xC7,0x06,
+0x22,0x7A,0x16,0x51,0xC6,0x06,0x24,0x7A,0x01,0xC6,0x06,0x25,0x7A,0x01,0xC6,0x06,
+0x26,0x7A,0x03,0xC3,0xC7,0x06,0x0C,0x7A,0x1A,0x51,0xC7,0x06,0x0E,0x7A,0x4D,0x51,
+0xC7,0x06,0x10,0x7A,0x47,0x51,0xC7,0x06,0x12,0x7A,0x4A,0x51,0xC7,0x06,0x14,0x7A,
+0x4F,0x51,0xC7,0x06,0x16,0x7A,0x51,0x51,0xC7,0x06,0x18,0x7A,0x55,0x51,0xC7,0x06,
+0x1A,0x7A,0x56,0x51,0xC7,0x06,0x1C,0x7A,0x59,0x51,0xC7,0x06,0x1E,0x7A,0x5A,0x51,
+0xC7,0x06,0x20,0x7A,0x5B,0x51,0xC7,0x06,0x22,0x7A,0x5E,0x51,0xC6,0x06,0x24,0x7A,
+0x20,0xC6,0x06,0x25,0x7A,0x20,0xC6,0x06,0x26,0x7A,0x02,0xC3,0xA1,0xF8,0x79,0x48,
+0x74,0x14,0xBE,0xD7,0x79,0xE8,0x3C,0x00,0x8B,0xF8,0xAC,0x3C,0x3A,0x75,0x07,0x8E,
+0xC7,0xE8,0x30,0x00,0x8B,0xF8,0xC3,0x90,0x8B,0xC7,0x2B,0x06,0xFE,0x79,0x8A,0xF0,
+0x24,0x0F,0x8A,0xD0,0x02,0xD0,0x02,0xD0,0x80,0xC2,0x0B,0xC0,0xEE,0x04,0x80,0xC6,
+0x03,0x04,0x3D,0xC3,0x8C,0xC0,0xE8,0x93,0x00,0xB0,0x3A,0xE8,0xE4,0x00,0x8B,0xC7,
+0xE8,0x89,0x00,0xC3,0x51,0x33,0xC9,0x90,0xAC,0x3C,0x20,0x74,0xFB,0x90,0x0A,0xC0,
+0x74,0x26,0x2C,0x30,0x72,0x22,0x3C,0x09,0x76,0x14,0x3C,0x11,0x72,0x1A,0x2C,0x07,
+0x3C,0x0F,0x76,0x0A,0x3C,0x2A,0x72,0x10,0x2C,0x20,0x3C,0x0F,0x77,0x0A,0x98,0xC1,
+0xE1,0x04,0x03,0xC8,0xAC,0xEB,0xD7,0x90,0x4E,0x8B,0xC1,0x59,0xC3,0x90,0x06,0x8C,
+0xC8,0x8E,0xC0,0xE8,0x02,0x00,0x07,0xC3,0x26,0x8A,0x04,0x46,0x0A,0xC0,0x74,0x06,
+0xE8,0x8F,0x00,0xEB,0xF3,0x90,0xC3,0x90,0x0B,0xC0,0x74,0x7A,0x51,0x33,0xD2,0xB9,
+0xE8,0x03,0xF7,0xF1,0x8B,0xCA,0xE8,0x03,0x00,0x8B,0xC1,0x59,0xBA,0x64,0x00,0xF6,
+0xF2,0xE8,0x0C,0x00,0x8A,0xC4,0x98,0xB2,0x0A,0xF6,0xF2,0xE8,0x02,0x00,0x8A,0xC4,
+0x50,0x0A,0xF0,0x74,0x05,0x04,0x30,0xE8,0x58,0x00,0x58,0xC3,0x86,0xC4,0xE8,0x07,
+0x00,0x86,0xC4,0xE8,0x02,0x00,0xC3,0x90,0xC1,0xC8,0x04,0xE8,0x08,0x00,0xC1,0xC0,
+0x04,0xE8,0x02,0x00,0xC3,0x90,0x53,0x50,0x24,0x0F,0xBB,0xCA,0x62,0x2E,0xD7,0xE8,
+0x30,0x00,0x58,0x5B,0xC3,0x90,0x86,0xC4,0xE8,0x07,0x00,0x86,0xC4,0xE8,0x02,0x00,
+0xC3,0x90,0x50,0xB9,0x08,0x00,0x8A,0xE0,0x32,0xC0,0xD1,0xC0,0x04,0x30,0xE8,0x11,
+0x00,0xE2,0xF5,0x58,0xC3,0x90,0xB0,0x30,0xE8,0x07,0x00,0xC3,0xB0,0x20,0xE8,0x01,
+0x00,0xC3,0x56,0x8B,0x36,0xD0,0x79,0x88,0x84,0xD0,0x77,0x46,0x81,0xE6,0xFF,0x01,
+0xFF,0x06,0xD4,0x79,0x89,0x36,0xD0,0x79,0x81,0x3E,0xD4,0x79,0xFE,0x01,0x75,0x08,
+0x56,0xE8,0x14,0x00,0x5E,0xEB,0xF1,0x90,0x5E,0xC3,0xBA,0x02,0x02,0xEC,0x24,0x01,
+0x74,0x04,0xBA,0x06,0x02,0xEC,0xC3,0x90,0x80,0x3E,0xF6,0x79,0x00,0x74,0x09,0x60,
+0xB8,0x01,0x00,0xE8,0x2C,0xEA,0x61,0x90,0xBA,0x02,0x02,0xEC,0xA8,0x04,0x74,0x28,
+0x8B,0x36,0xD2,0x79,0x83,0x3E,0xD4,0x79,0x00,0x74,0x1D,0x8A,0x84,0xD0,0x77,0x46,
+0x81,0xE6,0xFF,0x01,0x89,0x36,0xD2,0x79,0xFF,0x0E,0xD4,0x79,0xBA,0x06,0x02,0xEE,
+0xBA,0x02,0x02,0xEC,0xA8,0x04,0x75,0xDC,0xA1,0xD4,0x79,0xC3,0x52,0xBA,0x06,0x02,
+0xEE,0x5A,0xC3,0x90,0x52,0x50,0xBA,0x02,0x02,0xEC,0xA8,0x04,0x74,0x08,0x58,0x5A,
+0xE8,0xE9,0xFF,0xF9,0xC3,0x90,0x58,0x5A,0xF8,0xC3,0x52,0x50,0xBA,0x02,0x02,0xEC,
+0xA8,0x04,0x74,0xFB,0x58,0x5A,0xE8,0xD3,0xFF,0xC3,0x30,0x31,0x32,0x33,0x34,0x35,
+0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,0x46,0x53,0x50,0x8A,0xE0,0x80,0xE4,
+0x0F,0xBB,0xCA,0x62,0xC0,0xE8,0x04,0x2E,0xD7,0xE8,0xCE,0xFF,0x8A,0xC4,0x2E,0xD7,
+0xE8,0xC7,0xFF,0x58,0x5B,0xC3,0x86,0xE0,0xE8,0xDF,0xFF,0x86,0xE0,0xE8,0xDA,0xFF,
+0xC3,0x90,0xBE,0xB2,0x4D,0x50,0x2E,0xAC,0x3C,0x00,0x74,0x05,0xE8,0xAB,0xFF,0xEB,
+0xF5,0x58,0xC3,0x90,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x76,0x04,0xBF,0x04,0x00,
+0xC7,0x46,0xFC,0x00,0x00,0xC7,0x46,0xFA,0x00,0x00,0xC7,0x46,0xF8,0x00,0x00,0x83,
+0x7E,0x06,0x00,0x75,0x0E,0x56,0xE8,0xB6,0x0E,0x59,0x0B,0xC0,0x75,0x05,0x8B,0xC7,
+0xE9,0x5B,0x01,0x8B,0x46,0xFC,0x89,0x46,0xFE,0x0B,0xFF,0x75,0x05,0xB8,0x01,0x00,
+0xEB,0x02,0x33,0xC0,0x50,0x56,0xE8,0xA4,0x0D,0x59,0x59,0xB4,0x00,0x89,0x46,0xFC,
+0x8B,0x5E,0xFC,0x83,0xFB,0x08,0x76,0x03,0xE9,0x2B,0x01,0xD1,0xE3,0x2E,0xFF,0xA7,
+0xB2,0x64,0xB8,0x03,0x00,0xE9,0x26,0x01,0x83,0x7E,0xFA,0x00,0x74,0x14,0xC7,0x46,
+0xFA,0x00,0x00,0x8A,0x44,0x58,0x98,0x50,0x8A,0x44,0x59,0x98,0x50,0xE8,0xC2,0x0F,
+0x59,0x59,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,0xE8,0x9B,
+0x08,0x59,0x83,0x7E,0x06,0x00,0x75,0x05,0x8B,0xC7,0xE9,0xF1,0x00,0x83,0xFF,0x04,
+0x75,0x03,0xE9,0xE6,0x00,0x8B,0xC7,0xE9,0xE4,0x00,0x83,0x7E,0xFE,0x00,0x75,0x03,
+0xBF,0x02,0x00,0xE9,0xD5,0x00,0x83,0x7E,0xFE,0x00,0x75,0x03,0xBF,0x01,0x00,0xE9,
+0xC9,0x00,0x8B,0x5E,0xFE,0x83,0xFB,0x07,0x76,0x03,0xE9,0x86,0x00,0xD1,0xE3,0x2E,
+0xFF,0xA7,0xA2,0x64,0x33,0xFF,0xE9,0x7F,0x00,0xBF,0x04,0x00,0x80,0x7C,0x58,0x0F,
+0x74,0x22,0x83,0x7E,0xF8,0x00,0x75,0x1C,0xFE,0x44,0x58,0x6A,0x08,0x56,0xE8,0x7E,
+0x0C,0x59,0x59,0x8A,0x44,0x58,0x04,0x80,0x50,0x56,0xE8,0x72,0x0C,0x59,0x59,0xC7,
+0x46,0xFA,0x01,0x00,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,
+0xE8,0x19,0x08,0x59,0xEB,0x42,0xBF,0x04,0x00,0x80,0x7C,0x58,0x00,0x74,0x22,0x83,
+0x7E,0xF8,0x00,0x75,0x1C,0xFE,0x4C,0x58,0x6A,0x08,0x56,0xE8,0x41,0x0C,0x59,0x59,
+0x8A,0x44,0x58,0x04,0x80,0x50,0x56,0xE8,0x35,0x0C,0x59,0x59,0xC7,0x46,0xFA,0x01,
+0x00,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,0xE8,0xDC,0x07,
+0x59,0xEB,0x05,0xBF,0x04,0x00,0xEB,0x00,0xEB,0x31,0xBF,0x04,0x00,0xEB,0x2C,0xC7,
+0x46,0xF8,0x01,0x00,0x6A,0x08,0x56,0xE8,0x05,0x0C,0x59,0x59,0x80,0x7C,0x58,0x09,
+0x7D,0x04,0xB0,0x0F,0xEB,0x02,0xB0,0x00,0x04,0x80,0x50,0x56,0xE8,0xF0,0x0B,0x59,
+0x59,0xBF,0x04,0x00,0xEB,0x05,0xBF,0x04,0x00,0xEB,0x00,0xE9,0xA5,0xFE,0x5F,0x5E,
+0xC9,0xC3,0xE4,0x63,0x63,0x64,0x63,0x64,0x63,0x64,0x63,0x64,0xE9,0x63,0x26,0x64,
+0x51,0x64,0x78,0x63,0xBA,0x63,0xC6,0x63,0x96,0x64,0xD2,0x63,0x6A,0x64,0x6A,0x64,
+0x6F,0x64,0x72,0x63,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x76,0x04,0x8B,0x7E,0x08,
+0x6A,0x01,0x56,0xE8,0xA9,0x0B,0x59,0x59,0x8A,0x46,0x06,0xC0,0xE0,0x06,0x04,0x80,
+0x50,0x56,0xE8,0x9A,0x0B,0x59,0x59,0xC7,0x46,0xFE,0x00,0x00,0x89,0x7E,0xF8,0xEB,
+0x03,0xFF,0x46,0xFE,0x8B,0x5E,0xF8,0xFF,0x46,0xF8,0x80,0x3F,0x00,0x75,0xF2,0x83,
+0x7E,0xFE,0x10,0x7D,0x25,0xB8,0x10,0x00,0x2B,0x46,0xFE,0xD1,0xF8,0x89,0x46,0xFC,
+0xC7,0x46,0xFA,0x00,0x00,0xEB,0x0B,0x6A,0x20,0x56,0xE8,0x62,0x0B,0x59,0x59,0xFF,
+0x46,0xFA,0x8B,0x46,0xFA,0x3B,0x46,0xFC,0x7C,0xED,0xEB,0x0C,0x8B,0xDF,0x47,0x8A,
+0x07,0x50,0x56,0xE8,0x49,0x0B,0x59,0x59,0x80,0x3D,0x00,0x75,0xEF,0x6A,0x02,0x56,
+0xE8,0x3C,0x0B,0x59,0x59,0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x04,0x00,0x00,0x56,
+0x57,0x8B,0x7E,0x04,0xC7,0x46,0xFE,0x00,0x00,0xBE,0x14,0x00,0xE9,0x09,0x01,0x8B,
+0x5E,0xFE,0x83,0xC3,0x04,0x2B,0xDF,0x8A,0x87,0xAC,0x0B,0x88,0x44,0x5A,0xC6,0x44,
+0x58,0x08,0x8A,0x46,0xFE,0x88,0x44,0x59,0xC7,0x44,0x06,0x00,0x00,0xC6,0x44,0x19,
+0x00,0xC6,0x44,0x1A,0x00,0xC6,0x44,0x1B,0x00,0xC6,0x44,0x1D,0x0D,0xC6,0x44,0x1E,
+0x03,0xC6,0x44,0x1F,0x00,0xC6,0x44,0x20,0x00,0xC6,0x44,0x21,0x00,0xC6,0x44,0x5B,
+0x00,0xC6,0x44,0x5D,0x00,0xC6,0x44,0x5E,0x00,0xC6,0x44,0x5F,0x00,0xC6,0x44,0x60,
+0x00,0xC7,0x46,0xFC,0x00,0x00,0xEB,0x0D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x40,0x30,
+0x00,0x00,0xFF,0x46,0xFC,0x83,0x7E,0xFC,0x10,0x7C,0xED,0xC7,0x46,0xFC,0x00,0x00,
+0xEB,0x0A,0x8B,0x5E,0xFC,0xC6,0x40,0x50,0x00,0xFF,0x46,0xFC,0x83,0x7E,0xFC,0x04,
+0x7C,0xF0,0xC7,0x44,0x54,0x00,0x00,0xC7,0x44,0x56,0x00,0x00,0x8A,0x44,0x5A,0x98,
+0xBA,0xF8,0x00,0x23,0xD0,0xB8,0x05,0x00,0x0B,0xC2,0x89,0x46,0xFC,0x9C,0xFA,0x8A,
+0x46,0xFC,0xBA,0xFE,0x00,0xEE,0xBA,0x00,0x00,0xEC,0x9D,0x24,0x08,0x88,0x46,0xFC,
+0x83,0x7E,0xFC,0x00,0x75,0x02,0xEB,0x4A,0xFF,0x76,0xFE,0xE8,0x7A,0x0C,0x59,0x68,
+0x35,0x02,0x56,0xE8,0x32,0x0A,0x59,0x59,0x0B,0xC0,0x75,0x34,0x68,0x38,0x02,0x56,
+0xE8,0x25,0x0A,0x59,0x59,0x0B,0xC0,0x75,0x27,0x68,0x42,0x02,0x56,0xE8,0x18,0x0A,
+0x59,0x59,0x0B,0xC0,0x75,0x1A,0x68,0x4C,0x02,0x56,0xE8,0x0B,0x0A,0x59,0x59,0x0B,
+0xC0,0x75,0x0D,0x68,0x56,0x02,0x56,0xE8,0xFE,0x09,0x59,0x59,0x0B,0xC0,0x74,0x02,
+0xEB,0x00,0xFF,0x46,0xFE,0x83,0xC6,0x62,0x39,0x7E,0xFE,0x7D,0x03,0xE9,0xEF,0xFE,
+0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x46,0x04,0xBA,
+0x62,0x00,0xF7,0xEA,0x05,0x14,0x00,0x8B,0xF0,0x83,0x7E,0x06,0x00,0x74,0x05,0xB8,
+0x10,0x00,0xEB,0x03,0xB8,0x08,0x00,0x89,0x44,0x04,0x8A,0x46,0x08,0x88,0x44,0x5C,
+0x56,0xE8,0x59,0x04,0x59,0x8B,0xF8,0x8B,0xC7,0x89,0x44,0x56,0x89,0x44,0x54,0x8A,
+0x44,0x5D,0x88,0x44,0x2F,0x0B,0xFF,0x75,0x1D,0x68,0xC2,0x0F,0x6A,0x01,0x56,0xE8,
+0x02,0xFE,0x83,0xC4,0x06,0xEB,0x00,0x6A,0x01,0x56,0xE8,0x47,0xFC,0x59,0x59,0x0B,
+0xC0,0x75,0xF4,0xBF,0x01,0x00,0x89,0x7E,0xFA,0xB9,0x05,0x00,0xBB,0xE9,0x6A,0x2E,
+0x8B,0x07,0x3B,0x46,0xFA,0x74,0x07,0x43,0x43,0xE2,0xF4,0xE9,0xA4,0x03,0x2E,0xFF,
+0x67,0x0A,0xC7,0x44,0x06,0x02,0x00,0xC7,0x44,0x08,0xF4,0x08,0x8B,0x5E,0x04,0xD1,
+0xE3,0x8B,0x87,0xFC,0x08,0x89,0x44,0x0A,0x33,0xC0,0x8B,0xF8,0x89,0x44,0x54,0xE9,
+0x80,0x03,0x56,0xE8,0xBB,0x05,0x59,0xBF,0x01,0x00,0x8A,0x44,0x5D,0x88,0x44,0x60,
+0xE9,0x6F,0x03,0x83,0x7C,0x04,0x08,0x75,0x30,0x80,0x7C,0x5C,0x01,0x75,0x15,0x8A,
+0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xE4,0x08,0x56,0xE8,0xF7,0x08,
+0x59,0x59,0xEB,0x13,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xC4,
+0x08,0x56,0xE8,0xE2,0x08,0x59,0x59,0xEB,0x2E,0x80,0x7C,0x5C,0x01,0x75,0x15,0x8A,
+0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xD4,0x08,0x56,0xE8,0xC7,0x08,
+0x59,0x59,0xEB,0x13,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xB4,
+0x08,0x56,0xE8,0xB2,0x08,0x59,0x59,0x6A,0x01,0x56,0xE8,0x87,0xFB,0x59,0x59,0x8B,
+0xD8,0x83,0xFB,0x03,0x77,0x2A,0xD1,0xE3,0x2E,0xFF,0xA7,0xE1,0x6A,0xBF,0x01,0x00,
+0x8A,0x44,0x5D,0x88,0x44,0x5E,0xEB,0x18,0x8A,0x44,0x5D,0x04,0xFF,0x24,0x07,0x88,
+0x44,0x5D,0xEB,0x0C,0x8A,0x44,0x5D,0xFE,0xC0,0x24,0x07,0x88,0x44,0x5D,0xEB,0x00,
+0xE9,0xCF,0x02,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xFD,0x02,
+0x56,0xE8,0x63,0x08,0x59,0x59,0x68,0x1D,0x03,0x56,0xE8,0x5A,0x08,0x59,0x59,0x6A,
+0x01,0x56,0xE8,0x2F,0xFB,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x03,0x77,0x36,0xD1,0xE3,
+0x2E,0xFF,0xA7,0xD9,0x6A,0xBF,0x01,0x00,0x8A,0x44,0x5D,0x88,0x44,0x5F,0xEB,0x24,
+0x8A,0x44,0x5D,0x04,0xFF,0x8A,0x54,0x04,0x80,0xC2,0xFF,0x22,0xC2,0x88,0x44,0x5D,
+0xEB,0x12,0x8A,0x44,0x5D,0xFE,0xC0,0x8A,0x54,0x04,0x80,0xC2,0xFF,0x22,0xC2,0x88,
+0x44,0x5D,0xEB,0x00,0xE9,0x6B,0x02,0x8B,0x5C,0x06,0x83,0xC3,0xFE,0xD1,0xE3,0x8B,
+0x40,0x08,0x89,0x04,0x8B,0x1C,0xFF,0x77,0x06,0x6A,0x00,0x56,0xE8,0x85,0xFC,0x83,
+0xC4,0x06,0x8B,0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x40,0x08,0x89,0x44,0x02,0x8B,0x5C,
+0x02,0xFF,0x77,0x06,0x6A,0x01,0x56,0xE8,0x6A,0xFC,0x83,0xC4,0x06,0x6A,0x01,0x56,
+0xE8,0xB1,0xFA,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x03,0x76,0x03,0xE9,0x1F,0x02,0xD1,
+0xE3,0x2E,0xFF,0xA7,0xD1,0x6A,0x8B,0x5C,0x02,0x8B,0x47,0x04,0x89,0x44,0x02,0x8B,
+0x5C,0x02,0x80,0x3F,0x44,0x75,0x0D,0x8B,0x5C,0x02,0x8A,0x47,0x01,0xB4,0x00,0x3B,
+0x44,0x04,0x7D,0xE2,0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x1C,0x03,0xD8,0x8B,0x44,0x02,
+0x89,0x47,0x08,0x8B,0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x44,0x02,0x89,0x40,0x08,0xE9,
+0xDE,0x01,0x8B,0x5C,0x02,0x8B,0x47,0x02,0x89,0x44,0x02,0x8B,0x5C,0x02,0x80,0x3F,
+0x44,0x75,0x0D,0x8B,0x5C,0x02,0x8A,0x47,0x01,0xB4,0x00,0x3B,0x44,0x04,0x7D,0xE2,
+0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x1C,0x03,0xD8,0x8B,0x44,0x02,0x89,0x47,0x08,0x8B,
+0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x44,0x02,0x89,0x40,0x08,0xE9,0xA2,0x01,0xBF,0x01,
+0x00,0xE9,0x9C,0x01,0x8B,0x5C,0x02,0x8A,0x07,0xB4,0x00,0x89,0x46,0xF8,0xB9,0x0C,
+0x00,0xBB,0xA1,0x6A,0x2E,0x8B,0x07,0x3B,0x46,0xF8,0x74,0x07,0x43,0x43,0xE2,0xF4,
+0xE9,0x77,0x01,0x2E,0xFF,0x67,0x18,0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x5C,0x02,0x03,
+0xD8,0x8B,0x47,0x08,0x8B,0x5C,0x06,0xFF,0x44,0x06,0xD1,0xE3,0x89,0x40,0x08,0x8B,
+0x1C,0x80,0x7F,0x01,0x00,0x74,0x12,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,
+0x57,0x01,0xB6,0x00,0x8B,0xDA,0x88,0x40,0x18,0xE9,0x40,0x01,0xFF,0x4C,0x06,0xE9,
+0x3A,0x01,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,0x57,0x01,0xB6,0x00,0x8B,
+0xDA,0x88,0x40,0x18,0xE9,0x25,0x01,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,
+0x57,0x01,0xB6,0x00,0x8B,0xDA,0x88,0x40,0x18,0xFF,0x4C,0x06,0xE9,0x0D,0x01,0x8B,
+0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,0x57,0x01,0xB6,0x00,0x8B,0xDA,0x30,0x40,
+0x18,0xE9,0xF8,0x00,0xB8,0xF0,0x10,0x8B,0xF8,0x89,0x44,0x54,0x8A,0x44,0x5F,0x88,
+0x44,0x5D,0xE9,0xE7,0x00,0x8A,0x44,0x1C,0x98,0x3D,0x02,0x00,0x74,0x07,0x3D,0x03,
+0x00,0x74,0x02,0xEB,0x07,0xC7,0x46,0xFE,0x00,0x00,0xEB,0x2B,0x8A,0x44,0x1C,0x98,
+0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0x69,0x02,0x56,0xE8,0x6B,0x06,0x59,0x59,0x6A,0x01,
+0x56,0xE8,0x40,0xF9,0x59,0x59,0x89,0x46,0xFE,0x83,0x7E,0xFE,0x00,0x74,0x06,0x83,
+0x7E,0xFE,0x03,0x75,0xE9,0xEB,0x00,0x83,0x7E,0xFE,0x03,0x74,0x62,0x8A,0x44,0x1C,
+0x98,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0x6D,0x02,0x56,0xE8,0x3A,0x06,0x59,0x59,0x56,
+0xE8,0x4D,0x97,0x59,0x89,0x46,0xFC,0x8B,0x5E,0xFC,0x83,0xEB,0xFE,0x83,0xFB,0x03,
+0x77,0x33,0xD1,0xE3,0x2E,0xFF,0xA7,0x99,0x6A,0x68,0xAC,0x02,0x56,0xE8,0x17,0x06,
+0x59,0x59,0xEB,0x23,0x68,0x8F,0x02,0x56,0xE8,0x0C,0x06,0x59,0x59,0xEB,0x18,0x68,
+0x75,0x02,0x56,0xE8,0x01,0x06,0x59,0x59,0xEB,0x0D,0x68,0xC6,0x02,0x56,0xE8,0xF6,
+0x05,0x59,0x59,0xEB,0x02,0xEB,0x00,0x6A,0x01,0x56,0xE8,0xC7,0xF8,0x59,0x59,0xBF,
+0x01,0x00,0xEB,0x38,0x68,0xDD,0x02,0x56,0xE8,0xDC,0x05,0x59,0x59,0x6A,0x01,0x56,
+0xE8,0xB1,0xF8,0x59,0x59,0xBF,0x01,0x00,0xEB,0x22,0xB8,0xD0,0x30,0x8B,0xF8,0x89,
+0x44,0x54,0x8A,0x44,0x60,0x88,0x44,0x5D,0xEB,0x12,0xB8,0xE0,0x20,0x8B,0xF8,0x89,
+0x44,0x54,0x8A,0x44,0x5E,0x88,0x44,0x5D,0xEB,0x02,0xEB,0x00,0xEB,0x02,0xEB,0x00,
+0xEB,0x00,0xE9,0x41,0xFC,0x5F,0x5E,0xC9,0xC3,0x19,0x6A,0x24,0x6A,0x2F,0x6A,0x3A,
+0x6A,0x00,0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x41,0x00,0x42,0x00,0x43,0x00,0x44,
+0x00,0x80,0x00,0x81,0x00,0x82,0x00,0xFF,0x00,0x17,0x69,0x54,0x6A,0x7A,0x6A,0xA5,
+0x69,0x52,0x69,0x94,0x69,0x6A,0x6A,0x67,0x69,0x52,0x69,0x7F,0x69,0x67,0x69,0x4C,
+0x69,0xF4,0x68,0x76,0x68,0xB2,0x68,0xEE,0x68,0xF5,0x67,0x00,0x68,0x12,0x68,0xF5,
+0x67,0x9D,0x67,0xA8,0x67,0xB4,0x67,0x9D,0x67,0x00,0x00,0x01,0x00,0xF0,0x10,0xE0,
+0x20,0xD0,0x30,0x27,0x68,0xF2,0x66,0xC3,0x67,0x23,0x67,0x12,0x67,0xC8,0x04,0x00,
+0x00,0x56,0x57,0x8B,0x76,0x04,0x8A,0x44,0x59,0x98,0x89,0x46,0xFC,0x6A,0x09,0x8B,
+0x46,0xFC,0x05,0x84,0x01,0x50,0xE8,0x93,0x08,0x59,0x59,0x8B,0xF8,0x8B,0xC7,0x25,
+0x00,0xF0,0x3D,0x00,0x10,0x75,0x55,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xF0,0x00,0x75,
+0x4B,0x8B,0xC7,0x25,0x00,0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x8B,0x44,0x04,0x3B,
+0x46,0xFE,0x7D,0x05,0x33,0xC0,0xE9,0xEF,0x00,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,
+0x00,0x2B,0xD0,0x3B,0x56,0xFE,0x74,0x05,0x33,0xC0,0xE9,0xDB,0x00,0xC7,0x44,0x02,
+0x04,0x09,0x8A,0x46,0xFE,0x88,0x44,0x5F,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,
+0xC7,0x87,0xFC,0x08,0x04,0x09,0xB8,0xF0,0x10,0xE9,0xBC,0x00,0x8B,0xC7,0x25,0x00,
+0xF0,0x3D,0x00,0x20,0x75,0x52,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xE0,0x00,0x75,0x48,
+0x8B,0xC7,0x25,0x00,0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x83,0x7E,0xFE,0x08,0x7E,
+0x05,0x33,0xC0,0xE9,0x92,0x00,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,0x00,0x2B,0xD0,
+0x3B,0x56,0xFE,0x74,0x05,0x33,0xC0,0xEB,0x7F,0x90,0xC7,0x44,0x02,0x0C,0x09,0x8A,
+0x46,0xFE,0x88,0x44,0x5E,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x87,0xFC,
+0x08,0x0C,0x09,0xB8,0xE0,0x20,0xEB,0x60,0x8B,0xC7,0x25,0x00,0xF0,0x3D,0x00,0x30,
+0x75,0x52,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xD0,0x00,0x75,0x48,0x8B,0xC7,0x25,0x00,
+0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x8B,0x44,0x04,0x3B,0x46,0xFE,0x7D,0x04,0x33,
+0xC0,0xEB,0x35,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,0x00,0x2B,0xD0,0x3B,0x56,0xFE,
+0x74,0x04,0x33,0xC0,0xEB,0x22,0xC7,0x44,0x02,0x14,0x09,0x8A,0x46,0xFE,0x88,0x44,
+0x60,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x87,0xFC,0x08,0x14,0x09,0xB8,
+0xD0,0x30,0xEB,0x04,0x33,0xC0,0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x06,0x00,0x00,
+0x56,0x8B,0x76,0x04,0x6A,0x08,0x56,0xE8,0x35,0x04,0x59,0x59,0x8A,0x44,0x58,0x04,
+0x80,0x50,0x56,0xE8,0x29,0x04,0x59,0x59,0x8B,0x44,0x54,0x3B,0x44,0x56,0x75,0x0A,
+0x8A,0x44,0x5D,0x3A,0x44,0x2F,0x75,0x02,0xEB,0x64,0x8B,0x44,0x54,0x89,0x44,0x56,
+0x8B,0x5C,0x02,0x8A,0x47,0x01,0x88,0x44,0x2F,0x8A,0x44,0x5D,0xB4,0x00,0xC1,0xE0,
+0x08,0x8B,0x54,0x54,0x0B,0xD0,0x8A,0x44,0x5D,0xB4,0x00,0xBB,0x0F,0x00,0x2B,0xD8,
+0x0B,0xD3,0x89,0x56,0xFE,0x6A,0x10,0x8A,0x44,0x59,0x98,0x05,0x04,0x00,0x99,0x05,
+0x40,0x01,0x83,0xD2,0x00,0x52,0x50,0xE8,0x54,0x08,0x83,0xC4,0x06,0x89,0x56,0xFC,
+0x89,0x46,0xFA,0x8B,0x46,0xFE,0x09,0x46,0xFA,0x83,0x4E,0xFC,0x00,0x6A,0x19,0xFF,
+0x76,0xFC,0xFF,0x76,0xFA,0xE8,0x73,0x07,0x83,0xC4,0x06,0xE8,0xFE,0x07,0x5E,0xC9,
+0xC3,0xC8,0x1C,0x00,0x00,0x56,0x57,0x8B,0x5E,0x04,0x8A,0x47,0x59,0x98,0x8B,0xF0,
+0x8B,0x5E,0x04,0x8A,0x47,0x5D,0xB4,0x00,0x89,0x46,0xE6,0x83,0x7E,0xE6,0x00,0x7D,
+0x0A,0x8B,0x5E,0x04,0x8B,0x47,0x04,0x48,0x89,0x46,0xE6,0x8B,0x5E,0x04,0x8B,0x47,
+0x04,0x3B,0x46,0xE6,0x7F,0x05,0xC7,0x46,0xE6,0x00,0x00,0x8B,0x5E,0x04,0x8A,0x46,
+0xE6,0x88,0x47,0x5D,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x02,0x20,
+0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x03,0x30,0x8B,0xDE,0xD1,0xE3,
+0x8B,0x9F,0x61,0x02,0xC6,0x47,0x02,0x20,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,
+0xC6,0x47,0x03,0x30,0x8B,0x46,0xE6,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,
+0x8B,0x46,0xFA,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x59,0x02,0x88,0x57,0x03,0xBB,0x0A,0x00,0x8B,0x46,0xFA,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,0x46,0xFA,0xBB,0x0A,
+0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,
+0x88,0x57,0x02,0x8B,0x46,0xE6,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,
+0x46,0xFA,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,
+0x8B,0x9F,0x61,0x02,0x88,0x57,0x03,0xBB,0x0A,0x00,0x8B,0x46,0xFA,0x33,0xD2,0xF7,
+0xF3,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,0x46,0xFA,0xBB,0x0A,0x00,
+0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0x88,
+0x57,0x02,0x8B,0x5E,0xE6,0xD1,0xE3,0xFF,0xB7,0x12,0x02,0x6A,0x00,0xFF,0x76,0x04,
+0xE8,0xD1,0xF6,0x83,0xC4,0x06,0x68,0xD3,0x0F,0x6A,0x01,0xFF,0x76,0x04,0xE8,0xC3,
+0xF6,0x83,0xC4,0x06,0xFF,0x76,0xE6,0x56,0xE8,0x01,0x93,0x59,0x59,0x89,0x56,0xF2,
+0x89,0x46,0xF0,0xFF,0x76,0xE6,0x56,0xE8,0x14,0x93,0x59,0x59,0x89,0x56,0xEE,0x89,
+0x46,0xEC,0x9C,0xFA,0xC4,0x5E,0xF0,0x26,0x8B,0x07,0x89,0x46,0xEA,0xC4,0x5E,0xEC,
+0x26,0x8B,0x07,0x89,0x46,0xE8,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFE,0x9D,0xC7,0x46,
+0xE4,0x01,0x00,0xE8,0xEE,0xA0,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFC,0x8B,0x46,0xFC,
+0x2B,0x46,0xFE,0x3D,0xE8,0x03,0x73,0x03,0xE9,0x80,0x01,0x9C,0xFA,0xBA,0x50,0xFF,
+0xED,0x89,0x46,0xFC,0x8B,0x46,0xFC,0x2B,0x46,0xFE,0x89,0x46,0xF8,0xC4,0x5E,0xF0,
+0x26,0x8B,0x07,0x2B,0x46,0xEA,0x89,0x46,0xF6,0xC4,0x5E,0xF0,0x26,0x8B,0x07,0x89,
+0x46,0xEA,0xC4,0x5E,0xEC,0x26,0x8B,0x07,0x2B,0x46,0xE8,0x89,0x46,0xF4,0xC4,0x5E,
+0xEC,0x26,0x8B,0x07,0x89,0x46,0xE8,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFE,0x9D,0x81,
+0x7E,0xF8,0xE8,0x03,0x76,0x1C,0xFF,0x76,0xF8,0xFF,0x76,0xF6,0xE8,0x76,0x01,0x59,
+0x59,0x89,0x46,0xF6,0xFF,0x76,0xF8,0xFF,0x76,0xF4,0xE8,0x68,0x01,0x59,0x59,0x89,
+0x46,0xF4,0xBF,0x0E,0x00,0xEB,0x17,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,
+0x01,0x20,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0xC6,0x01,0x20,0x47,0x83,0xFF,
+0x11,0x76,0xE4,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x0D,0x30,0x8B,
+0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0xC6,0x47,0x0D,0x30,0x83,0x7E,0xF6,0x09,0x77,
+0x05,0xB8,0x0D,0x00,0xEB,0x26,0x83,0x7E,0xF6,0x63,0x77,0x05,0xB8,0x0E,0x00,0xEB,
+0x1B,0x81,0x7E,0xF6,0xE7,0x03,0x77,0x05,0xB8,0x0F,0x00,0xEB,0x0F,0x81,0x7E,0xF6,
+0x0F,0x27,0x77,0x05,0xB8,0x10,0x00,0xEB,0x03,0xB8,0x11,0x00,0x8B,0xF8,0xEB,0x25,
+0x8B,0x46,0xF6,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x59,0x02,0x88,0x11,0x4F,0xBB,0x0A,0x00,0x8B,0x46,0xF6,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xF6,0x83,0x7E,0xF6,0x00,0x75,0xD5,0x83,0x7E,0xF4,0x09,0x77,
+0x05,0xB8,0x0D,0x00,0xEB,0x26,0x83,0x7E,0xF4,0x63,0x77,0x05,0xB8,0x0E,0x00,0xEB,
+0x1B,0x81,0x7E,0xF4,0xE7,0x03,0x77,0x05,0xB8,0x0F,0x00,0xEB,0x0F,0x81,0x7E,0xF4,
+0x0F,0x27,0x77,0x05,0xB8,0x10,0x00,0xEB,0x03,0xB8,0x11,0x00,0x8B,0xF8,0xEB,0x25,
+0x8B,0x46,0xF4,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x61,0x02,0x88,0x11,0x4F,0xBB,0x0A,0x00,0x8B,0x46,0xF4,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xF4,0x83,0x7E,0xF4,0x00,0x75,0xD5,0x8B,0xDE,0xD1,0xE3,0xFF,
+0xB7,0x59,0x02,0xFF,0x76,0x04,0xE8,0x6E,0x00,0x59,0x59,0x8B,0xDE,0xD1,0xE3,0xFF,
+0xB7,0x61,0x02,0xFF,0x76,0x04,0xE8,0x5E,0x00,0x59,0x59,0x6A,0x00,0xFF,0x76,0x04,
+0xE8,0x31,0xF3,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x04,0x77,0x1F,0xD1,0xE3,0x2E,0xFF,
+0xA7,0x1B,0x70,0xEB,0x22,0xC7,0x46,0xE4,0x00,0x00,0xFF,0x4E,0xE6,0xEB,0x0C,0xC7,
+0x46,0xE4,0x00,0x00,0xFF,0x46,0xE6,0xEB,0x02,0xEB,0x00,0x83,0x7E,0xE4,0x00,0x74,
+0x03,0xE9,0x2A,0xFE,0xE9,0xD4,0xFC,0x5F,0x5E,0xC9,0xC3,0xF3,0x6F,0xF5,0x6F,0xFF,
+0x6F,0xF3,0x6F,0x09,0x70,0x55,0x8B,0xEC,0x8B,0x46,0x04,0xB9,0xE8,0x03,0xF7,0xE1,
+0x8B,0x4E,0x06,0xF7,0xF1,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x8B,0x76,0x06,0xEB,0x0E,
+0x8B,0xDE,0x46,0x8A,0x07,0x50,0xFF,0x76,0x04,0xE8,0x33,0x00,0x59,0x59,0x80,0x3C,
+0x00,0x75,0xED,0xEB,0x00,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x8B,0x76,0x06,0xEB,
+0x14,0x8B,0xDE,0x46,0x8A,0x07,0x50,0xFF,0x76,0x04,0xE8,0x45,0x00,0x59,0x59,0x0B,
+0xC0,0x74,0x02,0xEB,0x07,0x80,0x3C,0x00,0x75,0xE7,0xEB,0x00,0x5E,0x5D,0xC3,0xC8,
+0x02,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,
+0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,0xBA,0x02,0x00,0xEC,0xA8,0x02,0x74,0x06,0x9D,
+0xE8,0x91,0x9E,0xEB,0xE9,0xBA,0x00,0x00,0x8A,0x46,0x06,0xEE,0x9D,0xEB,0x00,0x5E,
+0xC9,0xC3,0xC8,0x04,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,
+0xFE,0xE8,0xE6,0xA1,0x89,0x46,0xFC,0xE8,0xE0,0xA1,0x2B,0x46,0xFC,0x3D,0xB8,0x0B,
+0x76,0x05,0xB8,0x01,0x00,0xEB,0x23,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,
+0xBA,0x02,0x00,0xEC,0xA8,0x02,0x74,0x06,0x9D,0xE8,0x48,0x9E,0xEB,0xD9,0xBA,0x00,
+0x00,0x8A,0x46,0x06,0xEE,0x9D,0x33,0xC0,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,0x04,0x00,
+0x00,0x56,0x57,0x8B,0x76,0x04,0x83,0x7E,0x06,0x00,0x74,0x07,0x56,0xE8,0x03,0x01,
+0x59,0xEB,0x05,0x56,0xE8,0xA2,0x00,0x59,0x88,0x46,0xFF,0x80,0x7E,0xFF,0x08,0x77,
+0x06,0x8A,0x46,0xFF,0xE9,0x84,0x00,0x80,0x7E,0xFF,0x0F,0x76,0x03,0xEB,0x79,0x90,
+0x8A,0x46,0xFF,0xB4,0x00,0x2D,0x0A,0x00,0x8B,0xD8,0x83,0xFB,0x04,0x77,0x67,0xD1,
+0xE3,0x2E,0xFF,0xA7,0xAF,0x71,0xB0,0x00,0xEB,0x61,0x56,0xE8,0x6B,0x00,0x59,0xB4,
+0x00,0x25,0x0F,0x00,0x89,0x46,0xFC,0x56,0xE8,0x5E,0x00,0x59,0xB4,0x00,0x8B,0xF8,
+0x56,0xE8,0x55,0x00,0x59,0xB4,0x00,0xC1,0xE0,0x08,0x8B,0xD7,0x03,0xD0,0x8B,0xFA,
+0x8B,0x5E,0xFC,0xD1,0xE3,0x89,0x78,0x30,0xEB,0x2E,0x56,0xE8,0x3B,0x00,0x59,0x88,
+0x44,0x5B,0xEB,0x24,0x56,0xE8,0x31,0x00,0x59,0x88,0x44,0x50,0x56,0xE8,0x29,0x00,
+0x59,0x88,0x44,0x51,0x56,0xE8,0x21,0x00,0x59,0x88,0x44,0x52,0x56,0xE8,0x19,0x00,
+0x59,0x88,0x44,0x53,0xEB,0x02,0xEB,0x00,0xE9,0x5B,0xFF,0x5F,0x5E,0xC9,0xC3,0x46,
+0x71,0xA6,0x71,0x4A,0x71,0x7A,0x71,0x84,0x71,0xC8,0x04,0x00,0x00,0x56,0x8B,0x76,
+0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,
+0xEE,0xBA,0x02,0x00,0xEC,0xA8,0x01,0x75,0x06,0x9D,0xE8,0x57,0x9D,0xEB,0xE9,0xBA,
+0x00,0x00,0xEC,0x88,0x46,0xFD,0x9D,0x8A,0x46,0xFD,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,
+0x02,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,
+0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,0xBA,0x02,0x00,0xEC,0x32,0xE4,0x24,0x01,0x9D,
+0x5E,0xC9,0xC3,0xC8,0x06,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,
+0x46,0xFE,0xE8,0x85,0xA0,0x89,0x46,0xFA,0xE8,0x7F,0xA0,0x2B,0x46,0xFA,0x3D,0xB8,
+0x0B,0x76,0x04,0xB0,0x08,0xEB,0x24,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,
+0xBA,0x02,0x00,0xEC,0xA8,0x01,0x75,0x06,0x9D,0xE8,0xE8,0x9C,0xEB,0xDA,0xBA,0x00,
+0x00,0xEC,0x88,0x46,0xFD,0x9D,0x8A,0x46,0xFD,0xEB,0x00,0x5E,0xC9,0xC3,0x55,0x8B,
+0xEC,0x56,0x8B,0x56,0x04,0x8A,0x46,0x06,0xEE,0x33,0xF6,0xEB,0x03,0x50,0x58,0x46,
+0x83,0xFE,0x14,0x7C,0xF8,0x5E,0x5D,0xC3,0xC8,0x02,0x00,0x00,0x56,0x8B,0x56,0x04,
+0xEC,0x88,0x46,0xFF,0x33,0xF6,0xEB,0x03,0x50,0x58,0x46,0x83,0xFE,0x14,0x7C,0xF8,
+0x8A,0x46,0xFF,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,0x02,0x00,0x00,0x56,0x57,0x8B,0x76,
+0x04,0x83,0x3E,0xB0,0x0B,0x00,0x75,0x1F,0xBA,0x88,0x01,0xB0,0x00,0xEE,0xBA,0x86,
+0x01,0xB0,0x00,0xEE,0x6A,0x09,0x6A,0x00,0x68,0x30,0x01,0xE8,0x7D,0x01,0x83,0xC4,
+0x06,0xC7,0x06,0xB0,0x0B,0x01,0x00,0x6A,0x09,0x8B,0xC6,0x05,0x80,0x01,0x50,0xE8,
+0xDA,0x00,0x59,0x59,0x8B,0xF8,0x8B,0xC7,0xC1,0xE8,0x0C,0x25,0x0F,0x00,0x89,0x46,
+0xFE,0x8B,0xC7,0xC1,0xE8,0x08,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,0x0C,0x3B,
+0xC2,0x75,0x21,0x8B,0xC7,0xC1,0xE8,0x04,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,
+0x06,0x3B,0xC2,0x75,0x0F,0x8B,0xC7,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,0x09,
+0x3B,0xC2,0x74,0x0D,0x6A,0x07,0x56,0xE8,0x38,0x00,0x59,0x59,0xC7,0x46,0xFE,0x07,
+0x00,0x8A,0x46,0xFE,0x04,0x80,0xA2,0x33,0x02,0x8B,0xC6,0xBA,0x62,0x00,0xF7,0xEA,
+0x8A,0x56,0xFE,0x8B,0xD8,0x88,0x97,0x6C,0x00,0x68,0x32,0x02,0x8B,0xC6,0xBA,0x62,
+0x00,0xF7,0xEA,0x05,0x14,0x00,0x50,0xE8,0x0E,0xFD,0x59,0x59,0xEB,0x00,0x5F,0x5E,
+0xC9,0xC3,0xC8,0x02,0x00,0x00,0x56,0x8B,0x76,0x06,0x83,0xE6,0x0F,0x8B,0xC6,0xC1,
+0xE0,0x0C,0x8B,0xD6,0x83,0xF2,0x0C,0xC1,0xE2,0x08,0x0B,0xC2,0x8B,0xD6,0x83,0xF2,
+0x06,0xC1,0xE2,0x04,0x0B,0xC2,0x8B,0xD6,0x83,0xF2,0x09,0x0B,0xC2,0x89,0x46,0xFE,
+0x6A,0x19,0x6A,0x10,0x8B,0x46,0x04,0x99,0x05,0x40,0x01,0x83,0xD2,0x00,0x52,0x50,
+0xE8,0x6B,0x01,0x83,0xC4,0x06,0x0B,0x46,0xFE,0x83,0xCA,0x00,0x52,0x50,0xE8,0x9A,
+0x00,0x83,0xC4,0x06,0xE8,0x25,0x01,0xEB,0x00,0x5E,0xC9,0xC3,0x55,0x8B,0xEC,0x56,
+0x57,0x33,0xFF,0x6A,0x01,0x68,0x86,0x01,0xE8,0xA3,0xFE,0x59,0x59,0xB1,0x10,0x2A,
+0x4E,0x06,0xD3,0x66,0x04,0x33,0xF6,0xEB,0x2E,0x81,0x7E,0x04,0x00,0x80,0x72,0x04,
+0xB0,0x01,0xEB,0x02,0xB0,0x00,0x50,0x68,0x88,0x01,0xE8,0x81,0xFE,0x59,0x59,0x6A,
+0x03,0x68,0x86,0x01,0xE8,0x77,0xFE,0x59,0x59,0x6A,0x01,0x68,0x86,0x01,0xE8,0x6D,
+0xFE,0x59,0x59,0xD1,0x66,0x04,0x46,0x3B,0x76,0x06,0x7C,0xCD,0x33,0xF6,0xEB,0x24,
+0xD1,0xE7,0x6A,0x03,0x68,0x86,0x01,0xE8,0x54,0xFE,0x59,0x59,0x6A,0x01,0x68,0x86,
+0x01,0xE8,0x4A,0xFE,0x59,0x59,0x68,0x88,0x01,0xE8,0x5C,0xFE,0x59,0x98,0x25,0x01,
+0x00,0x0B,0xF8,0x46,0x83,0xFE,0x10,0x7C,0xD7,0x6A,0x00,0x68,0x86,0x01,0xE8,0x2D,
+0xFE,0x59,0x59,0x8B,0xC7,0xEB,0x00,0x5F,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x57,
+0x8B,0x7E,0x08,0x6A,0x01,0x68,0x86,0x01,0xE8,0x13,0xFE,0x59,0x59,0xB8,0x20,0x00,
+0x2B,0xC7,0x50,0xFF,0x76,0x06,0xFF,0x76,0x04,0xE8,0xA2,0x00,0x83,0xC4,0x06,0x89,
+0x56,0x06,0x89,0x46,0x04,0x33,0xF6,0xEB,0x47,0x81,0x7E,0x06,0x00,0x80,0x72,0x0C,
+0x75,0x06,0x83,0x7E,0x04,0x00,0x72,0x04,0xB0,0x01,0xEB,0x02,0xB0,0x00,0x50,0x68,
+0x88,0x01,0xE8,0xD9,0xFD,0x59,0x59,0x6A,0x03,0x68,0x86,0x01,0xE8,0xCF,0xFD,0x59,
+0x59,0x6A,0x01,0x68,0x86,0x01,0xE8,0xC5,0xFD,0x59,0x59,0x6A,0x01,0xFF,0x76,0x06,
+0xFF,0x76,0x04,0xE8,0x58,0x00,0x83,0xC4,0x06,0x89,0x56,0x06,0x89,0x46,0x04,0x46,
+0x3B,0xF7,0x7C,0xB5,0x6A,0x00,0x68,0x86,0x01,0xE8,0xA2,0xFD,0x59,0x59,0x6A,0x00,
+0x68,0x86,0x01,0xE8,0x98,0xFD,0x59,0x59,0x5F,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,
+0x6A,0x01,0x68,0x86,0x01,0xE8,0x86,0xFD,0x59,0x59,0x33,0xF6,0xEB,0x00,0x68,0x88,
+0x01,0xE8,0x94,0xFD,0x59,0xA8,0x01,0x75,0x08,0x8B,0xC6,0x46,0x3D,0x64,0x00,0x7C,
+0xED,0x6A,0x00,0x68,0x86,0x01,0xE8,0x65,0xFD,0x59,0x59,0x5E,0x5D,0xC3,0xC8,0x04,
+0x00,0x00,0x8B,0x46,0x04,0x8B,0x56,0x06,0x8B,0x4E,0x08,0xE3,0x06,0xD1,0xE0,0xD1,
+0xD2,0xE2,0xFA,0x89,0x46,0xFC,0x89,0x56,0xFE,0x8B,0x56,0xFE,0x8B,0x46,0xFC,0xEB,
+0x00,0xC9,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x50,0x72,0x65,0x76,0x69,0x6F,0x75,0x73,0x20,0x4D,0x65,0x6E,0x75,0x00,0x42,0x65,
+0x67,0x69,0x6E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x6F,0x72,0x74,
+0x20,0x30,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x00,0x50,0x6F,0x72,0x74,0x20,0x32,
+0x00,0x50,0x6F,0x72,0x74,0x20,0x33,0x00,0x50,0x6F,0x72,0x74,0x20,0x34,0x00,0x50,
+0x6F,0x72,0x74,0x20,0x35,0x00,0x50,0x6F,0x72,0x74,0x20,0x36,0x00,0x50,0x6F,0x72,
+0x74,0x20,0x37,0x00,0x50,0x6F,0x72,0x74,0x20,0x38,0x00,0x50,0x6F,0x72,0x74,0x20,
+0x39,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x30,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x31,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x32,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x33,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x34,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x35,0x00,0x9C,0x01,0xA3,0x01,0xAA,0x01,0xB1,0x01,0xB8,0x01,0xBF,0x01,0xC6,0x01,
+0xCD,0x01,0xD4,0x01,0xDB,0x01,0xE2,0x01,0xEA,0x01,0xF2,0x01,0xFA,0x01,0x02,0x02,
+0x0A,0x02,0x08,0x00,0x00,0x07,0x81,0x00,0x03,0x80,0x80,0x80,0x9F,0x91,0x95,0x91,
+0x9F,0x00,0x03,0x81,0x84,0x8E,0x95,0x84,0x84,0x84,0x84,0x00,0x03,0x82,0x84,0x84,
+0x84,0x84,0x95,0x8E,0x84,0x00,0x04,0x88,0x00,0xB2,0x0B,0xC6,0x0B,0xDA,0x0B,0xEE,
+0x0B,0x02,0x0C,0x16,0x0C,0x2A,0x0C,0x3E,0x0C,0x52,0x0C,0x77,0x0C,0x9C,0x0C,0xBE,
+0x0C,0xE0,0x0C,0x02,0x0D,0x01,0x80,0x20,0x54,0x65,0x73,0x74,0x20,0x50,0x61,0x73,
+0x73,0x65,0x64,0x20,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,
+0x80,0x20,0x4D,0x69,0x73,0x73,0x69,0x6E,0x67,0x20,0x52,0x78,0x20,0x44,0x61,0x74,
+0x61,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,0x42,
+0x61,0x64,0x20,0x52,0x78,0x20,0x44,0x61,0x74,0x61,0x20,0x1F,0x20,0x50,0x72,0x65,
+0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,0x58,0x6D,0x74,0x72,0x20,0x42,0x75,
+0x73,0x79,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,
+0x6E,0x6F,0x74,0x20,0x63,0x75,0x72,0x72,0x65,0x6E,0x74,0x6C,0x79,0x1F,0x20,0x20,
+0x69,0x6D,0x70,0x6C,0x65,0x6D,0x65,0x6E,0x74,0x65,0x64,0x02,0x00,0x24,0x0D,0x2F,
+0x0D,0x3A,0x0D,0x45,0x0D,0x50,0x0D,0x5B,0x0D,0x66,0x0D,0x71,0x0D,0x7C,0x0D,0x87,
+0x0D,0x92,0x0D,0x9D,0x0D,0xA8,0x0D,0xB3,0x0D,0xBE,0x0D,0xC9,0x0D,0x53,0x80,0x2C,
+0x32,0x54,0x44,0x20,0x53,0x86,0x2C,0x33,0x44,0x54,0x52,0x20,0x53,0x82,0x2C,0x33,
+0x52,0x54,0x53,0x20,0x1F,0x53,0x81,0x2C,0x32,0x52,0x44,0x20,0x53,0x85,0x2C,0x32,
+0x43,0x44,0x20,0x53,0x83,0x2C,0x33,0x43,0x54,0x53,0x20,0x53,0x84,0x2C,0x33,0x44,
+0x53,0x52,0x20,0x53,0x87,0x2C,0x32,0x52,0x49,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x30,0x1F,0x27,0x53,0x85,
+0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
+0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x53,0x52,
+0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x31,0x1F,0x27,0x53,0x84,0x2E,0x31,0x81,
+0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,
+0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x43,0x54,0x53,0x20,0x2D,0x20,
+0x70,0x69,0x6E,0x20,0x34,0x1F,0x27,0x53,0x83,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,
+0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,
+0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,
+0x32,0x1F,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,
+0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x2F,0x38,0x1F,
+0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x27,0x53,0x82,0x2E,
+0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,0x27,0x53,0x81,0x2E,0x30,0x53,0x4D,0x81,
+0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,
+0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,0x44,0x20,0x2D,0x20,
+0x70,0x69,0x6E,0x20,0x33,0x1F,0x27,0x53,0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,
+0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
+0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,
+0x6E,0x20,0x35,0x1F,0x27,0x53,0x85,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,
+0x27,0x53,0x84,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x43,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x1F,0x27,0x53,0x83,0x2E,
+0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,
+0x20,0x28,0x6E,0x2E,0x63,0x2E,0x29,0x1F,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,
+0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
+0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,
+0x6E,0x20,0x32,0x1F,0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x37,0x1F,
+0x27,0x53,0x82,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x52,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x1F,0x27,0x53,0x81,0x2E,
+0x30,0x53,0x4D,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,
+0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,
+0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x33,0x1F,0x27,0x53,0x80,0x2E,0x30,0x53,
+0x4D,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x43,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x85,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x84,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x43,0x54,0x53,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x83,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x28,0x6E,0x2E,0x63,0x2E,0x29,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x54,0x52,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x86,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x37,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x82,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x81,0x2E,
+0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,
+0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,
+0x33,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,
+0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x30,0x1F,0x20,0x20,0x20,
+0x20,0x27,0x53,0x85,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,
+0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,
+0x69,0x6E,0x20,0x31,0x31,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x84,0x2E,0x31,0x81,
+0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x43,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x34,0x1F,0x20,0x20,
+0x20,0x20,0x27,0x53,0x83,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,
+0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x70,
+0x69,0x6E,0x20,0x32,0x32,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x87,0x2E,0x31,0x81,
+0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x2F,0x38,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x82,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x81,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,
+0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,
+0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x33,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,
+0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x27,0x02,0x00,0x68,0x04,0x96,0x04,0xB6,0x03,0x3C,0x04,0x0E,0x04,0x89,0x03,
+0x5C,0x03,0xE2,0x03,0x60,0x08,0x8A,0x08,0xBE,0x07,0x38,0x08,0x0E,0x08,0x95,0x07,
+0x6C,0x07,0xE6,0x07,0x1C,0x05,0x74,0x05,0xFA,0x05,0xC4,0x04,0xF0,0x04,0xCC,0x05,
+0xA0,0x05,0x48,0x05,0x78,0x06,0xC8,0x06,0x42,0x07,0x28,0x06,0x50,0x06,0x18,0x07,
+0xF0,0x06,0xA0,0x06,0x00,0x00,0xF4,0x08,0xF4,0x08,0xD4,0x0D,0x04,0x09,0x04,0x09,
+0x04,0x09,0x04,0x09,0x42,0x00,0x0C,0x09,0x1C,0x09,0xE5,0x0D,0x02,0x00,0x14,0x09,
+0x04,0x09,0xF4,0x0D,0x43,0x00,0x1C,0x09,0x0C,0x09,0x05,0x0E,0x00,0x04,0x04,0x09,
+0x14,0x09,0x12,0x0E,0x2C,0x09,0x2C,0x09,0x2C,0x09,0x2C,0x09,0x00,0x00,0x3C,0x09,
+0x6C,0x09,0x1E,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x01,0x4C,0x09,
+0x2C,0x09,0x2D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x02,0x5C,0x09,
+0x3C,0x09,0x3D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x03,0x6C,0x09,
+0x4C,0x09,0x4D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0xFF,0x00,0x2C,0x09,
+0x5C,0x09,0x00,0x00,0x00,0x05,0x84,0x09,0xEC,0x09,0x5E,0x0E,0xF4,0x09,0xF4,0x09,
+0xF4,0x09,0xF4,0x09,0x00,0x06,0x94,0x09,0x74,0x09,0x68,0x0E,0xAC,0x0A,0xAC,0x0A,
+0xAC,0x0A,0xAC,0x0A,0x00,0x07,0xA4,0x09,0x84,0x09,0x72,0x0E,0xBC,0x0A,0xBC,0x0A,
+0xBC,0x0A,0xBC,0x0A,0x00,0x08,0xB4,0x09,0x94,0x09,0x7C,0x0E,0xD4,0x0A,0xD4,0x0A,
+0xD4,0x0A,0xD4,0x0A,0x00,0x0B,0xC4,0x09,0xA4,0x09,0x83,0x0E,0xFC,0x0A,0xFC,0x0A,
+0xFC,0x0A,0xFC,0x0A,0x00,0x0C,0xD4,0x09,0xB4,0x09,0x90,0x0E,0x14,0x0B,0x14,0x0B,
+0x14,0x0B,0x14,0x0B,0x00,0x02,0xE4,0x09,0xC4,0x09,0xA0,0x0E,0x2C,0x0B,0x2C,0x0B,
+0x2C,0x0B,0x2C,0x0B,0x04,0x00,0xEC,0x09,0xD4,0x09,0x0E,0x00,0xFF,0x00,0x74,0x09,
+0xE4,0x09,0x00,0x00,0x82,0x01,0xFC,0x09,0xA4,0x0A,0xAC,0x0E,0x82,0x02,0x04,0x0A,
+0xF4,0x09,0xAF,0x0E,0x82,0x03,0x0C,0x0A,0xFC,0x09,0xB2,0x0E,0x82,0x04,0x14,0x0A,
+0x04,0x0A,0xB6,0x0E,0x82,0x05,0x1C,0x0A,0x0C,0x0A,0xBC,0x0E,0x82,0x06,0x24,0x0A,
+0x14,0x0A,0xC0,0x0E,0x82,0x07,0x2C,0x0A,0x1C,0x0A,0xC4,0x0E,0x82,0x08,0x34,0x0A,
+0x24,0x0A,0xC8,0x0E,0x82,0x09,0x3C,0x0A,0x2C,0x0A,0xCC,0x0E,0x82,0x0A,0x44,0x0A,
+0x34,0x0A,0xD1,0x0E,0x82,0x10,0x4C,0x0A,0x3C,0x0A,0xD6,0x0E,0x82,0x0B,0x54,0x0A,
+0x44,0x0A,0xDB,0x0E,0x82,0x11,0x5C,0x0A,0x4C,0x0A,0xE0,0x0E,0x82,0x0C,0x64,0x0A,
+0x54,0x0A,0xE5,0x0E,0x82,0x12,0x6C,0x0A,0x5C,0x0A,0xEA,0x0E,0x82,0x0D,0x74,0x0A,
+0x64,0x0A,0xEF,0x0E,0x82,0x0E,0x7C,0x0A,0x6C,0x0A,0xF4,0x0E,0x82,0x0F,0x84,0x0A,
+0x74,0x0A,0xFB,0x0E,0x82,0x13,0x8C,0x0A,0x7C,0x0A,0x02,0x0F,0x82,0x14,0x94,0x0A,
+0x84,0x0A,0x09,0x0F,0x82,0x15,0x9C,0x0A,0x8C,0x0A,0x10,0x0F,0x82,0x16,0xA4,0x0A,
+0x94,0x0A,0x17,0x0F,0x82,0x17,0xF4,0x09,0x9C,0x0A,0x1E,0x0F,0x82,0x02,0xB4,0x0A,
+0xB4,0x0A,0x26,0x0F,0x82,0x03,0xAC,0x0A,0xAC,0x0A,0x2D,0x0F,0x82,0x00,0xC4,0x0A,
+0xCC,0x0A,0x34,0x0F,0x82,0x01,0xCC,0x0A,0xBC,0x0A,0x3F,0x0F,0x82,0x02,0xBC,0x0A,
+0xC4,0x0A,0x4D,0x0F,0x82,0x00,0xDC,0x0A,0xF4,0x0A,0x59,0x0F,0x82,0x01,0xE4,0x0A,
+0xD4,0x0A,0x63,0x0F,0x82,0x02,0xEC,0x0A,0xDC,0x0A,0x6E,0x0F,0x82,0x03,0xF4,0x0A,
+0xE4,0x0A,0x7A,0x0F,0x82,0x04,0xD4,0x0A,0xEC,0x0A,0x87,0x0F,0x82,0x00,0x04,0x0B,
+0x0C,0x0B,0x93,0x0F,0x82,0x01,0x0C,0x0B,0xFC,0x0A,0x9B,0x0F,0x82,0x02,0xFC,0x0A,
+0x04,0x0B,0xA7,0x0F,0x82,0x00,0x1C,0x0B,0x24,0x0B,0xB0,0x0F,0x82,0x01,0x24,0x0B,
+0x14,0x0B,0xB5,0x0F,0x82,0x02,0x14,0x0B,0x1C,0x0B,0xBE,0x0F,0x44,0x00,0x34,0x0B,
+0xA4,0x0B,0x9C,0x01,0x44,0x01,0x3C,0x0B,0x2C,0x0B,0xA3,0x01,0x44,0x02,0x44,0x0B,
+0x34,0x0B,0xAA,0x01,0x44,0x03,0x4C,0x0B,0x3C,0x0B,0xB1,0x01,0x44,0x04,0x54,0x0B,
+0x44,0x0B,0xB8,0x01,0x44,0x05,0x5C,0x0B,0x4C,0x0B,0xBF,0x01,0x44,0x06,0x64,0x0B,
+0x54,0x0B,0xC6,0x01,0x44,0x07,0x6C,0x0B,0x5C,0x0B,0xCD,0x01,0x44,0x08,0x74,0x0B,
+0x64,0x0B,0xD4,0x01,0x44,0x09,0x7C,0x0B,0x6C,0x0B,0xDB,0x01,0x44,0x0A,0x84,0x0B,
+0x74,0x0B,0xE2,0x01,0x44,0x0B,0x8C,0x0B,0x7C,0x0B,0xEA,0x01,0x44,0x0C,0x94,0x0B,
+0x84,0x0B,0xF2,0x01,0x44,0x0D,0x9C,0x0B,0x8C,0x0B,0xFA,0x01,0x44,0x0E,0xA4,0x0B,
+0x94,0x0B,0x02,0x02,0x44,0x0F,0x2C,0x0B,0x9C,0x0B,0x0A,0x02,0x17,0x1F,0x0F,0x2F,
+0x00,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,
+0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,
+0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,
+0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,
+0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,
+0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,
+0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,
+0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,
+0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,
+0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,
+0x02,0x00,0x01,0x80,0x49,0x6E,0x73,0x74,0x61,0x6C,0x6C,0x20,0x4C,0x6F,0x6F,0x70,
+0x62,0x61,0x63,0x6B,0x1F,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x20,0x74,0x6F,0x20,
+0x73,0x74,0x61,0x72,0x74,0x02,0x00,0x01,0x80,0x20,0x43,0x61,0x62,0x6C,0x65,0x20,
+0x74,0x6F,0x20,0x52,0x65,0x6D,0x6F,0x74,0x65,0x1F,0x50,0x72,0x65,0x73,0x73,0x20,
+0x80,0x20,0x74,0x6F,0x20,0x73,0x74,0x61,0x72,0x74,0x02,0x00,0x01,0x80,0x20,0x4C,
+0x6F,0x63,0x61,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x20,0x1F,0x20,
+0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,0x02,0x00,0x01,0x80,
+0x52,0x65,0x6D,0x6F,0x74,0x65,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x20,
+0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,0x02,0x00,
+0x01,0x80,0x20,0x49,0x6E,0x74,0x72,0x6E,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,
+0x63,0x6B,0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,
+0x02,0x00,0x01,0x80,0x54,0x72,0x61,0x6E,0x73,0x6D,0x69,0x74,0x20,0x50,0x61,0x74,
+0x74,0x65,0x72,0x6E,0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,
+0x2E,0x2E,0x02,0x00,0x01,0x80,0x20,0x20,0x30,0x3A,0x20,0x27,0x43,0x80,0x00,0x01,
+0x80,0x20,0x20,0x31,0x3A,0x20,0x27,0x43,0x81,0x00,0x01,0x80,0x20,0x20,0x32,0x3A,
+0x20,0x27,0x43,0x82,0x00,0x01,0x80,0x20,0x20,0x33,0x3A,0x20,0x27,0x43,0x83,0x00,
+0x01,0x80,0x20,0x20,0x34,0x3A,0x20,0x27,0x43,0x84,0x00,0x01,0x80,0x20,0x20,0x35,
+0x3A,0x20,0x27,0x43,0x85,0x00,0x01,0x80,0x20,0x20,0x36,0x3A,0x20,0x27,0x43,0x86,
+0x00,0x01,0x80,0x20,0x20,0x37,0x3A,0x20,0x27,0x43,0x87,0x00,0x01,0x80,0x20,0x20,
+0x38,0x3A,0x20,0x27,0x43,0x88,0x00,0x01,0x80,0x20,0x20,0x39,0x3A,0x20,0x27,0x43,
+0x89,0x00,0x01,0x80,0x20,0x31,0x30,0x3A,0x20,0x27,0x43,0x8A,0x00,0x01,0x80,0x20,
+0x31,0x31,0x3A,0x20,0x27,0x43,0x8B,0x00,0x01,0x80,0x20,0x31,0x32,0x3A,0x20,0x27,
+0x43,0x8C,0x00,0x01,0x80,0x20,0x31,0x33,0x3A,0x20,0x27,0x43,0x8D,0x00,0x01,0x80,
+0x20,0x31,0x34,0x3A,0x20,0x27,0x43,0x8E,0x00,0x01,0x80,0x20,0x31,0x35,0x3A,0x20,
+0x27,0x43,0x8F,0x00,0x2A,0x2A,0x20,0x4D,0x61,0x69,0x6E,0x20,0x20,0x4D,0x65,0x6E,
+0x75,0x20,0x2A,0x2A,0x00,0x4D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,0x61,0x20,0x50,
+0x6F,0x72,0x74,0x00,0x4D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,0x61,0x20,0x53,0x69,
+0x67,0x6E,0x61,0x6C,0x00,0x45,0x73,0x74,0x69,0x6D,0x61,0x74,0x65,0x20,0x43,0x50,
+0x53,0x00,0x44,0x69,0x61,0x67,0x6E,0x6F,0x73,0x74,0x69,0x63,0x73,0x00,0x4C,0x6F,
+0x63,0x61,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x52,0x65,0x6D,
+0x6F,0x74,0x65,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x49,0x6E,0x74,
+0x72,0x6E,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x54,0x72,0x61,
+0x6E,0x73,0x6D,0x69,0x74,0x20,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x00,0x42,0x61,
+0x75,0x64,0x20,0x52,0x61,0x74,0x65,0x00,0x44,0x61,0x74,0x61,0x20,0x42,0x69,0x74,
+0x73,0x00,0x53,0x74,0x6F,0x70,0x20,0x42,0x69,0x74,0x73,0x00,0x50,0x61,0x72,0x69,
+0x74,0x79,0x00,0x44,0x61,0x74,0x61,0x20,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x00,
+0x54,0x78,0x20,0x46,0x6C,0x6F,0x77,0x20,0x43,0x6F,0x6E,0x74,0x72,0x6F,0x6C,0x00,
+0x50,0x6F,0x72,0x74,0x20,0x4E,0x75,0x6D,0x62,0x65,0x72,0x00,0x35,0x30,0x00,0x37,
+0x35,0x00,0x31,0x31,0x30,0x00,0x31,0x33,0x34,0x2E,0x35,0x00,0x31,0x35,0x30,0x00,
+0x32,0x30,0x30,0x00,0x33,0x30,0x30,0x00,0x36,0x30,0x30,0x00,0x31,0x32,0x30,0x30,
+0x00,0x31,0x38,0x30,0x30,0x00,0x32,0x30,0x30,0x30,0x00,0x32,0x34,0x30,0x30,0x00,
+0x33,0x36,0x30,0x30,0x00,0x34,0x38,0x30,0x30,0x00,0x37,0x32,0x30,0x30,0x00,0x39,
+0x36,0x30,0x30,0x00,0x31,0x39,0x2C,0x32,0x30,0x30,0x00,0x33,0x38,0x2C,0x34,0x30,
+0x30,0x00,0x35,0x36,0x2C,0x30,0x30,0x30,0x00,0x35,0x37,0x2C,0x36,0x30,0x30,0x00,
+0x36,0x34,0x2C,0x30,0x30,0x30,0x00,0x37,0x36,0x2C,0x38,0x30,0x30,0x00,0x31,0x31,
+0x35,0x2C,0x32,0x30,0x30,0x00,0x37,0x20,0x62,0x69,0x74,0x73,0x00,0x38,0x20,0x62,
+0x69,0x74,0x73,0x00,0x31,0x20,0x73,0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x00,0x31,
+0x2E,0x35,0x20,0x73,0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x73,0x00,0x32,0x20,0x73,
+0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x73,0x00,0x6E,0x6F,0x20,0x70,0x61,0x72,0x69,
+0x74,0x79,0x00,0x6F,0x64,0x64,0x20,0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x65,0x76,
+0x65,0x6E,0x20,0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x73,0x70,0x61,0x63,0x65,0x20,
+0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x6D,0x61,0x72,0x6B,0x20,0x70,0x61,0x72,0x69,
+0x74,0x79,0x00,0x43,0x6F,0x6C,0x75,0x6D,0x6E,0x73,0x00,0x42,0x61,0x72,0x62,0x65,
+0x72,0x20,0x50,0x6F,0x6C,0x65,0x00,0x55,0x55,0x55,0x55,0x55,0x2E,0x2E,0x2E,0x00,
+0x4E,0x6F,0x6E,0x65,0x00,0x58,0x6F,0x6E,0x2F,0x58,0x6F,0x66,0x66,0x00,0x43,0x54,
+0x53,0x00,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x20,0x66,0x6F,0x72,0x20,0x6D,0x65,
+0x6E,0x75,0x00,0x28,0x63,0x6F,0x75,0x6E,0x74,0x69,0x6E,0x67,0x2E,0x2E,0x2E,0x29,
+0x00,0x00,0x65,0x4E,0x64,0x20,0x4F,0x66,0x20,0x43,0x6F,0x44,0x65,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+};
diff --git a/drivers/char/ip2/i2cmd.c b/drivers/char/ip2/i2cmd.c
new file mode 100644
index 000000000000..fd299d6c42ac
--- /dev/null
+++ b/drivers/char/ip2/i2cmd.c
@@ -0,0 +1,209 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Definition table for In-line and Bypass commands. Applicable
+* only when the standard loadware is active. (This is included
+* source code, not a separate compilation module.)
+*
+*******************************************************************************/
+
+//------------------------------------------------------------------------------
+//
+// Revision History:
+//
+// 10 October 1991 MAG First Draft
+// 7 November 1991 MAG Reflects additional commands.
+// 24 February 1992 MAG Additional commands for 1.4.x loadware
+// 11 March 1992 MAG Additional commands
+// 30 March 1992 MAG Additional command: CMD_DSS_NOW
+// 18 May 1992 MAG Discovered commands 39 & 40 must be at the end of a
+// packet: affects implementation.
+//------------------------------------------------------------------------------
+
+//************
+//* Includes *
+//************
+
+#include "i2cmd.h" /* To get some bit-defines */
+
+//------------------------------------------------------------------------------
+// Here is the table of global arrays which represent each type of command
+// supported in the IntelliPort standard loadware. See also i2cmd.h
+// for a more complete explanation of what is going on.
+//------------------------------------------------------------------------------
+
+// Here are the various globals: note that the names are not used except through
+// the macros defined in i2cmd.h. Also note that although they are character
+// arrays here (for extendability) they are cast to structure pointers in the
+// i2cmd.h macros. See i2cmd.h for flags definitions.
+
+// Length Flags Command
+static UCHAR ct02[] = { 1, BTH, 0x02 }; // DTR UP
+static UCHAR ct03[] = { 1, BTH, 0x03 }; // DTR DN
+static UCHAR ct04[] = { 1, BTH, 0x04 }; // RTS UP
+static UCHAR ct05[] = { 1, BTH, 0x05 }; // RTS DN
+static UCHAR ct06[] = { 1, BYP, 0x06 }; // START FL
+static UCHAR ct07[] = { 2, BTH, 0x07,0 }; // BAUD
+static UCHAR ct08[] = { 2, BTH, 0x08,0 }; // BITS
+static UCHAR ct09[] = { 2, BTH, 0x09,0 }; // STOP
+static UCHAR ct10[] = { 2, BTH, 0x0A,0 }; // PARITY
+static UCHAR ct11[] = { 2, BTH, 0x0B,0 }; // XON
+static UCHAR ct12[] = { 2, BTH, 0x0C,0 }; // XOFF
+static UCHAR ct13[] = { 1, BTH, 0x0D }; // STOP FL
+static UCHAR ct14[] = { 1, BYP|VIP, 0x0E }; // ACK HOTK
+//static UCHAR ct15[]={ 2, BTH|VIP, 0x0F,0 }; // IRQ SET
+static UCHAR ct16[] = { 2, INL, 0x10,0 }; // IXONOPTS
+static UCHAR ct17[] = { 2, INL, 0x11,0 }; // OXONOPTS
+static UCHAR ct18[] = { 1, INL, 0x12 }; // CTSENAB
+static UCHAR ct19[] = { 1, BTH, 0x13 }; // CTSDSAB
+static UCHAR ct20[] = { 1, INL, 0x14 }; // DCDENAB
+static UCHAR ct21[] = { 1, BTH, 0x15 }; // DCDDSAB
+static UCHAR ct22[] = { 1, BTH, 0x16 }; // DSRENAB
+static UCHAR ct23[] = { 1, BTH, 0x17 }; // DSRDSAB
+static UCHAR ct24[] = { 1, BTH, 0x18 }; // RIENAB
+static UCHAR ct25[] = { 1, BTH, 0x19 }; // RIDSAB
+static UCHAR ct26[] = { 2, BTH, 0x1A,0 }; // BRKENAB
+static UCHAR ct27[] = { 1, BTH, 0x1B }; // BRKDSAB
+//static UCHAR ct28[]={ 2, BTH, 0x1C,0 }; // MAXBLOKSIZE
+//static UCHAR ct29[]={ 2, 0, 0x1D,0 }; // reserved
+static UCHAR ct30[] = { 1, INL, 0x1E }; // CTSFLOWENAB
+static UCHAR ct31[] = { 1, INL, 0x1F }; // CTSFLOWDSAB
+static UCHAR ct32[] = { 1, INL, 0x20 }; // RTSFLOWENAB
+static UCHAR ct33[] = { 1, INL, 0x21 }; // RTSFLOWDSAB
+static UCHAR ct34[] = { 2, BTH, 0x22,0 }; // ISTRIPMODE
+static UCHAR ct35[] = { 2, BTH|END, 0x23,0 }; // SENDBREAK
+static UCHAR ct36[] = { 2, BTH, 0x24,0 }; // SETERRMODE
+//static UCHAR ct36a[]={ 3, INL, 0x24,0,0 }; // SET_REPLACE
+
+// The following is listed for completeness, but should never be sent directly
+// by user-level code. It is sent only by library routines in response to data
+// movement.
+//static UCHAR ct37[]={ 5, BYP|VIP, 0x25,0,0,0,0 }; // FLOW PACKET
+
+// Back to normal
+//static UCHAR ct38[] = {11, BTH|VAR, 0x26,0,0,0,0,0,0,0,0,0,0 }; // DEF KEY SEQ
+//static UCHAR ct39[]={ 3, BTH|END, 0x27,0,0 }; // OPOSTON
+//static UCHAR ct40[]={ 1, BTH|END, 0x28 }; // OPOSTOFF
+static UCHAR ct41[] = { 1, BYP, 0x29 }; // RESUME
+//static UCHAR ct42[]={ 2, BTH, 0x2A,0 }; // TXBAUD
+//static UCHAR ct43[]={ 2, BTH, 0x2B,0 }; // RXBAUD
+//static UCHAR ct44[]={ 2, BTH, 0x2C,0 }; // MS PING
+//static UCHAR ct45[]={ 1, BTH, 0x2D }; // HOTENAB
+//static UCHAR ct46[]={ 1, BTH, 0x2E }; // HOTDSAB
+static UCHAR ct47[] = { 7, BTH, 0x2F,0,0,0,0,0,0 }; // UNIX FLAGS
+//static UCHAR ct48[]={ 1, BTH, 0x30 }; // DSRFLOWENAB
+//static UCHAR ct49[]={ 1, BTH, 0x31 }; // DSRFLOWDSAB
+//static UCHAR ct50[]={ 1, BTH, 0x32 }; // DTRFLOWENAB
+//static UCHAR ct51[]={ 1, BTH, 0x33 }; // DTRFLOWDSAB
+//static UCHAR ct52[]={ 1, BTH, 0x34 }; // BAUDTABRESET
+//static UCHAR ct53[] = { 3, BTH, 0x35,0,0 }; // BAUDREMAP
+static UCHAR ct54[] = { 3, BTH, 0x36,0,0 }; // CUSTOMBAUD1
+static UCHAR ct55[] = { 3, BTH, 0x37,0,0 }; // CUSTOMBAUD2
+static UCHAR ct56[] = { 2, BTH|END, 0x38,0 }; // PAUSE
+static UCHAR ct57[] = { 1, BYP, 0x39 }; // SUSPEND
+static UCHAR ct58[] = { 1, BYP, 0x3A }; // UNSUSPEND
+static UCHAR ct59[] = { 2, BTH, 0x3B,0 }; // PARITYCHK
+static UCHAR ct60[] = { 1, INL|VIP, 0x3C }; // BOOKMARKREQ
+//static UCHAR ct61[]={ 2, BTH, 0x3D,0 }; // INTERNALLOOP
+//static UCHAR ct62[]={ 2, BTH, 0x3E,0 }; // HOTKTIMEOUT
+static UCHAR ct63[] = { 2, INL, 0x3F,0 }; // SETTXON
+static UCHAR ct64[] = { 2, INL, 0x40,0 }; // SETTXOFF
+//static UCHAR ct65[]={ 2, BTH, 0x41,0 }; // SETAUTORTS
+//static UCHAR ct66[]={ 2, BTH, 0x42,0 }; // SETHIGHWAT
+//static UCHAR ct67[]={ 2, BYP, 0x43,0 }; // STARTSELFL
+//static UCHAR ct68[]={ 2, INL, 0x44,0 }; // ENDSELFL
+//static UCHAR ct69[]={ 1, BYP, 0x45 }; // HWFLOW_OFF
+//static UCHAR ct70[]={ 1, BTH, 0x46 }; // ODSRFL_ENAB
+//static UCHAR ct71[]={ 1, BTH, 0x47 }; // ODSRFL_DSAB
+//static UCHAR ct72[]={ 1, BTH, 0x48 }; // ODCDFL_ENAB
+//static UCHAR ct73[]={ 1, BTH, 0x49 }; // ODCDFL_DSAB
+//static UCHAR ct74[]={ 2, BTH, 0x4A,0 }; // LOADLEVEL
+//static UCHAR ct75[]={ 2, BTH, 0x4B,0 }; // STATDATA
+//static UCHAR ct76[]={ 1, BYP, 0x4C }; // BREAK_ON
+//static UCHAR ct77[]={ 1, BYP, 0x4D }; // BREAK_OFF
+//static UCHAR ct78[]={ 1, BYP, 0x4E }; // GETFC
+static UCHAR ct79[] = { 2, BYP, 0x4F,0 }; // XMIT_NOW
+//static UCHAR ct80[]={ 4, BTH, 0x50,0,0,0 }; // DIVISOR_LATCH
+//static UCHAR ct81[]={ 1, BYP, 0x51 }; // GET_STATUS
+//static UCHAR ct82[]={ 1, BYP, 0x52 }; // GET_TXCNT
+//static UCHAR ct83[]={ 1, BYP, 0x53 }; // GET_RXCNT
+//static UCHAR ct84[]={ 1, BYP, 0x54 }; // GET_BOXIDS
+//static UCHAR ct85[]={10, BYP, 0x55,0,0,0,0,0,0,0,0,0 }; // ENAB_MULT
+//static UCHAR ct86[]={ 2, BTH, 0x56,0 }; // RCV_ENABLE
+static UCHAR ct87[] = { 1, BYP, 0x57 }; // HW_TEST
+//static UCHAR ct88[]={ 3, BTH, 0x58,0,0 }; // RCV_THRESHOLD
+static UCHAR ct89[]={ 1, BYP, 0x59 }; // DSS_NOW
+//static UCHAR ct90[]={ 3, BYP, 0x5A,0,0 }; // Set SILO
+//static UCHAR ct91[]={ 2, BYP, 0x5B,0 }; // timed break
+
+// Some composite commands as well
+//static UCHAR cc01[]={ 2, BTH, 0x02,0x04 }; // DTR & RTS UP
+//static UCHAR cc02[]={ 2, BTH, 0x03,0x05 }; // DTR & RTS DN
+
+//********
+//* Code *
+//********
+
+//******************************************************************************
+// Function: i2cmdUnixFlags(iflag, cflag, lflag)
+// Parameters: Unix tty flags
+//
+// Returns: Pointer to command structure
+//
+// Description:
+//
+// This routine sets the parameters of command 47 and returns a pointer to the
+// appropriate structure.
+//******************************************************************************
+cmdSyntaxPtr
+i2cmdUnixFlags(unsigned short iflag,unsigned short cflag,unsigned short lflag)
+{
+ cmdSyntaxPtr pCM = (cmdSyntaxPtr) ct47;
+
+ pCM->cmd[1] = (unsigned char) iflag;
+ pCM->cmd[2] = (unsigned char) (iflag >> 8);
+ pCM->cmd[3] = (unsigned char) cflag;
+ pCM->cmd[4] = (unsigned char) (cflag >> 8);
+ pCM->cmd[5] = (unsigned char) lflag;
+ pCM->cmd[6] = (unsigned char) (lflag >> 8);
+ return pCM;
+}
+
+//******************************************************************************
+// Function: i2cmdBaudDef(which, rate)
+// Parameters: ?
+//
+// Returns: Pointer to command structure
+//
+// Description:
+//
+// This routine sets the parameters of commands 54 or 55 (according to the
+// argument which), and returns a pointer to the appropriate structure.
+//******************************************************************************
+cmdSyntaxPtr
+i2cmdBaudDef(int which, unsigned short rate)
+{
+ cmdSyntaxPtr pCM;
+
+ switch(which)
+ {
+ case 1:
+ pCM = (cmdSyntaxPtr) ct54;
+ break;
+ default:
+ case 2:
+ pCM = (cmdSyntaxPtr) ct55;
+ break;
+ }
+ pCM->cmd[1] = (unsigned char) rate;
+ pCM->cmd[2] = (unsigned char) (rate >> 8);
+ return pCM;
+}
+
diff --git a/drivers/char/ip2/i2cmd.h b/drivers/char/ip2/i2cmd.h
new file mode 100644
index 000000000000..c41728a85710
--- /dev/null
+++ b/drivers/char/ip2/i2cmd.h
@@ -0,0 +1,643 @@
+/*******************************************************************************
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Definitions and support for In-line and Bypass commands.
+* Applicable only when the standard loadware is active.
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 10 October 1991 MAG First Draft
+// 7 November 1991 MAG Reflects some new commands
+// 20 February 1992 MAG CMD_HOTACK corrected: no argument.
+// 24 February 1992 MAG Support added for new commands for 1.4.x loadware.
+// 11 March 1992 MAG Additional commands.
+// 16 March 1992 MAG Additional commands.
+// 30 March 1992 MAG Additional command: CMD_DSS_NOW
+// 18 May 1992 MAG Changed CMD_OPOST
+//
+//------------------------------------------------------------------------------
+#ifndef I2CMD_H // To prevent multiple includes
+#define I2CMD_H 1
+
+#include "ip2types.h"
+
+// This module is designed to provide a uniform method of sending commands to
+// the board through command packets. The difficulty is, some commands take
+// parameters, others do not. Furthermore, it is often useful to send several
+// commands to the same channel as part of the same packet. (See also i2pack.h.)
+//
+// This module is designed so that the caller should not be responsible for
+// remembering the exact syntax of each command, or at least so that the
+// compiler could check things somewhat. I'll explain as we go...
+//
+// First, a structure which can embody the syntax of each type of command.
+//
+typedef struct _cmdSyntax
+{
+ UCHAR length; // Number of bytes in the command
+ UCHAR flags; // Information about the command (see below)
+
+ // The command and its parameters, which may be of arbitrary length. Don't
+ // worry yet how the parameters will be initialized; macros later take care
+ // of it. Also, don't worry about the arbitrary length issue; this structure
+ // is never used to allocate space (see i2cmd.c).
+ UCHAR cmd[2];
+} cmdSyntax, *cmdSyntaxPtr;
+
+// Bit assignments for flags
+
+#define INL 1 // Set if suitable for inline commands
+#define BYP 2 // Set if suitable for bypass commands
+#define BTH (INL|BYP) // suitable for either!
+#define END 4 // Set if this must be the last command in a block
+#define VIP 8 // Set if this command is special in some way and really
+ // should only be sent from the library-level and not
+ // directly from user-level
+#define VAR 0x10 // This command is of variable length!
+
+//-----------------------------------
+// External declarations for i2cmd.c
+//-----------------------------------
+// Routine to set up parameters for the "define hot-key sequence" command. Since
+// there is more than one parameter to assign, we must use a function rather
+// than a macro (used usually).
+//
+extern cmdSyntaxPtr i2cmdUnixFlags(USHORT iflag,USHORT cflag,USHORT lflag);
+extern cmdSyntaxPtr i2cmdBaudDef(int which, USHORT rate);
+
+// Declarations for the global arrays used to bear the commands and their
+// arguments.
+//
+// Note: Since these are globals and the arguments might change, it is important
+// that the library routine COPY these into buffers from whence they would be
+// sent, rather than merely storing the pointers. In multi-threaded
+// environments, important that the copy should obtain before any context switch
+// is allowed. Also, for parameterized commands, DO NOT ISSUE THE SAME COMMAND
+// MORE THAN ONCE WITH THE SAME PARAMETERS in the same call.
+//
+static UCHAR ct02[];
+static UCHAR ct03[];
+static UCHAR ct04[];
+static UCHAR ct05[];
+static UCHAR ct06[];
+static UCHAR ct07[];
+static UCHAR ct08[];
+static UCHAR ct09[];
+static UCHAR ct10[];
+static UCHAR ct11[];
+static UCHAR ct12[];
+static UCHAR ct13[];
+static UCHAR ct14[];
+static UCHAR ct15[];
+static UCHAR ct16[];
+static UCHAR ct17[];
+static UCHAR ct18[];
+static UCHAR ct19[];
+static UCHAR ct20[];
+static UCHAR ct21[];
+static UCHAR ct22[];
+static UCHAR ct23[];
+static UCHAR ct24[];
+static UCHAR ct25[];
+static UCHAR ct26[];
+static UCHAR ct27[];
+static UCHAR ct28[];
+static UCHAR ct29[];
+static UCHAR ct30[];
+static UCHAR ct31[];
+static UCHAR ct32[];
+static UCHAR ct33[];
+static UCHAR ct34[];
+static UCHAR ct35[];
+static UCHAR ct36[];
+static UCHAR ct36a[];
+static UCHAR ct41[];
+static UCHAR ct42[];
+static UCHAR ct43[];
+static UCHAR ct44[];
+static UCHAR ct45[];
+static UCHAR ct46[];
+static UCHAR ct48[];
+static UCHAR ct49[];
+static UCHAR ct50[];
+static UCHAR ct51[];
+static UCHAR ct52[];
+static UCHAR ct56[];
+static UCHAR ct57[];
+static UCHAR ct58[];
+static UCHAR ct59[];
+static UCHAR ct60[];
+static UCHAR ct61[];
+static UCHAR ct62[];
+static UCHAR ct63[];
+static UCHAR ct64[];
+static UCHAR ct65[];
+static UCHAR ct66[];
+static UCHAR ct67[];
+static UCHAR ct68[];
+static UCHAR ct69[];
+static UCHAR ct70[];
+static UCHAR ct71[];
+static UCHAR ct72[];
+static UCHAR ct73[];
+static UCHAR ct74[];
+static UCHAR ct75[];
+static UCHAR ct76[];
+static UCHAR ct77[];
+static UCHAR ct78[];
+static UCHAR ct79[];
+static UCHAR ct80[];
+static UCHAR ct81[];
+static UCHAR ct82[];
+static UCHAR ct83[];
+static UCHAR ct84[];
+static UCHAR ct85[];
+static UCHAR ct86[];
+static UCHAR ct87[];
+static UCHAR ct88[];
+static UCHAR ct89[];
+static UCHAR ct90[];
+static UCHAR ct91[];
+static UCHAR cc01[];
+static UCHAR cc02[];
+
+// Now, refer to i2cmd.c, and see the character arrays defined there. They are
+// cast here to cmdSyntaxPtr.
+//
+// There are library functions for issuing bypass or inline commands. These
+// functions take one or more arguments of the type cmdSyntaxPtr. The routine
+// then can figure out how long each command is supposed to be and easily add it
+// to the list.
+//
+// For ease of use, we define manifests which return pointers to appropriate
+// cmdSyntaxPtr things. But some commands also take arguments. If a single
+// argument is used, we define a macro which performs the single assignment and
+// (through the expedient of a comma expression) references the appropriate
+// pointer. For commands requiring several arguments, we actually define a
+// function to perform the assignments.
+
+#define CMD_DTRUP (cmdSyntaxPtr)(ct02) // Raise DTR
+#define CMD_DTRDN (cmdSyntaxPtr)(ct03) // Lower DTR
+#define CMD_RTSUP (cmdSyntaxPtr)(ct04) // Raise RTS
+#define CMD_RTSDN (cmdSyntaxPtr)(ct05) // Lower RTS
+#define CMD_STARTFL (cmdSyntaxPtr)(ct06) // Start Flushing Data
+
+#define CMD_DTRRTS_UP (cmdSyntaxPtr)(cc01) // Raise DTR and RTS
+#define CMD_DTRRTS_DN (cmdSyntaxPtr)(cc02) // Lower DTR and RTS
+
+// Set Baud Rate for transmit and receive
+#define CMD_SETBAUD(arg) \
+ (((cmdSyntaxPtr)(ct07))->cmd[1] = (arg),(cmdSyntaxPtr)(ct07))
+
+#define CBR_50 1
+#define CBR_75 2
+#define CBR_110 3
+#define CBR_134 4
+#define CBR_150 5
+#define CBR_200 6
+#define CBR_300 7
+#define CBR_600 8
+#define CBR_1200 9
+#define CBR_1800 10
+#define CBR_2400 11
+#define CBR_4800 12
+#define CBR_9600 13
+#define CBR_19200 14
+#define CBR_38400 15
+#define CBR_2000 16
+#define CBR_3600 17
+#define CBR_7200 18
+#define CBR_56000 19
+#define CBR_57600 20
+#define CBR_64000 21
+#define CBR_76800 22
+#define CBR_115200 23
+#define CBR_C1 24 // Custom baud rate 1
+#define CBR_C2 25 // Custom baud rate 2
+#define CBR_153600 26
+#define CBR_230400 27
+#define CBR_307200 28
+#define CBR_460800 29
+#define CBR_921600 30
+
+// Set Character size
+//
+#define CMD_SETBITS(arg) \
+ (((cmdSyntaxPtr)(ct08))->cmd[1] = (arg),(cmdSyntaxPtr)(ct08))
+
+#define CSZ_5 0
+#define CSZ_6 1
+#define CSZ_7 2
+#define CSZ_8 3
+
+// Set number of stop bits
+//
+#define CMD_SETSTOP(arg) \
+ (((cmdSyntaxPtr)(ct09))->cmd[1] = (arg),(cmdSyntaxPtr)(ct09))
+
+#define CST_1 0
+#define CST_15 1 // 1.5 stop bits
+#define CST_2 2
+
+// Set parity option
+//
+#define CMD_SETPAR(arg) \
+ (((cmdSyntaxPtr)(ct10))->cmd[1] = (arg),(cmdSyntaxPtr)(ct10))
+
+#define CSP_NP 0 // no parity
+#define CSP_OD 1 // odd parity
+#define CSP_EV 2 // Even parity
+#define CSP_SP 3 // Space parity
+#define CSP_MK 4 // Mark parity
+
+// Define xon char for transmitter flow control
+//
+#define CMD_DEF_IXON(arg) \
+ (((cmdSyntaxPtr)(ct11))->cmd[1] = (arg),(cmdSyntaxPtr)(ct11))
+
+// Define xoff char for transmitter flow control
+//
+#define CMD_DEF_IXOFF(arg) \
+ (((cmdSyntaxPtr)(ct12))->cmd[1] = (arg),(cmdSyntaxPtr)(ct12))
+
+#define CMD_STOPFL (cmdSyntaxPtr)(ct13) // Stop Flushing data
+
+// Acknowledge receipt of hotkey signal
+//
+#define CMD_HOTACK (cmdSyntaxPtr)(ct14)
+
+// Define irq level to use. Should actually be sent by library-level code, not
+// directly from user...
+//
+#define CMDVALUE_IRQ 15 // For library use at initialization. Until this command
+ // is sent, board processing doesn't really start.
+#define CMD_SET_IRQ(arg) \
+ (((cmdSyntaxPtr)(ct15))->cmd[1] = (arg),(cmdSyntaxPtr)(ct15))
+
+#define CIR_POLL 0 // No IRQ - Poll
+#define CIR_3 3 // IRQ 3
+#define CIR_4 4 // IRQ 4
+#define CIR_5 5 // IRQ 5
+#define CIR_7 7 // IRQ 7
+#define CIR_10 10 // IRQ 10
+#define CIR_11 11 // IRQ 11
+#define CIR_12 12 // IRQ 12
+#define CIR_15 15 // IRQ 15
+
+// Select transmit flow xon/xoff options
+//
+#define CMD_IXON_OPT(arg) \
+ (((cmdSyntaxPtr)(ct16))->cmd[1] = (arg),(cmdSyntaxPtr)(ct16))
+
+#define CIX_NONE 0 // Incoming Xon/Xoff characters not special
+#define CIX_XON 1 // Xoff disable, Xon enable
+#define CIX_XANY 2 // Xoff disable, any key enable
+
+// Select receive flow xon/xoff options
+//
+#define CMD_OXON_OPT(arg) \
+ (((cmdSyntaxPtr)(ct17))->cmd[1] = (arg),(cmdSyntaxPtr)(ct17))
+
+#define COX_NONE 0 // Don't send Xon/Xoff
+#define COX_XON 1 // Send xon/xoff to start/stop incoming data
+
+
+#define CMD_CTS_REP (cmdSyntaxPtr)(ct18) // Enable CTS reporting
+#define CMD_CTS_NREP (cmdSyntaxPtr)(ct19) // Disable CTS reporting
+
+#define CMD_DCD_REP (cmdSyntaxPtr)(ct20) // Enable DCD reporting
+#define CMD_DCD_NREP (cmdSyntaxPtr)(ct21) // Disable DCD reporting
+
+#define CMD_DSR_REP (cmdSyntaxPtr)(ct22) // Enable DSR reporting
+#define CMD_DSR_NREP (cmdSyntaxPtr)(ct23) // Disable DSR reporting
+
+#define CMD_RI_REP (cmdSyntaxPtr)(ct24) // Enable RI reporting
+#define CMD_RI_NREP (cmdSyntaxPtr)(ct25) // Disable RI reporting
+
+// Enable break reporting and select style
+//
+#define CMD_BRK_REP(arg) \
+ (((cmdSyntaxPtr)(ct26))->cmd[1] = (arg),(cmdSyntaxPtr)(ct26))
+
+#define CBK_STAT 0x00 // Report breaks as a status (exception,irq)
+#define CBK_NULL 0x01 // Report breaks as a good null
+#define CBK_STAT_SEQ 0x02 // Report breaks as a status AND as in-band character
+ // sequence FFh, 01h, 10h
+#define CBK_SEQ 0x03 // Report breaks as the in-band
+ //sequence FFh, 01h, 10h ONLY.
+#define CBK_FLSH 0x04 // if this bit set also flush input data
+#define CBK_POSIX 0x08 // if this bit set report as FF,0,0 sequence
+#define CBK_SINGLE 0x10 // if this bit set with CBK_SEQ or CBK_STAT_SEQ
+ //then reports single null instead of triple
+
+#define CMD_BRK_NREP (cmdSyntaxPtr)(ct27) // Disable break reporting
+
+// Specify maximum block size for received data
+//
+#define CMD_MAX_BLOCK(arg) \
+ (((cmdSyntaxPtr)(ct28))->cmd[1] = (arg),(cmdSyntaxPtr)(ct28))
+
+// -- COMMAND 29 is reserved --
+
+#define CMD_CTSFL_ENAB (cmdSyntaxPtr)(ct30) // Enable CTS flow control
+#define CMD_CTSFL_DSAB (cmdSyntaxPtr)(ct31) // Disable CTS flow control
+#define CMD_RTSFL_ENAB (cmdSyntaxPtr)(ct32) // Enable RTS flow control
+#define CMD_RTSFL_DSAB (cmdSyntaxPtr)(ct33) // Disable RTS flow control
+
+// Specify istrip option
+//
+#define CMD_ISTRIP_OPT(arg) \
+ (((cmdSyntaxPtr)(ct34))->cmd[1] = (arg),(cmdSyntaxPtr)(ct34))
+
+#define CIS_NOSTRIP 0 // Strip characters to character size
+#define CIS_STRIP 1 // Strip any 8-bit characters to 7 bits
+
+// Send a break of arg milliseconds
+//
+#define CMD_SEND_BRK(arg) \
+ (((cmdSyntaxPtr)(ct35))->cmd[1] = (arg),(cmdSyntaxPtr)(ct35))
+
+// Set error reporting mode
+//
+#define CMD_SET_ERROR(arg) \
+ (((cmdSyntaxPtr)(ct36))->cmd[1] = (arg),(cmdSyntaxPtr)(ct36))
+
+#define CSE_ESTAT 0 // Report error in a status packet
+#define CSE_NOREP 1 // Treat character as though it were good
+#define CSE_DROP 2 // Discard the character
+#define CSE_NULL 3 // Replace with a null
+#define CSE_MARK 4 // Replace with a 3-character sequence (as Unix)
+
+#define CMD_SET_REPLACEMENT(arg,ch) \
+ (((cmdSyntaxPtr)(ct36a))->cmd[1] = (arg), \
+ (((cmdSyntaxPtr)(ct36a))->cmd[2] = (ch), \
+ (cmdSyntaxPtr)(ct36a))
+
+#define CSE_REPLACE 0x8 // Replace the errored character with the
+ // replacement character defined here
+
+#define CSE_STAT_REPLACE 0x18 // Replace the errored character with the
+ // replacement character defined here AND
+ // report the error as a status packet (as in
+ // CSE_ESTAT).
+
+
+// COMMAND 37, to send flow control packets, is handled only by low-level
+// library code in response to data movement and shouldn't ever be sent by the
+// user code. See i2pack.h and the body of i2lib.c for details.
+
+// Enable on-board post-processing, using options given in oflag argument.
+// Formerly, this command was automatically preceded by a CMD_OPOST_OFF command
+// because the loadware does not permit sending back-to-back CMD_OPOST_ON
+// commands without an intervening CMD_OPOST_OFF. BUT, WE LEARN 18 MAY 92, that
+// CMD_OPOST_ON and CMD_OPOST_OFF must each be at the end of a packet (or in a
+// solo packet). This means the caller must specify separately CMD_OPOST_OFF,
+// CMD_OPOST_ON(parm) when he calls i2QueueCommands(). That function will ensure
+// each gets a separate packet. Extra CMD_OPOST_OFF's are always ok.
+//
+#define CMD_OPOST_ON(oflag) \
+ (*(USHORT *)(((cmdSyntaxPtr)(ct39))->cmd[1]) = (oflag), \
+ (cmdSyntaxPtr)(ct39))
+
+#define CMD_OPOST_OFF (cmdSyntaxPtr)(ct40) // Disable on-board post-proc
+
+#define CMD_RESUME (cmdSyntaxPtr)(ct41) // Resume: behave as though an XON
+ // were received;
+
+// Set Transmit baud rate (see command 7 for arguments)
+//
+#define CMD_SETBAUD_TX(arg) \
+ (((cmdSyntaxPtr)(ct42))->cmd[1] = (arg),(cmdSyntaxPtr)(ct42))
+
+// Set Receive baud rate (see command 7 for arguments)
+//
+#define CMD_SETBAUD_RX(arg) \
+ (((cmdSyntaxPtr)(ct43))->cmd[1] = (arg),(cmdSyntaxPtr)(ct43))
+
+// Request interrupt from board each arg milliseconds. Interrupt will specify
+// "received data", even though there may be no data present. If arg == 0,
+// disables any such interrupts.
+//
+#define CMD_PING_REQ(arg) \
+ (((cmdSyntaxPtr)(ct44))->cmd[1] = (arg),(cmdSyntaxPtr)(ct44))
+
+#define CMD_HOT_ENAB (cmdSyntaxPtr)(ct45) // Enable Hot-key checking
+#define CMD_HOT_DSAB (cmdSyntaxPtr)(ct46) // Disable Hot-key checking
+
+// COMMAND 47: Send Protocol info via Unix flags:
+// iflag = Unix tty t_iflag
+// cflag = Unix tty t_cflag
+// lflag = Unix tty t_lflag
+// See System V Unix/Xenix documentation for the meanings of the bit fields
+// within these flags
+//
+#define CMD_UNIX_FLAGS(iflag,cflag,lflag) i2cmdUnixFlags(iflag,cflag,lflag)
+
+#define CMD_DSRFL_ENAB (cmdSyntaxPtr)(ct48) // Enable DSR receiver ctrl
+#define CMD_DSRFL_DSAB (cmdSyntaxPtr)(ct49) // Disable DSR receiver ctrl
+#define CMD_DTRFL_ENAB (cmdSyntaxPtr)(ct50) // Enable DTR flow control
+#define CMD_DTRFL_DSAB (cmdSyntaxPtr)(ct51) // Disable DTR flow control
+#define CMD_BAUD_RESET (cmdSyntaxPtr)(ct52) // Reset baudrate table
+
+// COMMAND 54: Define custom rate #1
+// rate = (short) 1/10 of the desired baud rate
+//
+#define CMD_BAUD_DEF1(rate) i2cmdBaudDef(1,rate)
+
+// COMMAND 55: Define custom rate #2
+// rate = (short) 1/10 of the desired baud rate
+//
+#define CMD_BAUD_DEF2(rate) i2cmdBaudDef(2,rate)
+
+// Pause arg hundredths of seconds. (Note, this is NOT milliseconds.)
+//
+#define CMD_PAUSE(arg) \
+ (((cmdSyntaxPtr)(ct56))->cmd[1] = (arg),(cmdSyntaxPtr)(ct56))
+
+#define CMD_SUSPEND (cmdSyntaxPtr)(ct57) // Suspend output
+#define CMD_UNSUSPEND (cmdSyntaxPtr)(ct58) // Un-Suspend output
+
+// Set parity-checking options
+//
+#define CMD_PARCHK(arg) \
+ (((cmdSyntaxPtr)(ct59))->cmd[1] = (arg),(cmdSyntaxPtr)(ct59))
+
+#define CPK_ENAB 0 // Enable parity checking on input
+#define CPK_DSAB 1 // Disable parity checking on input
+
+#define CMD_BMARK_REQ (cmdSyntaxPtr)(ct60) // Bookmark request
+
+
+// Enable/Disable internal loopback mode
+//
+#define CMD_INLOOP(arg) \
+ (((cmdSyntaxPtr)(ct61))->cmd[1] = (arg),(cmdSyntaxPtr)(ct61))
+
+#define CIN_DISABLE 0 // Normal operation (default)
+#define CIN_ENABLE 1 // Internal (local) loopback
+#define CIN_REMOTE 2 // Remote loopback
+
+// Specify timeout for hotkeys: Delay will be (arg x 10) milliseconds, arg == 0
+// --> no timeout: wait forever.
+//
+#define CMD_HOT_TIME(arg) \
+ (((cmdSyntaxPtr)(ct62))->cmd[1] = (arg),(cmdSyntaxPtr)(ct62))
+
+
+// Define (outgoing) xon for receive flow control
+//
+#define CMD_DEF_OXON(arg) \
+ (((cmdSyntaxPtr)(ct63))->cmd[1] = (arg),(cmdSyntaxPtr)(ct63))
+
+// Define (outgoing) xoff for receiver flow control
+//
+#define CMD_DEF_OXOFF(arg) \
+ (((cmdSyntaxPtr)(ct64))->cmd[1] = (arg),(cmdSyntaxPtr)(ct64))
+
+// Enable/Disable RTS on transmit (1/2 duplex-style)
+//
+#define CMD_RTS_XMIT(arg) \
+ (((cmdSyntaxPtr)(ct65))->cmd[1] = (arg),(cmdSyntaxPtr)(ct65))
+
+#define CHD_DISABLE 0
+#define CHD_ENABLE 1
+
+// Set high-water-mark level (debugging use only)
+//
+#define CMD_SETHIGHWAT(arg) \
+ (((cmdSyntaxPtr)(ct66))->cmd[1] = (arg),(cmdSyntaxPtr)(ct66))
+
+// Start flushing tagged data (tag = 0-14)
+//
+#define CMD_START_SELFL(tag) \
+ (((cmdSyntaxPtr)(ct67))->cmd[1] = (tag),(cmdSyntaxPtr)(ct67))
+
+// End flushing tagged data (tag = 0-14)
+//
+#define CMD_END_SELFL(tag) \
+ (((cmdSyntaxPtr)(ct68))->cmd[1] = (tag),(cmdSyntaxPtr)(ct68))
+
+#define CMD_HWFLOW_OFF (cmdSyntaxPtr)(ct69) // Disable HW TX flow control
+#define CMD_ODSRFL_ENAB (cmdSyntaxPtr)(ct70) // Enable DSR output f/c
+#define CMD_ODSRFL_DSAB (cmdSyntaxPtr)(ct71) // Disable DSR output f/c
+#define CMD_ODCDFL_ENAB (cmdSyntaxPtr)(ct72) // Enable DCD output f/c
+#define CMD_ODCDFL_DSAB (cmdSyntaxPtr)(ct73) // Disable DCD output f/c
+
+// Set transmit interrupt load level. Count should be an even value 2-12
+//
+#define CMD_LOADLEVEL(count) \
+ (((cmdSyntaxPtr)(ct74))->cmd[1] = (count),(cmdSyntaxPtr)(ct74))
+
+// If reporting DSS changes, map to character sequence FFh, 2, MSR
+//
+#define CMD_STATDATA(arg) \
+ (((cmdSyntaxPtr)(ct75))->cmd[1] = (arg),(cmdSyntaxPtr)(ct75))
+
+#define CSTD_DISABLE// Report DSS changes as status packets only (default)
+#define CSTD_ENABLE // Report DSS changes as in-band data sequence as well as
+ // by status packet.
+
+#define CMD_BREAK_ON (cmdSyntaxPtr)(ct76)// Set break and stop xmit
+#define CMD_BREAK_OFF (cmdSyntaxPtr)(ct77)// End break and restart xmit
+#define CMD_GETFC (cmdSyntaxPtr)(ct78)// Request for flow control packet
+ // from board.
+
+// Transmit this character immediately
+//
+#define CMD_XMIT_NOW(ch) \
+ (((cmdSyntaxPtr)(ct79))->cmd[1] = (ch),(cmdSyntaxPtr)(ct79))
+
+// Set baud rate via "divisor latch"
+//
+#define CMD_DIVISOR_LATCH(which,value) \
+ (((cmdSyntaxPtr)(ct80))->cmd[1] = (which), \
+ *(USHORT *)(((cmdSyntaxPtr)(ct80))->cmd[2]) = (value), \
+ (cmdSyntaxPtr)(ct80))
+
+#define CDL_RX 1 // Set receiver rate
+#define CDL_TX 2 // Set transmit rate
+ // (CDL_TX | CDL_RX) Set both rates
+
+// Request for special diagnostic status pkt from the board.
+//
+#define CMD_GET_STATUS (cmdSyntaxPtr)(ct81)
+
+// Request time-stamped transmit character count packet.
+//
+#define CMD_GET_TXCNT (cmdSyntaxPtr)(ct82)
+
+// Request time-stamped receive character count packet.
+//
+#define CMD_GET_RXCNT (cmdSyntaxPtr)(ct83)
+
+// Request for box/board I.D. packet.
+#define CMD_GET_BOXIDS (cmdSyntaxPtr)(ct84)
+
+// Enable or disable multiple channels according to bit-mapped ushorts box 1-4
+//
+#define CMD_ENAB_MULT(enable, box1, box2, box3, box4) \
+ (((cmdSytaxPtr)(ct85))->cmd[1] = (enable), \
+ *(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[2]) = (box1), \
+ *(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[4]) = (box2), \
+ *(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[6]) = (box3), \
+ *(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[8]) = (box4), \
+ (cmdSyntaxPtr)(ct85))
+
+#define CEM_DISABLE 0
+#define CEM_ENABLE 1
+
+// Enable or disable receiver or receiver interrupts (default both enabled)
+//
+#define CMD_RCV_ENABLE(ch) \
+ (((cmdSyntaxPtr)(ct86))->cmd[1] = (ch),(cmdSyntaxPtr)(ct86))
+
+#define CRE_OFF 0 // Disable the receiver
+#define CRE_ON 1 // Enable the receiver
+#define CRE_INTOFF 2 // Disable receiver interrupts (to loadware)
+#define CRE_INTON 3 // Enable receiver interrupts (to loadware)
+
+// Starts up a hardware test process, which runs transparently, and sends a
+// STAT_HWFAIL packet in case a hardware failure is detected.
+//
+#define CMD_HW_TEST (cmdSyntaxPtr)(ct87)
+
+// Change receiver threshold and timeout value:
+// Defaults: timeout = 20mS
+// threshold count = 8 when DTRflow not in use,
+// threshold count = 5 when DTRflow in use.
+//
+#define CMD_RCV_THRESHOLD(count,ms) \
+ (((cmdSyntaxPtr)(ct88))->cmd[1] = (count), \
+ ((cmdSyntaxPtr)(ct88))->cmd[2] = (ms), \
+ (cmdSyntaxPtr)(ct88))
+
+// Makes the loadware report DSS signals for this channel immediately.
+//
+#define CMD_DSS_NOW (cmdSyntaxPtr)(ct89)
+
+// Set the receive silo parameters
+// timeout is ms idle wait until delivery (~VTIME)
+// threshold is max characters cause interrupt (~VMIN)
+//
+#define CMD_SET_SILO(timeout,threshold) \
+ (((cmdSyntaxPtr)(ct90))->cmd[1] = (timeout), \
+ ((cmdSyntaxPtr)(ct90))->cmd[2] = (threshold), \
+ (cmdSyntaxPtr)(ct90))
+
+// Set timed break in decisecond (1/10s)
+//
+#define CMD_LBREAK(ds) \
+ (((cmdSyntaxPtr)(ct91))->cmd[1] = (ds),(cmdSyntaxPtr)(ct66))
+
+
+
+#endif // I2CMD_H
diff --git a/drivers/char/ip2/i2ellis.c b/drivers/char/ip2/i2ellis.c
new file mode 100644
index 000000000000..f834d05ccc97
--- /dev/null
+++ b/drivers/char/ip2/i2ellis.c
@@ -0,0 +1,1487 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Low-level interface code for the device driver
+* (This is included source code, not a separate compilation
+* module.)
+*
+*******************************************************************************/
+//---------------------------------------------
+// Function declarations private to this module
+//---------------------------------------------
+// Functions called only indirectly through i2eBordStr entries.
+
+static int iiWriteBuf16(i2eBordStrPtr, unsigned char *, int);
+static int iiWriteBuf8(i2eBordStrPtr, unsigned char *, int);
+static int iiReadBuf16(i2eBordStrPtr, unsigned char *, int);
+static int iiReadBuf8(i2eBordStrPtr, unsigned char *, int);
+
+static unsigned short iiReadWord16(i2eBordStrPtr);
+static unsigned short iiReadWord8(i2eBordStrPtr);
+static void iiWriteWord16(i2eBordStrPtr, unsigned short);
+static void iiWriteWord8(i2eBordStrPtr, unsigned short);
+
+static int iiWaitForTxEmptyII(i2eBordStrPtr, int);
+static int iiWaitForTxEmptyIIEX(i2eBordStrPtr, int);
+static int iiTxMailEmptyII(i2eBordStrPtr);
+static int iiTxMailEmptyIIEX(i2eBordStrPtr);
+static int iiTrySendMailII(i2eBordStrPtr, unsigned char);
+static int iiTrySendMailIIEX(i2eBordStrPtr, unsigned char);
+
+static unsigned short iiGetMailII(i2eBordStrPtr);
+static unsigned short iiGetMailIIEX(i2eBordStrPtr);
+
+static void iiEnableMailIrqII(i2eBordStrPtr);
+static void iiEnableMailIrqIIEX(i2eBordStrPtr);
+static void iiWriteMaskII(i2eBordStrPtr, unsigned char);
+static void iiWriteMaskIIEX(i2eBordStrPtr, unsigned char);
+
+static void ii2DelayTimer(unsigned int);
+static void ii2DelayWakeup(unsigned long id);
+static void ii2Nop(void);
+
+//***************
+//* Static Data *
+//***************
+
+static int ii2Safe; // Safe I/O address for delay routine
+
+static int iiDelayed; // Set when the iiResetDelay function is
+ // called. Cleared when ANY board is reset.
+static struct timer_list * pDelayTimer; // Used by iiDelayTimer
+static wait_queue_head_t pDelayWait; // Used by iiDelayTimer
+static rwlock_t Dl_spinlock;
+
+//********
+//* Code *
+//********
+
+//=======================================================
+// Initialization Routines
+//
+// iiSetAddress
+// iiReset
+// iiResetDelay
+// iiInitialize
+//=======================================================
+
+//******************************************************************************
+// Function: iiEllisInit()
+// Parameters: None
+//
+// Returns: Nothing
+//
+// Description:
+//
+// This routine performs any required initialization of the iiEllis subsystem.
+//
+//******************************************************************************
+static void
+iiEllisInit(void)
+{
+ pDelayTimer = kmalloc ( sizeof (struct timer_list), GFP_KERNEL );
+ init_timer(pDelayTimer);
+ init_waitqueue_head(&pDelayWait);
+ LOCK_INIT(&Dl_spinlock);
+}
+
+//******************************************************************************
+// Function: iiEllisCleanup()
+// Parameters: None
+//
+// Returns: Nothing
+//
+// Description:
+//
+// This routine performs any required cleanup of the iiEllis subsystem.
+//
+//******************************************************************************
+static void
+iiEllisCleanup(void)
+{
+ if ( pDelayTimer != NULL ) {
+ kfree ( pDelayTimer );
+ }
+}
+
+//******************************************************************************
+// Function: iiSetAddress(pB, address, delay)
+// Parameters: pB - pointer to the board structure
+// address - the purported I/O address of the board
+// delay - pointer to the 1-ms delay function to use
+// in this and any future operations to this board
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// This routine (roughly) checks for address validity, sets the i2eValid OK and
+// sets the state to II_STATE_COLD which means that we haven't even sent a reset
+// yet.
+//
+//******************************************************************************
+static int
+iiSetAddress( i2eBordStrPtr pB, int address, delayFunc_t delay )
+{
+ // Should any failure occur before init is finished...
+ pB->i2eValid = I2E_INCOMPLETE;
+
+ // Cannot check upper limit except extremely: Might be microchannel
+ // Address must be on an 8-byte boundary
+
+ if ((unsigned int)address <= 0x100
+ || (unsigned int)address >= 0xfff8
+ || (address & 0x7)
+ )
+ {
+ COMPLETE(pB,I2EE_BADADDR);
+ }
+
+ // Initialize accelerators
+ pB->i2eBase = address;
+ pB->i2eData = address + FIFO_DATA;
+ pB->i2eStatus = address + FIFO_STATUS;
+ pB->i2ePointer = address + FIFO_PTR;
+ pB->i2eXMail = address + FIFO_MAIL;
+ pB->i2eXMask = address + FIFO_MASK;
+
+ // Initialize i/o address for ii2DelayIO
+ ii2Safe = address + FIFO_NOP;
+
+ // Initialize the delay routine
+ pB->i2eDelay = ((delay != (delayFunc_t)NULL) ? delay : (delayFunc_t)ii2Nop);
+
+ pB->i2eValid = I2E_MAGIC;
+ pB->i2eState = II_STATE_COLD;
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiReset(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Attempts to reset the board (see also i2hw.h). Normally, we would use this to
+// reset a board immediately after iiSetAddress(), but it is valid to reset a
+// board from any state, say, in order to change or re-load loadware. (Under
+// such circumstances, no reason to re-run iiSetAddress(), which is why it is a
+// separate routine and not included in this routine.
+//
+//******************************************************************************
+static int
+iiReset(i2eBordStrPtr pB)
+{
+ // Magic number should be set, else even the address is suspect
+ if (pB->i2eValid != I2E_MAGIC)
+ {
+ COMPLETE(pB, I2EE_BADMAGIC);
+ }
+
+ OUTB(pB->i2eBase + FIFO_RESET, 0); // Any data will do
+ iiDelay(pB, 50); // Pause between resets
+ OUTB(pB->i2eBase + FIFO_RESET, 0); // Second reset
+
+ // We must wait before even attempting to read anything from the FIFO: the
+ // board's P.O.S.T may actually attempt to read and write its end of the
+ // FIFO in order to check flags, loop back (where supported), etc. On
+ // completion of this testing it would reset the FIFO, and on completion
+ // of all // P.O.S.T., write the message. We must not mistake data which
+ // might have been sent for testing as part of the reset message. To
+ // better utilize time, say, when resetting several boards, we allow the
+ // delay to be performed externally; in this way the caller can reset
+ // several boards, delay a single time, then call the initialization
+ // routine for all.
+
+ pB->i2eState = II_STATE_RESET;
+
+ iiDelayed = 0; // i.e., the delay routine hasn't been called since the most
+ // recent reset.
+
+ // Ensure anything which would have been of use to standard loadware is
+ // blanked out, since board has now forgotten everything!.
+
+ pB->i2eUsingIrq = IRQ_UNDEFINED; // Not set up to use an interrupt yet
+ pB->i2eWaitingForEmptyFifo = 0;
+ pB->i2eOutMailWaiting = 0;
+ pB->i2eChannelPtr = NULL;
+ pB->i2eChannelCnt = 0;
+
+ pB->i2eLeadoffWord[0] = 0;
+ pB->i2eFifoInInts = 0;
+ pB->i2eFifoOutInts = 0;
+ pB->i2eFatalTrap = NULL;
+ pB->i2eFatal = 0;
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiResetDelay(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Using the delay defined in board structure, waits two seconds (for board to
+// reset).
+//
+//******************************************************************************
+static int
+iiResetDelay(i2eBordStrPtr pB)
+{
+ if (pB->i2eValid != I2E_MAGIC) {
+ COMPLETE(pB, I2EE_BADMAGIC);
+ }
+ if (pB->i2eState != II_STATE_RESET) {
+ COMPLETE(pB, I2EE_BADSTATE);
+ }
+ iiDelay(pB,2000); /* Now we wait for two seconds. */
+ iiDelayed = 1; /* Delay has been called: ok to initialize */
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiInitialize(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Attempts to read the Power-on reset message. Initializes any remaining fields
+// in the pB structure.
+//
+// This should be called as the third step of a process beginning with
+// iiReset(), then iiResetDelay(). This routine checks to see that the structure
+// is "valid" and in the reset state, also confirms that the delay routine has
+// been called since the latest reset (to any board! overly strong!).
+//
+//******************************************************************************
+static int
+iiInitialize(i2eBordStrPtr pB)
+{
+ int itemp;
+ unsigned char c;
+ unsigned short utemp;
+ unsigned int ilimit;
+
+ if (pB->i2eValid != I2E_MAGIC)
+ {
+ COMPLETE(pB, I2EE_BADMAGIC);
+ }
+
+ if (pB->i2eState != II_STATE_RESET || !iiDelayed)
+ {
+ COMPLETE(pB, I2EE_BADSTATE);
+ }
+
+ // In case there is a failure short of our completely reading the power-up
+ // message.
+ pB->i2eValid = I2E_INCOMPLETE;
+
+
+ // Now attempt to read the message.
+
+ for (itemp = 0; itemp < sizeof(porStr); itemp++)
+ {
+ // We expect the entire message is ready.
+ if (HAS_NO_INPUT(pB))
+ {
+ pB->i2ePomSize = itemp;
+ COMPLETE(pB, I2EE_PORM_SHORT);
+ }
+
+ pB->i2ePom.c[itemp] = c = BYTE_FROM(pB);
+
+ // We check the magic numbers as soon as they are supposed to be read
+ // (rather than after) to minimize effect of reading something we
+ // already suspect can't be "us".
+ if ( (itemp == POR_1_INDEX && c != POR_MAGIC_1) ||
+ (itemp == POR_2_INDEX && c != POR_MAGIC_2))
+ {
+ pB->i2ePomSize = itemp+1;
+ COMPLETE(pB, I2EE_BADMAGIC);
+ }
+ }
+
+ pB->i2ePomSize = itemp;
+
+ // Ensure that this was all the data...
+ if (HAS_INPUT(pB))
+ COMPLETE(pB, I2EE_PORM_LONG);
+
+ // For now, we'll fail to initialize if P.O.S.T reports bad chip mapper:
+ // Implying we will not be able to download any code either: That's ok: the
+ // condition is pretty explicit.
+ if (pB->i2ePom.e.porDiag1 & POR_BAD_MAPPER)
+ {
+ COMPLETE(pB, I2EE_POSTERR);
+ }
+
+ // Determine anything which must be done differently depending on the family
+ // of boards!
+ switch (pB->i2ePom.e.porID & POR_ID_FAMILY)
+ {
+ case POR_ID_FII: // IntelliPort-II
+
+ pB->i2eFifoStyle = FIFO_II;
+ pB->i2eFifoSize = 512; // 512 bytes, always
+ pB->i2eDataWidth16 = NO;
+
+ pB->i2eMaxIrq = 15; // Because board cannot tell us it is in an 8-bit
+ // slot, we do allow it to be done (documentation!)
+
+ pB->i2eGoodMap[1] =
+ pB->i2eGoodMap[2] =
+ pB->i2eGoodMap[3] =
+ pB->i2eChannelMap[1] =
+ pB->i2eChannelMap[2] =
+ pB->i2eChannelMap[3] = 0;
+
+ switch (pB->i2ePom.e.porID & POR_ID_SIZE)
+ {
+ case POR_ID_II_4:
+ pB->i2eGoodMap[0] =
+ pB->i2eChannelMap[0] = 0x0f; // four-port
+
+ // Since porPorts1 is based on the Hardware ID register, the numbers
+ // should always be consistent for IntelliPort-II. Ditto below...
+ if (pB->i2ePom.e.porPorts1 != 4)
+ {
+ COMPLETE(pB, I2EE_INCONSIST);
+ }
+ break;
+
+ case POR_ID_II_8:
+ case POR_ID_II_8R:
+ pB->i2eGoodMap[0] =
+ pB->i2eChannelMap[0] = 0xff; // Eight port
+ if (pB->i2ePom.e.porPorts1 != 8)
+ {
+ COMPLETE(pB, I2EE_INCONSIST);
+ }
+ break;
+
+ case POR_ID_II_6:
+ pB->i2eGoodMap[0] =
+ pB->i2eChannelMap[0] = 0x3f; // Six Port
+ if (pB->i2ePom.e.porPorts1 != 6)
+ {
+ COMPLETE(pB, I2EE_INCONSIST);
+ }
+ break;
+ }
+
+ // Fix up the "good channel list based on any errors reported.
+ if (pB->i2ePom.e.porDiag1 & POR_BAD_UART1)
+ {
+ pB->i2eGoodMap[0] &= ~0x0f;
+ }
+
+ if (pB->i2ePom.e.porDiag1 & POR_BAD_UART2)
+ {
+ pB->i2eGoodMap[0] &= ~0xf0;
+ }
+
+ break; // POR_ID_FII case
+
+ case POR_ID_FIIEX: // IntelliPort-IIEX
+
+ pB->i2eFifoStyle = FIFO_IIEX;
+
+ itemp = pB->i2ePom.e.porFifoSize;
+
+ // Implicit assumption that fifo would not grow beyond 32k,
+ // nor would ever be less than 256.
+
+ if (itemp < 8 || itemp > 15)
+ {
+ COMPLETE(pB, I2EE_INCONSIST);
+ }
+ pB->i2eFifoSize = (1 << itemp);
+
+ // These are based on what P.O.S.T thinks should be there, based on
+ // box ID registers
+ ilimit = pB->i2ePom.e.porNumBoxes;
+ if (ilimit > ABS_MAX_BOXES)
+ {
+ ilimit = ABS_MAX_BOXES;
+ }
+
+ // For as many boxes as EXIST, gives the type of box.
+ // Added 8/6/93: check for the ISA-4 (asic) which looks like an
+ // expandable but for whom "8 or 16?" is not the right question.
+
+ utemp = pB->i2ePom.e.porFlags;
+ if (utemp & POR_CEX4)
+ {
+ pB->i2eChannelMap[0] = 0x000f;
+ } else {
+ utemp &= POR_BOXES;
+ for (itemp = 0; itemp < ilimit; itemp++)
+ {
+ pB->i2eChannelMap[itemp] =
+ ((utemp & POR_BOX_16) ? 0xffff : 0x00ff);
+ utemp >>= 1;
+ }
+ }
+
+ // These are based on what P.O.S.T actually found.
+
+ utemp = (pB->i2ePom.e.porPorts2 << 8) + pB->i2ePom.e.porPorts1;
+
+ for (itemp = 0; itemp < ilimit; itemp++)
+ {
+ pB->i2eGoodMap[itemp] = 0;
+ if (utemp & 1) pB->i2eGoodMap[itemp] |= 0x000f;
+ if (utemp & 2) pB->i2eGoodMap[itemp] |= 0x00f0;
+ if (utemp & 4) pB->i2eGoodMap[itemp] |= 0x0f00;
+ if (utemp & 8) pB->i2eGoodMap[itemp] |= 0xf000;
+ utemp >>= 4;
+ }
+
+ // Now determine whether we should transfer in 8 or 16-bit mode.
+ switch (pB->i2ePom.e.porBus & (POR_BUS_SLOT16 | POR_BUS_DIP16) )
+ {
+ case POR_BUS_SLOT16 | POR_BUS_DIP16:
+ pB->i2eDataWidth16 = YES;
+ pB->i2eMaxIrq = 15;
+ break;
+
+ case POR_BUS_SLOT16:
+ pB->i2eDataWidth16 = NO;
+ pB->i2eMaxIrq = 15;
+ break;
+
+ case 0:
+ case POR_BUS_DIP16: // In an 8-bit slot, DIP switch don't care.
+ default:
+ pB->i2eDataWidth16 = NO;
+ pB->i2eMaxIrq = 7;
+ break;
+ }
+ break; // POR_ID_FIIEX case
+
+ default: // Unknown type of board
+ COMPLETE(pB, I2EE_BAD_FAMILY);
+ break;
+ } // End the switch based on family
+
+ // Temporarily, claim there is no room in the outbound fifo.
+ // We will maintain this whenever we check for an empty outbound FIFO.
+ pB->i2eFifoRemains = 0;
+
+ // Now, based on the bus type, should we expect to be able to re-configure
+ // interrupts (say, for testing purposes).
+ switch (pB->i2ePom.e.porBus & POR_BUS_TYPE)
+ {
+ case POR_BUS_T_ISA:
+ case POR_BUS_T_UNK: // If the type of bus is undeclared, assume ok.
+ pB->i2eChangeIrq = YES;
+ break;
+ case POR_BUS_T_MCA:
+ case POR_BUS_T_EISA:
+ pB->i2eChangeIrq = NO;
+ break;
+ default:
+ COMPLETE(pB, I2EE_BADBUS);
+ }
+
+ if (pB->i2eDataWidth16 == YES)
+ {
+ pB->i2eWriteBuf = iiWriteBuf16;
+ pB->i2eReadBuf = iiReadBuf16;
+ pB->i2eWriteWord = iiWriteWord16;
+ pB->i2eReadWord = iiReadWord16;
+ } else {
+ pB->i2eWriteBuf = iiWriteBuf8;
+ pB->i2eReadBuf = iiReadBuf8;
+ pB->i2eWriteWord = iiWriteWord8;
+ pB->i2eReadWord = iiReadWord8;
+ }
+
+ switch(pB->i2eFifoStyle)
+ {
+ case FIFO_II:
+ pB->i2eWaitForTxEmpty = iiWaitForTxEmptyII;
+ pB->i2eTxMailEmpty = iiTxMailEmptyII;
+ pB->i2eTrySendMail = iiTrySendMailII;
+ pB->i2eGetMail = iiGetMailII;
+ pB->i2eEnableMailIrq = iiEnableMailIrqII;
+ pB->i2eWriteMask = iiWriteMaskII;
+
+ break;
+
+ case FIFO_IIEX:
+ pB->i2eWaitForTxEmpty = iiWaitForTxEmptyIIEX;
+ pB->i2eTxMailEmpty = iiTxMailEmptyIIEX;
+ pB->i2eTrySendMail = iiTrySendMailIIEX;
+ pB->i2eGetMail = iiGetMailIIEX;
+ pB->i2eEnableMailIrq = iiEnableMailIrqIIEX;
+ pB->i2eWriteMask = iiWriteMaskIIEX;
+
+ break;
+
+ default:
+ COMPLETE(pB, I2EE_INCONSIST);
+ }
+
+ // Initialize state information.
+ pB->i2eState = II_STATE_READY; // Ready to load loadware.
+
+ // Some Final cleanup:
+ // For some boards, the bootstrap firmware may perform some sort of test
+ // resulting in a stray character pending in the incoming mailbox. If one is
+ // there, it should be read and discarded, especially since for the standard
+ // firmware, it's the mailbox that interrupts the host.
+
+ pB->i2eStartMail = iiGetMail(pB);
+
+ // Throw it away and clear the mailbox structure element
+ pB->i2eStartMail = NO_MAIL_HERE;
+
+ // Everything is ok now, return with good status/
+
+ pB->i2eValid = I2E_MAGIC;
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//=======================================================
+// Delay Routines
+//
+// iiDelayIO
+// iiNop
+//=======================================================
+
+static void
+ii2DelayWakeup(unsigned long id)
+{
+ wake_up_interruptible ( &pDelayWait );
+}
+
+//******************************************************************************
+// Function: ii2DelayTimer(mseconds)
+// Parameters: mseconds - number of milliseconds to delay
+//
+// Returns: Nothing
+//
+// Description:
+//
+// This routine delays for approximately mseconds milliseconds and is intended
+// to be called indirectly through i2Delay field in i2eBordStr. It uses the
+// Linux timer_list mechanism.
+//
+// The Linux timers use a unit called "jiffies" which are 10mS in the Intel
+// architecture. This function rounds the delay period up to the next "jiffy".
+// In the Alpha architecture the "jiffy" is 1mS, but this driver is not intended
+// for Alpha platforms at this time.
+//
+//******************************************************************************
+static void
+ii2DelayTimer(unsigned int mseconds)
+{
+ wait_queue_t wait;
+
+ init_waitqueue_entry(&wait, current);
+
+ init_timer ( pDelayTimer );
+
+ add_wait_queue(&pDelayWait, &wait);
+
+ set_current_state( TASK_INTERRUPTIBLE );
+
+ pDelayTimer->expires = jiffies + ( mseconds + 9 ) / 10;
+ pDelayTimer->function = ii2DelayWakeup;
+ pDelayTimer->data = 0;
+
+ add_timer ( pDelayTimer );
+
+ schedule();
+
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pDelayWait, &wait);
+
+ del_timer ( pDelayTimer );
+}
+
+#if 0
+//static void ii2DelayIO(unsigned int);
+//******************************************************************************
+// !!! Not Used, this is DOS crap, some of you young folks may be interested in
+// in how things were done in the stone age of caculating machines !!!
+// Function: ii2DelayIO(mseconds)
+// Parameters: mseconds - number of milliseconds to delay
+//
+// Returns: Nothing
+//
+// Description:
+//
+// This routine delays for approximately mseconds milliseconds and is intended
+// to be called indirectly through i2Delay field in i2eBordStr. It is intended
+// for use where a clock-based function is impossible: for example, DOS drivers.
+//
+// This function uses the IN instruction to place bounds on the timing and
+// assumes that ii2Safe has been set. This is because I/O instructions are not
+// subject to caching and will therefore take a certain minimum time. To ensure
+// the delay is at least long enough on fast machines, it is based on some
+// fastest-case calculations. On slower machines this may cause VERY long
+// delays. (3 x fastest case). In the fastest case, everything is cached except
+// the I/O instruction itself.
+//
+// Timing calculations:
+// The fastest bus speed for I/O operations is likely to be 10 MHz. The I/O
+// operation in question is a byte operation to an odd address. For 8-bit
+// operations, the architecture generally enforces two wait states. At 10 MHz, a
+// single cycle time is 100nS. A read operation at two wait states takes 6
+// cycles for a total time of 600nS. Therefore approximately 1666 iterations
+// would be required to generate a single millisecond delay. The worst
+// (reasonable) case would be an 8MHz system with no cacheing. In this case, the
+// I/O instruction would take 125nS x 6 cyles = 750 nS. More importantly, code
+// fetch of other instructions in the loop would take time (zero wait states,
+// however) and would be hard to estimate. This is minimized by using in-line
+// assembler for the in inner loop of IN instructions. This consists of just a
+// few bytes. So we'll guess about four code fetches per loop. Each code fetch
+// should take four cycles, so we have 125nS * 8 = 1000nS. Worst case then is
+// that what should have taken 1 mS takes instead 1666 * (1750) = 2.9 mS.
+//
+// So much for theoretical timings: results using 1666 value on some actual
+// machines:
+// IBM 286 6MHz 3.15 mS
+// Zenith 386 33MHz 2.45 mS
+// (brandX) 386 33MHz 1.90 mS (has cache)
+// (brandY) 486 33MHz 2.35 mS
+// NCR 486 ?? 1.65 mS (microchannel)
+//
+// For most machines, it is probably safe to scale this number back (remember,
+// for robust operation use an actual timed delay if possible), so we are using
+// a value of 1190. This yields 1.17 mS for the fastest machine in our sample,
+// 1.75 mS for typical 386 machines, and 2.25 mS the absolute slowest machine.
+//
+// 1/29/93:
+// The above timings are too slow. Actual cycle times might be faster. ISA cycle
+// times could approach 500 nS, and ...
+// The IBM model 77 being microchannel has no wait states for 8-bit reads and
+// seems to be accessing the I/O at 440 nS per access (from start of one to
+// start of next). This would imply we need 1000/.440 = 2272 iterations to
+// guarantee we are fast enough. In actual testing, we see that 2 * 1190 are in
+// fact enough. For diagnostics, we keep the level at 1190, but developers note
+// this needs tuning.
+//
+// Safe assumption: 2270 i/o reads = 1 millisecond
+//
+//******************************************************************************
+
+
+static int ii2DelValue = 1190; // See timing calculations below
+ // 1666 for fastest theoretical machine
+ // 1190 safe for most fast 386 machines
+ // 1000 for fastest machine tested here
+ // 540 (sic) for AT286/6Mhz
+static void
+ii2DelayIO(unsigned int mseconds)
+{
+ if (!ii2Safe)
+ return; /* Do nothing if this variable uninitialized */
+
+ while(mseconds--) {
+ int i = ii2DelValue;
+ while ( i-- ) {
+ INB ( ii2Safe );
+ }
+ }
+}
+#endif
+
+//******************************************************************************
+// Function: ii2Nop()
+// Parameters: None
+//
+// Returns: Nothing
+//
+// Description:
+//
+// iiInitialize will set i2eDelay to this if the delay parameter is NULL. This
+// saves checking for a NULL pointer at every call.
+//******************************************************************************
+static void
+ii2Nop(void)
+{
+ return; // no mystery here
+}
+
+//=======================================================
+// Routines which are available in 8/16-bit versions, or
+// in different fifo styles. These are ALL called
+// indirectly through the board structure.
+//=======================================================
+
+//******************************************************************************
+// Function: iiWriteBuf16(pB, address, count)
+// Parameters: pB - pointer to board structure
+// address - address of data to write
+// count - number of data bytes to write
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes 'count' bytes from 'address' to the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// sent (identity unknown...). Uses 16-bit (word) operations. Is called
+// indirectly through pB->i2eWriteBuf.
+//
+//******************************************************************************
+static int
+iiWriteBuf16(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+ // Rudimentary sanity checking here.
+ if (pB->i2eValid != I2E_MAGIC)
+ COMPLETE(pB, I2EE_INVALID);
+
+ OUTSW ( pB->i2eData, address, count);
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiWriteBuf8(pB, address, count)
+// Parameters: pB - pointer to board structure
+// address - address of data to write
+// count - number of data bytes to write
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes 'count' bytes from 'address' to the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// sent (identity unknown...). This is to be consistent with the 16-bit version.
+// Uses 8-bit (byte) operations. Is called indirectly through pB->i2eWriteBuf.
+//
+//******************************************************************************
+static int
+iiWriteBuf8(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+ /* Rudimentary sanity checking here */
+ if (pB->i2eValid != I2E_MAGIC)
+ COMPLETE(pB, I2EE_INVALID);
+
+ OUTSB ( pB->i2eData, address, count );
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiReadBuf16(pB, address, count)
+// Parameters: pB - pointer to board structure
+// address - address to put data read
+// count - number of data bytes to read
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Reads 'count' bytes into 'address' from the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// received (identity unknown...). Uses 16-bit (word) operations. Is called
+// indirectly through pB->i2eReadBuf.
+//
+//******************************************************************************
+static int
+iiReadBuf16(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+ // Rudimentary sanity checking here.
+ if (pB->i2eValid != I2E_MAGIC)
+ COMPLETE(pB, I2EE_INVALID);
+
+ INSW ( pB->i2eData, address, count);
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiReadBuf8(pB, address, count)
+// Parameters: pB - pointer to board structure
+// address - address to put data read
+// count - number of data bytes to read
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Reads 'count' bytes into 'address' from the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// received (identity unknown...). This to match the 16-bit behaviour. Uses
+// 8-bit (byte) operations. Is called indirectly through pB->i2eReadBuf.
+//
+//******************************************************************************
+static int
+iiReadBuf8(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+ // Rudimentary sanity checking here.
+ if (pB->i2eValid != I2E_MAGIC)
+ COMPLETE(pB, I2EE_INVALID);
+
+ INSB ( pB->i2eData, address, count);
+
+ COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function: iiReadWord16(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Returns the word read from the data fifo specified by the board-structure
+// pointer pB. Uses a 16-bit operation. Is called indirectly through
+// pB->i2eReadWord.
+//
+//******************************************************************************
+static unsigned short
+iiReadWord16(i2eBordStrPtr pB)
+{
+ return (unsigned short)( INW(pB->i2eData) );
+}
+
+//******************************************************************************
+// Function: iiReadWord8(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Returns the word read from the data fifo specified by the board-structure
+// pointer pB. Uses two 8-bit operations. Bytes are assumed to be LSB first. Is
+// called indirectly through pB->i2eReadWord.
+//
+//******************************************************************************
+static unsigned short
+iiReadWord8(i2eBordStrPtr pB)
+{
+ unsigned short urs;
+
+ urs = INB ( pB->i2eData );
+
+ return ( ( INB ( pB->i2eData ) << 8 ) | urs );
+}
+
+//******************************************************************************
+// Function: iiWriteWord16(pB, value)
+// Parameters: pB - pointer to board structure
+// value - data to write
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes the word 'value' to the data fifo specified by the board-structure
+// pointer pB. Uses 16-bit operation. Is called indirectly through
+// pB->i2eWriteWord.
+//
+//******************************************************************************
+static void
+iiWriteWord16(i2eBordStrPtr pB, unsigned short value)
+{
+ WORD_TO(pB, (int)value);
+}
+
+//******************************************************************************
+// Function: iiWriteWord8(pB, value)
+// Parameters: pB - pointer to board structure
+// value - data to write
+//
+// Returns: True if everything appears copacetic.
+// False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes the word 'value' to the data fifo specified by the board-structure
+// pointer pB. Uses two 8-bit operations (writes LSB first). Is called
+// indirectly through pB->i2eWriteWord.
+//
+//******************************************************************************
+static void
+iiWriteWord8(i2eBordStrPtr pB, unsigned short value)
+{
+ BYTE_TO(pB, (char)value);
+ BYTE_TO(pB, (char)(value >> 8) );
+}
+
+//******************************************************************************
+// Function: iiWaitForTxEmptyII(pB, mSdelay)
+// Parameters: pB - pointer to board structure
+// mSdelay - period to wait before returning
+//
+// Returns: True if the FIFO is empty.
+// False if it not empty in the required time: the pB->i2eError
+// field has the error.
+//
+// Description:
+//
+// Waits up to "mSdelay" milliseconds for the outgoing FIFO to become empty; if
+// not empty by the required time, returns false and error in pB->i2eError,
+// otherwise returns true.
+//
+// mSdelay == 0 is taken to mean must be empty on the first test.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+// Note this routine is organized so that if status is ok there is no delay at
+// all called either before or after the test. Is called indirectly through
+// pB->i2eWaitForTxEmpty.
+//
+//******************************************************************************
+static int
+iiWaitForTxEmptyII(i2eBordStrPtr pB, int mSdelay)
+{
+ unsigned long flags;
+ int itemp;
+
+ for (;;)
+ {
+ // This routine hinges on being able to see the "other" status register
+ // (as seen by the local processor). His incoming fifo is our outgoing
+ // FIFO.
+ //
+ // By the nature of this routine, you would be using this as part of a
+ // larger atomic context: i.e., you would use this routine to ensure the
+ // fifo empty, then act on this information. Between these two halves,
+ // you will generally not want to service interrupts or in any way
+ // disrupt the assumptions implicit in the larger context.
+ //
+ // Even worse, however, this routine "shifts" the status register to
+ // point to the local status register which is not the usual situation.
+ // Therefore for extra safety, we force the critical section to be
+ // completely atomic, and pick up after ourselves before allowing any
+ // interrupts of any kind.
+
+
+ WRITE_LOCK_IRQSAVE(&Dl_spinlock,flags)
+ OUTB(pB->i2ePointer, SEL_COMMAND);
+ OUTB(pB->i2ePointer, SEL_CMD_SH);
+
+ itemp = INB(pB->i2eStatus);
+
+ OUTB(pB->i2ePointer, SEL_COMMAND);
+ OUTB(pB->i2ePointer, SEL_CMD_UNSH);
+
+ if (itemp & ST_IN_EMPTY)
+ {
+ UPDATE_FIFO_ROOM(pB);
+ WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+ COMPLETE(pB, I2EE_GOOD);
+ }
+
+ WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+
+ if (mSdelay-- == 0)
+ break;
+
+ iiDelay(pB, 1); /* 1 mS granularity on checking condition */
+ }
+ COMPLETE(pB, I2EE_TXE_TIME);
+}
+
+//******************************************************************************
+// Function: iiWaitForTxEmptyIIEX(pB, mSdelay)
+// Parameters: pB - pointer to board structure
+// mSdelay - period to wait before returning
+//
+// Returns: True if the FIFO is empty.
+// False if it not empty in the required time: the pB->i2eError
+// field has the error.
+//
+// Description:
+//
+// Waits up to "mSdelay" milliseconds for the outgoing FIFO to become empty; if
+// not empty by the required time, returns false and error in pB->i2eError,
+// otherwise returns true.
+//
+// mSdelay == 0 is taken to mean must be empty on the first test.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+// Note this routine is organized so that if status is ok there is no delay at
+// all called either before or after the test. Is called indirectly through
+// pB->i2eWaitForTxEmpty.
+//
+//******************************************************************************
+static int
+iiWaitForTxEmptyIIEX(i2eBordStrPtr pB, int mSdelay)
+{
+ unsigned long flags;
+
+ for (;;)
+ {
+ // By the nature of this routine, you would be using this as part of a
+ // larger atomic context: i.e., you would use this routine to ensure the
+ // fifo empty, then act on this information. Between these two halves,
+ // you will generally not want to service interrupts or in any way
+ // disrupt the assumptions implicit in the larger context.
+
+ WRITE_LOCK_IRQSAVE(&Dl_spinlock,flags)
+
+ if (INB(pB->i2eStatus) & STE_OUT_MT) {
+ UPDATE_FIFO_ROOM(pB);
+ WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+ COMPLETE(pB, I2EE_GOOD);
+ }
+ WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+
+ if (mSdelay-- == 0)
+ break;
+
+ iiDelay(pB, 1); // 1 mS granularity on checking condition
+ }
+ COMPLETE(pB, I2EE_TXE_TIME);
+}
+
+//******************************************************************************
+// Function: iiTxMailEmptyII(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: True if the transmit mailbox is empty.
+// False if it not empty.
+//
+// Description:
+//
+// Returns true or false according to whether the transmit mailbox is empty (and
+// therefore able to accept more mail)
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static int
+iiTxMailEmptyII(i2eBordStrPtr pB)
+{
+ int port = pB->i2ePointer;
+ OUTB ( port, SEL_OUTMAIL );
+ return ( INB(port) == 0 );
+}
+
+//******************************************************************************
+// Function: iiTxMailEmptyIIEX(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: True if the transmit mailbox is empty.
+// False if it not empty.
+//
+// Description:
+//
+// Returns true or false according to whether the transmit mailbox is empty (and
+// therefore able to accept more mail)
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static int
+iiTxMailEmptyIIEX(i2eBordStrPtr pB)
+{
+ return !(INB(pB->i2eStatus) & STE_OUT_MAIL);
+}
+
+//******************************************************************************
+// Function: iiTrySendMailII(pB,mail)
+// Parameters: pB - pointer to board structure
+// mail - value to write to mailbox
+//
+// Returns: True if the transmit mailbox is empty, and mail is sent.
+// False if it not empty.
+//
+// Description:
+//
+// If outgoing mailbox is empty, sends mail and returns true. If outgoing
+// mailbox is not empty, returns false.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static int
+iiTrySendMailII(i2eBordStrPtr pB, unsigned char mail)
+{
+ int port = pB->i2ePointer;
+
+ OUTB(port, SEL_OUTMAIL);
+ if (INB(port) == 0) {
+ OUTB(port, SEL_OUTMAIL);
+ OUTB(port, mail);
+ return 1;
+ }
+ return 0;
+}
+
+//******************************************************************************
+// Function: iiTrySendMailIIEX(pB,mail)
+// Parameters: pB - pointer to board structure
+// mail - value to write to mailbox
+//
+// Returns: True if the transmit mailbox is empty, and mail is sent.
+// False if it not empty.
+//
+// Description:
+//
+// If outgoing mailbox is empty, sends mail and returns true. If outgoing
+// mailbox is not empty, returns false.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static int
+iiTrySendMailIIEX(i2eBordStrPtr pB, unsigned char mail)
+{
+ if(INB(pB->i2eStatus) & STE_OUT_MAIL) {
+ return 0;
+ }
+ OUTB(pB->i2eXMail, mail);
+ return 1;
+}
+
+//******************************************************************************
+// Function: iiGetMailII(pB,mail)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Mailbox data or NO_MAIL_HERE.
+//
+// Description:
+//
+// If no mail available, returns NO_MAIL_HERE otherwise returns the data from
+// the mailbox, which is guaranteed != NO_MAIL_HERE.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static unsigned short
+iiGetMailII(i2eBordStrPtr pB)
+{
+ if (HAS_MAIL(pB)) {
+ OUTB(pB->i2ePointer, SEL_INMAIL);
+ return INB(pB->i2ePointer);
+ } else {
+ return NO_MAIL_HERE;
+ }
+}
+
+//******************************************************************************
+// Function: iiGetMailIIEX(pB,mail)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Mailbox data or NO_MAIL_HERE.
+//
+// Description:
+//
+// If no mail available, returns NO_MAIL_HERE otherwise returns the data from
+// the mailbox, which is guaranteed != NO_MAIL_HERE.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static unsigned short
+iiGetMailIIEX(i2eBordStrPtr pB)
+{
+ if (HAS_MAIL(pB)) {
+ return INB(pB->i2eXMail);
+ } else {
+ return NO_MAIL_HERE;
+ }
+}
+
+//******************************************************************************
+// Function: iiEnableMailIrqII(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Nothing
+//
+// Description:
+//
+// Enables board to interrupt host (only) by writing to host's in-bound mailbox.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static void
+iiEnableMailIrqII(i2eBordStrPtr pB)
+{
+ OUTB(pB->i2ePointer, SEL_MASK);
+ OUTB(pB->i2ePointer, ST_IN_MAIL);
+}
+
+//******************************************************************************
+// Function: iiEnableMailIrqIIEX(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Nothing
+//
+// Description:
+//
+// Enables board to interrupt host (only) by writing to host's in-bound mailbox.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static void
+iiEnableMailIrqIIEX(i2eBordStrPtr pB)
+{
+ OUTB(pB->i2eXMask, MX_IN_MAIL);
+}
+
+//******************************************************************************
+// Function: iiWriteMaskII(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Nothing
+//
+// Description:
+//
+// Writes arbitrary value to the mask register.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static void
+iiWriteMaskII(i2eBordStrPtr pB, unsigned char value)
+{
+ OUTB(pB->i2ePointer, SEL_MASK);
+ OUTB(pB->i2ePointer, value);
+}
+
+//******************************************************************************
+// Function: iiWriteMaskIIEX(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns: Nothing
+//
+// Description:
+//
+// Writes arbitrary value to the mask register.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static void
+iiWriteMaskIIEX(i2eBordStrPtr pB, unsigned char value)
+{
+ OUTB(pB->i2eXMask, value);
+}
+
+//******************************************************************************
+// Function: iiDownloadBlock(pB, pSource, isStandard)
+// Parameters: pB - pointer to board structure
+// pSource - loadware block to download
+// isStandard - True if "standard" loadware, else false.
+//
+// Returns: Success or Failure
+//
+// Description:
+//
+// Downloads a single block (at pSource)to the board referenced by pB. Caller
+// sets isStandard to true/false according to whether the "standard" loadware is
+// what's being loaded. The normal process, then, is to perform an iiInitialize
+// to the board, then perform some number of iiDownloadBlocks using the returned
+// state to determine when download is complete.
+//
+// Possible return values: (see I2ELLIS.H)
+// II_DOWN_BADVALID
+// II_DOWN_BADFILE
+// II_DOWN_CONTINUING
+// II_DOWN_GOOD
+// II_DOWN_BAD
+// II_DOWN_BADSTATE
+// II_DOWN_TIMEOUT
+//
+// Uses the i2eState and i2eToLoad fields (initialized at iiInitialize) to
+// determine whether this is the first block, whether to check for magic
+// numbers, how many blocks there are to go...
+//
+//******************************************************************************
+static int
+iiDownloadBlock ( i2eBordStrPtr pB, loadHdrStrPtr pSource, int isStandard)
+{
+ int itemp;
+ int loadedFirst;
+
+ if (pB->i2eValid != I2E_MAGIC) return II_DOWN_BADVALID;
+
+ switch(pB->i2eState)
+ {
+ case II_STATE_READY:
+
+ // Loading the first block after reset. Must check the magic number of the
+ // loadfile, store the number of blocks we expect to load.
+ if (pSource->e.loadMagic != MAGIC_LOADFILE)
+ {
+ return II_DOWN_BADFILE;
+ }
+
+ // Next we store the total number of blocks to load, including this one.
+ pB->i2eToLoad = 1 + pSource->e.loadBlocksMore;
+
+ // Set the state, store the version numbers. ('Cause this may have come
+ // from a file - we might want to report these versions and revisions in
+ // case of an error!
+ pB->i2eState = II_STATE_LOADING;
+ pB->i2eLVersion = pSource->e.loadVersion;
+ pB->i2eLRevision = pSource->e.loadRevision;
+ pB->i2eLSub = pSource->e.loadSubRevision;
+
+ // The time and date of compilation is also available but don't bother
+ // storing it for normal purposes.
+ loadedFirst = 1;
+ break;
+
+ case II_STATE_LOADING:
+ loadedFirst = 0;
+ break;
+
+ default:
+ return II_DOWN_BADSTATE;
+ }
+
+ // Now we must be in the II_STATE_LOADING state, and we assume i2eToLoad
+ // must be positive still, because otherwise we would have cleaned up last
+ // time and set the state to II_STATE_LOADED.
+ if (!iiWaitForTxEmpty(pB, MAX_DLOAD_READ_TIME)) {
+ return II_DOWN_TIMEOUT;
+ }
+
+ if (!iiWriteBuf(pB, pSource->c, LOADWARE_BLOCK_SIZE)) {
+ return II_DOWN_BADVALID;
+ }
+
+ // If we just loaded the first block, wait for the fifo to empty an extra
+ // long time to allow for any special startup code in the firmware, like
+ // sending status messages to the LCD's.
+
+ if (loadedFirst) {
+ if (!iiWaitForTxEmpty(pB, MAX_DLOAD_START_TIME)) {
+ return II_DOWN_TIMEOUT;
+ }
+ }
+
+ // Determine whether this was our last block!
+ if (--(pB->i2eToLoad)) {
+ return II_DOWN_CONTINUING; // more to come...
+ }
+
+ // It WAS our last block: Clean up operations...
+ // ...Wait for last buffer to drain from the board...
+ if (!iiWaitForTxEmpty(pB, MAX_DLOAD_READ_TIME)) {
+ return II_DOWN_TIMEOUT;
+ }
+ // If there were only a single block written, this would come back
+ // immediately and be harmless, though not strictly necessary.
+ itemp = MAX_DLOAD_ACK_TIME/10;
+ while (--itemp) {
+ if (HAS_INPUT(pB)) {
+ switch(BYTE_FROM(pB))
+ {
+ case LOADWARE_OK:
+ pB->i2eState =
+ isStandard ? II_STATE_STDLOADED :II_STATE_LOADED;
+
+ // Some revisions of the bootstrap firmware (e.g. ISA-8 1.0.2)
+ // will, // if there is a debug port attached, require some
+ // time to send information to the debug port now. It will do
+ // this before // executing any of the code we just downloaded.
+ // It may take up to 700 milliseconds.
+ if (pB->i2ePom.e.porDiag2 & POR_DEBUG_PORT) {
+ iiDelay(pB, 700);
+ }
+
+ return II_DOWN_GOOD;
+
+ case LOADWARE_BAD:
+ default:
+ return II_DOWN_BAD;
+ }
+ }
+
+ iiDelay(pB, 10); // 10 mS granularity on checking condition
+ }
+
+ // Drop-through --> timed out waiting for firmware confirmation
+
+ pB->i2eState = II_STATE_BADLOAD;
+ return II_DOWN_TIMEOUT;
+}
+
+//******************************************************************************
+// Function: iiDownloadAll(pB, pSource, isStandard, size)
+// Parameters: pB - pointer to board structure
+// pSource - loadware block to download
+// isStandard - True if "standard" loadware, else false.
+// size - size of data to download (in bytes)
+//
+// Returns: Success or Failure
+//
+// Description:
+//
+// Given a pointer to a board structure, a pointer to the beginning of some
+// loadware, whether it is considered the "standard loadware", and the size of
+// the array in bytes loads the entire array to the board as loadware.
+//
+// Assumes the board has been freshly reset and the power-up reset message read.
+// (i.e., in II_STATE_READY). Complains if state is bad, or if there seems to be
+// too much or too little data to load, or if iiDownloadBlock complains.
+//******************************************************************************
+static int
+iiDownloadAll(i2eBordStrPtr pB, loadHdrStrPtr pSource, int isStandard, int size)
+{
+ int status;
+
+ // We know (from context) board should be ready for the first block of
+ // download. Complain if not.
+ if (pB->i2eState != II_STATE_READY) return II_DOWN_BADSTATE;
+
+ while (size > 0) {
+ size -= LOADWARE_BLOCK_SIZE; // How much data should there be left to
+ // load after the following operation ?
+
+ // Note we just bump pSource by "one", because its size is actually that
+ // of an entire block, same as LOADWARE_BLOCK_SIZE.
+ status = iiDownloadBlock(pB, pSource++, isStandard);
+
+ switch(status)
+ {
+ case II_DOWN_GOOD:
+ return ( (size > 0) ? II_DOWN_OVER : II_DOWN_GOOD);
+
+ case II_DOWN_CONTINUING:
+ break;
+
+ default:
+ return status;
+ }
+ }
+
+ // We shouldn't drop out: it means "while" caught us with nothing left to
+ // download, yet the previous DownloadBlock did not return complete. Ergo,
+ // not enough data to match the size byte in the header.
+ return II_DOWN_UNDER;
+}
diff --git a/drivers/char/ip2/i2ellis.h b/drivers/char/ip2/i2ellis.h
new file mode 100644
index 000000000000..510b026d7d26
--- /dev/null
+++ b/drivers/char/ip2/i2ellis.h
@@ -0,0 +1,615 @@
+/*******************************************************************************
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Mainline code for the device driver
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// i2ellis.h
+//
+// IntelliPort-II and IntelliPort-IIEX
+//
+// Extremely
+// Low
+// Level
+// Interface
+// Services
+//
+// Structure Definitions and declarations for "ELLIS" service routines found in
+// i2ellis.c
+//
+// These routines are based on properties of the IntelliPort-II and -IIEX
+// hardware and bootstrap firmware, and are not sensitive to particular
+// conventions of any particular loadware.
+//
+// Unlike i2hw.h, which provides IRONCLAD hardware definitions, the material
+// here and in i2ellis.c is intended to provice a useful, but not required,
+// layer of insulation from the hardware specifics.
+//------------------------------------------------------------------------------
+#ifndef I2ELLIS_H /* To prevent multiple includes */
+#define I2ELLIS_H 1
+//------------------------------------------------
+// Revision History:
+//
+// 30 September 1991 MAG First Draft Started
+// 12 October 1991 ...continued...
+//
+// 20 December 1996 AKM Linux version
+//-------------------------------------------------
+
+//----------------------
+// Mandatory Includes:
+//----------------------
+#include <linux/config.h>
+#include "ip2types.h"
+#include "i2hw.h" // The hardware definitions
+
+//------------------------------------------
+// STAT_BOXIDS packets
+//------------------------------------------
+#define MAX_BOX 4
+
+typedef struct _bidStat
+{
+ unsigned char bid_value[MAX_BOX];
+} bidStat, *bidStatPtr;
+
+// This packet is sent in response to a CMD_GET_BOXIDS bypass command. For -IIEX
+// boards, reports the hardware-specific "asynchronous resource register" on
+// each expansion box. Boxes not present report 0xff. For -II boards, the first
+// element contains 0x80 for 8-port, 0x40 for 4-port boards.
+
+// Box IDs aka ARR or Async Resource Register (more than you want to know)
+// 7 6 5 4 3 2 1 0
+// F F N N L S S S
+// =============================
+// F F - Product Family Designator
+// =====+++++++++++++++++++++++++++++++
+// 0 0 - Intelliport II EX / ISA-8
+// 1 0 - IntelliServer
+// 0 1 - SAC - Port Device (Intelliport III ??? )
+// =====+++++++++++++++++++++++++++++++++++++++
+// N N - Number of Ports
+// 0 0 - 8 (eight)
+// 0 1 - 4 (four)
+// 1 0 - 12 (twelve)
+// 1 1 - 16 (sixteen)
+// =++++++++++++++++++++++++++++++++++
+// L - LCD Display Module Present
+// 0 - No
+// 1 - LCD module present
+// =========+++++++++++++++++++++++++++++++++++++
+// S S S - Async Signals Supported Designator
+// 0 0 0 - 8dss, Mod DCE DB25 Female
+// 0 0 1 - 6dss, RJ-45
+// 0 1 0 - RS-232/422 dss, DB25 Female
+// 0 1 1 - RS-232/422 dss, separate 232/422 DB25 Female
+// 1 0 0 - 6dss, 921.6 I/F with ST654's
+// 1 0 1 - RS-423/232 8dss, RJ-45 10Pin
+// 1 1 0 - 6dss, Mod DCE DB25 Female
+// 1 1 1 - NO BOX PRESENT
+
+#define FF(c) ((c & 0xC0) >> 6)
+#define NN(c) ((c & 0x30) >> 4)
+#define L(c) ((c & 0x08) >> 3)
+#define SSS(c) (c & 0x07)
+
+#define BID_HAS_654(x) (SSS(x) == 0x04)
+#define BID_NO_BOX 0xff /* no box */
+#define BID_8PORT 0x80 /* IP2-8 port */
+#define BID_4PORT 0x81 /* IP2-4 port */
+#define BID_EXP_MASK 0x30 /* IP2-EX */
+#define BID_EXP_8PORT 0x00 /* 8, */
+#define BID_EXP_4PORT 0x10 /* 4, */
+#define BID_EXP_UNDEF 0x20 /* UNDEF, */
+#define BID_EXP_16PORT 0x30 /* 16, */
+#define BID_LCD_CTRL 0x08 /* LCD Controller */
+#define BID_LCD_NONE 0x00 /* - no controller present */
+#define BID_LCD_PRES 0x08 /* - controller present */
+#define BID_CON_MASK 0x07 /* - connector pinouts */
+#define BID_CON_DB25 0x00 /* - DB-25 F */
+#define BID_CON_RJ45 0x01 /* - rj45 */
+
+//------------------------------------------------------------------------------
+// i2eBordStr
+//
+// This structure contains all the information the ELLIS routines require in
+// dealing with a particular board.
+//------------------------------------------------------------------------------
+// There are some queues here which are guaranteed to never contain the entry
+// for a single channel twice. So they must be slightly larger to allow
+// unambiguous full/empty management
+//
+#define CH_QUEUE_SIZE ABS_MOST_PORTS+2
+
+typedef struct _i2eBordStr
+{
+ porStr i2ePom; // Structure containing the power-on message.
+
+ unsigned short i2ePomSize;
+ // The number of bytes actually read if
+ // different from sizeof i2ePom, indicates
+ // there is an error!
+
+ unsigned short i2eStartMail;
+ // Contains whatever inbound mailbox data
+ // present at startup. NO_MAIL_HERE indicates
+ // nothing was present. No special
+ // significance as of this writing, but may be
+ // useful for diagnostic reasons.
+
+ unsigned short i2eValid;
+ // Indicates validity of the structure; if
+ // i2eValid == I2E_MAGIC, then we can trust
+ // the other fields. Some (especially
+ // initialization) functions are good about
+ // checking for validity. Many functions do
+ // not, it being assumed that the larger
+ // context assures we are using a valid
+ // i2eBordStrPtr.
+
+ unsigned short i2eError;
+ // Used for returning an error condition from
+ // several functions which use i2eBordStrPtr
+ // as an argument.
+
+ // Accelerators to characterize separate features of a board, derived from a
+ // number of sources.
+
+ unsigned short i2eFifoSize;
+ // Always, the size of the FIFO. For
+ // IntelliPort-II, always the same, for -IIEX
+ // taken from the Power-On reset message.
+
+ volatile
+ unsigned short i2eFifoRemains;
+ // Used during normal operation to indicate a
+ // lower bound on the amount of data which
+ // might be in the outbound fifo.
+
+ unsigned char i2eFifoStyle;
+ // Accelerator which tells which style (-II or
+ // -IIEX) FIFO we are using.
+
+ unsigned char i2eDataWidth16;
+ // Accelerator which tells whether we should
+ // do 8 or 16-bit data transfers.
+
+ unsigned char i2eMaxIrq;
+ // The highest allowable IRQ, based on the
+ // slot size.
+
+ unsigned char i2eChangeIrq;
+ // Whether tis valid to change IRQ's
+ // ISA = ok, EISA, MicroChannel, no
+
+ // Accelerators for various addresses on the board
+ int i2eBase; // I/O Address of the Board
+ int i2eData; // From here data transfers happen
+ int i2eStatus; // From here status reads happen
+ int i2ePointer; // (IntelliPort-II: pointer/commands)
+ int i2eXMail; // (IntelliPOrt-IIEX: mailboxes
+ int i2eXMask; // (IntelliPort-IIEX: mask write
+
+ //-------------------------------------------------------
+ // Information presented in a common format across boards
+ // For each box, bit map of the channels present. Box closest to
+ // the host is box 0. LSB is channel 0. IntelliPort-II (non-expandable)
+ // is taken to be box 0. These are derived from product i.d. registers.
+
+ unsigned short i2eChannelMap[ABS_MAX_BOXES];
+
+ // Same as above, except each is derived from firmware attempting to detect
+ // the uart presence (by reading a valid GFRCR register). If bits are set in
+ // i2eChannelMap and not in i2eGoodMap, there is a potential problem.
+
+ unsigned short i2eGoodMap[ABS_MAX_BOXES];
+
+ // ---------------------------
+ // For indirect function calls
+
+ // Routine to cause an N-millisecond delay: Patched by the ii2Initialize
+ // function.
+
+ void (*i2eDelay)(unsigned int);
+
+ // Routine to write N bytes to the board through the FIFO. Returns true if
+ // all copacetic, otherwise returns false and error is in i2eError field.
+ // IF COUNT IS ODD, ROUNDS UP TO THE NEXT EVEN NUMBER.
+
+ int (*i2eWriteBuf)(struct _i2eBordStr *, unsigned char *, int);
+
+ // Routine to read N bytes from the board through the FIFO. Returns true if
+ // copacetic, otherwise returns false and error in i2eError.
+ // IF COUNT IS ODD, ROUNDS UP TO THE NEXT EVEN NUMBER.
+
+ int (*i2eReadBuf)(struct _i2eBordStr *, unsigned char *, int);
+
+ // Returns a word from FIFO. Will use 2 byte operations if needed.
+
+ unsigned short (*i2eReadWord)(struct _i2eBordStr *);
+
+ // Writes a word to FIFO. Will use 2 byte operations if needed.
+
+ void (*i2eWriteWord)(struct _i2eBordStr *, unsigned short);
+
+ // Waits specified time for the Transmit FIFO to go empty. Returns true if
+ // ok, otherwise returns false and error in i2eError.
+
+ int (*i2eWaitForTxEmpty)(struct _i2eBordStr *, int);
+
+ // Returns true or false according to whether the outgoing mailbox is empty.
+
+ int (*i2eTxMailEmpty)(struct _i2eBordStr *);
+
+ // Checks whether outgoing mailbox is empty. If so, sends mail and returns
+ // true. Otherwise returns false.
+
+ int (*i2eTrySendMail)(struct _i2eBordStr *, unsigned char);
+
+ // If no mail available, returns NO_MAIL_HERE, else returns the value in the
+ // mailbox (guaranteed can't be NO_MAIL_HERE).
+
+ unsigned short (*i2eGetMail)(struct _i2eBordStr *);
+
+ // Enables the board to interrupt the host when it writes to the mailbox.
+ // Irqs will not occur, however, until the loadware separately enables
+ // interrupt generation to the host. The standard loadware does this in
+ // response to a command packet sent by the host. (Also, disables
+ // any other potential interrupt sources from the board -- other than the
+ // inbound mailbox).
+
+ void (*i2eEnableMailIrq)(struct _i2eBordStr *);
+
+ // Writes an arbitrary value to the mask register.
+
+ void (*i2eWriteMask)(struct _i2eBordStr *, unsigned char);
+
+
+ // State information
+
+ // During downloading, indicates the number of blocks remaining to download
+ // to the board.
+
+ short i2eToLoad;
+
+ // State of board (see manifests below) (e.g., whether in reset condition,
+ // whether standard loadware is installed, etc.
+
+ unsigned char i2eState;
+
+ // These three fields are only valid when there is loadware running on the
+ // board. (i2eState == II_STATE_LOADED or i2eState == II_STATE_STDLOADED )
+
+ unsigned char i2eLVersion; // Loadware version
+ unsigned char i2eLRevision; // Loadware revision
+ unsigned char i2eLSub; // Loadware subrevision
+
+ // Flags which only have meaning in the context of the standard loadware.
+ // Somewhat violates the layering concept, but there is so little additional
+ // needed at the board level (while much additional at the channel level),
+ // that this beats maintaining two different per-board structures.
+
+ // Indicates which IRQ the board has been initialized (from software) to use
+ // For MicroChannel boards, any value different from IRQ_UNDEFINED means
+ // that the software command has been sent to enable interrupts (or specify
+ // they are disabled). Special value: IRQ_UNDEFINED indicates that the
+ // software command to select the interrupt has not yet been sent, therefore
+ // (since the standard loadware insists that it be sent before any other
+ // packets are sent) no other packets should be sent yet.
+
+ unsigned short i2eUsingIrq;
+
+ // This is set when we hit the MB_OUT_STUFFED mailbox, which prevents us
+ // putting more in the mailbox until an appropriate mailbox message is
+ // received.
+
+ unsigned char i2eWaitingForEmptyFifo;
+
+ // Any mailbox bits waiting to be sent to the board are OR'ed in here.
+
+ unsigned char i2eOutMailWaiting;
+
+ // The head of any incoming packet is read into here, is then examined and
+ // we dispatch accordingly.
+
+ unsigned short i2eLeadoffWord[1];
+
+ // Running counter of interrupts where the mailbox indicated incoming data.
+
+ unsigned short i2eFifoInInts;
+
+ // Running counter of interrupts where the mailbox indicated outgoing data
+ // had been stripped.
+
+ unsigned short i2eFifoOutInts;
+
+ // If not void, gives the address of a routine to call if fatal board error
+ // is found (only applies to standard l/w).
+
+ void (*i2eFatalTrap)(struct _i2eBordStr *);
+
+ // Will point to an array of some sort of channel structures (whose format
+ // is unknown at this level, being a function of what loadware is
+ // installed and the code configuration (max sizes of buffers, etc.)).
+
+ void *i2eChannelPtr;
+
+ // Set indicates that the board has gone fatal.
+
+ unsigned short i2eFatal;
+
+ // The number of elements pointed to by i2eChannelPtr.
+
+ unsigned short i2eChannelCnt;
+
+ // Ring-buffers of channel structures whose channels have particular needs.
+
+ rwlock_t Fbuf_spinlock;
+ volatile
+ unsigned short i2Fbuf_strip; // Strip index
+ volatile
+ unsigned short i2Fbuf_stuff; // Stuff index
+ void *i2Fbuf[CH_QUEUE_SIZE]; // An array of channel pointers
+ // of channels who need to send
+ // flow control packets.
+ rwlock_t Dbuf_spinlock;
+ volatile
+ unsigned short i2Dbuf_strip; // Strip index
+ volatile
+ unsigned short i2Dbuf_stuff; // Stuff index
+ void *i2Dbuf[CH_QUEUE_SIZE]; // An array of channel pointers
+ // of channels who need to send
+ // data or in-line command packets.
+ rwlock_t Bbuf_spinlock;
+ volatile
+ unsigned short i2Bbuf_strip; // Strip index
+ volatile
+ unsigned short i2Bbuf_stuff; // Stuff index
+ void *i2Bbuf[CH_QUEUE_SIZE]; // An array of channel pointers
+ // of channels who need to send
+ // bypass command packets.
+
+ /*
+ * A set of flags to indicate that certain events have occurred on at least
+ * one of the ports on this board. We use this to decide whether to spin
+ * through the channels looking for breaks, etc.
+ */
+ int got_input;
+ int status_change;
+ bidStat channelBtypes;
+
+ /*
+ * Debugging counters, etc.
+ */
+ unsigned long debugFlowQueued;
+ unsigned long debugInlineQueued;
+ unsigned long debugDataQueued;
+ unsigned long debugBypassQueued;
+ unsigned long debugFlowCount;
+ unsigned long debugInlineCount;
+ unsigned long debugBypassCount;
+
+ rwlock_t read_fifo_spinlock;
+ rwlock_t write_fifo_spinlock;
+
+// For queuing interrupt bottom half handlers. /\/\|=mhw=|\/\/
+ struct work_struct tqueue_interrupt;
+
+ struct timer_list SendPendingTimer; // Used by iiSendPending
+ unsigned int SendPendingRetry;
+} i2eBordStr, *i2eBordStrPtr;
+
+//-------------------------------------------------------------------
+// Macro Definitions for the indirect calls defined in the i2eBordStr
+//-------------------------------------------------------------------
+//
+#define iiDelay(a,b) (*(a)->i2eDelay)(b)
+#define iiWriteBuf(a,b,c) (*(a)->i2eWriteBuf)(a,b,c)
+#define iiReadBuf(a,b,c) (*(a)->i2eReadBuf)(a,b,c)
+
+#define iiWriteWord(a,b) (*(a)->i2eWriteWord)(a,b)
+#define iiReadWord(a) (*(a)->i2eReadWord)(a)
+
+#define iiWaitForTxEmpty(a,b) (*(a)->i2eWaitForTxEmpty)(a,b)
+
+#define iiTxMailEmpty(a) (*(a)->i2eTxMailEmpty)(a)
+#define iiTrySendMail(a,b) (*(a)->i2eTrySendMail)(a,b)
+
+#define iiGetMail(a) (*(a)->i2eGetMail)(a)
+#define iiEnableMailIrq(a) (*(a)->i2eEnableMailIrq)(a)
+#define iiDisableMailIrq(a) (*(a)->i2eWriteMask)(a,0)
+#define iiWriteMask(a,b) (*(a)->i2eWriteMask)(a,b)
+
+//-------------------------------------------
+// Manifests for i2eBordStr:
+//-------------------------------------------
+
+#define YES 1
+#define NO 0
+
+#define NULLFUNC (void (*)(void))0
+#define NULLPTR (void *)0
+
+typedef void (*delayFunc_t)(unsigned int);
+
+// i2eValid
+//
+#define I2E_MAGIC 0x4251 // Structure is valid.
+#define I2E_INCOMPLETE 0x1122 // Structure failed during init.
+
+
+// i2eError
+//
+#define I2EE_GOOD 0 // Operation successful
+#define I2EE_BADADDR 1 // Address out of range
+#define I2EE_BADSTATE 2 // Attempt to perform a function when the board
+ // structure was in the incorrect state
+#define I2EE_BADMAGIC 3 // Bad magic number from Power On test (i2ePomSize
+ // reflects what was read
+#define I2EE_PORM_SHORT 4 // Power On message too short
+#define I2EE_PORM_LONG 5 // Power On message too long
+#define I2EE_BAD_FAMILY 6 // Un-supported board family type
+#define I2EE_INCONSIST 7 // Firmware reports something impossible,
+ // e.g. unexpected number of ports... Almost no
+ // excuse other than bad FIFO...
+#define I2EE_POSTERR 8 // Power-On self test reported a bad error
+#define I2EE_BADBUS 9 // Unknown Bus type declared in message
+#define I2EE_TXE_TIME 10 // Timed out waiting for TX Fifo to empty
+#define I2EE_INVALID 11 // i2eValid field does not indicate a valid and
+ // complete board structure (for functions which
+ // require this be so.)
+#define I2EE_BAD_PORT 12 // Discrepancy between channels actually found and
+ // what the product is supposed to have. Check
+ // i2eGoodMap vs i2eChannelMap for details.
+#define I2EE_BAD_IRQ 13 // Someone specified an unsupported IRQ
+#define I2EE_NOCHANNELS 14 // No channel structures have been defined (for
+ // functions requiring this).
+
+// i2eFifoStyle
+//
+#define FIFO_II 0 /* IntelliPort-II style: see also i2hw.h */
+#define FIFO_IIEX 1 /* IntelliPort-IIEX style */
+
+// i2eGetMail
+//
+#define NO_MAIL_HERE 0x1111 // Since mail is unsigned char, cannot possibly
+ // promote to 0x1111.
+// i2eState
+//
+#define II_STATE_COLD 0 // Addresses have been defined, but board not even
+ // reset yet.
+#define II_STATE_RESET 1 // Board,if it exists, has just been reset
+#define II_STATE_READY 2 // Board ready for its first block
+#define II_STATE_LOADING 3 // Board continuing load
+#define II_STATE_LOADED 4 // Board has finished load: status ok
+#define II_STATE_BADLOAD 5 // Board has finished load: failed!
+#define II_STATE_STDLOADED 6 // Board has finished load: standard firmware
+
+// i2eUsingIrq
+//
+#define IRQ_UNDEFINED 0x1352 // No valid irq (or polling = 0) can ever
+ // promote to this!
+//------------------------------------------
+// Handy Macros for i2ellis.c and others
+// Note these are common to -II and -IIEX
+//------------------------------------------
+
+// Given a pointer to the board structure, does the input FIFO have any data or
+// not?
+//
+#define HAS_INPUT(pB) !(INB(pB->i2eStatus) & ST_IN_EMPTY)
+#define HAS_NO_INPUT(pB) (INB(pB->i2eStatus) & ST_IN_EMPTY)
+
+// Given a pointer to board structure, read a byte or word from the fifo
+//
+#define BYTE_FROM(pB) (unsigned char)INB(pB->i2eData)
+#define WORD_FROM(pB) (unsigned short)INW(pB->i2eData)
+
+// Given a pointer to board structure, is there room for any data to be written
+// to the data fifo?
+//
+#define HAS_OUTROOM(pB) !(INB(pB->i2eStatus) & ST_OUT_FULL)
+#define HAS_NO_OUTROOM(pB) (INB(pB->i2eStatus) & ST_OUT_FULL)
+
+// Given a pointer to board structure, write a single byte to the fifo
+// structure. Note that for 16-bit interfaces, the high order byte is undefined
+// and unknown.
+//
+#define BYTE_TO(pB, c) OUTB(pB->i2eData,(c))
+
+// Write a word to the fifo structure. For 8-bit interfaces, this may have
+// unknown results.
+//
+#define WORD_TO(pB, c) OUTW(pB->i2eData,(c))
+
+// Given a pointer to the board structure, is there anything in the incoming
+// mailbox?
+//
+#define HAS_MAIL(pB) (INB(pB->i2eStatus) & ST_IN_MAIL)
+
+#define UPDATE_FIFO_ROOM(pB) (pB)->i2eFifoRemains=(pB)->i2eFifoSize
+
+// Handy macro to round up a number (like the buffer write and read routines do)
+//
+#define ROUNDUP(number) (((number)+1) & (~1))
+
+//------------------------------------------
+// Function Declarations for i2ellis.c
+//------------------------------------------
+//
+// Functions called directly
+//
+// Initialization of a board & structure is in four (five!) parts:
+//
+// 0) iiEllisInit() - Initialize iiEllis subsystem.
+// 1) iiSetAddress() - Define the board address & delay function for a board.
+// 2) iiReset() - Reset the board (provided it exists)
+// -- Note you may do this to several boards --
+// 3) iiResetDelay() - Delay for 2 seconds (once for all boards)
+// 4) iiInitialize() - Attempt to read Power-up message; further initialize
+// accelerators
+//
+// Then you may use iiDownloadAll() or iiDownloadFile() (in i2file.c) to write
+// loadware. To change loadware, you must begin again with step 2, resetting
+// the board again (step 1 not needed).
+
+static void iiEllisInit(void);
+static int iiSetAddress(i2eBordStrPtr, int, delayFunc_t );
+static int iiReset(i2eBordStrPtr);
+static int iiResetDelay(i2eBordStrPtr);
+static int iiInitialize(i2eBordStrPtr);
+
+// Routine to validate that all channels expected are there.
+//
+extern int iiValidateChannels(i2eBordStrPtr);
+
+// Routine used to download a block of loadware.
+//
+static int iiDownloadBlock(i2eBordStrPtr, loadHdrStrPtr, int);
+
+// Return values given by iiDownloadBlock, iiDownloadAll, iiDownloadFile:
+//
+#define II_DOWN_BADVALID 0 // board structure is invalid
+#define II_DOWN_CONTINUING 1 // So far, so good, firmware expects more
+#define II_DOWN_GOOD 2 // Download complete, CRC good
+#define II_DOWN_BAD 3 // Download complete, but CRC bad
+#define II_DOWN_BADFILE 4 // Bad magic number in loadware file
+#define II_DOWN_BADSTATE 5 // Board is in an inappropriate state for
+ // downloading loadware. (see i2eState)
+#define II_DOWN_TIMEOUT 6 // Timeout waiting for firmware
+#define II_DOWN_OVER 7 // Too much data
+#define II_DOWN_UNDER 8 // Not enough data
+#define II_DOWN_NOFILE 9 // Loadware file not found
+
+// Routine to download an entire loadware module: Return values are a subset of
+// iiDownloadBlock's, excluding, of course, II_DOWN_CONTINUING
+//
+static int iiDownloadAll(i2eBordStrPtr, loadHdrStrPtr, int, int);
+
+// Called indirectly always. Needed externally so the routine might be
+// SPECIFIED as an argument to iiReset()
+//
+//static void ii2DelayIO(unsigned int); // N-millisecond delay using
+ //hardware spin
+//static void ii2DelayTimer(unsigned int); // N-millisecond delay using Linux
+ //timer
+
+// Many functions defined here return True if good, False otherwise, with an
+// error code in i2eError field. Here is a handy macro for setting the error
+// code and returning.
+//
+#define COMPLETE(pB,code) \
+ if(1){ \
+ pB->i2eError = code; \
+ return (code == I2EE_GOOD);\
+ }
+
+#endif // I2ELLIS_H
diff --git a/drivers/char/ip2/i2hw.h b/drivers/char/ip2/i2hw.h
new file mode 100644
index 000000000000..15fe04e748f4
--- /dev/null
+++ b/drivers/char/ip2/i2hw.h
@@ -0,0 +1,648 @@
+/*******************************************************************************
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Definitions limited to properties of the hardware or the
+* bootstrap firmware. As such, they are applicable regardless of
+* operating system or loadware (standard or diagnostic).
+*
+*******************************************************************************/
+#ifndef I2HW_H
+#define I2HW_H 1
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 23 September 1991 MAG First Draft Started...through...
+// 11 October 1991 ... Continuing development...
+// 6 August 1993 Added support for ISA-4 (asic) which is architected
+// as an ISA-CEX with a single 4-port box.
+//
+// 20 December 1996 AKM Version for Linux
+//
+//------------------------------------------------------------------------------
+/*------------------------------------------------------------------------------
+
+HARDWARE DESCRIPTION:
+
+Introduction:
+
+The IntelliPort-II and IntelliPort-IIEX products occupy a block of eight (8)
+addresses in the host's I/O space.
+
+Some addresses are used to transfer data to/from the board, some to transfer
+so-called "mailbox" messages, and some to read bit-mapped status information.
+While all the products in the line are functionally similar, some use a 16-bit
+data path to transfer data while others use an 8-bit path. Also, the use of
+command /status/mailbox registers differs slightly between the II and IIEX
+branches of the family.
+
+The host determines what type of board it is dealing with by reading a string of
+sixteen characters from the board. These characters are always placed in the
+fifo by the board's local processor whenever the board is reset (either from
+power-on or under software control) and are known as the "Power-on Reset
+Message." In order that this message can be read from either type of board, the
+hardware registers used in reading this message are the same. Once this message
+has been read by the host, then it has the information required to operate.
+
+General Differences between boards:
+
+The greatest structural difference is between the -II and -IIEX families of
+product. The -II boards use the Am4701 dual 512x8 bidirectional fifo to support
+the data path, mailbox registers, and status registers. This chip contains some
+features which are not used in the IntelliPort-II products; a description of
+these is omitted here. Because of these many features, it contains many
+registers, too many to access directly within a small address space. They are
+accessed by first writing a value to a "pointer" register. This value selects
+the register to be accessed. The next read or write to that address accesses
+the selected register rather than the pointer register.
+
+The -IIEX boards use a proprietary design similar to the Am4701 in function. But
+because of a simpler, more streamlined design it doesn't require so many
+registers. This means they can be accessed directly in single operations rather
+than through a pointer register.
+
+Besides these differences, there are differences in whether 8-bit or 16-bit
+transfers are used to move data to the board.
+
+The -II boards are capable only of 8-bit data transfers, while the -IIEX boards
+may be configured for either 8-bit or 16-bit data transfers. If the on-board DIP
+switch #8 is ON, and the card has been installed in a 16-bit slot, 16-bit
+transfers are supported (and will be expected by the standard loadware). The
+on-board firmware can determine the position of the switch, and whether the
+board is installed in a 16-bit slot; it supplies this information to the host as
+part of the power-up reset message.
+
+The configuration switch (#8) and slot selection do not directly configure the
+hardware. It is up to the on-board loadware and host-based drivers to act
+according to the selected options. That is, loadware and drivers could be
+written to perform 8-bit transfers regardless of the state of the DIP switch or
+slot (and in a diagnostic environment might well do so). Likewise, 16-bit
+transfers could be performed as long as the card is in a 16-bit slot.
+
+Note the slot selection and DIP switch selection are provided separately: a
+board running in 8-bit mode in a 16-bit slot has a greater range of possible
+interrupts to choose from; information of potential use to the host.
+
+All 8-bit data transfers are done in the same way, regardless of whether on a
+-II board or a -IIEX board.
+
+The host must consider two things then: 1) whether a -II or -IIEX product is
+being used, and 2) whether an 8-bit or 16-bit data path is used.
+
+A further difference is that -II boards always have a 512-byte fifo operating in
+each direction. -IIEX boards may use fifos of varying size; this size is
+reported as part of the power-up message.
+
+I/O Map Of IntelliPort-II and IntelliPort-IIEX boards:
+(Relative to the chosen base address)
+
+Addr R/W IntelliPort-II IntelliPort-IIEX
+---- --- -------------- ----------------
+0 R/W Data Port (byte) Data Port (byte or word)
+1 R/W (Not used) (MSB of word-wide data written to Data Port)
+2 R Status Register Status Register
+2 W Pointer Register Interrupt Mask Register
+3 R/W (Not used) Mailbox Registers (6 bits: 11111100)
+4,5 -- Reserved for future products
+6 -- Reserved for future products
+7 R Guaranteed to have no effect
+7 W Hardware reset of board.
+
+
+Rules:
+All data transfers are performed using the even i/o address. If byte-wide data
+transfers are being used, do INB/OUTB operations on the data port. If word-wide
+transfers are used, do INW/OUTW operations. In some circumstances (such as
+reading the power-up message) you will do INB from the data port, but in this
+case the MSB of each word read is lost. When accessing all other unreserved
+registers, use byte operations only.
+------------------------------------------------------------------------------*/
+
+//------------------------------------------------
+// Mandatory Includes:
+//------------------------------------------------
+//
+#include "ip2types.h"
+#include "i2os.h" /* For any o.s., compiler, or host-related issues */
+
+//-------------------------------------------------------------------------
+// Manifests for the I/O map:
+//-------------------------------------------------------------------------
+// R/W: Data port (byte) for IntelliPort-II,
+// R/W: Data port (byte or word) for IntelliPort-IIEX
+// Incoming or outgoing data passes through a FIFO, the status of which is
+// available in some of the bits in FIFO_STATUS. This (bidirectional) FIFO is
+// the primary means of transferring data, commands, flow-control, and status
+// information between the host and board.
+//
+#define FIFO_DATA 0
+
+// Another way of passing information between the board and the host is
+// through "mailboxes". Unlike a FIFO, a mailbox holds only a single byte of
+// data. Writing data to the mailbox causes a status bit to be set, and
+// potentially interrupting the intended receiver. The sender has some way to
+// determine whether the data has been read yet; as soon as it has, it may send
+// more. The mailboxes are handled differently on -II and -IIEX products, as
+// suggested below.
+//------------------------------------------------------------------------------
+// Read: Status Register for IntelliPort-II or -IIEX
+// The presence of any bit set here will cause an interrupt to the host,
+// provided the corresponding bit has been unmasked in the interrupt mask
+// register. Furthermore, interrupts to the host are disabled globally until the
+// loadware selects the irq line to use. With the exception of STN_MR, the bits
+// remain set so long as the associated condition is true.
+//
+#define FIFO_STATUS 2
+
+// Bit map of status bits which are identical for -II and -IIEX
+//
+#define ST_OUT_FULL 0x40 // Outbound FIFO full
+#define ST_IN_EMPTY 0x20 // Inbound FIFO empty
+#define ST_IN_MAIL 0x04 // Inbound Mailbox full
+
+// The following exists only on the Intelliport-IIEX, and indicates that the
+// board has not read the last outgoing mailbox data yet. In the IntelliPort-II,
+// the outgoing mailbox may be read back: a zero indicates the board has read
+// the data.
+//
+#define STE_OUT_MAIL 0x80 // Outbound mailbox full (!)
+
+// The following bits are defined differently for -II and -IIEX boards. Code
+// which relies on these bits will need to be functionally different for the two
+// types of boards and should be generally avoided because of the additional
+// complexity this creates:
+
+// Bit map of status bits only on -II
+
+// Fifo has been RESET (cleared when the status register is read). Note that
+// this condition cannot be masked and would always interrupt the host, except
+// that the hardware reset also disables interrupts globally from the board
+// until re-enabled by loadware. This could also arise from the
+// Am4701-supported command to reset the chip, but this command is generally not
+// used here.
+//
+#define STN_MR 0x80
+
+// See the AMD Am4701 data sheet for details on the following four bits. They
+// are not presently used by Computone drivers.
+//
+#define STN_OUT_AF 0x10 // Outbound FIFO almost full (programmable)
+#define STN_IN_AE 0x08 // Inbound FIFO almost empty (programmable)
+#define STN_BD 0x02 // Inbound byte detected
+#define STN_PE 0x01 // Parity/Framing condition detected
+
+// Bit-map of status bits only on -IIEX
+//
+#define STE_OUT_HF 0x10 // Outbound FIFO half full
+#define STE_IN_HF 0x08 // Inbound FIFO half full
+#define STE_IN_FULL 0x02 // Inbound FIFO full
+#define STE_OUT_MT 0x01 // Outbound FIFO empty
+
+//------------------------------------------------------------------------------
+
+// Intelliport-II -- Write Only: the pointer register.
+// Values are written to this register to select the Am4701 internal register to
+// be accessed on the next operation.
+//
+#define FIFO_PTR 0x02
+
+// Values for the pointer register
+//
+#define SEL_COMMAND 0x1 // Selects the Am4701 command register
+
+// Some possible commands:
+//
+#define SEL_CMD_MR 0x80 // Am4701 command to reset the chip
+#define SEL_CMD_SH 0x40 // Am4701 command to map the "other" port into the
+ // status register.
+#define SEL_CMD_UNSH 0 // Am4701 command to "unshift": port maps into its
+ // own status register.
+#define SEL_MASK 0x2 // Selects the Am4701 interrupt mask register. The
+ // interrupt mask register is bit-mapped to match
+ // the status register (FIFO_STATUS) except for
+ // STN_MR. (See above.)
+#define SEL_BYTE_DET 0x3 // Selects the Am4701 byte-detect register. (Not
+ // normally used except in diagnostics.)
+#define SEL_OUTMAIL 0x4 // Selects the outbound mailbox (R/W). Reading back
+ // a value of zero indicates that the mailbox has
+ // been read by the board and is available for more
+ // data./ Writing to the mailbox optionally
+ // interrupts the board, depending on the loadware's
+ // setting of its interrupt mask register.
+#define SEL_AEAF 0x5 // Selects AE/AF threshold register.
+#define SEL_INMAIL 0x6 // Selects the inbound mailbox (Read)
+
+//------------------------------------------------------------------------------
+// IntelliPort-IIEX -- Write Only: interrupt mask (and misc flags) register:
+// Unlike IntelliPort-II, bit assignments do NOT match those of the status
+// register.
+//
+#define FIFO_MASK 0x2
+
+// Mailbox readback select:
+// If set, reads to FIFO_MAIL will read the OUTBOUND mailbox (host to board). If
+// clear (default on reset) reads to FIFO_MAIL will read the INBOUND mailbox.
+// This is the normal situation. The clearing of a mailbox is determined on
+// -IIEX boards by waiting for the STE_OUT_MAIL bit to clear. Readback
+// capability is provided for diagnostic purposes only.
+//
+#define MX_OUTMAIL_RSEL 0x80
+
+#define MX_IN_MAIL 0x40 // Enables interrupts when incoming mailbox goes
+ // full (ST_IN_MAIL set).
+#define MX_IN_FULL 0x20 // Enables interrupts when incoming FIFO goes full
+ // (STE_IN_FULL).
+#define MX_IN_MT 0x08 // Enables interrupts when incoming FIFO goes empty
+ // (ST_IN_MT).
+#define MX_OUT_FULL 0x04 // Enables interrupts when outgoing FIFO goes full
+ // (ST_OUT_FULL).
+#define MX_OUT_MT 0x01 // Enables interrupts when outgoing FIFO goes empty
+ // (STE_OUT_MT).
+
+// Any remaining bits are reserved, and should be written to ZERO for
+// compatibility with future Computone products.
+
+//------------------------------------------------------------------------------
+// IntelliPort-IIEX: -- These are only 6-bit mailboxes !!! -- 11111100 (low two
+// bits always read back 0).
+// Read: One of the mailboxes, usually Inbound.
+// Inbound Mailbox (MX_OUTMAIL_RSEL = 0)
+// Outbound Mailbox (MX_OUTMAIL_RSEL = 1)
+// Write: Outbound Mailbox
+// For the IntelliPort-II boards, the outbound mailbox is read back to determine
+// whether the board has read the data (0 --> data has been read). For the
+// IntelliPort-IIEX, this is done by reading a status register. To determine
+// whether mailbox is available for more outbound data, use the STE_OUT_MAIL bit
+// in FIFO_STATUS. Moreover, although the Outbound Mailbox can be read back by
+// setting MX_OUTMAIL_RSEL, it is NOT cleared when the board reads it, as is the
+// case with the -II boards. For this reason, FIFO_MAIL is normally used to read
+// the inbound FIFO, and MX_OUTMAIL_RSEL kept clear. (See above for
+// MX_OUTMAIL_RSEL description.)
+//
+#define FIFO_MAIL 0x3
+
+//------------------------------------------------------------------------------
+// WRITE ONLY: Resets the board. (Data doesn't matter).
+//
+#define FIFO_RESET 0x7
+
+//------------------------------------------------------------------------------
+// READ ONLY: Will have no effect. (Data is undefined.)
+// Actually, there will be an effect, in that the operation is sure to generate
+// a bus cycle: viz., an I/O byte Read. This fact can be used to enforce short
+// delays when no comparable time constant is available.
+//
+#define FIFO_NOP 0x7
+
+//------------------------------------------------------------------------------
+// RESET & POWER-ON RESET MESSAGE
+/*------------------------------------------------------------------------------
+RESET:
+
+The IntelliPort-II and -IIEX boards are reset in three ways: Power-up, channel
+reset, and via a write to the reset register described above. For products using
+the ISA bus, these three sources of reset are equvalent. For MCA and EISA buses,
+the Power-up and channel reset sources cause additional hardware initialization
+which should only occur at system startup time.
+
+The third type of reset, called a "command reset", is done by writing any data
+to the FIFO_RESET address described above. This resets the on-board processor,
+FIFO, UARTS, and associated hardware.
+
+This passes control of the board to the bootstrap firmware, which performs a
+Power-On Self Test and which detects its current configuration. For example,
+-IIEX products determine the size of FIFO which has been installed, and the
+number and type of expansion boxes attached.
+
+This and other information is then written to the FIFO in a 16-byte data block
+to be read by the host. This block is guaranteed to be present within two (2)
+seconds of having received the command reset. The firmware is now ready to
+receive loadware from the host.
+
+It is good practice to perform a command reset to the board explicitly as part
+of your software initialization. This allows your code to properly restart from
+a soft boot. (Many systems do not issue channel reset on soft boot).
+
+Because of a hardware reset problem on some of the Cirrus Logic 1400's which are
+used on the product, it is recommended that you reset the board twice, separated
+by an approximately 50 milliseconds delay. (VERY approximately: probably ok to
+be off by a factor of five. The important point is that the first command reset
+in fact generates a reset pulse on the board. This pulse is guaranteed to last
+less than 10 milliseconds. The additional delay ensures the 1400 has had the
+chance to respond sufficiently to the first reset. Why not a longer delay? Much
+more than 50 milliseconds gets to be noticable, but the board would still work.
+
+Once all 16 bytes of the Power-on Reset Message have been read, the bootstrap
+firmware is ready to receive loadware.
+
+Note on Power-on Reset Message format:
+The various fields have been designed with future expansion in view.
+Combinations of bitfields and values have been defined which define products
+which may not currently exist. This has been done to allow drivers to anticipate
+the possible introduction of products in a systematic fashion. This is not
+intended to suggest that each potential product is actually under consideration.
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Format of Power-on Reset Message
+//----------------------------------------
+
+typedef union _porStr // "por" stands for Power On Reset
+{
+ unsigned char c[16]; // array used when considering the message as a
+ // string of undifferentiated characters
+
+ struct // Elements used when considering values
+ {
+ // The first two bytes out of the FIFO are two magic numbers. These are
+ // intended to establish that there is indeed a member of the
+ // IntelliPort-II(EX) family present. The remaining bytes may be
+ // expected // to be valid. When reading the Power-on Reset message,
+ // if the magic numbers do not match it is probably best to stop
+ // reading immediately. You are certainly not reading our board (unless
+ // hardware is faulty), and may in fact be reading some other piece of
+ // hardware.
+
+ unsigned char porMagic1; // magic number: first byte == POR_MAGIC_1
+ unsigned char porMagic2; // magic number: second byte == POR_MAGIC_2
+
+ // The Version, Revision, and Subrevision are stored as absolute numbers
+ // and would normally be displayed in the format V.R.S (e.g. 1.0.2)
+
+ unsigned char porVersion; // Bootstrap firmware version number
+ unsigned char porRevision; // Bootstrap firmware revision number
+ unsigned char porSubRev; // Bootstrap firmware sub-revision number
+
+ unsigned char porID; // Product ID: Bit-mapped according to
+ // conventions described below. Among other
+ // things, this allows us to distinguish
+ // IntelliPort-II boards from IntelliPort-IIEX
+ // boards.
+
+ unsigned char porBus; // IntelliPort-II: Unused
+ // IntelliPort-IIEX: Bus Information:
+ // Bit-mapped below
+
+ unsigned char porMemory; // On-board DRAM size: in 32k blocks
+
+ // porPorts1 (and porPorts2) are used to determine the ports which are
+ // available to the board. For non-expandable product, a single number
+ // is sufficient. For expandable product, the board may be connected
+ // to as many as four boxes. Each box may be (so far) either a 16-port
+ // or an 8-port size. Whenever an 8-port box is used, the remaining 8
+ // ports leave gaps between existing channels. For that reason,
+ // expandable products must report a MAP of available channels. Since
+ // each UART supports four ports, we represent each UART found by a
+ // single bit. Using two bytes to supply the mapping information we
+ // report the presense or absense of up to 16 UARTS, or 64 ports in
+ // steps of 4 ports. For -IIEX products, the ports are numbered
+ // starting at the box closest to the controller in the "chain".
+
+ // Interpreted Differently for IntelliPort-II and -IIEX.
+ // -II: Number of ports (Derived actually from product ID). See
+ // Diag1&2 to indicate if uart was actually detected.
+ // -IIEX: Bit-map of UARTS found, LSB (see below for MSB of this). This
+ // bitmap is based on detecting the uarts themselves;
+ // see porFlags for information from the box i.d's.
+ unsigned char porPorts1;
+
+ unsigned char porDiag1; // Results of on-board P.O.S.T, 1st byte
+ unsigned char porDiag2; // Results of on-board P.O.S.T, 2nd byte
+ unsigned char porSpeed; // Speed of local CPU: given as MHz x10
+ // e.g., 16.0 MHz CPU is reported as 160
+ unsigned char porFlags; // Misc information (see manifests below)
+ // Bit-mapped: CPU type, UART's present
+
+ unsigned char porPorts2; // -II: Undefined
+ // -IIEX: Bit-map of UARTS found, MSB (see
+ // above for LSB)
+
+ // IntelliPort-II: undefined
+ // IntelliPort-IIEX: 1 << porFifoSize gives the size, in bytes, of the
+ // host interface FIFO, in each direction. When running the -IIEX in
+ // 8-bit mode, fifo capacity is halved. The bootstrap firmware will
+ // have already accounted for this fact in generating this number.
+ unsigned char porFifoSize;
+
+ // IntelliPort-II: undefined
+ // IntelliPort-IIEX: The number of boxes connected. (Presently 1-4)
+ unsigned char porNumBoxes;
+ } e;
+} porStr, *porStrPtr;
+
+//--------------------------
+// Values for porStr fields
+//--------------------------
+
+//---------------------
+// porMagic1, porMagic2
+//----------------------
+//
+#define POR_MAGIC_1 0x96 // The only valid value for porMagic1
+#define POR_MAGIC_2 0x35 // The only valid value for porMagic2
+#define POR_1_INDEX 0 // Byte position of POR_MAGIC_1
+#define POR_2_INDEX 1 // Ditto for POR_MAGIC_2
+
+//----------------------
+// porID
+//----------------------
+//
+#define POR_ID_FAMILY 0xc0 // These bits indicate the general family of
+ // product.
+#define POR_ID_FII 0x00 // Family is "IntelliPort-II"
+#define POR_ID_FIIEX 0x40 // Family is "IntelliPort-IIEX"
+
+// These bits are reserved, presently zero. May be used at a later date to
+// convey other product information.
+//
+#define POR_ID_RESERVED 0x3c
+
+#define POR_ID_SIZE 0x03 // Remaining bits indicate number of ports &
+ // Connector information.
+#define POR_ID_II_8 0x00 // For IntelliPort-II, indicates 8-port using
+ // standard brick.
+#define POR_ID_II_8R 0x01 // For IntelliPort-II, indicates 8-port using
+ // RJ11's (no CTS)
+#define POR_ID_II_6 0x02 // For IntelliPort-II, indicates 6-port using
+ // RJ45's
+#define POR_ID_II_4 0x03 // For IntelliPort-II, indicates 4-port using
+ // 4xRJ45 connectors
+#define POR_ID_EX 0x00 // For IntelliPort-IIEX, indicates standard
+ // expandable controller (other values reserved)
+
+//----------------------
+// porBus
+//----------------------
+
+// IntelliPort-IIEX only: Board is installed in a 16-bit slot
+//
+#define POR_BUS_SLOT16 0x20
+
+// IntelliPort-IIEX only: DIP switch #8 is on, selecting 16-bit host interface
+// operation.
+//
+#define POR_BUS_DIP16 0x10
+
+// Bits 0-2 indicate type of bus: This information is stored in the bootstrap
+// loadware, different loadware being used on different products for different
+// buses. For most situations, the drivers do not need this information; but it
+// is handy in a diagnostic environment. For example, on microchannel boards,
+// you would not want to try to test several interrupts, only the one for which
+// you were configured.
+//
+#define POR_BUS_TYPE 0x07
+
+// Unknown: this product doesn't know what bus it is running in. (e.g. if same
+// bootstrap firmware were wanted for two different buses.)
+//
+#define POR_BUS_T_UNK 0
+
+// Note: existing firmware for ISA-8 and MC-8 currently report the POR_BUS_T_UNK
+// state, since the same bootstrap firmware is used for each.
+
+#define POR_BUS_T_MCA 1 // MCA BUS */
+#define POR_BUS_T_EISA 2 // EISA BUS */
+#define POR_BUS_T_ISA 3 // ISA BUS */
+
+// Values 4-7 Reserved
+
+// Remaining bits are reserved
+
+//----------------------
+// porDiag1
+//----------------------
+
+#define POR_BAD_MAPPER 0x80 // HW failure on P.O.S.T: Chip mapper failed
+
+// These two bits valid only for the IntelliPort-II
+//
+#define POR_BAD_UART1 0x01 // First 1400 bad
+#define POR_BAD_UART2 0x02 // Second 1400 bad
+
+//----------------------
+// porDiag2
+//----------------------
+
+#define POR_DEBUG_PORT 0x80 // debug port was detected by the P.O.S.T
+#define POR_DIAG_OK 0x00 // Indicates passage: Failure codes not yet
+ // available.
+ // Other bits undefined.
+//----------------------
+// porFlags
+//----------------------
+
+#define POR_CPU 0x03 // These bits indicate supposed CPU type
+#define POR_CPU_8 0x01 // Board uses an 80188 (no such thing yet)
+#define POR_CPU_6 0x02 // Board uses an 80186 (all existing products)
+#define POR_CEX4 0x04 // If set, this is an ISA-CEX/4: An ISA-4 (asic)
+ // which is architected like an ISA-CEX connected
+ // to a (hitherto impossible) 4-port box.
+#define POR_BOXES 0xf0 // Valid for IntelliPort-IIEX only: Map of Box
+ // sizes based on box I.D.
+#define POR_BOX_16 0x10 // Set indicates 16-port, clear 8-port
+
+//-------------------------------------
+// LOADWARE and DOWNLOADING CODE
+//-------------------------------------
+
+/*
+Loadware may be sent to the board in two ways:
+1) It may be read from a (binary image) data file block by block as each block
+ is sent to the board. This is only possible when the initialization is
+ performed by code which can access your file system. This is most suitable
+ for diagnostics and appications which use the interface library directly.
+
+2) It may be hard-coded into your source by including a .h file (typically
+ supplied by Computone), which declares a data array and initializes every
+ element. This acheives the same result as if an entire loadware file had
+ been read into the array.
+
+ This requires more data space in your program, but access to the file system
+ is not required. This method is more suited to driver code, which typically
+ is running at a level too low to access the file system directly.
+
+At present, loadware can only be generated at Computone.
+
+All Loadware begins with a header area which has a particular format. This
+includes a magic number which identifies the file as being (purportedly)
+loadware, CRC (for the loader), and version information.
+*/
+
+
+//-----------------------------------------------------------------------------
+// Format of loadware block
+//
+// This is defined as a union so we can pass a pointer to one of these items
+// and (if it is the first block) pick out the version information, etc.
+//
+// Otherwise, to deal with this as a simple character array
+//------------------------------------------------------------------------------
+
+#define LOADWARE_BLOCK_SIZE 512 // Number of bytes in each block of loadware
+
+typedef union _loadHdrStr
+{
+ unsigned char c[LOADWARE_BLOCK_SIZE]; // Valid for every block
+
+ struct // These fields are valid for only the first block of loadware.
+ {
+ unsigned char loadMagic; // Magic number: see below
+ unsigned char loadBlocksMore; // How many more blocks?
+ unsigned char loadCRC[2]; // Two CRC bytes: used by loader
+ unsigned char loadVersion; // Version number
+ unsigned char loadRevision; // Revision number
+ unsigned char loadSubRevision; // Sub-revision number
+ unsigned char loadSpares[9]; // Presently unused
+ unsigned char loadDates[32]; // Null-terminated string which can give
+ // date and time of compilation
+ } e;
+} loadHdrStr, *loadHdrStrPtr;
+
+//------------------------------------
+// Defines for downloading code:
+//------------------------------------
+
+// The loadMagic field in the first block of the loadfile must be this, else the
+// file is not valid.
+//
+#define MAGIC_LOADFILE 0x3c
+
+// How do we know the load was successful? On completion of the load, the
+// bootstrap firmware returns a code to indicate whether it thought the download
+// was valid and intends to execute it. These are the only possible valid codes:
+//
+#define LOADWARE_OK 0xc3 // Download was ok
+#define LOADWARE_BAD 0x5a // Download was bad (CRC error)
+
+// Constants applicable to writing blocks of loadware:
+// The first block of loadware might take 600 mS to load, in extreme cases.
+// (Expandable board: worst case for sending startup messages to the LCD's).
+// The 600mS figure is not really a calculation, but a conservative
+// guess/guarantee. Usually this will be within 100 mS, like subsequent blocks.
+//
+#define MAX_DLOAD_START_TIME 1000 // 1000 mS
+#define MAX_DLOAD_READ_TIME 100 // 100 mS
+
+// Firmware should respond with status (see above) within this long of host
+// having sent the final block.
+//
+#define MAX_DLOAD_ACK_TIME 100 // 100 mS, again!
+
+//------------------------------------------------------
+// MAXIMUM NUMBER OF PORTS PER BOARD:
+// This is fixed for now (with the expandable), but may
+// be expanding according to even newer products.
+//------------------------------------------------------
+//
+#define ABS_MAX_BOXES 4 // Absolute most boxes per board
+#define ABS_BIGGEST_BOX 16 // Absolute the most ports per box
+#define ABS_MOST_PORTS (ABS_MAX_BOXES * ABS_BIGGEST_BOX)
+
+#endif // I2HW_H
+
diff --git a/drivers/char/ip2/i2lib.c b/drivers/char/ip2/i2lib.c
new file mode 100644
index 000000000000..82c5f30375ac
--- /dev/null
+++ b/drivers/char/ip2/i2lib.c
@@ -0,0 +1,2219 @@
+/*******************************************************************************
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: High-level interface code for the device driver. Uses the
+* Extremely Low Level Interface Support (i2ellis.c). Provides an
+* interface to the standard loadware, to support drivers or
+* application code. (This is included source code, not a separate
+* compilation module.)
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// Note on Strategy:
+// Once the board has been initialized, it will interrupt us when:
+// 1) It has something in the fifo for us to read (incoming data, flow control
+// packets, or whatever).
+// 2) It has stripped whatever we have sent last time in the FIFO (and
+// consequently is ready for more).
+//
+// Note also that the buffer sizes declared in i2lib.h are VERY SMALL. This
+// worsens performance considerably, but is done so that a great many channels
+// might use only a little memory.
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 0.00 - 4/16/91 --- First Draft
+// 0.01 - 4/29/91 --- 1st beta release
+// 0.02 - 6/14/91 --- Changes to allow small model compilation
+// 0.03 - 6/17/91 MAG Break reporting protected from interrupts routines with
+// in-line asm added for moving data to/from ring buffers,
+// replacing a variety of methods used previously.
+// 0.04 - 6/21/91 MAG Initial flow-control packets not queued until
+// i2_enable_interrupts time. Former versions would enqueue
+// them at i2_init_channel time, before we knew how many
+// channels were supposed to exist!
+// 0.05 - 10/12/91 MAG Major changes: works through the ellis.c routines now;
+// supports new 16-bit protocol and expandable boards.
+// - 10/24/91 MAG Most changes in place and stable.
+// 0.06 - 2/20/92 MAG Format of CMD_HOTACK corrected: the command takes no
+// argument.
+// 0.07 -- 3/11/92 MAG Support added to store special packet types at interrupt
+// level (mostly responses to specific commands.)
+// 0.08 -- 3/30/92 MAG Support added for STAT_MODEM packet
+// 0.09 -- 6/24/93 MAG i2Link... needed to update number of boards BEFORE
+// turning on the interrupt.
+// 0.10 -- 6/25/93 MAG To avoid gruesome death from a bad board, we sanity check
+// some incoming.
+//
+// 1.1 - 12/25/96 AKM Linux version.
+// - 10/09/98 DMC Revised Linux version.
+//------------------------------------------------------------------------------
+
+//************
+//* Includes *
+//************
+
+#include <linux/sched.h>
+#include "i2lib.h"
+
+
+//***********************
+//* Function Prototypes *
+//***********************
+static void i2QueueNeeds(i2eBordStrPtr, i2ChanStrPtr, int);
+static i2ChanStrPtr i2DeQueueNeeds(i2eBordStrPtr, int );
+static void i2StripFifo(i2eBordStrPtr);
+static void i2StuffFifoBypass(i2eBordStrPtr);
+static void i2StuffFifoFlow(i2eBordStrPtr);
+static void i2StuffFifoInline(i2eBordStrPtr);
+static int i2RetryFlushOutput(i2ChanStrPtr);
+
+// Not a documented part of the library routines (careful...) but the Diagnostic
+// i2diag.c finds them useful to help the throughput in certain limited
+// single-threaded operations.
+static void iiSendPendingMail(i2eBordStrPtr);
+static void serviceOutgoingFifo(i2eBordStrPtr);
+
+// Functions defined in ip2.c as part of interrupt handling
+static void do_input(void *);
+static void do_status(void *);
+
+//***************
+//* Debug Data *
+//***************
+#ifdef DEBUG_FIFO
+
+unsigned char DBGBuf[0x4000];
+unsigned short I = 0;
+
+static void
+WriteDBGBuf(char *s, unsigned char *src, unsigned short n )
+{
+ char *p = src;
+
+ // XXX: We need a spin lock here if we ever use this again
+
+ while (*s) { // copy label
+ DBGBuf[I] = *s++;
+ I = I++ & 0x3fff;
+ }
+ while (n--) { // copy data
+ DBGBuf[I] = *p++;
+ I = I++ & 0x3fff;
+ }
+}
+
+static void
+fatality(i2eBordStrPtr pB )
+{
+ int i;
+
+ for (i=0;i<sizeof(DBGBuf);i++) {
+ if ((i%16) == 0)
+ printk("\n%4x:",i);
+ printk("%02x ",DBGBuf[i]);
+ }
+ printk("\n");
+ for (i=0;i<sizeof(DBGBuf);i++) {
+ if ((i%16) == 0)
+ printk("\n%4x:",i);
+ if (DBGBuf[i] >= ' ' && DBGBuf[i] <= '~') {
+ printk(" %c ",DBGBuf[i]);
+ } else {
+ printk(" . ");
+ }
+ }
+ printk("\n");
+ printk("Last index %x\n",I);
+}
+#endif /* DEBUG_FIFO */
+
+//********
+//* Code *
+//********
+
+static inline int
+i2Validate ( i2ChanStrPtr pCh )
+{
+ //ip2trace(pCh->port_index, ITRC_VERIFY,ITRC_ENTER,2,pCh->validity,
+ // (CHANNEL_MAGIC | CHANNEL_SUPPORT));
+ return ((pCh->validity & (CHANNEL_MAGIC_BITS | CHANNEL_SUPPORT))
+ == (CHANNEL_MAGIC | CHANNEL_SUPPORT));
+}
+
+//******************************************************************************
+// Function: iiSendPendingMail(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// If any outgoing mail bits are set and there is outgoing mailbox is empty,
+// send the mail and clear the bits.
+//******************************************************************************
+static inline void
+iiSendPendingMail(i2eBordStrPtr pB)
+{
+ if (pB->i2eOutMailWaiting && (!pB->i2eWaitingForEmptyFifo) )
+ {
+ if (iiTrySendMail(pB, pB->i2eOutMailWaiting))
+ {
+ /* If we were already waiting for fifo to empty,
+ * or just sent MB_OUT_STUFFED, then we are
+ * still waiting for it to empty, until we should
+ * receive an MB_IN_STRIPPED from the board.
+ */
+ pB->i2eWaitingForEmptyFifo |=
+ (pB->i2eOutMailWaiting & MB_OUT_STUFFED);
+ pB->i2eOutMailWaiting = 0;
+ pB->SendPendingRetry = 0;
+ } else {
+/* The only time we hit this area is when "iiTrySendMail" has
+ failed. That only occurs when the outbound mailbox is
+ still busy with the last message. We take a short breather
+ to let the board catch up with itself and then try again.
+ 16 Retries is the limit - then we got a borked board.
+ /\/\|=mhw=|\/\/ */
+
+ if( ++pB->SendPendingRetry < 16 ) {
+
+ init_timer( &(pB->SendPendingTimer) );
+ pB->SendPendingTimer.expires = jiffies + 1;
+ pB->SendPendingTimer.function = (void*)(unsigned long)iiSendPendingMail;
+ pB->SendPendingTimer.data = (unsigned long)pB;
+ add_timer( &(pB->SendPendingTimer) );
+ } else {
+ printk( KERN_ERR "IP2: iiSendPendingMail unable to queue outbound mail\n" );
+ }
+ }
+ }
+}
+
+//******************************************************************************
+// Function: i2InitChannels(pB, nChannels, pCh)
+// Parameters: Pointer to Ellis Board structure
+// Number of channels to initialize
+// Pointer to first element in an array of channel structures
+// Returns: Success or failure
+//
+// Description:
+//
+// This function patches pointers, back-pointers, and initializes all the
+// elements in the channel structure array.
+//
+// This should be run after the board structure is initialized, through having
+// loaded the standard loadware (otherwise it complains).
+//
+// In any case, it must be done before any serious work begins initializing the
+// irq's or sending commands...
+//
+//******************************************************************************
+static int
+i2InitChannels ( i2eBordStrPtr pB, int nChannels, i2ChanStrPtr pCh)
+{
+ int index, stuffIndex;
+ i2ChanStrPtr *ppCh;
+
+ if (pB->i2eValid != I2E_MAGIC) {
+ COMPLETE(pB, I2EE_BADMAGIC);
+ }
+ if (pB->i2eState != II_STATE_STDLOADED) {
+ COMPLETE(pB, I2EE_BADSTATE);
+ }
+
+ LOCK_INIT(&pB->read_fifo_spinlock);
+ LOCK_INIT(&pB->write_fifo_spinlock);
+ LOCK_INIT(&pB->Dbuf_spinlock);
+ LOCK_INIT(&pB->Bbuf_spinlock);
+ LOCK_INIT(&pB->Fbuf_spinlock);
+
+ // NO LOCK needed yet - this is init
+
+ pB->i2eChannelPtr = pCh;
+ pB->i2eChannelCnt = nChannels;
+
+ pB->i2Fbuf_strip = pB->i2Fbuf_stuff = 0;
+ pB->i2Dbuf_strip = pB->i2Dbuf_stuff = 0;
+ pB->i2Bbuf_strip = pB->i2Bbuf_stuff = 0;
+
+ pB->SendPendingRetry = 0;
+
+ memset ( pCh, 0, sizeof (i2ChanStr) * nChannels );
+
+ for (index = stuffIndex = 0, ppCh = (i2ChanStrPtr *)(pB->i2Fbuf);
+ nChannels && index < ABS_MOST_PORTS;
+ index++)
+ {
+ if ( !(pB->i2eChannelMap[index >> 4] & (1 << (index & 0xf)) ) ) {
+ continue;
+ }
+ LOCK_INIT(&pCh->Ibuf_spinlock);
+ LOCK_INIT(&pCh->Obuf_spinlock);
+ LOCK_INIT(&pCh->Cbuf_spinlock);
+ LOCK_INIT(&pCh->Pbuf_spinlock);
+ // NO LOCK needed yet - this is init
+ // Set up validity flag according to support level
+ if (pB->i2eGoodMap[index >> 4] & (1 << (index & 0xf)) ) {
+ pCh->validity = CHANNEL_MAGIC | CHANNEL_SUPPORT;
+ } else {
+ pCh->validity = CHANNEL_MAGIC;
+ }
+ pCh->pMyBord = pB; /* Back-pointer */
+
+ // Prepare an outgoing flow-control packet to send as soon as the chance
+ // occurs.
+ if ( pCh->validity & CHANNEL_SUPPORT ) {
+ pCh->infl.hd.i2sChannel = index;
+ pCh->infl.hd.i2sCount = 5;
+ pCh->infl.hd.i2sType = PTYPE_BYPASS;
+ pCh->infl.fcmd = 37;
+ pCh->infl.asof = 0;
+ pCh->infl.room = IBUF_SIZE - 1;
+
+ pCh->whenSendFlow = (IBUF_SIZE/5)*4; // when 80% full
+
+ // The following is similar to calling i2QueueNeeds, except that this
+ // is done in longhand, since we are setting up initial conditions on
+ // many channels at once.
+ pCh->channelNeeds = NEED_FLOW; // Since starting from scratch
+ pCh->sinceLastFlow = 0; // No bytes received since last flow
+ // control packet was queued
+ stuffIndex++;
+ *ppCh++ = pCh; // List this channel as needing
+ // initial flow control packet sent
+ }
+
+ // Don't allow anything to be sent until the status packets come in from
+ // the board.
+
+ pCh->outfl.asof = 0;
+ pCh->outfl.room = 0;
+
+ // Initialize all the ring buffers
+
+ pCh->Ibuf_stuff = pCh->Ibuf_strip = 0;
+ pCh->Obuf_stuff = pCh->Obuf_strip = 0;
+ pCh->Cbuf_stuff = pCh->Cbuf_strip = 0;
+
+ memset( &pCh->icount, 0, sizeof (struct async_icount) );
+ pCh->hotKeyIn = HOT_CLEAR;
+ pCh->channelOptions = 0;
+ pCh->bookMarks = 0;
+ init_waitqueue_head(&pCh->pBookmarkWait);
+
+ init_waitqueue_head(&pCh->open_wait);
+ init_waitqueue_head(&pCh->close_wait);
+ init_waitqueue_head(&pCh->delta_msr_wait);
+
+ // Set base and divisor so default custom rate is 9600
+ pCh->BaudBase = 921600; // MAX for ST654, changed after we get
+ pCh->BaudDivisor = 96; // the boxids (UART types) later
+
+ pCh->dataSetIn = 0;
+ pCh->dataSetOut = 0;
+
+ pCh->wopen = 0;
+ pCh->throttled = 0;
+
+ pCh->speed = CBR_9600;
+
+ pCh->flags = 0;
+
+ pCh->ClosingDelay = 5*HZ/10;
+ pCh->ClosingWaitTime = 30*HZ;
+
+ // Initialize task queue objects
+ INIT_WORK(&pCh->tqueue_input, do_input, pCh);
+ INIT_WORK(&pCh->tqueue_status, do_status, pCh);
+
+#ifdef IP2DEBUG_TRACE
+ pCh->trace = ip2trace;
+#endif
+
+ ++pCh;
+ --nChannels;
+ }
+ // No need to check for wrap here; this is initialization.
+ pB->i2Fbuf_stuff = stuffIndex;
+ COMPLETE(pB, I2EE_GOOD);
+
+}
+
+//******************************************************************************
+// Function: i2DeQueueNeeds(pB, type)
+// Parameters: Pointer to a board structure
+// type bit map: may include NEED_INLINE, NEED_BYPASS, or NEED_FLOW
+// Returns:
+// Pointer to a channel structure
+//
+// Description: Returns pointer struct of next channel that needs service of
+// the type specified. Otherwise returns a NULL reference.
+//
+//******************************************************************************
+static i2ChanStrPtr
+i2DeQueueNeeds(i2eBordStrPtr pB, int type)
+{
+ unsigned short queueIndex;
+ unsigned long flags;
+
+ i2ChanStrPtr pCh = NULL;
+
+ switch(type) {
+
+ case NEED_INLINE:
+
+ WRITE_LOCK_IRQSAVE(&pB->Dbuf_spinlock,flags);
+ if ( pB->i2Dbuf_stuff != pB->i2Dbuf_strip)
+ {
+ queueIndex = pB->i2Dbuf_strip;
+ pCh = pB->i2Dbuf[queueIndex];
+ queueIndex++;
+ if (queueIndex >= CH_QUEUE_SIZE) {
+ queueIndex = 0;
+ }
+ pB->i2Dbuf_strip = queueIndex;
+ pCh->channelNeeds &= ~NEED_INLINE;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Dbuf_spinlock,flags);
+ break;
+
+ case NEED_BYPASS:
+
+ WRITE_LOCK_IRQSAVE(&pB->Bbuf_spinlock,flags);
+ if (pB->i2Bbuf_stuff != pB->i2Bbuf_strip)
+ {
+ queueIndex = pB->i2Bbuf_strip;
+ pCh = pB->i2Bbuf[queueIndex];
+ queueIndex++;
+ if (queueIndex >= CH_QUEUE_SIZE) {
+ queueIndex = 0;
+ }
+ pB->i2Bbuf_strip = queueIndex;
+ pCh->channelNeeds &= ~NEED_BYPASS;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Bbuf_spinlock,flags);
+ break;
+
+ case NEED_FLOW:
+
+ WRITE_LOCK_IRQSAVE(&pB->Fbuf_spinlock,flags);
+ if (pB->i2Fbuf_stuff != pB->i2Fbuf_strip)
+ {
+ queueIndex = pB->i2Fbuf_strip;
+ pCh = pB->i2Fbuf[queueIndex];
+ queueIndex++;
+ if (queueIndex >= CH_QUEUE_SIZE) {
+ queueIndex = 0;
+ }
+ pB->i2Fbuf_strip = queueIndex;
+ pCh->channelNeeds &= ~NEED_FLOW;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Fbuf_spinlock,flags);
+ break;
+ default:
+ printk(KERN_ERR "i2DeQueueNeeds called with bad type:%x\n",type);
+ break;
+ }
+ return pCh;
+}
+
+//******************************************************************************
+// Function: i2QueueNeeds(pB, pCh, type)
+// Parameters: Pointer to a board structure
+// Pointer to a channel structure
+// type bit map: may include NEED_INLINE, NEED_BYPASS, or NEED_FLOW
+// Returns: Nothing
+//
+// Description:
+// For each type of need selected, if the given channel is not already in the
+// queue, adds it, and sets the flag indicating it is in the queue.
+//******************************************************************************
+static void
+i2QueueNeeds(i2eBordStrPtr pB, i2ChanStrPtr pCh, int type)
+{
+ unsigned short queueIndex;
+ unsigned long flags;
+
+ // We turn off all the interrupts during this brief process, since the
+ // interrupt-level code might want to put things on the queue as well.
+
+ switch (type) {
+
+ case NEED_INLINE:
+
+ WRITE_LOCK_IRQSAVE(&pB->Dbuf_spinlock,flags);
+ if ( !(pCh->channelNeeds & NEED_INLINE) )
+ {
+ pCh->channelNeeds |= NEED_INLINE;
+ queueIndex = pB->i2Dbuf_stuff;
+ pB->i2Dbuf[queueIndex++] = pCh;
+ if (queueIndex >= CH_QUEUE_SIZE)
+ queueIndex = 0;
+ pB->i2Dbuf_stuff = queueIndex;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Dbuf_spinlock,flags);
+ break;
+
+ case NEED_BYPASS:
+
+ WRITE_LOCK_IRQSAVE(&pB->Bbuf_spinlock,flags);
+ if ((type & NEED_BYPASS) && !(pCh->channelNeeds & NEED_BYPASS))
+ {
+ pCh->channelNeeds |= NEED_BYPASS;
+ queueIndex = pB->i2Bbuf_stuff;
+ pB->i2Bbuf[queueIndex++] = pCh;
+ if (queueIndex >= CH_QUEUE_SIZE)
+ queueIndex = 0;
+ pB->i2Bbuf_stuff = queueIndex;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Bbuf_spinlock,flags);
+ break;
+
+ case NEED_FLOW:
+
+ WRITE_LOCK_IRQSAVE(&pB->Fbuf_spinlock,flags);
+ if ((type & NEED_FLOW) && !(pCh->channelNeeds & NEED_FLOW))
+ {
+ pCh->channelNeeds |= NEED_FLOW;
+ queueIndex = pB->i2Fbuf_stuff;
+ pB->i2Fbuf[queueIndex++] = pCh;
+ if (queueIndex >= CH_QUEUE_SIZE)
+ queueIndex = 0;
+ pB->i2Fbuf_stuff = queueIndex;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->Fbuf_spinlock,flags);
+ break;
+
+ case NEED_CREDIT:
+ pCh->channelNeeds |= NEED_CREDIT;
+ break;
+ default:
+ printk(KERN_ERR "i2QueueNeeds called with bad type:%x\n",type);
+ break;
+ }
+ return;
+}
+
+//******************************************************************************
+// Function: i2QueueCommands(type, pCh, timeout, nCommands, pCs,...)
+// Parameters: type - PTYPE_BYPASS or PTYPE_INLINE
+// pointer to the channel structure
+// maximum period to wait
+// number of commands (n)
+// n commands
+// Returns: Number of commands sent, or -1 for error
+//
+// get board lock before calling
+//
+// Description:
+// Queues up some commands to be sent to a channel. To send possibly several
+// bypass or inline commands to the given channel. The timeout parameter
+// indicates how many HUNDREDTHS OF SECONDS to wait until there is room:
+// 0 = return immediately if no room, -ive = wait forever, +ive = number of
+// 1/100 seconds to wait. Return values:
+// -1 Some kind of nasty error: bad channel structure or invalid arguments.
+// 0 No room to send all the commands
+// (+) Number of commands sent
+//******************************************************************************
+static int
+i2QueueCommands(int type, i2ChanStrPtr pCh, int timeout, int nCommands,
+ cmdSyntaxPtr pCs0,...)
+{
+ int totalsize = 0;
+ int blocksize;
+ int lastended;
+ cmdSyntaxPtr *ppCs;
+ cmdSyntaxPtr pCs;
+ int count;
+ int flag;
+ i2eBordStrPtr pB;
+
+ unsigned short maxBlock;
+ unsigned short maxBuff;
+ short bufroom;
+ unsigned short stuffIndex;
+ unsigned char *pBuf;
+ unsigned char *pInsert;
+ unsigned char *pDest, *pSource;
+ unsigned short channel;
+ int cnt;
+ unsigned long flags = 0;
+ rwlock_t *lock_var_p = NULL;
+
+ // Make sure the channel exists, otherwise do nothing
+ if ( !i2Validate ( pCh ) ) {
+ return -1;
+ }
+
+ ip2trace (CHANN, ITRC_QUEUE, ITRC_ENTER, 0 );
+
+ pB = pCh->pMyBord;
+
+ // Board must also exist, and THE INTERRUPT COMMAND ALREADY SENT
+ if (pB->i2eValid != I2E_MAGIC || pB->i2eUsingIrq == IRQ_UNDEFINED) {
+ return -2;
+ }
+ // If the board has gone fatal, return bad, and also hit the trap routine if
+ // it exists.
+ if (pB->i2eFatal) {
+ if ( pB->i2eFatalTrap ) {
+ (*(pB)->i2eFatalTrap)(pB);
+ }
+ return -3;
+ }
+ // Set up some variables, Which buffers are we using? How big are they?
+ switch(type)
+ {
+ case PTYPE_INLINE:
+ flag = INL;
+ maxBlock = MAX_OBUF_BLOCK;
+ maxBuff = OBUF_SIZE;
+ pBuf = pCh->Obuf;
+ break;
+ case PTYPE_BYPASS:
+ flag = BYP;
+ maxBlock = MAX_CBUF_BLOCK;
+ maxBuff = CBUF_SIZE;
+ pBuf = pCh->Cbuf;
+ break;
+ default:
+ return -4;
+ }
+ // Determine the total size required for all the commands
+ totalsize = blocksize = sizeof(i2CmdHeader);
+ lastended = 0;
+ ppCs = &pCs0;
+ for ( count = nCommands; count; count--, ppCs++)
+ {
+ pCs = *ppCs;
+ cnt = pCs->length;
+ // Will a new block be needed for this one?
+ // Two possible reasons: too
+ // big or previous command has to be at the end of a packet.
+ if ((blocksize + cnt > maxBlock) || lastended) {
+ blocksize = sizeof(i2CmdHeader);
+ totalsize += sizeof(i2CmdHeader);
+ }
+ totalsize += cnt;
+ blocksize += cnt;
+
+ // If this command had to end a block, then we will make sure to
+ // account for it should there be any more blocks.
+ lastended = pCs->flags & END;
+ }
+ for (;;) {
+ // Make sure any pending flush commands go out before we add more data.
+ if ( !( pCh->flush_flags && i2RetryFlushOutput( pCh ) ) ) {
+ // How much room (this time through) ?
+ switch(type) {
+ case PTYPE_INLINE:
+ lock_var_p = &pCh->Obuf_spinlock;
+ WRITE_LOCK_IRQSAVE(lock_var_p,flags);
+ stuffIndex = pCh->Obuf_stuff;
+ bufroom = pCh->Obuf_strip - stuffIndex;
+ break;
+ case PTYPE_BYPASS:
+ lock_var_p = &pCh->Cbuf_spinlock;
+ WRITE_LOCK_IRQSAVE(lock_var_p,flags);
+ stuffIndex = pCh->Cbuf_stuff;
+ bufroom = pCh->Cbuf_strip - stuffIndex;
+ break;
+ default:
+ return -5;
+ }
+ if (--bufroom < 0) {
+ bufroom += maxBuff;
+ }
+
+ ip2trace (CHANN, ITRC_QUEUE, 2, 1, bufroom );
+
+ // Check for overflow
+ if (totalsize <= bufroom) {
+ // Normal Expected path - We still hold LOCK
+ break; /* from for()- Enough room: goto proceed */
+ }
+ }
+
+ ip2trace (CHANN, ITRC_QUEUE, 3, 1, totalsize );
+
+ // Prepare to wait for buffers to empty
+ WRITE_UNLOCK_IRQRESTORE(lock_var_p,flags);
+ serviceOutgoingFifo(pB); // Dump what we got
+
+ if (timeout == 0) {
+ return 0; // Tired of waiting
+ }
+ if (timeout > 0)
+ timeout--; // So negative values == forever
+
+ if (!in_interrupt()) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1); // short nap
+ } else {
+ // we cannot sched/sleep in interrrupt silly
+ return 0;
+ }
+ if (signal_pending(current)) {
+ return 0; // Wake up! Time to die!!!
+ }
+
+ ip2trace (CHANN, ITRC_QUEUE, 4, 0 );
+
+ } // end of for(;;)
+
+ // At this point we have room and the lock - stick them in.
+ channel = pCh->infl.hd.i2sChannel;
+ pInsert = &pBuf[stuffIndex]; // Pointer to start of packet
+ pDest = CMD_OF(pInsert); // Pointer to start of command
+
+ // When we start counting, the block is the size of the header
+ for (blocksize = sizeof(i2CmdHeader), count = nCommands,
+ lastended = 0, ppCs = &pCs0;
+ count;
+ count--, ppCs++)
+ {
+ pCs = *ppCs; // Points to command protocol structure
+
+ // If this is a bookmark request command, post the fact that a bookmark
+ // request is pending. NOTE THIS TRICK ONLY WORKS BECAUSE CMD_BMARK_REQ
+ // has no parameters! The more general solution would be to reference
+ // pCs->cmd[0].
+ if (pCs == CMD_BMARK_REQ) {
+ pCh->bookMarks++;
+
+ ip2trace (CHANN, ITRC_DRAIN, 30, 1, pCh->bookMarks );
+
+ }
+ cnt = pCs->length;
+
+ // If this command would put us over the maximum block size or
+ // if the last command had to be at the end of a block, we end
+ // the existing block here and start a new one.
+ if ((blocksize + cnt > maxBlock) || lastended) {
+
+ ip2trace (CHANN, ITRC_QUEUE, 5, 0 );
+
+ PTYPE_OF(pInsert) = type;
+ CHANNEL_OF(pInsert) = channel;
+ // count here does not include the header
+ CMD_COUNT_OF(pInsert) = blocksize - sizeof(i2CmdHeader);
+ stuffIndex += blocksize;
+ if(stuffIndex >= maxBuff) {
+ stuffIndex = 0;
+ pInsert = pBuf;
+ }
+ pInsert = &pBuf[stuffIndex]; // Pointer to start of next pkt
+ pDest = CMD_OF(pInsert);
+ blocksize = sizeof(i2CmdHeader);
+ }
+ // Now we know there is room for this one in the current block
+
+ blocksize += cnt; // Total bytes in this command
+ pSource = pCs->cmd; // Copy the command into the buffer
+ while (cnt--) {
+ *pDest++ = *pSource++;
+ }
+ // If this command had to end a block, then we will make sure to account
+ // for it should there be any more blocks.
+ lastended = pCs->flags & END;
+ } // end for
+ // Clean up the final block by writing header, etc
+
+ PTYPE_OF(pInsert) = type;
+ CHANNEL_OF(pInsert) = channel;
+ // count here does not include the header
+ CMD_COUNT_OF(pInsert) = blocksize - sizeof(i2CmdHeader);
+ stuffIndex += blocksize;
+ if(stuffIndex >= maxBuff) {
+ stuffIndex = 0;
+ pInsert = pBuf;
+ }
+ // Updates the index, and post the need for service. When adding these to
+ // the queue of channels, we turn off the interrupt while doing so,
+ // because at interrupt level we might want to push a channel back to the
+ // end of the queue.
+ switch(type)
+ {
+ case PTYPE_INLINE:
+ pCh->Obuf_stuff = stuffIndex; // Store buffer pointer
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+
+ pB->debugInlineQueued++;
+ // Add the channel pointer to list of channels needing service (first
+ // come...), if it's not already there.
+ i2QueueNeeds(pB, pCh, NEED_INLINE);
+ break;
+
+ case PTYPE_BYPASS:
+ pCh->Cbuf_stuff = stuffIndex; // Store buffer pointer
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Cbuf_spinlock,flags);
+
+ pB->debugBypassQueued++;
+ // Add the channel pointer to list of channels needing service (first
+ // come...), if it's not already there.
+ i2QueueNeeds(pB, pCh, NEED_BYPASS);
+ break;
+ }
+
+ ip2trace (CHANN, ITRC_QUEUE, ITRC_RETURN, 1, nCommands );
+
+ return nCommands; // Good status: number of commands sent
+}
+
+//******************************************************************************
+// Function: i2GetStatus(pCh,resetBits)
+// Parameters: Pointer to a channel structure
+// Bit map of status bits to clear
+// Returns: Bit map of current status bits
+//
+// Description:
+// Returns the state of data set signals, and whether a break has been received,
+// (see i2lib.h for bit-mapped result). resetBits is a bit-map of any status
+// bits to be cleared: I2_BRK, I2_PAR, I2_FRA, I2_OVR,... These are cleared
+// AFTER the condition is passed. If pCh does not point to a valid channel,
+// returns -1 (which would be impossible otherwise.
+//******************************************************************************
+static int
+i2GetStatus(i2ChanStrPtr pCh, int resetBits)
+{
+ unsigned short status;
+ i2eBordStrPtr pB;
+
+ ip2trace (CHANN, ITRC_STATUS, ITRC_ENTER, 2, pCh->dataSetIn, resetBits );
+
+ // Make sure the channel exists, otherwise do nothing */
+ if ( !i2Validate ( pCh ) )
+ return -1;
+
+ pB = pCh->pMyBord;
+
+ status = pCh->dataSetIn;
+
+ // Clear any specified error bits: but note that only actual error bits can
+ // be cleared, regardless of the value passed.
+ if (resetBits)
+ {
+ pCh->dataSetIn &= ~(resetBits & (I2_BRK | I2_PAR | I2_FRA | I2_OVR));
+ pCh->dataSetIn &= ~(I2_DDCD | I2_DCTS | I2_DDSR | I2_DRI);
+ }
+
+ ip2trace (CHANN, ITRC_STATUS, ITRC_RETURN, 1, pCh->dataSetIn );
+
+ return status;
+}
+
+//******************************************************************************
+// Function: i2Input(pChpDest,count)
+// Parameters: Pointer to a channel structure
+// Pointer to data buffer
+// Number of bytes to read
+// Returns: Number of bytes read, or -1 for error
+//
+// Description:
+// Strips data from the input buffer and writes it to pDest. If there is a
+// collosal blunder, (invalid structure pointers or the like), returns -1.
+// Otherwise, returns the number of bytes read.
+//******************************************************************************
+static int
+i2Input(i2ChanStrPtr pCh)
+{
+ int amountToMove;
+ unsigned short stripIndex;
+ int count;
+ unsigned long flags = 0;
+
+ ip2trace (CHANN, ITRC_INPUT, ITRC_ENTER, 0);
+
+ // Ensure channel structure seems real
+ if ( !i2Validate( pCh ) ) {
+ count = -1;
+ goto i2Input_exit;
+ }
+ WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+
+ // initialize some accelerators and private copies
+ stripIndex = pCh->Ibuf_strip;
+
+ count = pCh->Ibuf_stuff - stripIndex;
+
+ // If buffer is empty or requested data count was 0, (trivial case) return
+ // without any further thought.
+ if ( count == 0 ) {
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+ goto i2Input_exit;
+ }
+ // Adjust for buffer wrap
+ if ( count < 0 ) {
+ count += IBUF_SIZE;
+ }
+ // Don't give more than can be taken by the line discipline
+ amountToMove = pCh->pTTY->ldisc.receive_room( pCh->pTTY );
+ if (count > amountToMove) {
+ count = amountToMove;
+ }
+ // How much could we copy without a wrap?
+ amountToMove = IBUF_SIZE - stripIndex;
+
+ if (amountToMove > count) {
+ amountToMove = count;
+ }
+ // Move the first block
+ pCh->pTTY->ldisc.receive_buf( pCh->pTTY,
+ &(pCh->Ibuf[stripIndex]), NULL, amountToMove );
+ // If we needed to wrap, do the second data move
+ if (count > amountToMove) {
+ pCh->pTTY->ldisc.receive_buf( pCh->pTTY,
+ pCh->Ibuf, NULL, count - amountToMove );
+ }
+ // Bump and wrap the stripIndex all at once by the amount of data read. This
+ // method is good regardless of whether the data was in one or two pieces.
+ stripIndex += count;
+ if (stripIndex >= IBUF_SIZE) {
+ stripIndex -= IBUF_SIZE;
+ }
+ pCh->Ibuf_strip = stripIndex;
+
+ // Update our flow control information and possibly queue ourselves to send
+ // it, depending on how much data has been stripped since the last time a
+ // packet was sent.
+ pCh->infl.asof += count;
+
+ if ((pCh->sinceLastFlow += count) >= pCh->whenSendFlow) {
+ pCh->sinceLastFlow -= pCh->whenSendFlow;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+ i2QueueNeeds(pCh->pMyBord, pCh, NEED_FLOW);
+ } else {
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+ }
+
+i2Input_exit:
+
+ ip2trace (CHANN, ITRC_INPUT, ITRC_RETURN, 1, count);
+
+ return count;
+}
+
+//******************************************************************************
+// Function: i2InputFlush(pCh)
+// Parameters: Pointer to a channel structure
+// Returns: Number of bytes stripped, or -1 for error
+//
+// Description:
+// Strips any data from the input buffer. If there is a collosal blunder,
+// (invalid structure pointers or the like), returns -1. Otherwise, returns the
+// number of bytes stripped.
+//******************************************************************************
+static int
+i2InputFlush(i2ChanStrPtr pCh)
+{
+ int count;
+ unsigned long flags;
+
+ // Ensure channel structure seems real
+ if ( !i2Validate ( pCh ) )
+ return -1;
+
+ ip2trace (CHANN, ITRC_INPUT, 10, 0);
+
+ WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+ count = pCh->Ibuf_stuff - pCh->Ibuf_strip;
+
+ // Adjust for buffer wrap
+ if (count < 0) {
+ count += IBUF_SIZE;
+ }
+
+ // Expedient way to zero out the buffer
+ pCh->Ibuf_strip = pCh->Ibuf_stuff;
+
+
+ // Update our flow control information and possibly queue ourselves to send
+ // it, depending on how much data has been stripped since the last time a
+ // packet was sent.
+
+ pCh->infl.asof += count;
+
+ if ( (pCh->sinceLastFlow += count) >= pCh->whenSendFlow )
+ {
+ pCh->sinceLastFlow -= pCh->whenSendFlow;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+ i2QueueNeeds(pCh->pMyBord, pCh, NEED_FLOW);
+ } else {
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+ }
+
+ ip2trace (CHANN, ITRC_INPUT, 19, 1, count);
+
+ return count;
+}
+
+//******************************************************************************
+// Function: i2InputAvailable(pCh)
+// Parameters: Pointer to a channel structure
+// Returns: Number of bytes available, or -1 for error
+//
+// Description:
+// If there is a collosal blunder, (invalid structure pointers or the like),
+// returns -1. Otherwise, returns the number of bytes stripped. Otherwise,
+// returns the number of bytes available in the buffer.
+//******************************************************************************
+#if 0
+static int
+i2InputAvailable(i2ChanStrPtr pCh)
+{
+ int count;
+
+ // Ensure channel structure seems real
+ if ( !i2Validate ( pCh ) ) return -1;
+
+
+ // initialize some accelerators and private copies
+ READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+ count = pCh->Ibuf_stuff - pCh->Ibuf_strip;
+ READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+
+ // Adjust for buffer wrap
+ if (count < 0)
+ {
+ count += IBUF_SIZE;
+ }
+
+ return count;
+}
+#endif
+
+//******************************************************************************
+// Function: i2Output(pCh, pSource, count)
+// Parameters: Pointer to channel structure
+// Pointer to source data
+// Number of bytes to send
+// Returns: Number of bytes sent, or -1 for error
+//
+// Description:
+// Queues the data at pSource to be sent as data packets to the board. If there
+// is a collosal blunder, (invalid structure pointers or the like), returns -1.
+// Otherwise, returns the number of bytes written. What if there is not enough
+// room for all the data? If pCh->channelOptions & CO_NBLOCK_WRITE is set, then
+// we transfer as many characters as we can now, then return. If this bit is
+// clear (default), routine will spin along until all the data is buffered.
+// Should this occur, the 1-ms delay routine is called while waiting to avoid
+// applications that one cannot break out of.
+//******************************************************************************
+static int
+i2Output(i2ChanStrPtr pCh, const char *pSource, int count, int user )
+{
+ i2eBordStrPtr pB;
+ unsigned char *pInsert;
+ int amountToMove;
+ int countOriginal = count;
+ unsigned short channel;
+ unsigned short stuffIndex;
+ unsigned long flags;
+ int rc = 0;
+
+ int bailout = 10;
+
+ ip2trace (CHANN, ITRC_OUTPUT, ITRC_ENTER, 2, count, user );
+
+ // Ensure channel structure seems real
+ if ( !i2Validate ( pCh ) )
+ return -1;
+
+ // initialize some accelerators and private copies
+ pB = pCh->pMyBord;
+ channel = pCh->infl.hd.i2sChannel;
+
+ // If the board has gone fatal, return bad, and also hit the trap routine if
+ // it exists.
+ if (pB->i2eFatal) {
+ if (pB->i2eFatalTrap) {
+ (*(pB)->i2eFatalTrap)(pB);
+ }
+ return -1;
+ }
+ // Proceed as though we would do everything
+ while ( count > 0 ) {
+
+ // How much room in output buffer is there?
+ READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+ amountToMove = pCh->Obuf_strip - pCh->Obuf_stuff - 1;
+ READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+ if (amountToMove < 0) {
+ amountToMove += OBUF_SIZE;
+ }
+ // Subtract off the headers size and see how much room there is for real
+ // data. If this is negative, we will discover later.
+ amountToMove -= sizeof (i2DataHeader);
+
+ // Don't move more (now) than can go in a single packet
+ if ( amountToMove > (int)(MAX_OBUF_BLOCK - sizeof(i2DataHeader)) ) {
+ amountToMove = MAX_OBUF_BLOCK - sizeof(i2DataHeader);
+ }
+ // Don't move more than the count we were given
+ if (amountToMove > count) {
+ amountToMove = count;
+ }
+ // Now we know how much we must move: NB because the ring buffers have
+ // an overflow area at the end, we needn't worry about wrapping in the
+ // middle of a packet.
+
+// Small WINDOW here with no LOCK but I can't call Flush with LOCK
+// We would be flushing (or ending flush) anyway
+
+ ip2trace (CHANN, ITRC_OUTPUT, 10, 1, amountToMove );
+
+ if ( !(pCh->flush_flags && i2RetryFlushOutput(pCh) )
+ && amountToMove > 0 )
+ {
+ WRITE_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+ stuffIndex = pCh->Obuf_stuff;
+
+ // Had room to move some data: don't know whether the block size,
+ // buffer space, or what was the limiting factor...
+ pInsert = &(pCh->Obuf[stuffIndex]);
+
+ // Set up the header
+ CHANNEL_OF(pInsert) = channel;
+ PTYPE_OF(pInsert) = PTYPE_DATA;
+ TAG_OF(pInsert) = 0;
+ ID_OF(pInsert) = ID_ORDINARY_DATA;
+ DATA_COUNT_OF(pInsert) = amountToMove;
+
+ // Move the data
+ if ( user ) {
+ rc = copy_from_user((char*)(DATA_OF(pInsert)), pSource,
+ amountToMove );
+ } else {
+ memcpy( (char*)(DATA_OF(pInsert)), pSource, amountToMove );
+ }
+ // Adjust pointers and indices
+ pSource += amountToMove;
+ pCh->Obuf_char_count += amountToMove;
+ stuffIndex += amountToMove + sizeof(i2DataHeader);
+ count -= amountToMove;
+
+ if (stuffIndex >= OBUF_SIZE) {
+ stuffIndex = 0;
+ }
+ pCh->Obuf_stuff = stuffIndex;
+
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+
+ ip2trace (CHANN, ITRC_OUTPUT, 13, 1, stuffIndex );
+
+ } else {
+
+ // Cannot move data
+ // becuz we need to stuff a flush
+ // or amount to move is <= 0
+
+ ip2trace(CHANN, ITRC_OUTPUT, 14, 3,
+ amountToMove, pB->i2eFifoRemains,
+ pB->i2eWaitingForEmptyFifo );
+
+ // Put this channel back on queue
+ // this ultimatly gets more data or wakes write output
+ i2QueueNeeds(pB, pCh, NEED_INLINE);
+
+ if ( pB->i2eWaitingForEmptyFifo ) {
+
+ ip2trace (CHANN, ITRC_OUTPUT, 16, 0 );
+
+ // or schedule
+ if (!in_interrupt()) {
+
+ ip2trace (CHANN, ITRC_OUTPUT, 61, 0 );
+
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(2);
+ if (signal_pending(current)) {
+ break;
+ }
+ continue;
+ } else {
+
+ ip2trace (CHANN, ITRC_OUTPUT, 62, 0 );
+
+ // let interrupt in = WAS restore_flags()
+ // We hold no lock nor is irq off anymore???
+
+ break;
+ }
+ break; // from while(count)
+ }
+ else if ( pB->i2eFifoRemains < 32 && !pB->i2eTxMailEmpty ( pB ) )
+ {
+ ip2trace (CHANN, ITRC_OUTPUT, 19, 2,
+ pB->i2eFifoRemains,
+ pB->i2eTxMailEmpty );
+
+ break; // from while(count)
+ } else if ( pCh->channelNeeds & NEED_CREDIT ) {
+
+ ip2trace (CHANN, ITRC_OUTPUT, 22, 0 );
+
+ break; // from while(count)
+ } else if ( --bailout) {
+
+ // Try to throw more things (maybe not us) in the fifo if we're
+ // not already waiting for it.
+
+ ip2trace (CHANN, ITRC_OUTPUT, 20, 0 );
+
+ serviceOutgoingFifo(pB);
+ //break; CONTINUE;
+ } else {
+ ip2trace (CHANN, ITRC_OUTPUT, 21, 3,
+ pB->i2eFifoRemains,
+ pB->i2eOutMailWaiting,
+ pB->i2eWaitingForEmptyFifo );
+
+ break; // from while(count)
+ }
+ }
+ } // End of while(count)
+
+ i2QueueNeeds(pB, pCh, NEED_INLINE);
+
+ // We drop through either when the count expires, or when there is some
+ // count left, but there was a non-blocking write.
+ if (countOriginal > count) {
+
+ ip2trace (CHANN, ITRC_OUTPUT, 17, 2, countOriginal, count );
+
+ serviceOutgoingFifo( pB );
+ }
+
+ ip2trace (CHANN, ITRC_OUTPUT, ITRC_RETURN, 2, countOriginal, count );
+
+ return countOriginal - count;
+}
+
+//******************************************************************************
+// Function: i2FlushOutput(pCh)
+// Parameters: Pointer to a channel structure
+// Returns: Nothing
+//
+// Description:
+// Sends bypass command to start flushing (waiting possibly forever until there
+// is room), then sends inline command to stop flushing output, (again waiting
+// possibly forever).
+//******************************************************************************
+static inline void
+i2FlushOutput(i2ChanStrPtr pCh)
+{
+
+ ip2trace (CHANN, ITRC_FLUSH, 1, 1, pCh->flush_flags );
+
+ if (pCh->flush_flags)
+ return;
+
+ if ( 1 != i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_STARTFL) ) {
+ pCh->flush_flags = STARTFL_FLAG; // Failed - flag for later
+
+ ip2trace (CHANN, ITRC_FLUSH, 2, 0 );
+
+ } else if ( 1 != i2QueueCommands(PTYPE_INLINE, pCh, 0, 1, CMD_STOPFL) ) {
+ pCh->flush_flags = STOPFL_FLAG; // Failed - flag for later
+
+ ip2trace (CHANN, ITRC_FLUSH, 3, 0 );
+ }
+}
+
+static int
+i2RetryFlushOutput(i2ChanStrPtr pCh)
+{
+ int old_flags = pCh->flush_flags;
+
+ ip2trace (CHANN, ITRC_FLUSH, 14, 1, old_flags );
+
+ pCh->flush_flags = 0; // Clear flag so we can avoid recursion
+ // and queue the commands
+
+ if ( old_flags & STARTFL_FLAG ) {
+ if ( 1 == i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_STARTFL) ) {
+ old_flags = STOPFL_FLAG; //Success - send stop flush
+ } else {
+ old_flags = STARTFL_FLAG; //Failure - Flag for retry later
+ }
+
+ ip2trace (CHANN, ITRC_FLUSH, 15, 1, old_flags );
+
+ }
+ if ( old_flags & STOPFL_FLAG ) {
+ if (1 == i2QueueCommands(PTYPE_INLINE, pCh, 0, 1, CMD_STOPFL)) {
+ old_flags = 0; // Success - clear flags
+ }
+
+ ip2trace (CHANN, ITRC_FLUSH, 16, 1, old_flags );
+ }
+ pCh->flush_flags = old_flags;
+
+ ip2trace (CHANN, ITRC_FLUSH, 17, 1, old_flags );
+
+ return old_flags;
+}
+
+//******************************************************************************
+// Function: i2DrainOutput(pCh,timeout)
+// Parameters: Pointer to a channel structure
+// Maximum period to wait
+// Returns: ?
+//
+// Description:
+// Uses the bookmark request command to ask the board to send a bookmark back as
+// soon as all the data is completely sent.
+//******************************************************************************
+static void
+i2DrainWakeup(i2ChanStrPtr pCh)
+{
+ ip2trace (CHANN, ITRC_DRAIN, 10, 1, pCh->BookmarkTimer.expires );
+
+ pCh->BookmarkTimer.expires = 0;
+ wake_up_interruptible( &pCh->pBookmarkWait );
+}
+
+static void
+i2DrainOutput(i2ChanStrPtr pCh, int timeout)
+{
+ wait_queue_t wait;
+ i2eBordStrPtr pB;
+
+ ip2trace (CHANN, ITRC_DRAIN, ITRC_ENTER, 1, pCh->BookmarkTimer.expires);
+
+ pB = pCh->pMyBord;
+ // If the board has gone fatal, return bad,
+ // and also hit the trap routine if it exists.
+ if (pB->i2eFatal) {
+ if (pB->i2eFatalTrap) {
+ (*(pB)->i2eFatalTrap)(pB);
+ }
+ return;
+ }
+ if ((timeout > 0) && (pCh->BookmarkTimer.expires == 0 )) {
+ // One per customer (channel)
+ init_timer( &(pCh->BookmarkTimer) );
+ pCh->BookmarkTimer.expires = jiffies + timeout;
+ pCh->BookmarkTimer.function = (void*)(unsigned long)i2DrainWakeup;
+ pCh->BookmarkTimer.data = (unsigned long)pCh;
+
+ ip2trace (CHANN, ITRC_DRAIN, 1, 1, pCh->BookmarkTimer.expires );
+
+ add_timer( &(pCh->BookmarkTimer) );
+ }
+
+ i2QueueCommands( PTYPE_INLINE, pCh, -1, 1, CMD_BMARK_REQ );
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&(pCh->pBookmarkWait), &wait);
+ set_current_state( TASK_INTERRUPTIBLE );
+
+ serviceOutgoingFifo( pB );
+
+ schedule(); // Now we take our interruptible sleep on
+
+ // Clean up the queue
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&(pCh->pBookmarkWait), &wait);
+
+ // if expires == 0 then timer poped, then do not need to del_timer
+ if ((timeout > 0) && pCh->BookmarkTimer.expires &&
+ time_before(jiffies, pCh->BookmarkTimer.expires)) {
+ del_timer( &(pCh->BookmarkTimer) );
+ pCh->BookmarkTimer.expires = 0;
+
+ ip2trace (CHANN, ITRC_DRAIN, 3, 1, pCh->BookmarkTimer.expires );
+
+ }
+ ip2trace (CHANN, ITRC_DRAIN, ITRC_RETURN, 1, pCh->BookmarkTimer.expires );
+ return;
+}
+
+//******************************************************************************
+// Function: i2OutputFree(pCh)
+// Parameters: Pointer to a channel structure
+// Returns: Space in output buffer
+//
+// Description:
+// Returns -1 if very gross error. Otherwise returns the amount of bytes still
+// free in the output buffer.
+//******************************************************************************
+static int
+i2OutputFree(i2ChanStrPtr pCh)
+{
+ int amountToMove;
+ unsigned long flags;
+
+ // Ensure channel structure seems real
+ if ( !i2Validate ( pCh ) ) {
+ return -1;
+ }
+ READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+ amountToMove = pCh->Obuf_strip - pCh->Obuf_stuff - 1;
+ READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+
+ if (amountToMove < 0) {
+ amountToMove += OBUF_SIZE;
+ }
+ // If this is negative, we will discover later
+ amountToMove -= sizeof(i2DataHeader);
+
+ return (amountToMove < 0) ? 0 : amountToMove;
+}
+static void
+
+ip2_owake( PTTY tp)
+{
+ i2ChanStrPtr pCh;
+
+ if (tp == NULL) return;
+
+ pCh = tp->driver_data;
+
+ ip2trace (CHANN, ITRC_SICMD, 10, 2, tp->flags,
+ (1 << TTY_DO_WRITE_WAKEUP) );
+
+ wake_up_interruptible ( &tp->write_wait );
+ if ( ( tp->flags & (1 << TTY_DO_WRITE_WAKEUP) )
+ && tp->ldisc.write_wakeup )
+ {
+ (tp->ldisc.write_wakeup) ( tp );
+
+ ip2trace (CHANN, ITRC_SICMD, 11, 0 );
+
+ }
+}
+
+static inline void
+set_baud_params(i2eBordStrPtr pB)
+{
+ int i,j;
+ i2ChanStrPtr *pCh;
+
+ pCh = (i2ChanStrPtr *) pB->i2eChannelPtr;
+
+ for (i = 0; i < ABS_MAX_BOXES; i++) {
+ if (pB->channelBtypes.bid_value[i]) {
+ if (BID_HAS_654(pB->channelBtypes.bid_value[i])) {
+ for (j = 0; j < ABS_BIGGEST_BOX; j++) {
+ if (pCh[i*16+j] == NULL)
+ break;
+ (pCh[i*16+j])->BaudBase = 921600; // MAX for ST654
+ (pCh[i*16+j])->BaudDivisor = 96;
+ }
+ } else { // has cirrus cd1400
+ for (j = 0; j < ABS_BIGGEST_BOX; j++) {
+ if (pCh[i*16+j] == NULL)
+ break;
+ (pCh[i*16+j])->BaudBase = 115200; // MAX for CD1400
+ (pCh[i*16+j])->BaudDivisor = 12;
+ }
+ }
+ }
+ }
+}
+
+//******************************************************************************
+// Function: i2StripFifo(pB)
+// Parameters: Pointer to a board structure
+// Returns: ?
+//
+// Description:
+// Strips all the available data from the incoming FIFO, identifies the type of
+// packet, and either buffers the data or does what needs to be done.
+//
+// Note there is no overflow checking here: if the board sends more data than it
+// ought to, we will not detect it here, but blindly overflow...
+//******************************************************************************
+
+// A buffer for reading in blocks for unknown channels
+static unsigned char junkBuffer[IBUF_SIZE];
+
+// A buffer to read in a status packet. Because of the size of the count field
+// for these things, the maximum packet size must be less than MAX_CMD_PACK_SIZE
+static unsigned char cmdBuffer[MAX_CMD_PACK_SIZE + 4];
+
+// This table changes the bit order from MSR order given by STAT_MODEM packet to
+// status bits used in our library.
+static char xlatDss[16] = {
+0 | 0 | 0 | 0 ,
+0 | 0 | 0 | I2_CTS ,
+0 | 0 | I2_DSR | 0 ,
+0 | 0 | I2_DSR | I2_CTS ,
+0 | I2_RI | 0 | 0 ,
+0 | I2_RI | 0 | I2_CTS ,
+0 | I2_RI | I2_DSR | 0 ,
+0 | I2_RI | I2_DSR | I2_CTS ,
+I2_DCD | 0 | 0 | 0 ,
+I2_DCD | 0 | 0 | I2_CTS ,
+I2_DCD | 0 | I2_DSR | 0 ,
+I2_DCD | 0 | I2_DSR | I2_CTS ,
+I2_DCD | I2_RI | 0 | 0 ,
+I2_DCD | I2_RI | 0 | I2_CTS ,
+I2_DCD | I2_RI | I2_DSR | 0 ,
+I2_DCD | I2_RI | I2_DSR | I2_CTS };
+
+static inline void
+i2StripFifo(i2eBordStrPtr pB)
+{
+ i2ChanStrPtr pCh;
+ int channel;
+ int count;
+ unsigned short stuffIndex;
+ int amountToRead;
+ unsigned char *pc, *pcLimit;
+ unsigned char uc;
+ unsigned char dss_change;
+ unsigned long bflags,cflags;
+
+// ip2trace (ITRC_NO_PORT, ITRC_SFIFO, ITRC_ENTER, 0 );
+
+ while (HAS_INPUT(pB)) {
+// ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 2, 0 );
+
+ // Process packet from fifo a one atomic unit
+ WRITE_LOCK_IRQSAVE(&pB->read_fifo_spinlock,bflags);
+
+ // The first word (or two bytes) will have channel number and type of
+ // packet, possibly other information
+ pB->i2eLeadoffWord[0] = iiReadWord(pB);
+
+ switch(PTYPE_OF(pB->i2eLeadoffWord))
+ {
+ case PTYPE_DATA:
+ pB->got_input = 1;
+
+// ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 3, 0 );
+
+ channel = CHANNEL_OF(pB->i2eLeadoffWord); /* Store channel */
+ count = iiReadWord(pB); /* Count is in the next word */
+
+// NEW: Check the count for sanity! Should the hardware fail, our death
+// is more pleasant. While an oversize channel is acceptable (just more
+// than the driver supports), an over-length count clearly means we are
+// sick!
+ if ( ((unsigned int)count) > IBUF_SIZE ) {
+ pB->i2eFatal = 2;
+ WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+ return; /* Bail out ASAP */
+ }
+ // Channel is illegally big ?
+ if ((channel >= pB->i2eChannelCnt) ||
+ (NULL==(pCh = ((i2ChanStrPtr*)pB->i2eChannelPtr)[channel])))
+ {
+ iiReadBuf(pB, junkBuffer, count);
+ WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+ break; /* From switch: ready for next packet */
+ }
+
+ // Channel should be valid, then
+
+ // If this is a hot-key, merely post its receipt for now. These are
+ // always supposed to be 1-byte packets, so we won't even check the
+ // count. Also we will post an acknowledgement to the board so that
+ // more data can be forthcoming. Note that we are not trying to use
+ // these sequences in this driver, merely to robustly ignore them.
+ if(ID_OF(pB->i2eLeadoffWord) == ID_HOT_KEY)
+ {
+ pCh->hotKeyIn = iiReadWord(pB) & 0xff;
+ WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+ i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_HOTACK);
+ break; /* From the switch: ready for next packet */
+ }
+
+ // Normal data! We crudely assume there is room for the data in our
+ // buffer because the board wouldn't have exceeded his credit limit.
+ WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,cflags);
+ // We have 2 locks now
+ stuffIndex = pCh->Ibuf_stuff;
+ amountToRead = IBUF_SIZE - stuffIndex;
+ if (amountToRead > count)
+ amountToRead = count;
+
+ // stuffIndex would have been already adjusted so there would
+ // always be room for at least one, and count is always at least
+ // one.
+
+ iiReadBuf(pB, &(pCh->Ibuf[stuffIndex]), amountToRead);
+ pCh->icount.rx += amountToRead;
+
+ // Update the stuffIndex by the amount of data moved. Note we could
+ // never ask for more data than would just fit. However, we might
+ // have read in one more byte than we wanted because the read
+ // rounds up to even bytes. If this byte is on the end of the
+ // packet, and is padding, we ignore it. If the byte is part of
+ // the actual data, we need to move it.
+
+ stuffIndex += amountToRead;
+
+ if (stuffIndex >= IBUF_SIZE) {
+ if ((amountToRead & 1) && (count > amountToRead)) {
+ pCh->Ibuf[0] = pCh->Ibuf[IBUF_SIZE];
+ amountToRead++;
+ stuffIndex = 1;
+ } else {
+ stuffIndex = 0;
+ }
+ }
+
+ // If there is anything left over, read it as well
+ if (count > amountToRead) {
+ amountToRead = count - amountToRead;
+ iiReadBuf(pB, &(pCh->Ibuf[stuffIndex]), amountToRead);
+ pCh->icount.rx += amountToRead;
+ stuffIndex += amountToRead;
+ }
+
+ // Update stuff index
+ pCh->Ibuf_stuff = stuffIndex;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,cflags);
+ WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+
+#ifdef USE_IQ
+ schedule_work(&pCh->tqueue_input);
+#else
+ do_input(pCh);
+#endif
+
+ // Note we do not need to maintain any flow-control credits at this
+ // time: if we were to increment .asof and decrement .room, there
+ // would be no net effect. Instead, when we strip data, we will
+ // increment .asof and leave .room unchanged.
+
+ break; // From switch: ready for next packet
+
+ case PTYPE_STATUS:
+ ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 4, 0 );
+
+ count = CMD_COUNT_OF(pB->i2eLeadoffWord);
+
+ iiReadBuf(pB, cmdBuffer, count);
+ // We can release early with buffer grab
+ WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+
+ pc = cmdBuffer;
+ pcLimit = &(cmdBuffer[count]);
+
+ while (pc < pcLimit) {
+ channel = *pc++;
+
+ ip2trace (channel, ITRC_SFIFO, 7, 2, channel, *pc );
+
+ /* check for valid channel */
+ if (channel < pB->i2eChannelCnt
+ &&
+ (pCh = (((i2ChanStrPtr*)pB->i2eChannelPtr)[channel])) != NULL
+ )
+ {
+ dss_change = 0;
+
+ switch (uc = *pc++)
+ {
+ /* Breaks and modem signals are easy: just update status */
+ case STAT_CTS_UP:
+ if ( !(pCh->dataSetIn & I2_CTS) )
+ {
+ pCh->dataSetIn |= I2_DCTS;
+ pCh->icount.cts++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn |= I2_CTS;
+ break;
+
+ case STAT_CTS_DN:
+ if ( pCh->dataSetIn & I2_CTS )
+ {
+ pCh->dataSetIn |= I2_DCTS;
+ pCh->icount.cts++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn &= ~I2_CTS;
+ break;
+
+ case STAT_DCD_UP:
+ ip2trace (channel, ITRC_MODEM, 1, 1, pCh->dataSetIn );
+
+ if ( !(pCh->dataSetIn & I2_DCD) )
+ {
+ ip2trace (CHANN, ITRC_MODEM, 2, 0 );
+ pCh->dataSetIn |= I2_DDCD;
+ pCh->icount.dcd++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn |= I2_DCD;
+
+ ip2trace (channel, ITRC_MODEM, 3, 1, pCh->dataSetIn );
+ break;
+
+ case STAT_DCD_DN:
+ ip2trace (channel, ITRC_MODEM, 4, 1, pCh->dataSetIn );
+ if ( pCh->dataSetIn & I2_DCD )
+ {
+ ip2trace (channel, ITRC_MODEM, 5, 0 );
+ pCh->dataSetIn |= I2_DDCD;
+ pCh->icount.dcd++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn &= ~I2_DCD;
+
+ ip2trace (channel, ITRC_MODEM, 6, 1, pCh->dataSetIn );
+ break;
+
+ case STAT_DSR_UP:
+ if ( !(pCh->dataSetIn & I2_DSR) )
+ {
+ pCh->dataSetIn |= I2_DDSR;
+ pCh->icount.dsr++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn |= I2_DSR;
+ break;
+
+ case STAT_DSR_DN:
+ if ( pCh->dataSetIn & I2_DSR )
+ {
+ pCh->dataSetIn |= I2_DDSR;
+ pCh->icount.dsr++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn &= ~I2_DSR;
+ break;
+
+ case STAT_RI_UP:
+ if ( !(pCh->dataSetIn & I2_RI) )
+ {
+ pCh->dataSetIn |= I2_DRI;
+ pCh->icount.rng++;
+ dss_change = 1;
+ }
+ pCh->dataSetIn |= I2_RI ;
+ break;
+
+ case STAT_RI_DN:
+ // to be compat with serial.c
+ //if ( pCh->dataSetIn & I2_RI )
+ //{
+ // pCh->dataSetIn |= I2_DRI;
+ // pCh->icount.rng++;
+ // dss_change = 1;
+ //}
+ pCh->dataSetIn &= ~I2_RI ;
+ break;
+
+ case STAT_BRK_DET:
+ pCh->dataSetIn |= I2_BRK;
+ pCh->icount.brk++;
+ dss_change = 1;
+ break;
+
+ // Bookmarks? one less request we're waiting for
+ case STAT_BMARK:
+ pCh->bookMarks--;
+ if (pCh->bookMarks <= 0 ) {
+ pCh->bookMarks = 0;
+ wake_up_interruptible( &pCh->pBookmarkWait );
+
+ ip2trace (channel, ITRC_DRAIN, 20, 1, pCh->BookmarkTimer.expires );
+ }
+ break;
+
+ // Flow control packets? Update the new credits, and if
+ // someone was waiting for output, queue him up again.
+ case STAT_FLOW:
+ pCh->outfl.room =
+ ((flowStatPtr)pc)->room -
+ (pCh->outfl.asof - ((flowStatPtr)pc)->asof);
+
+ ip2trace (channel, ITRC_STFLW, 1, 1, pCh->outfl.room );
+
+ if (pCh->channelNeeds & NEED_CREDIT)
+ {
+ ip2trace (channel, ITRC_STFLW, 2, 1, pCh->channelNeeds);
+
+ pCh->channelNeeds &= ~NEED_CREDIT;
+ i2QueueNeeds(pB, pCh, NEED_INLINE);
+ if ( pCh->pTTY )
+ ip2_owake(pCh->pTTY);
+ }
+
+ ip2trace (channel, ITRC_STFLW, 3, 1, pCh->channelNeeds);
+
+ pc += sizeof(flowStat);
+ break;
+
+ /* Special packets: */
+ /* Just copy the information into the channel structure */
+
+ case STAT_STATUS:
+
+ pCh->channelStatus = *((debugStatPtr)pc);
+ pc += sizeof(debugStat);
+ break;
+
+ case STAT_TXCNT:
+
+ pCh->channelTcount = *((cntStatPtr)pc);
+ pc += sizeof(cntStat);
+ break;
+
+ case STAT_RXCNT:
+
+ pCh->channelRcount = *((cntStatPtr)pc);
+ pc += sizeof(cntStat);
+ break;
+
+ case STAT_BOXIDS:
+ pB->channelBtypes = *((bidStatPtr)pc);
+ pc += sizeof(bidStat);
+ set_baud_params(pB);
+ break;
+
+ case STAT_HWFAIL:
+ i2QueueCommands (PTYPE_INLINE, pCh, 0, 1, CMD_HW_TEST);
+ pCh->channelFail = *((failStatPtr)pc);
+ pc += sizeof(failStat);
+ break;
+
+ /* No explicit match? then
+ * Might be an error packet...
+ */
+ default:
+ switch (uc & STAT_MOD_ERROR)
+ {
+ case STAT_ERROR:
+ if (uc & STAT_E_PARITY) {
+ pCh->dataSetIn |= I2_PAR;
+ pCh->icount.parity++;
+ }
+ if (uc & STAT_E_FRAMING){
+ pCh->dataSetIn |= I2_FRA;
+ pCh->icount.frame++;
+ }
+ if (uc & STAT_E_OVERRUN){
+ pCh->dataSetIn |= I2_OVR;
+ pCh->icount.overrun++;
+ }
+ break;
+
+ case STAT_MODEM:
+ // the answer to DSS_NOW request (not change)
+ pCh->dataSetIn = (pCh->dataSetIn
+ & ~(I2_RI | I2_CTS | I2_DCD | I2_DSR) )
+ | xlatDss[uc & 0xf];
+ wake_up_interruptible ( &pCh->dss_now_wait );
+ default:
+ break;
+ }
+ } /* End of switch on status type */
+ if (dss_change) {
+#ifdef USE_IQ
+ schedule_work(&pCh->tqueue_status);
+#else
+ do_status(pCh);
+#endif
+ }
+ }
+ else /* Or else, channel is invalid */
+ {
+ // Even though the channel is invalid, we must test the
+ // status to see how much additional data it has (to be
+ // skipped)
+ switch (*pc++)
+ {
+ case STAT_FLOW:
+ pc += 4; /* Skip the data */
+ break;
+
+ default:
+ break;
+ }
+ }
+ } // End of while (there is still some status packet left)
+ break;
+
+ default: // Neither packet? should be impossible
+ ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 5, 1,
+ PTYPE_OF(pB->i2eLeadoffWord) );
+
+ break;
+ } // End of switch on type of packets
+ } //while(board HAS_INPUT)
+
+ ip2trace (ITRC_NO_PORT, ITRC_SFIFO, ITRC_RETURN, 0 );
+
+ // Send acknowledgement to the board even if there was no data!
+ pB->i2eOutMailWaiting |= MB_IN_STRIPPED;
+ return;
+}
+
+//******************************************************************************
+// Function: i2Write2Fifo(pB,address,count)
+// Parameters: Pointer to a board structure, source address, byte count
+// Returns: bytes written
+//
+// Description:
+// Writes count bytes to board io address(implied) from source
+// Adjusts count, leaves reserve for next time around bypass cmds
+//******************************************************************************
+static int
+i2Write2Fifo(i2eBordStrPtr pB, unsigned char *source, int count,int reserve)
+{
+ int rc = 0;
+ unsigned long flags;
+ WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+ if (!pB->i2eWaitingForEmptyFifo) {
+ if (pB->i2eFifoRemains > (count+reserve)) {
+ pB->i2eFifoRemains -= count;
+ iiWriteBuf(pB, source, count);
+ pB->i2eOutMailWaiting |= MB_OUT_STUFFED;
+ rc = count;
+ }
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+ return rc;
+}
+//******************************************************************************
+// Function: i2StuffFifoBypass(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// Stuffs as many bypass commands into the fifo as possible. This is simpler
+// than stuffing data or inline commands to fifo, since we do not have
+// flow-control to deal with.
+//******************************************************************************
+static inline void
+i2StuffFifoBypass(i2eBordStrPtr pB)
+{
+ i2ChanStrPtr pCh;
+ unsigned char *pRemove;
+ unsigned short stripIndex;
+ unsigned short packetSize;
+ unsigned short paddedSize;
+ unsigned short notClogged = 1;
+ unsigned long flags;
+
+ int bailout = 1000;
+
+ // Continue processing so long as there are entries, or there is room in the
+ // fifo. Each entry represents a channel with something to do.
+ while ( --bailout && notClogged &&
+ (NULL != (pCh = i2DeQueueNeeds(pB,NEED_BYPASS))))
+ {
+ WRITE_LOCK_IRQSAVE(&pCh->Cbuf_spinlock,flags);
+ stripIndex = pCh->Cbuf_strip;
+
+ // as long as there are packets for this channel...
+
+ while (stripIndex != pCh->Cbuf_stuff) {
+ pRemove = &(pCh->Cbuf[stripIndex]);
+ packetSize = CMD_COUNT_OF(pRemove) + sizeof(i2CmdHeader);
+ paddedSize = ROUNDUP(packetSize);
+
+ if (paddedSize > 0) {
+ if ( 0 == i2Write2Fifo(pB, pRemove, paddedSize,0)) {
+ notClogged = 0; /* fifo full */
+ i2QueueNeeds(pB, pCh, NEED_BYPASS); // Put back on queue
+ break; // Break from the channel
+ }
+ }
+#ifdef DEBUG_FIFO
+WriteDBGBuf("BYPS", pRemove, paddedSize);
+#endif /* DEBUG_FIFO */
+ pB->debugBypassCount++;
+
+ pRemove += packetSize;
+ stripIndex += packetSize;
+ if (stripIndex >= CBUF_SIZE) {
+ stripIndex = 0;
+ pRemove = pCh->Cbuf;
+ }
+ }
+ // Done with this channel. Move to next, removing this one from
+ // the queue of channels if we cleaned it out (i.e., didn't get clogged.
+ pCh->Cbuf_strip = stripIndex;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Cbuf_spinlock,flags);
+ } // Either clogged or finished all the work
+
+#ifdef IP2DEBUG_TRACE
+ if ( !bailout ) {
+ ip2trace (ITRC_NO_PORT, ITRC_ERROR, 1, 0 );
+ }
+#endif
+}
+
+//******************************************************************************
+// Function: i2StuffFifoFlow(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// Stuffs as many flow control packets into the fifo as possible. This is easier
+// even than doing normal bypass commands, because there is always at most one
+// packet, already assembled, for each channel.
+//******************************************************************************
+static inline void
+i2StuffFifoFlow(i2eBordStrPtr pB)
+{
+ i2ChanStrPtr pCh;
+ unsigned short paddedSize = ROUNDUP(sizeof(flowIn));
+
+ ip2trace (ITRC_NO_PORT, ITRC_SFLOW, ITRC_ENTER, 2,
+ pB->i2eFifoRemains, paddedSize );
+
+ // Continue processing so long as there are entries, or there is room in the
+ // fifo. Each entry represents a channel with something to do.
+ while ( (NULL != (pCh = i2DeQueueNeeds(pB,NEED_FLOW)))) {
+ pB->debugFlowCount++;
+
+ // NO Chan LOCK needed ???
+ if ( 0 == i2Write2Fifo(pB,(unsigned char *)&(pCh->infl),paddedSize,0)) {
+ break;
+ }
+#ifdef DEBUG_FIFO
+ WriteDBGBuf("FLOW",(unsigned char *) &(pCh->infl), paddedSize);
+#endif /* DEBUG_FIFO */
+
+ } // Either clogged or finished all the work
+
+ ip2trace (ITRC_NO_PORT, ITRC_SFLOW, ITRC_RETURN, 0 );
+}
+
+//******************************************************************************
+// Function: i2StuffFifoInline(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// Stuffs as much data and inline commands into the fifo as possible. This is
+// the most complex fifo-stuffing operation, since there if now the channel
+// flow-control issue to deal with.
+//******************************************************************************
+static inline void
+i2StuffFifoInline(i2eBordStrPtr pB)
+{
+ i2ChanStrPtr pCh;
+ unsigned char *pRemove;
+ unsigned short stripIndex;
+ unsigned short packetSize;
+ unsigned short paddedSize;
+ unsigned short notClogged = 1;
+ unsigned short flowsize;
+ unsigned long flags;
+
+ int bailout = 1000;
+ int bailout2;
+
+ ip2trace (ITRC_NO_PORT, ITRC_SICMD, ITRC_ENTER, 3, pB->i2eFifoRemains,
+ pB->i2Dbuf_strip, pB->i2Dbuf_stuff );
+
+ // Continue processing so long as there are entries, or there is room in the
+ // fifo. Each entry represents a channel with something to do.
+ while ( --bailout && notClogged &&
+ (NULL != (pCh = i2DeQueueNeeds(pB,NEED_INLINE))) )
+ {
+ WRITE_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+ stripIndex = pCh->Obuf_strip;
+
+ ip2trace (CHANN, ITRC_SICMD, 3, 2, stripIndex, pCh->Obuf_stuff );
+
+ // as long as there are packets for this channel...
+ bailout2 = 1000;
+ while ( --bailout2 && stripIndex != pCh->Obuf_stuff) {
+ pRemove = &(pCh->Obuf[stripIndex]);
+
+ // Must determine whether this be a data or command packet to
+ // calculate correctly the header size and the amount of
+ // flow-control credit this type of packet will use.
+ if (PTYPE_OF(pRemove) == PTYPE_DATA) {
+ flowsize = DATA_COUNT_OF(pRemove);
+ packetSize = flowsize + sizeof(i2DataHeader);
+ } else {
+ flowsize = CMD_COUNT_OF(pRemove);
+ packetSize = flowsize + sizeof(i2CmdHeader);
+ }
+ flowsize = CREDIT_USAGE(flowsize);
+ paddedSize = ROUNDUP(packetSize);
+
+ ip2trace (CHANN, ITRC_SICMD, 4, 2, pB->i2eFifoRemains, paddedSize );
+
+ // If we don't have enough credits from the board to send the data,
+ // flag the channel that we are waiting for flow control credit, and
+ // break out. This will clean up this channel and remove us from the
+ // queue of hot things to do.
+
+ ip2trace (CHANN, ITRC_SICMD, 5, 2, pCh->outfl.room, flowsize );
+
+ if (pCh->outfl.room <= flowsize) {
+ // Do Not have the credits to send this packet.
+ i2QueueNeeds(pB, pCh, NEED_CREDIT);
+ notClogged = 0;
+ break; // So to do next channel
+ }
+ if ( (paddedSize > 0)
+ && ( 0 == i2Write2Fifo(pB, pRemove, paddedSize, 128))) {
+ // Do Not have room in fifo to send this packet.
+ notClogged = 0;
+ i2QueueNeeds(pB, pCh, NEED_INLINE);
+ break; // Break from the channel
+ }
+#ifdef DEBUG_FIFO
+WriteDBGBuf("DATA", pRemove, paddedSize);
+#endif /* DEBUG_FIFO */
+ pB->debugInlineCount++;
+
+ pCh->icount.tx += flowsize;
+ // Update current credits
+ pCh->outfl.room -= flowsize;
+ pCh->outfl.asof += flowsize;
+ if (PTYPE_OF(pRemove) == PTYPE_DATA) {
+ pCh->Obuf_char_count -= DATA_COUNT_OF(pRemove);
+ }
+ pRemove += packetSize;
+ stripIndex += packetSize;
+
+ ip2trace (CHANN, ITRC_SICMD, 6, 2, stripIndex, pCh->Obuf_strip);
+
+ if (stripIndex >= OBUF_SIZE) {
+ stripIndex = 0;
+ pRemove = pCh->Obuf;
+
+ ip2trace (CHANN, ITRC_SICMD, 7, 1, stripIndex );
+
+ }
+ } /* while */
+ if ( !bailout2 ) {
+ ip2trace (CHANN, ITRC_ERROR, 3, 0 );
+ }
+ // Done with this channel. Move to next, removing this one from the
+ // queue of channels if we cleaned it out (i.e., didn't get clogged.
+ pCh->Obuf_strip = stripIndex;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+ if ( notClogged )
+ {
+
+ ip2trace (CHANN, ITRC_SICMD, 8, 0 );
+
+ if ( pCh->pTTY ) {
+ ip2_owake(pCh->pTTY);
+ }
+ }
+ } // Either clogged or finished all the work
+
+ if ( !bailout ) {
+ ip2trace (ITRC_NO_PORT, ITRC_ERROR, 4, 0 );
+ }
+
+ ip2trace (ITRC_NO_PORT, ITRC_SICMD, ITRC_RETURN, 1,pB->i2Dbuf_strip);
+}
+
+//******************************************************************************
+// Function: serviceOutgoingFifo(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// Helper routine to put data in the outgoing fifo, if we aren't already waiting
+// for something to be there. If the fifo has only room for a very little data,
+// go head and hit the board with a mailbox hit immediately. Otherwise, it will
+// have to happen later in the interrupt processing. Since this routine may be
+// called both at interrupt and foreground time, we must turn off interrupts
+// during the entire process.
+//******************************************************************************
+static void
+serviceOutgoingFifo(i2eBordStrPtr pB)
+{
+ // If we aren't currently waiting for the board to empty our fifo, service
+ // everything that is pending, in priority order (especially, Bypass before
+ // Inline).
+ if ( ! pB->i2eWaitingForEmptyFifo )
+ {
+ i2StuffFifoFlow(pB);
+ i2StuffFifoBypass(pB);
+ i2StuffFifoInline(pB);
+
+ iiSendPendingMail(pB);
+ }
+}
+
+//******************************************************************************
+// Function: i2ServiceBoard(pB)
+// Parameters: Pointer to a board structure
+// Returns: Nothing
+//
+// Description:
+// Normally this is called from interrupt level, but there is deliberately
+// nothing in here specific to being called from interrupt level. All the
+// hardware-specific, interrupt-specific things happen at the outer levels.
+//
+// For example, a timer interrupt could drive this routine for some sort of
+// polled operation. The only requirement is that the programmer deal with any
+// atomiticity/concurrency issues that result.
+//
+// This routine responds to the board's having sent mailbox information to the
+// host (which would normally cause an interrupt). This routine reads the
+// incoming mailbox. If there is no data in it, this board did not create the
+// interrupt and/or has nothing to be done to it. (Except, if we have been
+// waiting to write mailbox data to it, we may do so.
+//
+// Based on the value in the mailbox, we may take various actions.
+//
+// No checking here of pB validity: after all, it shouldn't have been called by
+// the handler unless pB were on the list.
+//******************************************************************************
+static inline int
+i2ServiceBoard ( i2eBordStrPtr pB )
+{
+ unsigned inmail;
+ unsigned long flags;
+
+
+ /* This should be atomic because of the way we are called... */
+ if (NO_MAIL_HERE == ( inmail = pB->i2eStartMail ) ) {
+ inmail = iiGetMail(pB);
+ }
+ pB->i2eStartMail = NO_MAIL_HERE;
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, 2, 1, inmail );
+
+ if (inmail != NO_MAIL_HERE) {
+ // If the board has gone fatal, nothing to do but hit a bit that will
+ // alert foreground tasks to protest!
+ if ( inmail & MB_FATAL_ERROR ) {
+ pB->i2eFatal = 1;
+ goto exit_i2ServiceBoard;
+ }
+
+ /* Assuming no fatal condition, we proceed to do work */
+ if ( inmail & MB_IN_STUFFED ) {
+ pB->i2eFifoInInts++;
+ i2StripFifo(pB); /* There might be incoming packets */
+ }
+
+ if (inmail & MB_OUT_STRIPPED) {
+ pB->i2eFifoOutInts++;
+ WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+ pB->i2eFifoRemains = pB->i2eFifoSize;
+ pB->i2eWaitingForEmptyFifo = 0;
+ WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, 30, 1, pB->i2eFifoRemains );
+
+ }
+ serviceOutgoingFifo(pB);
+ }
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, 8, 0 );
+
+exit_i2ServiceBoard:
+
+ return 0;
+}
diff --git a/drivers/char/ip2/i2lib.h b/drivers/char/ip2/i2lib.h
new file mode 100644
index 000000000000..952e113ccd8a
--- /dev/null
+++ b/drivers/char/ip2/i2lib.h
@@ -0,0 +1,351 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Header file for high level library functions
+*
+*******************************************************************************/
+#ifndef I2LIB_H
+#define I2LIB_H 1
+//------------------------------------------------------------------------------
+// I2LIB.H
+//
+// IntelliPort-II and IntelliPort-IIEX
+//
+// Defines, structure definitions, and external declarations for i2lib.c
+//------------------------------------------------------------------------------
+//--------------------------------------
+// Mandatory Includes:
+//--------------------------------------
+#include "ip2types.h"
+#include "i2ellis.h"
+#include "i2pack.h"
+#include "i2cmd.h"
+#include <linux/workqueue.h>
+
+//------------------------------------------------------------------------------
+// i2ChanStr -- Channel Structure:
+// Used to track per-channel information for the library routines using standard
+// loadware. Note also, a pointer to an array of these structures is patched
+// into the i2eBordStr (see i2ellis.h)
+//------------------------------------------------------------------------------
+//
+// If we make some limits on the maximum block sizes, we can avoid dealing with
+// buffer wrap. The wrapping of the buffer is based on where the start of the
+// packet is. Then there is always room for the packet contiguously.
+//
+// Maximum total length of an outgoing data or in-line command block. The limit
+// of 36 on data is quite arbitrary and based more on DOS memory limitations
+// than the board interface. However, for commands, the maximum packet length is
+// MAX_CMD_PACK_SIZE, because the field size for the count is only a few bits
+// (see I2PACK.H) in such packets. For data packets, the count field size is not
+// the limiting factor. As of this writing, MAX_OBUF_BLOCK < MAX_CMD_PACK_SIZE,
+// but be careful if wanting to modify either.
+//
+#define MAX_OBUF_BLOCK 36
+
+// Another note on maximum block sizes: we are buffering packets here. Data is
+// put into the buffer (if there is room) regardless of the credits from the
+// board. The board sends new credits whenever it has removed from his buffers a
+// number of characters equal to 80% of total buffer size. (Of course, the total
+// buffer size is what is reported when the very first set of flow control
+// status packets are received from the board. Therefore, to be robust, you must
+// always fill the board to at least 80% of the current credit limit, else you
+// might not give it enough to trigger a new report. These conditions are
+// obtained here so long as the maximum output block size is less than 20% the
+// size of the board's output buffers. This is true at present by "coincidence"
+// or "infernal knowledge": the board's output buffers are at least 700 bytes
+// long (20% = 140 bytes, at least). The 80% figure is "official", so the safest
+// strategy might be to trap the first flow control report and guarantee that
+// the effective maxObufBlock is the minimum of MAX_OBUF_BLOCK and 20% of first
+// reported buffer credit.
+//
+#define MAX_CBUF_BLOCK 6 // Maximum total length of a bypass command block
+
+#define IBUF_SIZE 512 // character capacity of input buffer per channel
+#define OBUF_SIZE 1024// character capacity of output buffer per channel
+#define CBUF_SIZE 10 // character capacity of output bypass buffer
+
+typedef struct _i2ChanStr
+{
+ // First, back-pointers so that given a pointer to this structure, you can
+ // determine the correct board and channel number to reference, (say, when
+ // issuing commands, etc. (Note, channel number is in infl.hd.i2sChannel.)
+
+ int port_index; // Index of port in channel structure array attached
+ // to board structure.
+ PTTY pTTY; // Pointer to tty structure for port (OS specific)
+ USHORT validity; // Indicates whether the given channel has been
+ // initialized, really exists (or is a missing
+ // channel, e.g. channel 9 on an 8-port box.)
+
+ i2eBordStrPtr pMyBord; // Back-pointer to this channel's board structure
+
+ int wopen; // waiting fer carrier
+
+ int throttled; // Set if upper layer can take no data
+
+ int flags; // Defined in tty.h
+
+ PWAITQ open_wait; // Pointer for OS sleep function.
+ PWAITQ close_wait; // Pointer for OS sleep function.
+ PWAITQ delta_msr_wait;// Pointer for OS sleep function.
+ PWAITQ dss_now_wait; // Pointer for OS sleep function.
+
+ struct timer_list BookmarkTimer; // Used by i2DrainOutput
+ wait_queue_head_t pBookmarkWait; // Used by i2DrainOutput
+
+ int BaudBase;
+ int BaudDivisor;
+
+ USHORT ClosingDelay;
+ USHORT ClosingWaitTime;
+
+ volatile
+ flowIn infl; // This structure is initialized as a completely
+ // formed flow-control command packet, and as such
+ // has the channel number, also the capacity and
+ // "as-of" data needed continuously.
+
+ USHORT sinceLastFlow; // Counts the number of characters read from input
+ // buffers, since the last time flow control info
+ // was sent.
+
+ USHORT whenSendFlow; // Determines when new flow control is to be sent to
+ // the board. Note unlike earlier manifestations of
+ // the driver, these packets can be sent from
+ // in-place.
+
+ USHORT channelNeeds; // Bit map of important things which must be done
+ // for this channel. (See bits below )
+
+ volatile
+ flowStat outfl; // Same type of structure is used to hold current
+ // flow control information used to control our
+ // output. "asof" is kept updated as data is sent,
+ // and "room" never goes to zero.
+
+ // The incoming ring buffer
+ // Unlike the outgoing buffers, this holds raw data, not packets. The two
+ // extra bytes are used to hold the byte-padding when there is room for an
+ // odd number of bytes before we must wrap.
+ //
+ UCHAR Ibuf[IBUF_SIZE + 2];
+ volatile
+ USHORT Ibuf_stuff; // Stuffing index
+ volatile
+ USHORT Ibuf_strip; // Stripping index
+
+ // The outgoing ring-buffer: Holds Data and command packets. N.B., even
+ // though these are in the channel structure, the channel is also written
+ // here, the easier to send it to the fifo when ready. HOWEVER, individual
+ // packets here are NOT padded to even length: the routines for writing
+ // blocks to the fifo will pad to even byte counts.
+ //
+ UCHAR Obuf[OBUF_SIZE+MAX_OBUF_BLOCK+4];
+ volatile
+ USHORT Obuf_stuff; // Stuffing index
+ volatile
+ USHORT Obuf_strip; // Stripping index
+ int Obuf_char_count;
+
+ // The outgoing bypass-command buffer. Unlike earlier manifestations, the
+ // flow control packets are sent directly from the structures. As above, the
+ // channel number is included in the packet, but they are NOT padded to even
+ // size.
+ //
+ UCHAR Cbuf[CBUF_SIZE+MAX_CBUF_BLOCK+2];
+ volatile
+ USHORT Cbuf_stuff; // Stuffing index
+ volatile
+ USHORT Cbuf_strip; // Stripping index
+
+ // The temporary buffer for the Linux tty driver PutChar entry.
+ //
+ UCHAR Pbuf[MAX_OBUF_BLOCK - sizeof (i2DataHeader)];
+ volatile
+ USHORT Pbuf_stuff; // Stuffing index
+
+ // The state of incoming data-set signals
+ //
+ USHORT dataSetIn; // Bit-mapped according to below. Also indicates
+ // whether a break has been detected since last
+ // inquiry.
+
+ // The state of outcoming data-set signals (as far as we can tell!)
+ //
+ USHORT dataSetOut; // Bit-mapped according to below.
+
+ // Most recent hot-key identifier detected
+ //
+ USHORT hotKeyIn; // Hot key as sent by the board, HOT_CLEAR indicates
+ // no hot key detected since last examined.
+
+ // Counter of outstanding requests for bookmarks
+ //
+ short bookMarks; // Number of outstanding bookmark requests, (+ive
+ // whenever a bookmark request if queued up, -ive
+ // whenever a bookmark is received).
+
+ // Misc options
+ //
+ USHORT channelOptions; // See below
+
+ // To store various incoming special packets
+ //
+ debugStat channelStatus;
+ cntStat channelRcount;
+ cntStat channelTcount;
+ failStat channelFail;
+
+ // To store the last values for line characteristics we sent to the board.
+ //
+ int speed;
+
+ int flush_flags;
+
+ void (*trace)(unsigned short,unsigned char,unsigned char,unsigned long,...);
+
+ /*
+ * Kernel counters for the 4 input interrupts
+ */
+ struct async_icount icount;
+
+ /*
+ * Task queues for processing input packets from the board.
+ */
+ struct work_struct tqueue_input;
+ struct work_struct tqueue_status;
+ struct work_struct tqueue_hangup;
+
+ rwlock_t Ibuf_spinlock;
+ rwlock_t Obuf_spinlock;
+ rwlock_t Cbuf_spinlock;
+ rwlock_t Pbuf_spinlock;
+
+} i2ChanStr, *i2ChanStrPtr;
+
+//---------------------------------------------------
+// Manifests and bit-maps for elements in i2ChanStr
+//---------------------------------------------------
+//
+// flush flags
+//
+#define STARTFL_FLAG 1
+#define STOPFL_FLAG 2
+
+// validity
+//
+#define CHANNEL_MAGIC_BITS 0xff00
+#define CHANNEL_MAGIC 0x5300 // (validity & CHANNEL_MAGIC_BITS) ==
+ // CHANNEL_MAGIC --> structure good
+
+#define CHANNEL_SUPPORT 0x0001 // Indicates channel is supported, exists,
+ // and passed P.O.S.T.
+
+// channelNeeds
+//
+#define NEED_FLOW 1 // Indicates flow control has been queued
+#define NEED_INLINE 2 // Indicates inline commands or data queued
+#define NEED_BYPASS 4 // Indicates bypass commands queued
+#define NEED_CREDIT 8 // Indicates would be sending except has not sufficient
+ // credit. The data is still in the channel structure,
+ // but the channel is not enqueued in the board
+ // structure again until there is a credit received from
+ // the board.
+
+// dataSetIn (Also the bits for i2GetStatus return value)
+//
+#define I2_DCD 1
+#define I2_CTS 2
+#define I2_DSR 4
+#define I2_RI 8
+
+// dataSetOut (Also the bits for i2GetStatus return value)
+//
+#define I2_DTR 1
+#define I2_RTS 2
+
+// i2GetStatus() can optionally clear these bits
+//
+#define I2_BRK 0x10 // A break was detected
+#define I2_PAR 0x20 // A parity error was received
+#define I2_FRA 0x40 // A framing error was received
+#define I2_OVR 0x80 // An overrun error was received
+
+// i2GetStatus() automatically clears these bits */
+//
+#define I2_DDCD 0x100 // DCD changed from its former value
+#define I2_DCTS 0x200 // CTS changed from its former value
+#define I2_DDSR 0x400 // DSR changed from its former value
+#define I2_DRI 0x800 // RI changed from its former value
+
+// hotKeyIn
+//
+#define HOT_CLEAR 0x1322 // Indicates that no hot-key has been detected
+
+// channelOptions
+//
+#define CO_NBLOCK_WRITE 1 // Writes don't block waiting for buffer. (Default
+ // is, they do wait.)
+
+// fcmodes
+//
+#define I2_OUTFLOW_CTS 0x0001
+#define I2_INFLOW_RTS 0x0002
+#define I2_INFLOW_DSR 0x0004
+#define I2_INFLOW_DTR 0x0008
+#define I2_OUTFLOW_DSR 0x0010
+#define I2_OUTFLOW_DTR 0x0020
+#define I2_OUTFLOW_XON 0x0040
+#define I2_OUTFLOW_XANY 0x0080
+#define I2_INFLOW_XON 0x0100
+
+#define I2_CRTSCTS (I2_OUTFLOW_CTS|I2_INFLOW_RTS)
+#define I2_IXANY_MODE (I2_OUTFLOW_XON|I2_OUTFLOW_XANY)
+
+//-------------------------------------------
+// Macros used from user level like functions
+//-------------------------------------------
+
+// Macros to set and clear channel options
+//
+#define i2SetOption(pCh, option) pCh->channelOptions |= option
+#define i2ClrOption(pCh, option) pCh->channelOptions &= ~option
+
+// Macro to set fatal-error trap
+//
+#define i2SetFatalTrap(pB, routine) pB->i2eFatalTrap = routine
+
+//--------------------------------------------
+// Declarations and prototypes for i2lib.c
+//--------------------------------------------
+//
+static int i2InitChannels(i2eBordStrPtr, int, i2ChanStrPtr);
+static int i2QueueCommands(int, i2ChanStrPtr, int, int, cmdSyntaxPtr,...);
+static int i2GetStatus(i2ChanStrPtr, int);
+static int i2Input(i2ChanStrPtr);
+static int i2InputFlush(i2ChanStrPtr);
+static int i2Output(i2ChanStrPtr, const char *, int, int);
+static int i2OutputFree(i2ChanStrPtr);
+static int i2ServiceBoard(i2eBordStrPtr);
+static void i2DrainOutput(i2ChanStrPtr, int);
+
+#ifdef IP2DEBUG_TRACE
+void ip2trace(unsigned short,unsigned char,unsigned char,unsigned long,...);
+#else
+#define ip2trace(a,b,c,d...) do {} while (0)
+#endif
+
+// Argument to i2QueueCommands
+//
+#define C_IN_LINE 1
+#define C_BYPASS 0
+
+#endif // I2LIB_H
diff --git a/drivers/char/ip2/i2os.h b/drivers/char/ip2/i2os.h
new file mode 100644
index 000000000000..eff9b542d699
--- /dev/null
+++ b/drivers/char/ip2/i2os.h
@@ -0,0 +1,127 @@
+/*******************************************************************************
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Defines, definitions and includes which are heavily dependent
+* on O/S, host, compiler, etc. This file is tailored for:
+* Linux v2.0.0 and later
+* Gnu gcc c2.7.2
+* 80x86 architecture
+*
+*******************************************************************************/
+
+#ifndef I2OS_H /* To prevent multiple includes */
+#define I2OS_H 1
+
+//-------------------------------------------------
+// Required Includes
+//-------------------------------------------------
+
+#include "ip2types.h"
+#include <asm/io.h> /* For inb, etc */
+
+//------------------------------------
+// Defines for I/O instructions:
+//------------------------------------
+
+#define INB(port) inb(port)
+#define OUTB(port,value) outb((value),(port))
+#define INW(port) inw(port)
+#define OUTW(port,value) outw((value),(port))
+#define OUTSW(port,addr,count) outsw((port),(addr),(((count)+1)/2))
+#define OUTSB(port,addr,count) outsb((port),(addr),(((count)+1))&-2)
+#define INSW(port,addr,count) insw((port),(addr),(((count)+1)/2))
+#define INSB(port,addr,count) insb((port),(addr),(((count)+1))&-2)
+
+//--------------------------------------------
+// Interrupt control
+//--------------------------------------------
+
+#define LOCK_INIT(a) rwlock_init(a)
+
+#define SAVE_AND_DISABLE_INTS(a,b) { \
+ /* printk("get_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ spin_lock_irqsave(a,b); \
+}
+
+#define RESTORE_INTS(a,b) { \
+ /* printk("rel_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ spin_unlock_irqrestore(a,b); \
+}
+
+#define READ_LOCK_IRQSAVE(a,b) { \
+ /* printk("get_read_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ read_lock_irqsave(a,b); \
+}
+
+#define READ_UNLOCK_IRQRESTORE(a,b) { \
+ /* printk("rel_read_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ read_unlock_irqrestore(a,b); \
+}
+
+#define WRITE_LOCK_IRQSAVE(a,b) { \
+ /* printk("get_write_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ write_lock_irqsave(a,b); \
+}
+
+#define WRITE_UNLOCK_IRQRESTORE(a,b) { \
+ /* printk("rel_write_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+ write_unlock_irqrestore(a,b); \
+}
+
+
+//------------------------------------------------------------------------------
+// Hardware-delay loop
+//
+// Probably used in only one place (see i2ellis.c) but this helps keep things
+// together. Note we have unwound the IN instructions. On machines with a
+// reasonable cache, the eight instructions (1 byte each) should fit in cache
+// nicely, and on un-cached machines, the code-fetch would tend not to dominate.
+// Note that cx is shifted so that "count" still reflects the total number of
+// iterations assuming no unwinding.
+//------------------------------------------------------------------------------
+
+//#define DELAY1MS(port,count,label)
+
+//------------------------------------------------------------------------------
+// Macros to switch to a new stack, saving stack pointers, and to restore the
+// old stack (Used, for example, in i2lib.c) "heap" is the address of some
+// buffer which will become the new stack (working down from highest address).
+// The two words at the two lowest addresses in this stack are for storing the
+// SS and SP.
+//------------------------------------------------------------------------------
+
+//#define TO_NEW_STACK(heap,size)
+//#define TO_OLD_STACK(heap)
+
+//------------------------------------------------------------------------------
+// Macros to save the original IRQ vectors and masks, and to patch in new ones.
+//------------------------------------------------------------------------------
+
+//#define SAVE_IRQ_MASKS(dest)
+//#define WRITE_IRQ_MASKS(src)
+//#define SAVE_IRQ_VECTOR(value,dest)
+//#define WRITE_IRQ_VECTOR(value,src)
+
+//------------------------------------------------------------------------------
+// Macro to copy data from one far pointer to another.
+//------------------------------------------------------------------------------
+
+#define I2_MOVE_DATA(fpSource,fpDest,count) memmove(fpDest,fpSource,count);
+
+//------------------------------------------------------------------------------
+// Macros to issue eoi's to host interrupt control (IBM AT 8259-style).
+//------------------------------------------------------------------------------
+
+//#define MASTER_EOI
+//#define SLAVE_EOI
+
+#endif /* I2OS_H */
+
+
diff --git a/drivers/char/ip2/i2pack.h b/drivers/char/ip2/i2pack.h
new file mode 100644
index 000000000000..e9b87a78622c
--- /dev/null
+++ b/drivers/char/ip2/i2pack.h
@@ -0,0 +1,364 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Definitions of the packets used to transfer data and commands
+* Host <--> Board. Information provided here is only applicable
+* when the standard loadware is active.
+*
+*******************************************************************************/
+#ifndef I2PACK_H
+#define I2PACK_H 1
+
+//-----------------------------------------------
+// Revision History:
+//
+// 10 October 1991 MAG First draft
+// 24 February 1992 MAG Additions for 1.4.x loadware
+// 11 March 1992 MAG New status packets
+//
+//-----------------------------------------------
+
+//------------------------------------------------------------------------------
+// Packet Formats:
+//
+// Information passes between the host and board through the FIFO in packets.
+// These have headers which indicate the type of packet. Because the fifo data
+// path may be 16-bits wide, the protocol is constrained such that each packet
+// is always padded to an even byte count. (The lower-level interface routines
+// -- i2ellis.c -- are designed to do this).
+//
+// The sender (be it host or board) must place some number of complete packets
+// in the fifo, then place a message in the mailbox that packets are available.
+// Placing such a message interrupts the "receiver" (be it board or host), who
+// reads the mailbox message and determines that there are incoming packets
+// ready. Since there are no partial packets, and the length of a packet is
+// given in the header, the remainder of the packet can be read without checking
+// for FIFO empty condition. The process is repeated, packet by packet, until
+// the incoming FIFO is empty. Then the receiver uses the outbound mailbox to
+// signal the board that it has read the data. Only then can the sender place
+// additional data in the fifo.
+//------------------------------------------------------------------------------
+//
+//------------------------------------------------
+// Definition of Packet Header Area
+//------------------------------------------------
+//
+// Caution: these only define header areas. In actual use the data runs off
+// beyond the end of these structures.
+//
+// Since these structures are based on sequences of bytes which go to the board,
+// there cannot be ANY padding between the elements.
+#pragma pack(1)
+
+//----------------------------
+// DATA PACKETS
+//----------------------------
+
+typedef struct _i2DataHeader
+{
+ unsigned char i2sChannel; /* The channel number: 0-255 */
+
+ // -- Bitfields are allocated LSB first --
+
+ // For incoming data, indicates whether this is an ordinary packet or a
+ // special one (e.g., hot key hit).
+ unsigned i2sId : 2 __attribute__ ((__packed__));
+
+ // For tagging data packets. There are flush commands which flush only data
+ // packets bearing a particular tag. (used in implementing IntelliView and
+ // IntelliPrint). THE TAG VALUE 0xf is RESERVED and must not be used (it has
+ // meaning internally to the loadware).
+ unsigned i2sTag : 4;
+
+ // These two bits determine the type of packet sent/received.
+ unsigned i2sType : 2;
+
+ // The count of data to follow: does not include the possible additional
+ // padding byte. MAXIMUM COUNT: 4094. The top four bits must be 0.
+ unsigned short i2sCount;
+
+} i2DataHeader, *i2DataHeaderPtr;
+
+// Structure is immediately followed by the data, proper.
+
+//----------------------------
+// NON-DATA PACKETS
+//----------------------------
+
+typedef struct _i2CmdHeader
+{
+ unsigned char i2sChannel; // The channel number: 0-255 (Except where noted
+ // - see below
+
+ // Number of bytes of commands, status or whatever to follow
+ unsigned i2sCount : 6;
+
+ // These two bits determine the type of packet sent/received.
+ unsigned i2sType : 2;
+
+} i2CmdHeader, *i2CmdHeaderPtr;
+
+// Structure is immediately followed by the applicable data.
+
+//---------------------------------------
+// Flow Control Packets (Outbound)
+//---------------------------------------
+
+// One type of outbound command packet is so important that the entire structure
+// is explicitly defined here. That is the flow-control packet. This is never
+// sent by user-level code (as would be the commands to raise/lower DTR, for
+// example). These are only sent by the library routines in response to reading
+// incoming data into the buffers.
+//
+// The parameters inside the command block are maintained in place, then the
+// block is sent at the appropriate time.
+
+typedef struct _flowIn
+{
+ i2CmdHeader hd; // Channel #, count, type (see above)
+ unsigned char fcmd; // The flow control command (37)
+ unsigned short asof; // As of byte number "asof" (LSB first!) I have room
+ // for "room" bytes
+ unsigned short room;
+} flowIn, *flowInPtr;
+
+//----------------------------------------
+// (Incoming) Status Packets
+//----------------------------------------
+
+// Incoming packets which are non-data packets are status packets. In this case,
+// the channel number in the header is unimportant. What follows are one or more
+// sub-packets, the first word of which consists of the channel (first or low
+// byte) and the status indicator (second or high byte), followed by possibly
+// more data.
+
+#define STAT_CTS_UP 0 /* CTS raised (no other bytes) */
+#define STAT_CTS_DN 1 /* CTS dropped (no other bytes) */
+#define STAT_DCD_UP 2 /* DCD raised (no other bytes) */
+#define STAT_DCD_DN 3 /* DCD dropped (no other bytes) */
+#define STAT_DSR_UP 4 /* DSR raised (no other bytes) */
+#define STAT_DSR_DN 5 /* DSR dropped (no other bytes) */
+#define STAT_RI_UP 6 /* RI raised (no other bytes) */
+#define STAT_RI_DN 7 /* RI dropped (no other bytes) */
+#define STAT_BRK_DET 8 /* BRK detect (no other bytes) */
+#define STAT_FLOW 9 /* Flow control(-- more: see below */
+#define STAT_BMARK 10 /* Bookmark (no other bytes)
+ * Bookmark is sent as a response to
+ * a command 60: request for bookmark
+ */
+#define STAT_STATUS 11 /* Special packet: see below */
+#define STAT_TXCNT 12 /* Special packet: see below */
+#define STAT_RXCNT 13 /* Special packet: see below */
+#define STAT_BOXIDS 14 /* Special packet: see below */
+#define STAT_HWFAIL 15 /* Special packet: see below */
+
+#define STAT_MOD_ERROR 0xc0
+#define STAT_MODEM 0xc0/* If status & STAT_MOD_ERROR:
+ * == STAT_MODEM, then this is a modem
+ * status packet, given in response to a
+ * CMD_DSS_NOW command.
+ * The low nibble has each data signal:
+ */
+#define STAT_MOD_DCD 0x8
+#define STAT_MOD_RI 0x4
+#define STAT_MOD_DSR 0x2
+#define STAT_MOD_CTS 0x1
+
+#define STAT_ERROR 0x80/* If status & STAT_MOD_ERROR
+ * == STAT_ERROR, then
+ * sort of error on the channel.
+ * The remaining seven bits indicate
+ * what sort of error it is.
+ */
+/* The low three bits indicate parity, framing, or overrun errors */
+
+#define STAT_E_PARITY 4 /* Parity error */
+#define STAT_E_FRAMING 2 /* Framing error */
+#define STAT_E_OVERRUN 1 /* (uxart) overrun error */
+
+//---------------------------------------
+// STAT_FLOW packets
+//---------------------------------------
+
+typedef struct _flowStat
+{
+ unsigned short asof;
+ unsigned short room;
+}flowStat, *flowStatPtr;
+
+// flowStat packets are received from the board to regulate the flow of outgoing
+// data. A local copy of this structure is also kept to track the amount of
+// credits used and credits remaining. "room" is the amount of space in the
+// board's buffers, "as of" having received a certain byte number. When sending
+// data to the fifo, you must calculate how much buffer space your packet will
+// use. Add this to the current "asof" and subtract it from the current "room".
+//
+// The calculation for the board's buffer is given by CREDIT_USAGE, where size
+// is the un-rounded count of either data characters or command characters.
+// (Which is to say, the count rounded up, plus two).
+
+#define CREDIT_USAGE(size) (((size) + 3) & ~1)
+
+//---------------------------------------
+// STAT_STATUS packets
+//---------------------------------------
+
+typedef struct _debugStat
+{
+ unsigned char d_ccsr;
+ unsigned char d_txinh;
+ unsigned char d_stat1;
+ unsigned char d_stat2;
+} debugStat, *debugStatPtr;
+
+// debugStat packets are sent to the host in response to a CMD_GET_STATUS
+// command. Each byte is bit-mapped as described below:
+
+#define D_CCSR_XON 2 /* Has received XON, ready to transmit */
+#define D_CCSR_XOFF 4 /* Has received XOFF, not transmitting */
+#define D_CCSR_TXENAB 8 /* Transmitter is enabled */
+#define D_CCSR_RXENAB 0x80 /* Receiver is enabled */
+
+#define D_TXINH_BREAK 1 /* We are sending a break */
+#define D_TXINH_EMPTY 2 /* No data to send */
+#define D_TXINH_SUSP 4 /* Output suspended via command 57 */
+#define D_TXINH_CMD 8 /* We are processing an in-line command */
+#define D_TXINH_LCD 0x10 /* LCD diagnostics are running */
+#define D_TXINH_PAUSE 0x20 /* We are processing a PAUSE command */
+#define D_TXINH_DCD 0x40 /* DCD is low, preventing transmission */
+#define D_TXINH_DSR 0x80 /* DSR is low, preventing transmission */
+
+#define D_STAT1_TXEN 1 /* Transmit INTERRUPTS enabled */
+#define D_STAT1_RXEN 2 /* Receiver INTERRUPTS enabled */
+#define D_STAT1_MDEN 4 /* Modem (data set sigs) interrupts enabled */
+#define D_STAT1_RLM 8 /* Remote loopback mode selected */
+#define D_STAT1_LLM 0x10 /* Local internal loopback mode selected */
+#define D_STAT1_CTS 0x20 /* CTS is low, preventing transmission */
+#define D_STAT1_DTR 0x40 /* DTR is low, to stop remote transmission */
+#define D_STAT1_RTS 0x80 /* RTS is low, to stop remote transmission */
+
+#define D_STAT2_TXMT 1 /* Transmit buffers are all empty */
+#define D_STAT2_RXMT 2 /* Receive buffers are all empty */
+#define D_STAT2_RXINH 4 /* Loadware has tried to inhibit remote
+ * transmission: dropped DTR, sent XOFF,
+ * whatever...
+ */
+#define D_STAT2_RXFLO 8 /* Loadware can send no more data to host
+ * until it receives a flow-control packet
+ */
+//-----------------------------------------
+// STAT_TXCNT and STAT_RXCNT packets
+//----------------------------------------
+
+typedef struct _cntStat
+{
+ unsigned short cs_time; // (Assumes host is little-endian!)
+ unsigned short cs_count;
+} cntStat, *cntStatPtr;
+
+// These packets are sent in response to a CMD_GET_RXCNT or a CMD_GET_TXCNT
+// bypass command. cs_time is a running 1 Millisecond counter which acts as a
+// time stamp. cs_count is a running counter of data sent or received from the
+// uxarts. (Not including data added by the chip itself, as with CRLF
+// processing).
+//------------------------------------------
+// STAT_HWFAIL packets
+//------------------------------------------
+
+typedef struct _failStat
+{
+ unsigned char fs_written;
+ unsigned char fs_read;
+ unsigned short fs_address;
+} failStat, *failStatPtr;
+
+// This packet is sent whenever the on-board diagnostic process detects an
+// error. At startup, this process is dormant. The host can wake it up by
+// issuing the bypass command CMD_HW_TEST. The process runs at low priority and
+// performs continuous hardware verification; writing data to certain on-board
+// registers, reading it back, and comparing. If it detects an error, this
+// packet is sent to the host, and the process goes dormant again until the host
+// sends another CMD_HW_TEST. It then continues with the next register to be
+// tested.
+
+//------------------------------------------------------------------------------
+// Macros to deal with the headers more easily! Note that these are defined so
+// they may be used as "left" as well as "right" expressions.
+//------------------------------------------------------------------------------
+
+// Given a pointer to the packet, reference the channel number
+//
+#define CHANNEL_OF(pP) ((i2DataHeaderPtr)(pP))->i2sChannel
+
+// Given a pointer to the packet, reference the Packet type
+//
+#define PTYPE_OF(pP) ((i2DataHeaderPtr)(pP))->i2sType
+
+// The possible types of packets
+//
+#define PTYPE_DATA 0 /* Host <--> Board */
+#define PTYPE_BYPASS 1 /* Host ---> Board */
+#define PTYPE_INLINE 2 /* Host ---> Board */
+#define PTYPE_STATUS 2 /* Host <--- Board */
+
+// Given a pointer to a Data packet, reference the Tag
+//
+#define TAG_OF(pP) ((i2DataHeaderPtr)(pP))->i2sTag
+
+// Given a pointer to a Data packet, reference the data i.d.
+//
+#define ID_OF(pP) ((i2DataHeaderPtr)(pP))->i2sId
+
+// The possible types of ID's
+//
+#define ID_ORDINARY_DATA 0
+#define ID_HOT_KEY 1
+
+// Given a pointer to a Data packet, reference the count
+//
+#define DATA_COUNT_OF(pP) ((i2DataHeaderPtr)(pP))->i2sCount
+
+// Given a pointer to a Data packet, reference the beginning of data
+//
+#define DATA_OF(pP) &((unsigned char *)(pP))[4] // 4 = size of header
+
+// Given a pointer to a Non-Data packet, reference the count
+//
+#define CMD_COUNT_OF(pP) ((i2CmdHeaderPtr)(pP))->i2sCount
+
+#define MAX_CMD_PACK_SIZE 62 // Maximum size of such a count
+
+// Given a pointer to a Non-Data packet, reference the beginning of data
+//
+#define CMD_OF(pP) &((unsigned char *)(pP))[2] // 2 = size of header
+
+//--------------------------------
+// MailBox Bits:
+//--------------------------------
+
+//--------------------------
+// Outgoing (host to board)
+//--------------------------
+//
+#define MB_OUT_STUFFED 0x80 // Host has placed output in fifo
+#define MB_IN_STRIPPED 0x40 // Host has read in all input from fifo
+
+//--------------------------
+// Incoming (board to host)
+//--------------------------
+//
+#define MB_IN_STUFFED 0x80 // Board has placed input in fifo
+#define MB_OUT_STRIPPED 0x40 // Board has read all output from fifo
+#define MB_FATAL_ERROR 0x20 // Board has encountered a fatal error
+
+#pragma pack(4) // Reset padding to command-line default
+
+#endif // I2PACK_H
+
diff --git a/drivers/char/ip2/ip2.h b/drivers/char/ip2/ip2.h
new file mode 100644
index 000000000000..936ccc533949
--- /dev/null
+++ b/drivers/char/ip2/ip2.h
@@ -0,0 +1,107 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Driver constants for configuration and tuning
+*
+* NOTES:
+*
+*******************************************************************************/
+#ifndef IP2_H
+#define IP2_H
+
+#include "ip2types.h"
+#include "i2cmd.h"
+
+/*************/
+/* Constants */
+/*************/
+
+/* Device major numbers - since version 2.0.26. */
+#define IP2_TTY_MAJOR 71
+#define IP2_CALLOUT_MAJOR 72
+#define IP2_IPL_MAJOR 73
+
+/* Board configuration array.
+ * This array defines the hardware irq and address for up to IP2_MAX_BOARDS
+ * (4 supported per ip2_types.h) ISA board addresses and irqs MUST be specified,
+ * PCI and EISA boards are probed for and automagicly configed
+ * iff the addresses are set to 1 and 2 respectivily.
+ * 0x0100 - 0x03f0 == ISA
+ * 1 == PCI
+ * 2 == EISA
+ * 0 == (skip this board)
+ * This array defines the hardware addresses for them. Special
+ * addresses are EISA and PCI which go sniffing for boards.
+
+ * In a multiboard system the position in the array determines which port
+ * devices are assigned to each board:
+ * board 0 is assigned ttyF0.. to ttyF63,
+ * board 1 is assigned ttyF64 to ttyF127,
+ * board 2 is assigned ttyF128 to ttyF191,
+ * board 3 is assigned ttyF192 to ttyF255.
+ *
+ * In PCI and EISA bus systems each range is mapped to card in
+ * monotonically increasing slot number order, ISA position is as specified
+ * here.
+
+ * If the irqs are ALL set to 0,0,0,0 all boards operate in
+ * polled mode. For interrupt operation ISA boards require that the IRQ be
+ * specified, while PCI and EISA boards any nonzero entry
+ * will enable interrupts using the BIOS configured irq for the board.
+ * An invalid irq entry will default to polled mode for that card and print
+ * console warning.
+
+ * When the driver is loaded as a module these setting can be overridden on the
+ * modprobe command line or on an option line in /etc/modprobe.conf.
+ * If the driver is built-in the configuration must be
+ * set here for ISA cards and address set to 1 and 2 for PCI and EISA.
+ *
+ * Here is an example that shows most if not all possibe combinations:
+
+ *static ip2config_t ip2config =
+ *{
+ * {11,1,0,0}, // irqs
+ * { // Addresses
+ * 0x0308, // Board 0, ttyF0 - ttyF63// ISA card at io=0x308, irq=11
+ * 0x0001, // Board 1, ttyF64 - ttyF127//PCI card configured by BIOS
+ * 0x0000, // Board 2, ttyF128 - ttyF191// Slot skipped
+ * 0x0002 // Board 3, ttyF192 - ttyF255//EISA card configured by BIOS
+ * // but polled not irq driven
+ * }
+ *};
+ */
+
+ /* this structure is zeroed out because the suggested method is to configure
+ * the driver as a module, set up the parameters with an options line in
+ * /etc/modprobe.conf and load with modprobe or kmod, the kernel
+ * module loader
+ */
+
+ /* This structure is NOW always initialized when the driver is initialized.
+ * Compiled in defaults MUST be added to the io and irq arrays in
+ * ip2.c. Those values are configurable from insmod parameters in the
+ * case of modules or from command line parameters (ip2=io,irq) when
+ * compiled in.
+ */
+
+static ip2config_t ip2config =
+{
+ {0,0,0,0}, // irqs
+ { // Addresses
+ /* Do NOT set compile time defaults HERE! Use the arrays in
+ ip2.c! These WILL be overwritten! =mhw= */
+ 0x0000, // Board 0, ttyF0 - ttyF63
+ 0x0000, // Board 1, ttyF64 - ttyF127
+ 0x0000, // Board 2, ttyF128 - ttyF191
+ 0x0000 // Board 3, ttyF192 - ttyF255
+ }
+};
+
+#endif
diff --git a/drivers/char/ip2/ip2ioctl.h b/drivers/char/ip2/ip2ioctl.h
new file mode 100644
index 000000000000..aa0a9da85e05
--- /dev/null
+++ b/drivers/char/ip2/ip2ioctl.h
@@ -0,0 +1,35 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Driver constants for configuration and tuning
+*
+* NOTES:
+*
+*******************************************************************************/
+
+#ifndef IP2IOCTL_H
+#define IP2IOCTL_H
+
+//*************
+//* Constants *
+//*************
+
+// High baud rates (if not defined elsewhere.
+#ifndef B153600
+# define B153600 0010005
+#endif
+#ifndef B307200
+# define B307200 0010006
+#endif
+#ifndef B921600
+# define B921600 0010007
+#endif
+
+#endif
diff --git a/drivers/char/ip2/ip2trace.h b/drivers/char/ip2/ip2trace.h
new file mode 100644
index 000000000000..da20435dc8a6
--- /dev/null
+++ b/drivers/char/ip2/ip2trace.h
@@ -0,0 +1,42 @@
+
+//
+union ip2breadcrumb
+{
+ struct {
+ unsigned char port, cat, codes, label;
+ } __attribute__ ((packed)) hdr;
+ unsigned long value;
+};
+
+#define ITRC_NO_PORT 0xFF
+#define CHANN (pCh->port_index)
+
+#define ITRC_ERROR '!'
+#define ITRC_INIT 'A'
+#define ITRC_OPEN 'B'
+#define ITRC_CLOSE 'C'
+#define ITRC_DRAIN 'D'
+#define ITRC_IOCTL 'E'
+#define ITRC_FLUSH 'F'
+#define ITRC_STATUS 'G'
+#define ITRC_HANGUP 'H'
+#define ITRC_INTR 'I'
+#define ITRC_SFLOW 'J'
+#define ITRC_SBCMD 'K'
+#define ITRC_SICMD 'L'
+#define ITRC_MODEM 'M'
+#define ITRC_INPUT 'N'
+#define ITRC_OUTPUT 'O'
+#define ITRC_PUTC 'P'
+#define ITRC_QUEUE 'Q'
+#define ITRC_STFLW 'R'
+#define ITRC_SFIFO 'S'
+#define ITRC_VERIFY 'V'
+#define ITRC_WRITE 'W'
+
+#define ITRC_ENTER 0x00
+#define ITRC_RETURN 0xFF
+
+#define ITRC_QUEUE_ROOM 2
+#define ITRC_QUEUE_CMD 6
+
diff --git a/drivers/char/ip2/ip2types.h b/drivers/char/ip2/ip2types.h
new file mode 100644
index 000000000000..9d67b260b2f6
--- /dev/null
+++ b/drivers/char/ip2/ip2types.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+*
+* (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort II family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Driver constants and type definitions.
+*
+* NOTES:
+*
+*******************************************************************************/
+#ifndef IP2TYPES_H
+#define IP2TYPES_H
+
+//*************
+//* Constants *
+//*************
+
+// Define some limits for this driver. Ports per board is a hardware limitation
+// that will not change. Current hardware limits this to 64 ports per board.
+// Boards per driver is a self-imposed limit.
+//
+#define IP2_MAX_BOARDS 4
+#define IP2_PORTS_PER_BOARD ABS_MOST_PORTS
+#define IP2_MAX_PORTS (IP2_MAX_BOARDS*IP2_PORTS_PER_BOARD)
+
+#define ISA 0
+#define PCI 1
+#define EISA 2
+
+//********************
+//* Type Definitions *
+//********************
+
+typedef struct tty_struct * PTTY;
+typedef wait_queue_head_t PWAITQ;
+
+typedef unsigned char UCHAR;
+typedef unsigned int UINT;
+typedef unsigned short USHORT;
+typedef unsigned long ULONG;
+
+typedef struct
+{
+ short irq[IP2_MAX_BOARDS];
+ unsigned short addr[IP2_MAX_BOARDS];
+ int type[IP2_MAX_BOARDS];
+#ifdef CONFIG_PCI
+ struct pci_dev *pci_dev[IP2_MAX_BOARDS];
+#endif
+} ip2config_t;
+
+#endif
diff --git a/drivers/char/ip27-rtc.c b/drivers/char/ip27-rtc.c
new file mode 100644
index 000000000000..3acdac3c967e
--- /dev/null
+++ b/drivers/char/ip27-rtc.c
@@ -0,0 +1,327 @@
+/*
+ * Driver for the SGS-Thomson M48T35 Timekeeper RAM chip
+ *
+ * Real Time Clock interface for Linux
+ *
+ * TODO: Implement periodic interrupts.
+ *
+ * Copyright (C) 2000 Silicon Graphics, Inc.
+ * Written by Ulf Carlsson (ulfc@engr.sgi.com)
+ *
+ * Based on code written by Paul Gortmaker.
+ *
+ * This driver allows use of the real time clock (built into
+ * nearly all computers) from user space. It exports the /dev/rtc
+ * interface supporting various ioctl() and also the /proc/rtc
+ * pseudo-file for status information.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#define RTC_VERSION "1.09b"
+
+#include <linux/bcd.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/smp_lock.h>
+
+#include <asm/m48t35.h>
+#include <asm/sn/ioc3.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/sn/klconfig.h>
+#include <asm/sn/sn0/ip27.h>
+#include <asm/sn/sn0/hub.h>
+#include <asm/sn/sn_private.h>
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data);
+
+static void get_rtc_time(struct rtc_time *rtc_tm);
+
+/*
+ * Bits in rtc_status. (6 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
+#define RTC_TIMER_ON 0x02 /* missed irq timer active */
+
+static unsigned char rtc_status; /* bitmapped status byte. */
+static unsigned long rtc_freq; /* Current periodic IRQ rate */
+static struct m48t35_rtc *rtc;
+
+/*
+ * If this driver ever becomes modularised, it will be really nice
+ * to make the epoch retain its value across module reload...
+ */
+
+static unsigned long epoch = 1970; /* year corresponding to 0x00 */
+
+static const unsigned char days_in_mo[] =
+{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+
+ struct rtc_time wtime;
+
+ switch (cmd) {
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ {
+ get_rtc_time(&wtime);
+ break;
+ }
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ struct rtc_time rtc_tm;
+ unsigned char mon, day, hrs, min, sec, leap_yr;
+ unsigned int yrs;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ yrs = rtc_tm.tm_year + 1900;
+ mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */
+ day = rtc_tm.tm_mday;
+ hrs = rtc_tm.tm_hour;
+ min = rtc_tm.tm_min;
+ sec = rtc_tm.tm_sec;
+
+ if (yrs < 1970)
+ return -EINVAL;
+
+ leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+ if ((mon > 12) || (day == 0))
+ return -EINVAL;
+
+ if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+ return -EINVAL;
+
+ if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+ return -EINVAL;
+
+ if ((yrs -= epoch) > 255) /* They are unsigned */
+ return -EINVAL;
+
+ if (yrs > 169)
+ return -EINVAL;
+
+ if (yrs >= 100)
+ yrs -= 100;
+
+ sec = BIN2BCD(sec);
+ min = BIN2BCD(min);
+ hrs = BIN2BCD(hrs);
+ day = BIN2BCD(day);
+ mon = BIN2BCD(mon);
+ yrs = BIN2BCD(yrs);
+
+ spin_lock_irq(&rtc_lock);
+ rtc->control |= M48T35_RTC_SET;
+ rtc->year = yrs;
+ rtc->month = mon;
+ rtc->date = day;
+ rtc->hour = hrs;
+ rtc->min = min;
+ rtc->sec = sec;
+ rtc->control &= ~M48T35_RTC_SET;
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+ return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ spin_lock_irq(&rtc_lock);
+
+ if (rtc_status & RTC_IS_OPEN) {
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+ }
+
+ rtc_status |= RTC_IS_OPEN;
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Turn off all interrupts once the device is no longer
+ * in use, and clear the data.
+ */
+
+ spin_lock_irq(&rtc_lock);
+ rtc_status &= ~RTC_IS_OPEN;
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+};
+
+static struct miscdevice rtc_dev=
+{
+ RTC_MINOR,
+ "rtc",
+ &rtc_fops
+};
+
+static int __init rtc_init(void)
+{
+ rtc = (struct m48t35_rtc *)
+ (KL_CONFIG_CH_CONS_INFO(master_nasid)->memory_base + IOC3_BYTEBUS_DEV0);
+
+ printk(KERN_INFO "Real Time Clock Driver v%s\n", RTC_VERSION);
+ if (misc_register(&rtc_dev)) {
+ printk(KERN_ERR "rtc: cannot register misc device.\n");
+ return -ENODEV;
+ }
+ if (!create_proc_read_entry("driver/rtc", 0, NULL, rtc_read_proc, NULL)) {
+ printk(KERN_ERR "rtc: cannot create /proc/rtc.\n");
+ misc_deregister(&rtc_dev);
+ return -ENOENT;
+ }
+
+ rtc_freq = 1024;
+
+ return 0;
+}
+
+static void __exit rtc_exit (void)
+{
+ /* interrupts and timer disabled at this point by rtc_release */
+
+ remove_proc_entry ("rtc", NULL);
+ misc_deregister(&rtc_dev);
+}
+
+module_init(rtc_init);
+module_exit(rtc_exit);
+
+/*
+ * Info exported via "/proc/rtc".
+ */
+
+static int rtc_get_status(char *buf)
+{
+ char *p;
+ struct rtc_time tm;
+
+ /*
+ * Just emulate the standard /proc/rtc
+ */
+
+ p = buf;
+
+ get_rtc_time(&tm);
+
+ /*
+ * There is no way to tell if the luser has the RTC set for local
+ * time or for Universal Standard Time (GMT). Probably local though.
+ */
+ p += sprintf(p,
+ "rtc_time\t: %02d:%02d:%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n"
+ "rtc_epoch\t: %04lu\n"
+ "24hr\t\t: yes\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch);
+
+ return p - buf;
+}
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int len = rtc_get_status(page);
+ if (len <= off+count) *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len>count) len = count;
+ if (len<0) len = 0;
+ return len;
+}
+
+static void get_rtc_time(struct rtc_time *rtc_tm)
+{
+ /*
+ * Do we need to wait for the last update to finish?
+ */
+
+ /*
+ * Only the values that we read from the RTC are set. We leave
+ * tm_wday, tm_yday and tm_isdst untouched. Even though the
+ * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+ * by the RTC when initially set to a non-zero value.
+ */
+ spin_lock_irq(&rtc_lock);
+ rtc->control |= M48T35_RTC_READ;
+ rtc_tm->tm_sec = rtc->sec;
+ rtc_tm->tm_min = rtc->min;
+ rtc_tm->tm_hour = rtc->hour;
+ rtc_tm->tm_mday = rtc->date;
+ rtc_tm->tm_mon = rtc->month;
+ rtc_tm->tm_year = rtc->year;
+ rtc->control &= ~M48T35_RTC_READ;
+ spin_unlock_irq(&rtc_lock);
+
+ rtc_tm->tm_sec = BCD2BIN(rtc_tm->tm_sec);
+ rtc_tm->tm_min = BCD2BIN(rtc_tm->tm_min);
+ rtc_tm->tm_hour = BCD2BIN(rtc_tm->tm_hour);
+ rtc_tm->tm_mday = BCD2BIN(rtc_tm->tm_mday);
+ rtc_tm->tm_mon = BCD2BIN(rtc_tm->tm_mon);
+ rtc_tm->tm_year = BCD2BIN(rtc_tm->tm_year);
+
+ /*
+ * Account for differences between how the RTC uses the values
+ * and how they are defined in a struct rtc_time;
+ */
+ if ((rtc_tm->tm_year += (epoch - 1900)) <= 69)
+ rtc_tm->tm_year += 100;
+
+ rtc_tm->tm_mon--;
+}
diff --git a/drivers/char/ip2main.c b/drivers/char/ip2main.c
new file mode 100644
index 000000000000..fca9a978fb73
--- /dev/null
+++ b/drivers/char/ip2main.c
@@ -0,0 +1,3265 @@
+/*
+*
+* (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+* PACKAGE: Linux tty Device Driver for IntelliPort family of multiport
+* serial I/O controllers.
+*
+* DESCRIPTION: Mainline code for the device driver
+*
+*******************************************************************************/
+// ToDo:
+//
+// Fix the immediate DSS_NOW problem.
+// Work over the channel stats return logic in ip2_ipl_ioctl so they
+// make sense for all 256 possible channels and so the user space
+// utilities will compile and work properly.
+//
+// Done:
+//
+// 1.2.14 /\/\|=mhw=|\/\/
+// Added bounds checking to ip2_ipl_ioctl to avoid potential terroristic acts.
+// Changed the definition of ip2trace to be more consistent with kernel style
+// Thanks to Andreas Dilger <adilger@turbolabs.com> for these updates
+//
+// 1.2.13 /\/\|=mhw=|\/\/
+// DEVFS: Renamed ttf/{n} to tts/F{n} and cuf/{n} to cua/F{n} to conform
+// to agreed devfs serial device naming convention.
+//
+// 1.2.12 /\/\|=mhw=|\/\/
+// Cleaned up some remove queue cut and paste errors
+//
+// 1.2.11 /\/\|=mhw=|\/\/
+// Clean up potential NULL pointer dereferences
+// Clean up devfs registration
+// Add kernel command line parsing for io and irq
+// Compile defaults for io and irq are now set in ip2.c not ip2/ip2.h!
+// Reworked poll_only hack for explicit parameter setting
+// You must now EXPLICITLY set poll_only = 1 or set all irqs to 0
+// Merged ip2_loadmain and old_ip2_init
+// Converted all instances of interruptible_sleep_on into queue calls
+// Most of these had no race conditions but better to clean up now
+//
+// 1.2.10 /\/\|=mhw=|\/\/
+// Fixed the bottom half interrupt handler and enabled USE_IQI
+// to split the interrupt handler into a formal top-half / bottom-half
+// Fixed timing window on high speed processors that queued messages to
+// the outbound mail fifo faster than the board could handle.
+//
+// 1.2.9
+// Four box EX was barfing on >128k kmalloc, made structure smaller by
+// reducing output buffer size
+//
+// 1.2.8
+// Device file system support (MHW)
+//
+// 1.2.7
+// Fixed
+// Reload of ip2 without unloading ip2main hangs system on cat of /proc/modules
+//
+// 1.2.6
+//Fixes DCD problems
+// DCD was not reported when CLOCAL was set on call to TIOCMGET
+//
+//Enhancements:
+// TIOCMGET requests and waits for status return
+// No DSS interrupts enabled except for DCD when needed
+//
+// For internal use only
+//
+//#define IP2DEBUG_INIT
+//#define IP2DEBUG_OPEN
+//#define IP2DEBUG_WRITE
+//#define IP2DEBUG_READ
+//#define IP2DEBUG_IOCTL
+//#define IP2DEBUG_IPL
+
+//#define IP2DEBUG_TRACE
+//#define DEBUG_FIFO
+
+/************/
+/* Includes */
+/************/
+#include <linux/config.h>
+
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/wait.h>
+#include <linux/device.h>
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/termios.h>
+#include <linux/tty_driver.h>
+#include <linux/serial.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+
+#include <linux/cdk.h>
+#include <linux/comstats.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+
+#include <asm/uaccess.h>
+
+#include "./ip2/ip2types.h"
+#include "./ip2/ip2trace.h"
+#include "./ip2/ip2ioctl.h"
+#include "./ip2/ip2.h"
+#include "./ip2/i2ellis.h"
+#include "./ip2/i2lib.h"
+
+/*****************
+ * /proc/ip2mem *
+ *****************/
+
+#include <linux/proc_fs.h>
+
+static int ip2_read_procmem(char *, char **, off_t, int);
+static int ip2_read_proc(char *, char **, off_t, int, int *, void * );
+
+/********************/
+/* Type Definitions */
+/********************/
+
+/*************/
+/* Constants */
+/*************/
+
+/* String constants to identify ourselves */
+static char *pcName = "Computone IntelliPort Plus multiport driver";
+static char *pcVersion = "1.2.14";
+
+/* String constants for port names */
+static char *pcDriver_name = "ip2";
+static char *pcIpl = "ip2ipl";
+
+/* Serial subtype definitions */
+#define SERIAL_TYPE_NORMAL 1
+
+// cheezy kludge or genius - you decide?
+int ip2_loadmain(int *, int *, unsigned char *, int);
+static unsigned char *Fip_firmware;
+static int Fip_firmware_size;
+
+/***********************/
+/* Function Prototypes */
+/***********************/
+
+/* Global module entry functions */
+
+/* Private (static) functions */
+static int ip2_open(PTTY, struct file *);
+static void ip2_close(PTTY, struct file *);
+static int ip2_write(PTTY, int, const unsigned char *, int);
+static void ip2_putchar(PTTY, unsigned char);
+static void ip2_flush_chars(PTTY);
+static int ip2_write_room(PTTY);
+static int ip2_chars_in_buf(PTTY);
+static void ip2_flush_buffer(PTTY);
+static int ip2_ioctl(PTTY, struct file *, UINT, ULONG);
+static void ip2_set_termios(PTTY, struct termios *);
+static void ip2_set_line_discipline(PTTY);
+static void ip2_throttle(PTTY);
+static void ip2_unthrottle(PTTY);
+static void ip2_stop(PTTY);
+static void ip2_start(PTTY);
+static void ip2_hangup(PTTY);
+static int ip2_tiocmget(struct tty_struct *tty, struct file *file);
+static int ip2_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+
+static void set_irq(int, int);
+static void ip2_interrupt_bh(i2eBordStrPtr pB);
+static irqreturn_t ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs);
+static void ip2_poll(unsigned long arg);
+static inline void service_all_boards(void);
+static void do_input(void *p);
+static void do_status(void *p);
+
+static void ip2_wait_until_sent(PTTY,int);
+
+static void set_params (i2ChanStrPtr, struct termios *);
+static int get_serial_info(i2ChanStrPtr, struct serial_struct __user *);
+static int set_serial_info(i2ChanStrPtr, struct serial_struct __user *);
+
+static ssize_t ip2_ipl_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t ip2_ipl_write(struct file *, const char __user *, size_t, loff_t *);
+static int ip2_ipl_ioctl(struct inode *, struct file *, UINT, ULONG);
+static int ip2_ipl_open(struct inode *, struct file *);
+
+static int DumpTraceBuffer(char __user *, int);
+static int DumpFifoBuffer( char __user *, int);
+
+static void ip2_init_board(int);
+static unsigned short find_eisa_board(int);
+
+/***************/
+/* Static Data */
+/***************/
+
+static struct tty_driver *ip2_tty_driver;
+
+/* Here, then is a table of board pointers which the interrupt routine should
+ * scan through to determine who it must service.
+ */
+static unsigned short i2nBoards; // Number of boards here
+
+static i2eBordStrPtr i2BoardPtrTable[IP2_MAX_BOARDS];
+
+static i2ChanStrPtr DevTable[IP2_MAX_PORTS];
+//DevTableMem just used to save addresses for kfree
+static void *DevTableMem[IP2_MAX_BOARDS];
+
+/* This is the driver descriptor for the ip2ipl device, which is used to
+ * download the loadware to the boards.
+ */
+static struct file_operations ip2_ipl = {
+ .owner = THIS_MODULE,
+ .read = ip2_ipl_read,
+ .write = ip2_ipl_write,
+ .ioctl = ip2_ipl_ioctl,
+ .open = ip2_ipl_open,
+};
+
+static unsigned long irq_counter = 0;
+static unsigned long bh_counter = 0;
+
+// Use immediate queue to service interrupts
+#define USE_IQI
+//#define USE_IQ // PCI&2.2 needs work
+
+/* The timer_list entry for our poll routine. If interrupt operation is not
+ * selected, the board is serviced periodically to see if anything needs doing.
+ */
+#define POLL_TIMEOUT (jiffies + 1)
+static struct timer_list PollTimer = TIMER_INITIALIZER(ip2_poll, 0, 0);
+static char TimerOn;
+
+#ifdef IP2DEBUG_TRACE
+/* Trace (debug) buffer data */
+#define TRACEMAX 1000
+static unsigned long tracebuf[TRACEMAX];
+static int tracestuff;
+static int tracestrip;
+static int tracewrap;
+#endif
+
+/**********/
+/* Macros */
+/**********/
+
+#if defined(MODULE) && defined(IP2DEBUG_OPEN)
+#define DBG_CNT(s) printk(KERN_DEBUG "(%s): [%x] refc=%d, ttyc=%d, modc=%x -> %s\n", \
+ tty->name,(pCh->flags),ip2_tty_driver->refcount, \
+ tty->count,/*GET_USE_COUNT(module)*/0,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+/********/
+/* Code */
+/********/
+
+#include "./ip2/i2ellis.c" /* Extremely low-level interface services */
+#include "./ip2/i2cmd.c" /* Standard loadware command definitions */
+#include "./ip2/i2lib.c" /* High level interface services */
+
+/* Configuration area for modprobe */
+
+MODULE_AUTHOR("Doug McNash");
+MODULE_DESCRIPTION("Computone IntelliPort Plus Driver");
+
+static int poll_only = 0;
+
+static int Eisa_irq;
+static int Eisa_slot;
+
+static int iindx;
+static char rirqs[IP2_MAX_BOARDS];
+static int Valid_Irqs[] = { 3, 4, 5, 7, 10, 11, 12, 15, 0};
+
+/* for sysfs class support */
+static struct class_simple *ip2_class;
+
+// Some functions to keep track of what irq's we have
+
+static int __init
+is_valid_irq(int irq)
+{
+ int *i = Valid_Irqs;
+
+ while ((*i != 0) && (*i != irq)) {
+ i++;
+ }
+ return (*i);
+}
+
+static void __init
+mark_requested_irq( char irq )
+{
+ rirqs[iindx++] = irq;
+}
+
+#ifdef MODULE
+static int __init
+clear_requested_irq( char irq )
+{
+ int i;
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if (rirqs[i] == irq) {
+ rirqs[i] = 0;
+ return 1;
+ }
+ }
+ return 0;
+}
+#endif
+
+static int __init
+have_requested_irq( char irq )
+{
+ // array init to zeros so 0 irq will not be requested as a side effect
+ int i;
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if (rirqs[i] == irq)
+ return 1;
+ }
+ return 0;
+}
+
+/******************************************************************************/
+/* Function: init_module() */
+/* Parameters: None */
+/* Returns: Success (0) */
+/* */
+/* Description: */
+/* This is a required entry point for an installable module. It simply calls */
+/* the driver initialisation function and returns what it returns. */
+/******************************************************************************/
+#ifdef MODULE
+int
+init_module(void)
+{
+#ifdef IP2DEBUG_INIT
+ printk (KERN_DEBUG "Loading module ...\n" );
+#endif
+ return 0;
+}
+#endif /* MODULE */
+
+/******************************************************************************/
+/* Function: cleanup_module() */
+/* Parameters: None */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This is a required entry point for an installable module. It has to return */
+/* the device and the driver to a passive state. It should not be necessary */
+/* to reset the board fully, especially as the loadware is downloaded */
+/* externally rather than in the driver. We just want to disable the board */
+/* and clear the loadware to a reset state. To allow this there has to be a */
+/* way to detect whether the board has the loadware running at init time to */
+/* handle subsequent installations of the driver. All memory allocated by the */
+/* driver should be returned since it may be unloaded from memory. */
+/******************************************************************************/
+#ifdef MODULE
+void
+cleanup_module(void)
+{
+ int err;
+ int i;
+
+#ifdef IP2DEBUG_INIT
+ printk (KERN_DEBUG "Unloading %s: version %s\n", pcName, pcVersion );
+#endif
+ /* Stop poll timer if we had one. */
+ if ( TimerOn ) {
+ del_timer ( &PollTimer );
+ TimerOn = 0;
+ }
+
+ /* Reset the boards we have. */
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( i2BoardPtrTable[i] ) {
+ iiReset( i2BoardPtrTable[i] );
+ }
+ }
+
+ /* The following is done at most once, if any boards were installed. */
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( i2BoardPtrTable[i] ) {
+ iiResetDelay( i2BoardPtrTable[i] );
+ /* free io addresses and Tibet */
+ release_region( ip2config.addr[i], 8 );
+ class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 4 * i));
+ devfs_remove("ip2/ipl%d", i);
+ class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 4 * i + 1));
+ devfs_remove("ip2/stat%d", i);
+ }
+ /* Disable and remove interrupt handler. */
+ if ( (ip2config.irq[i] > 0) && have_requested_irq(ip2config.irq[i]) ) {
+ free_irq ( ip2config.irq[i], (void *)&pcName);
+ clear_requested_irq( ip2config.irq[i]);
+ }
+ }
+ class_simple_destroy(ip2_class);
+ devfs_remove("ip2");
+ if ( ( err = tty_unregister_driver ( ip2_tty_driver ) ) ) {
+ printk(KERN_ERR "IP2: failed to unregister tty driver (%d)\n", err);
+ }
+ put_tty_driver(ip2_tty_driver);
+ if ( ( err = unregister_chrdev ( IP2_IPL_MAJOR, pcIpl ) ) ) {
+ printk(KERN_ERR "IP2: failed to unregister IPL driver (%d)\n", err);
+ }
+ remove_proc_entry("ip2mem", &proc_root);
+
+ // free memory
+ for (i = 0; i < IP2_MAX_BOARDS; i++) {
+ void *pB;
+#ifdef CONFIG_PCI
+ if (ip2config.type[i] == PCI && ip2config.pci_dev[i]) {
+ pci_disable_device(ip2config.pci_dev[i]);
+ ip2config.pci_dev[i] = NULL;
+ }
+#endif
+ if ((pB = i2BoardPtrTable[i]) != 0 ) {
+ kfree ( pB );
+ i2BoardPtrTable[i] = NULL;
+ }
+ if ((DevTableMem[i]) != NULL ) {
+ kfree ( DevTableMem[i] );
+ DevTableMem[i] = NULL;
+ }
+ }
+
+ /* Cleanup the iiEllis subsystem. */
+ iiEllisCleanup();
+#ifdef IP2DEBUG_INIT
+ printk (KERN_DEBUG "IP2 Unloaded\n" );
+#endif
+}
+#endif /* MODULE */
+
+static struct tty_operations ip2_ops = {
+ .open = ip2_open,
+ .close = ip2_close,
+ .write = ip2_write,
+ .put_char = ip2_putchar,
+ .flush_chars = ip2_flush_chars,
+ .write_room = ip2_write_room,
+ .chars_in_buffer = ip2_chars_in_buf,
+ .flush_buffer = ip2_flush_buffer,
+ .ioctl = ip2_ioctl,
+ .throttle = ip2_throttle,
+ .unthrottle = ip2_unthrottle,
+ .set_termios = ip2_set_termios,
+ .set_ldisc = ip2_set_line_discipline,
+ .stop = ip2_stop,
+ .start = ip2_start,
+ .hangup = ip2_hangup,
+ .read_proc = ip2_read_proc,
+ .tiocmget = ip2_tiocmget,
+ .tiocmset = ip2_tiocmset,
+};
+
+/******************************************************************************/
+/* Function: ip2_loadmain() */
+/* Parameters: irq, io from command line of insmod et. al. */
+/* pointer to fip firmware and firmware size for boards */
+/* Returns: Success (0) */
+/* */
+/* Description: */
+/* This was the required entry point for all drivers (now in ip2.c) */
+/* It performs all */
+/* initialisation of the devices and driver structures, and registers itself */
+/* with the relevant kernel modules. */
+/******************************************************************************/
+/* SA_INTERRUPT- if set blocks all interrupts else only this line */
+/* SA_SHIRQ - for shared irq PCI or maybe EISA only */
+/* SA_RANDOM - can be source for cert. random number generators */
+#define IP2_SA_FLAGS 0
+
+int
+ip2_loadmain(int *iop, int *irqp, unsigned char *firmware, int firmsize)
+{
+ int i, j, box;
+ int err = 0;
+ int status = 0;
+ static int loaded;
+ i2eBordStrPtr pB = NULL;
+ int rc = -1;
+
+ ip2trace (ITRC_NO_PORT, ITRC_INIT, ITRC_ENTER, 0 );
+
+ /* process command line arguments to modprobe or
+ insmod i.e. iop & irqp */
+ /* irqp and iop should ALWAYS be specified now... But we check
+ them individually just to be sure, anyways... */
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if (iop) {
+ ip2config.addr[i] = iop[i];
+ if (irqp) {
+ if( irqp[i] >= 0 ) {
+ ip2config.irq[i] = irqp[i];
+ } else {
+ ip2config.irq[i] = 0;
+ }
+ // This is a little bit of a hack. If poll_only=1 on command
+ // line back in ip2.c OR all IRQs on all specified boards are
+ // explicitly set to 0, then drop to poll only mode and override
+ // PCI or EISA interrupts. This superceeds the old hack of
+ // triggering if all interrupts were zero (like da default).
+ // Still a hack but less prone to random acts of terrorism.
+ //
+ // What we really should do, now that the IRQ default is set
+ // to -1, is to use 0 as a hard coded, do not probe.
+ //
+ // /\/\|=mhw=|\/\/
+ poll_only |= irqp[i];
+ }
+ }
+ }
+ poll_only = !poll_only;
+
+ Fip_firmware = firmware;
+ Fip_firmware_size = firmsize;
+
+ /* Announce our presence */
+ printk( KERN_INFO "%s version %s\n", pcName, pcVersion );
+
+ // ip2 can be unloaded and reloaded for no good reason
+ // we can't let that happen here or bad things happen
+ // second load hoses board but not system - fixme later
+ if (loaded) {
+ printk( KERN_INFO "Still loaded\n" );
+ return 0;
+ }
+ loaded++;
+
+ ip2_tty_driver = alloc_tty_driver(IP2_MAX_PORTS);
+ if (!ip2_tty_driver)
+ return -ENOMEM;
+
+ /* Initialise the iiEllis subsystem. */
+ iiEllisInit();
+
+ /* Initialize arrays. */
+ memset( i2BoardPtrTable, 0, sizeof i2BoardPtrTable );
+ memset( DevTable, 0, sizeof DevTable );
+
+ /* Initialise all the boards we can find (up to the maximum). */
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ switch ( ip2config.addr[i] ) {
+ case 0: /* skip this slot even if card is present */
+ break;
+ default: /* ISA */
+ /* ISA address must be specified */
+ if ( (ip2config.addr[i] < 0x100) || (ip2config.addr[i] > 0x3f8) ) {
+ printk ( KERN_ERR "IP2: Bad ISA board %d address %x\n",
+ i, ip2config.addr[i] );
+ ip2config.addr[i] = 0;
+ } else {
+ ip2config.type[i] = ISA;
+
+ /* Check for valid irq argument, set for polling if invalid */
+ if (ip2config.irq[i] && !is_valid_irq(ip2config.irq[i])) {
+ printk(KERN_ERR "IP2: Bad IRQ(%d) specified\n",ip2config.irq[i]);
+ ip2config.irq[i] = 0;// 0 is polling and is valid in that sense
+ }
+ }
+ break;
+ case PCI:
+#ifdef CONFIG_PCI
+ {
+ struct pci_dev *pci_dev_i = NULL;
+ pci_dev_i = pci_find_device(PCI_VENDOR_ID_COMPUTONE,
+ PCI_DEVICE_ID_COMPUTONE_IP2EX, pci_dev_i);
+ if (pci_dev_i != NULL) {
+ unsigned int addr;
+
+ if (pci_enable_device(pci_dev_i)) {
+ printk( KERN_ERR "IP2: can't enable PCI device at %s\n",
+ pci_name(pci_dev_i));
+ break;
+ }
+ ip2config.type[i] = PCI;
+ ip2config.pci_dev[i] = pci_dev_i;
+ status =
+ pci_read_config_dword(pci_dev_i, PCI_BASE_ADDRESS_1, &addr);
+ if ( addr & 1 ) {
+ ip2config.addr[i]=(USHORT)(addr&0xfffe);
+ } else {
+ printk( KERN_ERR "IP2: PCI I/O address error\n");
+ }
+
+// If the PCI BIOS assigned it, lets try and use it. If we
+// can't acquire it or it screws up, deal with it then.
+
+// if (!is_valid_irq(pci_irq)) {
+// printk( KERN_ERR "IP2: Bad PCI BIOS IRQ(%d)\n",pci_irq);
+// pci_irq = 0;
+// }
+ ip2config.irq[i] = pci_dev_i->irq;
+ } else { // ann error
+ ip2config.addr[i] = 0;
+ if (status == PCIBIOS_DEVICE_NOT_FOUND) {
+ printk( KERN_ERR "IP2: PCI board %d not found\n", i );
+ } else {
+ printk( KERN_ERR "IP2: PCI error 0x%x \n", status );
+ }
+ }
+ }
+#else
+ printk( KERN_ERR "IP2: PCI card specified but PCI support not\n");
+ printk( KERN_ERR "IP2: configured in this kernel.\n");
+ printk( KERN_ERR "IP2: Recompile kernel with CONFIG_PCI defined!\n");
+#endif /* CONFIG_PCI */
+ break;
+ case EISA:
+ if ( (ip2config.addr[i] = find_eisa_board( Eisa_slot + 1 )) != 0) {
+ /* Eisa_irq set as side effect, boo */
+ ip2config.type[i] = EISA;
+ }
+ ip2config.irq[i] = Eisa_irq;
+ break;
+ } /* switch */
+ } /* for */
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( ip2config.addr[i] ) {
+ pB = kmalloc( sizeof(i2eBordStr), GFP_KERNEL);
+ if ( pB != NULL ) {
+ i2BoardPtrTable[i] = pB;
+ memset( pB, 0, sizeof(i2eBordStr) );
+ iiSetAddress( pB, ip2config.addr[i], ii2DelayTimer );
+ iiReset( pB );
+ } else {
+ printk(KERN_ERR "IP2: board memory allocation error\n");
+ }
+ }
+ }
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( ( pB = i2BoardPtrTable[i] ) != NULL ) {
+ iiResetDelay( pB );
+ break;
+ }
+ }
+ for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( i2BoardPtrTable[i] != NULL ) {
+ ip2_init_board( i );
+ }
+ }
+
+ ip2trace (ITRC_NO_PORT, ITRC_INIT, 2, 0 );
+
+ ip2_tty_driver->owner = THIS_MODULE;
+ ip2_tty_driver->name = "ttyF";
+ ip2_tty_driver->devfs_name = "tts/F";
+ ip2_tty_driver->driver_name = pcDriver_name;
+ ip2_tty_driver->major = IP2_TTY_MAJOR;
+ ip2_tty_driver->minor_start = 0;
+ ip2_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ ip2_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ ip2_tty_driver->init_termios = tty_std_termios;
+ ip2_tty_driver->init_termios.c_cflag = B9600|CS8|CREAD|HUPCL|CLOCAL;
+ ip2_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
+ tty_set_operations(ip2_tty_driver, &ip2_ops);
+
+ ip2trace (ITRC_NO_PORT, ITRC_INIT, 3, 0 );
+
+ /* Register the tty devices. */
+ if ( ( err = tty_register_driver ( ip2_tty_driver ) ) ) {
+ printk(KERN_ERR "IP2: failed to register tty driver (%d)\n", err);
+ put_tty_driver(ip2_tty_driver);
+ return -EINVAL;
+ } else
+ /* Register the IPL driver. */
+ if ( ( err = register_chrdev ( IP2_IPL_MAJOR, pcIpl, &ip2_ipl ) ) ) {
+ printk(KERN_ERR "IP2: failed to register IPL device (%d)\n", err );
+ } else {
+ /* create the sysfs class */
+ ip2_class = class_simple_create(THIS_MODULE, "ip2");
+ if (IS_ERR(ip2_class)) {
+ err = PTR_ERR(ip2_class);
+ goto out_chrdev;
+ }
+ }
+ /* Register the read_procmem thing */
+ if (!create_proc_info_entry("ip2mem",0,&proc_root,ip2_read_procmem)) {
+ printk(KERN_ERR "IP2: failed to register read_procmem\n");
+ } else {
+
+ ip2trace (ITRC_NO_PORT, ITRC_INIT, 4, 0 );
+ /* Register the interrupt handler or poll handler, depending upon the
+ * specified interrupt.
+ */
+
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( 0 == ip2config.addr[i] ) {
+ continue;
+ }
+
+ if ( NULL != ( pB = i2BoardPtrTable[i] ) ) {
+ class_simple_device_add(ip2_class, MKDEV(IP2_IPL_MAJOR,
+ 4 * i), NULL, "ipl%d", i);
+ err = devfs_mk_cdev(MKDEV(IP2_IPL_MAJOR, 4 * i),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IFCHR,
+ "ip2/ipl%d", i);
+ if (err) {
+ class_simple_device_remove(MKDEV(IP2_IPL_MAJOR,
+ 4 * i));
+ goto out_class;
+ }
+
+ class_simple_device_add(ip2_class, MKDEV(IP2_IPL_MAJOR,
+ 4 * i + 1), NULL, "stat%d", i);
+ err = devfs_mk_cdev(MKDEV(IP2_IPL_MAJOR, 4 * i + 1),
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IFCHR,
+ "ip2/stat%d", i);
+ if (err) {
+ class_simple_device_remove(MKDEV(IP2_IPL_MAJOR,
+ 4 * i + 1));
+ goto out_class;
+ }
+
+ for ( box = 0; box < ABS_MAX_BOXES; ++box )
+ {
+ for ( j = 0; j < ABS_BIGGEST_BOX; ++j )
+ {
+ if ( pB->i2eChannelMap[box] & (1 << j) )
+ {
+ tty_register_device(ip2_tty_driver,
+ j + ABS_BIGGEST_BOX *
+ (box+i*ABS_MAX_BOXES), NULL);
+ }
+ }
+ }
+ }
+
+ if (poll_only) {
+// Poll only forces driver to only use polling and
+// to ignore the probed PCI or EISA interrupts.
+ ip2config.irq[i] = CIR_POLL;
+ }
+ if ( ip2config.irq[i] == CIR_POLL ) {
+retry:
+ if (!TimerOn) {
+ PollTimer.expires = POLL_TIMEOUT;
+ add_timer ( &PollTimer );
+ TimerOn = 1;
+ printk( KERN_INFO "IP2: polling\n");
+ }
+ } else {
+ if (have_requested_irq(ip2config.irq[i]))
+ continue;
+ rc = request_irq( ip2config.irq[i], ip2_interrupt,
+ IP2_SA_FLAGS | (ip2config.type[i] == PCI ? SA_SHIRQ : 0),
+ pcName, (void *)&pcName);
+ if (rc) {
+ printk(KERN_ERR "IP2: an request_irq failed: error %d\n",rc);
+ ip2config.irq[i] = CIR_POLL;
+ printk( KERN_INFO "IP2: Polling %ld/sec.\n",
+ (POLL_TIMEOUT - jiffies));
+ goto retry;
+ }
+ mark_requested_irq(ip2config.irq[i]);
+ /* Initialise the interrupt handler bottom half (aka slih). */
+ }
+ }
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ if ( i2BoardPtrTable[i] ) {
+ set_irq( i, ip2config.irq[i] ); /* set and enable board interrupt */
+ }
+ }
+ }
+ ip2trace (ITRC_NO_PORT, ITRC_INIT, ITRC_RETURN, 0 );
+ goto out;
+
+out_class:
+ class_simple_destroy(ip2_class);
+out_chrdev:
+ unregister_chrdev(IP2_IPL_MAJOR, "ip2");
+out:
+ return err;
+}
+
+EXPORT_SYMBOL(ip2_loadmain);
+
+/******************************************************************************/
+/* Function: ip2_init_board() */
+/* Parameters: Index of board in configuration structure */
+/* Returns: Success (0) */
+/* */
+/* Description: */
+/* This function initializes the specified board. The loadware is copied to */
+/* the board, the channel structures are initialized, and the board details */
+/* are reported on the console. */
+/******************************************************************************/
+static void __init
+ip2_init_board( int boardnum )
+{
+ int i;
+ int nports = 0, nboxes = 0;
+ i2ChanStrPtr pCh;
+ i2eBordStrPtr pB = i2BoardPtrTable[boardnum];
+
+ if ( !iiInitialize ( pB ) ) {
+ printk ( KERN_ERR "IP2: Failed to initialize board at 0x%x, error %d\n",
+ pB->i2eBase, pB->i2eError );
+ goto err_initialize;
+ }
+ printk(KERN_INFO "IP2: Board %d: addr=0x%x irq=%d\n", boardnum + 1,
+ ip2config.addr[boardnum], ip2config.irq[boardnum] );
+
+ if (!request_region( ip2config.addr[boardnum], 8, pcName )) {
+ printk(KERN_ERR "IP2: bad addr=0x%x\n", ip2config.addr[boardnum]);
+ goto err_initialize;
+ }
+
+ if ( iiDownloadAll ( pB, (loadHdrStrPtr)Fip_firmware, 1, Fip_firmware_size )
+ != II_DOWN_GOOD ) {
+ printk ( KERN_ERR "IP2: failed to download loadware\n" );
+ goto err_release_region;
+ } else {
+ printk ( KERN_INFO "IP2: fv=%d.%d.%d lv=%d.%d.%d\n",
+ pB->i2ePom.e.porVersion,
+ pB->i2ePom.e.porRevision,
+ pB->i2ePom.e.porSubRev, pB->i2eLVersion,
+ pB->i2eLRevision, pB->i2eLSub );
+ }
+
+ switch ( pB->i2ePom.e.porID & ~POR_ID_RESERVED ) {
+
+ default:
+ printk( KERN_ERR "IP2: Unknown board type, ID = %x\n",
+ pB->i2ePom.e.porID );
+ nports = 0;
+ goto err_release_region;
+ break;
+
+ case POR_ID_II_4: /* IntelliPort-II, ISA-4 (4xRJ45) */
+ printk ( KERN_INFO "IP2: ISA-4\n" );
+ nports = 4;
+ break;
+
+ case POR_ID_II_8: /* IntelliPort-II, 8-port using standard brick. */
+ printk ( KERN_INFO "IP2: ISA-8 std\n" );
+ nports = 8;
+ break;
+
+ case POR_ID_II_8R: /* IntelliPort-II, 8-port using RJ11's (no CTS) */
+ printk ( KERN_INFO "IP2: ISA-8 RJ11\n" );
+ nports = 8;
+ break;
+
+ case POR_ID_FIIEX: /* IntelliPort IIEX */
+ {
+ int portnum = IP2_PORTS_PER_BOARD * boardnum;
+ int box;
+
+ for( box = 0; box < ABS_MAX_BOXES; ++box ) {
+ if ( pB->i2eChannelMap[box] != 0 ) {
+ ++nboxes;
+ }
+ for( i = 0; i < ABS_BIGGEST_BOX; ++i ) {
+ if ( pB->i2eChannelMap[box] & 1<< i ) {
+ ++nports;
+ }
+ }
+ }
+ DevTableMem[boardnum] = pCh =
+ kmalloc( sizeof(i2ChanStr) * nports, GFP_KERNEL );
+ if ( !pCh ) {
+ printk ( KERN_ERR "IP2: (i2_init_channel:) Out of memory.\n");
+ goto err_release_region;
+ }
+ if ( !i2InitChannels( pB, nports, pCh ) ) {
+ printk(KERN_ERR "IP2: i2InitChannels failed: %d\n",pB->i2eError);
+ kfree ( pCh );
+ goto err_release_region;
+ }
+ pB->i2eChannelPtr = &DevTable[portnum];
+ pB->i2eChannelCnt = ABS_MOST_PORTS;
+
+ for( box = 0; box < ABS_MAX_BOXES; ++box, portnum += ABS_BIGGEST_BOX ) {
+ for( i = 0; i < ABS_BIGGEST_BOX; ++i ) {
+ if ( pB->i2eChannelMap[box] & (1 << i) ) {
+ DevTable[portnum + i] = pCh;
+ pCh->port_index = portnum + i;
+ pCh++;
+ }
+ }
+ }
+ printk(KERN_INFO "IP2: EX box=%d ports=%d %d bit\n",
+ nboxes, nports, pB->i2eDataWidth16 ? 16 : 8 );
+ }
+ goto ex_exit;
+ }
+ DevTableMem[boardnum] = pCh =
+ kmalloc ( sizeof (i2ChanStr) * nports, GFP_KERNEL );
+ if ( !pCh ) {
+ printk ( KERN_ERR "IP2: (i2_init_channel:) Out of memory.\n");
+ goto err_release_region;
+ }
+ pB->i2eChannelPtr = pCh;
+ pB->i2eChannelCnt = nports;
+ if ( !i2InitChannels( pB, nports, pCh ) ) {
+ printk(KERN_ERR "IP2: i2InitChannels failed: %d\n",pB->i2eError);
+ kfree ( pCh );
+ goto err_release_region;
+ }
+ pB->i2eChannelPtr = &DevTable[IP2_PORTS_PER_BOARD * boardnum];
+
+ for( i = 0; i < pB->i2eChannelCnt; ++i ) {
+ DevTable[IP2_PORTS_PER_BOARD * boardnum + i] = pCh;
+ pCh->port_index = (IP2_PORTS_PER_BOARD * boardnum) + i;
+ pCh++;
+ }
+ex_exit:
+ INIT_WORK(&pB->tqueue_interrupt, (void(*)(void*)) ip2_interrupt_bh, pB);
+ return;
+
+err_release_region:
+ release_region(ip2config.addr[boardnum], 8);
+err_initialize:
+ kfree ( pB );
+ i2BoardPtrTable[boardnum] = NULL;
+ return;
+}
+
+/******************************************************************************/
+/* Function: find_eisa_board ( int start_slot ) */
+/* Parameters: First slot to check */
+/* Returns: Address of EISA IntelliPort II controller */
+/* */
+/* Description: */
+/* This function searches for an EISA IntelliPort controller, starting */
+/* from the specified slot number. If the motherboard is not identified as an */
+/* EISA motherboard, or no valid board ID is selected it returns 0. Otherwise */
+/* it returns the base address of the controller. */
+/******************************************************************************/
+static unsigned short __init
+find_eisa_board( int start_slot )
+{
+ int i, j;
+ unsigned int idm = 0;
+ unsigned int idp = 0;
+ unsigned int base = 0;
+ unsigned int value;
+ int setup_address;
+ int setup_irq;
+ int ismine = 0;
+
+ /*
+ * First a check for an EISA motherboard, which we do by comparing the
+ * EISA ID registers for the system board and the first couple of slots.
+ * No slot ID should match the system board ID, but on an ISA or PCI
+ * machine the odds are that an empty bus will return similar values for
+ * each slot.
+ */
+ i = 0x0c80;
+ value = (inb(i) << 24) + (inb(i+1) << 16) + (inb(i+2) << 8) + inb(i+3);
+ for( i = 0x1c80; i <= 0x4c80; i += 0x1000 ) {
+ j = (inb(i)<<24)+(inb(i+1)<<16)+(inb(i+2)<<8)+inb(i+3);
+ if ( value == j )
+ return 0;
+ }
+
+ /*
+ * OK, so we are inclined to believe that this is an EISA machine. Find
+ * an IntelliPort controller.
+ */
+ for( i = start_slot; i < 16; i++ ) {
+ base = i << 12;
+ idm = (inb(base + 0xc80) << 8) | (inb(base + 0xc81) & 0xff);
+ idp = (inb(base + 0xc82) << 8) | (inb(base + 0xc83) & 0xff);
+ ismine = 0;
+ if ( idm == 0x0e8e ) {
+ if ( idp == 0x0281 || idp == 0x0218 ) {
+ ismine = 1;
+ } else if ( idp == 0x0282 || idp == 0x0283 ) {
+ ismine = 3; /* Can do edge-trigger */
+ }
+ if ( ismine ) {
+ Eisa_slot = i;
+ break;
+ }
+ }
+ }
+ if ( !ismine )
+ return 0;
+
+ /* It's some sort of EISA card, but at what address is it configured? */
+
+ setup_address = base + 0xc88;
+ value = inb(base + 0xc86);
+ setup_irq = (value & 8) ? Valid_Irqs[value & 7] : 0;
+
+ if ( (ismine & 2) && !(value & 0x10) ) {
+ ismine = 1; /* Could be edging, but not */
+ }
+
+ if ( Eisa_irq == 0 ) {
+ Eisa_irq = setup_irq;
+ } else if ( Eisa_irq != setup_irq ) {
+ printk ( KERN_ERR "IP2: EISA irq mismatch between EISA controllers\n" );
+ }
+
+#ifdef IP2DEBUG_INIT
+printk(KERN_DEBUG "Computone EISA board in slot %d, I.D. 0x%x%x, Address 0x%x",
+ base >> 12, idm, idp, setup_address);
+ if ( Eisa_irq ) {
+ printk(KERN_DEBUG ", Interrupt %d %s\n",
+ setup_irq, (ismine & 2) ? "(edge)" : "(level)");
+ } else {
+ printk(KERN_DEBUG ", (polled)\n");
+ }
+#endif
+ return setup_address;
+}
+
+/******************************************************************************/
+/* Function: set_irq() */
+/* Parameters: index to board in board table */
+/* IRQ to use */
+/* Returns: Success (0) */
+/* */
+/* Description: */
+/******************************************************************************/
+static void
+set_irq( int boardnum, int boardIrq )
+{
+ unsigned char tempCommand[16];
+ i2eBordStrPtr pB = i2BoardPtrTable[boardnum];
+ unsigned long flags;
+
+ /*
+ * Notify the boards they may generate interrupts. This is done by
+ * sending an in-line command to channel 0 on each board. This is why
+ * the channels have to be defined already. For each board, if the
+ * interrupt has never been defined, we must do so NOW, directly, since
+ * board will not send flow control or even give an interrupt until this
+ * is done. If polling we must send 0 as the interrupt parameter.
+ */
+
+ // We will get an interrupt here at the end of this function
+
+ iiDisableMailIrq(pB);
+
+ /* We build up the entire packet header. */
+ CHANNEL_OF(tempCommand) = 0;
+ PTYPE_OF(tempCommand) = PTYPE_INLINE;
+ CMD_COUNT_OF(tempCommand) = 2;
+ (CMD_OF(tempCommand))[0] = CMDVALUE_IRQ;
+ (CMD_OF(tempCommand))[1] = boardIrq;
+ /*
+ * Write to FIFO; don't bother to adjust fifo capacity for this, since
+ * board will respond almost immediately after SendMail hit.
+ */
+ WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+ iiWriteBuf(pB, tempCommand, 4);
+ WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+ pB->i2eUsingIrq = boardIrq;
+ pB->i2eOutMailWaiting |= MB_OUT_STUFFED;
+
+ /* Need to update number of boards before you enable mailbox int */
+ ++i2nBoards;
+
+ CHANNEL_OF(tempCommand) = 0;
+ PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+ CMD_COUNT_OF(tempCommand) = 6;
+ (CMD_OF(tempCommand))[0] = 88; // SILO
+ (CMD_OF(tempCommand))[1] = 64; // chars
+ (CMD_OF(tempCommand))[2] = 32; // ms
+
+ (CMD_OF(tempCommand))[3] = 28; // MAX_BLOCK
+ (CMD_OF(tempCommand))[4] = 64; // chars
+
+ (CMD_OF(tempCommand))[5] = 87; // HW_TEST
+ WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+ iiWriteBuf(pB, tempCommand, 8);
+ WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+
+ CHANNEL_OF(tempCommand) = 0;
+ PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+ CMD_COUNT_OF(tempCommand) = 1;
+ (CMD_OF(tempCommand))[0] = 84; /* get BOX_IDS */
+ iiWriteBuf(pB, tempCommand, 3);
+
+#ifdef XXX
+ // enable heartbeat for test porpoises
+ CHANNEL_OF(tempCommand) = 0;
+ PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+ CMD_COUNT_OF(tempCommand) = 2;
+ (CMD_OF(tempCommand))[0] = 44; /* get ping */
+ (CMD_OF(tempCommand))[1] = 200; /* 200 ms */
+ WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+ iiWriteBuf(pB, tempCommand, 4);
+ WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+#endif
+
+ iiEnableMailIrq(pB);
+ iiSendPendingMail(pB);
+}
+
+/******************************************************************************/
+/* Interrupt Handler Section */
+/******************************************************************************/
+
+static inline void
+service_all_boards(void)
+{
+ int i;
+ i2eBordStrPtr pB;
+
+ /* Service every board on the list */
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ pB = i2BoardPtrTable[i];
+ if ( pB ) {
+ i2ServiceBoard( pB );
+ }
+ }
+}
+
+
+/******************************************************************************/
+/* Function: ip2_interrupt_bh(pB) */
+/* Parameters: pB - pointer to the board structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* Service the board in a bottom half interrupt handler and then */
+/* reenable the board's interrupts if it has an IRQ number */
+/* */
+/******************************************************************************/
+static void
+ip2_interrupt_bh(i2eBordStrPtr pB)
+{
+// pB better well be set or we have a problem! We can only get
+// here from the IMMEDIATE queue. Here, we process the boards.
+// Checking pB doesn't cost much and it saves us from the sanity checkers.
+
+ bh_counter++;
+
+ if ( pB ) {
+ i2ServiceBoard( pB );
+ if( pB->i2eUsingIrq ) {
+// Re-enable his interrupts
+ iiEnableMailIrq(pB);
+ }
+ }
+}
+
+
+/******************************************************************************/
+/* Function: ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs) */
+/* Parameters: irq - interrupt number */
+/* pointer to optional device ID structure */
+/* pointer to register structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* Our task here is simply to identify each board which needs servicing. */
+/* If we are queuing then, queue it to be serviced, and disable its irq */
+/* mask otherwise process the board directly. */
+/* */
+/* We could queue by IRQ but that just complicates things on both ends */
+/* with very little gain in performance (how many instructions does */
+/* it take to iterate on the immediate queue). */
+/* */
+/* */
+/******************************************************************************/
+static irqreturn_t
+ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ int i;
+ i2eBordStrPtr pB;
+ int handled = 0;
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, 99, 1, irq );
+
+ /* Service just the boards on the list using this irq */
+ for( i = 0; i < i2nBoards; ++i ) {
+ pB = i2BoardPtrTable[i];
+
+// Only process those boards which match our IRQ.
+// IRQ = 0 for polled boards, we won't poll "IRQ" boards
+
+ if ( pB && (pB->i2eUsingIrq == irq) ) {
+ handled = 1;
+#ifdef USE_IQI
+
+ if (NO_MAIL_HERE != ( pB->i2eStartMail = iiGetMail(pB))) {
+// Disable his interrupt (will be enabled when serviced)
+// This is mostly to protect from reentrancy.
+ iiDisableMailIrq(pB);
+
+// Park the board on the immediate queue for processing.
+ schedule_work(&pB->tqueue_interrupt);
+
+// Make sure the immediate queue is flagged to fire.
+ }
+#else
+// We are using immediate servicing here. This sucks and can
+// cause all sorts of havoc with ppp and others. The failsafe
+// check on iiSendPendingMail could also throw a hairball.
+ i2ServiceBoard( pB );
+#endif /* USE_IQI */
+ }
+ }
+
+ ++irq_counter;
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, ITRC_RETURN, 0 );
+ return IRQ_RETVAL(handled);
+}
+
+/******************************************************************************/
+/* Function: ip2_poll(unsigned long arg) */
+/* Parameters: ? */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This function calls the library routine i2ServiceBoard for each board in */
+/* the board table. This is used instead of the interrupt routine when polled */
+/* mode is specified. */
+/******************************************************************************/
+static void
+ip2_poll(unsigned long arg)
+{
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, 100, 0 );
+
+ TimerOn = 0; // it's the truth but not checked in service
+
+ // Just polled boards, IRQ = 0 will hit all non-interrupt boards.
+ // It will NOT poll boards handled by hard interrupts.
+ // The issue of queued BH interrups is handled in ip2_interrupt().
+ ip2_interrupt(0, NULL, NULL);
+
+ PollTimer.expires = POLL_TIMEOUT;
+ add_timer( &PollTimer );
+ TimerOn = 1;
+
+ ip2trace (ITRC_NO_PORT, ITRC_INTR, ITRC_RETURN, 0 );
+}
+
+static void do_input(void *p)
+{
+ i2ChanStrPtr pCh = p;
+ unsigned long flags;
+
+ ip2trace(CHANN, ITRC_INPUT, 21, 0 );
+
+ // Data input
+ if ( pCh->pTTY != NULL ) {
+ READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags)
+ if (!pCh->throttled && (pCh->Ibuf_stuff != pCh->Ibuf_strip)) {
+ READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+ i2Input( pCh );
+ } else
+ READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+ } else {
+ ip2trace(CHANN, ITRC_INPUT, 22, 0 );
+
+ i2InputFlush( pCh );
+ }
+}
+
+// code duplicated from n_tty (ldisc)
+static inline void isig(int sig, struct tty_struct *tty, int flush)
+{
+ if (tty->pgrp > 0)
+ kill_pg(tty->pgrp, sig, 1);
+ if (flush || !L_NOFLSH(tty)) {
+ if ( tty->ldisc.flush_buffer )
+ tty->ldisc.flush_buffer(tty);
+ i2InputFlush( tty->driver_data );
+ }
+}
+
+static void do_status(void *p)
+{
+ i2ChanStrPtr pCh = p;
+ int status;
+
+ status = i2GetStatus( pCh, (I2_BRK|I2_PAR|I2_FRA|I2_OVR) );
+
+ ip2trace (CHANN, ITRC_STATUS, 21, 1, status );
+
+ if (pCh->pTTY && (status & (I2_BRK|I2_PAR|I2_FRA|I2_OVR)) ) {
+ if ( (status & I2_BRK) ) {
+ // code duplicated from n_tty (ldisc)
+ if (I_IGNBRK(pCh->pTTY))
+ goto skip_this;
+ if (I_BRKINT(pCh->pTTY)) {
+ isig(SIGINT, pCh->pTTY, 1);
+ goto skip_this;
+ }
+ wake_up_interruptible(&pCh->pTTY->read_wait);
+ }
+#ifdef NEVER_HAPPENS_AS_SETUP_XXX
+ // and can't work because we don't know the_char
+ // as the_char is reported on a separate path
+ // The intelligent board does this stuff as setup
+ {
+ char brkf = TTY_NORMAL;
+ unsigned char brkc = '\0';
+ unsigned char tmp;
+ if ( (status & I2_BRK) ) {
+ brkf = TTY_BREAK;
+ brkc = '\0';
+ }
+ else if (status & I2_PAR) {
+ brkf = TTY_PARITY;
+ brkc = the_char;
+ } else if (status & I2_FRA) {
+ brkf = TTY_FRAME;
+ brkc = the_char;
+ } else if (status & I2_OVR) {
+ brkf = TTY_OVERRUN;
+ brkc = the_char;
+ }
+ tmp = pCh->pTTY->real_raw;
+ pCh->pTTY->real_raw = 0;
+ pCh->pTTY->ldisc.receive_buf( pCh->pTTY, &brkc, &brkf, 1 );
+ pCh->pTTY->real_raw = tmp;
+ }
+#endif /* NEVER_HAPPENS_AS_SETUP_XXX */
+ }
+skip_this:
+
+ if ( status & (I2_DDCD | I2_DDSR | I2_DCTS | I2_DRI) ) {
+ wake_up_interruptible(&pCh->delta_msr_wait);
+
+ if ( (pCh->flags & ASYNC_CHECK_CD) && (status & I2_DDCD) ) {
+ if ( status & I2_DCD ) {
+ if ( pCh->wopen ) {
+ wake_up_interruptible ( &pCh->open_wait );
+ }
+ } else {
+ if (pCh->pTTY && (!(pCh->pTTY->termios->c_cflag & CLOCAL)) ) {
+ tty_hangup( pCh->pTTY );
+ }
+ }
+ }
+ }
+
+ ip2trace (CHANN, ITRC_STATUS, 26, 0 );
+}
+
+/******************************************************************************/
+/* Device Open/Close/Ioctl Entry Point Section */
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function: open_sanity_check() */
+/* Parameters: Pointer to tty structure */
+/* Pointer to file structure */
+/* Returns: Success or failure */
+/* */
+/* Description: */
+/* Verifies the structure magic numbers and cross links. */
+/******************************************************************************/
+#ifdef IP2DEBUG_OPEN
+static void
+open_sanity_check( i2ChanStrPtr pCh, i2eBordStrPtr pBrd )
+{
+ if ( pBrd->i2eValid != I2E_MAGIC ) {
+ printk(KERN_ERR "IP2: invalid board structure\n" );
+ } else if ( pBrd != pCh->pMyBord ) {
+ printk(KERN_ERR "IP2: board structure pointer mismatch (%p)\n",
+ pCh->pMyBord );
+ } else if ( pBrd->i2eChannelCnt < pCh->port_index ) {
+ printk(KERN_ERR "IP2: bad device index (%d)\n", pCh->port_index );
+ } else if (&((i2ChanStrPtr)pBrd->i2eChannelPtr)[pCh->port_index] != pCh) {
+ } else {
+ printk(KERN_INFO "IP2: all pointers check out!\n" );
+ }
+}
+#endif
+
+
+/******************************************************************************/
+/* Function: ip2_open() */
+/* Parameters: Pointer to tty structure */
+/* Pointer to file structure */
+/* Returns: Success or failure */
+/* */
+/* Description: (MANDATORY) */
+/* A successful device open has to run a gauntlet of checks before it */
+/* completes. After some sanity checking and pointer setup, the function */
+/* blocks until all conditions are satisfied. It then initialises the port to */
+/* the default characteristics and returns. */
+/******************************************************************************/
+static int
+ip2_open( PTTY tty, struct file *pFile )
+{
+ wait_queue_t wait;
+ int rc = 0;
+ int do_clocal = 0;
+ i2ChanStrPtr pCh = DevTable[tty->index];
+
+ ip2trace (tty->index, ITRC_OPEN, ITRC_ENTER, 0 );
+
+ if ( pCh == NULL ) {
+ return -ENODEV;
+ }
+ /* Setup pointer links in device and tty structures */
+ pCh->pTTY = tty;
+ tty->driver_data = pCh;
+
+#ifdef IP2DEBUG_OPEN
+ printk(KERN_DEBUG \
+ "IP2:open(tty=%p,pFile=%p):dev=%s,ch=%d,idx=%d\n",
+ tty, pFile, tty->name, pCh->infl.hd.i2sChannel, pCh->port_index);
+ open_sanity_check ( pCh, pCh->pMyBord );
+#endif
+
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 3, CMD_DTRUP,CMD_RTSUP,CMD_DCD_REP);
+ pCh->dataSetOut |= (I2_DTR | I2_RTS);
+ serviceOutgoingFifo( pCh->pMyBord );
+
+ /* Block here until the port is ready (per serial and istallion) */
+ /*
+ * 1. If the port is in the middle of closing wait for the completion
+ * and then return the appropriate error.
+ */
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pCh->close_wait, &wait);
+ set_current_state( TASK_INTERRUPTIBLE );
+
+ if ( tty_hung_up_p(pFile) || ( pCh->flags & ASYNC_CLOSING )) {
+ if ( pCh->flags & ASYNC_CLOSING ) {
+ schedule();
+ }
+ if ( tty_hung_up_p(pFile) ) {
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->close_wait, &wait);
+ return( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EAGAIN : -ERESTARTSYS;
+ }
+ }
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->close_wait, &wait);
+
+ /*
+ * 3. Handle a non-blocking open of a normal port.
+ */
+ if ( (pFile->f_flags & O_NONBLOCK) || (tty->flags & (1<<TTY_IO_ERROR) )) {
+ pCh->flags |= ASYNC_NORMAL_ACTIVE;
+ goto noblock;
+ }
+ /*
+ * 4. Now loop waiting for the port to be free and carrier present
+ * (if required).
+ */
+ if ( tty->termios->c_cflag & CLOCAL )
+ do_clocal = 1;
+
+#ifdef IP2DEBUG_OPEN
+ printk(KERN_DEBUG "OpenBlock: do_clocal = %d\n", do_clocal);
+#endif
+
+ ++pCh->wopen;
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pCh->open_wait, &wait);
+
+ for(;;) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 2, CMD_DTRUP, CMD_RTSUP);
+ pCh->dataSetOut |= (I2_DTR | I2_RTS);
+ set_current_state( TASK_INTERRUPTIBLE );
+ serviceOutgoingFifo( pCh->pMyBord );
+ if ( tty_hung_up_p(pFile) ) {
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->open_wait, &wait);
+ return ( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EBUSY : -ERESTARTSYS;
+ }
+ if (!(pCh->flags & ASYNC_CLOSING) &&
+ (do_clocal || (pCh->dataSetIn & I2_DCD) )) {
+ rc = 0;
+ break;
+ }
+
+#ifdef IP2DEBUG_OPEN
+ printk(KERN_DEBUG "ASYNC_CLOSING = %s\n",
+ (pCh->flags & ASYNC_CLOSING)?"True":"False");
+ printk(KERN_DEBUG "OpenBlock: waiting for CD or signal\n");
+#endif
+ ip2trace (CHANN, ITRC_OPEN, 3, 2, 0,
+ (pCh->flags & ASYNC_CLOSING) );
+ /* check for signal */
+ if (signal_pending(current)) {
+ rc = (( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EAGAIN : -ERESTARTSYS);
+ break;
+ }
+ schedule();
+ }
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->open_wait, &wait);
+
+ --pCh->wopen; //why count?
+
+ ip2trace (CHANN, ITRC_OPEN, 4, 0 );
+
+ if (rc != 0 ) {
+ return rc;
+ }
+ pCh->flags |= ASYNC_NORMAL_ACTIVE;
+
+noblock:
+
+ /* first open - Assign termios structure to port */
+ if ( tty->count == 1 ) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 0, 2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+ /* Now we must send the termios settings to the loadware */
+ set_params( pCh, NULL );
+ }
+
+ /*
+ * Now set any i2lib options. These may go away if the i2lib code ends
+ * up rolled into the mainline.
+ */
+ pCh->channelOptions |= CO_NBLOCK_WRITE;
+
+#ifdef IP2DEBUG_OPEN
+ printk (KERN_DEBUG "IP2: open completed\n" );
+#endif
+ serviceOutgoingFifo( pCh->pMyBord );
+
+ ip2trace (CHANN, ITRC_OPEN, ITRC_RETURN, 0 );
+
+ return 0;
+}
+
+/******************************************************************************/
+/* Function: ip2_close() */
+/* Parameters: Pointer to tty structure */
+/* Pointer to file structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_close( PTTY tty, struct file *pFile )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+
+ if ( !pCh ) {
+ return;
+ }
+
+ ip2trace (CHANN, ITRC_CLOSE, ITRC_ENTER, 0 );
+
+#ifdef IP2DEBUG_OPEN
+ printk(KERN_DEBUG "IP2:close %s:\n",tty->name);
+#endif
+
+ if ( tty_hung_up_p ( pFile ) ) {
+
+ ip2trace (CHANN, ITRC_CLOSE, 2, 1, 2 );
+
+ return;
+ }
+ if ( tty->count > 1 ) { /* not the last close */
+
+ ip2trace (CHANN, ITRC_CLOSE, 2, 1, 3 );
+
+ return;
+ }
+ pCh->flags |= ASYNC_CLOSING; // last close actually
+
+ tty->closing = 1;
+
+ if (pCh->ClosingWaitTime != ASYNC_CLOSING_WAIT_NONE) {
+ /*
+ * Before we drop DTR, make sure the transmitter has completely drained.
+ * This uses an timeout, after which the close
+ * completes.
+ */
+ ip2_wait_until_sent(tty, pCh->ClosingWaitTime );
+ }
+ /*
+ * At this point we stop accepting input. Here we flush the channel
+ * input buffer which will allow the board to send up more data. Any
+ * additional input is tossed at interrupt/poll time.
+ */
+ i2InputFlush( pCh );
+
+ /* disable DSS reporting */
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 4,
+ CMD_DCD_NREP, CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+ if ( !tty || (tty->termios->c_cflag & HUPCL) ) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 2, CMD_RTSDN, CMD_DTRDN);
+ pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+ i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+ }
+
+ serviceOutgoingFifo ( pCh->pMyBord );
+
+ if ( tty->driver->flush_buffer )
+ tty->driver->flush_buffer(tty);
+ if ( tty->ldisc.flush_buffer )
+ tty->ldisc.flush_buffer(tty);
+ tty->closing = 0;
+
+ pCh->pTTY = NULL;
+
+ if (pCh->wopen) {
+ if (pCh->ClosingDelay) {
+ msleep_interruptible(jiffies_to_msecs(pCh->ClosingDelay));
+ }
+ wake_up_interruptible(&pCh->open_wait);
+ }
+
+ pCh->flags &=~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&pCh->close_wait);
+
+#ifdef IP2DEBUG_OPEN
+ DBG_CNT("ip2_close: after wakeups--");
+#endif
+
+
+ ip2trace (CHANN, ITRC_CLOSE, ITRC_RETURN, 1, 1 );
+
+ return;
+}
+
+/******************************************************************************/
+/* Function: ip2_hangup() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_hangup ( PTTY tty )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+
+ if( !pCh ) {
+ return;
+ }
+
+ ip2trace (CHANN, ITRC_HANGUP, ITRC_ENTER, 0 );
+
+ ip2_flush_buffer(tty);
+
+ /* disable DSS reporting */
+
+ i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_DCD_NREP);
+ i2QueueCommands(PTYPE_INLINE, pCh, 0, 2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+ if ( (tty->termios->c_cflag & HUPCL) ) {
+ i2QueueCommands(PTYPE_BYPASS, pCh, 0, 2, CMD_RTSDN, CMD_DTRDN);
+ pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+ i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+ }
+ i2QueueCommands(PTYPE_INLINE, pCh, 1, 3,
+ CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+ serviceOutgoingFifo ( pCh->pMyBord );
+
+ wake_up_interruptible ( &pCh->delta_msr_wait );
+
+ pCh->flags &= ~ASYNC_NORMAL_ACTIVE;
+ pCh->pTTY = NULL;
+ wake_up_interruptible ( &pCh->open_wait );
+
+ ip2trace (CHANN, ITRC_HANGUP, ITRC_RETURN, 0 );
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/* Device Output Section */
+/******************************************************************************/
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function: ip2_write() */
+/* Parameters: Pointer to tty structure */
+/* Flag denoting data is in user (1) or kernel (0) space */
+/* Pointer to data */
+/* Number of bytes to write */
+/* Returns: Number of bytes actually written */
+/* */
+/* Description: (MANDATORY) */
+/* */
+/* */
+/******************************************************************************/
+static int
+ip2_write( PTTY tty, int user, const unsigned char *pData, int count)
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+ int bytesSent = 0;
+ unsigned long flags;
+
+ ip2trace (CHANN, ITRC_WRITE, ITRC_ENTER, 2, count, -1 );
+
+ /* Flush out any buffered data left over from ip2_putchar() calls. */
+ ip2_flush_chars( tty );
+
+ /* This is the actual move bit. Make sure it does what we need!!!!! */
+ WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ bytesSent = i2Output( pCh, pData, count, user );
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+ ip2trace (CHANN, ITRC_WRITE, ITRC_RETURN, 1, bytesSent );
+
+ return bytesSent > 0 ? bytesSent : 0;
+}
+
+/******************************************************************************/
+/* Function: ip2_putchar() */
+/* Parameters: Pointer to tty structure */
+/* Character to write */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_putchar( PTTY tty, unsigned char ch )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+ unsigned long flags;
+
+// ip2trace (CHANN, ITRC_PUTC, ITRC_ENTER, 1, ch );
+
+ WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ pCh->Pbuf[pCh->Pbuf_stuff++] = ch;
+ if ( pCh->Pbuf_stuff == sizeof pCh->Pbuf ) {
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+ ip2_flush_chars( tty );
+ } else
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+// ip2trace (CHANN, ITRC_PUTC, ITRC_RETURN, 1, ch );
+}
+
+/******************************************************************************/
+/* Function: ip2_flush_chars() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/******************************************************************************/
+static void
+ip2_flush_chars( PTTY tty )
+{
+ int strip;
+ i2ChanStrPtr pCh = tty->driver_data;
+ unsigned long flags;
+
+ WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ if ( pCh->Pbuf_stuff ) {
+
+// ip2trace (CHANN, ITRC_PUTC, 10, 1, strip );
+
+ //
+ // We may need to restart i2Output if it does not fullfill this request
+ //
+ strip = i2Output( pCh, pCh->Pbuf, pCh->Pbuf_stuff, 0 );
+ if ( strip != pCh->Pbuf_stuff ) {
+ memmove( pCh->Pbuf, &pCh->Pbuf[strip], pCh->Pbuf_stuff - strip );
+ }
+ pCh->Pbuf_stuff -= strip;
+ }
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+}
+
+/******************************************************************************/
+/* Function: ip2_write_room() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Number of bytes that the driver can accept */
+/* */
+/* Description: */
+/* */
+/******************************************************************************/
+static int
+ip2_write_room ( PTTY tty )
+{
+ int bytesFree;
+ i2ChanStrPtr pCh = tty->driver_data;
+ unsigned long flags;
+
+ READ_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ bytesFree = i2OutputFree( pCh ) - pCh->Pbuf_stuff;
+ READ_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+ ip2trace (CHANN, ITRC_WRITE, 11, 1, bytesFree );
+
+ return ((bytesFree > 0) ? bytesFree : 0);
+}
+
+/******************************************************************************/
+/* Function: ip2_chars_in_buf() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Number of bytes queued for transmission */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static int
+ip2_chars_in_buf ( PTTY tty )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+ int rc;
+ unsigned long flags;
+
+ ip2trace (CHANN, ITRC_WRITE, 12, 1, pCh->Obuf_char_count + pCh->Pbuf_stuff );
+
+#ifdef IP2DEBUG_WRITE
+ printk (KERN_DEBUG "IP2: chars in buffer = %d (%d,%d)\n",
+ pCh->Obuf_char_count + pCh->Pbuf_stuff,
+ pCh->Obuf_char_count, pCh->Pbuf_stuff );
+#endif
+ READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+ rc = pCh->Obuf_char_count;
+ READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+ READ_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ rc += pCh->Pbuf_stuff;
+ READ_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+ return rc;
+}
+
+/******************************************************************************/
+/* Function: ip2_flush_buffer() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_flush_buffer( PTTY tty )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+ unsigned long flags;
+
+ ip2trace (CHANN, ITRC_FLUSH, ITRC_ENTER, 0 );
+
+#ifdef IP2DEBUG_WRITE
+ printk (KERN_DEBUG "IP2: flush buffer\n" );
+#endif
+ WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+ pCh->Pbuf_stuff = 0;
+ WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+ i2FlushOutput( pCh );
+ ip2_owake(tty);
+
+ ip2trace (CHANN, ITRC_FLUSH, ITRC_RETURN, 0 );
+
+}
+
+/******************************************************************************/
+/* Function: ip2_wait_until_sent() */
+/* Parameters: Pointer to tty structure */
+/* Timeout for wait. */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This function is used in place of the normal tty_wait_until_sent, which */
+/* only waits for the driver buffers to be empty (or rather, those buffers */
+/* reported by chars_in_buffer) which doesn't work for IP2 due to the */
+/* indeterminate number of bytes buffered on the board. */
+/******************************************************************************/
+static void
+ip2_wait_until_sent ( PTTY tty, int timeout )
+{
+ int i = jiffies;
+ i2ChanStrPtr pCh = tty->driver_data;
+
+ tty_wait_until_sent(tty, timeout );
+ if ( (i = timeout - (jiffies -i)) > 0)
+ i2DrainOutput( pCh, i );
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/* Device Input Section */
+/******************************************************************************/
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function: ip2_throttle() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_throttle ( PTTY tty )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+
+#ifdef IP2DEBUG_READ
+ printk (KERN_DEBUG "IP2: throttle\n" );
+#endif
+ /*
+ * Signal the poll/interrupt handlers not to forward incoming data to
+ * the line discipline. This will cause the buffers to fill up in the
+ * library and thus cause the library routines to send the flow control
+ * stuff.
+ */
+ pCh->throttled = 1;
+}
+
+/******************************************************************************/
+/* Function: ip2_unthrottle() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_unthrottle ( PTTY tty )
+{
+ i2ChanStrPtr pCh = tty->driver_data;
+ unsigned long flags;
+
+#ifdef IP2DEBUG_READ
+ printk (KERN_DEBUG "IP2: unthrottle\n" );
+#endif
+
+ /* Pass incoming data up to the line discipline again. */
+ pCh->throttled = 0;
+ i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_RESUME);
+ serviceOutgoingFifo( pCh->pMyBord );
+ READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags)
+ if ( pCh->Ibuf_stuff != pCh->Ibuf_strip ) {
+ READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+#ifdef IP2DEBUG_READ
+ printk (KERN_DEBUG "i2Input called from unthrottle\n" );
+#endif
+ i2Input( pCh );
+ } else
+ READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+}
+
+static void
+ip2_start ( PTTY tty )
+{
+ i2ChanStrPtr pCh = DevTable[tty->index];
+
+ i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_RESUME);
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_UNSUSPEND);
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_RESUME);
+#ifdef IP2DEBUG_WRITE
+ printk (KERN_DEBUG "IP2: start tx\n" );
+#endif
+}
+
+static void
+ip2_stop ( PTTY tty )
+{
+ i2ChanStrPtr pCh = DevTable[tty->index];
+
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_SUSPEND);
+#ifdef IP2DEBUG_WRITE
+ printk (KERN_DEBUG "IP2: stop tx\n" );
+#endif
+}
+
+/******************************************************************************/
+/* Device Ioctl Section */
+/******************************************************************************/
+
+static int ip2_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ i2ChanStrPtr pCh = DevTable[tty->index];
+ wait_queue_t wait;
+
+ if (pCh == NULL)
+ return -ENODEV;
+
+/*
+ FIXME - the following code is causing a NULL pointer dereference in
+ 2.3.51 in an interrupt handler. It's suppose to prompt the board
+ to return the DSS signal status immediately. Why doesn't it do
+ the same thing in 2.2.14?
+*/
+
+/* This thing is still busted in the 1.2.12 driver on 2.4.x
+ and even hoses the serial console so the oops can be trapped.
+ /\/\|=mhw=|\/\/ */
+
+#ifdef ENABLE_DSSNOW
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DSS_NOW);
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pCh->dss_now_wait, &wait);
+ set_current_state( TASK_INTERRUPTIBLE );
+
+ serviceOutgoingFifo( pCh->pMyBord );
+
+ schedule();
+
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->dss_now_wait, &wait);
+
+ if (signal_pending(current)) {
+ return -EINTR;
+ }
+#endif
+ return ((pCh->dataSetOut & I2_RTS) ? TIOCM_RTS : 0)
+ | ((pCh->dataSetOut & I2_DTR) ? TIOCM_DTR : 0)
+ | ((pCh->dataSetIn & I2_DCD) ? TIOCM_CAR : 0)
+ | ((pCh->dataSetIn & I2_RI) ? TIOCM_RNG : 0)
+ | ((pCh->dataSetIn & I2_DSR) ? TIOCM_DSR : 0)
+ | ((pCh->dataSetIn & I2_CTS) ? TIOCM_CTS : 0);
+}
+
+static int ip2_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ i2ChanStrPtr pCh = DevTable[tty->index];
+
+ if (pCh == NULL)
+ return -ENODEV;
+
+ if (set & TIOCM_RTS) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_RTSUP);
+ pCh->dataSetOut |= I2_RTS;
+ }
+ if (set & TIOCM_DTR) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DTRUP);
+ pCh->dataSetOut |= I2_DTR;
+ }
+
+ if (clear & TIOCM_RTS) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_RTSDN);
+ pCh->dataSetOut &= ~I2_RTS;
+ }
+ if (clear & TIOCM_DTR) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DTRDN);
+ pCh->dataSetOut &= ~I2_DTR;
+ }
+ serviceOutgoingFifo( pCh->pMyBord );
+ return 0;
+}
+
+/******************************************************************************/
+/* Function: ip2_ioctl() */
+/* Parameters: Pointer to tty structure */
+/* Pointer to file structure */
+/* Command */
+/* Argument */
+/* Returns: Success or failure */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static int
+ip2_ioctl ( PTTY tty, struct file *pFile, UINT cmd, ULONG arg )
+{
+ wait_queue_t wait;
+ i2ChanStrPtr pCh = DevTable[tty->index];
+ struct async_icount cprev, cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser;
+ int rc = 0;
+ unsigned long flags;
+ void __user *argp = (void __user *)arg;
+
+ if ( pCh == NULL ) {
+ return -ENODEV;
+ }
+
+ ip2trace (CHANN, ITRC_IOCTL, ITRC_ENTER, 2, cmd, arg );
+
+#ifdef IP2DEBUG_IOCTL
+ printk(KERN_DEBUG "IP2: ioctl cmd (%x), arg (%lx)\n", cmd, arg );
+#endif
+
+ switch(cmd) {
+ case TIOCGSERIAL:
+
+ ip2trace (CHANN, ITRC_IOCTL, 2, 1, rc );
+
+ rc = get_serial_info(pCh, argp);
+ if (rc)
+ return rc;
+ break;
+
+ case TIOCSSERIAL:
+
+ ip2trace (CHANN, ITRC_IOCTL, 3, 1, rc );
+
+ rc = set_serial_info(pCh, argp);
+ if (rc)
+ return rc;
+ break;
+
+ case TCXONC:
+ rc = tty_check_change(tty);
+ if (rc)
+ return rc;
+ switch (arg) {
+ case TCOOFF:
+ //return -ENOIOCTLCMD;
+ break;
+ case TCOON:
+ //return -ENOIOCTLCMD;
+ break;
+ case TCIOFF:
+ if (STOP_CHAR(tty) != __DISABLED_CHAR) {
+ i2QueueCommands( PTYPE_BYPASS, pCh, 100, 1,
+ CMD_XMIT_NOW(STOP_CHAR(tty)));
+ }
+ break;
+ case TCION:
+ if (START_CHAR(tty) != __DISABLED_CHAR) {
+ i2QueueCommands( PTYPE_BYPASS, pCh, 100, 1,
+ CMD_XMIT_NOW(START_CHAR(tty)));
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ rc = tty_check_change(tty);
+
+ ip2trace (CHANN, ITRC_IOCTL, 4, 1, rc );
+
+ if (!rc) {
+ ip2_wait_until_sent(tty,0);
+ if (!arg) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_SEND_BRK(250));
+ serviceOutgoingFifo( pCh->pMyBord );
+ }
+ }
+ break;
+
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ rc = tty_check_change(tty);
+
+ ip2trace (CHANN, ITRC_IOCTL, 5, 1, rc );
+
+ if (!rc) {
+ ip2_wait_until_sent(tty,0);
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1,
+ CMD_SEND_BRK(arg ? arg*100 : 250));
+ serviceOutgoingFifo ( pCh->pMyBord );
+ }
+ break;
+
+ case TIOCGSOFTCAR:
+
+ ip2trace (CHANN, ITRC_IOCTL, 6, 1, rc );
+
+ rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *)argp);
+ if (rc)
+ return rc;
+ break;
+
+ case TIOCSSOFTCAR:
+
+ ip2trace (CHANN, ITRC_IOCTL, 7, 1, rc );
+
+ rc = get_user(arg,(unsigned long __user *) argp);
+ if (rc)
+ return rc;
+ tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL)
+ | (arg ? CLOCAL : 0));
+
+ break;
+
+ /*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change - mask
+ * passed in arg for lines of interest (use |'ed TIOCM_RNG/DSR/CD/CTS
+ * for masking). Caller should use TIOCGICOUNT to see which one it was
+ */
+ case TIOCMIWAIT:
+ save_flags(flags);cli();
+ cprev = pCh->icount; /* note the counters on entry */
+ restore_flags(flags);
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 4,
+ CMD_DCD_REP, CMD_CTS_REP, CMD_DSR_REP, CMD_RI_REP);
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&pCh->delta_msr_wait, &wait);
+ set_current_state( TASK_INTERRUPTIBLE );
+
+ serviceOutgoingFifo( pCh->pMyBord );
+ for(;;) {
+ ip2trace (CHANN, ITRC_IOCTL, 10, 0 );
+
+ schedule();
+
+ ip2trace (CHANN, ITRC_IOCTL, 11, 0 );
+
+ /* see if a signal did it */
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+ save_flags(flags);cli();
+ cnow = pCh->icount; /* atomic copy */
+ restore_flags(flags);
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+ rc = -EIO; /* no change => rc */
+ break;
+ }
+ if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+ ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+ ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) ||
+ ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+ rc = 0;
+ break;
+ }
+ cprev = cnow;
+ }
+ set_current_state( TASK_RUNNING );
+ remove_wait_queue(&pCh->delta_msr_wait, &wait);
+
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 3,
+ CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+ if ( ! (pCh->flags & ASYNC_CHECK_CD)) {
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DCD_NREP);
+ }
+ serviceOutgoingFifo( pCh->pMyBord );
+ return rc;
+ break;
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for RI where
+ * only 0->1 is counted. The controller is quite capable of counting
+ * both, but this done to preserve compatibility with the standard
+ * serial driver.
+ */
+ case TIOCGICOUNT:
+ ip2trace (CHANN, ITRC_IOCTL, 11, 1, rc );
+
+ save_flags(flags);cli();
+ cnow = pCh->icount;
+ restore_flags(flags);
+ p_cuser = argp;
+ rc = put_user(cnow.cts, &p_cuser->cts);
+ rc = put_user(cnow.dsr, &p_cuser->dsr);
+ rc = put_user(cnow.rng, &p_cuser->rng);
+ rc = put_user(cnow.dcd, &p_cuser->dcd);
+ rc = put_user(cnow.rx, &p_cuser->rx);
+ rc = put_user(cnow.tx, &p_cuser->tx);
+ rc = put_user(cnow.frame, &p_cuser->frame);
+ rc = put_user(cnow.overrun, &p_cuser->overrun);
+ rc = put_user(cnow.parity, &p_cuser->parity);
+ rc = put_user(cnow.brk, &p_cuser->brk);
+ rc = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
+ break;
+
+ /*
+ * The rest are not supported by this driver. By returning -ENOIOCTLCMD they
+ * will be passed to the line discipline for it to handle.
+ */
+ case TIOCSERCONFIG:
+ case TIOCSERGWILD:
+ case TIOCSERGETLSR:
+ case TIOCSERSWILD:
+ case TIOCSERGSTRUCT:
+ case TIOCSERGETMULTI:
+ case TIOCSERSETMULTI:
+
+ default:
+ ip2trace (CHANN, ITRC_IOCTL, 12, 0 );
+
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ ip2trace (CHANN, ITRC_IOCTL, ITRC_RETURN, 0 );
+
+ return rc;
+}
+
+/******************************************************************************/
+/* Function: GetSerialInfo() */
+/* Parameters: Pointer to channel structure */
+/* Pointer to old termios structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This is to support the setserial command, and requires processing of the */
+/* standard Linux serial structure. */
+/******************************************************************************/
+static int
+get_serial_info ( i2ChanStrPtr pCh, struct serial_struct __user *retinfo )
+{
+ struct serial_struct tmp;
+
+ memset ( &tmp, 0, sizeof(tmp) );
+ tmp.type = pCh->pMyBord->channelBtypes.bid_value[(pCh->port_index & (IP2_PORTS_PER_BOARD-1))/16];
+ if (BID_HAS_654(tmp.type)) {
+ tmp.type = PORT_16650;
+ } else {
+ tmp.type = PORT_CIRRUS;
+ }
+ tmp.line = pCh->port_index;
+ tmp.port = pCh->pMyBord->i2eBase;
+ tmp.irq = ip2config.irq[pCh->port_index/64];
+ tmp.flags = pCh->flags;
+ tmp.baud_base = pCh->BaudBase;
+ tmp.close_delay = pCh->ClosingDelay;
+ tmp.closing_wait = pCh->ClosingWaitTime;
+ tmp.custom_divisor = pCh->BaudDivisor;
+ return copy_to_user(retinfo,&tmp,sizeof(*retinfo));
+}
+
+/******************************************************************************/
+/* Function: SetSerialInfo() */
+/* Parameters: Pointer to channel structure */
+/* Pointer to old termios structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This function provides support for setserial, which uses the TIOCSSERIAL */
+/* ioctl. Not all setserial parameters are relevant. If the user attempts to */
+/* change the IRQ, address or type of the port the ioctl fails. */
+/******************************************************************************/
+static int
+set_serial_info( i2ChanStrPtr pCh, struct serial_struct __user *new_info )
+{
+ struct serial_struct ns;
+ int old_flags, old_baud_divisor;
+
+ if (copy_from_user(&ns, new_info, sizeof (ns)))
+ return -EFAULT;
+
+ /*
+ * We don't allow setserial to change IRQ, board address, type or baud
+ * base. Also line nunber as such is meaningless but we use it for our
+ * array index so it is fixed also.
+ */
+ if ( (ns.irq != ip2config.irq[pCh->port_index])
+ || ((int) ns.port != ((int) (pCh->pMyBord->i2eBase)))
+ || (ns.baud_base != pCh->BaudBase)
+ || (ns.line != pCh->port_index) ) {
+ return -EINVAL;
+ }
+
+ old_flags = pCh->flags;
+ old_baud_divisor = pCh->BaudDivisor;
+
+ if ( !capable(CAP_SYS_ADMIN) ) {
+ if ( ( ns.close_delay != pCh->ClosingDelay ) ||
+ ( (ns.flags & ~ASYNC_USR_MASK) !=
+ (pCh->flags & ~ASYNC_USR_MASK) ) ) {
+ return -EPERM;
+ }
+
+ pCh->flags = (pCh->flags & ~ASYNC_USR_MASK) |
+ (ns.flags & ASYNC_USR_MASK);
+ pCh->BaudDivisor = ns.custom_divisor;
+ } else {
+ pCh->flags = (pCh->flags & ~ASYNC_FLAGS) |
+ (ns.flags & ASYNC_FLAGS);
+ pCh->BaudDivisor = ns.custom_divisor;
+ pCh->ClosingDelay = ns.close_delay * HZ/100;
+ pCh->ClosingWaitTime = ns.closing_wait * HZ/100;
+ }
+
+ if ( ( (old_flags & ASYNC_SPD_MASK) != (pCh->flags & ASYNC_SPD_MASK) )
+ || (old_baud_divisor != pCh->BaudDivisor) ) {
+ // Invalidate speed and reset parameters
+ set_params( pCh, NULL );
+ }
+
+ return 0;
+}
+
+/******************************************************************************/
+/* Function: ip2_set_termios() */
+/* Parameters: Pointer to tty structure */
+/* Pointer to old termios structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_set_termios( PTTY tty, struct termios *old_termios )
+{
+ i2ChanStrPtr pCh = (i2ChanStrPtr)tty->driver_data;
+
+#ifdef IP2DEBUG_IOCTL
+ printk (KERN_DEBUG "IP2: set termios %p\n", old_termios );
+#endif
+
+ set_params( pCh, old_termios );
+}
+
+/******************************************************************************/
+/* Function: ip2_set_line_discipline() */
+/* Parameters: Pointer to tty structure */
+/* Returns: Nothing */
+/* */
+/* Description: Does nothing */
+/* */
+/* */
+/******************************************************************************/
+static void
+ip2_set_line_discipline ( PTTY tty )
+{
+#ifdef IP2DEBUG_IOCTL
+ printk (KERN_DEBUG "IP2: set line discipline\n" );
+#endif
+
+ ip2trace (((i2ChanStrPtr)tty->driver_data)->port_index, ITRC_IOCTL, 16, 0 );
+
+}
+
+/******************************************************************************/
+/* Function: SetLine Characteristics() */
+/* Parameters: Pointer to channel structure */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* This routine is called to update the channel structure with the new line */
+/* characteristics, and send the appropriate commands to the board when they */
+/* change. */
+/******************************************************************************/
+static void
+set_params( i2ChanStrPtr pCh, struct termios *o_tios )
+{
+ tcflag_t cflag, iflag, lflag;
+ char stop_char, start_char;
+ struct termios dummy;
+
+ lflag = pCh->pTTY->termios->c_lflag;
+ cflag = pCh->pTTY->termios->c_cflag;
+ iflag = pCh->pTTY->termios->c_iflag;
+
+ if (o_tios == NULL) {
+ dummy.c_lflag = ~lflag;
+ dummy.c_cflag = ~cflag;
+ dummy.c_iflag = ~iflag;
+ o_tios = &dummy;
+ }
+
+ {
+ switch ( cflag & CBAUD ) {
+ case B0:
+ i2QueueCommands( PTYPE_BYPASS, pCh, 100, 2, CMD_RTSDN, CMD_DTRDN);
+ pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+ i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+ pCh->pTTY->termios->c_cflag |= (CBAUD & o_tios->c_cflag);
+ goto service_it;
+ break;
+ case B38400:
+ /*
+ * This is the speed that is overloaded with all the other high
+ * speeds, depending upon the flag settings.
+ */
+ if ( ( pCh->flags & ASYNC_SPD_MASK ) == ASYNC_SPD_HI ) {
+ pCh->speed = CBR_57600;
+ } else if ( (pCh->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI ) {
+ pCh->speed = CBR_115200;
+ } else if ( (pCh->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST ) {
+ pCh->speed = CBR_C1;
+ } else {
+ pCh->speed = CBR_38400;
+ }
+ break;
+ case B50: pCh->speed = CBR_50; break;
+ case B75: pCh->speed = CBR_75; break;
+ case B110: pCh->speed = CBR_110; break;
+ case B134: pCh->speed = CBR_134; break;
+ case B150: pCh->speed = CBR_150; break;
+ case B200: pCh->speed = CBR_200; break;
+ case B300: pCh->speed = CBR_300; break;
+ case B600: pCh->speed = CBR_600; break;
+ case B1200: pCh->speed = CBR_1200; break;
+ case B1800: pCh->speed = CBR_1800; break;
+ case B2400: pCh->speed = CBR_2400; break;
+ case B4800: pCh->speed = CBR_4800; break;
+ case B9600: pCh->speed = CBR_9600; break;
+ case B19200: pCh->speed = CBR_19200; break;
+ case B57600: pCh->speed = CBR_57600; break;
+ case B115200: pCh->speed = CBR_115200; break;
+ case B153600: pCh->speed = CBR_153600; break;
+ case B230400: pCh->speed = CBR_230400; break;
+ case B307200: pCh->speed = CBR_307200; break;
+ case B460800: pCh->speed = CBR_460800; break;
+ case B921600: pCh->speed = CBR_921600; break;
+ default: pCh->speed = CBR_9600; break;
+ }
+ if ( pCh->speed == CBR_C1 ) {
+ // Process the custom speed parameters.
+ int bps = pCh->BaudBase / pCh->BaudDivisor;
+ if ( bps == 921600 ) {
+ pCh->speed = CBR_921600;
+ } else {
+ bps = bps/10;
+ i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_BAUD_DEF1(bps) );
+ }
+ }
+ i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_SETBAUD(pCh->speed));
+
+ i2QueueCommands ( PTYPE_INLINE, pCh, 100, 2, CMD_DTRUP, CMD_RTSUP);
+ pCh->dataSetOut |= (I2_DTR | I2_RTS);
+ }
+ if ( (CSTOPB & cflag) ^ (CSTOPB & o_tios->c_cflag))
+ {
+ i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1,
+ CMD_SETSTOP( ( cflag & CSTOPB ) ? CST_2 : CST_1));
+ }
+ if (((PARENB|PARODD) & cflag) ^ ((PARENB|PARODD) & o_tios->c_cflag))
+ {
+ i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1,
+ CMD_SETPAR(
+ (cflag & PARENB ? (cflag & PARODD ? CSP_OD : CSP_EV) : CSP_NP)
+ )
+ );
+ }
+ /* byte size and parity */
+ if ( (CSIZE & cflag)^(CSIZE & o_tios->c_cflag))
+ {
+ int datasize;
+ switch ( cflag & CSIZE ) {
+ case CS5: datasize = CSZ_5; break;
+ case CS6: datasize = CSZ_6; break;
+ case CS7: datasize = CSZ_7; break;
+ case CS8: datasize = CSZ_8; break;
+ default: datasize = CSZ_5; break; /* as per serial.c */
+ }
+ i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1, CMD_SETBITS(datasize) );
+ }
+ /* Process CTS flow control flag setting */
+ if ( (cflag & CRTSCTS) ) {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100,
+ 2, CMD_CTSFL_ENAB, CMD_RTSFL_ENAB);
+ } else {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100,
+ 2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+ }
+ //
+ // Process XON/XOFF flow control flags settings
+ //
+ stop_char = STOP_CHAR(pCh->pTTY);
+ start_char = START_CHAR(pCh->pTTY);
+
+ //////////// can't be \000
+ if (stop_char == __DISABLED_CHAR )
+ {
+ stop_char = ~__DISABLED_CHAR;
+ }
+ if (start_char == __DISABLED_CHAR )
+ {
+ start_char = ~__DISABLED_CHAR;
+ }
+ /////////////////////////////////
+
+ if ( o_tios->c_cc[VSTART] != start_char )
+ {
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DEF_IXON(start_char));
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DEF_OXON(start_char));
+ }
+ if ( o_tios->c_cc[VSTOP] != stop_char )
+ {
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DEF_IXOFF(stop_char));
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DEF_OXOFF(stop_char));
+ }
+ if (stop_char == __DISABLED_CHAR )
+ {
+ stop_char = ~__DISABLED_CHAR; //TEST123
+ goto no_xoff;
+ }
+ if ((iflag & (IXOFF))^(o_tios->c_iflag & (IXOFF)))
+ {
+ if ( iflag & IXOFF ) { // Enable XOFF output flow control
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_OXON_OPT(COX_XON));
+ } else { // Disable XOFF output flow control
+no_xoff:
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_OXON_OPT(COX_NONE));
+ }
+ }
+ if (start_char == __DISABLED_CHAR )
+ {
+ goto no_xon;
+ }
+ if ((iflag & (IXON|IXANY)) ^ (o_tios->c_iflag & (IXON|IXANY)))
+ {
+ if ( iflag & IXON ) {
+ if ( iflag & IXANY ) { // Enable XON/XANY output flow control
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_XANY));
+ } else { // Enable XON output flow control
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_XON));
+ }
+ } else { // Disable XON output flow control
+no_xon:
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_NONE));
+ }
+ }
+ if ( (iflag & ISTRIP) ^ ( o_tios->c_iflag & (ISTRIP)) )
+ {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1,
+ CMD_ISTRIP_OPT((iflag & ISTRIP ? 1 : 0)));
+ }
+ if ( (iflag & INPCK) ^ ( o_tios->c_iflag & (INPCK)) )
+ {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1,
+ CMD_PARCHK((iflag & INPCK) ? CPK_ENAB : CPK_DSAB));
+ }
+
+ if ( (iflag & (IGNBRK|PARMRK|BRKINT|IGNPAR))
+ ^ ( o_tios->c_iflag & (IGNBRK|PARMRK|BRKINT|IGNPAR)) )
+ {
+ char brkrpt = 0;
+ char parrpt = 0;
+
+ if ( iflag & IGNBRK ) { /* Ignore breaks altogether */
+ /* Ignore breaks altogether */
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_BRK_NREP);
+ } else {
+ if ( iflag & BRKINT ) {
+ if ( iflag & PARMRK ) {
+ brkrpt = 0x0a; // exception an inline triple
+ } else {
+ brkrpt = 0x1a; // exception and NULL
+ }
+ brkrpt |= 0x04; // flush input
+ } else {
+ if ( iflag & PARMRK ) {
+ brkrpt = 0x0b; //POSIX triple \0377 \0 \0
+ } else {
+ brkrpt = 0x01; // Null only
+ }
+ }
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_BRK_REP(brkrpt));
+ }
+
+ if (iflag & IGNPAR) {
+ parrpt = 0x20;
+ /* would be 2 for not cirrus bug */
+ /* would be 0x20 cept for cirrus bug */
+ } else {
+ if ( iflag & PARMRK ) {
+ /*
+ * Replace error characters with 3-byte sequence (\0377,\0,char)
+ */
+ parrpt = 0x04 ;
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_ISTRIP_OPT((char)0));
+ } else {
+ parrpt = 0x03;
+ }
+ }
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_SET_ERROR(parrpt));
+ }
+ if (cflag & CLOCAL) {
+ // Status reporting fails for DCD if this is off
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DCD_NREP);
+ pCh->flags &= ~ASYNC_CHECK_CD;
+ } else {
+ i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DCD_REP);
+ pCh->flags |= ASYNC_CHECK_CD;
+ }
+
+#ifdef XXX
+do_flags_thing: // This is a test, we don't do the flags thing
+
+ if ( (cflag & CRTSCTS) ) {
+ cflag |= 014000000000;
+ }
+ i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1,
+ CMD_UNIX_FLAGS(iflag,cflag,lflag));
+#endif
+
+service_it:
+ i2DrainOutput( pCh, 100 );
+}
+
+/******************************************************************************/
+/* IPL Device Section */
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function: ip2_ipl_read() */
+/* Parameters: Pointer to device inode */
+/* Pointer to file structure */
+/* Pointer to data */
+/* Number of bytes to read */
+/* Returns: Success or failure */
+/* */
+/* Description: Ugly */
+/* */
+/* */
+/******************************************************************************/
+
+static
+ssize_t
+ip2_ipl_read(struct file *pFile, char __user *pData, size_t count, loff_t *off )
+{
+ unsigned int minor = iminor(pFile->f_dentry->d_inode);
+ int rc = 0;
+
+#ifdef IP2DEBUG_IPL
+ printk (KERN_DEBUG "IP2IPL: read %p, %d bytes\n", pData, count );
+#endif
+
+ switch( minor ) {
+ case 0: // IPL device
+ rc = -EINVAL;
+ break;
+ case 1: // Status dump
+ rc = -EINVAL;
+ break;
+ case 2: // Ping device
+ rc = -EINVAL;
+ break;
+ case 3: // Trace device
+ rc = DumpTraceBuffer ( pData, count );
+ break;
+ case 4: // Trace device
+ rc = DumpFifoBuffer ( pData, count );
+ break;
+ default:
+ rc = -ENODEV;
+ break;
+ }
+ return rc;
+}
+
+static int
+DumpFifoBuffer ( char __user *pData, int count )
+{
+#ifdef DEBUG_FIFO
+ int rc;
+ rc = copy_to_user(pData, DBGBuf, count);
+
+ printk(KERN_DEBUG "Last index %d\n", I );
+
+ return count;
+#endif /* DEBUG_FIFO */
+ return 0;
+}
+
+static int
+DumpTraceBuffer ( char __user *pData, int count )
+{
+#ifdef IP2DEBUG_TRACE
+ int rc;
+ int dumpcount;
+ int chunk;
+ int *pIndex = (int __user *)pData;
+
+ if ( count < (sizeof(int) * 6) ) {
+ return -EIO;
+ }
+ rc = put_user(tracewrap, pIndex );
+ rc = put_user(TRACEMAX, ++pIndex );
+ rc = put_user(tracestrip, ++pIndex );
+ rc = put_user(tracestuff, ++pIndex );
+ pData += sizeof(int) * 6;
+ count -= sizeof(int) * 6;
+
+ dumpcount = tracestuff - tracestrip;
+ if ( dumpcount < 0 ) {
+ dumpcount += TRACEMAX;
+ }
+ if ( dumpcount > count ) {
+ dumpcount = count;
+ }
+ chunk = TRACEMAX - tracestrip;
+ if ( dumpcount > chunk ) {
+ rc = copy_to_user(pData, &tracebuf[tracestrip],
+ chunk * sizeof(tracebuf[0]) );
+ pData += chunk * sizeof(tracebuf[0]);
+ tracestrip = 0;
+ chunk = dumpcount - chunk;
+ } else {
+ chunk = dumpcount;
+ }
+ rc = copy_to_user(pData, &tracebuf[tracestrip],
+ chunk * sizeof(tracebuf[0]) );
+ tracestrip += chunk;
+ tracewrap = 0;
+
+ rc = put_user(tracestrip, ++pIndex );
+ rc = put_user(tracestuff, ++pIndex );
+
+ return dumpcount;
+#else
+ return 0;
+#endif
+}
+
+/******************************************************************************/
+/* Function: ip2_ipl_write() */
+/* Parameters: */
+/* Pointer to file structure */
+/* Pointer to data */
+/* Number of bytes to write */
+/* Returns: Success or failure */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static ssize_t
+ip2_ipl_write(struct file *pFile, const char __user *pData, size_t count, loff_t *off)
+{
+#ifdef IP2DEBUG_IPL
+ printk (KERN_DEBUG "IP2IPL: write %p, %d bytes\n", pData, count );
+#endif
+ return 0;
+}
+
+/******************************************************************************/
+/* Function: ip2_ipl_ioctl() */
+/* Parameters: Pointer to device inode */
+/* Pointer to file structure */
+/* Command */
+/* Argument */
+/* Returns: Success or failure */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static int
+ip2_ipl_ioctl ( struct inode *pInode, struct file *pFile, UINT cmd, ULONG arg )
+{
+ unsigned int iplminor = iminor(pInode);
+ int rc = 0;
+ void __user *argp = (void __user *)arg;
+ ULONG __user *pIndex = argp;
+ i2eBordStrPtr pB = i2BoardPtrTable[iplminor / 4];
+ i2ChanStrPtr pCh;
+
+#ifdef IP2DEBUG_IPL
+ printk (KERN_DEBUG "IP2IPL: ioctl cmd %d, arg %ld\n", cmd, arg );
+#endif
+
+ switch ( iplminor ) {
+ case 0: // IPL device
+ rc = -EINVAL;
+ break;
+ case 1: // Status dump
+ case 5:
+ case 9:
+ case 13:
+ switch ( cmd ) {
+ case 64: /* Driver - ip2stat */
+ rc = put_user(ip2_tty_driver->refcount, pIndex++ );
+ rc = put_user(irq_counter, pIndex++ );
+ rc = put_user(bh_counter, pIndex++ );
+ break;
+
+ case 65: /* Board - ip2stat */
+ if ( pB ) {
+ rc = copy_to_user(argp, pB, sizeof(i2eBordStr));
+ rc = put_user(INB(pB->i2eStatus),
+ (ULONG __user *)(arg + (ULONG)(&pB->i2eStatus) - (ULONG)pB ) );
+ } else {
+ rc = -ENODEV;
+ }
+ break;
+
+ default:
+ if (cmd < IP2_MAX_PORTS) {
+ pCh = DevTable[cmd];
+ if ( pCh )
+ {
+ rc = copy_to_user(argp, pCh, sizeof(i2ChanStr));
+ } else {
+ rc = -ENODEV;
+ }
+ } else {
+ rc = -EINVAL;
+ }
+ }
+ break;
+
+ case 2: // Ping device
+ rc = -EINVAL;
+ break;
+ case 3: // Trace device
+ if ( cmd == 1 ) {
+ rc = put_user(iiSendPendingMail, pIndex++ );
+ rc = put_user(i2InitChannels, pIndex++ );
+ rc = put_user(i2QueueNeeds, pIndex++ );
+ rc = put_user(i2QueueCommands, pIndex++ );
+ rc = put_user(i2GetStatus, pIndex++ );
+ rc = put_user(i2Input, pIndex++ );
+ rc = put_user(i2InputFlush, pIndex++ );
+ rc = put_user(i2Output, pIndex++ );
+ rc = put_user(i2FlushOutput, pIndex++ );
+ rc = put_user(i2DrainWakeup, pIndex++ );
+ rc = put_user(i2DrainOutput, pIndex++ );
+ rc = put_user(i2OutputFree, pIndex++ );
+ rc = put_user(i2StripFifo, pIndex++ );
+ rc = put_user(i2StuffFifoBypass, pIndex++ );
+ rc = put_user(i2StuffFifoFlow, pIndex++ );
+ rc = put_user(i2StuffFifoInline, pIndex++ );
+ rc = put_user(i2ServiceBoard, pIndex++ );
+ rc = put_user(serviceOutgoingFifo, pIndex++ );
+ // rc = put_user(ip2_init, pIndex++ );
+ rc = put_user(ip2_init_board, pIndex++ );
+ rc = put_user(find_eisa_board, pIndex++ );
+ rc = put_user(set_irq, pIndex++ );
+ rc = put_user(ip2_interrupt, pIndex++ );
+ rc = put_user(ip2_poll, pIndex++ );
+ rc = put_user(service_all_boards, pIndex++ );
+ rc = put_user(do_input, pIndex++ );
+ rc = put_user(do_status, pIndex++ );
+#ifndef IP2DEBUG_OPEN
+ rc = put_user(0, pIndex++ );
+#else
+ rc = put_user(open_sanity_check, pIndex++ );
+#endif
+ rc = put_user(ip2_open, pIndex++ );
+ rc = put_user(ip2_close, pIndex++ );
+ rc = put_user(ip2_hangup, pIndex++ );
+ rc = put_user(ip2_write, pIndex++ );
+ rc = put_user(ip2_putchar, pIndex++ );
+ rc = put_user(ip2_flush_chars, pIndex++ );
+ rc = put_user(ip2_write_room, pIndex++ );
+ rc = put_user(ip2_chars_in_buf, pIndex++ );
+ rc = put_user(ip2_flush_buffer, pIndex++ );
+
+ //rc = put_user(ip2_wait_until_sent, pIndex++ );
+ rc = put_user(0, pIndex++ );
+
+ rc = put_user(ip2_throttle, pIndex++ );
+ rc = put_user(ip2_unthrottle, pIndex++ );
+ rc = put_user(ip2_ioctl, pIndex++ );
+ rc = put_user(0, pIndex++ );
+ rc = put_user(get_serial_info, pIndex++ );
+ rc = put_user(set_serial_info, pIndex++ );
+ rc = put_user(ip2_set_termios, pIndex++ );
+ rc = put_user(ip2_set_line_discipline, pIndex++ );
+ rc = put_user(set_params, pIndex++ );
+ } else {
+ rc = -EINVAL;
+ }
+
+ break;
+
+ default:
+ rc = -ENODEV;
+ break;
+ }
+ return rc;
+}
+
+/******************************************************************************/
+/* Function: ip2_ipl_open() */
+/* Parameters: Pointer to device inode */
+/* Pointer to file structure */
+/* Returns: Success or failure */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+static int
+ip2_ipl_open( struct inode *pInode, struct file *pFile )
+{
+ unsigned int iplminor = iminor(pInode);
+ i2eBordStrPtr pB;
+ i2ChanStrPtr pCh;
+
+#ifdef IP2DEBUG_IPL
+ printk (KERN_DEBUG "IP2IPL: open\n" );
+#endif
+
+ switch(iplminor) {
+ // These are the IPL devices
+ case 0:
+ case 4:
+ case 8:
+ case 12:
+ break;
+
+ // These are the status devices
+ case 1:
+ case 5:
+ case 9:
+ case 13:
+ break;
+
+ // These are the debug devices
+ case 2:
+ case 6:
+ case 10:
+ case 14:
+ pB = i2BoardPtrTable[iplminor / 4];
+ pCh = (i2ChanStrPtr) pB->i2eChannelPtr;
+ break;
+
+ // This is the trace device
+ case 3:
+ break;
+ }
+ return 0;
+}
+/******************************************************************************/
+/* Function: ip2_read_procmem */
+/* Parameters: */
+/* */
+/* Returns: Length of output */
+/* */
+/* Description: */
+/* Supplies some driver operating parameters */
+/* Not real useful unless your debugging the fifo */
+/* */
+/******************************************************************************/
+
+#define LIMIT (PAGE_SIZE - 120)
+
+static int
+ip2_read_procmem(char *buf, char **start, off_t offset, int len)
+{
+ i2eBordStrPtr pB;
+ i2ChanStrPtr pCh;
+ PTTY tty;
+ int i;
+
+ len = 0;
+
+#define FMTLINE "%3d: 0x%08x 0x%08x 0%011o 0%011o\n"
+#define FMTLIN2 " 0x%04x 0x%04x tx flow 0x%x\n"
+#define FMTLIN3 " 0x%04x 0x%04x rc flow\n"
+
+ len += sprintf(buf+len,"\n");
+
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ pB = i2BoardPtrTable[i];
+ if ( pB ) {
+ len += sprintf(buf+len,"board %d:\n",i);
+ len += sprintf(buf+len,"\tFifo rem: %d mty: %x outM %x\n",
+ pB->i2eFifoRemains,pB->i2eWaitingForEmptyFifo,pB->i2eOutMailWaiting);
+ }
+ }
+
+ len += sprintf(buf+len,"#: tty flags, port flags, cflags, iflags\n");
+ for (i=0; i < IP2_MAX_PORTS; i++) {
+ if (len > LIMIT)
+ break;
+ pCh = DevTable[i];
+ if (pCh) {
+ tty = pCh->pTTY;
+ if (tty && tty->count) {
+ len += sprintf(buf+len,FMTLINE,i,(int)tty->flags,pCh->flags,
+ tty->termios->c_cflag,tty->termios->c_iflag);
+
+ len += sprintf(buf+len,FMTLIN2,
+ pCh->outfl.asof,pCh->outfl.room,pCh->channelNeeds);
+ len += sprintf(buf+len,FMTLIN3,pCh->infl.asof,pCh->infl.room);
+ }
+ }
+ }
+ return len;
+}
+
+/*
+ * This is the handler for /proc/tty/driver/ip2
+ *
+ * This stretch of code has been largely plagerized from at least three
+ * different sources including ip2mkdev.c and a couple of other drivers.
+ * The bugs are all mine. :-) =mhw=
+ */
+static int ip2_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ int i, j, box;
+ int len = 0;
+ int boxes = 0;
+ int ports = 0;
+ int tports = 0;
+ off_t begin = 0;
+ i2eBordStrPtr pB;
+
+ len += sprintf(page, "ip2info: 1.0 driver: %s\n", pcVersion );
+ len += sprintf(page+len, "Driver: SMajor=%d CMajor=%d IMajor=%d MaxBoards=%d MaxBoxes=%d MaxPorts=%d\n",
+ IP2_TTY_MAJOR, IP2_CALLOUT_MAJOR, IP2_IPL_MAJOR,
+ IP2_MAX_BOARDS, ABS_MAX_BOXES, ABS_BIGGEST_BOX);
+
+ for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+ /* This need to be reset for a board by board count... */
+ boxes = 0;
+ pB = i2BoardPtrTable[i];
+ if( pB ) {
+ switch( pB->i2ePom.e.porID & ~POR_ID_RESERVED )
+ {
+ case POR_ID_FIIEX:
+ len += sprintf( page+len, "Board %d: EX ports=", i );
+ for( box = 0; box < ABS_MAX_BOXES; ++box )
+ {
+ ports = 0;
+
+ if( pB->i2eChannelMap[box] != 0 ) ++boxes;
+ for( j = 0; j < ABS_BIGGEST_BOX; ++j )
+ {
+ if( pB->i2eChannelMap[box] & 1<< j ) {
+ ++ports;
+ }
+ }
+ len += sprintf( page+len, "%d,", ports );
+ tports += ports;
+ }
+
+ --len; /* Backup over that last comma */
+
+ len += sprintf( page+len, " boxes=%d width=%d", boxes, pB->i2eDataWidth16 ? 16 : 8 );
+ break;
+
+ case POR_ID_II_4:
+ len += sprintf(page+len, "Board %d: ISA-4 ports=4 boxes=1", i );
+ tports = ports = 4;
+ break;
+
+ case POR_ID_II_8:
+ len += sprintf(page+len, "Board %d: ISA-8-std ports=8 boxes=1", i );
+ tports = ports = 8;
+ break;
+
+ case POR_ID_II_8R:
+ len += sprintf(page+len, "Board %d: ISA-8-RJ11 ports=8 boxes=1", i );
+ tports = ports = 8;
+ break;
+
+ default:
+ len += sprintf(page+len, "Board %d: unknown", i );
+ /* Don't try and probe for minor numbers */
+ tports = ports = 0;
+ }
+
+ } else {
+ /* Don't try and probe for minor numbers */
+ len += sprintf(page+len, "Board %d: vacant", i );
+ tports = ports = 0;
+ }
+
+ if( tports ) {
+ len += sprintf(page+len, " minors=" );
+
+ for ( box = 0; box < ABS_MAX_BOXES; ++box )
+ {
+ for ( j = 0; j < ABS_BIGGEST_BOX; ++j )
+ {
+ if ( pB->i2eChannelMap[box] & (1 << j) )
+ {
+ len += sprintf (page+len,"%d,",
+ j + ABS_BIGGEST_BOX *
+ (box+i*ABS_MAX_BOXES));
+ }
+ }
+ }
+
+ page[ len - 1 ] = '\n'; /* Overwrite that last comma */
+ } else {
+ len += sprintf (page+len,"\n" );
+ }
+
+ if (len+begin > off+count)
+ break;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ }
+
+ if (i >= IP2_MAX_BOARDS)
+ *eof = 1;
+ if (off >= len+begin)
+ return 0;
+
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+ }
+
+/******************************************************************************/
+/* Function: ip2trace() */
+/* Parameters: Value to add to trace buffer */
+/* Returns: Nothing */
+/* */
+/* Description: */
+/* */
+/* */
+/******************************************************************************/
+#ifdef IP2DEBUG_TRACE
+void
+ip2trace (unsigned short pn, unsigned char cat, unsigned char label, unsigned long codes, ...)
+{
+ long flags;
+ unsigned long *pCode = &codes;
+ union ip2breadcrumb bc;
+ i2ChanStrPtr pCh;
+
+
+ tracebuf[tracestuff++] = jiffies;
+ if ( tracestuff == TRACEMAX ) {
+ tracestuff = 0;
+ }
+ if ( tracestuff == tracestrip ) {
+ if ( ++tracestrip == TRACEMAX ) {
+ tracestrip = 0;
+ }
+ ++tracewrap;
+ }
+
+ bc.hdr.port = 0xff & pn;
+ bc.hdr.cat = cat;
+ bc.hdr.codes = (unsigned char)( codes & 0xff );
+ bc.hdr.label = label;
+ tracebuf[tracestuff++] = bc.value;
+
+ for (;;) {
+ if ( tracestuff == TRACEMAX ) {
+ tracestuff = 0;
+ }
+ if ( tracestuff == tracestrip ) {
+ if ( ++tracestrip == TRACEMAX ) {
+ tracestrip = 0;
+ }
+ ++tracewrap;
+ }
+
+ if ( !codes-- )
+ break;
+
+ tracebuf[tracestuff++] = *++pCode;
+ }
+}
+#endif
+
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
new file mode 100644
index 000000000000..a6dcb2918157
--- /dev/null
+++ b/drivers/char/ipmi/Kconfig
@@ -0,0 +1,67 @@
+#
+# IPMI device configuration
+#
+
+menu "IPMI"
+config IPMI_HANDLER
+ tristate 'IPMI top-level message handler'
+ help
+ This enables the central IPMI message handler, required for IPMI
+ to work.
+
+ IPMI is a standard for managing sensors (temperature,
+ voltage, etc.) in a system.
+
+ See <file:Documentation/IPMI.txt> for more details on the driver.
+
+ If unsure, say N.
+
+config IPMI_PANIC_EVENT
+ bool 'Generate a panic event to all BMCs on a panic'
+ depends on IPMI_HANDLER
+ help
+ When a panic occurs, this will cause the IPMI message handler to
+ generate an IPMI event describing the panic to each interface
+ registered with the message handler.
+
+config IPMI_PANIC_STRING
+ bool 'Generate OEM events containing the panic string'
+ depends on IPMI_PANIC_EVENT
+ help
+ When a panic occurs, this will cause the IPMI message handler to
+ generate IPMI OEM type f0 events holding the IPMB address of the
+ panic generator (byte 4 of the event), a sequence number for the
+ string (byte 5 of the event) and part of the string (the rest of the
+ event). Bytes 1, 2, and 3 are the normal usage for an OEM event.
+ You can fetch these events and use the sequence numbers to piece the
+ string together.
+
+config IPMI_DEVICE_INTERFACE
+ tristate 'Device interface for IPMI'
+ depends on IPMI_HANDLER
+ help
+ This provides an IOCTL interface to the IPMI message handler so
+ userland processes may use IPMI. It supports poll() and select().
+
+config IPMI_SI
+ tristate 'IPMI System Interface handler'
+ depends on IPMI_HANDLER
+ help
+ Provides a driver for System Interfaces (KCS, SMIC, BT).
+ Currently, only KCS and SMIC are supported. If
+ you are using IPMI, you should probably say "y" here.
+
+config IPMI_WATCHDOG
+ tristate 'IPMI Watchdog Timer'
+ depends on IPMI_HANDLER
+ help
+ This enables the IPMI watchdog timer.
+
+config IPMI_POWEROFF
+ tristate 'IPMI Poweroff'
+ depends on IPMI_HANDLER
+ help
+ This enables a function to power off the system with IPMI if
+ the IPMI management controller is capable of this.
+
+endmenu
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
new file mode 100644
index 000000000000..553f0a408eda
--- /dev/null
+++ b/drivers/char/ipmi/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for the ipmi drivers.
+#
+
+ipmi_si-objs := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o
+
+obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o
+obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o
+obj-$(CONFIG_IPMI_SI) += ipmi_si.o
+obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
+obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
+
+ipmi_si.o: $(ipmi_si-objs)
+ $(LD) -r -o $@ $(ipmi_si-objs)
+
diff --git a/drivers/char/ipmi/ipmi_bt_sm.c b/drivers/char/ipmi/ipmi_bt_sm.c
new file mode 100644
index 000000000000..225b330115bb
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_bt_sm.c
@@ -0,0 +1,513 @@
+/*
+ * ipmi_bt_sm.c
+ *
+ * The state machine for an Open IPMI BT sub-driver under ipmi_si.c, part
+ * of the driver architecture at http://sourceforge.net/project/openipmi
+ *
+ * Author: Rocky Craig <first.last@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h> /* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_BT_VERSION "v33"
+
+static int bt_debug = 0x00; /* Production value 0, see following flags */
+
+#define BT_DEBUG_ENABLE 1
+#define BT_DEBUG_MSG 2
+#define BT_DEBUG_STATES 4
+
+/* Typical "Get BT Capabilities" values are 2-3 retries, 5-10 seconds,
+ and 64 byte buffers. However, one HP implementation wants 255 bytes of
+ buffer (with a documented message of 160 bytes) so go for the max.
+ Since the Open IPMI architecture is single-message oriented at this
+ stage, the queue depth of BT is of no concern. */
+
+#define BT_NORMAL_TIMEOUT 2000000 /* seconds in microseconds */
+#define BT_RETRY_LIMIT 2
+#define BT_RESET_DELAY 6000000 /* 6 seconds after warm reset */
+
+enum bt_states {
+ BT_STATE_IDLE,
+ BT_STATE_XACTION_START,
+ BT_STATE_WRITE_BYTES,
+ BT_STATE_WRITE_END,
+ BT_STATE_WRITE_CONSUME,
+ BT_STATE_B2H_WAIT,
+ BT_STATE_READ_END,
+ BT_STATE_RESET1, /* These must come last */
+ BT_STATE_RESET2,
+ BT_STATE_RESET3,
+ BT_STATE_RESTART,
+ BT_STATE_HOSED
+};
+
+struct si_sm_data {
+ enum bt_states state;
+ enum bt_states last_state; /* assist printing and resets */
+ unsigned char seq; /* BT sequence number */
+ struct si_sm_io *io;
+ unsigned char write_data[IPMI_MAX_MSG_LENGTH];
+ int write_count;
+ unsigned char read_data[IPMI_MAX_MSG_LENGTH];
+ int read_count;
+ int truncated;
+ long timeout;
+ unsigned int error_retries; /* end of "common" fields */
+ int nonzero_status; /* hung BMCs stay all 0 */
+};
+
+#define BT_CLR_WR_PTR 0x01 /* See IPMI 1.5 table 11.6.4 */
+#define BT_CLR_RD_PTR 0x02
+#define BT_H2B_ATN 0x04
+#define BT_B2H_ATN 0x08
+#define BT_SMS_ATN 0x10
+#define BT_OEM0 0x20
+#define BT_H_BUSY 0x40
+#define BT_B_BUSY 0x80
+
+/* Some bits are toggled on each write: write once to set it, once
+ more to clear it; writing a zero does nothing. To absolutely
+ clear it, check its state and write if set. This avoids the "get
+ current then use as mask" scheme to modify one bit. Note that the
+ variable "bt" is hardcoded into these macros. */
+
+#define BT_STATUS bt->io->inputb(bt->io, 0)
+#define BT_CONTROL(x) bt->io->outputb(bt->io, 0, x)
+
+#define BMC2HOST bt->io->inputb(bt->io, 1)
+#define HOST2BMC(x) bt->io->outputb(bt->io, 1, x)
+
+#define BT_INTMASK_R bt->io->inputb(bt->io, 2)
+#define BT_INTMASK_W(x) bt->io->outputb(bt->io, 2, x)
+
+/* Convenience routines for debugging. These are not multi-open safe!
+ Note the macros have hardcoded variables in them. */
+
+static char *state2txt(unsigned char state)
+{
+ switch (state) {
+ case BT_STATE_IDLE: return("IDLE");
+ case BT_STATE_XACTION_START: return("XACTION");
+ case BT_STATE_WRITE_BYTES: return("WR_BYTES");
+ case BT_STATE_WRITE_END: return("WR_END");
+ case BT_STATE_WRITE_CONSUME: return("WR_CONSUME");
+ case BT_STATE_B2H_WAIT: return("B2H_WAIT");
+ case BT_STATE_READ_END: return("RD_END");
+ case BT_STATE_RESET1: return("RESET1");
+ case BT_STATE_RESET2: return("RESET2");
+ case BT_STATE_RESET3: return("RESET3");
+ case BT_STATE_RESTART: return("RESTART");
+ case BT_STATE_HOSED: return("HOSED");
+ }
+ return("BAD STATE");
+}
+#define STATE2TXT state2txt(bt->state)
+
+static char *status2txt(unsigned char status, char *buf)
+{
+ strcpy(buf, "[ ");
+ if (status & BT_B_BUSY) strcat(buf, "B_BUSY ");
+ if (status & BT_H_BUSY) strcat(buf, "H_BUSY ");
+ if (status & BT_OEM0) strcat(buf, "OEM0 ");
+ if (status & BT_SMS_ATN) strcat(buf, "SMS ");
+ if (status & BT_B2H_ATN) strcat(buf, "B2H ");
+ if (status & BT_H2B_ATN) strcat(buf, "H2B ");
+ strcat(buf, "]");
+ return buf;
+}
+#define STATUS2TXT(buf) status2txt(status, buf)
+
+/* This will be called from within this module on a hosed condition */
+#define FIRST_SEQ 0
+static unsigned int bt_init_data(struct si_sm_data *bt, struct si_sm_io *io)
+{
+ bt->state = BT_STATE_IDLE;
+ bt->last_state = BT_STATE_IDLE;
+ bt->seq = FIRST_SEQ;
+ bt->io = io;
+ bt->write_count = 0;
+ bt->read_count = 0;
+ bt->error_retries = 0;
+ bt->nonzero_status = 0;
+ bt->truncated = 0;
+ bt->timeout = BT_NORMAL_TIMEOUT;
+ return 3; /* We claim 3 bytes of space; ought to check SPMI table */
+}
+
+static int bt_start_transaction(struct si_sm_data *bt,
+ unsigned char *data,
+ unsigned int size)
+{
+ unsigned int i;
+
+ if ((size < 2) || (size > IPMI_MAX_MSG_LENGTH)) return -1;
+
+ if ((bt->state != BT_STATE_IDLE) && (bt->state != BT_STATE_HOSED))
+ return -2;
+
+ if (bt_debug & BT_DEBUG_MSG) {
+ printk(KERN_WARNING "+++++++++++++++++++++++++++++++++++++\n");
+ printk(KERN_WARNING "BT: write seq=0x%02X:", bt->seq);
+ for (i = 0; i < size; i ++) printk (" %02x", data[i]);
+ printk("\n");
+ }
+ bt->write_data[0] = size + 1; /* all data plus seq byte */
+ bt->write_data[1] = *data; /* NetFn/LUN */
+ bt->write_data[2] = bt->seq;
+ memcpy(bt->write_data + 3, data + 1, size - 1);
+ bt->write_count = size + 2;
+
+ bt->error_retries = 0;
+ bt->nonzero_status = 0;
+ bt->read_count = 0;
+ bt->truncated = 0;
+ bt->state = BT_STATE_XACTION_START;
+ bt->last_state = BT_STATE_IDLE;
+ bt->timeout = BT_NORMAL_TIMEOUT;
+ return 0;
+}
+
+/* After the upper state machine has been told SI_SM_TRANSACTION_COMPLETE
+ it calls this. Strip out the length and seq bytes. */
+
+static int bt_get_result(struct si_sm_data *bt,
+ unsigned char *data,
+ unsigned int length)
+{
+ int i, msg_len;
+
+ msg_len = bt->read_count - 2; /* account for length & seq */
+ /* Always NetFn, Cmd, cCode */
+ if (msg_len < 3 || msg_len > IPMI_MAX_MSG_LENGTH) {
+ printk(KERN_WARNING "BT results: bad msg_len = %d\n", msg_len);
+ data[0] = bt->write_data[1] | 0x4; /* Kludge a response */
+ data[1] = bt->write_data[3];
+ data[2] = IPMI_ERR_UNSPECIFIED;
+ msg_len = 3;
+ } else {
+ data[0] = bt->read_data[1];
+ data[1] = bt->read_data[3];
+ if (length < msg_len) bt->truncated = 1;
+ if (bt->truncated) { /* can be set in read_all_bytes() */
+ data[2] = IPMI_ERR_MSG_TRUNCATED;
+ msg_len = 3;
+ } else memcpy(data + 2, bt->read_data + 4, msg_len - 2);
+
+ if (bt_debug & BT_DEBUG_MSG) {
+ printk (KERN_WARNING "BT: res (raw)");
+ for (i = 0; i < msg_len; i++) printk(" %02x", data[i]);
+ printk ("\n");
+ }
+ }
+ bt->read_count = 0; /* paranoia */
+ return msg_len;
+}
+
+/* This bit's functionality is optional */
+#define BT_BMC_HWRST 0x80
+
+static void reset_flags(struct si_sm_data *bt)
+{
+ if (BT_STATUS & BT_H_BUSY) BT_CONTROL(BT_H_BUSY);
+ if (BT_STATUS & BT_B_BUSY) BT_CONTROL(BT_B_BUSY);
+ BT_CONTROL(BT_CLR_WR_PTR);
+ BT_CONTROL(BT_SMS_ATN);
+ BT_INTMASK_W(BT_BMC_HWRST);
+#ifdef DEVELOPMENT_ONLY_NOT_FOR_PRODUCTION
+ if (BT_STATUS & BT_B2H_ATN) {
+ int i;
+ BT_CONTROL(BT_H_BUSY);
+ BT_CONTROL(BT_B2H_ATN);
+ BT_CONTROL(BT_CLR_RD_PTR);
+ for (i = 0; i < IPMI_MAX_MSG_LENGTH + 2; i++) BMC2HOST;
+ BT_CONTROL(BT_H_BUSY);
+ }
+#endif
+}
+
+static inline void write_all_bytes(struct si_sm_data *bt)
+{
+ int i;
+
+ if (bt_debug & BT_DEBUG_MSG) {
+ printk(KERN_WARNING "BT: write %d bytes seq=0x%02X",
+ bt->write_count, bt->seq);
+ for (i = 0; i < bt->write_count; i++)
+ printk (" %02x", bt->write_data[i]);
+ printk ("\n");
+ }
+ for (i = 0; i < bt->write_count; i++) HOST2BMC(bt->write_data[i]);
+}
+
+static inline int read_all_bytes(struct si_sm_data *bt)
+{
+ unsigned char i;
+
+ bt->read_data[0] = BMC2HOST;
+ bt->read_count = bt->read_data[0];
+ if (bt_debug & BT_DEBUG_MSG)
+ printk(KERN_WARNING "BT: read %d bytes:", bt->read_count);
+
+ /* minimum: length, NetFn, Seq, Cmd, cCode == 5 total, or 4 more
+ following the length byte. */
+ if (bt->read_count < 4 || bt->read_count >= IPMI_MAX_MSG_LENGTH) {
+ if (bt_debug & BT_DEBUG_MSG)
+ printk("bad length %d\n", bt->read_count);
+ bt->truncated = 1;
+ return 1; /* let next XACTION START clean it up */
+ }
+ for (i = 1; i <= bt->read_count; i++) bt->read_data[i] = BMC2HOST;
+ bt->read_count++; /* account for the length byte */
+
+ if (bt_debug & BT_DEBUG_MSG) {
+ for (i = 0; i < bt->read_count; i++)
+ printk (" %02x", bt->read_data[i]);
+ printk ("\n");
+ }
+ if (bt->seq != bt->write_data[2]) /* idiot check */
+ printk(KERN_WARNING "BT: internal error: sequence mismatch\n");
+
+ /* per the spec, the (NetFn, Seq, Cmd) tuples should match */
+ if ((bt->read_data[3] == bt->write_data[3]) && /* Cmd */
+ (bt->read_data[2] == bt->write_data[2]) && /* Sequence */
+ ((bt->read_data[1] & 0xF8) == (bt->write_data[1] & 0xF8)))
+ return 1;
+
+ if (bt_debug & BT_DEBUG_MSG) printk(KERN_WARNING "BT: bad packet: "
+ "want 0x(%02X, %02X, %02X) got (%02X, %02X, %02X)\n",
+ bt->write_data[1], bt->write_data[2], bt->write_data[3],
+ bt->read_data[1], bt->read_data[2], bt->read_data[3]);
+ return 0;
+}
+
+/* Modifies bt->state appropriately, need to get into the bt_event() switch */
+
+static void error_recovery(struct si_sm_data *bt, char *reason)
+{
+ unsigned char status;
+ char buf[40]; /* For getting status */
+
+ bt->timeout = BT_NORMAL_TIMEOUT; /* various places want to retry */
+
+ status = BT_STATUS;
+ printk(KERN_WARNING "BT: %s in %s %s ", reason, STATE2TXT,
+ STATUS2TXT(buf));
+
+ (bt->error_retries)++;
+ if (bt->error_retries > BT_RETRY_LIMIT) {
+ printk("retry limit (%d) exceeded\n", BT_RETRY_LIMIT);
+ bt->state = BT_STATE_HOSED;
+ if (!bt->nonzero_status)
+ printk(KERN_ERR "IPMI: BT stuck, try power cycle\n");
+ else if (bt->seq == FIRST_SEQ + BT_RETRY_LIMIT) {
+ /* most likely during insmod */
+ printk(KERN_WARNING "IPMI: BT reset (takes 5 secs)\n");
+ bt->state = BT_STATE_RESET1;
+ }
+ return;
+ }
+
+ /* Sometimes the BMC queues get in an "off-by-one" state...*/
+ if ((bt->state == BT_STATE_B2H_WAIT) && (status & BT_B2H_ATN)) {
+ printk("retry B2H_WAIT\n");
+ return;
+ }
+
+ printk("restart command\n");
+ bt->state = BT_STATE_RESTART;
+}
+
+/* Check the status and (possibly) advance the BT state machine. The
+ default return is SI_SM_CALL_WITH_DELAY. */
+
+static enum si_sm_result bt_event(struct si_sm_data *bt, long time)
+{
+ unsigned char status;
+ char buf[40]; /* For getting status */
+ int i;
+
+ status = BT_STATUS;
+ bt->nonzero_status |= status;
+
+ if ((bt_debug & BT_DEBUG_STATES) && (bt->state != bt->last_state))
+ printk(KERN_WARNING "BT: %s %s TO=%ld - %ld \n",
+ STATE2TXT,
+ STATUS2TXT(buf),
+ bt->timeout,
+ time);
+ bt->last_state = bt->state;
+
+ if (bt->state == BT_STATE_HOSED) return SI_SM_HOSED;
+
+ if (bt->state != BT_STATE_IDLE) { /* do timeout test */
+
+ /* Certain states, on error conditions, can lock up a CPU
+ because they are effectively in an infinite loop with
+ CALL_WITHOUT_DELAY (right back here with time == 0).
+ Prevent infinite lockup by ALWAYS decrementing timeout. */
+
+ /* FIXME: bt_event is sometimes called with time > BT_NORMAL_TIMEOUT
+ (noticed in ipmi_smic_sm.c January 2004) */
+
+ if ((time <= 0) || (time >= BT_NORMAL_TIMEOUT)) time = 100;
+ bt->timeout -= time;
+ if ((bt->timeout < 0) && (bt->state < BT_STATE_RESET1)) {
+ error_recovery(bt, "timed out");
+ return SI_SM_CALL_WITHOUT_DELAY;
+ }
+ }
+
+ switch (bt->state) {
+
+ case BT_STATE_IDLE: /* check for asynchronous messages */
+ if (status & BT_SMS_ATN) {
+ BT_CONTROL(BT_SMS_ATN); /* clear it */
+ return SI_SM_ATTN;
+ }
+ return SI_SM_IDLE;
+
+ case BT_STATE_XACTION_START:
+ if (status & BT_H_BUSY) {
+ BT_CONTROL(BT_H_BUSY);
+ break;
+ }
+ if (status & BT_B2H_ATN) break;
+ bt->state = BT_STATE_WRITE_BYTES;
+ return SI_SM_CALL_WITHOUT_DELAY; /* for logging */
+
+ case BT_STATE_WRITE_BYTES:
+ if (status & (BT_B_BUSY | BT_H2B_ATN)) break;
+ BT_CONTROL(BT_CLR_WR_PTR);
+ write_all_bytes(bt);
+ BT_CONTROL(BT_H2B_ATN); /* clears too fast to catch? */
+ bt->state = BT_STATE_WRITE_CONSUME;
+ return SI_SM_CALL_WITHOUT_DELAY; /* it MIGHT sail through */
+
+ case BT_STATE_WRITE_CONSUME: /* BMCs usually blow right thru here */
+ if (status & (BT_H2B_ATN | BT_B_BUSY)) break;
+ bt->state = BT_STATE_B2H_WAIT;
+ /* fall through with status */
+
+ /* Stay in BT_STATE_B2H_WAIT until a packet matches. However, spinning
+ hard here, constantly reading status, seems to hold off the
+ generation of B2H_ATN so ALWAYS return CALL_WITH_DELAY. */
+
+ case BT_STATE_B2H_WAIT:
+ if (!(status & BT_B2H_ATN)) break;
+
+ /* Assume ordered, uncached writes: no need to wait */
+ if (!(status & BT_H_BUSY)) BT_CONTROL(BT_H_BUSY); /* set */
+ BT_CONTROL(BT_B2H_ATN); /* clear it, ACK to the BMC */
+ BT_CONTROL(BT_CLR_RD_PTR); /* reset the queue */
+ i = read_all_bytes(bt);
+ BT_CONTROL(BT_H_BUSY); /* clear */
+ if (!i) break; /* Try this state again */
+ bt->state = BT_STATE_READ_END;
+ return SI_SM_CALL_WITHOUT_DELAY; /* for logging */
+
+ case BT_STATE_READ_END:
+
+ /* I could wait on BT_H_BUSY to go clear for a truly clean
+ exit. However, this is already done in XACTION_START
+ and the (possible) extra loop/status/possible wait affects
+ performance. So, as long as it works, just ignore H_BUSY */
+
+#ifdef MAKE_THIS_TRUE_IF_NECESSARY
+
+ if (status & BT_H_BUSY) break;
+#endif
+ bt->seq++;
+ bt->state = BT_STATE_IDLE;
+ return SI_SM_TRANSACTION_COMPLETE;
+
+ case BT_STATE_RESET1:
+ reset_flags(bt);
+ bt->timeout = BT_RESET_DELAY;
+ bt->state = BT_STATE_RESET2;
+ break;
+
+ case BT_STATE_RESET2: /* Send a soft reset */
+ BT_CONTROL(BT_CLR_WR_PTR);
+ HOST2BMC(3); /* number of bytes following */
+ HOST2BMC(0x18); /* NetFn/LUN == Application, LUN 0 */
+ HOST2BMC(42); /* Sequence number */
+ HOST2BMC(3); /* Cmd == Soft reset */
+ BT_CONTROL(BT_H2B_ATN);
+ bt->state = BT_STATE_RESET3;
+ break;
+
+ case BT_STATE_RESET3:
+ if (bt->timeout > 0) return SI_SM_CALL_WITH_DELAY;
+ bt->state = BT_STATE_RESTART; /* printk in debug modes */
+ break;
+
+ case BT_STATE_RESTART: /* don't reset retries! */
+ bt->write_data[2] = ++bt->seq;
+ bt->read_count = 0;
+ bt->nonzero_status = 0;
+ bt->timeout = BT_NORMAL_TIMEOUT;
+ bt->state = BT_STATE_XACTION_START;
+ break;
+
+ default: /* HOSED is supposed to be caught much earlier */
+ error_recovery(bt, "internal logic error");
+ break;
+ }
+ return SI_SM_CALL_WITH_DELAY;
+}
+
+static int bt_detect(struct si_sm_data *bt)
+{
+ /* It's impossible for the BT status and interrupt registers to be
+ all 1's, (assuming a properly functioning, self-initialized BMC)
+ but that's what you get from reading a bogus address, so we
+ test that first. The calling routine uses negative logic. */
+
+ if ((BT_STATUS == 0xFF) && (BT_INTMASK_R == 0xFF)) return 1;
+ reset_flags(bt);
+ return 0;
+}
+
+static void bt_cleanup(struct si_sm_data *bt)
+{
+}
+
+static int bt_size(void)
+{
+ return sizeof(struct si_sm_data);
+}
+
+struct si_sm_handlers bt_smi_handlers =
+{
+ .version = IPMI_BT_VERSION,
+ .init_data = bt_init_data,
+ .start_transaction = bt_start_transaction,
+ .get_result = bt_get_result,
+ .event = bt_event,
+ .detect = bt_detect,
+ .cleanup = bt_cleanup,
+ .size = bt_size,
+};
diff --git a/drivers/char/ipmi/ipmi_devintf.c b/drivers/char/ipmi/ipmi_devintf.c
new file mode 100644
index 000000000000..49d67f5384a2
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_devintf.c
@@ -0,0 +1,582 @@
+/*
+ * ipmi_devintf.c
+ *
+ * Linux device interface for the IPMI message handler.
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ipmi.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+#define IPMI_DEVINTF_VERSION "v33"
+
+struct ipmi_file_private
+{
+ ipmi_user_t user;
+ spinlock_t recv_msg_lock;
+ struct list_head recv_msgs;
+ struct file *file;
+ struct fasync_struct *fasync_queue;
+ wait_queue_head_t wait;
+ struct semaphore recv_sem;
+ int default_retries;
+ unsigned int default_retry_time_ms;
+};
+
+static void file_receive_handler(struct ipmi_recv_msg *msg,
+ void *handler_data)
+{
+ struct ipmi_file_private *priv = handler_data;
+ int was_empty;
+ unsigned long flags;
+
+ spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+
+ was_empty = list_empty(&(priv->recv_msgs));
+ list_add_tail(&(msg->link), &(priv->recv_msgs));
+
+ if (was_empty) {
+ wake_up_interruptible(&priv->wait);
+ kill_fasync(&priv->fasync_queue, SIGIO, POLL_IN);
+ }
+
+ spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+}
+
+static unsigned int ipmi_poll(struct file *file, poll_table *wait)
+{
+ struct ipmi_file_private *priv = file->private_data;
+ unsigned int mask = 0;
+ unsigned long flags;
+
+ poll_wait(file, &priv->wait, wait);
+
+ spin_lock_irqsave(&priv->recv_msg_lock, flags);
+
+ if (! list_empty(&(priv->recv_msgs)))
+ mask |= (POLLIN | POLLRDNORM);
+
+ spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
+
+ return mask;
+}
+
+static int ipmi_fasync(int fd, struct file *file, int on)
+{
+ struct ipmi_file_private *priv = file->private_data;
+ int result;
+
+ result = fasync_helper(fd, file, on, &priv->fasync_queue);
+
+ return (result);
+}
+
+static struct ipmi_user_hndl ipmi_hndlrs =
+{
+ .ipmi_recv_hndl = file_receive_handler,
+};
+
+static int ipmi_open(struct inode *inode, struct file *file)
+{
+ int if_num = iminor(inode);
+ int rv;
+ struct ipmi_file_private *priv;
+
+
+ priv = kmalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->file = file;
+
+ rv = ipmi_create_user(if_num,
+ &ipmi_hndlrs,
+ priv,
+ &(priv->user));
+ if (rv) {
+ kfree(priv);
+ return rv;
+ }
+
+ file->private_data = priv;
+
+ spin_lock_init(&(priv->recv_msg_lock));
+ INIT_LIST_HEAD(&(priv->recv_msgs));
+ init_waitqueue_head(&priv->wait);
+ priv->fasync_queue = NULL;
+ sema_init(&(priv->recv_sem), 1);
+
+ /* Use the low-level defaults. */
+ priv->default_retries = -1;
+ priv->default_retry_time_ms = 0;
+
+ return 0;
+}
+
+static int ipmi_release(struct inode *inode, struct file *file)
+{
+ struct ipmi_file_private *priv = file->private_data;
+ int rv;
+
+ rv = ipmi_destroy_user(priv->user);
+ if (rv)
+ return rv;
+
+ ipmi_fasync (-1, file, 0);
+
+ /* FIXME - free the messages in the list. */
+ kfree(priv);
+
+ return 0;
+}
+
+static int handle_send_req(ipmi_user_t user,
+ struct ipmi_req *req,
+ int retries,
+ unsigned int retry_time_ms)
+{
+ int rv;
+ struct ipmi_addr addr;
+ struct kernel_ipmi_msg msg;
+
+ if (req->addr_len > sizeof(struct ipmi_addr))
+ return -EINVAL;
+
+ if (copy_from_user(&addr, req->addr, req->addr_len))
+ return -EFAULT;
+
+ msg.netfn = req->msg.netfn;
+ msg.cmd = req->msg.cmd;
+ msg.data_len = req->msg.data_len;
+ msg.data = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+ if (!msg.data)
+ return -ENOMEM;
+
+ /* From here out we cannot return, we must jump to "out" for
+ error exits to free msgdata. */
+
+ rv = ipmi_validate_addr(&addr, req->addr_len);
+ if (rv)
+ goto out;
+
+ if (req->msg.data != NULL) {
+ if (req->msg.data_len > IPMI_MAX_MSG_LENGTH) {
+ rv = -EMSGSIZE;
+ goto out;
+ }
+
+ if (copy_from_user(msg.data,
+ req->msg.data,
+ req->msg.data_len))
+ {
+ rv = -EFAULT;
+ goto out;
+ }
+ } else {
+ msg.data_len = 0;
+ }
+
+ rv = ipmi_request_settime(user,
+ &addr,
+ req->msgid,
+ &msg,
+ NULL,
+ 0,
+ retries,
+ retry_time_ms);
+ out:
+ kfree(msg.data);
+ return rv;
+}
+
+static int ipmi_ioctl(struct inode *inode,
+ struct file *file,
+ unsigned int cmd,
+ unsigned long data)
+{
+ int rv = -EINVAL;
+ struct ipmi_file_private *priv = file->private_data;
+ void __user *arg = (void __user *)data;
+
+ switch (cmd)
+ {
+ case IPMICTL_SEND_COMMAND:
+ {
+ struct ipmi_req req;
+
+ if (copy_from_user(&req, arg, sizeof(req))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = handle_send_req(priv->user,
+ &req,
+ priv->default_retries,
+ priv->default_retry_time_ms);
+ break;
+ }
+
+ case IPMICTL_SEND_COMMAND_SETTIME:
+ {
+ struct ipmi_req_settime req;
+
+ if (copy_from_user(&req, arg, sizeof(req))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = handle_send_req(priv->user,
+ &req.req,
+ req.retries,
+ req.retry_time_ms);
+ break;
+ }
+
+ case IPMICTL_RECEIVE_MSG:
+ case IPMICTL_RECEIVE_MSG_TRUNC:
+ {
+ struct ipmi_recv rsp;
+ int addr_len;
+ struct list_head *entry;
+ struct ipmi_recv_msg *msg;
+ unsigned long flags;
+
+
+ rv = 0;
+ if (copy_from_user(&rsp, arg, sizeof(rsp))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ /* We claim a semaphore because we don't want two
+ users getting something from the queue at a time.
+ Since we have to release the spinlock before we can
+ copy the data to the user, it's possible another
+ user will grab something from the queue, too. Then
+ the messages might get out of order if something
+ fails and the message gets put back onto the
+ queue. This semaphore prevents that problem. */
+ down(&(priv->recv_sem));
+
+ /* Grab the message off the list. */
+ spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+ if (list_empty(&(priv->recv_msgs))) {
+ spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+ rv = -EAGAIN;
+ goto recv_err;
+ }
+ entry = priv->recv_msgs.next;
+ msg = list_entry(entry, struct ipmi_recv_msg, link);
+ list_del(entry);
+ spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+
+ addr_len = ipmi_addr_length(msg->addr.addr_type);
+ if (rsp.addr_len < addr_len)
+ {
+ rv = -EINVAL;
+ goto recv_putback_on_err;
+ }
+
+ if (copy_to_user(rsp.addr, &(msg->addr), addr_len)) {
+ rv = -EFAULT;
+ goto recv_putback_on_err;
+ }
+ rsp.addr_len = addr_len;
+
+ rsp.recv_type = msg->recv_type;
+ rsp.msgid = msg->msgid;
+ rsp.msg.netfn = msg->msg.netfn;
+ rsp.msg.cmd = msg->msg.cmd;
+
+ if (msg->msg.data_len > 0) {
+ if (rsp.msg.data_len < msg->msg.data_len) {
+ rv = -EMSGSIZE;
+ if (cmd == IPMICTL_RECEIVE_MSG_TRUNC) {
+ msg->msg.data_len = rsp.msg.data_len;
+ } else {
+ goto recv_putback_on_err;
+ }
+ }
+
+ if (copy_to_user(rsp.msg.data,
+ msg->msg.data,
+ msg->msg.data_len))
+ {
+ rv = -EFAULT;
+ goto recv_putback_on_err;
+ }
+ rsp.msg.data_len = msg->msg.data_len;
+ } else {
+ rsp.msg.data_len = 0;
+ }
+
+ if (copy_to_user(arg, &rsp, sizeof(rsp))) {
+ rv = -EFAULT;
+ goto recv_putback_on_err;
+ }
+
+ up(&(priv->recv_sem));
+ ipmi_free_recv_msg(msg);
+ break;
+
+ recv_putback_on_err:
+ /* If we got an error, put the message back onto
+ the head of the queue. */
+ spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+ list_add(entry, &(priv->recv_msgs));
+ spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+ up(&(priv->recv_sem));
+ break;
+
+ recv_err:
+ up(&(priv->recv_sem));
+ break;
+ }
+
+ case IPMICTL_REGISTER_FOR_CMD:
+ {
+ struct ipmi_cmdspec val;
+
+ if (copy_from_user(&val, arg, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd);
+ break;
+ }
+
+ case IPMICTL_UNREGISTER_FOR_CMD:
+ {
+ struct ipmi_cmdspec val;
+
+ if (copy_from_user(&val, arg, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd);
+ break;
+ }
+
+ case IPMICTL_SET_GETS_EVENTS_CMD:
+ {
+ int val;
+
+ if (copy_from_user(&val, arg, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = ipmi_set_gets_events(priv->user, val);
+ break;
+ }
+
+ case IPMICTL_SET_MY_ADDRESS_CMD:
+ {
+ unsigned int val;
+
+ if (copy_from_user(&val, arg, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ ipmi_set_my_address(priv->user, val);
+ rv = 0;
+ break;
+ }
+
+ case IPMICTL_GET_MY_ADDRESS_CMD:
+ {
+ unsigned int val;
+
+ val = ipmi_get_my_address(priv->user);
+
+ if (copy_to_user(arg, &val, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+ rv = 0;
+ break;
+ }
+
+ case IPMICTL_SET_MY_LUN_CMD:
+ {
+ unsigned int val;
+
+ if (copy_from_user(&val, arg, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ ipmi_set_my_LUN(priv->user, val);
+ rv = 0;
+ break;
+ }
+
+ case IPMICTL_GET_MY_LUN_CMD:
+ {
+ unsigned int val;
+
+ val = ipmi_get_my_LUN(priv->user);
+
+ if (copy_to_user(arg, &val, sizeof(val))) {
+ rv = -EFAULT;
+ break;
+ }
+ rv = 0;
+ break;
+ }
+ case IPMICTL_SET_TIMING_PARMS_CMD:
+ {
+ struct ipmi_timing_parms parms;
+
+ if (copy_from_user(&parms, arg, sizeof(parms))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ priv->default_retries = parms.retries;
+ priv->default_retry_time_ms = parms.retry_time_ms;
+ rv = 0;
+ break;
+ }
+
+ case IPMICTL_GET_TIMING_PARMS_CMD:
+ {
+ struct ipmi_timing_parms parms;
+
+ parms.retries = priv->default_retries;
+ parms.retry_time_ms = priv->default_retry_time_ms;
+
+ if (copy_to_user(arg, &parms, sizeof(parms))) {
+ rv = -EFAULT;
+ break;
+ }
+
+ rv = 0;
+ break;
+ }
+ }
+
+ return rv;
+}
+
+
+static struct file_operations ipmi_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = ipmi_ioctl,
+ .open = ipmi_open,
+ .release = ipmi_release,
+ .fasync = ipmi_fasync,
+ .poll = ipmi_poll,
+};
+
+#define DEVICE_NAME "ipmidev"
+
+static int ipmi_major = 0;
+module_param(ipmi_major, int, 0);
+MODULE_PARM_DESC(ipmi_major, "Sets the major number of the IPMI device. By"
+ " default, or if you set it to zero, it will choose the next"
+ " available device. Setting it to -1 will disable the"
+ " interface. Other values will set the major device number"
+ " to that value.");
+
+static void ipmi_new_smi(int if_num)
+{
+ devfs_mk_cdev(MKDEV(ipmi_major, if_num),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "ipmidev/%d", if_num);
+}
+
+static void ipmi_smi_gone(int if_num)
+{
+ devfs_remove("ipmidev/%d", if_num);
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+ .owner = THIS_MODULE,
+ .new_smi = ipmi_new_smi,
+ .smi_gone = ipmi_smi_gone,
+};
+
+static __init int init_ipmi_devintf(void)
+{
+ int rv;
+
+ if (ipmi_major < 0)
+ return -EINVAL;
+
+ printk(KERN_INFO "ipmi device interface version "
+ IPMI_DEVINTF_VERSION "\n");
+
+ rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops);
+ if (rv < 0) {
+ printk(KERN_ERR "ipmi: can't get major %d\n", ipmi_major);
+ return rv;
+ }
+
+ if (ipmi_major == 0) {
+ ipmi_major = rv;
+ }
+
+ devfs_mk_dir(DEVICE_NAME);
+
+ rv = ipmi_smi_watcher_register(&smi_watcher);
+ if (rv) {
+ unregister_chrdev(ipmi_major, DEVICE_NAME);
+ printk(KERN_WARNING "ipmi: can't register smi watcher\n");
+ return rv;
+ }
+
+ return 0;
+}
+module_init(init_ipmi_devintf);
+
+static __exit void cleanup_ipmi(void)
+{
+ ipmi_smi_watcher_unregister(&smi_watcher);
+ devfs_remove(DEVICE_NAME);
+ unregister_chrdev(ipmi_major, DEVICE_NAME);
+}
+module_exit(cleanup_ipmi);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_kcs_sm.c b/drivers/char/ipmi/ipmi_kcs_sm.c
new file mode 100644
index 000000000000..48cce24329be
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_kcs_sm.c
@@ -0,0 +1,500 @@
+/*
+ * ipmi_kcs_sm.c
+ *
+ * State machine for handling IPMI KCS interfaces.
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This state machine is taken from the state machine in the IPMI spec,
+ * pretty much verbatim. If you have questions about the states, see
+ * that document.
+ */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h> /* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_KCS_VERSION "v33"
+
+/* Set this if you want a printout of why the state machine was hosed
+ when it gets hosed. */
+#define DEBUG_HOSED_REASON
+
+/* Print the state machine state on entry every time. */
+#undef DEBUG_STATE
+
+/* The states the KCS driver may be in. */
+enum kcs_states {
+ KCS_IDLE, /* The KCS interface is currently
+ doing nothing. */
+ KCS_START_OP, /* We are starting an operation. The
+ data is in the output buffer, but
+ nothing has been done to the
+ interface yet. This was added to
+ the state machine in the spec to
+ wait for the initial IBF. */
+ KCS_WAIT_WRITE_START, /* We have written a write cmd to the
+ interface. */
+ KCS_WAIT_WRITE, /* We are writing bytes to the
+ interface. */
+ KCS_WAIT_WRITE_END, /* We have written the write end cmd
+ to the interface, and still need to
+ write the last byte. */
+ KCS_WAIT_READ, /* We are waiting to read data from
+ the interface. */
+ KCS_ERROR0, /* State to transition to the error
+ handler, this was added to the
+ state machine in the spec to be
+ sure IBF was there. */
+ KCS_ERROR1, /* First stage error handler, wait for
+ the interface to respond. */
+ KCS_ERROR2, /* The abort cmd has been written,
+ wait for the interface to
+ respond. */
+ KCS_ERROR3, /* We wrote some data to the
+ interface, wait for it to switch to
+ read mode. */
+ KCS_HOSED /* The hardware failed to follow the
+ state machine. */
+};
+
+#define MAX_KCS_READ_SIZE 80
+#define MAX_KCS_WRITE_SIZE 80
+
+/* Timeouts in microseconds. */
+#define IBF_RETRY_TIMEOUT 1000000
+#define OBF_RETRY_TIMEOUT 1000000
+#define MAX_ERROR_RETRIES 10
+
+struct si_sm_data
+{
+ enum kcs_states state;
+ struct si_sm_io *io;
+ unsigned char write_data[MAX_KCS_WRITE_SIZE];
+ int write_pos;
+ int write_count;
+ int orig_write_count;
+ unsigned char read_data[MAX_KCS_READ_SIZE];
+ int read_pos;
+ int truncated;
+
+ unsigned int error_retries;
+ long ibf_timeout;
+ long obf_timeout;
+};
+
+static unsigned int init_kcs_data(struct si_sm_data *kcs,
+ struct si_sm_io *io)
+{
+ kcs->state = KCS_IDLE;
+ kcs->io = io;
+ kcs->write_pos = 0;
+ kcs->write_count = 0;
+ kcs->orig_write_count = 0;
+ kcs->read_pos = 0;
+ kcs->error_retries = 0;
+ kcs->truncated = 0;
+ kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+ kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+
+ /* Reserve 2 I/O bytes. */
+ return 2;
+}
+
+static inline unsigned char read_status(struct si_sm_data *kcs)
+{
+ return kcs->io->inputb(kcs->io, 1);
+}
+
+static inline unsigned char read_data(struct si_sm_data *kcs)
+{
+ return kcs->io->inputb(kcs->io, 0);
+}
+
+static inline void write_cmd(struct si_sm_data *kcs, unsigned char data)
+{
+ kcs->io->outputb(kcs->io, 1, data);
+}
+
+static inline void write_data(struct si_sm_data *kcs, unsigned char data)
+{
+ kcs->io->outputb(kcs->io, 0, data);
+}
+
+/* Control codes. */
+#define KCS_GET_STATUS_ABORT 0x60
+#define KCS_WRITE_START 0x61
+#define KCS_WRITE_END 0x62
+#define KCS_READ_BYTE 0x68
+
+/* Status bits. */
+#define GET_STATUS_STATE(status) (((status) >> 6) & 0x03)
+#define KCS_IDLE_STATE 0
+#define KCS_READ_STATE 1
+#define KCS_WRITE_STATE 2
+#define KCS_ERROR_STATE 3
+#define GET_STATUS_ATN(status) ((status) & 0x04)
+#define GET_STATUS_IBF(status) ((status) & 0x02)
+#define GET_STATUS_OBF(status) ((status) & 0x01)
+
+
+static inline void write_next_byte(struct si_sm_data *kcs)
+{
+ write_data(kcs, kcs->write_data[kcs->write_pos]);
+ (kcs->write_pos)++;
+ (kcs->write_count)--;
+}
+
+static inline void start_error_recovery(struct si_sm_data *kcs, char *reason)
+{
+ (kcs->error_retries)++;
+ if (kcs->error_retries > MAX_ERROR_RETRIES) {
+#ifdef DEBUG_HOSED_REASON
+ printk("ipmi_kcs_sm: kcs hosed: %s\n", reason);
+#endif
+ kcs->state = KCS_HOSED;
+ } else {
+ kcs->state = KCS_ERROR0;
+ }
+}
+
+static inline void read_next_byte(struct si_sm_data *kcs)
+{
+ if (kcs->read_pos >= MAX_KCS_READ_SIZE) {
+ /* Throw the data away and mark it truncated. */
+ read_data(kcs);
+ kcs->truncated = 1;
+ } else {
+ kcs->read_data[kcs->read_pos] = read_data(kcs);
+ (kcs->read_pos)++;
+ }
+ write_data(kcs, KCS_READ_BYTE);
+}
+
+static inline int check_ibf(struct si_sm_data *kcs, unsigned char status,
+ long time)
+{
+ if (GET_STATUS_IBF(status)) {
+ kcs->ibf_timeout -= time;
+ if (kcs->ibf_timeout < 0) {
+ start_error_recovery(kcs, "IBF not ready in time");
+ kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+ return 1;
+ }
+ return 0;
+ }
+ kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+ return 1;
+}
+
+static inline int check_obf(struct si_sm_data *kcs, unsigned char status,
+ long time)
+{
+ if (! GET_STATUS_OBF(status)) {
+ kcs->obf_timeout -= time;
+ if (kcs->obf_timeout < 0) {
+ start_error_recovery(kcs, "OBF not ready in time");
+ return 1;
+ }
+ return 0;
+ }
+ kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+ return 1;
+}
+
+static void clear_obf(struct si_sm_data *kcs, unsigned char status)
+{
+ if (GET_STATUS_OBF(status))
+ read_data(kcs);
+}
+
+static void restart_kcs_transaction(struct si_sm_data *kcs)
+{
+ kcs->write_count = kcs->orig_write_count;
+ kcs->write_pos = 0;
+ kcs->read_pos = 0;
+ kcs->state = KCS_WAIT_WRITE_START;
+ kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+ kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+ write_cmd(kcs, KCS_WRITE_START);
+}
+
+static int start_kcs_transaction(struct si_sm_data *kcs, unsigned char *data,
+ unsigned int size)
+{
+ if ((size < 2) || (size > MAX_KCS_WRITE_SIZE)) {
+ return -1;
+ }
+
+ if ((kcs->state != KCS_IDLE) && (kcs->state != KCS_HOSED)) {
+ return -2;
+ }
+
+ kcs->error_retries = 0;
+ memcpy(kcs->write_data, data, size);
+ kcs->write_count = size;
+ kcs->orig_write_count = size;
+ kcs->write_pos = 0;
+ kcs->read_pos = 0;
+ kcs->state = KCS_START_OP;
+ kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+ kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+ return 0;
+}
+
+static int get_kcs_result(struct si_sm_data *kcs, unsigned char *data,
+ unsigned int length)
+{
+ if (length < kcs->read_pos) {
+ kcs->read_pos = length;
+ kcs->truncated = 1;
+ }
+
+ memcpy(data, kcs->read_data, kcs->read_pos);
+
+ if ((length >= 3) && (kcs->read_pos < 3)) {
+ /* Guarantee that we return at least 3 bytes, with an
+ error in the third byte if it is too short. */
+ data[2] = IPMI_ERR_UNSPECIFIED;
+ kcs->read_pos = 3;
+ }
+ if (kcs->truncated) {
+ /* Report a truncated error. We might overwrite
+ another error, but that's too bad, the user needs
+ to know it was truncated. */
+ data[2] = IPMI_ERR_MSG_TRUNCATED;
+ kcs->truncated = 0;
+ }
+
+ return kcs->read_pos;
+}
+
+/* This implements the state machine defined in the IPMI manual, see
+ that for details on how this works. Divide that flowchart into
+ sections delimited by "Wait for IBF" and this will become clear. */
+static enum si_sm_result kcs_event(struct si_sm_data *kcs, long time)
+{
+ unsigned char status;
+ unsigned char state;
+
+ status = read_status(kcs);
+
+#ifdef DEBUG_STATE
+ printk(" State = %d, %x\n", kcs->state, status);
+#endif
+ /* All states wait for ibf, so just do it here. */
+ if (!check_ibf(kcs, status, time))
+ return SI_SM_CALL_WITH_DELAY;
+
+ /* Just about everything looks at the KCS state, so grab that, too. */
+ state = GET_STATUS_STATE(status);
+
+ switch (kcs->state) {
+ case KCS_IDLE:
+ /* If there's and interrupt source, turn it off. */
+ clear_obf(kcs, status);
+
+ if (GET_STATUS_ATN(status))
+ return SI_SM_ATTN;
+ else
+ return SI_SM_IDLE;
+
+ case KCS_START_OP:
+ if (state != KCS_IDLE) {
+ start_error_recovery(kcs,
+ "State machine not idle at start");
+ break;
+ }
+
+ clear_obf(kcs, status);
+ write_cmd(kcs, KCS_WRITE_START);
+ kcs->state = KCS_WAIT_WRITE_START;
+ break;
+
+ case KCS_WAIT_WRITE_START:
+ if (state != KCS_WRITE_STATE) {
+ start_error_recovery(
+ kcs,
+ "Not in write state at write start");
+ break;
+ }
+ read_data(kcs);
+ if (kcs->write_count == 1) {
+ write_cmd(kcs, KCS_WRITE_END);
+ kcs->state = KCS_WAIT_WRITE_END;
+ } else {
+ write_next_byte(kcs);
+ kcs->state = KCS_WAIT_WRITE;
+ }
+ break;
+
+ case KCS_WAIT_WRITE:
+ if (state != KCS_WRITE_STATE) {
+ start_error_recovery(kcs,
+ "Not in write state for write");
+ break;
+ }
+ clear_obf(kcs, status);
+ if (kcs->write_count == 1) {
+ write_cmd(kcs, KCS_WRITE_END);
+ kcs->state = KCS_WAIT_WRITE_END;
+ } else {
+ write_next_byte(kcs);
+ }
+ break;
+
+ case KCS_WAIT_WRITE_END:
+ if (state != KCS_WRITE_STATE) {
+ start_error_recovery(kcs,
+ "Not in write state for write end");
+ break;
+ }
+ clear_obf(kcs, status);
+ write_next_byte(kcs);
+ kcs->state = KCS_WAIT_READ;
+ break;
+
+ case KCS_WAIT_READ:
+ if ((state != KCS_READ_STATE) && (state != KCS_IDLE_STATE)) {
+ start_error_recovery(
+ kcs,
+ "Not in read or idle in read state");
+ break;
+ }
+
+ if (state == KCS_READ_STATE) {
+ if (! check_obf(kcs, status, time))
+ return SI_SM_CALL_WITH_DELAY;
+ read_next_byte(kcs);
+ } else {
+ /* We don't implement this exactly like the state
+ machine in the spec. Some broken hardware
+ does not write the final dummy byte to the
+ read register. Thus obf will never go high
+ here. We just go straight to idle, and we
+ handle clearing out obf in idle state if it
+ happens to come in. */
+ clear_obf(kcs, status);
+ kcs->orig_write_count = 0;
+ kcs->state = KCS_IDLE;
+ return SI_SM_TRANSACTION_COMPLETE;
+ }
+ break;
+
+ case KCS_ERROR0:
+ clear_obf(kcs, status);
+ write_cmd(kcs, KCS_GET_STATUS_ABORT);
+ kcs->state = KCS_ERROR1;
+ break;
+
+ case KCS_ERROR1:
+ clear_obf(kcs, status);
+ write_data(kcs, 0);
+ kcs->state = KCS_ERROR2;
+ break;
+
+ case KCS_ERROR2:
+ if (state != KCS_READ_STATE) {
+ start_error_recovery(kcs,
+ "Not in read state for error2");
+ break;
+ }
+ if (! check_obf(kcs, status, time))
+ return SI_SM_CALL_WITH_DELAY;
+
+ clear_obf(kcs, status);
+ write_data(kcs, KCS_READ_BYTE);
+ kcs->state = KCS_ERROR3;
+ break;
+
+ case KCS_ERROR3:
+ if (state != KCS_IDLE_STATE) {
+ start_error_recovery(kcs,
+ "Not in idle state for error3");
+ break;
+ }
+
+ if (! check_obf(kcs, status, time))
+ return SI_SM_CALL_WITH_DELAY;
+
+ clear_obf(kcs, status);
+ if (kcs->orig_write_count) {
+ restart_kcs_transaction(kcs);
+ } else {
+ kcs->state = KCS_IDLE;
+ return SI_SM_TRANSACTION_COMPLETE;
+ }
+ break;
+
+ case KCS_HOSED:
+ break;
+ }
+
+ if (kcs->state == KCS_HOSED) {
+ init_kcs_data(kcs, kcs->io);
+ return SI_SM_HOSED;
+ }
+
+ return SI_SM_CALL_WITHOUT_DELAY;
+}
+
+static int kcs_size(void)
+{
+ return sizeof(struct si_sm_data);
+}
+
+static int kcs_detect(struct si_sm_data *kcs)
+{
+ /* It's impossible for the KCS status register to be all 1's,
+ (assuming a properly functioning, self-initialized BMC)
+ but that's what you get from reading a bogus address, so we
+ test that first. */
+ if (read_status(kcs) == 0xff)
+ return 1;
+
+ return 0;
+}
+
+static void kcs_cleanup(struct si_sm_data *kcs)
+{
+}
+
+struct si_sm_handlers kcs_smi_handlers =
+{
+ .version = IPMI_KCS_VERSION,
+ .init_data = init_kcs_data,
+ .start_transaction = start_kcs_transaction,
+ .get_result = get_kcs_result,
+ .event = kcs_event,
+ .detect = kcs_detect,
+ .cleanup = kcs_cleanup,
+ .size = kcs_size,
+};
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
new file mode 100644
index 000000000000..a6606a1aced7
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_msghandler.c
@@ -0,0 +1,3174 @@
+/*
+ * ipmi_msghandler.c
+ *
+ * Incoming and outgoing message routing for an IPMI interface.
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#define PFX "IPMI message handler: "
+#define IPMI_MSGHANDLER_VERSION "v33"
+
+static struct ipmi_recv_msg *ipmi_alloc_recv_msg(void);
+static int ipmi_init_msghandler(void);
+
+static int initialized = 0;
+
+static struct proc_dir_entry *proc_ipmi_root = NULL;
+
+#define MAX_EVENTS_IN_QUEUE 25
+
+/* Don't let a message sit in a queue forever, always time it with at lest
+ the max message timer. This is in milliseconds. */
+#define MAX_MSG_TIMEOUT 60000
+
+struct ipmi_user
+{
+ struct list_head link;
+
+ /* The upper layer that handles receive messages. */
+ struct ipmi_user_hndl *handler;
+ void *handler_data;
+
+ /* The interface this user is bound to. */
+ ipmi_smi_t intf;
+
+ /* Does this interface receive IPMI events? */
+ int gets_events;
+};
+
+struct cmd_rcvr
+{
+ struct list_head link;
+
+ ipmi_user_t user;
+ unsigned char netfn;
+ unsigned char cmd;
+};
+
+struct seq_table
+{
+ unsigned int inuse : 1;
+ unsigned int broadcast : 1;
+
+ unsigned long timeout;
+ unsigned long orig_timeout;
+ unsigned int retries_left;
+
+ /* To verify on an incoming send message response that this is
+ the message that the response is for, we keep a sequence id
+ and increment it every time we send a message. */
+ long seqid;
+
+ /* This is held so we can properly respond to the message on a
+ timeout, and it is used to hold the temporary data for
+ retransmission, too. */
+ struct ipmi_recv_msg *recv_msg;
+};
+
+/* Store the information in a msgid (long) to allow us to find a
+ sequence table entry from the msgid. */
+#define STORE_SEQ_IN_MSGID(seq, seqid) (((seq&0xff)<<26) | (seqid&0x3ffffff))
+
+#define GET_SEQ_FROM_MSGID(msgid, seq, seqid) \
+ do { \
+ seq = ((msgid >> 26) & 0x3f); \
+ seqid = (msgid & 0x3fffff); \
+ } while(0)
+
+#define NEXT_SEQID(seqid) (((seqid) + 1) & 0x3fffff)
+
+struct ipmi_channel
+{
+ unsigned char medium;
+ unsigned char protocol;
+};
+
+struct ipmi_proc_entry
+{
+ char *name;
+ struct ipmi_proc_entry *next;
+};
+
+#define IPMI_IPMB_NUM_SEQ 64
+#define IPMI_MAX_CHANNELS 8
+struct ipmi_smi
+{
+ /* What interface number are we? */
+ int intf_num;
+
+ /* The list of upper layers that are using me. We read-lock
+ this when delivering messages to the upper layer to keep
+ the user from going away while we are processing the
+ message. This means that you cannot add or delete a user
+ from the receive callback. */
+ rwlock_t users_lock;
+ struct list_head users;
+
+ /* Used for wake ups at startup. */
+ wait_queue_head_t waitq;
+
+ /* The IPMI version of the BMC on the other end. */
+ unsigned char version_major;
+ unsigned char version_minor;
+
+ /* This is the lower-layer's sender routine. */
+ struct ipmi_smi_handlers *handlers;
+ void *send_info;
+
+ /* A list of proc entries for this interface. This does not
+ need a lock, only one thread creates it and only one thread
+ destroys it. */
+ struct ipmi_proc_entry *proc_entries;
+
+ /* A table of sequence numbers for this interface. We use the
+ sequence numbers for IPMB messages that go out of the
+ interface to match them up with their responses. A routine
+ is called periodically to time the items in this list. */
+ spinlock_t seq_lock;
+ struct seq_table seq_table[IPMI_IPMB_NUM_SEQ];
+ int curr_seq;
+
+ /* Messages that were delayed for some reason (out of memory,
+ for instance), will go in here to be processed later in a
+ periodic timer interrupt. */
+ spinlock_t waiting_msgs_lock;
+ struct list_head waiting_msgs;
+
+ /* The list of command receivers that are registered for commands
+ on this interface. */
+ rwlock_t cmd_rcvr_lock;
+ struct list_head cmd_rcvrs;
+
+ /* Events that were queues because no one was there to receive
+ them. */
+ spinlock_t events_lock; /* For dealing with event stuff. */
+ struct list_head waiting_events;
+ unsigned int waiting_events_count; /* How many events in queue? */
+
+ /* This will be non-null if someone registers to receive all
+ IPMI commands (this is for interface emulation). There
+ may not be any things in the cmd_rcvrs list above when
+ this is registered. */
+ ipmi_user_t all_cmd_rcvr;
+
+ /* My slave address. This is initialized to IPMI_BMC_SLAVE_ADDR,
+ but may be changed by the user. */
+ unsigned char my_address;
+
+ /* My LUN. This should generally stay the SMS LUN, but just in
+ case... */
+ unsigned char my_lun;
+
+ /* The event receiver for my BMC, only really used at panic
+ shutdown as a place to store this. */
+ unsigned char event_receiver;
+ unsigned char event_receiver_lun;
+ unsigned char local_sel_device;
+ unsigned char local_event_generator;
+
+ /* A cheap hack, if this is non-null and a message to an
+ interface comes in with a NULL user, call this routine with
+ it. Note that the message will still be freed by the
+ caller. This only works on the system interface. */
+ void (*null_user_handler)(ipmi_smi_t intf, struct ipmi_smi_msg *msg);
+
+ /* When we are scanning the channels for an SMI, this will
+ tell which channel we are scanning. */
+ int curr_channel;
+
+ /* Channel information */
+ struct ipmi_channel channels[IPMI_MAX_CHANNELS];
+
+ /* Proc FS stuff. */
+ struct proc_dir_entry *proc_dir;
+ char proc_dir_name[10];
+
+ spinlock_t counter_lock; /* For making counters atomic. */
+
+ /* Commands we got that were invalid. */
+ unsigned int sent_invalid_commands;
+
+ /* Commands we sent to the MC. */
+ unsigned int sent_local_commands;
+ /* Responses from the MC that were delivered to a user. */
+ unsigned int handled_local_responses;
+ /* Responses from the MC that were not delivered to a user. */
+ unsigned int unhandled_local_responses;
+
+ /* Commands we sent out to the IPMB bus. */
+ unsigned int sent_ipmb_commands;
+ /* Commands sent on the IPMB that had errors on the SEND CMD */
+ unsigned int sent_ipmb_command_errs;
+ /* Each retransmit increments this count. */
+ unsigned int retransmitted_ipmb_commands;
+ /* When a message times out (runs out of retransmits) this is
+ incremented. */
+ unsigned int timed_out_ipmb_commands;
+
+ /* This is like above, but for broadcasts. Broadcasts are
+ *not* included in the above count (they are expected to
+ time out). */
+ unsigned int timed_out_ipmb_broadcasts;
+
+ /* Responses I have sent to the IPMB bus. */
+ unsigned int sent_ipmb_responses;
+
+ /* The response was delivered to the user. */
+ unsigned int handled_ipmb_responses;
+ /* The response had invalid data in it. */
+ unsigned int invalid_ipmb_responses;
+ /* The response didn't have anyone waiting for it. */
+ unsigned int unhandled_ipmb_responses;
+
+ /* Commands we sent out to the IPMB bus. */
+ unsigned int sent_lan_commands;
+ /* Commands sent on the IPMB that had errors on the SEND CMD */
+ unsigned int sent_lan_command_errs;
+ /* Each retransmit increments this count. */
+ unsigned int retransmitted_lan_commands;
+ /* When a message times out (runs out of retransmits) this is
+ incremented. */
+ unsigned int timed_out_lan_commands;
+
+ /* Responses I have sent to the IPMB bus. */
+ unsigned int sent_lan_responses;
+
+ /* The response was delivered to the user. */
+ unsigned int handled_lan_responses;
+ /* The response had invalid data in it. */
+ unsigned int invalid_lan_responses;
+ /* The response didn't have anyone waiting for it. */
+ unsigned int unhandled_lan_responses;
+
+ /* The command was delivered to the user. */
+ unsigned int handled_commands;
+ /* The command had invalid data in it. */
+ unsigned int invalid_commands;
+ /* The command didn't have anyone waiting for it. */
+ unsigned int unhandled_commands;
+
+ /* Invalid data in an event. */
+ unsigned int invalid_events;
+ /* Events that were received with the proper format. */
+ unsigned int events;
+};
+
+#define MAX_IPMI_INTERFACES 4
+static ipmi_smi_t ipmi_interfaces[MAX_IPMI_INTERFACES];
+
+/* Used to keep interfaces from going away while operations are
+ operating on interfaces. Grab read if you are not modifying the
+ interfaces, write if you are. */
+static DECLARE_RWSEM(interfaces_sem);
+
+/* Directly protects the ipmi_interfaces data structure. This is
+ claimed in the timer interrupt. */
+static DEFINE_SPINLOCK(interfaces_lock);
+
+/* List of watchers that want to know when smi's are added and
+ deleted. */
+static struct list_head smi_watchers = LIST_HEAD_INIT(smi_watchers);
+static DECLARE_RWSEM(smi_watchers_sem);
+
+int ipmi_smi_watcher_register(struct ipmi_smi_watcher *watcher)
+{
+ int i;
+
+ down_read(&interfaces_sem);
+ down_write(&smi_watchers_sem);
+ list_add(&(watcher->link), &smi_watchers);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] != NULL) {
+ watcher->new_smi(i);
+ }
+ }
+ up_write(&smi_watchers_sem);
+ up_read(&interfaces_sem);
+ return 0;
+}
+
+int ipmi_smi_watcher_unregister(struct ipmi_smi_watcher *watcher)
+{
+ down_write(&smi_watchers_sem);
+ list_del(&(watcher->link));
+ up_write(&smi_watchers_sem);
+ return 0;
+}
+
+static void
+call_smi_watchers(int i)
+{
+ struct ipmi_smi_watcher *w;
+
+ down_read(&smi_watchers_sem);
+ list_for_each_entry(w, &smi_watchers, link) {
+ if (try_module_get(w->owner)) {
+ w->new_smi(i);
+ module_put(w->owner);
+ }
+ }
+ up_read(&smi_watchers_sem);
+}
+
+static int
+ipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2)
+{
+ if (addr1->addr_type != addr2->addr_type)
+ return 0;
+
+ if (addr1->channel != addr2->channel)
+ return 0;
+
+ if (addr1->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ struct ipmi_system_interface_addr *smi_addr1
+ = (struct ipmi_system_interface_addr *) addr1;
+ struct ipmi_system_interface_addr *smi_addr2
+ = (struct ipmi_system_interface_addr *) addr2;
+ return (smi_addr1->lun == smi_addr2->lun);
+ }
+
+ if ((addr1->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr1->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ struct ipmi_ipmb_addr *ipmb_addr1
+ = (struct ipmi_ipmb_addr *) addr1;
+ struct ipmi_ipmb_addr *ipmb_addr2
+ = (struct ipmi_ipmb_addr *) addr2;
+
+ return ((ipmb_addr1->slave_addr == ipmb_addr2->slave_addr)
+ && (ipmb_addr1->lun == ipmb_addr2->lun));
+ }
+
+ if (addr1->addr_type == IPMI_LAN_ADDR_TYPE) {
+ struct ipmi_lan_addr *lan_addr1
+ = (struct ipmi_lan_addr *) addr1;
+ struct ipmi_lan_addr *lan_addr2
+ = (struct ipmi_lan_addr *) addr2;
+
+ return ((lan_addr1->remote_SWID == lan_addr2->remote_SWID)
+ && (lan_addr1->local_SWID == lan_addr2->local_SWID)
+ && (lan_addr1->session_handle
+ == lan_addr2->session_handle)
+ && (lan_addr1->lun == lan_addr2->lun));
+ }
+
+ return 1;
+}
+
+int ipmi_validate_addr(struct ipmi_addr *addr, int len)
+{
+ if (len < sizeof(struct ipmi_system_interface_addr)) {
+ return -EINVAL;
+ }
+
+ if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ if (addr->channel != IPMI_BMC_CHANNEL)
+ return -EINVAL;
+ return 0;
+ }
+
+ if ((addr->channel == IPMI_BMC_CHANNEL)
+ || (addr->channel >= IPMI_NUM_CHANNELS)
+ || (addr->channel < 0))
+ return -EINVAL;
+
+ if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ if (len < sizeof(struct ipmi_ipmb_addr)) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+ if (len < sizeof(struct ipmi_lan_addr)) {
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+unsigned int ipmi_addr_length(int addr_type)
+{
+ if (addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
+ return sizeof(struct ipmi_system_interface_addr);
+
+ if ((addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ return sizeof(struct ipmi_ipmb_addr);
+ }
+
+ if (addr_type == IPMI_LAN_ADDR_TYPE)
+ return sizeof(struct ipmi_lan_addr);
+
+ return 0;
+}
+
+static void deliver_response(struct ipmi_recv_msg *msg)
+{
+ msg->user->handler->ipmi_recv_hndl(msg, msg->user->handler_data);
+}
+
+/* Find the next sequence number not being used and add the given
+ message with the given timeout to the sequence table. This must be
+ called with the interface's seq_lock held. */
+static int intf_next_seq(ipmi_smi_t intf,
+ struct ipmi_recv_msg *recv_msg,
+ unsigned long timeout,
+ int retries,
+ int broadcast,
+ unsigned char *seq,
+ long *seqid)
+{
+ int rv = 0;
+ unsigned int i;
+
+ for (i=intf->curr_seq;
+ (i+1)%IPMI_IPMB_NUM_SEQ != intf->curr_seq;
+ i=(i+1)%IPMI_IPMB_NUM_SEQ)
+ {
+ if (! intf->seq_table[i].inuse)
+ break;
+ }
+
+ if (! intf->seq_table[i].inuse) {
+ intf->seq_table[i].recv_msg = recv_msg;
+
+ /* Start with the maximum timeout, when the send response
+ comes in we will start the real timer. */
+ intf->seq_table[i].timeout = MAX_MSG_TIMEOUT;
+ intf->seq_table[i].orig_timeout = timeout;
+ intf->seq_table[i].retries_left = retries;
+ intf->seq_table[i].broadcast = broadcast;
+ intf->seq_table[i].inuse = 1;
+ intf->seq_table[i].seqid = NEXT_SEQID(intf->seq_table[i].seqid);
+ *seq = i;
+ *seqid = intf->seq_table[i].seqid;
+ intf->curr_seq = (i+1)%IPMI_IPMB_NUM_SEQ;
+ } else {
+ rv = -EAGAIN;
+ }
+
+ return rv;
+}
+
+/* Return the receive message for the given sequence number and
+ release the sequence number so it can be reused. Some other data
+ is passed in to be sure the message matches up correctly (to help
+ guard against message coming in after their timeout and the
+ sequence number being reused). */
+static int intf_find_seq(ipmi_smi_t intf,
+ unsigned char seq,
+ short channel,
+ unsigned char cmd,
+ unsigned char netfn,
+ struct ipmi_addr *addr,
+ struct ipmi_recv_msg **recv_msg)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+
+ if (seq >= IPMI_IPMB_NUM_SEQ)
+ return -EINVAL;
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ if (intf->seq_table[seq].inuse) {
+ struct ipmi_recv_msg *msg = intf->seq_table[seq].recv_msg;
+
+ if ((msg->addr.channel == channel)
+ && (msg->msg.cmd == cmd)
+ && (msg->msg.netfn == netfn)
+ && (ipmi_addr_equal(addr, &(msg->addr))))
+ {
+ *recv_msg = msg;
+ intf->seq_table[seq].inuse = 0;
+ rv = 0;
+ }
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ return rv;
+}
+
+
+/* Start the timer for a specific sequence table entry. */
+static int intf_start_seq_timer(ipmi_smi_t intf,
+ long msgid)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+ unsigned char seq;
+ unsigned long seqid;
+
+
+ GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ /* We do this verification because the user can be deleted
+ while a message is outstanding. */
+ if ((intf->seq_table[seq].inuse)
+ && (intf->seq_table[seq].seqid == seqid))
+ {
+ struct seq_table *ent = &(intf->seq_table[seq]);
+ ent->timeout = ent->orig_timeout;
+ rv = 0;
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ return rv;
+}
+
+/* Got an error for the send message for a specific sequence number. */
+static int intf_err_seq(ipmi_smi_t intf,
+ long msgid,
+ unsigned int err)
+{
+ int rv = -ENODEV;
+ unsigned long flags;
+ unsigned char seq;
+ unsigned long seqid;
+ struct ipmi_recv_msg *msg = NULL;
+
+
+ GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ /* We do this verification because the user can be deleted
+ while a message is outstanding. */
+ if ((intf->seq_table[seq].inuse)
+ && (intf->seq_table[seq].seqid == seqid))
+ {
+ struct seq_table *ent = &(intf->seq_table[seq]);
+
+ ent->inuse = 0;
+ msg = ent->recv_msg;
+ rv = 0;
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ if (msg) {
+ msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ msg->msg_data[0] = err;
+ msg->msg.netfn |= 1; /* Convert to a response. */
+ msg->msg.data_len = 1;
+ msg->msg.data = msg->msg_data;
+ deliver_response(msg);
+ }
+
+ return rv;
+}
+
+
+int ipmi_create_user(unsigned int if_num,
+ struct ipmi_user_hndl *handler,
+ void *handler_data,
+ ipmi_user_t *user)
+{
+ unsigned long flags;
+ ipmi_user_t new_user;
+ int rv = 0;
+ ipmi_smi_t intf;
+
+ /* There is no module usecount here, because it's not
+ required. Since this can only be used by and called from
+ other modules, they will implicitly use this module, and
+ thus this can't be removed unless the other modules are
+ removed. */
+
+ if (handler == NULL)
+ return -EINVAL;
+
+ /* Make sure the driver is actually initialized, this handles
+ problems with initialization order. */
+ if (!initialized) {
+ rv = ipmi_init_msghandler();
+ if (rv)
+ return rv;
+
+ /* The init code doesn't return an error if it was turned
+ off, but it won't initialize. Check that. */
+ if (!initialized)
+ return -ENODEV;
+ }
+
+ new_user = kmalloc(sizeof(*new_user), GFP_KERNEL);
+ if (! new_user)
+ return -ENOMEM;
+
+ down_read(&interfaces_sem);
+ if ((if_num > MAX_IPMI_INTERFACES) || ipmi_interfaces[if_num] == NULL)
+ {
+ rv = -EINVAL;
+ goto out_unlock;
+ }
+
+ intf = ipmi_interfaces[if_num];
+
+ new_user->handler = handler;
+ new_user->handler_data = handler_data;
+ new_user->intf = intf;
+ new_user->gets_events = 0;
+
+ if (!try_module_get(intf->handlers->owner)) {
+ rv = -ENODEV;
+ goto out_unlock;
+ }
+
+ if (intf->handlers->inc_usecount) {
+ rv = intf->handlers->inc_usecount(intf->send_info);
+ if (rv) {
+ module_put(intf->handlers->owner);
+ goto out_unlock;
+ }
+ }
+
+ write_lock_irqsave(&intf->users_lock, flags);
+ list_add_tail(&new_user->link, &intf->users);
+ write_unlock_irqrestore(&intf->users_lock, flags);
+
+ out_unlock:
+ if (rv) {
+ kfree(new_user);
+ } else {
+ *user = new_user;
+ }
+
+ up_read(&interfaces_sem);
+ return rv;
+}
+
+static int ipmi_destroy_user_nolock(ipmi_user_t user)
+{
+ int rv = -ENODEV;
+ ipmi_user_t t_user;
+ struct cmd_rcvr *rcvr, *rcvr2;
+ int i;
+ unsigned long flags;
+
+ /* Find the user and delete them from the list. */
+ list_for_each_entry(t_user, &(user->intf->users), link) {
+ if (t_user == user) {
+ list_del(&t_user->link);
+ rv = 0;
+ break;
+ }
+ }
+
+ if (rv) {
+ goto out_unlock;
+ }
+
+ /* Remove the user from the interfaces sequence table. */
+ spin_lock_irqsave(&(user->intf->seq_lock), flags);
+ for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+ if (user->intf->seq_table[i].inuse
+ && (user->intf->seq_table[i].recv_msg->user == user))
+ {
+ user->intf->seq_table[i].inuse = 0;
+ }
+ }
+ spin_unlock_irqrestore(&(user->intf->seq_lock), flags);
+
+ /* Remove the user from the command receiver's table. */
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ list_for_each_entry_safe(rcvr, rcvr2, &(user->intf->cmd_rcvrs), link) {
+ if (rcvr->user == user) {
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ }
+ }
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+
+ kfree(user);
+
+ out_unlock:
+
+ return rv;
+}
+
+int ipmi_destroy_user(ipmi_user_t user)
+{
+ int rv;
+ ipmi_smi_t intf = user->intf;
+ unsigned long flags;
+
+ down_read(&interfaces_sem);
+ write_lock_irqsave(&intf->users_lock, flags);
+ rv = ipmi_destroy_user_nolock(user);
+ if (!rv) {
+ module_put(intf->handlers->owner);
+ if (intf->handlers->dec_usecount)
+ intf->handlers->dec_usecount(intf->send_info);
+ }
+
+ write_unlock_irqrestore(&intf->users_lock, flags);
+ up_read(&interfaces_sem);
+ return rv;
+}
+
+void ipmi_get_version(ipmi_user_t user,
+ unsigned char *major,
+ unsigned char *minor)
+{
+ *major = user->intf->version_major;
+ *minor = user->intf->version_minor;
+}
+
+void ipmi_set_my_address(ipmi_user_t user,
+ unsigned char address)
+{
+ user->intf->my_address = address;
+}
+
+unsigned char ipmi_get_my_address(ipmi_user_t user)
+{
+ return user->intf->my_address;
+}
+
+void ipmi_set_my_LUN(ipmi_user_t user,
+ unsigned char LUN)
+{
+ user->intf->my_lun = LUN & 0x3;
+}
+
+unsigned char ipmi_get_my_LUN(ipmi_user_t user)
+{
+ return user->intf->my_lun;
+}
+
+int ipmi_set_gets_events(ipmi_user_t user, int val)
+{
+ unsigned long flags;
+ struct ipmi_recv_msg *msg, *msg2;
+
+ read_lock(&(user->intf->users_lock));
+ spin_lock_irqsave(&(user->intf->events_lock), flags);
+ user->gets_events = val;
+
+ if (val) {
+ /* Deliver any queued events. */
+ list_for_each_entry_safe(msg, msg2, &(user->intf->waiting_events), link) {
+ list_del(&msg->link);
+ msg->user = user;
+ deliver_response(msg);
+ }
+ }
+
+ spin_unlock_irqrestore(&(user->intf->events_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ return 0;
+}
+
+int ipmi_register_for_cmd(ipmi_user_t user,
+ unsigned char netfn,
+ unsigned char cmd)
+{
+ struct cmd_rcvr *cmp;
+ unsigned long flags;
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+
+
+ rcvr = kmalloc(sizeof(*rcvr), GFP_KERNEL);
+ if (! rcvr)
+ return -ENOMEM;
+
+ read_lock(&(user->intf->users_lock));
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ if (user->intf->all_cmd_rcvr != NULL) {
+ rv = -EBUSY;
+ goto out_unlock;
+ }
+
+ /* Make sure the command/netfn is not already registered. */
+ list_for_each_entry(cmp, &(user->intf->cmd_rcvrs), link) {
+ if ((cmp->netfn == netfn) && (cmp->cmd == cmd)) {
+ rv = -EBUSY;
+ break;
+ }
+ }
+
+ if (! rv) {
+ rcvr->cmd = cmd;
+ rcvr->netfn = netfn;
+ rcvr->user = user;
+ list_add_tail(&(rcvr->link), &(user->intf->cmd_rcvrs));
+ }
+ out_unlock:
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ if (rv)
+ kfree(rcvr);
+
+ return rv;
+}
+
+int ipmi_unregister_for_cmd(ipmi_user_t user,
+ unsigned char netfn,
+ unsigned char cmd)
+{
+ unsigned long flags;
+ struct cmd_rcvr *rcvr;
+ int rv = -ENOENT;
+
+ read_lock(&(user->intf->users_lock));
+ write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+ /* Make sure the command/netfn is not already registered. */
+ list_for_each_entry(rcvr, &(user->intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ rv = 0;
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ break;
+ }
+ }
+ write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+ read_unlock(&(user->intf->users_lock));
+
+ return rv;
+}
+
+void ipmi_user_set_run_to_completion(ipmi_user_t user, int val)
+{
+ user->intf->handlers->set_run_to_completion(user->intf->send_info,
+ val);
+}
+
+static unsigned char
+ipmb_checksum(unsigned char *data, int size)
+{
+ unsigned char csum = 0;
+
+ for (; size > 0; size--, data++)
+ csum += *data;
+
+ return -csum;
+}
+
+static inline void format_ipmb_msg(struct ipmi_smi_msg *smi_msg,
+ struct kernel_ipmi_msg *msg,
+ struct ipmi_ipmb_addr *ipmb_addr,
+ long msgid,
+ unsigned char ipmb_seq,
+ int broadcast,
+ unsigned char source_address,
+ unsigned char source_lun)
+{
+ int i = broadcast;
+
+ /* Format the IPMB header data. */
+ smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+ smi_msg->data[2] = ipmb_addr->channel;
+ if (broadcast)
+ smi_msg->data[3] = 0;
+ smi_msg->data[i+3] = ipmb_addr->slave_addr;
+ smi_msg->data[i+4] = (msg->netfn << 2) | (ipmb_addr->lun & 0x3);
+ smi_msg->data[i+5] = ipmb_checksum(&(smi_msg->data[i+3]), 2);
+ smi_msg->data[i+6] = source_address;
+ smi_msg->data[i+7] = (ipmb_seq << 2) | source_lun;
+ smi_msg->data[i+8] = msg->cmd;
+
+ /* Now tack on the data to the message. */
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[i+9]), msg->data,
+ msg->data_len);
+ smi_msg->data_size = msg->data_len + 9;
+
+ /* Now calculate the checksum and tack it on. */
+ smi_msg->data[i+smi_msg->data_size]
+ = ipmb_checksum(&(smi_msg->data[i+6]),
+ smi_msg->data_size-6);
+
+ /* Add on the checksum size and the offset from the
+ broadcast. */
+ smi_msg->data_size += 1 + i;
+
+ smi_msg->msgid = msgid;
+}
+
+static inline void format_lan_msg(struct ipmi_smi_msg *smi_msg,
+ struct kernel_ipmi_msg *msg,
+ struct ipmi_lan_addr *lan_addr,
+ long msgid,
+ unsigned char ipmb_seq,
+ unsigned char source_lun)
+{
+ /* Format the IPMB header data. */
+ smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+ smi_msg->data[2] = lan_addr->channel;
+ smi_msg->data[3] = lan_addr->session_handle;
+ smi_msg->data[4] = lan_addr->remote_SWID;
+ smi_msg->data[5] = (msg->netfn << 2) | (lan_addr->lun & 0x3);
+ smi_msg->data[6] = ipmb_checksum(&(smi_msg->data[4]), 2);
+ smi_msg->data[7] = lan_addr->local_SWID;
+ smi_msg->data[8] = (ipmb_seq << 2) | source_lun;
+ smi_msg->data[9] = msg->cmd;
+
+ /* Now tack on the data to the message. */
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[10]), msg->data,
+ msg->data_len);
+ smi_msg->data_size = msg->data_len + 10;
+
+ /* Now calculate the checksum and tack it on. */
+ smi_msg->data[smi_msg->data_size]
+ = ipmb_checksum(&(smi_msg->data[7]),
+ smi_msg->data_size-7);
+
+ /* Add on the checksum size and the offset from the
+ broadcast. */
+ smi_msg->data_size += 1;
+
+ smi_msg->msgid = msgid;
+}
+
+/* Separate from ipmi_request so that the user does not have to be
+ supplied in certain circumstances (mainly at panic time). If
+ messages are supplied, they will be freed, even if an error
+ occurs. */
+static inline int i_ipmi_request(ipmi_user_t user,
+ ipmi_smi_t intf,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ void *supplied_smi,
+ struct ipmi_recv_msg *supplied_recv,
+ int priority,
+ unsigned char source_address,
+ unsigned char source_lun,
+ int retries,
+ unsigned int retry_time_ms)
+{
+ int rv = 0;
+ struct ipmi_smi_msg *smi_msg;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ if (supplied_recv) {
+ recv_msg = supplied_recv;
+ } else {
+ recv_msg = ipmi_alloc_recv_msg();
+ if (recv_msg == NULL) {
+ return -ENOMEM;
+ }
+ }
+ recv_msg->user_msg_data = user_msg_data;
+
+ if (supplied_smi) {
+ smi_msg = (struct ipmi_smi_msg *) supplied_smi;
+ } else {
+ smi_msg = ipmi_alloc_smi_msg();
+ if (smi_msg == NULL) {
+ ipmi_free_recv_msg(recv_msg);
+ return -ENOMEM;
+ }
+ }
+
+ recv_msg->user = user;
+ recv_msg->msgid = msgid;
+ /* Store the message to send in the receive message so timeout
+ responses can get the proper response data. */
+ recv_msg->msg = *msg;
+
+ if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+ struct ipmi_system_interface_addr *smi_addr;
+
+ if (msg->netfn & 1) {
+ /* Responses are not allowed to the SMI. */
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ smi_addr = (struct ipmi_system_interface_addr *) addr;
+ if (smi_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, smi_addr, sizeof(*smi_addr));
+
+ if ((msg->netfn == IPMI_NETFN_APP_REQUEST)
+ && ((msg->cmd == IPMI_SEND_MSG_CMD)
+ || (msg->cmd == IPMI_GET_MSG_CMD)
+ || (msg->cmd == IPMI_READ_EVENT_MSG_BUFFER_CMD)))
+ {
+ /* We don't let the user do these, since we manage
+ the sequence numbers. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if ((msg->data_len + 2) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ smi_msg->data[0] = (msg->netfn << 2) | (smi_addr->lun & 0x3);
+ smi_msg->data[1] = msg->cmd;
+ smi_msg->msgid = msgid;
+ smi_msg->user_data = recv_msg;
+ if (msg->data_len > 0)
+ memcpy(&(smi_msg->data[2]), msg->data, msg->data_len);
+ smi_msg->data_size = msg->data_len + 2;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_local_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ } else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+ || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+ {
+ struct ipmi_ipmb_addr *ipmb_addr;
+ unsigned char ipmb_seq;
+ long seqid;
+ int broadcast = 0;
+
+ if (addr->channel > IPMI_NUM_CHANNELS) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if (intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_IPMB)
+ {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if (retries < 0) {
+ if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)
+ retries = 0; /* Don't retry broadcasts. */
+ else
+ retries = 4;
+ }
+ if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE) {
+ /* Broadcasts add a zero at the beginning of the
+ message, but otherwise is the same as an IPMB
+ address. */
+ addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+ broadcast = 1;
+ }
+
+
+ /* Default to 1 second retries. */
+ if (retry_time_ms == 0)
+ retry_time_ms = 1000;
+
+ /* 9 for the header and 1 for the checksum, plus
+ possibly one for the broadcast. */
+ if ((msg->data_len + 10 + broadcast) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ ipmb_addr = (struct ipmi_ipmb_addr *) addr;
+ if (ipmb_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, ipmb_addr, sizeof(*ipmb_addr));
+
+ if (recv_msg->msg.netfn & 0x1) {
+ /* It's a response, so use the user's sequence
+ from msgid. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ format_ipmb_msg(smi_msg, msg, ipmb_addr, msgid,
+ msgid, broadcast,
+ source_address, source_lun);
+
+ /* Save the receive message so we can use it
+ to deliver the response. */
+ smi_msg->user_data = recv_msg;
+ } else {
+ /* It's a command, so get a sequence for it. */
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->sent_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Create a sequence number with a 1 second
+ timeout and 4 retries. */
+ rv = intf_next_seq(intf,
+ recv_msg,
+ retry_time_ms,
+ retries,
+ broadcast,
+ &ipmb_seq,
+ &seqid);
+ if (rv) {
+ /* We have used up all the sequence numbers,
+ probably, so abort. */
+ spin_unlock_irqrestore(&(intf->seq_lock),
+ flags);
+ goto out_err;
+ }
+
+ /* Store the sequence number in the message,
+ so that when the send message response
+ comes back we can start the timer. */
+ format_ipmb_msg(smi_msg, msg, ipmb_addr,
+ STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+ ipmb_seq, broadcast,
+ source_address, source_lun);
+
+ /* Copy the message into the recv message data, so we
+ can retransmit it later if necessary. */
+ memcpy(recv_msg->msg_data, smi_msg->data,
+ smi_msg->data_size);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = smi_msg->data_size;
+
+ /* We don't unlock until here, because we need
+ to copy the completed message into the
+ recv_msg before we release the lock.
+ Otherwise, race conditions may bite us. I
+ know that's pretty paranoid, but I prefer
+ to be correct. */
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+ }
+ } else if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+ struct ipmi_lan_addr *lan_addr;
+ unsigned char ipmb_seq;
+ long seqid;
+
+ if (addr->channel > IPMI_NUM_CHANNELS) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ if ((intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_8023LAN)
+ && (intf->channels[addr->channel].medium
+ != IPMI_CHANNEL_MEDIUM_ASYNC))
+ {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ retries = 4;
+
+ /* Default to 1 second retries. */
+ if (retry_time_ms == 0)
+ retry_time_ms = 1000;
+
+ /* 11 for the header and 1 for the checksum. */
+ if ((msg->data_len + 12) > IPMI_MAX_MSG_LENGTH) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EMSGSIZE;
+ goto out_err;
+ }
+
+ lan_addr = (struct ipmi_lan_addr *) addr;
+ if (lan_addr->lun > 3) {
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+ memcpy(&recv_msg->addr, lan_addr, sizeof(*lan_addr));
+
+ if (recv_msg->msg.netfn & 0x1) {
+ /* It's a response, so use the user's sequence
+ from msgid. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ format_lan_msg(smi_msg, msg, lan_addr, msgid,
+ msgid, source_lun);
+
+ /* Save the receive message so we can use it
+ to deliver the response. */
+ smi_msg->user_data = recv_msg;
+ } else {
+ /* It's a command, so get a sequence for it. */
+
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->sent_lan_commands++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Create a sequence number with a 1 second
+ timeout and 4 retries. */
+ rv = intf_next_seq(intf,
+ recv_msg,
+ retry_time_ms,
+ retries,
+ 0,
+ &ipmb_seq,
+ &seqid);
+ if (rv) {
+ /* We have used up all the sequence numbers,
+ probably, so abort. */
+ spin_unlock_irqrestore(&(intf->seq_lock),
+ flags);
+ goto out_err;
+ }
+
+ /* Store the sequence number in the message,
+ so that when the send message response
+ comes back we can start the timer. */
+ format_lan_msg(smi_msg, msg, lan_addr,
+ STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+ ipmb_seq, source_lun);
+
+ /* Copy the message into the recv message data, so we
+ can retransmit it later if necessary. */
+ memcpy(recv_msg->msg_data, smi_msg->data,
+ smi_msg->data_size);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = smi_msg->data_size;
+
+ /* We don't unlock until here, because we need
+ to copy the completed message into the
+ recv_msg before we release the lock.
+ Otherwise, race conditions may bite us. I
+ know that's pretty paranoid, but I prefer
+ to be correct. */
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+ }
+ } else {
+ /* Unknown address type. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->sent_invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ rv = -EINVAL;
+ goto out_err;
+ }
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ for (m=0; m<smi_msg->data_size; m++)
+ printk(" %2.2x", smi_msg->data[m]);
+ printk("\n");
+ }
+#endif
+ intf->handlers->sender(intf->send_info, smi_msg, priority);
+
+ return 0;
+
+ out_err:
+ ipmi_free_smi_msg(smi_msg);
+ ipmi_free_recv_msg(recv_msg);
+ return rv;
+}
+
+int ipmi_request_settime(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ int priority,
+ int retries,
+ unsigned int retry_time_ms)
+{
+ return i_ipmi_request(user,
+ user->intf,
+ addr,
+ msgid,
+ msg,
+ user_msg_data,
+ NULL, NULL,
+ priority,
+ user->intf->my_address,
+ user->intf->my_lun,
+ retries,
+ retry_time_ms);
+}
+
+int ipmi_request_supply_msgs(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ long msgid,
+ struct kernel_ipmi_msg *msg,
+ void *user_msg_data,
+ void *supplied_smi,
+ struct ipmi_recv_msg *supplied_recv,
+ int priority)
+{
+ return i_ipmi_request(user,
+ user->intf,
+ addr,
+ msgid,
+ msg,
+ user_msg_data,
+ supplied_smi,
+ supplied_recv,
+ priority,
+ user->intf->my_address,
+ user->intf->my_lun,
+ -1, 0);
+}
+
+static int ipmb_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ return sprintf(out, "%x\n", intf->my_address);
+}
+
+static int version_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ return sprintf(out, "%d.%d\n",
+ intf->version_major, intf->version_minor);
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ ipmi_smi_t intf = data;
+
+ out += sprintf(out, "sent_invalid_commands: %d\n",
+ intf->sent_invalid_commands);
+ out += sprintf(out, "sent_local_commands: %d\n",
+ intf->sent_local_commands);
+ out += sprintf(out, "handled_local_responses: %d\n",
+ intf->handled_local_responses);
+ out += sprintf(out, "unhandled_local_responses: %d\n",
+ intf->unhandled_local_responses);
+ out += sprintf(out, "sent_ipmb_commands: %d\n",
+ intf->sent_ipmb_commands);
+ out += sprintf(out, "sent_ipmb_command_errs: %d\n",
+ intf->sent_ipmb_command_errs);
+ out += sprintf(out, "retransmitted_ipmb_commands: %d\n",
+ intf->retransmitted_ipmb_commands);
+ out += sprintf(out, "timed_out_ipmb_commands: %d\n",
+ intf->timed_out_ipmb_commands);
+ out += sprintf(out, "timed_out_ipmb_broadcasts: %d\n",
+ intf->timed_out_ipmb_broadcasts);
+ out += sprintf(out, "sent_ipmb_responses: %d\n",
+ intf->sent_ipmb_responses);
+ out += sprintf(out, "handled_ipmb_responses: %d\n",
+ intf->handled_ipmb_responses);
+ out += sprintf(out, "invalid_ipmb_responses: %d\n",
+ intf->invalid_ipmb_responses);
+ out += sprintf(out, "unhandled_ipmb_responses: %d\n",
+ intf->unhandled_ipmb_responses);
+ out += sprintf(out, "sent_lan_commands: %d\n",
+ intf->sent_lan_commands);
+ out += sprintf(out, "sent_lan_command_errs: %d\n",
+ intf->sent_lan_command_errs);
+ out += sprintf(out, "retransmitted_lan_commands: %d\n",
+ intf->retransmitted_lan_commands);
+ out += sprintf(out, "timed_out_lan_commands: %d\n",
+ intf->timed_out_lan_commands);
+ out += sprintf(out, "sent_lan_responses: %d\n",
+ intf->sent_lan_responses);
+ out += sprintf(out, "handled_lan_responses: %d\n",
+ intf->handled_lan_responses);
+ out += sprintf(out, "invalid_lan_responses: %d\n",
+ intf->invalid_lan_responses);
+ out += sprintf(out, "unhandled_lan_responses: %d\n",
+ intf->unhandled_lan_responses);
+ out += sprintf(out, "handled_commands: %d\n",
+ intf->handled_commands);
+ out += sprintf(out, "invalid_commands: %d\n",
+ intf->invalid_commands);
+ out += sprintf(out, "unhandled_commands: %d\n",
+ intf->unhandled_commands);
+ out += sprintf(out, "invalid_events: %d\n",
+ intf->invalid_events);
+ out += sprintf(out, "events: %d\n",
+ intf->events);
+
+ return (out - ((char *) page));
+}
+
+int ipmi_smi_add_proc_entry(ipmi_smi_t smi, char *name,
+ read_proc_t *read_proc, write_proc_t *write_proc,
+ void *data, struct module *owner)
+{
+ struct proc_dir_entry *file;
+ int rv = 0;
+ struct ipmi_proc_entry *entry;
+
+ /* Create a list element. */
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+ entry->name = kmalloc(strlen(name)+1, GFP_KERNEL);
+ if (!entry->name) {
+ kfree(entry);
+ return -ENOMEM;
+ }
+ strcpy(entry->name, name);
+
+ file = create_proc_entry(name, 0, smi->proc_dir);
+ if (!file) {
+ kfree(entry->name);
+ kfree(entry);
+ rv = -ENOMEM;
+ } else {
+ file->nlink = 1;
+ file->data = data;
+ file->read_proc = read_proc;
+ file->write_proc = write_proc;
+ file->owner = owner;
+
+ /* Stick it on the list. */
+ entry->next = smi->proc_entries;
+ smi->proc_entries = entry;
+ }
+
+ return rv;
+}
+
+static int add_proc_entries(ipmi_smi_t smi, int num)
+{
+ int rv = 0;
+
+ sprintf(smi->proc_dir_name, "%d", num);
+ smi->proc_dir = proc_mkdir(smi->proc_dir_name, proc_ipmi_root);
+ if (!smi->proc_dir)
+ rv = -ENOMEM;
+ else {
+ smi->proc_dir->owner = THIS_MODULE;
+ }
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "stats",
+ stat_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "ipmb",
+ ipmb_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ if (rv == 0)
+ rv = ipmi_smi_add_proc_entry(smi, "version",
+ version_file_read_proc, NULL,
+ smi, THIS_MODULE);
+
+ return rv;
+}
+
+static void remove_proc_entries(ipmi_smi_t smi)
+{
+ struct ipmi_proc_entry *entry;
+
+ while (smi->proc_entries) {
+ entry = smi->proc_entries;
+ smi->proc_entries = entry->next;
+
+ remove_proc_entry(entry->name, smi->proc_dir);
+ kfree(entry->name);
+ kfree(entry);
+ }
+ remove_proc_entry(smi->proc_dir_name, proc_ipmi_root);
+}
+
+static int
+send_channel_info_cmd(ipmi_smi_t intf, int chan)
+{
+ struct kernel_ipmi_msg msg;
+ unsigned char data[1];
+ struct ipmi_system_interface_addr si;
+
+ si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si.channel = IPMI_BMC_CHANNEL;
+ si.lun = 0;
+
+ msg.netfn = IPMI_NETFN_APP_REQUEST;
+ msg.cmd = IPMI_GET_CHANNEL_INFO_CMD;
+ msg.data = data;
+ msg.data_len = 1;
+ data[0] = chan;
+ return i_ipmi_request(NULL,
+ intf,
+ (struct ipmi_addr *) &si,
+ 0,
+ &msg,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ -1, 0);
+}
+
+static void
+channel_handler(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ int rv = 0;
+ int chan;
+
+ if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_CHANNEL_INFO_CMD))
+ {
+ /* It's the one we want */
+ if (msg->rsp[2] != 0) {
+ /* Got an error from the channel, just go on. */
+
+ if (msg->rsp[2] == IPMI_INVALID_COMMAND_ERR) {
+ /* If the MC does not support this
+ command, that is legal. We just
+ assume it has one IPMB at channel
+ zero. */
+ intf->channels[0].medium
+ = IPMI_CHANNEL_MEDIUM_IPMB;
+ intf->channels[0].protocol
+ = IPMI_CHANNEL_PROTOCOL_IPMB;
+ rv = -ENOSYS;
+
+ intf->curr_channel = IPMI_MAX_CHANNELS;
+ wake_up(&intf->waitq);
+ goto out;
+ }
+ goto next_channel;
+ }
+ if (msg->rsp_size < 6) {
+ /* Message not big enough, just go on. */
+ goto next_channel;
+ }
+ chan = intf->curr_channel;
+ intf->channels[chan].medium = msg->rsp[4] & 0x7f;
+ intf->channels[chan].protocol = msg->rsp[5] & 0x1f;
+
+ next_channel:
+ intf->curr_channel++;
+ if (intf->curr_channel >= IPMI_MAX_CHANNELS)
+ wake_up(&intf->waitq);
+ else
+ rv = send_channel_info_cmd(intf, intf->curr_channel);
+
+ if (rv) {
+ /* Got an error somehow, just give up. */
+ intf->curr_channel = IPMI_MAX_CHANNELS;
+ wake_up(&intf->waitq);
+
+ printk(KERN_WARNING PFX
+ "Error sending channel information: %d\n",
+ rv);
+ }
+ }
+ out:
+ return;
+}
+
+int ipmi_register_smi(struct ipmi_smi_handlers *handlers,
+ void *send_info,
+ unsigned char version_major,
+ unsigned char version_minor,
+ unsigned char slave_addr,
+ ipmi_smi_t *intf)
+{
+ int i, j;
+ int rv;
+ ipmi_smi_t new_intf;
+ unsigned long flags;
+
+
+ /* Make sure the driver is actually initialized, this handles
+ problems with initialization order. */
+ if (!initialized) {
+ rv = ipmi_init_msghandler();
+ if (rv)
+ return rv;
+ /* The init code doesn't return an error if it was turned
+ off, but it won't initialize. Check that. */
+ if (!initialized)
+ return -ENODEV;
+ }
+
+ new_intf = kmalloc(sizeof(*new_intf), GFP_KERNEL);
+ if (!new_intf)
+ return -ENOMEM;
+ memset(new_intf, 0, sizeof(*new_intf));
+
+ new_intf->proc_dir = NULL;
+
+ rv = -ENOMEM;
+
+ down_write(&interfaces_sem);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] == NULL) {
+ new_intf->intf_num = i;
+ new_intf->version_major = version_major;
+ new_intf->version_minor = version_minor;
+ if (slave_addr == 0)
+ new_intf->my_address = IPMI_BMC_SLAVE_ADDR;
+ else
+ new_intf->my_address = slave_addr;
+ new_intf->my_lun = 2; /* the SMS LUN. */
+ rwlock_init(&(new_intf->users_lock));
+ INIT_LIST_HEAD(&(new_intf->users));
+ new_intf->handlers = handlers;
+ new_intf->send_info = send_info;
+ spin_lock_init(&(new_intf->seq_lock));
+ for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+ new_intf->seq_table[j].inuse = 0;
+ new_intf->seq_table[j].seqid = 0;
+ }
+ new_intf->curr_seq = 0;
+ spin_lock_init(&(new_intf->waiting_msgs_lock));
+ INIT_LIST_HEAD(&(new_intf->waiting_msgs));
+ spin_lock_init(&(new_intf->events_lock));
+ INIT_LIST_HEAD(&(new_intf->waiting_events));
+ new_intf->waiting_events_count = 0;
+ rwlock_init(&(new_intf->cmd_rcvr_lock));
+ init_waitqueue_head(&new_intf->waitq);
+ INIT_LIST_HEAD(&(new_intf->cmd_rcvrs));
+ new_intf->all_cmd_rcvr = NULL;
+
+ spin_lock_init(&(new_intf->counter_lock));
+
+ spin_lock_irqsave(&interfaces_lock, flags);
+ ipmi_interfaces[i] = new_intf;
+ spin_unlock_irqrestore(&interfaces_lock, flags);
+
+ rv = 0;
+ *intf = new_intf;
+ break;
+ }
+ }
+
+ downgrade_write(&interfaces_sem);
+
+ if (rv == 0)
+ rv = add_proc_entries(*intf, i);
+
+ if (rv == 0) {
+ if ((version_major > 1)
+ || ((version_major == 1) && (version_minor >= 5)))
+ {
+ /* Start scanning the channels to see what is
+ available. */
+ (*intf)->null_user_handler = channel_handler;
+ (*intf)->curr_channel = 0;
+ rv = send_channel_info_cmd(*intf, 0);
+ if (rv)
+ goto out;
+
+ /* Wait for the channel info to be read. */
+ up_read(&interfaces_sem);
+ wait_event((*intf)->waitq,
+ ((*intf)->curr_channel>=IPMI_MAX_CHANNELS));
+ down_read(&interfaces_sem);
+
+ if (ipmi_interfaces[i] != new_intf)
+ /* Well, it went away. Just return. */
+ goto out;
+ } else {
+ /* Assume a single IPMB channel at zero. */
+ (*intf)->channels[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
+ (*intf)->channels[0].protocol
+ = IPMI_CHANNEL_PROTOCOL_IPMB;
+ }
+
+ /* Call all the watcher interfaces to tell
+ them that a new interface is available. */
+ call_smi_watchers(i);
+ }
+
+ out:
+ up_read(&interfaces_sem);
+
+ if (rv) {
+ if (new_intf->proc_dir)
+ remove_proc_entries(new_intf);
+ kfree(new_intf);
+ }
+
+ return rv;
+}
+
+static void free_recv_msg_list(struct list_head *q)
+{
+ struct ipmi_recv_msg *msg, *msg2;
+
+ list_for_each_entry_safe(msg, msg2, q, link) {
+ list_del(&msg->link);
+ ipmi_free_recv_msg(msg);
+ }
+}
+
+static void free_cmd_rcvr_list(struct list_head *q)
+{
+ struct cmd_rcvr *rcvr, *rcvr2;
+
+ list_for_each_entry_safe(rcvr, rcvr2, q, link) {
+ list_del(&rcvr->link);
+ kfree(rcvr);
+ }
+}
+
+static void clean_up_interface_data(ipmi_smi_t intf)
+{
+ int i;
+
+ free_recv_msg_list(&(intf->waiting_msgs));
+ free_recv_msg_list(&(intf->waiting_events));
+ free_cmd_rcvr_list(&(intf->cmd_rcvrs));
+
+ for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+ if ((intf->seq_table[i].inuse)
+ && (intf->seq_table[i].recv_msg))
+ {
+ ipmi_free_recv_msg(intf->seq_table[i].recv_msg);
+ }
+ }
+}
+
+int ipmi_unregister_smi(ipmi_smi_t intf)
+{
+ int rv = -ENODEV;
+ int i;
+ struct ipmi_smi_watcher *w;
+ unsigned long flags;
+
+ down_write(&interfaces_sem);
+ if (list_empty(&(intf->users)))
+ {
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ if (ipmi_interfaces[i] == intf) {
+ remove_proc_entries(intf);
+ spin_lock_irqsave(&interfaces_lock, flags);
+ ipmi_interfaces[i] = NULL;
+ clean_up_interface_data(intf);
+ spin_unlock_irqrestore(&interfaces_lock,flags);
+ kfree(intf);
+ rv = 0;
+ goto out_call_watcher;
+ }
+ }
+ } else {
+ rv = -EBUSY;
+ }
+ up_write(&interfaces_sem);
+
+ return rv;
+
+ out_call_watcher:
+ downgrade_write(&interfaces_sem);
+
+ /* Call all the watcher interfaces to tell them that
+ an interface is gone. */
+ down_read(&smi_watchers_sem);
+ list_for_each_entry(w, &smi_watchers, link) {
+ w->smi_gone(i);
+ }
+ up_read(&smi_watchers_sem);
+ up_read(&interfaces_sem);
+ return 0;
+}
+
+static int handle_ipmb_get_msg_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_ipmb_addr ipmb_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ /* This is 11, not 10, because the response must contain a
+ * completion code. */
+ if (msg->rsp_size < 11) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb_addr.slave_addr = msg->rsp[6];
+ ipmb_addr.channel = msg->rsp[3] & 0x0f;
+ ipmb_addr.lun = msg->rsp[7] & 3;
+
+ /* It's a response from a remote entity. Look up the sequence
+ number and handle the response. */
+ if (intf_find_seq(intf,
+ msg->rsp[7] >> 2,
+ msg->rsp[3] & 0x0f,
+ msg->rsp[8],
+ (msg->rsp[4] >> 2) & (~1),
+ (struct ipmi_addr *) &(ipmb_addr),
+ &recv_msg))
+ {
+ /* We were unable to find the sequence number,
+ so just nuke the message. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[9]),
+ msg->rsp_size - 9);
+ /* THe other fields matched, so no need to set them, except
+ for netfn, which needs to be the response that was
+ returned, not the request value. */
+ recv_msg->msg.netfn = msg->rsp[4] >> 2;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 10;
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_ipmb_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ deliver_response(recv_msg);
+
+ return 0;
+}
+
+static int handle_ipmb_get_msg_cmd(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+ unsigned char netfn;
+ unsigned char cmd;
+ ipmi_user_t user = NULL;
+ struct ipmi_ipmb_addr *ipmb_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+ if (msg->rsp_size < 10) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ netfn = msg->rsp[4] >> 2;
+ cmd = msg->rsp[8];
+
+ read_lock(&(intf->cmd_rcvr_lock));
+
+ if (intf->all_cmd_rcvr) {
+ user = intf->all_cmd_rcvr;
+ } else {
+ /* Find the command/netfn. */
+ list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ user = rcvr->user;
+ break;
+ }
+ }
+ }
+ read_unlock(&(intf->cmd_rcvr_lock));
+
+ if (user == NULL) {
+ /* We didn't find a user, deliver an error response. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg->data[1] = IPMI_SEND_MSG_CMD;
+ msg->data[2] = msg->rsp[3];
+ msg->data[3] = msg->rsp[6];
+ msg->data[4] = ((netfn + 1) << 2) | (msg->rsp[7] & 0x3);
+ msg->data[5] = ipmb_checksum(&(msg->data[3]), 2);
+ msg->data[6] = intf->my_address;
+ /* rqseq/lun */
+ msg->data[7] = (msg->rsp[7] & 0xfc) | (msg->rsp[4] & 0x3);
+ msg->data[8] = msg->rsp[8]; /* cmd */
+ msg->data[9] = IPMI_INVALID_CMD_COMPLETION_CODE;
+ msg->data[10] = ipmb_checksum(&(msg->data[6]), 4);
+ msg->data_size = 11;
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ printk("Invalid command:");
+ for (m=0; m<msg->data_size; m++)
+ printk(" %2.2x", msg->data[m]);
+ printk("\n");
+ }
+#endif
+ intf->handlers->sender(intf->send_info, msg, 0);
+
+ rv = -1; /* We used the message, so return the value that
+ causes it to not be freed or queued. */
+ } else {
+ /* Deliver the message to the user. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ } else {
+ /* Extract the source address from the data. */
+ ipmb_addr = (struct ipmi_ipmb_addr *) &recv_msg->addr;
+ ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb_addr->slave_addr = msg->rsp[6];
+ ipmb_addr->lun = msg->rsp[7] & 3;
+ ipmb_addr->channel = msg->rsp[3] & 0xf;
+
+ /* Extract the rest of the message information
+ from the IPMB header.*/
+ recv_msg->user = user;
+ recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+ recv_msg->msgid = msg->rsp[7] >> 2;
+ recv_msg->msg.netfn = msg->rsp[4] >> 2;
+ recv_msg->msg.cmd = msg->rsp[8];
+ recv_msg->msg.data = recv_msg->msg_data;
+
+ /* We chop off 10, not 9 bytes because the checksum
+ at the end also needs to be removed. */
+ recv_msg->msg.data_len = msg->rsp_size - 10;
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[9]),
+ msg->rsp_size - 10);
+ deliver_response(recv_msg);
+ }
+ }
+
+ return rv;
+}
+
+static int handle_lan_get_msg_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_lan_addr lan_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+
+ /* This is 13, not 12, because the response must contain a
+ * completion code. */
+ if (msg->rsp_size < 13) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ lan_addr.addr_type = IPMI_LAN_ADDR_TYPE;
+ lan_addr.session_handle = msg->rsp[4];
+ lan_addr.remote_SWID = msg->rsp[8];
+ lan_addr.local_SWID = msg->rsp[5];
+ lan_addr.channel = msg->rsp[3] & 0x0f;
+ lan_addr.privilege = msg->rsp[3] >> 4;
+ lan_addr.lun = msg->rsp[9] & 3;
+
+ /* It's a response from a remote entity. Look up the sequence
+ number and handle the response. */
+ if (intf_find_seq(intf,
+ msg->rsp[9] >> 2,
+ msg->rsp[3] & 0x0f,
+ msg->rsp[10],
+ (msg->rsp[6] >> 2) & (~1),
+ (struct ipmi_addr *) &(lan_addr),
+ &recv_msg))
+ {
+ /* We were unable to find the sequence number,
+ so just nuke the message. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[11]),
+ msg->rsp_size - 11);
+ /* The other fields matched, so no need to set them, except
+ for netfn, which needs to be the response that was
+ returned, not the request value. */
+ recv_msg->msg.netfn = msg->rsp[6] >> 2;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 12;
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_lan_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ deliver_response(recv_msg);
+
+ return 0;
+}
+
+static int handle_lan_get_msg_cmd(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct cmd_rcvr *rcvr;
+ int rv = 0;
+ unsigned char netfn;
+ unsigned char cmd;
+ ipmi_user_t user = NULL;
+ struct ipmi_lan_addr *lan_addr;
+ struct ipmi_recv_msg *recv_msg;
+ unsigned long flags;
+
+ if (msg->rsp_size < 12) {
+ /* Message not big enough, just ignore it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the response, just ignore it. */
+ return 0;
+ }
+
+ netfn = msg->rsp[6] >> 2;
+ cmd = msg->rsp[10];
+
+ read_lock(&(intf->cmd_rcvr_lock));
+
+ if (intf->all_cmd_rcvr) {
+ user = intf->all_cmd_rcvr;
+ } else {
+ /* Find the command/netfn. */
+ list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+ if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+ user = rcvr->user;
+ break;
+ }
+ }
+ }
+ read_unlock(&(intf->cmd_rcvr_lock));
+
+ if (user == NULL) {
+ /* We didn't find a user, deliver an error response. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ rv = 0; /* Don't do anything with these messages, just
+ allow them to be freed. */
+ } else {
+ /* Deliver the message to the user. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_commands++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ } else {
+ /* Extract the source address from the data. */
+ lan_addr = (struct ipmi_lan_addr *) &recv_msg->addr;
+ lan_addr->addr_type = IPMI_LAN_ADDR_TYPE;
+ lan_addr->session_handle = msg->rsp[4];
+ lan_addr->remote_SWID = msg->rsp[8];
+ lan_addr->local_SWID = msg->rsp[5];
+ lan_addr->lun = msg->rsp[9] & 3;
+ lan_addr->channel = msg->rsp[3] & 0xf;
+ lan_addr->privilege = msg->rsp[3] >> 4;
+
+ /* Extract the rest of the message information
+ from the IPMB header.*/
+ recv_msg->user = user;
+ recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+ recv_msg->msgid = msg->rsp[9] >> 2;
+ recv_msg->msg.netfn = msg->rsp[6] >> 2;
+ recv_msg->msg.cmd = msg->rsp[10];
+ recv_msg->msg.data = recv_msg->msg_data;
+
+ /* We chop off 12, not 11 bytes because the checksum
+ at the end also needs to be removed. */
+ recv_msg->msg.data_len = msg->rsp_size - 12;
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[11]),
+ msg->rsp_size - 12);
+ deliver_response(recv_msg);
+ }
+ }
+
+ return rv;
+}
+
+static void copy_event_into_recv_msg(struct ipmi_recv_msg *recv_msg,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_system_interface_addr *smi_addr;
+
+ recv_msg->msgid = 0;
+ smi_addr = (struct ipmi_system_interface_addr *) &(recv_msg->addr);
+ smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr->channel = IPMI_BMC_CHANNEL;
+ smi_addr->lun = msg->rsp[0] & 3;
+ recv_msg->recv_type = IPMI_ASYNC_EVENT_RECV_TYPE;
+ recv_msg->msg.netfn = msg->rsp[0] >> 2;
+ recv_msg->msg.cmd = msg->rsp[1];
+ memcpy(recv_msg->msg_data, &(msg->rsp[3]), msg->rsp_size - 3);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 3;
+}
+
+/* This will be called with the intf->users_lock read-locked, so no need
+ to do that here. */
+static int handle_read_event_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_recv_msg *recv_msg, *recv_msg2;
+ struct list_head msgs;
+ ipmi_user_t user;
+ int rv = 0;
+ int deliver_count = 0;
+ unsigned long flags;
+
+ if (msg->rsp_size < 19) {
+ /* Message is too small to be an IPMB event. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->invalid_events++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ return 0;
+ }
+
+ if (msg->rsp[2] != 0) {
+ /* An error getting the event, just ignore it. */
+ return 0;
+ }
+
+ INIT_LIST_HEAD(&msgs);
+
+ spin_lock_irqsave(&(intf->events_lock), flags);
+
+ spin_lock(&intf->counter_lock);
+ intf->events++;
+ spin_unlock(&intf->counter_lock);
+
+ /* Allocate and fill in one message for every user that is getting
+ events. */
+ list_for_each_entry(user, &(intf->users), link) {
+ if (! user->gets_events)
+ continue;
+
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+ list_del(&recv_msg->link);
+ ipmi_free_recv_msg(recv_msg);
+ }
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ goto out;
+ }
+
+ deliver_count++;
+
+ copy_event_into_recv_msg(recv_msg, msg);
+ recv_msg->user = user;
+ list_add_tail(&(recv_msg->link), &msgs);
+ }
+
+ if (deliver_count) {
+ /* Now deliver all the messages. */
+ list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+ list_del(&recv_msg->link);
+ deliver_response(recv_msg);
+ }
+ } else if (intf->waiting_events_count < MAX_EVENTS_IN_QUEUE) {
+ /* No one to receive the message, put it in queue if there's
+ not already too many things in the queue. */
+ recv_msg = ipmi_alloc_recv_msg();
+ if (! recv_msg) {
+ /* We couldn't allocate memory for the
+ message, so requeue it for handling
+ later. */
+ rv = 1;
+ goto out;
+ }
+
+ copy_event_into_recv_msg(recv_msg, msg);
+ list_add_tail(&(recv_msg->link), &(intf->waiting_events));
+ } else {
+ /* There's too many things in the queue, discard this
+ message. */
+ printk(KERN_WARNING PFX "Event queue full, discarding an"
+ " incoming event\n");
+ }
+
+ out:
+ spin_unlock_irqrestore(&(intf->events_lock), flags);
+
+ return rv;
+}
+
+static int handle_bmc_rsp(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ struct ipmi_recv_msg *recv_msg;
+ int found = 0;
+ struct ipmi_user *user;
+ unsigned long flags;
+
+ recv_msg = (struct ipmi_recv_msg *) msg->user_data;
+
+ /* Make sure the user still exists. */
+ list_for_each_entry(user, &(intf->users), link) {
+ if (user == recv_msg->user) {
+ /* Found it, so we can deliver it */
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Special handling for NULL users. */
+ if (!recv_msg->user && intf->null_user_handler){
+ intf->null_user_handler(intf, msg);
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ }else{
+ /* The user for the message went away, so give up. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->unhandled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ }
+ ipmi_free_recv_msg(recv_msg);
+ } else {
+ struct ipmi_system_interface_addr *smi_addr;
+
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ intf->handled_local_responses++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ recv_msg->msgid = msg->msgid;
+ smi_addr = ((struct ipmi_system_interface_addr *)
+ &(recv_msg->addr));
+ smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr->channel = IPMI_BMC_CHANNEL;
+ smi_addr->lun = msg->rsp[0] & 3;
+ recv_msg->msg.netfn = msg->rsp[0] >> 2;
+ recv_msg->msg.cmd = msg->rsp[1];
+ memcpy(recv_msg->msg_data,
+ &(msg->rsp[2]),
+ msg->rsp_size - 2);
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = msg->rsp_size - 2;
+ deliver_response(recv_msg);
+ }
+
+ return 0;
+}
+
+/* Handle a new message. Return 1 if the message should be requeued,
+ 0 if the message should be freed, or -1 if the message should not
+ be freed or requeued. */
+static int handle_new_recv_msg(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ int requeue;
+ int chan;
+
+#ifdef DEBUG_MSGING
+ int m;
+ printk("Recv:");
+ for (m=0; m<msg->rsp_size; m++)
+ printk(" %2.2x", msg->rsp[m]);
+ printk("\n");
+#endif
+ if (msg->rsp_size < 2) {
+ /* Message is too small to be correct. */
+ printk(KERN_WARNING PFX "BMC returned to small a message"
+ " for netfn %x cmd %x, got %d bytes\n",
+ (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size);
+
+ /* Generate an error response for the message. */
+ msg->rsp[0] = msg->data[0] | (1 << 2);
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+ msg->rsp_size = 3;
+ } else if (((msg->rsp[0] >> 2) != ((msg->data[0] >> 2) | 1))/* Netfn */
+ || (msg->rsp[1] != msg->data[1])) /* Command */
+ {
+ /* The response is not even marginally correct. */
+ printk(KERN_WARNING PFX "BMC returned incorrect response,"
+ " expected netfn %x cmd %x, got netfn %x cmd %x\n",
+ (msg->data[0] >> 2) | 1, msg->data[1],
+ msg->rsp[0] >> 2, msg->rsp[1]);
+
+ /* Generate an error response for the message. */
+ msg->rsp[0] = msg->data[0] | (1 << 2);
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+ msg->rsp_size = 3;
+ }
+
+ if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_SEND_MSG_CMD)
+ && (msg->user_data != NULL))
+ {
+ /* It's a response to a response we sent. For this we
+ deliver a send message response to the user. */
+ struct ipmi_recv_msg *recv_msg = msg->user_data;
+
+ requeue = 0;
+ if (msg->rsp_size < 2)
+ /* Message is too small to be correct. */
+ goto out;
+
+ chan = msg->data[2] & 0x0f;
+ if (chan >= IPMI_MAX_CHANNELS)
+ /* Invalid channel number */
+ goto out;
+
+ if (recv_msg) {
+ recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE;
+ recv_msg->msg.data = recv_msg->msg_data;
+ recv_msg->msg.data_len = 1;
+ recv_msg->msg_data[0] = msg->rsp[2];
+ deliver_response(recv_msg);
+ }
+ } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_GET_MSG_CMD))
+ {
+ /* It's from the receive queue. */
+ chan = msg->rsp[3] & 0xf;
+ if (chan >= IPMI_MAX_CHANNELS) {
+ /* Invalid channel number */
+ requeue = 0;
+ goto out;
+ }
+
+ switch (intf->channels[chan].medium) {
+ case IPMI_CHANNEL_MEDIUM_IPMB:
+ if (msg->rsp[4] & 0x04) {
+ /* It's a response, so find the
+ requesting message and send it up. */
+ requeue = handle_ipmb_get_msg_rsp(intf, msg);
+ } else {
+ /* It's a command to the SMS from some other
+ entity. Handle that. */
+ requeue = handle_ipmb_get_msg_cmd(intf, msg);
+ }
+ break;
+
+ case IPMI_CHANNEL_MEDIUM_8023LAN:
+ case IPMI_CHANNEL_MEDIUM_ASYNC:
+ if (msg->rsp[6] & 0x04) {
+ /* It's a response, so find the
+ requesting message and send it up. */
+ requeue = handle_lan_get_msg_rsp(intf, msg);
+ } else {
+ /* It's a command to the SMS from some other
+ entity. Handle that. */
+ requeue = handle_lan_get_msg_cmd(intf, msg);
+ }
+ break;
+
+ default:
+ /* We don't handle the channel type, so just
+ * free the message. */
+ requeue = 0;
+ }
+
+ } else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+ && (msg->rsp[1] == IPMI_READ_EVENT_MSG_BUFFER_CMD))
+ {
+ /* It's an asyncronous event. */
+ requeue = handle_read_event_rsp(intf, msg);
+ } else {
+ /* It's a response from the local BMC. */
+ requeue = handle_bmc_rsp(intf, msg);
+ }
+
+ out:
+ return requeue;
+}
+
+/* Handle a new message from the lower layer. */
+void ipmi_smi_msg_received(ipmi_smi_t intf,
+ struct ipmi_smi_msg *msg)
+{
+ unsigned long flags;
+ int rv;
+
+
+ /* Lock the user lock so the user can't go away while we are
+ working on it. */
+ read_lock(&(intf->users_lock));
+
+ if ((msg->data_size >= 2)
+ && (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2))
+ && (msg->data[1] == IPMI_SEND_MSG_CMD)
+ && (msg->user_data == NULL)) {
+ /* This is the local response to a command send, start
+ the timer for these. The user_data will not be
+ NULL if this is a response send, and we will let
+ response sends just go through. */
+
+ /* Check for errors, if we get certain errors (ones
+ that mean basically we can try again later), we
+ ignore them and start the timer. Otherwise we
+ report the error immediately. */
+ if ((msg->rsp_size >= 3) && (msg->rsp[2] != 0)
+ && (msg->rsp[2] != IPMI_NODE_BUSY_ERR)
+ && (msg->rsp[2] != IPMI_LOST_ARBITRATION_ERR))
+ {
+ int chan = msg->rsp[3] & 0xf;
+
+ /* Got an error sending the message, handle it. */
+ spin_lock_irqsave(&intf->counter_lock, flags);
+ if (chan >= IPMI_MAX_CHANNELS)
+ ; /* This shouldn't happen */
+ else if ((intf->channels[chan].medium
+ == IPMI_CHANNEL_MEDIUM_8023LAN)
+ || (intf->channels[chan].medium
+ == IPMI_CHANNEL_MEDIUM_ASYNC))
+ intf->sent_lan_command_errs++;
+ else
+ intf->sent_ipmb_command_errs++;
+ spin_unlock_irqrestore(&intf->counter_lock, flags);
+ intf_err_seq(intf, msg->msgid, msg->rsp[2]);
+ } else {
+ /* The message was sent, start the timer. */
+ intf_start_seq_timer(intf, msg->msgid);
+ }
+
+ ipmi_free_smi_msg(msg);
+ goto out_unlock;
+ }
+
+ /* To preserve message order, if the list is not empty, we
+ tack this message onto the end of the list. */
+ spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+ if (!list_empty(&(intf->waiting_msgs))) {
+ list_add_tail(&(msg->link), &(intf->waiting_msgs));
+ spin_unlock(&(intf->waiting_msgs_lock));
+ goto out_unlock;
+ }
+ spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+
+ rv = handle_new_recv_msg(intf, msg);
+ if (rv > 0) {
+ /* Could not handle the message now, just add it to a
+ list to handle later. */
+ spin_lock(&(intf->waiting_msgs_lock));
+ list_add_tail(&(msg->link), &(intf->waiting_msgs));
+ spin_unlock(&(intf->waiting_msgs_lock));
+ } else if (rv == 0) {
+ ipmi_free_smi_msg(msg);
+ }
+
+ out_unlock:
+ read_unlock(&(intf->users_lock));
+}
+
+void ipmi_smi_watchdog_pretimeout(ipmi_smi_t intf)
+{
+ ipmi_user_t user;
+
+ read_lock(&(intf->users_lock));
+ list_for_each_entry(user, &(intf->users), link) {
+ if (! user->handler->ipmi_watchdog_pretimeout)
+ continue;
+
+ user->handler->ipmi_watchdog_pretimeout(user->handler_data);
+ }
+ read_unlock(&(intf->users_lock));
+}
+
+static void
+handle_msg_timeout(struct ipmi_recv_msg *msg)
+{
+ msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+ msg->msg_data[0] = IPMI_TIMEOUT_COMPLETION_CODE;
+ msg->msg.netfn |= 1; /* Convert to a response. */
+ msg->msg.data_len = 1;
+ msg->msg.data = msg->msg_data;
+ deliver_response(msg);
+}
+
+static void
+send_from_recv_msg(ipmi_smi_t intf, struct ipmi_recv_msg *recv_msg,
+ struct ipmi_smi_msg *smi_msg,
+ unsigned char seq, long seqid)
+{
+ if (!smi_msg)
+ smi_msg = ipmi_alloc_smi_msg();
+ if (!smi_msg)
+ /* If we can't allocate the message, then just return, we
+ get 4 retries, so this should be ok. */
+ return;
+
+ memcpy(smi_msg->data, recv_msg->msg.data, recv_msg->msg.data_len);
+ smi_msg->data_size = recv_msg->msg.data_len;
+ smi_msg->msgid = STORE_SEQ_IN_MSGID(seq, seqid);
+
+ /* Send the new message. We send with a zero priority. It
+ timed out, I doubt time is that critical now, and high
+ priority messages are really only for messages to the local
+ MC, which don't get resent. */
+ intf->handlers->sender(intf->send_info, smi_msg, 0);
+
+#ifdef DEBUG_MSGING
+ {
+ int m;
+ printk("Resend: ");
+ for (m=0; m<smi_msg->data_size; m++)
+ printk(" %2.2x", smi_msg->data[m]);
+ printk("\n");
+ }
+#endif
+}
+
+static void
+ipmi_timeout_handler(long timeout_period)
+{
+ ipmi_smi_t intf;
+ struct list_head timeouts;
+ struct ipmi_recv_msg *msg, *msg2;
+ struct ipmi_smi_msg *smi_msg, *smi_msg2;
+ unsigned long flags;
+ int i, j;
+
+ INIT_LIST_HEAD(&timeouts);
+
+ spin_lock(&interfaces_lock);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ read_lock(&(intf->users_lock));
+
+ /* See if any waiting messages need to be processed. */
+ spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+ list_for_each_entry_safe(smi_msg, smi_msg2, &(intf->waiting_msgs), link) {
+ if (! handle_new_recv_msg(intf, smi_msg)) {
+ list_del(&smi_msg->link);
+ ipmi_free_smi_msg(smi_msg);
+ } else {
+ /* To preserve message order, quit if we
+ can't handle a message. */
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+
+ /* Go through the seq table and find any messages that
+ have timed out, putting them in the timeouts
+ list. */
+ spin_lock_irqsave(&(intf->seq_lock), flags);
+ for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+ struct seq_table *ent = &(intf->seq_table[j]);
+ if (!ent->inuse)
+ continue;
+
+ ent->timeout -= timeout_period;
+ if (ent->timeout > 0)
+ continue;
+
+ if (ent->retries_left == 0) {
+ /* The message has used all its retries. */
+ ent->inuse = 0;
+ msg = ent->recv_msg;
+ list_add_tail(&(msg->link), &timeouts);
+ spin_lock(&intf->counter_lock);
+ if (ent->broadcast)
+ intf->timed_out_ipmb_broadcasts++;
+ else if (ent->recv_msg->addr.addr_type
+ == IPMI_LAN_ADDR_TYPE)
+ intf->timed_out_lan_commands++;
+ else
+ intf->timed_out_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+ } else {
+ /* More retries, send again. */
+
+ /* Start with the max timer, set to normal
+ timer after the message is sent. */
+ ent->timeout = MAX_MSG_TIMEOUT;
+ ent->retries_left--;
+ send_from_recv_msg(intf, ent->recv_msg, NULL,
+ j, ent->seqid);
+ spin_lock(&intf->counter_lock);
+ if (ent->recv_msg->addr.addr_type
+ == IPMI_LAN_ADDR_TYPE)
+ intf->retransmitted_lan_commands++;
+ else
+ intf->retransmitted_ipmb_commands++;
+ spin_unlock(&intf->counter_lock);
+ }
+ }
+ spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+ list_for_each_entry_safe(msg, msg2, &timeouts, link) {
+ handle_msg_timeout(msg);
+ }
+
+ read_unlock(&(intf->users_lock));
+ }
+ spin_unlock(&interfaces_lock);
+}
+
+static void ipmi_request_event(void)
+{
+ ipmi_smi_t intf;
+ int i;
+
+ spin_lock(&interfaces_lock);
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ intf->handlers->request_events(intf->send_info);
+ }
+ spin_unlock(&interfaces_lock);
+}
+
+static struct timer_list ipmi_timer;
+
+/* Call every ~100 ms. */
+#define IPMI_TIMEOUT_TIME 100
+
+/* How many jiffies does it take to get to the timeout time. */
+#define IPMI_TIMEOUT_JIFFIES ((IPMI_TIMEOUT_TIME * HZ) / 1000)
+
+/* Request events from the queue every second (this is the number of
+ IPMI_TIMEOUT_TIMES between event requests). Hopefully, in the
+ future, IPMI will add a way to know immediately if an event is in
+ the queue and this silliness can go away. */
+#define IPMI_REQUEST_EV_TIME (1000 / (IPMI_TIMEOUT_TIME))
+
+static volatile int stop_operation = 0;
+static volatile int timer_stopped = 0;
+static unsigned int ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+
+static void ipmi_timeout(unsigned long data)
+{
+ if (stop_operation) {
+ timer_stopped = 1;
+ return;
+ }
+
+ ticks_to_req_ev--;
+ if (ticks_to_req_ev == 0) {
+ ipmi_request_event();
+ ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+ }
+
+ ipmi_timeout_handler(IPMI_TIMEOUT_TIME);
+
+ ipmi_timer.expires += IPMI_TIMEOUT_JIFFIES;
+ add_timer(&ipmi_timer);
+}
+
+
+static atomic_t smi_msg_inuse_count = ATOMIC_INIT(0);
+static atomic_t recv_msg_inuse_count = ATOMIC_INIT(0);
+
+/* FIXME - convert these to slabs. */
+static void free_smi_msg(struct ipmi_smi_msg *msg)
+{
+ atomic_dec(&smi_msg_inuse_count);
+ kfree(msg);
+}
+
+struct ipmi_smi_msg *ipmi_alloc_smi_msg(void)
+{
+ struct ipmi_smi_msg *rv;
+ rv = kmalloc(sizeof(struct ipmi_smi_msg), GFP_ATOMIC);
+ if (rv) {
+ rv->done = free_smi_msg;
+ rv->user_data = NULL;
+ atomic_inc(&smi_msg_inuse_count);
+ }
+ return rv;
+}
+
+static void free_recv_msg(struct ipmi_recv_msg *msg)
+{
+ atomic_dec(&recv_msg_inuse_count);
+ kfree(msg);
+}
+
+struct ipmi_recv_msg *ipmi_alloc_recv_msg(void)
+{
+ struct ipmi_recv_msg *rv;
+
+ rv = kmalloc(sizeof(struct ipmi_recv_msg), GFP_ATOMIC);
+ if (rv) {
+ rv->done = free_recv_msg;
+ atomic_inc(&recv_msg_inuse_count);
+ }
+ return rv;
+}
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+
+static void dummy_smi_done_handler(struct ipmi_smi_msg *msg)
+{
+}
+
+static void dummy_recv_done_handler(struct ipmi_recv_msg *msg)
+{
+}
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+static void event_receiver_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ if ((msg->rsp[0] == (IPMI_NETFN_SENSOR_EVENT_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_EVENT_RECEIVER_CMD)
+ && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+ {
+ /* A get event receiver command, save it. */
+ intf->event_receiver = msg->rsp[3];
+ intf->event_receiver_lun = msg->rsp[4] & 0x3;
+ }
+}
+
+static void device_id_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+ if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+ && (msg->rsp[1] == IPMI_GET_DEVICE_ID_CMD)
+ && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+ {
+ /* A get device id command, save if we are an event
+ receiver or generator. */
+ intf->local_sel_device = (msg->rsp[8] >> 2) & 1;
+ intf->local_event_generator = (msg->rsp[8] >> 5) & 1;
+ }
+}
+#endif
+
+static void send_panic_events(char *str)
+{
+ struct kernel_ipmi_msg msg;
+ ipmi_smi_t intf;
+ unsigned char data[16];
+ int i;
+ struct ipmi_system_interface_addr *si;
+ struct ipmi_addr addr;
+ struct ipmi_smi_msg smi_msg;
+ struct ipmi_recv_msg recv_msg;
+
+ si = (struct ipmi_system_interface_addr *) &addr;
+ si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si->channel = IPMI_BMC_CHANNEL;
+ si->lun = 0;
+
+ /* Fill in an event telling that we have failed. */
+ msg.netfn = 0x04; /* Sensor or Event. */
+ msg.cmd = 2; /* Platform event command. */
+ msg.data = data;
+ msg.data_len = 8;
+ data[0] = 0x21; /* Kernel generator ID, IPMI table 5-4 */
+ data[1] = 0x03; /* This is for IPMI 1.0. */
+ data[2] = 0x20; /* OS Critical Stop, IPMI table 36-3 */
+ data[4] = 0x6f; /* Sensor specific, IPMI table 36-1 */
+ data[5] = 0xa1; /* Runtime stop OEM bytes 2 & 3. */
+
+ /* Put a few breadcrumbs in. Hopefully later we can add more things
+ to make the panic events more useful. */
+ if (str) {
+ data[3] = str[0];
+ data[6] = str[1];
+ data[7] = str[2];
+ }
+
+ smi_msg.done = dummy_smi_done_handler;
+ recv_msg.done = dummy_recv_done_handler;
+
+ /* For every registered interface, send the event. */
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ /* Send the event announcing the panic. */
+ intf->handlers->set_run_to_completion(intf->send_info, 1);
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* Don't retry, and don't wait. */
+ }
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+ /* On every interface, dump a bunch of OEM event holding the
+ string. */
+ if (!str)
+ return;
+
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ char *p = str;
+ struct ipmi_ipmb_addr *ipmb;
+ int j;
+
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ /* First job here is to figure out where to send the
+ OEM events. There's no way in IPMI to send OEM
+ events using an event send command, so we have to
+ find the SEL to put them in and stick them in
+ there. */
+
+ /* Get capabilities from the get device id. */
+ intf->local_sel_device = 0;
+ intf->local_event_generator = 0;
+ intf->event_receiver = 0;
+
+ /* Request the device info from the local MC. */
+ msg.netfn = IPMI_NETFN_APP_REQUEST;
+ msg.cmd = IPMI_GET_DEVICE_ID_CMD;
+ msg.data = NULL;
+ msg.data_len = 0;
+ intf->null_user_handler = device_id_fetcher;
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* Don't retry, and don't wait. */
+
+ if (intf->local_event_generator) {
+ /* Request the event receiver from the local MC. */
+ msg.netfn = IPMI_NETFN_SENSOR_EVENT_REQUEST;
+ msg.cmd = IPMI_GET_EVENT_RECEIVER_CMD;
+ msg.data = NULL;
+ msg.data_len = 0;
+ intf->null_user_handler = event_receiver_fetcher;
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* no retry, and no wait. */
+ }
+ intf->null_user_handler = NULL;
+
+ /* Validate the event receiver. The low bit must not
+ be 1 (it must be a valid IPMB address), it cannot
+ be zero, and it must not be my address. */
+ if (((intf->event_receiver & 1) == 0)
+ && (intf->event_receiver != 0)
+ && (intf->event_receiver != intf->my_address))
+ {
+ /* The event receiver is valid, send an IPMB
+ message. */
+ ipmb = (struct ipmi_ipmb_addr *) &addr;
+ ipmb->addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb->channel = 0; /* FIXME - is this right? */
+ ipmb->lun = intf->event_receiver_lun;
+ ipmb->slave_addr = intf->event_receiver;
+ } else if (intf->local_sel_device) {
+ /* The event receiver was not valid (or was
+ me), but I am an SEL device, just dump it
+ in my SEL. */
+ si = (struct ipmi_system_interface_addr *) &addr;
+ si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ si->channel = IPMI_BMC_CHANNEL;
+ si->lun = 0;
+ } else
+ continue; /* No where to send the event. */
+
+
+ msg.netfn = IPMI_NETFN_STORAGE_REQUEST; /* Storage. */
+ msg.cmd = IPMI_ADD_SEL_ENTRY_CMD;
+ msg.data = data;
+ msg.data_len = 16;
+
+ j = 0;
+ while (*p) {
+ int size = strlen(p);
+
+ if (size > 11)
+ size = 11;
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0xf0; /* OEM event without timestamp. */
+ data[3] = intf->my_address;
+ data[4] = j++; /* sequence # */
+ /* Always give 11 bytes, so strncpy will fill
+ it with zeroes for me. */
+ strncpy(data+5, p, 11);
+ p += size;
+
+ i_ipmi_request(NULL,
+ intf,
+ &addr,
+ 0,
+ &msg,
+ NULL,
+ &smi_msg,
+ &recv_msg,
+ 0,
+ intf->my_address,
+ intf->my_lun,
+ 0, 1); /* no retry, and no wait. */
+ }
+ }
+#endif /* CONFIG_IPMI_PANIC_STRING */
+}
+#endif /* CONFIG_IPMI_PANIC_EVENT */
+
+static int has_paniced = 0;
+
+static int panic_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ int i;
+ ipmi_smi_t intf;
+
+ if (has_paniced)
+ return NOTIFY_DONE;
+ has_paniced = 1;
+
+ /* For every registered interface, set it to run to completion. */
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ intf = ipmi_interfaces[i];
+ if (intf == NULL)
+ continue;
+
+ intf->handlers->set_run_to_completion(intf->send_info, 1);
+ }
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+ send_panic_events(ptr);
+#endif
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_block = {
+ .notifier_call = panic_event,
+ .next = NULL,
+ .priority = 200 /* priority: INT_MAX >= x >= 0 */
+};
+
+static int ipmi_init_msghandler(void)
+{
+ int i;
+
+ if (initialized)
+ return 0;
+
+ printk(KERN_INFO "ipmi message handler version "
+ IPMI_MSGHANDLER_VERSION "\n");
+
+ for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+ ipmi_interfaces[i] = NULL;
+ }
+
+ proc_ipmi_root = proc_mkdir("ipmi", NULL);
+ if (!proc_ipmi_root) {
+ printk(KERN_ERR PFX "Unable to create IPMI proc dir");
+ return -ENOMEM;
+ }
+
+ proc_ipmi_root->owner = THIS_MODULE;
+
+ init_timer(&ipmi_timer);
+ ipmi_timer.data = 0;
+ ipmi_timer.function = ipmi_timeout;
+ ipmi_timer.expires = jiffies + IPMI_TIMEOUT_JIFFIES;
+ add_timer(&ipmi_timer);
+
+ notifier_chain_register(&panic_notifier_list, &panic_block);
+
+ initialized = 1;
+
+ return 0;
+}
+
+static __init int ipmi_init_msghandler_mod(void)
+{
+ ipmi_init_msghandler();
+ return 0;
+}
+
+static __exit void cleanup_ipmi(void)
+{
+ int count;
+
+ if (!initialized)
+ return;
+
+ notifier_chain_unregister(&panic_notifier_list, &panic_block);
+
+ /* This can't be called if any interfaces exist, so no worry about
+ shutting down the interfaces. */
+
+ /* Tell the timer to stop, then wait for it to stop. This avoids
+ problems with race conditions removing the timer here. */
+ stop_operation = 1;
+ while (!timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ remove_proc_entry(proc_ipmi_root->name, &proc_root);
+
+ initialized = 0;
+
+ /* Check for buffer leaks. */
+ count = atomic_read(&smi_msg_inuse_count);
+ if (count != 0)
+ printk(KERN_WARNING PFX "SMI message count %d at exit\n",
+ count);
+ count = atomic_read(&recv_msg_inuse_count);
+ if (count != 0)
+ printk(KERN_WARNING PFX "recv message count %d at exit\n",
+ count);
+}
+module_exit(cleanup_ipmi);
+
+module_init(ipmi_init_msghandler_mod);
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(ipmi_create_user);
+EXPORT_SYMBOL(ipmi_destroy_user);
+EXPORT_SYMBOL(ipmi_get_version);
+EXPORT_SYMBOL(ipmi_request_settime);
+EXPORT_SYMBOL(ipmi_request_supply_msgs);
+EXPORT_SYMBOL(ipmi_register_smi);
+EXPORT_SYMBOL(ipmi_unregister_smi);
+EXPORT_SYMBOL(ipmi_register_for_cmd);
+EXPORT_SYMBOL(ipmi_unregister_for_cmd);
+EXPORT_SYMBOL(ipmi_smi_msg_received);
+EXPORT_SYMBOL(ipmi_smi_watchdog_pretimeout);
+EXPORT_SYMBOL(ipmi_alloc_smi_msg);
+EXPORT_SYMBOL(ipmi_addr_length);
+EXPORT_SYMBOL(ipmi_validate_addr);
+EXPORT_SYMBOL(ipmi_set_gets_events);
+EXPORT_SYMBOL(ipmi_smi_watcher_register);
+EXPORT_SYMBOL(ipmi_smi_watcher_unregister);
+EXPORT_SYMBOL(ipmi_set_my_address);
+EXPORT_SYMBOL(ipmi_get_my_address);
+EXPORT_SYMBOL(ipmi_set_my_LUN);
+EXPORT_SYMBOL(ipmi_get_my_LUN);
+EXPORT_SYMBOL(ipmi_smi_add_proc_entry);
+EXPORT_SYMBOL(ipmi_user_set_run_to_completion);
diff --git a/drivers/char/ipmi/ipmi_poweroff.c b/drivers/char/ipmi/ipmi_poweroff.c
new file mode 100644
index 000000000000..cb5cdc6f14bf
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_poweroff.c
@@ -0,0 +1,549 @@
+/*
+ * ipmi_poweroff.c
+ *
+ * MontaVista IPMI Poweroff extension to sys_reboot
+ *
+ * Author: MontaVista Software, Inc.
+ * Steven Dake <sdake@mvista.com>
+ * Corey Minyard <cminyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002,2004 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <asm/semaphore.h>
+#include <linux/kdev_t.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+
+#define PFX "IPMI poweroff: "
+#define IPMI_POWEROFF_VERSION "v33"
+
+/* Where to we insert our poweroff function? */
+extern void (*pm_power_off)(void);
+
+/* Stuff from the get device id command. */
+static unsigned int mfg_id;
+static unsigned int prod_id;
+static unsigned char capabilities;
+
+/* We use our own messages for this operation, we don't let the system
+ allocate them, since we may be in a panic situation. The whole
+ thing is single-threaded, anyway, so multiple messages are not
+ required. */
+static void dummy_smi_free(struct ipmi_smi_msg *msg)
+{
+}
+static void dummy_recv_free(struct ipmi_recv_msg *msg)
+{
+}
+static struct ipmi_smi_msg halt_smi_msg =
+{
+ .done = dummy_smi_free
+};
+static struct ipmi_recv_msg halt_recv_msg =
+{
+ .done = dummy_recv_free
+};
+
+
+/*
+ * Code to send a message and wait for the reponse.
+ */
+
+static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data)
+{
+ struct semaphore *sem = recv_msg->user_msg_data;
+
+ if (sem)
+ up(sem);
+}
+
+static struct ipmi_user_hndl ipmi_poweroff_handler =
+{
+ .ipmi_recv_hndl = receive_handler
+};
+
+
+static int ipmi_request_wait_for_response(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ struct kernel_ipmi_msg *send_msg)
+{
+ int rv;
+ struct semaphore sem;
+
+ sema_init (&sem, 0);
+
+ rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &sem,
+ &halt_smi_msg, &halt_recv_msg, 0);
+ if (rv)
+ return rv;
+
+ down (&sem);
+
+ return halt_recv_msg.msg.data[0];
+}
+
+/* We are in run-to-completion mode, no semaphore is desired. */
+static int ipmi_request_in_rc_mode(ipmi_user_t user,
+ struct ipmi_addr *addr,
+ struct kernel_ipmi_msg *send_msg)
+{
+ int rv;
+
+ rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL,
+ &halt_smi_msg, &halt_recv_msg, 0);
+ if (rv)
+ return rv;
+
+ return halt_recv_msg.msg.data[0];
+}
+
+/*
+ * ATCA Support
+ */
+
+#define IPMI_NETFN_ATCA 0x2c
+#define IPMI_ATCA_SET_POWER_CMD 0x11
+#define IPMI_ATCA_GET_ADDR_INFO_CMD 0x01
+#define IPMI_PICMG_ID 0
+
+static int ipmi_atca_detect (ipmi_user_t user)
+{
+ struct ipmi_system_interface_addr smi_addr;
+ struct kernel_ipmi_msg send_msg;
+ int rv;
+ unsigned char data[1];
+
+ /*
+ * Configure IPMI address for local access
+ */
+ smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr.channel = IPMI_BMC_CHANNEL;
+ smi_addr.lun = 0;
+
+ /*
+ * Use get address info to check and see if we are ATCA
+ */
+ send_msg.netfn = IPMI_NETFN_ATCA;
+ send_msg.cmd = IPMI_ATCA_GET_ADDR_INFO_CMD;
+ data[0] = IPMI_PICMG_ID;
+ send_msg.data = data;
+ send_msg.data_len = sizeof(data);
+ rv = ipmi_request_wait_for_response(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ return !rv;
+}
+
+static void ipmi_poweroff_atca (ipmi_user_t user)
+{
+ struct ipmi_system_interface_addr smi_addr;
+ struct kernel_ipmi_msg send_msg;
+ int rv;
+ unsigned char data[4];
+
+ /*
+ * Configure IPMI address for local access
+ */
+ smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr.channel = IPMI_BMC_CHANNEL;
+ smi_addr.lun = 0;
+
+ printk(KERN_INFO PFX "Powering down via ATCA power command\n");
+
+ /*
+ * Power down
+ */
+ send_msg.netfn = IPMI_NETFN_ATCA;
+ send_msg.cmd = IPMI_ATCA_SET_POWER_CMD;
+ data[0] = IPMI_PICMG_ID;
+ data[1] = 0; /* FRU id */
+ data[2] = 0; /* Power Level */
+ data[3] = 0; /* Don't change saved presets */
+ send_msg.data = data;
+ send_msg.data_len = sizeof (data);
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv) {
+ printk(KERN_ERR PFX "Unable to send ATCA powerdown message,"
+ " IPMI error 0x%x\n", rv);
+ goto out;
+ }
+
+ out:
+ return;
+}
+
+/*
+ * CPI1 Support
+ */
+
+#define IPMI_NETFN_OEM_1 0xf8
+#define OEM_GRP_CMD_SET_RESET_STATE 0x84
+#define OEM_GRP_CMD_SET_POWER_STATE 0x82
+#define IPMI_NETFN_OEM_8 0xf8
+#define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL 0x80
+#define OEM_GRP_CMD_GET_SLOT_GA 0xa3
+#define IPMI_NETFN_SENSOR_EVT 0x10
+#define IPMI_CMD_GET_EVENT_RECEIVER 0x01
+
+#define IPMI_CPI1_PRODUCT_ID 0x000157
+#define IPMI_CPI1_MANUFACTURER_ID 0x0108
+
+static int ipmi_cpi1_detect (ipmi_user_t user)
+{
+ return ((mfg_id == IPMI_CPI1_MANUFACTURER_ID)
+ && (prod_id == IPMI_CPI1_PRODUCT_ID));
+}
+
+static void ipmi_poweroff_cpi1 (ipmi_user_t user)
+{
+ struct ipmi_system_interface_addr smi_addr;
+ struct ipmi_ipmb_addr ipmb_addr;
+ struct kernel_ipmi_msg send_msg;
+ int rv;
+ unsigned char data[1];
+ int slot;
+ unsigned char hotswap_ipmb;
+ unsigned char aer_addr;
+ unsigned char aer_lun;
+
+ /*
+ * Configure IPMI address for local access
+ */
+ smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr.channel = IPMI_BMC_CHANNEL;
+ smi_addr.lun = 0;
+
+ printk(KERN_INFO PFX "Powering down via CPI1 power command\n");
+
+ /*
+ * Get IPMI ipmb address
+ */
+ send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
+ send_msg.cmd = OEM_GRP_CMD_GET_SLOT_GA;
+ send_msg.data = NULL;
+ send_msg.data_len = 0;
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv)
+ goto out;
+ slot = halt_recv_msg.msg.data[1];
+ hotswap_ipmb = (slot > 9) ? (0xb0 + 2 * slot) : (0xae + 2 * slot);
+
+ /*
+ * Get active event receiver
+ */
+ send_msg.netfn = IPMI_NETFN_SENSOR_EVT >> 2;
+ send_msg.cmd = IPMI_CMD_GET_EVENT_RECEIVER;
+ send_msg.data = NULL;
+ send_msg.data_len = 0;
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv)
+ goto out;
+ aer_addr = halt_recv_msg.msg.data[1];
+ aer_lun = halt_recv_msg.msg.data[2];
+
+ /*
+ * Setup IPMB address target instead of local target
+ */
+ ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
+ ipmb_addr.channel = 0;
+ ipmb_addr.slave_addr = aer_addr;
+ ipmb_addr.lun = aer_lun;
+
+ /*
+ * Send request hotswap control to remove blade from dpv
+ */
+ send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
+ send_msg.cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL;
+ send_msg.data = &hotswap_ipmb;
+ send_msg.data_len = 1;
+ ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &ipmb_addr,
+ &send_msg);
+
+ /*
+ * Set reset asserted
+ */
+ send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
+ send_msg.cmd = OEM_GRP_CMD_SET_RESET_STATE;
+ send_msg.data = data;
+ data[0] = 1; /* Reset asserted state */
+ send_msg.data_len = 1;
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv)
+ goto out;
+
+ /*
+ * Power down
+ */
+ send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
+ send_msg.cmd = OEM_GRP_CMD_SET_POWER_STATE;
+ send_msg.data = data;
+ data[0] = 1; /* Power down state */
+ send_msg.data_len = 1;
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv)
+ goto out;
+
+ out:
+ return;
+}
+
+/*
+ * Standard chassis support
+ */
+
+#define IPMI_NETFN_CHASSIS_REQUEST 0
+#define IPMI_CHASSIS_CONTROL_CMD 0x02
+
+static int ipmi_chassis_detect (ipmi_user_t user)
+{
+ /* Chassis support, use it. */
+ return (capabilities & 0x80);
+}
+
+static void ipmi_poweroff_chassis (ipmi_user_t user)
+{
+ struct ipmi_system_interface_addr smi_addr;
+ struct kernel_ipmi_msg send_msg;
+ int rv;
+ unsigned char data[1];
+
+ /*
+ * Configure IPMI address for local access
+ */
+ smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr.channel = IPMI_BMC_CHANNEL;
+ smi_addr.lun = 0;
+
+ printk(KERN_INFO PFX "Powering down via IPMI chassis control command\n");
+
+ /*
+ * Power down
+ */
+ send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST;
+ send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD;
+ data[0] = 0; /* Power down */
+ send_msg.data = data;
+ send_msg.data_len = sizeof(data);
+ rv = ipmi_request_in_rc_mode(user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv) {
+ printk(KERN_ERR PFX "Unable to send chassis powerdown message,"
+ " IPMI error 0x%x\n", rv);
+ goto out;
+ }
+
+ out:
+ return;
+}
+
+
+/* Table of possible power off functions. */
+struct poweroff_function {
+ char *platform_type;
+ int (*detect)(ipmi_user_t user);
+ void (*poweroff_func)(ipmi_user_t user);
+};
+
+static struct poweroff_function poweroff_functions[] = {
+ { .platform_type = "ATCA",
+ .detect = ipmi_atca_detect,
+ .poweroff_func = ipmi_poweroff_atca },
+ { .platform_type = "CPI1",
+ .detect = ipmi_cpi1_detect,
+ .poweroff_func = ipmi_poweroff_cpi1 },
+ /* Chassis should generally be last, other things should override
+ it. */
+ { .platform_type = "chassis",
+ .detect = ipmi_chassis_detect,
+ .poweroff_func = ipmi_poweroff_chassis },
+};
+#define NUM_PO_FUNCS (sizeof(poweroff_functions) \
+ / sizeof(struct poweroff_function))
+
+
+/* Our local state. */
+static int ready = 0;
+static ipmi_user_t ipmi_user;
+static void (*specific_poweroff_func)(ipmi_user_t user) = NULL;
+
+/* Holds the old poweroff function so we can restore it on removal. */
+static void (*old_poweroff_func)(void);
+
+
+/* Called on a powerdown request. */
+static void ipmi_poweroff_function (void)
+{
+ if (!ready)
+ return;
+
+ /* Use run-to-completion mode, since interrupts may be off. */
+ ipmi_user_set_run_to_completion(ipmi_user, 1);
+ specific_poweroff_func(ipmi_user);
+ ipmi_user_set_run_to_completion(ipmi_user, 0);
+}
+
+/* Wait for an IPMI interface to be installed, the first one installed
+ will be grabbed by this code and used to perform the powerdown. */
+static void ipmi_po_new_smi(int if_num)
+{
+ struct ipmi_system_interface_addr smi_addr;
+ struct kernel_ipmi_msg send_msg;
+ int rv;
+ int i;
+
+ if (ready)
+ return;
+
+ rv = ipmi_create_user(if_num, &ipmi_poweroff_handler, NULL, &ipmi_user);
+ if (rv) {
+ printk(KERN_ERR PFX "could not create IPMI user, error %d\n",
+ rv);
+ return;
+ }
+
+ /*
+ * Do a get device ide and store some results, since this is
+ * used by several functions.
+ */
+ smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ smi_addr.channel = IPMI_BMC_CHANNEL;
+ smi_addr.lun = 0;
+
+ send_msg.netfn = IPMI_NETFN_APP_REQUEST;
+ send_msg.cmd = IPMI_GET_DEVICE_ID_CMD;
+ send_msg.data = NULL;
+ send_msg.data_len = 0;
+ rv = ipmi_request_wait_for_response(ipmi_user,
+ (struct ipmi_addr *) &smi_addr,
+ &send_msg);
+ if (rv) {
+ printk(KERN_ERR PFX "Unable to send IPMI get device id info,"
+ " IPMI error 0x%x\n", rv);
+ goto out_err;
+ }
+
+ if (halt_recv_msg.msg.data_len < 12) {
+ printk(KERN_ERR PFX "(chassis) IPMI get device id info too,"
+ " short, was %d bytes, needed %d bytes\n",
+ halt_recv_msg.msg.data_len, 12);
+ goto out_err;
+ }
+
+ mfg_id = (halt_recv_msg.msg.data[7]
+ | (halt_recv_msg.msg.data[8] << 8)
+ | (halt_recv_msg.msg.data[9] << 16));
+ prod_id = (halt_recv_msg.msg.data[10]
+ | (halt_recv_msg.msg.data[11] << 8));
+ capabilities = halt_recv_msg.msg.data[6];
+
+
+ /* Scan for a poweroff method */
+ for (i=0; i<NUM_PO_FUNCS; i++) {
+ if (poweroff_functions[i].detect(ipmi_user))
+ goto found;
+ }
+
+ out_err:
+ printk(KERN_ERR PFX "Unable to find a poweroff function that"
+ " will work, giving up\n");
+ ipmi_destroy_user(ipmi_user);
+ return;
+
+ found:
+ printk(KERN_INFO PFX "Found a %s style poweroff function\n",
+ poweroff_functions[i].platform_type);
+ specific_poweroff_func = poweroff_functions[i].poweroff_func;
+ old_poweroff_func = pm_power_off;
+ pm_power_off = ipmi_poweroff_function;
+ ready = 1;
+}
+
+static void ipmi_po_smi_gone(int if_num)
+{
+ /* This can never be called, because once poweroff driver is
+ registered, the interface can't go away until the power
+ driver is unregistered. */
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+ .owner = THIS_MODULE,
+ .new_smi = ipmi_po_new_smi,
+ .smi_gone = ipmi_po_smi_gone
+};
+
+
+/*
+ * Startup and shutdown functions.
+ */
+static int ipmi_poweroff_init (void)
+{
+ int rv;
+
+ printk ("Copyright (C) 2004 MontaVista Software -"
+ " IPMI Powerdown via sys_reboot version "
+ IPMI_POWEROFF_VERSION ".\n");
+
+ rv = ipmi_smi_watcher_register(&smi_watcher);
+ if (rv)
+ printk(KERN_ERR PFX "Unable to register SMI watcher: %d\n", rv);
+
+ return rv;
+}
+
+#ifdef MODULE
+static __exit void ipmi_poweroff_cleanup(void)
+{
+ int rv;
+
+ ipmi_smi_watcher_unregister(&smi_watcher);
+
+ if (ready) {
+ rv = ipmi_destroy_user(ipmi_user);
+ if (rv)
+ printk(KERN_ERR PFX "could not cleanup the IPMI"
+ " user: 0x%x\n", rv);
+ pm_power_off = old_poweroff_func;
+ }
+}
+module_exit(ipmi_poweroff_cleanup);
+#endif
+
+module_init(ipmi_poweroff_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c
new file mode 100644
index 000000000000..29de259a981e
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_intf.c
@@ -0,0 +1,2359 @@
+/*
+ * ipmi_si.c
+ *
+ * The interface to the IPMI driver for the system interfaces (KCS, SMIC,
+ * BT).
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This file holds the "policy" for the interface to the SMI state
+ * machine. It does the configuration, handles timers and interrupts,
+ * and drives the real SMI state machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <asm/irq.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtime.h>
+# if defined(schedule_next_int)
+/* Old high-res timer code, do translations. */
+# define get_arch_cycles(a) quick_update_jiffies_sub(a)
+# define arch_cycles_per_jiffy cycles_per_jiffies
+# endif
+static inline void add_usec_to_timer(struct timer_list *t, long v)
+{
+ t->sub_expires += nsec_to_arch_cycle(v * 1000);
+ while (t->sub_expires >= arch_cycles_per_jiffy)
+ {
+ t->expires++;
+ t->sub_expires -= arch_cycles_per_jiffy;
+ }
+}
+#endif
+#include <linux/interrupt.h>
+#include <linux/rcupdate.h>
+#include <linux/ipmi_smi.h>
+#include <asm/io.h>
+#include "ipmi_si_sm.h"
+#include <linux/init.h>
+
+#define IPMI_SI_VERSION "v33"
+
+/* Measure times between events in the driver. */
+#undef DEBUG_TIMING
+
+/* Call every 10 ms. */
+#define SI_TIMEOUT_TIME_USEC 10000
+#define SI_USEC_PER_JIFFY (1000000/HZ)
+#define SI_TIMEOUT_JIFFIES (SI_TIMEOUT_TIME_USEC/SI_USEC_PER_JIFFY)
+#define SI_SHORT_TIMEOUT_USEC 250 /* .25ms when the SM request a
+ short timeout */
+
+enum si_intf_state {
+ SI_NORMAL,
+ SI_GETTING_FLAGS,
+ SI_GETTING_EVENTS,
+ SI_CLEARING_FLAGS,
+ SI_CLEARING_FLAGS_THEN_SET_IRQ,
+ SI_GETTING_MESSAGES,
+ SI_ENABLE_INTERRUPTS1,
+ SI_ENABLE_INTERRUPTS2
+ /* FIXME - add watchdog stuff. */
+};
+
+enum si_type {
+ SI_KCS, SI_SMIC, SI_BT
+};
+
+struct smi_info
+{
+ ipmi_smi_t intf;
+ struct si_sm_data *si_sm;
+ struct si_sm_handlers *handlers;
+ enum si_type si_type;
+ spinlock_t si_lock;
+ spinlock_t msg_lock;
+ struct list_head xmit_msgs;
+ struct list_head hp_xmit_msgs;
+ struct ipmi_smi_msg *curr_msg;
+ enum si_intf_state si_state;
+
+ /* Used to handle the various types of I/O that can occur with
+ IPMI */
+ struct si_sm_io io;
+ int (*io_setup)(struct smi_info *info);
+ void (*io_cleanup)(struct smi_info *info);
+ int (*irq_setup)(struct smi_info *info);
+ void (*irq_cleanup)(struct smi_info *info);
+ unsigned int io_size;
+
+ /* Flags from the last GET_MSG_FLAGS command, used when an ATTN
+ is set to hold the flags until we are done handling everything
+ from the flags. */
+#define RECEIVE_MSG_AVAIL 0x01
+#define EVENT_MSG_BUFFER_FULL 0x02
+#define WDT_PRE_TIMEOUT_INT 0x08
+ unsigned char msg_flags;
+
+ /* If set to true, this will request events the next time the
+ state machine is idle. */
+ atomic_t req_events;
+
+ /* If true, run the state machine to completion on every send
+ call. Generally used after a panic to make sure stuff goes
+ out. */
+ int run_to_completion;
+
+ /* The I/O port of an SI interface. */
+ int port;
+
+ /* The space between start addresses of the two ports. For
+ instance, if the first port is 0xca2 and the spacing is 4, then
+ the second port is 0xca6. */
+ unsigned int spacing;
+
+ /* zero if no irq; */
+ int irq;
+
+ /* The timer for this si. */
+ struct timer_list si_timer;
+
+ /* The time (in jiffies) the last timeout occurred at. */
+ unsigned long last_timeout_jiffies;
+
+ /* Used to gracefully stop the timer without race conditions. */
+ volatile int stop_operation;
+ volatile int timer_stopped;
+
+ /* The driver will disable interrupts when it gets into a
+ situation where it cannot handle messages due to lack of
+ memory. Once that situation clears up, it will re-enable
+ interrupts. */
+ int interrupt_disabled;
+
+ unsigned char ipmi_si_dev_rev;
+ unsigned char ipmi_si_fw_rev_major;
+ unsigned char ipmi_si_fw_rev_minor;
+ unsigned char ipmi_version_major;
+ unsigned char ipmi_version_minor;
+
+ /* Slave address, could be reported from DMI. */
+ unsigned char slave_addr;
+
+ /* Counters and things for the proc filesystem. */
+ spinlock_t count_lock;
+ unsigned long short_timeouts;
+ unsigned long long_timeouts;
+ unsigned long timeout_restarts;
+ unsigned long idles;
+ unsigned long interrupts;
+ unsigned long attentions;
+ unsigned long flag_fetches;
+ unsigned long hosed_count;
+ unsigned long complete_transactions;
+ unsigned long events;
+ unsigned long watchdog_pretimeouts;
+ unsigned long incoming_messages;
+};
+
+static void si_restart_short_timer(struct smi_info *smi_info);
+
+static void deliver_recv_msg(struct smi_info *smi_info,
+ struct ipmi_smi_msg *msg)
+{
+ /* Deliver the message to the upper layer with the lock
+ released. */
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_msg_received(smi_info->intf, msg);
+ spin_lock(&(smi_info->si_lock));
+}
+
+static void return_hosed_msg(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg = smi_info->curr_msg;
+
+ /* Make it a reponse */
+ msg->rsp[0] = msg->data[0] | 4;
+ msg->rsp[1] = msg->data[1];
+ msg->rsp[2] = 0xFF; /* Unknown error. */
+ msg->rsp_size = 3;
+
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+}
+
+static enum si_sm_result start_next_msg(struct smi_info *smi_info)
+{
+ int rv;
+ struct list_head *entry = NULL;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ /* No need to save flags, we aleady have interrupts off and we
+ already hold the SMI lock. */
+ spin_lock(&(smi_info->msg_lock));
+
+ /* Pick the high priority queue first. */
+ if (! list_empty(&(smi_info->hp_xmit_msgs))) {
+ entry = smi_info->hp_xmit_msgs.next;
+ } else if (! list_empty(&(smi_info->xmit_msgs))) {
+ entry = smi_info->xmit_msgs.next;
+ }
+
+ if (!entry) {
+ smi_info->curr_msg = NULL;
+ rv = SI_SM_IDLE;
+ } else {
+ int err;
+
+ list_del(entry);
+ smi_info->curr_msg = list_entry(entry,
+ struct ipmi_smi_msg,
+ link);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Start2: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ err = smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ if (err) {
+ return_hosed_msg(smi_info);
+ }
+
+ rv = SI_SM_CALL_WITHOUT_DELAY;
+ }
+ spin_unlock(&(smi_info->msg_lock));
+
+ return rv;
+}
+
+static void start_enable_irq(struct smi_info *smi_info)
+{
+ unsigned char msg[2];
+
+ /* If we are enabling interrupts, we have to tell the
+ BMC to use them. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS1;
+}
+
+static void start_clear_flags(struct smi_info *smi_info)
+{
+ unsigned char msg[3];
+
+ /* Make sure the watchdog pre-timeout flag is not set at startup. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD;
+ msg[2] = WDT_PRE_TIMEOUT_INT;
+
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_CLEARING_FLAGS;
+}
+
+/* When we have a situtaion where we run out of memory and cannot
+ allocate messages, we just leave them in the BMC and run the system
+ polled until we can allocate some memory. Once we have some
+ memory, we will re-enable the interrupt. */
+static inline void disable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (!smi_info->interrupt_disabled)) {
+ disable_irq_nosync(smi_info->irq);
+ smi_info->interrupt_disabled = 1;
+ }
+}
+
+static inline void enable_si_irq(struct smi_info *smi_info)
+{
+ if ((smi_info->irq) && (smi_info->interrupt_disabled)) {
+ enable_irq(smi_info->irq);
+ smi_info->interrupt_disabled = 0;
+ }
+}
+
+static void handle_flags(struct smi_info *smi_info)
+{
+ if (smi_info->msg_flags & WDT_PRE_TIMEOUT_INT) {
+ /* Watchdog pre-timeout */
+ spin_lock(&smi_info->count_lock);
+ smi_info->watchdog_pretimeouts++;
+ spin_unlock(&smi_info->count_lock);
+
+ start_clear_flags(smi_info);
+ smi_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT;
+ spin_unlock(&(smi_info->si_lock));
+ ipmi_smi_watchdog_pretimeout(smi_info->intf);
+ spin_lock(&(smi_info->si_lock));
+ } else if (smi_info->msg_flags & RECEIVE_MSG_AVAIL) {
+ /* Messages available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_MESSAGES;
+ } else if (smi_info->msg_flags & EVENT_MSG_BUFFER_FULL) {
+ /* Events available. */
+ smi_info->curr_msg = ipmi_alloc_smi_msg();
+ if (!smi_info->curr_msg) {
+ disable_si_irq(smi_info);
+ smi_info->si_state = SI_NORMAL;
+ return;
+ }
+ enable_si_irq(smi_info);
+
+ smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD;
+ smi_info->curr_msg->data_size = 2;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm,
+ smi_info->curr_msg->data,
+ smi_info->curr_msg->data_size);
+ smi_info->si_state = SI_GETTING_EVENTS;
+ } else {
+ smi_info->si_state = SI_NORMAL;
+ }
+}
+
+static void handle_transaction_done(struct smi_info *smi_info)
+{
+ struct ipmi_smi_msg *msg;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+
+ do_gettimeofday(&t);
+ printk("**Done: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ switch (smi_info->si_state) {
+ case SI_NORMAL:
+ if (!smi_info->curr_msg)
+ break;
+
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ deliver_recv_msg(smi_info, msg);
+ break;
+
+ case SI_GETTING_FLAGS:
+ {
+ unsigned char msg[4];
+ unsigned int len;
+
+ /* We got the flags from the SMI, now handle them. */
+ len = smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ /* Error fetching flags, just give up for
+ now. */
+ smi_info->si_state = SI_NORMAL;
+ } else if (len < 4) {
+ /* Hmm, no flags. That's technically illegal, but
+ don't use uninitialized data. */
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ smi_info->msg_flags = msg[3];
+ handle_flags(smi_info);
+ }
+ break;
+ }
+
+ case SI_CLEARING_FLAGS:
+ case SI_CLEARING_FLAGS_THEN_SET_IRQ:
+ {
+ unsigned char msg[3];
+
+ /* We cleared the flags. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 3);
+ if (msg[2] != 0) {
+ /* Error clearing flags */
+ printk(KERN_WARNING
+ "ipmi_si: Error clearing flags: %2.2x\n",
+ msg[2]);
+ }
+ if (smi_info->si_state == SI_CLEARING_FLAGS_THEN_SET_IRQ)
+ start_enable_irq(smi_info);
+ else
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+
+ case SI_GETTING_EVENTS:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the event flag. */
+ smi_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->events++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_GETTING_MESSAGES:
+ {
+ smi_info->curr_msg->rsp_size
+ = smi_info->handlers->get_result(
+ smi_info->si_sm,
+ smi_info->curr_msg->rsp,
+ IPMI_MAX_MSG_LENGTH);
+
+ /* Do this here becase deliver_recv_msg() releases the
+ lock, and a new message can be put in during the
+ time the lock is released. */
+ msg = smi_info->curr_msg;
+ smi_info->curr_msg = NULL;
+ if (msg->rsp[2] != 0) {
+ /* Error getting event, probably done. */
+ msg->done(msg);
+
+ /* Take off the msg flag. */
+ smi_info->msg_flags &= ~RECEIVE_MSG_AVAIL;
+ handle_flags(smi_info);
+ } else {
+ spin_lock(&smi_info->count_lock);
+ smi_info->incoming_messages++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do this before we deliver the message
+ because delivering the message releases the
+ lock and something else can mess with the
+ state. */
+ handle_flags(smi_info);
+
+ deliver_recv_msg(smi_info, msg);
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS1:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed get, using polled mode.\n");
+ smi_info->si_state = SI_NORMAL;
+ } else {
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD;
+ msg[2] = msg[3] | 1; /* enable msg queue int */
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 3);
+ smi_info->si_state = SI_ENABLE_INTERRUPTS2;
+ }
+ break;
+ }
+
+ case SI_ENABLE_INTERRUPTS2:
+ {
+ unsigned char msg[4];
+
+ /* We got the flags from the SMI, now handle them. */
+ smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+ if (msg[2] != 0) {
+ printk(KERN_WARNING
+ "ipmi_si: Could not enable interrupts"
+ ", failed set, using polled mode.\n");
+ }
+ smi_info->si_state = SI_NORMAL;
+ break;
+ }
+ }
+}
+
+/* Called on timeouts and events. Timeouts should pass the elapsed
+ time, interrupts should pass in zero. */
+static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
+ int time)
+{
+ enum si_sm_result si_sm_result;
+
+ restart:
+ /* There used to be a loop here that waited a little while
+ (around 25us) before giving up. That turned out to be
+ pointless, the minimum delays I was seeing were in the 300us
+ range, which is far too long to wait in an interrupt. So
+ we just run until the state machine tells us something
+ happened or it needs a delay. */
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, time);
+ time = 0;
+ while (si_sm_result == SI_SM_CALL_WITHOUT_DELAY)
+ {
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ if (si_sm_result == SI_SM_TRANSACTION_COMPLETE)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->complete_transactions++;
+ spin_unlock(&smi_info->count_lock);
+
+ handle_transaction_done(smi_info);
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+ else if (si_sm_result == SI_SM_HOSED)
+ {
+ spin_lock(&smi_info->count_lock);
+ smi_info->hosed_count++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Do the before return_hosed_msg, because that
+ releases the lock. */
+ smi_info->si_state = SI_NORMAL;
+ if (smi_info->curr_msg != NULL) {
+ /* If we were handling a user message, format
+ a response to send to the upper layer to
+ tell it about the error. */
+ return_hosed_msg(smi_info);
+ }
+ si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ }
+
+ /* We prefer handling attn over new messages. */
+ if (si_sm_result == SI_SM_ATTN)
+ {
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->attentions++;
+ spin_unlock(&smi_info->count_lock);
+
+ /* Got a attn, send down a get message flags to see
+ what's causing it. It would be better to handle
+ this in the upper layer, but due to the way
+ interrupts work with the SMI, that's not really
+ possible. */
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ /* If we are currently idle, try to start the next message. */
+ if (si_sm_result == SI_SM_IDLE) {
+ spin_lock(&smi_info->count_lock);
+ smi_info->idles++;
+ spin_unlock(&smi_info->count_lock);
+
+ si_sm_result = start_next_msg(smi_info);
+ if (si_sm_result != SI_SM_IDLE)
+ goto restart;
+ }
+
+ if ((si_sm_result == SI_SM_IDLE)
+ && (atomic_read(&smi_info->req_events)))
+ {
+ /* We are idle and the upper layer requested that I fetch
+ events, so do so. */
+ unsigned char msg[2];
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->flag_fetches++;
+ spin_unlock(&smi_info->count_lock);
+
+ atomic_set(&smi_info->req_events, 0);
+ msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+ msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+ smi_info->handlers->start_transaction(
+ smi_info->si_sm, msg, 2);
+ smi_info->si_state = SI_GETTING_FLAGS;
+ goto restart;
+ }
+
+ return si_sm_result;
+}
+
+static void sender(void *send_info,
+ struct ipmi_smi_msg *msg,
+ int priority)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->msg_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Enqueue: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+
+ if (smi_info->run_to_completion) {
+ /* If we are running to completion, then throw it in
+ the list and run transactions until everything is
+ clear. Priority doesn't matter here. */
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+
+ /* We have to release the msg lock and claim the smi
+ lock in this case, because of race conditions. */
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return;
+ } else {
+ if (priority > 0) {
+ list_add_tail(&(msg->link), &(smi_info->hp_xmit_msgs));
+ } else {
+ list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+ }
+ }
+ spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+ if ((smi_info->si_state == SI_NORMAL)
+ && (smi_info->curr_msg == NULL))
+ {
+ start_next_msg(smi_info);
+ si_restart_short_timer(smi_info);
+ }
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void set_run_to_completion(void *send_info, int i_run_to_completion)
+{
+ struct smi_info *smi_info = send_info;
+ enum si_sm_result result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ smi_info->run_to_completion = i_run_to_completion;
+ if (i_run_to_completion) {
+ result = smi_event_handler(smi_info, 0);
+ while (result != SI_SM_IDLE) {
+ udelay(SI_SHORT_TIMEOUT_USEC);
+ result = smi_event_handler(smi_info,
+ SI_SHORT_TIMEOUT_USEC);
+ }
+ }
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void poll(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ smi_event_handler(smi_info, 0);
+}
+
+static void request_events(void *send_info)
+{
+ struct smi_info *smi_info = send_info;
+
+ atomic_set(&smi_info->req_events, 1);
+}
+
+static int initialized = 0;
+
+/* Must be called with interrupts off and with the si_lock held. */
+static void si_restart_short_timer(struct smi_info *smi_info)
+{
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ unsigned long flags;
+ unsigned long jiffies_now;
+
+ if (del_timer(&(smi_info->si_timer))) {
+ /* If we don't delete the timer, then it will go off
+ immediately, anyway. So we only process if we
+ actually delete the timer. */
+
+ /* We already have irqsave on, so no need for it
+ here. */
+ read_lock(&xtime_lock);
+ jiffies_now = jiffies;
+ smi_info->si_timer.expires = jiffies_now;
+ smi_info->si_timer.sub_expires = get_arch_cycles(jiffies_now);
+
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+
+ add_timer(&(smi_info->si_timer));
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->timeout_restarts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ }
+#endif
+}
+
+static void smi_timeout(unsigned long data)
+{
+ struct smi_info *smi_info = (struct smi_info *) data;
+ enum si_sm_result smi_result;
+ unsigned long flags;
+ unsigned long jiffies_now;
+ unsigned long time_diff;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ if (smi_info->stop_operation) {
+ smi_info->timer_stopped = 1;
+ return;
+ }
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Timer: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ jiffies_now = jiffies;
+ time_diff = ((jiffies_now - smi_info->last_timeout_jiffies)
+ * SI_USEC_PER_JIFFY);
+ smi_result = smi_event_handler(smi_info, time_diff);
+
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ smi_info->last_timeout_jiffies = jiffies_now;
+
+ if ((smi_info->irq) && (! smi_info->interrupt_disabled)) {
+ /* Running with interrupts, only do long timeouts. */
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ goto do_add_timer;
+ }
+
+ /* If the state machine asks for a short delay, then shorten
+ the timer timeout. */
+ if (smi_result == SI_SM_CALL_WITH_DELAY) {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->short_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ read_lock(&xtime_lock);
+ smi_info->si_timer.expires = jiffies;
+ smi_info->si_timer.sub_expires
+ = get_arch_cycles(smi_info->si_timer.expires);
+ read_unlock(&xtime_lock);
+ add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+#else
+ smi_info->si_timer.expires = jiffies + 1;
+#endif
+ } else {
+ spin_lock_irqsave(&smi_info->count_lock, flags);
+ smi_info->long_timeouts++;
+ spin_unlock_irqrestore(&smi_info->count_lock, flags);
+ smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+#if defined(CONFIG_HIGH_RES_TIMERS)
+ smi_info->si_timer.sub_expires = 0;
+#endif
+ }
+
+ do_add_timer:
+ add_timer(&(smi_info->si_timer));
+}
+
+static irqreturn_t si_irq_handler(int irq, void *data, struct pt_regs *regs)
+{
+ struct smi_info *smi_info = data;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->interrupts++;
+ spin_unlock(&smi_info->count_lock);
+
+ if (smi_info->stop_operation)
+ goto out;
+
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**Interrupt: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ smi_event_handler(smi_info, 0);
+ out:
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+ return IRQ_HANDLED;
+}
+
+static struct ipmi_smi_handlers handlers =
+{
+ .owner = THIS_MODULE,
+ .sender = sender,
+ .request_events = request_events,
+ .set_run_to_completion = set_run_to_completion,
+ .poll = poll,
+};
+
+/* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
+ a default IO port, and 1 ACPI/SPMI address. That sets SI_MAX_DRIVERS */
+
+#define SI_MAX_PARMS 4
+#define SI_MAX_DRIVERS ((SI_MAX_PARMS * 2) + 2)
+static struct smi_info *smi_infos[SI_MAX_DRIVERS] =
+{ NULL, NULL, NULL, NULL };
+
+#define DEVICE_NAME "ipmi_si"
+
+#define DEFAULT_KCS_IO_PORT 0xca2
+#define DEFAULT_SMIC_IO_PORT 0xca9
+#define DEFAULT_BT_IO_PORT 0xe4
+#define DEFAULT_REGSPACING 1
+
+static int si_trydefaults = 1;
+static char *si_type[SI_MAX_PARMS];
+#define MAX_SI_TYPE_STR 30
+static char si_type_str[MAX_SI_TYPE_STR];
+static unsigned long addrs[SI_MAX_PARMS];
+static int num_addrs;
+static unsigned int ports[SI_MAX_PARMS];
+static int num_ports;
+static int irqs[SI_MAX_PARMS];
+static int num_irqs;
+static int regspacings[SI_MAX_PARMS];
+static int num_regspacings = 0;
+static int regsizes[SI_MAX_PARMS];
+static int num_regsizes = 0;
+static int regshifts[SI_MAX_PARMS];
+static int num_regshifts = 0;
+static int slave_addrs[SI_MAX_PARMS];
+static int num_slave_addrs = 0;
+
+
+module_param_named(trydefaults, si_trydefaults, bool, 0);
+MODULE_PARM_DESC(trydefaults, "Setting this to 'false' will disable the"
+ " default scan of the KCS and SMIC interface at the standard"
+ " address");
+module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0);
+MODULE_PARM_DESC(type, "Defines the type of each interface, each"
+ " interface separated by commas. The types are 'kcs',"
+ " 'smic', and 'bt'. For example si_type=kcs,bt will set"
+ " the first interface to kcs and the second to bt");
+module_param_array(addrs, long, &num_addrs, 0);
+MODULE_PARM_DESC(addrs, "Sets the memory address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is in memory. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(ports, int, &num_ports, 0);
+MODULE_PARM_DESC(ports, "Sets the port address of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " is a port. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(irqs, int, &num_irqs, 0);
+MODULE_PARM_DESC(irqs, "Sets the interrupt of each interface, the"
+ " addresses separated by commas. Only use if an interface"
+ " has an interrupt. Otherwise, set it to zero or leave"
+ " it blank.");
+module_param_array(regspacings, int, &num_regspacings, 0);
+MODULE_PARM_DESC(regspacings, "The number of bytes between the start address"
+ " and each successive register used by the interface. For"
+ " instance, if the start address is 0xca2 and the spacing"
+ " is 2, then the second address is at 0xca4. Defaults"
+ " to 1.");
+module_param_array(regsizes, int, &num_regsizes, 0);
+MODULE_PARM_DESC(regsizes, "The size of the specific IPMI register in bytes."
+ " This should generally be 1, 2, 4, or 8 for an 8-bit,"
+ " 16-bit, 32-bit, or 64-bit register. Use this if you"
+ " the 8-bit IPMI register has to be read from a larger"
+ " register.");
+module_param_array(regshifts, int, &num_regshifts, 0);
+MODULE_PARM_DESC(regshifts, "The amount to shift the data read from the."
+ " IPMI register, in bits. For instance, if the data"
+ " is read from a 32-bit word and the IPMI data is in"
+ " bit 8-15, then the shift would be 8");
+module_param_array(slave_addrs, int, &num_slave_addrs, 0);
+MODULE_PARM_DESC(slave_addrs, "Set the default IPMB slave address for"
+ " the controller. Normally this is 0x20, but can be"
+ " overridden by this parm. This is an array indexed"
+ " by interface number.");
+
+
+#define IPMI_MEM_ADDR_SPACE 1
+#define IPMI_IO_ADDR_SPACE 2
+
+#if defined(CONFIG_ACPI_INTERPRETER) || defined(CONFIG_X86) || defined(CONFIG_PCI)
+static int is_new_interface(int intf, u8 addr_space, unsigned long base_addr)
+{
+ int i;
+
+ for (i = 0; i < SI_MAX_PARMS; ++i) {
+ /* Don't check our address. */
+ if (i == intf)
+ continue;
+ if (si_type[i] != NULL) {
+ if ((addr_space == IPMI_MEM_ADDR_SPACE &&
+ base_addr == addrs[i]) ||
+ (addr_space == IPMI_IO_ADDR_SPACE &&
+ base_addr == ports[i]))
+ return 0;
+ }
+ else
+ break;
+ }
+
+ return 1;
+}
+#endif
+
+static int std_irq_setup(struct smi_info *info)
+{
+ int rv;
+
+ if (!info->irq)
+ return 0;
+
+ rv = request_irq(info->irq,
+ si_irq_handler,
+ SA_INTERRUPT,
+ DEVICE_NAME,
+ info);
+ if (rv) {
+ printk(KERN_WARNING
+ "ipmi_si: %s unable to claim interrupt %d,"
+ " running polled\n",
+ DEVICE_NAME, info->irq);
+ info->irq = 0;
+ } else {
+ printk(" Using irq %d\n", info->irq);
+ }
+
+ return rv;
+}
+
+static void std_irq_cleanup(struct smi_info *info)
+{
+ if (!info->irq)
+ return;
+
+ free_irq(info->irq, info);
+}
+
+static unsigned char port_inb(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return inb((*addr)+(offset*io->regspacing));
+}
+
+static void port_outb(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outb(b, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inw(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inw((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outw(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inl(struct si_sm_io *io, unsigned int offset)
+{
+ unsigned int *addr = io->info;
+
+ return (inl((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ unsigned int *addr = io->info;
+
+ outl(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static void port_cleanup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (addr && (*addr)) {
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_region (*addr, mapsize);
+ }
+ kfree(info);
+}
+
+static int port_setup(struct smi_info *info)
+{
+ unsigned int *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = port_cleanup;
+
+ /* Figure out the actual inb/inw/inl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = port_inb;
+ info->io.outputb = port_outb;
+ break;
+ case 2:
+ info->io.inputb = port_inw;
+ info->io.outputb = port_outw;
+ break;
+ case 4:
+ info->io.inputb = port_inl;
+ info->io.outputb = port_outl;
+ break;
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+ return 0;
+}
+
+static int try_init_port(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+
+ if (!ports[intf_num])
+ return -ENODEV;
+
+ if (!is_new_interface(intf_num, IPMI_IO_ADDR_SPACE,
+ ports[intf_num]))
+ return -ENODEV;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (1)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = port_setup;
+ info->io.info = &(ports[intf_num]);
+ info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+ info->irq = 0;
+ info->irq_setup = NULL;
+ *new_info = info;
+
+ if (si_type[intf_num] == NULL)
+ si_type[intf_num] = "kcs";
+
+ printk("ipmi_si: Trying \"%s\" at I/O port 0x%x\n",
+ si_type[intf_num], ports[intf_num]);
+ return 0;
+}
+
+static unsigned char mem_inb(struct si_sm_io *io, unsigned int offset)
+{
+ return readb((io->addr)+(offset * io->regspacing));
+}
+
+static void mem_outb(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeb(b, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inw(struct si_sm_io *io, unsigned int offset)
+{
+ return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outw(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inl(struct si_sm_io *io, unsigned int offset)
+{
+ return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outl(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+#ifdef readq
+static unsigned char mem_inq(struct si_sm_io *io, unsigned int offset)
+{
+ return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
+ && 0xff;
+}
+
+static void mem_outq(struct si_sm_io *io, unsigned int offset,
+ unsigned char b)
+{
+ writeq(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+#endif
+
+static void mem_cleanup(struct smi_info *info)
+{
+ unsigned long *addr = info->io.info;
+ int mapsize;
+
+ if (info->io.addr) {
+ iounmap(info->io.addr);
+
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ release_mem_region(*addr, mapsize);
+ }
+ kfree(info);
+}
+
+static int mem_setup(struct smi_info *info)
+{
+ unsigned long *addr = info->io.info;
+ int mapsize;
+
+ if (!addr || (!*addr))
+ return -ENODEV;
+
+ info->io_cleanup = mem_cleanup;
+
+ /* Figure out the actual readb/readw/readl/etc routine to use based
+ upon the register size. */
+ switch (info->io.regsize) {
+ case 1:
+ info->io.inputb = mem_inb;
+ info->io.outputb = mem_outb;
+ break;
+ case 2:
+ info->io.inputb = mem_inw;
+ info->io.outputb = mem_outw;
+ break;
+ case 4:
+ info->io.inputb = mem_inl;
+ info->io.outputb = mem_outl;
+ break;
+#ifdef readq
+ case 8:
+ info->io.inputb = mem_inq;
+ info->io.outputb = mem_outq;
+ break;
+#endif
+ default:
+ printk("ipmi_si: Invalid register size: %d\n",
+ info->io.regsize);
+ return -EINVAL;
+ }
+
+ /* Calculate the total amount of memory to claim. This is an
+ * unusual looking calculation, but it avoids claiming any
+ * more memory than it has to. It will claim everything
+ * between the first address to the end of the last full
+ * register. */
+ mapsize = ((info->io_size * info->io.regspacing)
+ - (info->io.regspacing - info->io.regsize));
+
+ if (request_mem_region(*addr, mapsize, DEVICE_NAME) == NULL)
+ return -EIO;
+
+ info->io.addr = ioremap(*addr, mapsize);
+ if (info->io.addr == NULL) {
+ release_mem_region(*addr, mapsize);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int try_init_mem(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+
+ if (!addrs[intf_num])
+ return -ENODEV;
+
+ if (!is_new_interface(intf_num, IPMI_MEM_ADDR_SPACE,
+ addrs[intf_num]))
+ return -ENODEV;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (2)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = mem_setup;
+ info->io.info = &addrs[intf_num];
+ info->io.addr = NULL;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = regsizes[intf_num];
+ if (!info->io.regsize)
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+ info->irq = 0;
+ info->irq_setup = NULL;
+ *new_info = info;
+
+ if (si_type[intf_num] == NULL)
+ si_type[intf_num] = "kcs";
+
+ printk("ipmi_si: Trying \"%s\" at memory address 0x%lx\n",
+ si_type[intf_num], addrs[intf_num]);
+ return 0;
+}
+
+
+#ifdef CONFIG_ACPI_INTERPRETER
+
+#include <linux/acpi.h>
+
+/* Once we get an ACPI failure, we don't try any more, because we go
+ through the tables sequentially. Once we don't find a table, there
+ are no more. */
+static int acpi_failure = 0;
+
+/* For GPE-type interrupts. */
+static u32 ipmi_acpi_gpe(void *context)
+{
+ struct smi_info *smi_info = context;
+ unsigned long flags;
+#ifdef DEBUG_TIMING
+ struct timeval t;
+#endif
+
+ spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+ spin_lock(&smi_info->count_lock);
+ smi_info->interrupts++;
+ spin_unlock(&smi_info->count_lock);
+
+ if (smi_info->stop_operation)
+ goto out;
+
+#ifdef DEBUG_TIMING
+ do_gettimeofday(&t);
+ printk("**ACPI_GPE: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+ smi_event_handler(smi_info, 0);
+ out:
+ spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+ return ACPI_INTERRUPT_HANDLED;
+}
+
+static int acpi_gpe_irq_setup(struct smi_info *info)
+{
+ acpi_status status;
+
+ if (!info->irq)
+ return 0;
+
+ /* FIXME - is level triggered right? */
+ status = acpi_install_gpe_handler(NULL,
+ info->irq,
+ ACPI_GPE_LEVEL_TRIGGERED,
+ &ipmi_acpi_gpe,
+ info);
+ if (status != AE_OK) {
+ printk(KERN_WARNING
+ "ipmi_si: %s unable to claim ACPI GPE %d,"
+ " running polled\n",
+ DEVICE_NAME, info->irq);
+ info->irq = 0;
+ return -EINVAL;
+ } else {
+ printk(" Using ACPI GPE %d\n", info->irq);
+ return 0;
+ }
+}
+
+static void acpi_gpe_irq_cleanup(struct smi_info *info)
+{
+ if (!info->irq)
+ return;
+
+ acpi_remove_gpe_handler(NULL, info->irq, &ipmi_acpi_gpe);
+}
+
+/*
+ * Defined at
+ * http://h21007.www2.hp.com/dspp/files/unprotected/devresource/Docs/TechPapers/IA64/hpspmi.pdf
+ */
+struct SPMITable {
+ s8 Signature[4];
+ u32 Length;
+ u8 Revision;
+ u8 Checksum;
+ s8 OEMID[6];
+ s8 OEMTableID[8];
+ s8 OEMRevision[4];
+ s8 CreatorID[4];
+ s8 CreatorRevision[4];
+ u8 InterfaceType;
+ u8 IPMIlegacy;
+ s16 SpecificationRevision;
+
+ /*
+ * Bit 0 - SCI interrupt supported
+ * Bit 1 - I/O APIC/SAPIC
+ */
+ u8 InterruptType;
+
+ /* If bit 0 of InterruptType is set, then this is the SCI
+ interrupt in the GPEx_STS register. */
+ u8 GPE;
+
+ s16 Reserved;
+
+ /* If bit 1 of InterruptType is set, then this is the I/O
+ APIC/SAPIC interrupt. */
+ u32 GlobalSystemInterrupt;
+
+ /* The actual register address. */
+ struct acpi_generic_address addr;
+
+ u8 UID[4];
+
+ s8 spmi_id[1]; /* A '\0' terminated array starts here. */
+};
+
+static int try_init_acpi(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ acpi_status status;
+ struct SPMITable *spmi;
+ char *io_type;
+ u8 addr_space;
+
+ if (acpi_failure)
+ return -ENODEV;
+
+ status = acpi_get_firmware_table("SPMI", intf_num+1,
+ ACPI_LOGICAL_ADDRESSING,
+ (struct acpi_table_header **) &spmi);
+ if (status != AE_OK) {
+ acpi_failure = 1;
+ return -ENODEV;
+ }
+
+ if (spmi->IPMIlegacy != 1) {
+ printk(KERN_INFO "IPMI: Bad SPMI legacy %d\n", spmi->IPMIlegacy);
+ return -ENODEV;
+ }
+
+ if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
+ addr_space = IPMI_MEM_ADDR_SPACE;
+ else
+ addr_space = IPMI_IO_ADDR_SPACE;
+ if (!is_new_interface(-1, addr_space, spmi->addr.address))
+ return -ENODEV;
+
+ if (!spmi->addr.register_bit_width) {
+ acpi_failure = 1;
+ return -ENODEV;
+ }
+
+ /* Figure out the interface type. */
+ switch (spmi->InterfaceType)
+ {
+ case 1: /* KCS */
+ si_type[intf_num] = "kcs";
+ break;
+
+ case 2: /* SMIC */
+ si_type[intf_num] = "smic";
+ break;
+
+ case 3: /* BT */
+ si_type[intf_num] = "bt";
+ break;
+
+ default:
+ printk(KERN_INFO "ipmi_si: Unknown ACPI/SPMI SI type %d\n",
+ spmi->InterfaceType);
+ return -EIO;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (3)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ if (spmi->InterruptType & 1) {
+ /* We've got a GPE interrupt. */
+ info->irq = spmi->GPE;
+ info->irq_setup = acpi_gpe_irq_setup;
+ info->irq_cleanup = acpi_gpe_irq_cleanup;
+ } else if (spmi->InterruptType & 2) {
+ /* We've got an APIC/SAPIC interrupt. */
+ info->irq = spmi->GlobalSystemInterrupt;
+ info->irq_setup = std_irq_setup;
+ info->irq_cleanup = std_irq_cleanup;
+ } else {
+ /* Use the default interrupt setting. */
+ info->irq = 0;
+ info->irq_setup = NULL;
+ }
+
+ regspacings[intf_num] = spmi->addr.register_bit_width / 8;
+ info->io.regspacing = spmi->addr.register_bit_width / 8;
+ regsizes[intf_num] = regspacings[intf_num];
+ info->io.regsize = regsizes[intf_num];
+ regshifts[intf_num] = spmi->addr.register_bit_offset;
+ info->io.regshift = regshifts[intf_num];
+
+ if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+ io_type = "memory";
+ info->io_setup = mem_setup;
+ addrs[intf_num] = spmi->addr.address;
+ info->io.info = &(addrs[intf_num]);
+ } else if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+ io_type = "I/O";
+ info->io_setup = port_setup;
+ ports[intf_num] = spmi->addr.address;
+ info->io.info = &(ports[intf_num]);
+ } else {
+ kfree(info);
+ printk("ipmi_si: Unknown ACPI I/O Address type\n");
+ return -EIO;
+ }
+
+ *new_info = info;
+
+ printk("ipmi_si: ACPI/SPMI specifies \"%s\" %s SI @ 0x%lx\n",
+ si_type[intf_num], io_type, (unsigned long) spmi->addr.address);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_X86
+typedef struct dmi_ipmi_data
+{
+ u8 type;
+ u8 addr_space;
+ unsigned long base_addr;
+ u8 irq;
+ u8 offset;
+ u8 slave_addr;
+} dmi_ipmi_data_t;
+
+static dmi_ipmi_data_t dmi_data[SI_MAX_DRIVERS];
+static int dmi_data_entries;
+
+typedef struct dmi_header
+{
+ u8 type;
+ u8 length;
+ u16 handle;
+} dmi_header_t;
+
+static int decode_dmi(dmi_header_t *dm, int intf_num)
+{
+ u8 *data = (u8 *)dm;
+ unsigned long base_addr;
+ u8 reg_spacing;
+ u8 len = dm->length;
+ dmi_ipmi_data_t *ipmi_data = dmi_data+intf_num;
+
+ ipmi_data->type = data[4];
+
+ memcpy(&base_addr, data+8, sizeof(unsigned long));
+ if (len >= 0x11) {
+ if (base_addr & 1) {
+ /* I/O */
+ base_addr &= 0xFFFE;
+ ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+ }
+ else {
+ /* Memory */
+ ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
+ }
+ /* If bit 4 of byte 0x10 is set, then the lsb for the address
+ is odd. */
+ ipmi_data->base_addr = base_addr | ((data[0x10] & 0x10) >> 4);
+
+ ipmi_data->irq = data[0x11];
+
+ /* The top two bits of byte 0x10 hold the register spacing. */
+ reg_spacing = (data[0x10] & 0xC0) >> 6;
+ switch(reg_spacing){
+ case 0x00: /* Byte boundaries */
+ ipmi_data->offset = 1;
+ break;
+ case 0x01: /* 32-bit boundaries */
+ ipmi_data->offset = 4;
+ break;
+ case 0x02: /* 16-byte boundaries */
+ ipmi_data->offset = 16;
+ break;
+ default:
+ /* Some other interface, just ignore it. */
+ return -EIO;
+ }
+ } else {
+ /* Old DMI spec. */
+ ipmi_data->base_addr = base_addr;
+ ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+ ipmi_data->offset = 1;
+ }
+
+ ipmi_data->slave_addr = data[6];
+
+ if (is_new_interface(-1, ipmi_data->addr_space,ipmi_data->base_addr)) {
+ dmi_data_entries++;
+ return 0;
+ }
+
+ memset(ipmi_data, 0, sizeof(dmi_ipmi_data_t));
+
+ return -1;
+}
+
+static int dmi_table(u32 base, int len, int num)
+{
+ u8 *buf;
+ struct dmi_header *dm;
+ u8 *data;
+ int i=1;
+ int status=-1;
+ int intf_num = 0;
+
+ buf = ioremap(base, len);
+ if(buf==NULL)
+ return -1;
+
+ data = buf;
+
+ while(i<num && (data - buf) < len)
+ {
+ dm=(dmi_header_t *)data;
+
+ if((data-buf+dm->length) >= len)
+ break;
+
+ if (dm->type == 38) {
+ if (decode_dmi(dm, intf_num) == 0) {
+ intf_num++;
+ if (intf_num >= SI_MAX_DRIVERS)
+ break;
+ }
+ }
+
+ data+=dm->length;
+ while((data-buf) < len && (*data || data[1]))
+ data++;
+ data+=2;
+ i++;
+ }
+ iounmap(buf);
+
+ return status;
+}
+
+inline static int dmi_checksum(u8 *buf)
+{
+ u8 sum=0;
+ int a;
+
+ for(a=0; a<15; a++)
+ sum+=buf[a];
+ return (sum==0);
+}
+
+static int dmi_decode(void)
+{
+ u8 buf[15];
+ u32 fp=0xF0000;
+
+#ifdef CONFIG_SIMNOW
+ return -1;
+#endif
+
+ while(fp < 0xFFFFF)
+ {
+ isa_memcpy_fromio(buf, fp, 15);
+ if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf))
+ {
+ u16 num=buf[13]<<8|buf[12];
+ u16 len=buf[7]<<8|buf[6];
+ u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8];
+
+ if(dmi_table(base, len, num) == 0)
+ return 0;
+ }
+ fp+=16;
+ }
+
+ return -1;
+}
+
+static int try_init_smbios(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ dmi_ipmi_data_t *ipmi_data = dmi_data+intf_num;
+ char *io_type;
+
+ if (intf_num >= dmi_data_entries)
+ return -ENODEV;
+
+ switch(ipmi_data->type) {
+ case 0x01: /* KCS */
+ si_type[intf_num] = "kcs";
+ break;
+ case 0x02: /* SMIC */
+ si_type[intf_num] = "smic";
+ break;
+ case 0x03: /* BT */
+ si_type[intf_num] = "bt";
+ break;
+ default:
+ return -EIO;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (4)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ if (ipmi_data->addr_space == 1) {
+ io_type = "memory";
+ info->io_setup = mem_setup;
+ addrs[intf_num] = ipmi_data->base_addr;
+ info->io.info = &(addrs[intf_num]);
+ } else if (ipmi_data->addr_space == 2) {
+ io_type = "I/O";
+ info->io_setup = port_setup;
+ ports[intf_num] = ipmi_data->base_addr;
+ info->io.info = &(ports[intf_num]);
+ } else {
+ kfree(info);
+ printk("ipmi_si: Unknown SMBIOS I/O Address type.\n");
+ return -EIO;
+ }
+
+ regspacings[intf_num] = ipmi_data->offset;
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+
+ info->slave_addr = ipmi_data->slave_addr;
+
+ irqs[intf_num] = ipmi_data->irq;
+
+ *new_info = info;
+
+ printk("ipmi_si: Found SMBIOS-specified state machine at %s"
+ " address 0x%lx, slave address 0x%x\n",
+ io_type, (unsigned long)ipmi_data->base_addr,
+ ipmi_data->slave_addr);
+ return 0;
+}
+#endif /* CONFIG_X86 */
+
+#ifdef CONFIG_PCI
+
+#define PCI_ERMC_CLASSCODE 0x0C0700
+#define PCI_HP_VENDOR_ID 0x103C
+#define PCI_MMC_DEVICE_ID 0x121A
+#define PCI_MMC_ADDR_CW 0x10
+
+/* Avoid more than one attempt to probe pci smic. */
+static int pci_smic_checked = 0;
+
+static int find_pci_smic(int intf_num, struct smi_info **new_info)
+{
+ struct smi_info *info;
+ int error;
+ struct pci_dev *pci_dev = NULL;
+ u16 base_addr;
+ int fe_rmc = 0;
+
+ if (pci_smic_checked)
+ return -ENODEV;
+
+ pci_smic_checked = 1;
+
+ if ((pci_dev = pci_get_device(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID,
+ NULL)))
+ ;
+ else if ((pci_dev = pci_get_class(PCI_ERMC_CLASSCODE, NULL)) &&
+ pci_dev->subsystem_vendor == PCI_HP_VENDOR_ID)
+ fe_rmc = 1;
+ else
+ return -ENODEV;
+
+ error = pci_read_config_word(pci_dev, PCI_MMC_ADDR_CW, &base_addr);
+ if (error)
+ {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR
+ "ipmi_si: pci_read_config_word() failed (%d).\n",
+ error);
+ return -ENODEV;
+ }
+
+ /* Bit 0: 1 specifies programmed I/O, 0 specifies memory mapped I/O */
+ if (!(base_addr & 0x0001))
+ {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR
+ "ipmi_si: memory mapped I/O not supported for PCI"
+ " smic.\n");
+ return -ENODEV;
+ }
+
+ base_addr &= 0xFFFE;
+ if (!fe_rmc)
+ /* Data register starts at base address + 1 in eRMC */
+ ++base_addr;
+
+ if (!is_new_interface(-1, IPMI_IO_ADDR_SPACE, base_addr)) {
+ pci_dev_put(pci_dev);
+ return -ENODEV;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ pci_dev_put(pci_dev);
+ printk(KERN_ERR "ipmi_si: Could not allocate SI data (5)\n");
+ return -ENOMEM;
+ }
+ memset(info, 0, sizeof(*info));
+
+ info->io_setup = port_setup;
+ ports[intf_num] = base_addr;
+ info->io.info = &(ports[intf_num]);
+ info->io.regspacing = regspacings[intf_num];
+ if (!info->io.regspacing)
+ info->io.regspacing = DEFAULT_REGSPACING;
+ info->io.regsize = DEFAULT_REGSPACING;
+ info->io.regshift = regshifts[intf_num];
+
+ *new_info = info;
+
+ irqs[intf_num] = pci_dev->irq;
+ si_type[intf_num] = "smic";
+
+ printk("ipmi_si: Found PCI SMIC at I/O address 0x%lx\n",
+ (long unsigned int) base_addr);
+
+ pci_dev_put(pci_dev);
+ return 0;
+}
+#endif /* CONFIG_PCI */
+
+static int try_init_plug_and_play(int intf_num, struct smi_info **new_info)
+{
+#ifdef CONFIG_PCI
+ if (find_pci_smic(intf_num, new_info)==0)
+ return 0;
+#endif
+ /* Include other methods here. */
+
+ return -ENODEV;
+}
+
+
+static int try_get_dev_id(struct smi_info *smi_info)
+{
+ unsigned char msg[2];
+ unsigned char *resp;
+ unsigned long resp_len;
+ enum si_sm_result smi_result;
+ int rv = 0;
+
+ resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+ if (!resp)
+ return -ENOMEM;
+
+ /* Do a Get Device ID command, since it comes back with some
+ useful info. */
+ msg[0] = IPMI_NETFN_APP_REQUEST << 2;
+ msg[1] = IPMI_GET_DEVICE_ID_CMD;
+ smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+
+ smi_result = smi_info->handlers->event(smi_info->si_sm, 0);
+ for (;;)
+ {
+ if (smi_result == SI_SM_CALL_WITH_DELAY) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ smi_result = smi_info->handlers->event(
+ smi_info->si_sm, 100);
+ }
+ else if (smi_result == SI_SM_CALL_WITHOUT_DELAY)
+ {
+ smi_result = smi_info->handlers->event(
+ smi_info->si_sm, 0);
+ }
+ else
+ break;
+ }
+ if (smi_result == SI_SM_HOSED) {
+ /* We couldn't get the state machine to run, so whatever's at
+ the port is probably not an IPMI SMI interface. */
+ rv = -ENODEV;
+ goto out;
+ }
+
+ /* Otherwise, we got some data. */
+ resp_len = smi_info->handlers->get_result(smi_info->si_sm,
+ resp, IPMI_MAX_MSG_LENGTH);
+ if (resp_len < 6) {
+ /* That's odd, it should be longer. */
+ rv = -EINVAL;
+ goto out;
+ }
+
+ if ((resp[1] != IPMI_GET_DEVICE_ID_CMD) || (resp[2] != 0)) {
+ /* That's odd, it shouldn't be able to fail. */
+ rv = -EINVAL;
+ goto out;
+ }
+
+ /* Record info from the get device id, in case we need it. */
+ smi_info->ipmi_si_dev_rev = resp[4] & 0xf;
+ smi_info->ipmi_si_fw_rev_major = resp[5] & 0x7f;
+ smi_info->ipmi_si_fw_rev_minor = resp[6];
+ smi_info->ipmi_version_major = resp[7] & 0xf;
+ smi_info->ipmi_version_minor = resp[7] >> 4;
+
+ out:
+ kfree(resp);
+ return rv;
+}
+
+static int type_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ struct smi_info *smi = data;
+
+ switch (smi->si_type) {
+ case SI_KCS:
+ return sprintf(out, "kcs\n");
+ case SI_SMIC:
+ return sprintf(out, "smic\n");
+ case SI_BT:
+ return sprintf(out, "bt\n");
+ default:
+ return 0;
+ }
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+ int count, int *eof, void *data)
+{
+ char *out = (char *) page;
+ struct smi_info *smi = data;
+
+ out += sprintf(out, "interrupts_enabled: %d\n",
+ smi->irq && !smi->interrupt_disabled);
+ out += sprintf(out, "short_timeouts: %ld\n",
+ smi->short_timeouts);
+ out += sprintf(out, "long_timeouts: %ld\n",
+ smi->long_timeouts);
+ out += sprintf(out, "timeout_restarts: %ld\n",
+ smi->timeout_restarts);
+ out += sprintf(out, "idles: %ld\n",
+ smi->idles);
+ out += sprintf(out, "interrupts: %ld\n",
+ smi->interrupts);
+ out += sprintf(out, "attentions: %ld\n",
+ smi->attentions);
+ out += sprintf(out, "flag_fetches: %ld\n",
+ smi->flag_fetches);
+ out += sprintf(out, "hosed_count: %ld\n",
+ smi->hosed_count);
+ out += sprintf(out, "complete_transactions: %ld\n",
+ smi->complete_transactions);
+ out += sprintf(out, "events: %ld\n",
+ smi->events);
+ out += sprintf(out, "watchdog_pretimeouts: %ld\n",
+ smi->watchdog_pretimeouts);
+ out += sprintf(out, "incoming_messages: %ld\n",
+ smi->incoming_messages);
+
+ return (out - ((char *) page));
+}
+
+/* Returns 0 if initialized, or negative on an error. */
+static int init_one_smi(int intf_num, struct smi_info **smi)
+{
+ int rv;
+ struct smi_info *new_smi;
+
+
+ rv = try_init_mem(intf_num, &new_smi);
+ if (rv)
+ rv = try_init_port(intf_num, &new_smi);
+#ifdef CONFIG_ACPI_INTERPRETER
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_acpi(intf_num, &new_smi);
+ }
+#endif
+#ifdef CONFIG_X86
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_smbios(intf_num, &new_smi);
+ }
+#endif
+ if ((rv) && (si_trydefaults)) {
+ rv = try_init_plug_and_play(intf_num, &new_smi);
+ }
+
+
+ if (rv)
+ return rv;
+
+ /* So we know not to free it unless we have allocated one. */
+ new_smi->intf = NULL;
+ new_smi->si_sm = NULL;
+ new_smi->handlers = NULL;
+
+ if (!new_smi->irq_setup) {
+ new_smi->irq = irqs[intf_num];
+ new_smi->irq_setup = std_irq_setup;
+ new_smi->irq_cleanup = std_irq_cleanup;
+ }
+
+ /* Default to KCS if no type is specified. */
+ if (si_type[intf_num] == NULL) {
+ if (si_trydefaults)
+ si_type[intf_num] = "kcs";
+ else {
+ rv = -EINVAL;
+ goto out_err;
+ }
+ }
+
+ /* Set up the state machine to use. */
+ if (strcmp(si_type[intf_num], "kcs") == 0) {
+ new_smi->handlers = &kcs_smi_handlers;
+ new_smi->si_type = SI_KCS;
+ } else if (strcmp(si_type[intf_num], "smic") == 0) {
+ new_smi->handlers = &smic_smi_handlers;
+ new_smi->si_type = SI_SMIC;
+ } else if (strcmp(si_type[intf_num], "bt") == 0) {
+ new_smi->handlers = &bt_smi_handlers;
+ new_smi->si_type = SI_BT;
+ } else {
+ /* No support for anything else yet. */
+ rv = -EIO;
+ goto out_err;
+ }
+
+ /* Allocate the state machine's data and initialize it. */
+ new_smi->si_sm = kmalloc(new_smi->handlers->size(), GFP_KERNEL);
+ if (!new_smi->si_sm) {
+ printk(" Could not allocate state machine memory\n");
+ rv = -ENOMEM;
+ goto out_err;
+ }
+ new_smi->io_size = new_smi->handlers->init_data(new_smi->si_sm,
+ &new_smi->io);
+
+ /* Now that we know the I/O size, we can set up the I/O. */
+ rv = new_smi->io_setup(new_smi);
+ if (rv) {
+ printk(" Could not set up I/O space\n");
+ goto out_err;
+ }
+
+ spin_lock_init(&(new_smi->si_lock));
+ spin_lock_init(&(new_smi->msg_lock));
+ spin_lock_init(&(new_smi->count_lock));
+
+ /* Do low-level detection first. */
+ if (new_smi->handlers->detect(new_smi->si_sm)) {
+ rv = -ENODEV;
+ goto out_err;
+ }
+
+ /* Attempt a get device id command. If it fails, we probably
+ don't have a SMI here. */
+ rv = try_get_dev_id(new_smi);
+ if (rv)
+ goto out_err;
+
+ /* Try to claim any interrupts. */
+ new_smi->irq_setup(new_smi);
+
+ INIT_LIST_HEAD(&(new_smi->xmit_msgs));
+ INIT_LIST_HEAD(&(new_smi->hp_xmit_msgs));
+ new_smi->curr_msg = NULL;
+ atomic_set(&new_smi->req_events, 0);
+ new_smi->run_to_completion = 0;
+
+ new_smi->interrupt_disabled = 0;
+ new_smi->timer_stopped = 0;
+ new_smi->stop_operation = 0;
+
+ /* Start clearing the flags before we enable interrupts or the
+ timer to avoid racing with the timer. */
+ start_clear_flags(new_smi);
+ /* IRQ is defined to be set when non-zero. */
+ if (new_smi->irq)
+ new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ;
+
+ /* The ipmi_register_smi() code does some operations to
+ determine the channel information, so we must be ready to
+ handle operations before it is called. This means we have
+ to stop the timer if we get an error after this point. */
+ init_timer(&(new_smi->si_timer));
+ new_smi->si_timer.data = (long) new_smi;
+ new_smi->si_timer.function = smi_timeout;
+ new_smi->last_timeout_jiffies = jiffies;
+ new_smi->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+ add_timer(&(new_smi->si_timer));
+
+ rv = ipmi_register_smi(&handlers,
+ new_smi,
+ new_smi->ipmi_version_major,
+ new_smi->ipmi_version_minor,
+ new_smi->slave_addr,
+ &(new_smi->intf));
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to register device: error %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ rv = ipmi_smi_add_proc_entry(new_smi->intf, "type",
+ type_file_read_proc, NULL,
+ new_smi, THIS_MODULE);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to create proc entry: %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ rv = ipmi_smi_add_proc_entry(new_smi->intf, "si_stats",
+ stat_file_read_proc, NULL,
+ new_smi, THIS_MODULE);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to create proc entry: %d\n",
+ rv);
+ goto out_err_stop_timer;
+ }
+
+ *smi = new_smi;
+
+ printk(" IPMI %s interface initialized\n", si_type[intf_num]);
+
+ return 0;
+
+ out_err_stop_timer:
+ new_smi->stop_operation = 1;
+
+ /* Wait for the timer to stop. This avoids problems with race
+ conditions removing the timer here. */
+ while (!new_smi->timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ out_err:
+ if (new_smi->intf)
+ ipmi_unregister_smi(new_smi->intf);
+
+ new_smi->irq_cleanup(new_smi);
+
+ /* Wait until we know that we are out of any interrupt
+ handlers might have been running before we freed the
+ interrupt. */
+ synchronize_kernel();
+
+ if (new_smi->si_sm) {
+ if (new_smi->handlers)
+ new_smi->handlers->cleanup(new_smi->si_sm);
+ kfree(new_smi->si_sm);
+ }
+ new_smi->io_cleanup(new_smi);
+
+ return rv;
+}
+
+static __init int init_ipmi_si(void)
+{
+ int rv = 0;
+ int pos = 0;
+ int i;
+ char *str;
+
+ if (initialized)
+ return 0;
+ initialized = 1;
+
+ /* Parse out the si_type string into its components. */
+ str = si_type_str;
+ if (*str != '\0') {
+ for (i=0; (i<SI_MAX_PARMS) && (*str != '\0'); i++) {
+ si_type[i] = str;
+ str = strchr(str, ',');
+ if (str) {
+ *str = '\0';
+ str++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ printk(KERN_INFO "IPMI System Interface driver version "
+ IPMI_SI_VERSION);
+ if (kcs_smi_handlers.version)
+ printk(", KCS version %s", kcs_smi_handlers.version);
+ if (smic_smi_handlers.version)
+ printk(", SMIC version %s", smic_smi_handlers.version);
+ if (bt_smi_handlers.version)
+ printk(", BT version %s", bt_smi_handlers.version);
+ printk("\n");
+
+#ifdef CONFIG_X86
+ dmi_decode();
+#endif
+
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ if (rv && !ports[0] && si_trydefaults) {
+ /* If we are trying defaults and the initial port is
+ not set, then set it. */
+ si_type[0] = "kcs";
+ ports[0] = DEFAULT_KCS_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ if (rv) {
+ /* No KCS - try SMIC */
+ si_type[0] = "smic";
+ ports[0] = DEFAULT_SMIC_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ }
+ if (rv) {
+ /* No SMIC - try BT */
+ si_type[0] = "bt";
+ ports[0] = DEFAULT_BT_IO_PORT;
+ rv = init_one_smi(0, &(smi_infos[pos]));
+ }
+ }
+ if (rv == 0)
+ pos++;
+
+ for (i=1; i < SI_MAX_PARMS; i++) {
+ rv = init_one_smi(i, &(smi_infos[pos]));
+ if (rv == 0)
+ pos++;
+ }
+
+ if (smi_infos[0] == NULL) {
+ printk("ipmi_si: Unable to find any System Interface(s)\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+module_init(init_ipmi_si);
+
+static void __exit cleanup_one_si(struct smi_info *to_clean)
+{
+ int rv;
+ unsigned long flags;
+
+ if (! to_clean)
+ return;
+
+ /* Tell the timer and interrupt handlers that we are shutting
+ down. */
+ spin_lock_irqsave(&(to_clean->si_lock), flags);
+ spin_lock(&(to_clean->msg_lock));
+
+ to_clean->stop_operation = 1;
+
+ to_clean->irq_cleanup(to_clean);
+
+ spin_unlock(&(to_clean->msg_lock));
+ spin_unlock_irqrestore(&(to_clean->si_lock), flags);
+
+ /* Wait until we know that we are out of any interrupt
+ handlers might have been running before we freed the
+ interrupt. */
+ synchronize_kernel();
+
+ /* Wait for the timer to stop. This avoids problems with race
+ conditions removing the timer here. */
+ while (!to_clean->timer_stopped) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ /* Interrupts and timeouts are stopped, now make sure the
+ interface is in a clean state. */
+ while ((to_clean->curr_msg) || (to_clean->si_state != SI_NORMAL)) {
+ poll(to_clean);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ rv = ipmi_unregister_smi(to_clean->intf);
+ if (rv) {
+ printk(KERN_ERR
+ "ipmi_si: Unable to unregister device: errno=%d\n",
+ rv);
+ }
+
+ to_clean->handlers->cleanup(to_clean->si_sm);
+
+ kfree(to_clean->si_sm);
+
+ to_clean->io_cleanup(to_clean);
+}
+
+static __exit void cleanup_ipmi_si(void)
+{
+ int i;
+
+ if (!initialized)
+ return;
+
+ for (i=0; i<SI_MAX_DRIVERS; i++) {
+ cleanup_one_si(smi_infos[i]);
+ }
+}
+module_exit(cleanup_ipmi_si);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_si_sm.h b/drivers/char/ipmi/ipmi_si_sm.h
new file mode 100644
index 000000000000..a0212b004016
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_sm.h
@@ -0,0 +1,120 @@
+/*
+ * ipmi_si_sm.h
+ *
+ * State machine interface for low-level IPMI system management
+ * interface state machines. This code is the interface between
+ * the ipmi_smi code (that handles the policy of a KCS, SMIC, or
+ * BT interface) and the actual low-level state machine.
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This is defined by the state machines themselves, it is an opaque
+ data type for them to use. */
+struct si_sm_data;
+
+/* The structure for doing I/O in the state machine. The state
+ machine doesn't have the actual I/O routines, they are done through
+ this interface. */
+struct si_sm_io
+{
+ unsigned char (*inputb)(struct si_sm_io *io, unsigned int offset);
+ void (*outputb)(struct si_sm_io *io,
+ unsigned int offset,
+ unsigned char b);
+
+ /* Generic info used by the actual handling routines, the
+ state machine shouldn't touch these. */
+ void *info;
+ void *addr;
+ int regspacing;
+ int regsize;
+ int regshift;
+};
+
+/* Results of SMI events. */
+enum si_sm_result
+{
+ SI_SM_CALL_WITHOUT_DELAY, /* Call the driver again immediately */
+ SI_SM_CALL_WITH_DELAY, /* Delay some before calling again. */
+ SI_SM_TRANSACTION_COMPLETE, /* A transaction is finished. */
+ SI_SM_IDLE, /* The SM is in idle state. */
+ SI_SM_HOSED, /* The hardware violated the state machine. */
+ SI_SM_ATTN /* The hardware is asserting attn and the
+ state machine is idle. */
+};
+
+/* Handlers for the SMI state machine. */
+struct si_sm_handlers
+{
+ /* Put the version number of the state machine here so the
+ upper layer can print it. */
+ char *version;
+
+ /* Initialize the data and return the amount of I/O space to
+ reserve for the space. */
+ unsigned int (*init_data)(struct si_sm_data *smi,
+ struct si_sm_io *io);
+
+ /* Start a new transaction in the state machine. This will
+ return -2 if the state machine is not idle, -1 if the size
+ is invalid (to large or too small), or 0 if the transaction
+ is successfully completed. */
+ int (*start_transaction)(struct si_sm_data *smi,
+ unsigned char *data, unsigned int size);
+
+ /* Return the results after the transaction. This will return
+ -1 if the buffer is too small, zero if no transaction is
+ present, or the actual length of the result data. */
+ int (*get_result)(struct si_sm_data *smi,
+ unsigned char *data, unsigned int length);
+
+ /* Call this periodically (for a polled interface) or upon
+ receiving an interrupt (for a interrupt-driven interface).
+ If interrupt driven, you should probably poll this
+ periodically when not in idle state. This should be called
+ with the time that passed since the last call, if it is
+ significant. Time is in microseconds. */
+ enum si_sm_result (*event)(struct si_sm_data *smi, long time);
+
+ /* Attempt to detect an SMI. Returns 0 on success or nonzero
+ on failure. */
+ int (*detect)(struct si_sm_data *smi);
+
+ /* The interface is shutting down, so clean it up. */
+ void (*cleanup)(struct si_sm_data *smi);
+
+ /* Return the size of the SMI structure in bytes. */
+ int (*size)(void);
+};
+
+/* Current state machines that we can use. */
+extern struct si_sm_handlers kcs_smi_handlers;
+extern struct si_sm_handlers smic_smi_handlers;
+extern struct si_sm_handlers bt_smi_handlers;
+
diff --git a/drivers/char/ipmi/ipmi_smic_sm.c b/drivers/char/ipmi/ipmi_smic_sm.c
new file mode 100644
index 000000000000..ae18747e670b
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_smic_sm.c
@@ -0,0 +1,599 @@
+/*
+ * ipmi_smic_sm.c
+ *
+ * The state-machine driver for an IPMI SMIC driver
+ *
+ * It started as a copy of Corey Minyard's driver for the KSC interface
+ * and the kernel patch "mmcdev-patch-245" by HP
+ *
+ * modified by: Hannes Schulz <schulz@schwaar.com>
+ * ipmi@schwaar.com
+ *
+ *
+ * Corey Minyard's driver for the KSC interface has the following
+ * copyright notice:
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * the kernel patch "mmcdev-patch-245" by HP has the following
+ * copyright notice:
+ * (c) Copyright 2001 Grant Grundler (c) Copyright
+ * 2001 Hewlett-Packard Company
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h> /* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_SMIC_VERSION "v33"
+
+/* smic_debug is a bit-field
+ * SMIC_DEBUG_ENABLE - turned on for now
+ * SMIC_DEBUG_MSG - commands and their responses
+ * SMIC_DEBUG_STATES - state machine
+*/
+#define SMIC_DEBUG_STATES 4
+#define SMIC_DEBUG_MSG 2
+#define SMIC_DEBUG_ENABLE 1
+
+static int smic_debug = 1;
+
+enum smic_states {
+ SMIC_IDLE,
+ SMIC_START_OP,
+ SMIC_OP_OK,
+ SMIC_WRITE_START,
+ SMIC_WRITE_NEXT,
+ SMIC_WRITE_END,
+ SMIC_WRITE2READ,
+ SMIC_READ_START,
+ SMIC_READ_NEXT,
+ SMIC_READ_END,
+ SMIC_HOSED
+};
+
+#define MAX_SMIC_READ_SIZE 80
+#define MAX_SMIC_WRITE_SIZE 80
+#define SMIC_MAX_ERROR_RETRIES 3
+
+/* Timeouts in microseconds. */
+#define SMIC_RETRY_TIMEOUT 100000
+
+/* SMIC Flags Register Bits */
+#define SMIC_RX_DATA_READY 0x80
+#define SMIC_TX_DATA_READY 0x40
+#define SMIC_SMI 0x10
+#define SMIC_EVM_DATA_AVAIL 0x08
+#define SMIC_SMS_DATA_AVAIL 0x04
+#define SMIC_FLAG_BSY 0x01
+
+/* SMIC Error Codes */
+#define EC_NO_ERROR 0x00
+#define EC_ABORTED 0x01
+#define EC_ILLEGAL_CONTROL 0x02
+#define EC_NO_RESPONSE 0x03
+#define EC_ILLEGAL_COMMAND 0x04
+#define EC_BUFFER_FULL 0x05
+
+struct si_sm_data
+{
+ enum smic_states state;
+ struct si_sm_io *io;
+ unsigned char write_data[MAX_SMIC_WRITE_SIZE];
+ int write_pos;
+ int write_count;
+ int orig_write_count;
+ unsigned char read_data[MAX_SMIC_READ_SIZE];
+ int read_pos;
+ int truncated;
+ unsigned int error_retries;
+ long smic_timeout;
+};
+
+static unsigned int init_smic_data (struct si_sm_data *smic,
+ struct si_sm_io *io)
+{
+ smic->state = SMIC_IDLE;
+ smic->io = io;
+ smic->write_pos = 0;
+ smic->write_count = 0;
+ smic->orig_write_count = 0;
+ smic->read_pos = 0;
+ smic->error_retries = 0;
+ smic->truncated = 0;
+ smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+
+ /* We use 3 bytes of I/O. */
+ return 3;
+}
+
+static int start_smic_transaction(struct si_sm_data *smic,
+ unsigned char *data, unsigned int size)
+{
+ unsigned int i;
+
+ if ((size < 2) || (size > MAX_SMIC_WRITE_SIZE)) {
+ return -1;
+ }
+ if ((smic->state != SMIC_IDLE) && (smic->state != SMIC_HOSED)) {
+ return -2;
+ }
+ if (smic_debug & SMIC_DEBUG_MSG) {
+ printk(KERN_INFO "start_smic_transaction -");
+ for (i = 0; i < size; i ++) {
+ printk (" %02x", (unsigned char) (data [i]));
+ }
+ printk ("\n");
+ }
+ smic->error_retries = 0;
+ memcpy(smic->write_data, data, size);
+ smic->write_count = size;
+ smic->orig_write_count = size;
+ smic->write_pos = 0;
+ smic->read_pos = 0;
+ smic->state = SMIC_START_OP;
+ smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+ return 0;
+}
+
+static int smic_get_result(struct si_sm_data *smic,
+ unsigned char *data, unsigned int length)
+{
+ int i;
+
+ if (smic_debug & SMIC_DEBUG_MSG) {
+ printk (KERN_INFO "smic_get result -");
+ for (i = 0; i < smic->read_pos; i ++) {
+ printk (" %02x", (smic->read_data [i]));
+ }
+ printk ("\n");
+ }
+ if (length < smic->read_pos) {
+ smic->read_pos = length;
+ smic->truncated = 1;
+ }
+ memcpy(data, smic->read_data, smic->read_pos);
+
+ if ((length >= 3) && (smic->read_pos < 3)) {
+ data[2] = IPMI_ERR_UNSPECIFIED;
+ smic->read_pos = 3;
+ }
+ if (smic->truncated) {
+ data[2] = IPMI_ERR_MSG_TRUNCATED;
+ smic->truncated = 0;
+ }
+ return smic->read_pos;
+}
+
+static inline unsigned char read_smic_flags(struct si_sm_data *smic)
+{
+ return smic->io->inputb(smic->io, 2);
+}
+
+static inline unsigned char read_smic_status(struct si_sm_data *smic)
+{
+ return smic->io->inputb(smic->io, 1);
+}
+
+static inline unsigned char read_smic_data(struct si_sm_data *smic)
+{
+ return smic->io->inputb(smic->io, 0);
+}
+
+static inline void write_smic_flags(struct si_sm_data *smic,
+ unsigned char flags)
+{
+ smic->io->outputb(smic->io, 2, flags);
+}
+
+static inline void write_smic_control(struct si_sm_data *smic,
+ unsigned char control)
+{
+ smic->io->outputb(smic->io, 1, control);
+}
+
+static inline void write_si_sm_data (struct si_sm_data *smic,
+ unsigned char data)
+{
+ smic->io->outputb(smic->io, 0, data);
+}
+
+static inline void start_error_recovery(struct si_sm_data *smic, char *reason)
+{
+ (smic->error_retries)++;
+ if (smic->error_retries > SMIC_MAX_ERROR_RETRIES) {
+ if (smic_debug & SMIC_DEBUG_ENABLE) {
+ printk(KERN_WARNING
+ "ipmi_smic_drv: smic hosed: %s\n", reason);
+ }
+ smic->state = SMIC_HOSED;
+ } else {
+ smic->write_count = smic->orig_write_count;
+ smic->write_pos = 0;
+ smic->read_pos = 0;
+ smic->state = SMIC_START_OP;
+ smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+ }
+}
+
+static inline void write_next_byte(struct si_sm_data *smic)
+{
+ write_si_sm_data(smic, smic->write_data[smic->write_pos]);
+ (smic->write_pos)++;
+ (smic->write_count)--;
+}
+
+static inline void read_next_byte (struct si_sm_data *smic)
+{
+ if (smic->read_pos >= MAX_SMIC_READ_SIZE) {
+ read_smic_data (smic);
+ smic->truncated = 1;
+ } else {
+ smic->read_data[smic->read_pos] = read_smic_data(smic);
+ (smic->read_pos)++;
+ }
+}
+
+/* SMIC Control/Status Code Components */
+#define SMIC_GET_STATUS 0x00 /* Control form's name */
+#define SMIC_READY 0x00 /* Status form's name */
+#define SMIC_WR_START 0x01 /* Unified Control/Status names... */
+#define SMIC_WR_NEXT 0x02
+#define SMIC_WR_END 0x03
+#define SMIC_RD_START 0x04
+#define SMIC_RD_NEXT 0x05
+#define SMIC_RD_END 0x06
+#define SMIC_CODE_MASK 0x0f
+
+#define SMIC_CONTROL 0x00
+#define SMIC_STATUS 0x80
+#define SMIC_CS_MASK 0x80
+
+#define SMIC_SMS 0x40
+#define SMIC_SMM 0x60
+#define SMIC_STREAM_MASK 0x60
+
+/* SMIC Control Codes */
+#define SMIC_CC_SMS_GET_STATUS (SMIC_CONTROL|SMIC_SMS|SMIC_GET_STATUS)
+#define SMIC_CC_SMS_WR_START (SMIC_CONTROL|SMIC_SMS|SMIC_WR_START)
+#define SMIC_CC_SMS_WR_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_WR_NEXT)
+#define SMIC_CC_SMS_WR_END (SMIC_CONTROL|SMIC_SMS|SMIC_WR_END)
+#define SMIC_CC_SMS_RD_START (SMIC_CONTROL|SMIC_SMS|SMIC_RD_START)
+#define SMIC_CC_SMS_RD_NEXT (SMIC_CONTROL|SMIC_SMS|SMIC_RD_NEXT)
+#define SMIC_CC_SMS_RD_END (SMIC_CONTROL|SMIC_SMS|SMIC_RD_END)
+
+#define SMIC_CC_SMM_GET_STATUS (SMIC_CONTROL|SMIC_SMM|SMIC_GET_STATUS)
+#define SMIC_CC_SMM_WR_START (SMIC_CONTROL|SMIC_SMM|SMIC_WR_START)
+#define SMIC_CC_SMM_WR_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_WR_NEXT)
+#define SMIC_CC_SMM_WR_END (SMIC_CONTROL|SMIC_SMM|SMIC_WR_END)
+#define SMIC_CC_SMM_RD_START (SMIC_CONTROL|SMIC_SMM|SMIC_RD_START)
+#define SMIC_CC_SMM_RD_NEXT (SMIC_CONTROL|SMIC_SMM|SMIC_RD_NEXT)
+#define SMIC_CC_SMM_RD_END (SMIC_CONTROL|SMIC_SMM|SMIC_RD_END)
+
+/* SMIC Status Codes */
+#define SMIC_SC_SMS_READY (SMIC_STATUS|SMIC_SMS|SMIC_READY)
+#define SMIC_SC_SMS_WR_START (SMIC_STATUS|SMIC_SMS|SMIC_WR_START)
+#define SMIC_SC_SMS_WR_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_WR_NEXT)
+#define SMIC_SC_SMS_WR_END (SMIC_STATUS|SMIC_SMS|SMIC_WR_END)
+#define SMIC_SC_SMS_RD_START (SMIC_STATUS|SMIC_SMS|SMIC_RD_START)
+#define SMIC_SC_SMS_RD_NEXT (SMIC_STATUS|SMIC_SMS|SMIC_RD_NEXT)
+#define SMIC_SC_SMS_RD_END (SMIC_STATUS|SMIC_SMS|SMIC_RD_END)
+
+#define SMIC_SC_SMM_READY (SMIC_STATUS|SMIC_SMM|SMIC_READY)
+#define SMIC_SC_SMM_WR_START (SMIC_STATUS|SMIC_SMM|SMIC_WR_START)
+#define SMIC_SC_SMM_WR_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_WR_NEXT)
+#define SMIC_SC_SMM_WR_END (SMIC_STATUS|SMIC_SMM|SMIC_WR_END)
+#define SMIC_SC_SMM_RD_START (SMIC_STATUS|SMIC_SMM|SMIC_RD_START)
+#define SMIC_SC_SMM_RD_NEXT (SMIC_STATUS|SMIC_SMM|SMIC_RD_NEXT)
+#define SMIC_SC_SMM_RD_END (SMIC_STATUS|SMIC_SMM|SMIC_RD_END)
+
+/* these are the control/status codes we actually use
+ SMIC_CC_SMS_GET_STATUS 0x40
+ SMIC_CC_SMS_WR_START 0x41
+ SMIC_CC_SMS_WR_NEXT 0x42
+ SMIC_CC_SMS_WR_END 0x43
+ SMIC_CC_SMS_RD_START 0x44
+ SMIC_CC_SMS_RD_NEXT 0x45
+ SMIC_CC_SMS_RD_END 0x46
+
+ SMIC_SC_SMS_READY 0xC0
+ SMIC_SC_SMS_WR_START 0xC1
+ SMIC_SC_SMS_WR_NEXT 0xC2
+ SMIC_SC_SMS_WR_END 0xC3
+ SMIC_SC_SMS_RD_START 0xC4
+ SMIC_SC_SMS_RD_NEXT 0xC5
+ SMIC_SC_SMS_RD_END 0xC6
+*/
+
+static enum si_sm_result smic_event (struct si_sm_data *smic, long time)
+{
+ unsigned char status;
+ unsigned char flags;
+ unsigned char data;
+
+ if (smic->state == SMIC_HOSED) {
+ init_smic_data(smic, smic->io);
+ return SI_SM_HOSED;
+ }
+ if (smic->state != SMIC_IDLE) {
+ if (smic_debug & SMIC_DEBUG_STATES) {
+ printk(KERN_INFO
+ "smic_event - smic->smic_timeout = %ld,"
+ " time = %ld\n",
+ smic->smic_timeout, time);
+ }
+/* FIXME: smic_event is sometimes called with time > SMIC_RETRY_TIMEOUT */
+ if (time < SMIC_RETRY_TIMEOUT) {
+ smic->smic_timeout -= time;
+ if (smic->smic_timeout < 0) {
+ start_error_recovery(smic, "smic timed out.");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ }
+ }
+ flags = read_smic_flags(smic);
+ if (flags & SMIC_FLAG_BSY)
+ return SI_SM_CALL_WITH_DELAY;
+
+ status = read_smic_status (smic);
+ if (smic_debug & SMIC_DEBUG_STATES)
+ printk(KERN_INFO
+ "smic_event - state = %d, flags = 0x%02x,"
+ " status = 0x%02x\n",
+ smic->state, flags, status);
+
+ switch (smic->state) {
+ case SMIC_IDLE:
+ /* in IDLE we check for available messages */
+ if (flags & (SMIC_SMI |
+ SMIC_EVM_DATA_AVAIL | SMIC_SMS_DATA_AVAIL))
+ {
+ return SI_SM_ATTN;
+ }
+ return SI_SM_IDLE;
+
+ case SMIC_START_OP:
+ /* sanity check whether smic is really idle */
+ write_smic_control(smic, SMIC_CC_SMS_GET_STATUS);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_OP_OK;
+ break;
+
+ case SMIC_OP_OK:
+ if (status != SMIC_SC_SMS_READY) {
+ /* this should not happen */
+ start_error_recovery(smic,
+ "state = SMIC_OP_OK,"
+ " status != SMIC_SC_SMS_READY");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ /* OK so far; smic is idle let us start ... */
+ write_smic_control(smic, SMIC_CC_SMS_WR_START);
+ write_next_byte(smic);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_WRITE_START;
+ break;
+
+ case SMIC_WRITE_START:
+ if (status != SMIC_SC_SMS_WR_START) {
+ start_error_recovery(smic,
+ "state = SMIC_WRITE_START, "
+ "status != SMIC_SC_SMS_WR_START");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ /* we must not issue WR_(NEXT|END) unless
+ TX_DATA_READY is set */
+ if (flags & SMIC_TX_DATA_READY) {
+ if (smic->write_count == 1) {
+ /* last byte */
+ write_smic_control(smic, SMIC_CC_SMS_WR_END);
+ smic->state = SMIC_WRITE_END;
+ } else {
+ write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
+ smic->state = SMIC_WRITE_NEXT;
+ }
+ write_next_byte(smic);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ }
+ else {
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+
+ case SMIC_WRITE_NEXT:
+ if (status != SMIC_SC_SMS_WR_NEXT) {
+ start_error_recovery(smic,
+ "state = SMIC_WRITE_NEXT, "
+ "status != SMIC_SC_SMS_WR_NEXT");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ /* this is the same code as in SMIC_WRITE_START */
+ if (flags & SMIC_TX_DATA_READY) {
+ if (smic->write_count == 1) {
+ write_smic_control(smic, SMIC_CC_SMS_WR_END);
+ smic->state = SMIC_WRITE_END;
+ }
+ else {
+ write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
+ smic->state = SMIC_WRITE_NEXT;
+ }
+ write_next_byte(smic);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ }
+ else {
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+
+ case SMIC_WRITE_END:
+ if (status != SMIC_SC_SMS_WR_END) {
+ start_error_recovery (smic,
+ "state = SMIC_WRITE_END, "
+ "status != SMIC_SC_SMS_WR_END");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ /* data register holds an error code */
+ data = read_smic_data(smic);
+ if (data != 0) {
+ if (smic_debug & SMIC_DEBUG_ENABLE) {
+ printk(KERN_INFO
+ "SMIC_WRITE_END: data = %02x\n", data);
+ }
+ start_error_recovery(smic,
+ "state = SMIC_WRITE_END, "
+ "data != SUCCESS");
+ return SI_SM_CALL_WITH_DELAY;
+ } else {
+ smic->state = SMIC_WRITE2READ;
+ }
+ break;
+
+ case SMIC_WRITE2READ:
+ /* we must wait for RX_DATA_READY to be set before we
+ can continue */
+ if (flags & SMIC_RX_DATA_READY) {
+ write_smic_control(smic, SMIC_CC_SMS_RD_START);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_READ_START;
+ } else {
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+
+ case SMIC_READ_START:
+ if (status != SMIC_SC_SMS_RD_START) {
+ start_error_recovery(smic,
+ "state = SMIC_READ_START, "
+ "status != SMIC_SC_SMS_RD_START");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ if (flags & SMIC_RX_DATA_READY) {
+ read_next_byte(smic);
+ write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_READ_NEXT;
+ } else {
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+
+ case SMIC_READ_NEXT:
+ switch (status) {
+ /* smic tells us that this is the last byte to be read
+ --> clean up */
+ case SMIC_SC_SMS_RD_END:
+ read_next_byte(smic);
+ write_smic_control(smic, SMIC_CC_SMS_RD_END);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_READ_END;
+ break;
+ case SMIC_SC_SMS_RD_NEXT:
+ if (flags & SMIC_RX_DATA_READY) {
+ read_next_byte(smic);
+ write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
+ write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+ smic->state = SMIC_READ_NEXT;
+ } else {
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+ default:
+ start_error_recovery(
+ smic,
+ "state = SMIC_READ_NEXT, "
+ "status != SMIC_SC_SMS_RD_(NEXT|END)");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ break;
+
+ case SMIC_READ_END:
+ if (status != SMIC_SC_SMS_READY) {
+ start_error_recovery(smic,
+ "state = SMIC_READ_END, "
+ "status != SMIC_SC_SMS_READY");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ data = read_smic_data(smic);
+ /* data register holds an error code */
+ if (data != 0) {
+ if (smic_debug & SMIC_DEBUG_ENABLE) {
+ printk(KERN_INFO
+ "SMIC_READ_END: data = %02x\n", data);
+ }
+ start_error_recovery(smic,
+ "state = SMIC_READ_END, "
+ "data != SUCCESS");
+ return SI_SM_CALL_WITH_DELAY;
+ } else {
+ smic->state = SMIC_IDLE;
+ return SI_SM_TRANSACTION_COMPLETE;
+ }
+
+ case SMIC_HOSED:
+ init_smic_data(smic, smic->io);
+ return SI_SM_HOSED;
+
+ default:
+ if (smic_debug & SMIC_DEBUG_ENABLE) {
+ printk(KERN_WARNING "smic->state = %d\n", smic->state);
+ start_error_recovery(smic, "state = UNKNOWN");
+ return SI_SM_CALL_WITH_DELAY;
+ }
+ }
+ smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+ return SI_SM_CALL_WITHOUT_DELAY;
+}
+
+static int smic_detect(struct si_sm_data *smic)
+{
+ /* It's impossible for the SMIC fnags register to be all 1's,
+ (assuming a properly functioning, self-initialized BMC)
+ but that's what you get from reading a bogus address, so we
+ test that first. */
+ if (read_smic_flags(smic) == 0xff)
+ return 1;
+
+ return 0;
+}
+
+static void smic_cleanup(struct si_sm_data *kcs)
+{
+}
+
+static int smic_size(void)
+{
+ return sizeof(struct si_sm_data);
+}
+
+struct si_sm_handlers smic_smi_handlers =
+{
+ .version = IPMI_SMIC_VERSION,
+ .init_data = init_smic_data,
+ .start_transaction = start_smic_transaction,
+ .get_result = smic_get_result,
+ .event = smic_event,
+ .detect = smic_detect,
+ .cleanup = smic_cleanup,
+ .size = smic_size,
+};
diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
new file mode 100644
index 000000000000..fd7093879c66
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_watchdog.c
@@ -0,0 +1,1068 @@
+/*
+ * ipmi_watchdog.c
+ *
+ * A watchdog timer based upon the IPMI interface.
+ *
+ * Author: MontaVista Software, Inc.
+ * Corey Minyard <minyard@mvista.com>
+ * source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/watchdog.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/rwsem.h>
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/nmi.h>
+#include <linux/reboot.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#ifdef CONFIG_X86_LOCAL_APIC
+#include <asm/apic.h>
+#endif
+
+#define PFX "IPMI Watchdog: "
+
+#define IPMI_WATCHDOG_VERSION "v33"
+
+/*
+ * The IPMI command/response information for the watchdog timer.
+ */
+
+/* values for byte 1 of the set command, byte 2 of the get response. */
+#define WDOG_DONT_LOG (1 << 7)
+#define WDOG_DONT_STOP_ON_SET (1 << 6)
+#define WDOG_SET_TIMER_USE(byte, use) \
+ byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMER_USE(byte) ((byte) & 0x7)
+#define WDOG_TIMER_USE_BIOS_FRB2 1
+#define WDOG_TIMER_USE_BIOS_POST 2
+#define WDOG_TIMER_USE_OS_LOAD 3
+#define WDOG_TIMER_USE_SMS_OS 4
+#define WDOG_TIMER_USE_OEM 5
+
+/* values for byte 2 of the set command, byte 3 of the get response. */
+#define WDOG_SET_PRETIMEOUT_ACT(byte, use) \
+ byte = ((byte) & 0x8f) | (((use) & 0x7) << 4)
+#define WDOG_GET_PRETIMEOUT_ACT(byte) (((byte) >> 4) & 0x7)
+#define WDOG_PRETIMEOUT_NONE 0
+#define WDOG_PRETIMEOUT_SMI 1
+#define WDOG_PRETIMEOUT_NMI 2
+#define WDOG_PRETIMEOUT_MSG_INT 3
+
+/* Operations that can be performed on a pretimout. */
+#define WDOG_PREOP_NONE 0
+#define WDOG_PREOP_PANIC 1
+#define WDOG_PREOP_GIVE_DATA 2 /* Cause data to be available to
+ read. Doesn't work in NMI
+ mode. */
+
+/* Actions to perform on a full timeout. */
+#define WDOG_SET_TIMEOUT_ACT(byte, use) \
+ byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMEOUT_ACT(byte) ((byte) & 0x7)
+#define WDOG_TIMEOUT_NONE 0
+#define WDOG_TIMEOUT_RESET 1
+#define WDOG_TIMEOUT_POWER_DOWN 2
+#define WDOG_TIMEOUT_POWER_CYCLE 3
+
+/* Byte 3 of the get command, byte 4 of the get response is the
+ pre-timeout in seconds. */
+
+/* Bits for setting byte 4 of the set command, byte 5 of the get response. */
+#define WDOG_EXPIRE_CLEAR_BIOS_FRB2 (1 << 1)
+#define WDOG_EXPIRE_CLEAR_BIOS_POST (1 << 2)
+#define WDOG_EXPIRE_CLEAR_OS_LOAD (1 << 3)
+#define WDOG_EXPIRE_CLEAR_SMS_OS (1 << 4)
+#define WDOG_EXPIRE_CLEAR_OEM (1 << 5)
+
+/* Setting/getting the watchdog timer value. This is for bytes 5 and
+ 6 (the timeout time) of the set command, and bytes 6 and 7 (the
+ timeout time) and 8 and 9 (the current countdown value) of the
+ response. The timeout value is given in seconds (in the command it
+ is 100ms intervals). */
+#define WDOG_SET_TIMEOUT(byte1, byte2, val) \
+ (byte1) = (((val) * 10) & 0xff), (byte2) = (((val) * 10) >> 8)
+#define WDOG_GET_TIMEOUT(byte1, byte2) \
+ (((byte1) | ((byte2) << 8)) / 10)
+
+#define IPMI_WDOG_RESET_TIMER 0x22
+#define IPMI_WDOG_SET_TIMER 0x24
+#define IPMI_WDOG_GET_TIMER 0x25
+
+/* These are here until the real ones get into the watchdog.h interface. */
+#ifndef WDIOC_GETTIMEOUT
+#define WDIOC_GETTIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 20, int)
+#endif
+#ifndef WDIOC_SET_PRETIMEOUT
+#define WDIOC_SET_PRETIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 21, int)
+#endif
+#ifndef WDIOC_GET_PRETIMEOUT
+#define WDIOC_GET_PRETIMEOUT _IOW(WATCHDOG_IOCTL_BASE, 22, int)
+#endif
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout;
+#endif
+
+static ipmi_user_t watchdog_user = NULL;
+
+/* Default the timeout to 10 seconds. */
+static int timeout = 10;
+
+/* The pre-timeout is disabled by default. */
+static int pretimeout = 0;
+
+/* Default action is to reset the board on a timeout. */
+static unsigned char action_val = WDOG_TIMEOUT_RESET;
+
+static char action[16] = "reset";
+
+static unsigned char preaction_val = WDOG_PRETIMEOUT_NONE;
+
+static char preaction[16] = "pre_none";
+
+static unsigned char preop_val = WDOG_PREOP_NONE;
+
+static char preop[16] = "preop_none";
+static DEFINE_SPINLOCK(ipmi_read_lock);
+static char data_to_read = 0;
+static DECLARE_WAIT_QUEUE_HEAD(read_q);
+static struct fasync_struct *fasync_q = NULL;
+static char pretimeout_since_last_heartbeat = 0;
+static char expect_close;
+
+/* If true, the driver will start running as soon as it is configured
+ and ready. */
+static int start_now = 0;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Timeout value in seconds.");
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout, "Pretimeout value in seconds.");
+module_param_string(action, action, sizeof(action), 0);
+MODULE_PARM_DESC(action, "Timeout action. One of: "
+ "reset, none, power_cycle, power_off.");
+module_param_string(preaction, preaction, sizeof(preaction), 0);
+MODULE_PARM_DESC(preaction, "Pretimeout action. One of: "
+ "pre_none, pre_smi, pre_nmi, pre_int.");
+module_param_string(preop, preop, sizeof(preop), 0);
+MODULE_PARM_DESC(preop, "Pretimeout driver operation. One of: "
+ "preop_none, preop_panic, preop_give_data.");
+module_param(start_now, int, 0);
+MODULE_PARM_DESC(start_now, "Set to 1 to start the watchdog as"
+ "soon as the driver is loaded.");
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* Default state of the timer. */
+static unsigned char ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+
+/* If shutting down via IPMI, we ignore the heartbeat. */
+static int ipmi_ignore_heartbeat = 0;
+
+/* Is someone using the watchdog? Only one user is allowed. */
+static unsigned long ipmi_wdog_open = 0;
+
+/* If set to 1, the heartbeat command will set the state to reset and
+ start the timer. The timer doesn't normally run when the driver is
+ first opened until the heartbeat is set the first time, this
+ variable is used to accomplish this. */
+static int ipmi_start_timer_on_heartbeat = 0;
+
+/* IPMI version of the BMC. */
+static unsigned char ipmi_version_major;
+static unsigned char ipmi_version_minor;
+
+
+static int ipmi_heartbeat(void);
+static void panic_halt_ipmi_heartbeat(void);
+
+
+/* We use a semaphore to make sure that only one thing can send a set
+ timeout at one time, because we only have one copy of the data.
+ The semaphore is claimed when the set_timeout is sent and freed
+ when both messages are free. */
+static atomic_t set_timeout_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(set_timeout_lock);
+static void set_timeout_free_smi(struct ipmi_smi_msg *msg)
+{
+ if (atomic_dec_and_test(&set_timeout_tofree))
+ up(&set_timeout_lock);
+}
+static void set_timeout_free_recv(struct ipmi_recv_msg *msg)
+{
+ if (atomic_dec_and_test(&set_timeout_tofree))
+ up(&set_timeout_lock);
+}
+static struct ipmi_smi_msg set_timeout_smi_msg =
+{
+ .done = set_timeout_free_smi
+};
+static struct ipmi_recv_msg set_timeout_recv_msg =
+{
+ .done = set_timeout_free_recv
+};
+
+static int i_ipmi_set_timeout(struct ipmi_smi_msg *smi_msg,
+ struct ipmi_recv_msg *recv_msg,
+ int *send_heartbeat_now)
+{
+ struct kernel_ipmi_msg msg;
+ unsigned char data[6];
+ int rv;
+ struct ipmi_system_interface_addr addr;
+ int hbnow = 0;
+
+
+ data[0] = 0;
+ WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS);
+
+ if ((ipmi_version_major > 1)
+ || ((ipmi_version_major == 1) && (ipmi_version_minor >= 5)))
+ {
+ /* This is an IPMI 1.5-only feature. */
+ data[0] |= WDOG_DONT_STOP_ON_SET;
+ } else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
+ /* In ipmi 1.0, setting the timer stops the watchdog, we
+ need to start it back up again. */
+ hbnow = 1;
+ }
+
+ data[1] = 0;
+ WDOG_SET_TIMEOUT_ACT(data[1], ipmi_watchdog_state);
+ if (pretimeout > 0) {
+ WDOG_SET_PRETIMEOUT_ACT(data[1], preaction_val);
+ data[2] = pretimeout;
+ } else {
+ WDOG_SET_PRETIMEOUT_ACT(data[1], WDOG_PRETIMEOUT_NONE);
+ data[2] = 0; /* No pretimeout. */
+ }
+ data[3] = 0;
+ WDOG_SET_TIMEOUT(data[4], data[5], timeout);
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_SET_TIMER;
+ msg.data = data;
+ msg.data_len = sizeof(data);
+ rv = ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ smi_msg,
+ recv_msg,
+ 1);
+ if (rv) {
+ printk(KERN_WARNING PFX "set timeout error: %d\n",
+ rv);
+ }
+
+ if (send_heartbeat_now)
+ *send_heartbeat_now = hbnow;
+
+ return rv;
+}
+
+/* Parameters to ipmi_set_timeout */
+#define IPMI_SET_TIMEOUT_NO_HB 0
+#define IPMI_SET_TIMEOUT_HB_IF_NECESSARY 1
+#define IPMI_SET_TIMEOUT_FORCE_HB 2
+
+static int ipmi_set_timeout(int do_heartbeat)
+{
+ int send_heartbeat_now;
+ int rv;
+
+
+ /* We can only send one of these at a time. */
+ down(&set_timeout_lock);
+
+ atomic_set(&set_timeout_tofree, 2);
+
+ rv = i_ipmi_set_timeout(&set_timeout_smi_msg,
+ &set_timeout_recv_msg,
+ &send_heartbeat_now);
+ if (rv) {
+ up(&set_timeout_lock);
+ } else {
+ if ((do_heartbeat == IPMI_SET_TIMEOUT_FORCE_HB)
+ || ((send_heartbeat_now)
+ && (do_heartbeat == IPMI_SET_TIMEOUT_HB_IF_NECESSARY)))
+ {
+ rv = ipmi_heartbeat();
+ }
+ }
+
+ return rv;
+}
+
+static void dummy_smi_free(struct ipmi_smi_msg *msg)
+{
+}
+static void dummy_recv_free(struct ipmi_recv_msg *msg)
+{
+}
+static struct ipmi_smi_msg panic_halt_smi_msg =
+{
+ .done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_recv_msg =
+{
+ .done = dummy_recv_free
+};
+
+/* Special call, doesn't claim any locks. This is only to be called
+ at panic or halt time, in run-to-completion mode, when the caller
+ is the only CPU and the only thing that will be going is these IPMI
+ calls. */
+static void panic_halt_ipmi_set_timeout(void)
+{
+ int send_heartbeat_now;
+ int rv;
+
+ rv = i_ipmi_set_timeout(&panic_halt_smi_msg,
+ &panic_halt_recv_msg,
+ &send_heartbeat_now);
+ if (!rv) {
+ if (send_heartbeat_now)
+ panic_halt_ipmi_heartbeat();
+ }
+}
+
+/* We use a semaphore to make sure that only one thing can send a
+ heartbeat at one time, because we only have one copy of the data.
+ The semaphore is claimed when the set_timeout is sent and freed
+ when both messages are free. */
+static atomic_t heartbeat_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(heartbeat_lock);
+static DECLARE_MUTEX_LOCKED(heartbeat_wait_lock);
+static void heartbeat_free_smi(struct ipmi_smi_msg *msg)
+{
+ if (atomic_dec_and_test(&heartbeat_tofree))
+ up(&heartbeat_wait_lock);
+}
+static void heartbeat_free_recv(struct ipmi_recv_msg *msg)
+{
+ if (atomic_dec_and_test(&heartbeat_tofree))
+ up(&heartbeat_wait_lock);
+}
+static struct ipmi_smi_msg heartbeat_smi_msg =
+{
+ .done = heartbeat_free_smi
+};
+static struct ipmi_recv_msg heartbeat_recv_msg =
+{
+ .done = heartbeat_free_recv
+};
+
+static struct ipmi_smi_msg panic_halt_heartbeat_smi_msg =
+{
+ .done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_heartbeat_recv_msg =
+{
+ .done = dummy_recv_free
+};
+
+static int ipmi_heartbeat(void)
+{
+ struct kernel_ipmi_msg msg;
+ int rv;
+ struct ipmi_system_interface_addr addr;
+
+ if (ipmi_ignore_heartbeat) {
+ return 0;
+ }
+
+ if (ipmi_start_timer_on_heartbeat) {
+ ipmi_start_timer_on_heartbeat = 0;
+ ipmi_watchdog_state = action_val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ } else if (pretimeout_since_last_heartbeat) {
+ /* A pretimeout occurred, make sure we set the timeout.
+ We don't want to set the action, though, we want to
+ leave that alone (thus it can't be combined with the
+ above operation. */
+ pretimeout_since_last_heartbeat = 0;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+ }
+
+ down(&heartbeat_lock);
+
+ atomic_set(&heartbeat_tofree, 2);
+
+ /* Don't reset the timer if we have the timer turned off, that
+ re-enables the watchdog. */
+ if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE) {
+ up(&heartbeat_lock);
+ return 0;
+ }
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_RESET_TIMER;
+ msg.data = NULL;
+ msg.data_len = 0;
+ rv = ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ &heartbeat_smi_msg,
+ &heartbeat_recv_msg,
+ 1);
+ if (rv) {
+ up(&heartbeat_lock);
+ printk(KERN_WARNING PFX "heartbeat failure: %d\n",
+ rv);
+ return rv;
+ }
+
+ /* Wait for the heartbeat to be sent. */
+ down(&heartbeat_wait_lock);
+
+ if (heartbeat_recv_msg.msg.data[0] != 0) {
+ /* Got an error in the heartbeat response. It was already
+ reported in ipmi_wdog_msg_handler, but we should return
+ an error here. */
+ rv = -EINVAL;
+ }
+
+ up(&heartbeat_lock);
+
+ return rv;
+}
+
+static void panic_halt_ipmi_heartbeat(void)
+{
+ struct kernel_ipmi_msg msg;
+ struct ipmi_system_interface_addr addr;
+
+
+ /* Don't reset the timer if we have the timer turned off, that
+ re-enables the watchdog. */
+ if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
+ return;
+
+ addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+ addr.channel = IPMI_BMC_CHANNEL;
+ addr.lun = 0;
+
+ msg.netfn = 0x06;
+ msg.cmd = IPMI_WDOG_RESET_TIMER;
+ msg.data = NULL;
+ msg.data_len = 0;
+ ipmi_request_supply_msgs(watchdog_user,
+ (struct ipmi_addr *) &addr,
+ 0,
+ &msg,
+ NULL,
+ &panic_halt_heartbeat_smi_msg,
+ &panic_halt_heartbeat_recv_msg,
+ 1);
+}
+
+static struct watchdog_info ident=
+{
+ .options = 0, /* WDIOF_SETTIMEOUT, */
+ .firmware_version = 1,
+ .identity = "IPMI"
+};
+
+static int ipmi_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int i;
+ int val;
+
+ switch(cmd) {
+ case WDIOC_GETSUPPORT:
+ i = copy_to_user(argp, &ident, sizeof(ident));
+ return i ? -EFAULT : 0;
+
+ case WDIOC_SETTIMEOUT:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ timeout = val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+ case WDIOC_GETTIMEOUT:
+ i = copy_to_user(argp, &timeout, sizeof(timeout));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_SET_PRETIMEOUT:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ pretimeout = val;
+ return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+ case WDIOC_GET_PRETIMEOUT:
+ i = copy_to_user(argp, &pretimeout, sizeof(pretimeout));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_KEEPALIVE:
+ return ipmi_heartbeat();
+
+ case WDIOC_SETOPTIONS:
+ i = copy_from_user(&val, argp, sizeof(int));
+ if (i)
+ return -EFAULT;
+ if (val & WDIOS_DISABLECARD)
+ {
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+ ipmi_start_timer_on_heartbeat = 0;
+ }
+
+ if (val & WDIOS_ENABLECARD)
+ {
+ ipmi_watchdog_state = action_val;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ }
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ val = 0;
+ i = copy_to_user(argp, &val, sizeof(val));
+ if (i)
+ return -EFAULT;
+ return 0;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static ssize_t ipmi_write(struct file *file,
+ const char __user *buf,
+ size_t len,
+ loff_t *ppos)
+{
+ int rv;
+
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ rv = ipmi_heartbeat();
+ if (rv)
+ return rv;
+ return 1;
+ }
+ return 0;
+}
+
+static ssize_t ipmi_read(struct file *file,
+ char __user *buf,
+ size_t count,
+ loff_t *ppos)
+{
+ int rv = 0;
+ wait_queue_t wait;
+
+ if (count <= 0)
+ return 0;
+
+ /* Reading returns if the pretimeout has gone off, and it only does
+ it once per pretimeout. */
+ spin_lock(&ipmi_read_lock);
+ if (!data_to_read) {
+ if (file->f_flags & O_NONBLOCK) {
+ rv = -EAGAIN;
+ goto out;
+ }
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(&read_q, &wait);
+ while (!data_to_read) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock(&ipmi_read_lock);
+ schedule();
+ spin_lock(&ipmi_read_lock);
+ }
+ remove_wait_queue(&read_q, &wait);
+
+ if (signal_pending(current)) {
+ rv = -ERESTARTSYS;
+ goto out;
+ }
+ }
+ data_to_read = 0;
+
+ out:
+ spin_unlock(&ipmi_read_lock);
+
+ if (rv == 0) {
+ if (copy_to_user(buf, &data_to_read, 1))
+ rv = -EFAULT;
+ else
+ rv = 1;
+ }
+
+ return rv;
+}
+
+static int ipmi_open(struct inode *ino, struct file *filep)
+{
+ switch (iminor(ino))
+ {
+ case WATCHDOG_MINOR:
+ if(test_and_set_bit(0, &ipmi_wdog_open))
+ return -EBUSY;
+
+ /* Don't start the timer now, let it start on the
+ first heartbeat. */
+ ipmi_start_timer_on_heartbeat = 1;
+ return nonseekable_open(ino, filep);
+
+ default:
+ return (-ENODEV);
+ }
+}
+
+static unsigned int ipmi_poll(struct file *file, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ poll_wait(file, &read_q, wait);
+
+ spin_lock(&ipmi_read_lock);
+ if (data_to_read)
+ mask |= (POLLIN | POLLRDNORM);
+ spin_unlock(&ipmi_read_lock);
+
+ return mask;
+}
+
+static int ipmi_fasync(int fd, struct file *file, int on)
+{
+ int result;
+
+ result = fasync_helper(fd, file, on, &fasync_q);
+
+ return (result);
+}
+
+static int ipmi_close(struct inode *ino, struct file *filep)
+{
+ if (iminor(ino)==WATCHDOG_MINOR)
+ {
+ if (expect_close == 42) {
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+ clear_bit(0, &ipmi_wdog_open);
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ ipmi_heartbeat();
+ }
+ }
+
+ ipmi_fasync (-1, filep, 0);
+ expect_close = 0;
+
+ return 0;
+}
+
+static struct file_operations ipmi_wdog_fops = {
+ .owner = THIS_MODULE,
+ .read = ipmi_read,
+ .poll = ipmi_poll,
+ .write = ipmi_write,
+ .ioctl = ipmi_ioctl,
+ .open = ipmi_open,
+ .release = ipmi_close,
+ .fasync = ipmi_fasync,
+};
+
+static struct miscdevice ipmi_wdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ipmi_wdog_fops
+};
+
+static DECLARE_RWSEM(register_sem);
+
+static void ipmi_wdog_msg_handler(struct ipmi_recv_msg *msg,
+ void *handler_data)
+{
+ if (msg->msg.data[0] != 0) {
+ printk(KERN_ERR PFX "response: Error %x on cmd %x\n",
+ msg->msg.data[0],
+ msg->msg.cmd);
+ }
+
+ ipmi_free_recv_msg(msg);
+}
+
+static void ipmi_wdog_pretimeout_handler(void *handler_data)
+{
+ if (preaction_val != WDOG_PRETIMEOUT_NONE) {
+ if (preop_val == WDOG_PREOP_PANIC)
+ panic("Watchdog pre-timeout");
+ else if (preop_val == WDOG_PREOP_GIVE_DATA) {
+ spin_lock(&ipmi_read_lock);
+ data_to_read = 1;
+ wake_up_interruptible(&read_q);
+ kill_fasync(&fasync_q, SIGIO, POLL_IN);
+
+ spin_unlock(&ipmi_read_lock);
+ }
+ }
+
+ /* On some machines, the heartbeat will give
+ an error and not work unless we re-enable
+ the timer. So do so. */
+ pretimeout_since_last_heartbeat = 1;
+}
+
+static struct ipmi_user_hndl ipmi_hndlrs =
+{
+ .ipmi_recv_hndl = ipmi_wdog_msg_handler,
+ .ipmi_watchdog_pretimeout = ipmi_wdog_pretimeout_handler
+};
+
+static void ipmi_register_watchdog(int ipmi_intf)
+{
+ int rv = -EBUSY;
+
+ down_write(&register_sem);
+ if (watchdog_user)
+ goto out;
+
+ rv = ipmi_create_user(ipmi_intf, &ipmi_hndlrs, NULL, &watchdog_user);
+ if (rv < 0) {
+ printk(KERN_CRIT PFX "Unable to register with ipmi\n");
+ goto out;
+ }
+
+ ipmi_get_version(watchdog_user,
+ &ipmi_version_major,
+ &ipmi_version_minor);
+
+ rv = misc_register(&ipmi_wdog_miscdev);
+ if (rv < 0) {
+ ipmi_destroy_user(watchdog_user);
+ watchdog_user = NULL;
+ printk(KERN_CRIT PFX "Unable to register misc device\n");
+ }
+
+ out:
+ up_write(&register_sem);
+
+ if ((start_now) && (rv == 0)) {
+ /* Run from startup, so start the timer now. */
+ start_now = 0; /* Disable this function after first startup. */
+ ipmi_watchdog_state = action_val;
+ ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+ printk(KERN_INFO PFX "Starting now!\n");
+ }
+}
+
+#ifdef HAVE_NMI_HANDLER
+static int
+ipmi_nmi(void *dev_id, struct pt_regs *regs, int cpu, int handled)
+{
+ /* If no one else handled the NMI, we assume it was the IPMI
+ watchdog. */
+ if ((!handled) && (preop_val == WDOG_PREOP_PANIC))
+ panic(PFX "pre-timeout");
+
+ /* On some machines, the heartbeat will give
+ an error and not work unless we re-enable
+ the timer. So do so. */
+ pretimeout_since_last_heartbeat = 1;
+
+ return NOTIFY_DONE;
+}
+
+static struct nmi_handler ipmi_nmi_handler =
+{
+ .link = LIST_HEAD_INIT(ipmi_nmi_handler.link),
+ .dev_name = "ipmi_watchdog",
+ .dev_id = NULL,
+ .handler = ipmi_nmi,
+ .priority = 0, /* Call us last. */
+};
+#endif
+
+static int wdog_reboot_handler(struct notifier_block *this,
+ unsigned long code,
+ void *unused)
+{
+ static int reboot_event_handled = 0;
+
+ if ((watchdog_user) && (!reboot_event_handled)) {
+ /* Make sure we only do this once. */
+ reboot_event_handled = 1;
+
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Disable the WDT if we are shutting down. */
+ ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+ panic_halt_ipmi_set_timeout();
+ } else {
+ /* Set a long timer to let the reboot happens, but
+ reboot if it hangs. */
+ timeout = 120;
+ pretimeout = 0;
+ ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+ panic_halt_ipmi_set_timeout();
+ }
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_reboot_notifier = {
+ .notifier_call = wdog_reboot_handler,
+ .next = NULL,
+ .priority = 0
+};
+
+static int wdog_panic_handler(struct notifier_block *this,
+ unsigned long event,
+ void *unused)
+{
+ static int panic_event_handled = 0;
+
+ /* On a panic, if we have a panic timeout, make sure that the thing
+ reboots, even if it hangs during that panic. */
+ if (watchdog_user && !panic_event_handled) {
+ /* Make sure the panic doesn't hang, and make sure we
+ do this only once. */
+ panic_event_handled = 1;
+
+ timeout = 255;
+ pretimeout = 0;
+ ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+ panic_halt_ipmi_set_timeout();
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_panic_notifier = {
+ .notifier_call = wdog_panic_handler,
+ .next = NULL,
+ .priority = 150 /* priority: INT_MAX >= x >= 0 */
+};
+
+
+static void ipmi_new_smi(int if_num)
+{
+ ipmi_register_watchdog(if_num);
+}
+
+static void ipmi_smi_gone(int if_num)
+{
+ /* This can never be called, because once the watchdog is
+ registered, the interface can't go away until the watchdog
+ is unregistered. */
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+ .owner = THIS_MODULE,
+ .new_smi = ipmi_new_smi,
+ .smi_gone = ipmi_smi_gone
+};
+
+static int __init ipmi_wdog_init(void)
+{
+ int rv;
+
+ printk(KERN_INFO PFX "driver version "
+ IPMI_WATCHDOG_VERSION "\n");
+
+ if (strcmp(action, "reset") == 0) {
+ action_val = WDOG_TIMEOUT_RESET;
+ } else if (strcmp(action, "none") == 0) {
+ action_val = WDOG_TIMEOUT_NONE;
+ } else if (strcmp(action, "power_cycle") == 0) {
+ action_val = WDOG_TIMEOUT_POWER_CYCLE;
+ } else if (strcmp(action, "power_off") == 0) {
+ action_val = WDOG_TIMEOUT_POWER_DOWN;
+ } else {
+ action_val = WDOG_TIMEOUT_RESET;
+ printk(KERN_INFO PFX "Unknown action '%s', defaulting to"
+ " reset\n", action);
+ }
+
+ if (strcmp(preaction, "pre_none") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ } else if (strcmp(preaction, "pre_smi") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_SMI;
+#ifdef HAVE_NMI_HANDLER
+ } else if (strcmp(preaction, "pre_nmi") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_NMI;
+#endif
+ } else if (strcmp(preaction, "pre_int") == 0) {
+ preaction_val = WDOG_PRETIMEOUT_MSG_INT;
+ } else {
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ printk(KERN_INFO PFX "Unknown preaction '%s', defaulting to"
+ " none\n", preaction);
+ }
+
+ if (strcmp(preop, "preop_none") == 0) {
+ preop_val = WDOG_PREOP_NONE;
+ } else if (strcmp(preop, "preop_panic") == 0) {
+ preop_val = WDOG_PREOP_PANIC;
+ } else if (strcmp(preop, "preop_give_data") == 0) {
+ preop_val = WDOG_PREOP_GIVE_DATA;
+ } else {
+ preop_val = WDOG_PREOP_NONE;
+ printk(KERN_INFO PFX "Unknown preop '%s', defaulting to"
+ " none\n", preop);
+ }
+
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI) {
+ if (preop_val == WDOG_PREOP_GIVE_DATA) {
+ printk(KERN_WARNING PFX "Pretimeout op is to give data"
+ " but NMI pretimeout is enabled, setting"
+ " pretimeout op to none\n");
+ preop_val = WDOG_PREOP_NONE;
+ }
+#ifdef CONFIG_X86_LOCAL_APIC
+ if (nmi_watchdog == NMI_IO_APIC) {
+ printk(KERN_WARNING PFX "nmi_watchdog is set to IO APIC"
+ " mode (value is %d), that is incompatible"
+ " with using NMI in the IPMI watchdog."
+ " Disabling IPMI nmi pretimeout.\n",
+ nmi_watchdog);
+ preaction_val = WDOG_PRETIMEOUT_NONE;
+ } else {
+#endif
+ rv = request_nmi(&ipmi_nmi_handler);
+ if (rv) {
+ printk(KERN_WARNING PFX "Can't register nmi handler\n");
+ return rv;
+ }
+#ifdef CONFIG_X86_LOCAL_APIC
+ }
+#endif
+ }
+#endif
+
+ rv = ipmi_smi_watcher_register(&smi_watcher);
+ if (rv) {
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI)
+ release_nmi(&ipmi_nmi_handler);
+#endif
+ printk(KERN_WARNING PFX "can't register smi watcher\n");
+ return rv;
+ }
+
+ register_reboot_notifier(&wdog_reboot_notifier);
+ notifier_chain_register(&panic_notifier_list, &wdog_panic_notifier);
+
+ return 0;
+}
+
+static __exit void ipmi_unregister_watchdog(void)
+{
+ int rv;
+
+ down_write(&register_sem);
+
+#ifdef HAVE_NMI_HANDLER
+ if (preaction_val == WDOG_PRETIMEOUT_NMI)
+ release_nmi(&ipmi_nmi_handler);
+#endif
+
+ notifier_chain_unregister(&panic_notifier_list, &wdog_panic_notifier);
+ unregister_reboot_notifier(&wdog_reboot_notifier);
+
+ if (! watchdog_user)
+ goto out;
+
+ /* Make sure no one can call us any more. */
+ misc_deregister(&ipmi_wdog_miscdev);
+
+ /* Wait to make sure the message makes it out. The lower layer has
+ pointers to our buffers, we want to make sure they are done before
+ we release our memory. */
+ while (atomic_read(&set_timeout_tofree)) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ }
+
+ /* Disconnect from IPMI. */
+ rv = ipmi_destroy_user(watchdog_user);
+ if (rv) {
+ printk(KERN_WARNING PFX "error unlinking from IPMI: %d\n",
+ rv);
+ }
+ watchdog_user = NULL;
+
+ out:
+ up_write(&register_sem);
+}
+
+static void __exit ipmi_wdog_exit(void)
+{
+ ipmi_smi_watcher_unregister(&smi_watcher);
+ ipmi_unregister_watchdog();
+}
+module_exit(ipmi_wdog_exit);
+module_init(ipmi_wdog_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c
new file mode 100644
index 000000000000..601c7fccb4cf
--- /dev/null
+++ b/drivers/char/isicom.c
@@ -0,0 +1,2079 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Original driver code supplied by Multi-Tech
+ *
+ * Changes
+ * 1/9/98 alan@redhat.com Merge to 2.0.x kernel tree
+ * Obtain and use official major/minors
+ * Loader switched to a misc device
+ * (fixed range check bug as a side effect)
+ * Printk clean up
+ * 9/12/98 alan@redhat.com Rough port to 2.1.x
+ *
+ * 10/6/99 sameer Merged the ISA and PCI drivers to
+ * a new unified driver.
+ *
+ * 3/9/99 sameer Added support for ISI4616 cards.
+ *
+ * 16/9/99 sameer We do not force RTS low anymore.
+ * This is to prevent the firmware
+ * from getting confused.
+ *
+ * 26/10/99 sameer Cosmetic changes:The driver now
+ * dumps the Port Count information
+ * along with I/O address and IRQ.
+ *
+ * 13/12/99 sameer Fixed the problem with IRQ sharing.
+ *
+ * 10/5/00 sameer Fixed isicom_shutdown_board()
+ * to not lower DTR on all the ports
+ * when the last port on the card is
+ * closed.
+ *
+ * 10/5/00 sameer Signal mask setup command added
+ * to isicom_setup_port and
+ * isicom_shutdown_port.
+ *
+ * 24/5/00 sameer The driver is now SMP aware.
+ *
+ *
+ * 27/11/00 Vinayak P Risbud Fixed the Driver Crash Problem
+ *
+ *
+ * 03/01/01 anil .s Added support for resetting the
+ * internal modems on ISI cards.
+ *
+ * 08/02/01 anil .s Upgraded the driver for kernel
+ * 2.4.x
+ *
+ * 11/04/01 Kevin Fixed firmware load problem with
+ * ISIHP-4X card
+ *
+ * 30/04/01 anil .s Fixed the remote login through
+ * ISI port problem. Now the link
+ * does not go down before password
+ * prompt.
+ *
+ * 03/05/01 anil .s Fixed the problem with IRQ sharing
+ * among ISI-PCI cards.
+ *
+ * 03/05/01 anil .s Added support to display the version
+ * info during insmod as well as module
+ * listing by lsmod.
+ *
+ * 10/05/01 anil .s Done the modifications to the source
+ * file and Install script so that the
+ * same installation can be used for
+ * 2.2.x and 2.4.x kernel.
+ *
+ * 06/06/01 anil .s Now we drop both dtr and rts during
+ * shutdown_port as well as raise them
+ * during isicom_config_port.
+ *
+ * 09/06/01 acme@conectiva.com.br use capable, not suser, do
+ * restore_flags on failure in
+ * isicom_send_break, verify put_user
+ * result
+ *
+ * 11/02/03 ranjeeth Added support for 230 Kbps and 460 Kbps
+ * Baud index extended to 21
+ *
+ * 20/03/03 ranjeeth Made to work for Linux Advanced server.
+ * Taken care of license warning.
+ *
+ * 10/12/03 Ravindra Made to work for Fedora Core 1 of
+ * Red Hat Distribution
+ *
+ * 06/01/05 Alan Cox Merged the ISI and base kernel strands
+ * into a single 2.6 driver
+ *
+ * ***********************************************************
+ *
+ * To use this driver you also need the support package. You
+ * can find this in RPM format on
+ * ftp://ftp.linux.org.uk/pub/linux/alan
+ *
+ * You can find the original tools for this direct from Multitech
+ * ftp://ftp.multitech.com/ISI-Cards/
+ *
+ * Having installed the cards the module options (/etc/modprobe.conf)
+ *
+ * options isicom io=card1,card2,card3,card4 irq=card1,card2,card3,card4
+ *
+ * Omit those entries for boards you don't have installed.
+ *
+ * TODO
+ * Hotplug
+ * Merge testing
+ * 64-bit verification
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/termios.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/serial.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/pci.h>
+
+#include <linux/isicom.h>
+
+static struct pci_device_id isicom_pci_tbl[] = {
+ { VENDOR_ID, 0x2028, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2057, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { VENDOR_ID, 0x2058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, isicom_pci_tbl);
+
+static int prev_card = 3; /* start servicing isi_card[0] */
+static struct tty_driver *isicom_normal;
+
+static struct timer_list tx;
+static char re_schedule = 1;
+#ifdef ISICOM_DEBUG
+static unsigned long tx_count = 0;
+#endif
+
+static int ISILoad_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
+
+static void isicom_tx(unsigned long _data);
+static void isicom_start(struct tty_struct * tty);
+
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+/* baud index mappings from linux defns to isi */
+
+static signed char linuxb_to_isib[] = {
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17,
+ 18, 19
+};
+
+struct isi_board {
+ unsigned short base;
+ unsigned char irq;
+ unsigned char port_count;
+ unsigned short status;
+ unsigned short port_status; /* each bit represents a single port */
+ unsigned short shift_count;
+ struct isi_port * ports;
+ signed char count;
+ unsigned char isa;
+ spinlock_t card_lock; /* Card wide lock 11/5/00 -sameer */
+ unsigned long flags;
+};
+
+struct isi_port {
+ unsigned short magic;
+ unsigned int flags;
+ int count;
+ int blocked_open;
+ int close_delay;
+ unsigned short channel;
+ unsigned short status;
+ unsigned short closing_wait;
+ struct isi_board * card;
+ struct tty_struct * tty;
+ wait_queue_head_t close_wait;
+ wait_queue_head_t open_wait;
+ struct work_struct hangup_tq;
+ struct work_struct bh_tqueue;
+ unsigned char * xmit_buf;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+};
+
+static struct isi_board isi_card[BOARD_COUNT];
+static struct isi_port isi_ports[PORT_COUNT];
+
+/*
+ * Locking functions for card level locking. We need to own both
+ * the kernel lock for the card and have the card in a position that
+ * it wants to talk.
+ */
+
+static int lock_card(struct isi_board *card)
+{
+ char retries;
+ unsigned short base = card->base;
+
+ for (retries = 0; retries < 100; retries++) {
+ spin_lock_irqsave(&card->card_lock, card->flags);
+ if (inw(base + 0xe) & 0x1) {
+ return 1;
+ } else {
+ spin_unlock_irqrestore(&card->card_lock, card->flags);
+ udelay(1000); /* 1ms */
+ }
+ }
+ printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%x)\n", card->base);
+ return 0; /* Failed to aquire the card! */
+}
+
+static int lock_card_at_interrupt(struct isi_board *card)
+{
+ unsigned char retries;
+ unsigned short base = card->base;
+
+ for (retries = 0; retries < 200; retries++) {
+ spin_lock_irqsave(&card->card_lock, card->flags);
+
+ if (inw(base + 0xe) & 0x1)
+ return 1;
+ else
+ spin_unlock_irqrestore(&card->card_lock, card->flags);
+ }
+ /* Failing in interrupt is an acceptable event */
+ return 0; /* Failed to aquire the card! */
+}
+
+static void unlock_card(struct isi_board *card)
+{
+ spin_unlock_irqrestore(&card->card_lock, card->flags);
+}
+
+/*
+ * ISI Card specific ops ...
+ */
+
+static void raise_dtr(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0504, base);
+ InterruptTheCard(base);
+ port->status |= ISI_DTR;
+ unlock_card(card);
+}
+
+static inline void drop_dtr(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0404, base);
+ InterruptTheCard(base);
+ port->status &= ~ISI_DTR;
+ unlock_card(card);
+}
+
+static inline void raise_rts(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0a04, base);
+ InterruptTheCard(base);
+ port->status |= ISI_RTS;
+ unlock_card(card);
+}
+static inline void drop_rts(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0804, base);
+ InterruptTheCard(base);
+ port->status &= ~ISI_RTS;
+ unlock_card(card);
+}
+
+static inline void raise_dtr_rts(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0f04, base);
+ InterruptTheCard(base);
+ port->status |= (ISI_DTR | ISI_RTS);
+ unlock_card(card);
+}
+
+static void drop_dtr_rts(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw(0x0c04, base);
+ InterruptTheCard(base);
+ port->status &= ~(ISI_RTS | ISI_DTR);
+ unlock_card(card);
+}
+
+static inline void kill_queue(struct isi_port * port, short queue)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+ unsigned char channel = port->channel;
+
+ if (!lock_card(card))
+ return;
+
+ outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+ outw((queue << 8) | 0x06, base);
+ InterruptTheCard(base);
+ unlock_card(card);
+}
+
+
+/*
+ * Firmware loader driver specific routines. This needs to mostly die
+ * and be replaced with request_firmware.
+ */
+
+static struct file_operations ISILoad_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = ISILoad_ioctl,
+};
+
+static struct miscdevice isiloader_device = {
+ ISILOAD_MISC_MINOR, "isictl", &ISILoad_fops
+};
+
+
+static inline int WaitTillCardIsFree(unsigned short base)
+{
+ unsigned long count=0;
+ while( (!(inw(base+0xe) & 0x1)) && (count++ < 6000000));
+ if (inw(base+0xe)&0x1)
+ return 0;
+ else
+ return 1;
+}
+
+static int ISILoad_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int card, i, j, signature, status, portcount = 0;
+ unsigned long t;
+ unsigned short word_count, base;
+ bin_frame frame;
+ void __user *argp = (void __user *)arg;
+ /* exec_record exec_rec; */
+
+ if(get_user(card, (int __user *)argp))
+ return -EFAULT;
+
+ if(card < 0 || card >= BOARD_COUNT)
+ return -ENXIO;
+
+ base=isi_card[card].base;
+
+ if(base==0)
+ return -ENXIO; /* disabled or not used */
+
+ switch(cmd) {
+ case MIOCTL_RESET_CARD:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ printk(KERN_DEBUG "ISILoad:Resetting Card%d at 0x%x ",card+1,base);
+
+ inw(base+0x8);
+
+ for(t=jiffies+HZ/100;time_before(jiffies, t););
+
+ outw(0,base+0x8); /* Reset */
+
+ for(j=1;j<=3;j++) {
+ for(t=jiffies+HZ;time_before(jiffies, t););
+ printk(".");
+ }
+ signature=(inw(base+0x4)) & 0xff;
+ if (isi_card[card].isa) {
+
+ if (!(inw(base+0xe) & 0x1) || (inw(base+0x2))) {
+#ifdef ISICOM_DEBUG
+ printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
+#endif
+ printk("\nISILoad:ISA Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+ return -EIO;
+ }
+ }
+ else {
+ portcount = inw(base+0x2);
+ if (!(inw(base+0xe) & 0x1) || ((portcount!=0) && (portcount!=4) && (portcount!=8))) {
+#ifdef ISICOM_DEBUG
+ printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
+#endif
+ printk("\nISILoad:PCI Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+ return -EIO;
+ }
+ }
+ switch(signature) {
+ case 0xa5:
+ case 0xbb:
+ case 0xdd:
+ if (isi_card[card].isa)
+ isi_card[card].port_count = 8;
+ else {
+ if (portcount == 4)
+ isi_card[card].port_count = 4;
+ else
+ isi_card[card].port_count = 8;
+ }
+ isi_card[card].shift_count = 12;
+ break;
+
+ case 0xcc: isi_card[card].port_count = 16;
+ isi_card[card].shift_count = 11;
+ break;
+
+ default: printk("ISILoad:Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+#ifdef ISICOM_DEBUG
+ printk("Sig=0x%x\n",signature);
+#endif
+ return -EIO;
+ }
+ printk("-Done\n");
+ return put_user(signature,(unsigned __user *)argp);
+
+ case MIOCTL_LOAD_FIRMWARE:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if(copy_from_user(&frame, argp, sizeof(bin_frame)))
+ return -EFAULT;
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ outw(0xf0,base); /* start upload sequence */
+ outw(0x00,base);
+ outw((frame.addr), base);/* lsb of adderess */
+
+ word_count=(frame.count >> 1) + frame.count % 2;
+ outw(word_count, base);
+ InterruptTheCard(base);
+
+ for(i=0;i<=0x2f;i++); /* a wee bit of delay */
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ if ((status=inw(base+0x4))!=0) {
+ printk(KERN_WARNING "ISILoad:Card%d rejected load header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n",
+ card+1, frame.addr, frame.count, status);
+ return -EIO;
+ }
+ outsw(base, (void *) frame.bin_data, word_count);
+
+ InterruptTheCard(base);
+
+ for(i=0;i<=0x0f;i++); /* another wee bit of delay */
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ if ((status=inw(base+0x4))!=0) {
+ printk(KERN_ERR "ISILoad:Card%d got out of sync.Card Status:0x%x\n",card+1, status);
+ return -EIO;
+ }
+ return 0;
+
+ case MIOCTL_READ_FIRMWARE:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ if(copy_from_user(&frame, argp, sizeof(bin_header)))
+ return -EFAULT;
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ outw(0xf1,base); /* start download sequence */
+ outw(0x00,base);
+ outw((frame.addr), base);/* lsb of adderess */
+
+ word_count=(frame.count >> 1) + frame.count % 2;
+ outw(word_count+1, base);
+ InterruptTheCard(base);
+
+ for(i=0;i<=0xf;i++); /* a wee bit of delay */
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ if ((status=inw(base+0x4))!=0) {
+ printk(KERN_WARNING "ISILoad:Card%d rejected verify header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n",
+ card+1, frame.addr, frame.count, status);
+ return -EIO;
+ }
+
+ inw(base);
+ insw(base, frame.bin_data, word_count);
+ InterruptTheCard(base);
+
+ for(i=0;i<=0x0f;i++); /* another wee bit of delay */
+
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ if ((status=inw(base+0x4))!=0) {
+ printk(KERN_ERR "ISILoad:Card%d verify got out of sync.Card Status:0x%x\n",card+1, status);
+ return -EIO;
+ }
+
+ if(copy_to_user(argp, &frame, sizeof(bin_frame)))
+ return -EFAULT;
+ return 0;
+
+ case MIOCTL_XFER_CTRL:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (WaitTillCardIsFree(base))
+ return -EIO;
+
+ outw(0xf2, base);
+ outw(0x800, base);
+ outw(0x0, base);
+ outw(0x0, base);
+ InterruptTheCard(base);
+ outw(0x0, base+0x4); /* for ISI4608 cards */
+
+ isi_card[card].status |= FIRMWARE_LOADED;
+ return 0;
+
+ default:
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISILoad: Received Ioctl cmd 0x%x.\n", cmd);
+#endif
+ return -ENOIOCTLCMD;
+
+ }
+
+}
+
+
+/*
+ * ISICOM Driver specific routines ...
+ *
+ */
+
+static inline int isicom_paranoia_check(struct isi_port const * port, char *name,
+ const char * routine)
+{
+#ifdef ISICOM_DEBUG
+ static const char * badmagic =
+ KERN_WARNING "ISICOM: Warning: bad isicom magic for dev %s in %s.\n";
+ static const char * badport =
+ KERN_WARNING "ISICOM: Warning: NULL isicom port for dev %s in %s.\n";
+ if (!port) {
+ printk(badport, name, routine);
+ return 1;
+ }
+ if (port->magic != ISICOM_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/*
+ * Transmitter.
+ *
+ * We shovel data into the card buffers on a regular basis. The card
+ * will do the rest of the work for us.
+ */
+
+static void isicom_tx(unsigned long _data)
+{
+ short count = (BOARD_COUNT-1), card, base;
+ short txcount, wrd, residue, word_count, cnt;
+ struct isi_port * port;
+ struct tty_struct * tty;
+
+#ifdef ISICOM_DEBUG
+ ++tx_count;
+#endif
+
+ /* find next active board */
+ card = (prev_card + 1) & 0x0003;
+ while(count-- > 0) {
+ if (isi_card[card].status & BOARD_ACTIVE)
+ break;
+ card = (card + 1) & 0x0003;
+ }
+ if (!(isi_card[card].status & BOARD_ACTIVE))
+ goto sched_again;
+
+ prev_card = card;
+
+ count = isi_card[card].port_count;
+ port = isi_card[card].ports;
+ base = isi_card[card].base;
+ for (;count > 0;count--, port++) {
+ if (!lock_card_at_interrupt(&isi_card[card]))
+ continue;
+ /* port not active or tx disabled to force flow control */
+ if (!(port->flags & ASYNC_INITIALIZED) ||
+ !(port->status & ISI_TXOK))
+ unlock_card(&isi_card[card]);
+ continue;
+
+ tty = port->tty;
+
+
+ if(tty == NULL) {
+ unlock_card(&isi_card[card]);
+ continue;
+ }
+
+ txcount = min_t(short, TX_SIZE, port->xmit_cnt);
+ if (txcount <= 0 || tty->stopped || tty->hw_stopped) {
+ unlock_card(&isi_card[card]);
+ continue;
+ }
+ if (!(inw(base + 0x02) & (1 << port->channel))) {
+ unlock_card(&isi_card[card]);
+ continue;
+ }
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: txing %d bytes, port%d.\n",
+ txcount, port->channel+1);
+#endif
+ outw((port->channel << isi_card[card].shift_count) | txcount
+ , base);
+ residue = NO;
+ wrd = 0;
+ while (1) {
+ cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE - port->xmit_tail));
+ if (residue == YES) {
+ residue = NO;
+ if (cnt > 0) {
+ wrd |= (port->xmit_buf[port->xmit_tail] << 8);
+ port->xmit_tail = (port->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+ port->xmit_cnt--;
+ txcount--;
+ cnt--;
+ outw(wrd, base);
+ }
+ else {
+ outw(wrd, base);
+ break;
+ }
+ }
+ if (cnt <= 0) break;
+ word_count = cnt >> 1;
+ outsw(base, port->xmit_buf+port->xmit_tail, word_count);
+ port->xmit_tail = (port->xmit_tail + (word_count << 1)) &
+ (SERIAL_XMIT_SIZE - 1);
+ txcount -= (word_count << 1);
+ port->xmit_cnt -= (word_count << 1);
+ if (cnt & 0x0001) {
+ residue = YES;
+ wrd = port->xmit_buf[port->xmit_tail];
+ port->xmit_tail = (port->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+ port->xmit_cnt--;
+ txcount--;
+ }
+ }
+
+ InterruptTheCard(base);
+ if (port->xmit_cnt <= 0)
+ port->status &= ~ISI_TXOK;
+ if (port->xmit_cnt <= WAKEUP_CHARS)
+ schedule_work(&port->bh_tqueue);
+ unlock_card(&isi_card[card]);
+ }
+
+ /* schedule another tx for hopefully in about 10ms */
+sched_again:
+ if (!re_schedule)
+ return;
+ init_timer(&tx);
+ tx.expires = jiffies + HZ/100;
+ tx.data = 0;
+ tx.function = isicom_tx;
+ add_timer(&tx);
+
+ return;
+}
+
+/* Interrupt handlers */
+
+
+static void isicom_bottomhalf(void * data)
+{
+ struct isi_port * port = (struct isi_port *) data;
+ struct tty_struct * tty = port->tty;
+
+ if (!tty)
+ return;
+
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+}
+
+/*
+ * Main interrupt handler routine
+ */
+
+static irqreturn_t isicom_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ struct isi_board * card;
+ struct isi_port * port;
+ struct tty_struct * tty;
+ unsigned short base, header, word_count, count;
+ unsigned char channel;
+ short byte_count;
+
+ card = (struct isi_board *) dev_id;
+
+ if (!card || !(card->status & FIRMWARE_LOADED))
+ return IRQ_NONE;
+
+ base = card->base;
+ spin_lock(&card->card_lock);
+
+ if (card->isa == NO) {
+ /*
+ * disable any interrupts from the PCI card and lower the
+ * interrupt line
+ */
+ outw(0x8000, base+0x04);
+ ClearInterrupt(base);
+ }
+
+ inw(base); /* get the dummy word out */
+ header = inw(base);
+ channel = (header & 0x7800) >> card->shift_count;
+ byte_count = header & 0xff;
+
+ if (channel + 1 > card->port_count) {
+ printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%x): %d(channel) > port_count.\n",
+ base, channel+1);
+ if (card->isa)
+ ClearInterrupt(base);
+ else
+ outw(0x0000, base+0x04); /* enable interrupts */
+ spin_unlock(&card->card_lock);
+ return IRQ_HANDLED;
+ }
+ port = card->ports + channel;
+ if (!(port->flags & ASYNC_INITIALIZED)) {
+ if (card->isa)
+ ClearInterrupt(base);
+ else
+ outw(0x0000, base+0x04); /* enable interrupts */
+ return IRQ_HANDLED;
+ }
+
+ tty = port->tty;
+ if (tty == NULL) {
+ word_count = byte_count >> 1;
+ while(byte_count > 1) {
+ inw(base);
+ byte_count -= 2;
+ }
+ if (byte_count & 0x01)
+ inw(base);
+ if (card->isa == YES)
+ ClearInterrupt(base);
+ else
+ outw(0x0000, base+0x04); /* enable interrupts */
+ spin_unlock(&card->card_lock);
+ return IRQ_HANDLED;
+ }
+
+ if (header & 0x8000) { /* Status Packet */
+ header = inw(base);
+ switch(header & 0xff) {
+ case 0: /* Change in EIA signals */
+
+ if (port->flags & ASYNC_CHECK_CD) {
+ if (port->status & ISI_DCD) {
+ if (!(header & ISI_DCD)) {
+ /* Carrier has been lost */
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: interrupt: DCD->low.\n");
+#endif
+ port->status &= ~ISI_DCD;
+ schedule_work(&port->hangup_tq);
+ }
+ }
+ else {
+ if (header & ISI_DCD) {
+ /* Carrier has been detected */
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: interrupt: DCD->high.\n");
+#endif
+ port->status |= ISI_DCD;
+ wake_up_interruptible(&port->open_wait);
+ }
+ }
+ }
+ else {
+ if (header & ISI_DCD)
+ port->status |= ISI_DCD;
+ else
+ port->status &= ~ISI_DCD;
+ }
+
+ if (port->flags & ASYNC_CTS_FLOW) {
+ if (port->tty->hw_stopped) {
+ if (header & ISI_CTS) {
+ port->tty->hw_stopped = 0;
+ /* start tx ing */
+ port->status |= (ISI_TXOK | ISI_CTS);
+ schedule_work(&port->bh_tqueue);
+ }
+ }
+ else {
+ if (!(header & ISI_CTS)) {
+ port->tty->hw_stopped = 1;
+ /* stop tx ing */
+ port->status &= ~(ISI_TXOK | ISI_CTS);
+ }
+ }
+ }
+ else {
+ if (header & ISI_CTS)
+ port->status |= ISI_CTS;
+ else
+ port->status &= ~ISI_CTS;
+ }
+
+ if (header & ISI_DSR)
+ port->status |= ISI_DSR;
+ else
+ port->status &= ~ISI_DSR;
+
+ if (header & ISI_RI)
+ port->status |= ISI_RI;
+ else
+ port->status &= ~ISI_RI;
+
+ break;
+
+ case 1: /* Received Break !!! */
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ break;
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ *tty->flip.char_buf_ptr++ = 0;
+ tty->flip.count++;
+ if (port->flags & ASYNC_SAK)
+ do_SAK(tty);
+ schedule_delayed_work(&tty->flip.work, 1);
+ break;
+
+ case 2: /* Statistics */
+ printk(KERN_DEBUG "ISICOM: isicom_interrupt: stats!!!.\n");
+ break;
+
+ default:
+ printk(KERN_WARNING "ISICOM: Intr: Unknown code in status packet.\n");
+ break;
+ }
+ }
+ else { /* Data Packet */
+ count = min_t(unsigned short, byte_count, (TTY_FLIPBUF_SIZE - tty->flip.count));
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: Intr: Can rx %d of %d bytes.\n",
+ count, byte_count);
+#endif
+ word_count = count >> 1;
+ insw(base, tty->flip.char_buf_ptr, word_count);
+ tty->flip.char_buf_ptr += (word_count << 1);
+ byte_count -= (word_count << 1);
+ if (count & 0x0001) {
+ *tty->flip.char_buf_ptr++ = (char)(inw(base) & 0xff);
+ byte_count -= 2;
+ }
+ memset(tty->flip.flag_buf_ptr, 0, count);
+ tty->flip.flag_buf_ptr += count;
+ tty->flip.count += count;
+
+ if (byte_count > 0) {
+ printk(KERN_DEBUG "ISICOM: Intr(0x%x:%d): Flip buffer overflow! dropping bytes...\n",
+ base, channel+1);
+ while(byte_count > 0) { /* drain out unread xtra data */
+ inw(base);
+ byte_count -= 2;
+ }
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+ if (card->isa == YES)
+ ClearInterrupt(base);
+ else
+ outw(0x0000, base+0x04); /* enable interrupts */
+ return IRQ_HANDLED;
+}
+
+static void isicom_config_port(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ struct tty_struct * tty;
+ unsigned long baud;
+ unsigned short channel_setup, base = card->base;
+ unsigned short channel = port->channel, shift_count = card->shift_count;
+ unsigned char flow_ctrl;
+
+ if (!(tty = port->tty) || !tty->termios)
+ return;
+ baud = C_BAUD(tty);
+ if (baud & CBAUDEX) {
+ baud &= ~CBAUDEX;
+
+ /* if CBAUDEX bit is on and the baud is set to either 50 or 75
+ * then the card is programmed for 57.6Kbps or 115Kbps
+ * respectively.
+ */
+
+ if (baud < 1 || baud > 2)
+ port->tty->termios->c_cflag &= ~CBAUDEX;
+ else
+ baud += 15;
+ }
+ if (baud == 15) {
+
+ /* the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set
+ * by the set_serial_info ioctl ... this is done by
+ * the 'setserial' utility.
+ */
+
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baud++; /* 57.6 Kbps */
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baud +=2; /* 115 Kbps */
+ }
+ if (linuxb_to_isib[baud] == -1) {
+ /* hang up */
+ drop_dtr(port);
+ return;
+ }
+ else
+ raise_dtr(port);
+
+ if (lock_card(card)) {
+ outw(0x8000 | (channel << shift_count) |0x03, base);
+ outw(linuxb_to_isib[baud] << 8 | 0x03, base);
+ channel_setup = 0;
+ switch(C_CSIZE(tty)) {
+ case CS5:
+ channel_setup |= ISICOM_CS5;
+ break;
+ case CS6:
+ channel_setup |= ISICOM_CS6;
+ break;
+ case CS7:
+ channel_setup |= ISICOM_CS7;
+ break;
+ case CS8:
+ channel_setup |= ISICOM_CS8;
+ break;
+ }
+
+ if (C_CSTOPB(tty))
+ channel_setup |= ISICOM_2SB;
+ if (C_PARENB(tty)) {
+ channel_setup |= ISICOM_EVPAR;
+ if (C_PARODD(tty))
+ channel_setup |= ISICOM_ODPAR;
+ }
+ outw(channel_setup, base);
+ InterruptTheCard(base);
+ unlock_card(card);
+ }
+ if (C_CLOCAL(tty))
+ port->flags &= ~ASYNC_CHECK_CD;
+ else
+ port->flags |= ASYNC_CHECK_CD;
+
+ /* flow control settings ...*/
+ flow_ctrl = 0;
+ port->flags &= ~ASYNC_CTS_FLOW;
+ if (C_CRTSCTS(tty)) {
+ port->flags |= ASYNC_CTS_FLOW;
+ flow_ctrl |= ISICOM_CTSRTS;
+ }
+ if (I_IXON(tty))
+ flow_ctrl |= ISICOM_RESPOND_XONXOFF;
+ if (I_IXOFF(tty))
+ flow_ctrl |= ISICOM_INITIATE_XONXOFF;
+
+ if (lock_card(card)) {
+ outw(0x8000 | (channel << shift_count) |0x04, base);
+ outw(flow_ctrl << 8 | 0x05, base);
+ outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
+ InterruptTheCard(base);
+ unlock_card(card);
+ }
+
+ /* rx enabled -> enable port for rx on the card */
+ if (C_CREAD(tty)) {
+ card->port_status |= (1 << channel);
+ outw(card->port_status, base + 0x02);
+ }
+}
+
+/* open et all */
+
+static inline void isicom_setup_board(struct isi_board * bp)
+{
+ int channel;
+ struct isi_port * port;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bp->card_lock, flags);
+ if (bp->status & BOARD_ACTIVE) {
+ spin_unlock_irqrestore(&bp->card_lock, flags);
+ return;
+ }
+ port = bp->ports;
+ bp->status |= BOARD_ACTIVE;
+ spin_unlock_irqrestore(&bp->card_lock, flags);
+ for(channel = 0; channel < bp->port_count; channel++, port++)
+ drop_dtr_rts(port);
+ return;
+}
+
+static int isicom_setup_port(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ unsigned long flags;
+
+ if (port->flags & ASYNC_INITIALIZED) {
+ return 0;
+ }
+ if (!port->xmit_buf) {
+ unsigned long page;
+
+ if (!(page = get_zeroed_page(GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (port->xmit_buf) {
+ free_page(page);
+ return -ERESTARTSYS;
+ }
+ port->xmit_buf = (unsigned char *) page;
+ }
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (port->tty)
+ clear_bit(TTY_IO_ERROR, &port->tty->flags);
+ if (port->count == 1)
+ card->count++;
+
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+
+ /* discard any residual data */
+ kill_queue(port, ISICOM_KILLTX | ISICOM_KILLRX);
+
+ isicom_config_port(port);
+ port->flags |= ASYNC_INITIALIZED;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ return 0;
+}
+
+static int block_til_ready(struct tty_struct * tty, struct file * filp, struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ int do_clocal = 0, retval;
+ unsigned long flags;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /* block if port is in the process of being closed */
+
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: block_til_ready: close in progress.\n");
+#endif
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ /* if non-blocking mode is set ... */
+
+ if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) {
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: block_til_ready: non-block mode.\n");
+#endif
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /* block waiting for DCD to be asserted, and while
+ callout dev is busy */
+ retval = 0;
+ add_wait_queue(&port->open_wait, &wait);
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (!tty_hung_up_p(filp))
+ port->count--;
+ port->blocked_open++;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ while (1) {
+ raise_dtr_rts(port);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || (port->status & ISI_DCD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&port->open_wait, &wait);
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (!tty_hung_up_p(filp))
+ port->count++;
+ port->blocked_open--;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ if (retval)
+ return retval;
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+}
+
+static int isicom_open(struct tty_struct * tty, struct file * filp)
+{
+ struct isi_port * port;
+ struct isi_board * card;
+ unsigned int line, board;
+ int error;
+
+ line = tty->index;
+ if (line < 0 || line > PORT_COUNT-1)
+ return -ENODEV;
+ board = BOARD(line);
+ card = &isi_card[board];
+
+ if (!(card->status & FIRMWARE_LOADED))
+ return -ENODEV;
+
+ /* open on a port greater than the port count for the card !!! */
+ if (line > ((board * 16) + card->port_count - 1))
+ return -ENODEV;
+
+ port = &isi_ports[line];
+ if (isicom_paranoia_check(port, tty->name, "isicom_open"))
+ return -ENODEV;
+
+ isicom_setup_board(card);
+
+ port->count++;
+ tty->driver_data = port;
+ port->tty = tty;
+ if ((error = isicom_setup_port(port))!=0)
+ return error;
+ if ((error = block_til_ready(tty, filp, port))!=0)
+ return error;
+
+ return 0;
+}
+
+/* close et all */
+
+static inline void isicom_shutdown_board(struct isi_board * bp)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&bp->card_lock, flags);
+ if (bp->status & BOARD_ACTIVE) {
+ bp->status &= ~BOARD_ACTIVE;
+ }
+ spin_unlock_irqrestore(&bp->card_lock, flags);
+}
+
+static void isicom_shutdown_port(struct isi_port * port)
+{
+ struct isi_board * card = port->card;
+ struct tty_struct * tty;
+ unsigned long flags;
+
+ tty = port->tty;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (!(port->flags & ASYNC_INITIALIZED)) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return;
+ }
+ if (port->xmit_buf) {
+ free_page((unsigned long) port->xmit_buf);
+ port->xmit_buf = NULL;
+ }
+ port->flags &= ~ASYNC_INITIALIZED;
+ /* 3rd October 2000 : Vinayak P Risbud */
+ port->tty = NULL;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ /*Fix done by Anil .S on 30-04-2001
+ remote login through isi port has dtr toggle problem
+ due to which the carrier drops before the password prompt
+ appears on the remote end. Now we drop the dtr only if the
+ HUPCL(Hangup on close) flag is set for the tty*/
+
+ if (C_HUPCL(tty))
+ /* drop dtr on this port */
+ drop_dtr(port);
+
+ /* any other port uninits */
+ if (tty)
+ set_bit(TTY_IO_ERROR, &tty->flags);
+
+ if (--card->count < 0) {
+ printk(KERN_DEBUG "ISICOM: isicom_shutdown_port: bad board(0x%x) count %d.\n",
+ card->base, card->count);
+ card->count = 0;
+ }
+
+ /* last port was closed , shutdown that boad too */
+ if(C_HUPCL(tty)) {
+ if (!card->count)
+ isicom_shutdown_board(card);
+ }
+}
+
+static void isicom_close(struct tty_struct * tty, struct file * filp)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+ unsigned long flags;
+
+ if (!port)
+ return;
+ if (isicom_paranoia_check(port, tty->name, "isicom_close"))
+ return;
+
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: Close start!!!.\n");
+#endif
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (tty_hung_up_p(filp)) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return;
+ }
+
+ if (tty->count == 1 && port->count != 1) {
+ printk(KERN_WARNING "ISICOM:(0x%x) isicom_close: bad port count"
+ "tty->count = 1 port count = %d.\n",
+ card->base, port->count);
+ port->count = 1;
+ }
+ if (--port->count < 0) {
+ printk(KERN_WARNING "ISICOM:(0x%x) isicom_close: bad port count for"
+ "channel%d = %d", card->base, port->channel,
+ port->count);
+ port->count = 0;
+ }
+
+ if (port->count) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return;
+ }
+ port->flags |= ASYNC_CLOSING;
+ tty->closing = 1;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, port->closing_wait);
+ /* indicate to the card that no more data can be received
+ on this port */
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (port->flags & ASYNC_INITIALIZED) {
+ card->port_status &= ~(1 << port->channel);
+ outw(card->port_status, card->base + 0x02);
+ }
+ isicom_shutdown_port(port);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ tty->closing = 0;
+
+ if (port->blocked_open) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ if (port->close_delay) {
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: scheduling until time out.\n");
+#endif
+ msleep_interruptible(jiffies_to_msecs(port->close_delay));
+ }
+ spin_lock_irqsave(&card->card_lock, flags);
+ wake_up_interruptible(&port->open_wait);
+ }
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+ wake_up_interruptible(&port->close_wait);
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* write et all */
+static int isicom_write(struct tty_struct * tty,
+ const unsigned char * buf, int count)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+ unsigned long flags;
+ int cnt, total = 0;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_write"))
+ return 0;
+
+ if (!tty || !port->xmit_buf || !tmp_buf)
+ return 0;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+
+ while(1) {
+ cnt = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - port->xmit_head));
+ if (cnt <= 0)
+ break;
+
+ memcpy(port->xmit_buf + port->xmit_head, buf, cnt);
+ port->xmit_head = (port->xmit_head + cnt) & (SERIAL_XMIT_SIZE - 1);
+ port->xmit_cnt += cnt;
+ buf += cnt;
+ count -= cnt;
+ total += cnt;
+ }
+ if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped)
+ port->status |= ISI_TXOK;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return total;
+}
+
+/* put_char et all */
+static void isicom_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+ unsigned long flags;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_put_char"))
+ return;
+
+ if (!tty || !port->xmit_buf)
+ return;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+ spin_unlock_irqrestore(&card->card_lock, flags);
+ return;
+ }
+
+ port->xmit_buf[port->xmit_head++] = ch;
+ port->xmit_head &= (SERIAL_XMIT_SIZE - 1);
+ port->xmit_cnt++;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* flush_chars et all */
+static void isicom_flush_chars(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_flush_chars"))
+ return;
+
+ if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !port->xmit_buf)
+ return;
+
+ /* this tells the transmitter to consider this port for
+ data output to the card ... that's the best we can do. */
+ port->status |= ISI_TXOK;
+}
+
+/* write_room et all */
+static int isicom_write_room(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ int free;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_write_room"))
+ return 0;
+
+ free = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+ if (free < 0)
+ free = 0;
+ return free;
+}
+
+/* chars_in_buffer et all */
+static int isicom_chars_in_buffer(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ if (isicom_paranoia_check(port, tty->name, "isicom_chars_in_buffer"))
+ return 0;
+ return port->xmit_cnt;
+}
+
+/* ioctl et all */
+static inline void isicom_send_break(struct isi_port * port, unsigned long length)
+{
+ struct isi_board * card = port->card;
+ unsigned short base = card->base;
+
+ if(!lock_card(card))
+ return;
+
+ outw(0x8000 | ((port->channel) << (card->shift_count)) | 0x3, base);
+ outw((length & 0xff) << 8 | 0x00, base);
+ outw((length & 0xff00), base);
+ InterruptTheCard(base);
+
+ unlock_card(card);
+}
+
+static int isicom_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ /* just send the port status */
+ unsigned short status = port->status;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+ return -ENODEV;
+
+ return ((status & ISI_RTS) ? TIOCM_RTS : 0) |
+ ((status & ISI_DTR) ? TIOCM_DTR : 0) |
+ ((status & ISI_DCD) ? TIOCM_CAR : 0) |
+ ((status & ISI_DSR) ? TIOCM_DSR : 0) |
+ ((status & ISI_CTS) ? TIOCM_CTS : 0) |
+ ((status & ISI_RI ) ? TIOCM_RI : 0);
+}
+
+static int isicom_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+ return -ENODEV;
+
+ if (set & TIOCM_RTS)
+ raise_rts(port);
+ if (set & TIOCM_DTR)
+ raise_dtr(port);
+
+ if (clear & TIOCM_RTS)
+ drop_rts(port);
+ if (clear & TIOCM_DTR)
+ drop_dtr(port);
+
+ return 0;
+}
+
+static int isicom_set_serial_info(struct isi_port * port,
+ struct serial_struct __user *info)
+{
+ struct serial_struct newinfo;
+ int reconfig_port;
+
+ if(copy_from_user(&newinfo, info, sizeof(newinfo)))
+ return -EFAULT;
+
+ reconfig_port = ((port->flags & ASYNC_SPD_MASK) !=
+ (newinfo.flags & ASYNC_SPD_MASK));
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((newinfo.close_delay != port->close_delay) ||
+ (newinfo.closing_wait != port->closing_wait) ||
+ ((newinfo.flags & ~ASYNC_USR_MASK) !=
+ (port->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ port->flags = ((port->flags & ~ ASYNC_USR_MASK) |
+ (newinfo.flags & ASYNC_USR_MASK));
+ }
+ else {
+ port->close_delay = newinfo.close_delay;
+ port->closing_wait = newinfo.closing_wait;
+ port->flags = ((port->flags & ~ASYNC_FLAGS) |
+ (newinfo.flags & ASYNC_FLAGS));
+ }
+ if (reconfig_port) {
+ isicom_config_port(port);
+ }
+ return 0;
+}
+
+static int isicom_get_serial_info(struct isi_port * port,
+ struct serial_struct __user *info)
+{
+ struct serial_struct out_info;
+
+ memset(&out_info, 0, sizeof(out_info));
+/* out_info.type = ? */
+ out_info.line = port - isi_ports;
+ out_info.port = port->card->base;
+ out_info.irq = port->card->irq;
+ out_info.flags = port->flags;
+/* out_info.baud_base = ? */
+ out_info.close_delay = port->close_delay;
+ out_info.closing_wait = port->closing_wait;
+ if(copy_to_user(info, &out_info, sizeof(out_info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int isicom_ioctl(struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ void __user *argp = (void __user *)arg;
+ int retval;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+ return -ENODEV;
+
+ switch(cmd) {
+ case TCSBRK:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ isicom_send_break(port, HZ/4);
+ return 0;
+
+ case TCSBRKP:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ isicom_send_break(port, arg ? arg * (HZ/10) : HZ/4);
+ return 0;
+
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *)argp);
+
+ case TIOCSSOFTCAR:
+ if(get_user(arg, (unsigned long __user *) argp))
+ return -EFAULT;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ return 0;
+
+ case TIOCGSERIAL:
+ return isicom_get_serial_info(port, argp);
+
+ case TIOCSSERIAL:
+ return isicom_set_serial_info(port, argp);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+/* set_termios et all */
+static void isicom_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_set_termios"))
+ return;
+
+ if (tty->termios->c_cflag == old_termios->c_cflag &&
+ tty->termios->c_iflag == old_termios->c_iflag)
+ return;
+
+ isicom_config_port(port);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ isicom_start(tty);
+ }
+}
+
+/* throttle et all */
+static void isicom_throttle(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_throttle"))
+ return;
+
+ /* tell the card that this port cannot handle any more data for now */
+ card->port_status &= ~(1 << port->channel);
+ outw(card->port_status, card->base + 0x02);
+}
+
+/* unthrottle et all */
+static void isicom_unthrottle(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_unthrottle"))
+ return;
+
+ /* tell the card that this port is ready to accept more data */
+ card->port_status |= (1 << port->channel);
+ outw(card->port_status, card->base + 0x02);
+}
+
+/* stop et all */
+static void isicom_stop(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_stop"))
+ return;
+
+ /* this tells the transmitter not to consider this port for
+ data output to the card. */
+ port->status &= ~ISI_TXOK;
+}
+
+/* start et all */
+static void isicom_start(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_start"))
+ return;
+
+ /* this tells the transmitter to consider this port for
+ data output to the card. */
+ port->status |= ISI_TXOK;
+}
+
+/* hangup et all */
+static void do_isicom_hangup(void * data)
+{
+ struct isi_port * port = (struct isi_port *) data;
+ struct tty_struct * tty;
+
+ tty = port->tty;
+ if (tty)
+ tty_hangup(tty);
+}
+
+static void isicom_hangup(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_hangup"))
+ return;
+
+ isicom_shutdown_port(port);
+ port->count = 0;
+ port->flags &= ~ASYNC_NORMAL_ACTIVE;
+ port->tty = NULL;
+ wake_up_interruptible(&port->open_wait);
+}
+
+/* flush_buffer et all */
+static void isicom_flush_buffer(struct tty_struct * tty)
+{
+ struct isi_port * port = (struct isi_port *) tty->driver_data;
+ struct isi_board * card = port->card;
+ unsigned long flags;
+
+ if (isicom_paranoia_check(port, tty->name, "isicom_flush_buffer"))
+ return;
+
+ spin_lock_irqsave(&card->card_lock, flags);
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ spin_unlock_irqrestore(&card->card_lock, flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+
+static int __init register_ioregion(void)
+{
+ int count, done=0;
+ for (count=0; count < BOARD_COUNT; count++ ) {
+ if (isi_card[count].base)
+ if (!request_region(isi_card[count].base,16,ISICOM_NAME)) {
+ printk(KERN_DEBUG "ISICOM: I/O Region 0x%x-0x%x is busy. Card%d will be disabled.\n",
+ isi_card[count].base,isi_card[count].base+15,count+1);
+ isi_card[count].base=0;
+ done++;
+ }
+ }
+ return done;
+}
+
+static void __exit unregister_ioregion(void)
+{
+ int count;
+ for (count=0; count < BOARD_COUNT; count++ )
+ if (isi_card[count].base) {
+ release_region(isi_card[count].base,16);
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: I/O Region 0x%x-0x%x released for Card%d.\n",isi_card[count].base,isi_card[count].base+15,count+1);
+#endif
+ }
+}
+
+static struct tty_operations isicom_ops = {
+ .open = isicom_open,
+ .close = isicom_close,
+ .write = isicom_write,
+ .put_char = isicom_put_char,
+ .flush_chars = isicom_flush_chars,
+ .write_room = isicom_write_room,
+ .chars_in_buffer = isicom_chars_in_buffer,
+ .ioctl = isicom_ioctl,
+ .set_termios = isicom_set_termios,
+ .throttle = isicom_throttle,
+ .unthrottle = isicom_unthrottle,
+ .stop = isicom_stop,
+ .start = isicom_start,
+ .hangup = isicom_hangup,
+ .flush_buffer = isicom_flush_buffer,
+ .tiocmget = isicom_tiocmget,
+ .tiocmset = isicom_tiocmset,
+};
+
+static int __init register_drivers(void)
+{
+ int error;
+
+ /* tty driver structure initialization */
+ isicom_normal = alloc_tty_driver(PORT_COUNT);
+ if (!isicom_normal)
+ return -ENOMEM;
+
+ isicom_normal->owner = THIS_MODULE;
+ isicom_normal->name = "ttyM";
+ isicom_normal->devfs_name = "isicom/";
+ isicom_normal->major = ISICOM_NMAJOR;
+ isicom_normal->minor_start = 0;
+ isicom_normal->type = TTY_DRIVER_TYPE_SERIAL;
+ isicom_normal->subtype = SERIAL_TYPE_NORMAL;
+ isicom_normal->init_termios = tty_std_termios;
+ isicom_normal->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL |CLOCAL;
+ isicom_normal->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(isicom_normal, &isicom_ops);
+
+ if ((error=tty_register_driver(isicom_normal))!=0) {
+ printk(KERN_DEBUG "ISICOM: Couldn't register the dialin driver, error=%d\n",
+ error);
+ put_tty_driver(isicom_normal);
+ return error;
+ }
+ return 0;
+}
+
+static void __exit unregister_drivers(void)
+{
+ int error = tty_unregister_driver(isicom_normal);
+ if (error)
+ printk(KERN_DEBUG "ISICOM: couldn't unregister normal driver error=%d.\n",error);
+ put_tty_driver(isicom_normal);
+}
+
+static int __init register_isr(void)
+{
+ int count, done=0;
+ unsigned long irqflags;
+
+ for (count=0; count < BOARD_COUNT; count++ ) {
+ if (isi_card[count].base) {
+ irqflags = (isi_card[count].isa == YES) ?
+ SA_INTERRUPT :
+ (SA_INTERRUPT | SA_SHIRQ);
+
+ if (request_irq(isi_card[count].irq,
+ isicom_interrupt,
+ irqflags,
+ ISICOM_NAME, &isi_card[count])) {
+
+ printk(KERN_WARNING "ISICOM: Could not"
+ " install handler at Irq %d."
+ " Card%d will be disabled.\n",
+ isi_card[count].irq, count+1);
+
+ release_region(isi_card[count].base,16);
+ isi_card[count].base=0;
+ }
+ else
+ done++;
+ }
+ }
+ return done;
+}
+
+static void __exit unregister_isr(void)
+{
+ int count;
+
+ for (count=0; count < BOARD_COUNT; count++ ) {
+ if (isi_card[count].base)
+ free_irq(isi_card[count].irq, &isi_card[count]);
+ }
+}
+
+static int __init isicom_init(void)
+{
+ int card, channel, base;
+ struct isi_port * port;
+ unsigned long page;
+
+ if (!tmp_buf) {
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+#ifdef ISICOM_DEBUG
+ printk(KERN_DEBUG "ISICOM: Couldn't allocate page for tmp_buf.\n");
+#else
+ printk(KERN_ERR "ISICOM: Not enough memory...\n");
+#endif
+ return 0;
+ }
+ tmp_buf = (unsigned char *) page;
+ }
+
+ if (!register_ioregion())
+ {
+ printk(KERN_ERR "ISICOM: All required I/O space found busy.\n");
+ free_page((unsigned long)tmp_buf);
+ return 0;
+ }
+ if (register_drivers())
+ {
+ unregister_ioregion();
+ free_page((unsigned long)tmp_buf);
+ return 0;
+ }
+ if (!register_isr())
+ {
+ unregister_drivers();
+ /* ioports already uregistered in register_isr */
+ free_page((unsigned long)tmp_buf);
+ return 0;
+ }
+
+ memset(isi_ports, 0, sizeof(isi_ports));
+ for (card = 0; card < BOARD_COUNT; card++) {
+ port = &isi_ports[card * 16];
+ isi_card[card].ports = port;
+ spin_lock_init(&isi_card[card].card_lock);
+ base = isi_card[card].base;
+ for (channel = 0; channel < 16; channel++, port++) {
+ port->magic = ISICOM_MAGIC;
+ port->card = &isi_card[card];
+ port->channel = channel;
+ port->close_delay = 50 * HZ/100;
+ port->closing_wait = 3000 * HZ/100;
+ INIT_WORK(&port->hangup_tq, do_isicom_hangup, port);
+ INIT_WORK(&port->bh_tqueue, isicom_bottomhalf, port);
+ port->status = 0;
+ init_waitqueue_head(&port->open_wait);
+ init_waitqueue_head(&port->close_wait);
+ /* . . . */
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * Insmod can set static symbols so keep these static
+ */
+
+static int io[4];
+static int irq[4];
+
+MODULE_AUTHOR("MultiTech");
+MODULE_DESCRIPTION("Driver for the ISI series of cards by MultiTech");
+MODULE_LICENSE("GPL");
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io, "I/O ports for the cards");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq, "Interrupts for the cards");
+
+static int __devinit isicom_setup(void)
+{
+ struct pci_dev *dev = NULL;
+ int retval, card, idx, count;
+ unsigned char pciirq;
+ unsigned int ioaddr;
+
+ card = 0;
+ for(idx=0; idx < BOARD_COUNT; idx++) {
+ if (io[idx]) {
+ isi_card[idx].base=io[idx];
+ isi_card[idx].irq=irq[idx];
+ isi_card[idx].isa=YES;
+ card++;
+ }
+ else {
+ isi_card[idx].base = 0;
+ isi_card[idx].irq = 0;
+ }
+ }
+
+ for (idx=0 ;idx < card; idx++) {
+ if (!((isi_card[idx].irq==2)||(isi_card[idx].irq==3)||
+ (isi_card[idx].irq==4)||(isi_card[idx].irq==5)||
+ (isi_card[idx].irq==7)||(isi_card[idx].irq==10)||
+ (isi_card[idx].irq==11)||(isi_card[idx].irq==12)||
+ (isi_card[idx].irq==15))) {
+
+ if (isi_card[idx].base) {
+ printk(KERN_ERR "ISICOM: Irq %d unsupported. Disabling Card%d...\n",
+ isi_card[idx].irq, idx+1);
+ isi_card[idx].base=0;
+ card--;
+ }
+ }
+ }
+
+ if (card < BOARD_COUNT) {
+ for (idx=0; idx < DEVID_COUNT; idx++) {
+ dev = NULL;
+ for (;;){
+ if (!(dev = pci_find_device(VENDOR_ID, isicom_pci_tbl[idx].device, dev)))
+ break;
+ if (card >= BOARD_COUNT)
+ break;
+
+ if (pci_enable_device(dev))
+ break;
+
+ /* found a PCI ISI card! */
+ ioaddr = pci_resource_start (dev, 3); /* i.e at offset 0x1c in the
+ * PCI configuration register
+ * space.
+ */
+ pciirq = dev->irq;
+ printk(KERN_INFO "ISI PCI Card(Device ID 0x%x)\n", isicom_pci_tbl[idx].device);
+ /*
+ * allot the first empty slot in the array
+ */
+ for (count=0; count < BOARD_COUNT; count++) {
+ if (isi_card[count].base == 0) {
+ isi_card[count].base = ioaddr;
+ isi_card[count].irq = pciirq;
+ isi_card[count].isa = NO;
+ card++;
+ break;
+ }
+ }
+ }
+ if (card >= BOARD_COUNT) break;
+ }
+ }
+
+ if (!(isi_card[0].base || isi_card[1].base || isi_card[2].base || isi_card[3].base)) {
+ printk(KERN_ERR "ISICOM: No valid card configuration. Driver cannot be initialized...\n");
+ return -EIO;
+ }
+
+ retval = misc_register(&isiloader_device);
+ if (retval < 0) {
+ printk(KERN_ERR "ISICOM: Unable to register firmware loader driver.\n");
+ return retval;
+ }
+
+ if (!isicom_init()) {
+ if (misc_deregister(&isiloader_device))
+ printk(KERN_ERR "ISICOM: Unable to unregister Firmware Loader driver\n");
+ return -EIO;
+ }
+
+ init_timer(&tx);
+ tx.expires = jiffies + 1;
+ tx.data = 0;
+ tx.function = isicom_tx;
+ re_schedule = 1;
+ add_timer(&tx);
+
+ return 0;
+}
+
+static void __exit isicom_exit(void)
+{
+ re_schedule = 0;
+ /* FIXME */
+ msleep(1000);
+ unregister_isr();
+ unregister_drivers();
+ unregister_ioregion();
+ if(tmp_buf)
+ free_page((unsigned long)tmp_buf);
+ if (misc_deregister(&isiloader_device))
+ printk(KERN_ERR "ISICOM: Unable to unregister Firmware Loader driver\n");
+}
+
+module_init(isicom_setup);
+module_exit(isicom_exit);
diff --git a/drivers/char/istallion.c b/drivers/char/istallion.c
new file mode 100644
index 000000000000..21aed0e8779d
--- /dev/null
+++ b/drivers/char/istallion.c
@@ -0,0 +1,5276 @@
+/*****************************************************************************/
+
+/*
+ * istallion.c -- stallion intelligent multiport serial driver.
+ *
+ * Copyright (C) 1996-1999 Stallion Technologies
+ * Copyright (C) 1994-1996 Greg Ungerer.
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/cdk.h>
+#include <linux/comstats.h>
+#include <linux/istallion.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define different board types. Not all of the following board types
+ * are supported by this driver. But I will use the standard "assigned"
+ * board numbers. Currently supported boards are abbreviated as:
+ * ECP = EasyConnection 8/64, ONB = ONboard, BBY = Brumby and
+ * STAL = Stallion.
+ */
+#define BRD_UNKNOWN 0
+#define BRD_STALLION 1
+#define BRD_BRUMBY4 2
+#define BRD_ONBOARD2 3
+#define BRD_ONBOARD 4
+#define BRD_BRUMBY8 5
+#define BRD_BRUMBY16 6
+#define BRD_ONBOARDE 7
+#define BRD_ONBOARD32 9
+#define BRD_ONBOARD2_32 10
+#define BRD_ONBOARDRS 11
+#define BRD_EASYIO 20
+#define BRD_ECH 21
+#define BRD_ECHMC 22
+#define BRD_ECP 23
+#define BRD_ECPE 24
+#define BRD_ECPMC 25
+#define BRD_ECHPCI 26
+#define BRD_ECH64PCI 27
+#define BRD_EASYIOPCI 28
+#define BRD_ECPPCI 29
+
+#define BRD_BRUMBY BRD_BRUMBY4
+
+/*
+ * Define a configuration structure to hold the board configuration.
+ * Need to set this up in the code (for now) with the boards that are
+ * to be configured into the system. This is what needs to be modified
+ * when adding/removing/modifying boards. Each line entry in the
+ * stli_brdconf[] array is a board. Each line contains io/irq/memory
+ * ranges for that board (as well as what type of board it is).
+ * Some examples:
+ * { BRD_ECP, 0x2a0, 0, 0xcc000, 0, 0 },
+ * This line will configure an EasyConnection 8/64 at io address 2a0,
+ * and shared memory address of cc000. Multiple EasyConnection 8/64
+ * boards can share the same shared memory address space. No interrupt
+ * is required for this board type.
+ * Another example:
+ * { BRD_ECPE, 0x5000, 0, 0x80000000, 0, 0 },
+ * This line will configure an EasyConnection 8/64 EISA in slot 5 and
+ * shared memory address of 0x80000000 (2 GByte). Multiple
+ * EasyConnection 8/64 EISA boards can share the same shared memory
+ * address space. No interrupt is required for this board type.
+ * Another example:
+ * { BRD_ONBOARD, 0x240, 0, 0xd0000, 0, 0 },
+ * This line will configure an ONboard (ISA type) at io address 240,
+ * and shared memory address of d0000. Multiple ONboards can share
+ * the same shared memory address space. No interrupt required.
+ * Another example:
+ * { BRD_BRUMBY4, 0x360, 0, 0xc8000, 0, 0 },
+ * This line will configure a Brumby board (any number of ports!) at
+ * io address 360 and shared memory address of c8000. All Brumby boards
+ * configured into a system must have their own separate io and memory
+ * addresses. No interrupt is required.
+ * Another example:
+ * { BRD_STALLION, 0x330, 0, 0xd0000, 0, 0 },
+ * This line will configure an original Stallion board at io address 330
+ * and shared memory address d0000 (this would only be valid for a "V4.0"
+ * or Rev.O Stallion board). All Stallion boards configured into the
+ * system must have their own separate io and memory addresses. No
+ * interrupt is required.
+ */
+
+typedef struct {
+ int brdtype;
+ int ioaddr1;
+ int ioaddr2;
+ unsigned long memaddr;
+ int irq;
+ int irqtype;
+} stlconf_t;
+
+static stlconf_t stli_brdconf[] = {
+ /*{ BRD_ECP, 0x2a0, 0, 0xcc000, 0, 0 },*/
+};
+
+static int stli_nrbrds = sizeof(stli_brdconf) / sizeof(stlconf_t);
+
+/*
+ * There is some experimental EISA board detection code in this driver.
+ * By default it is disabled, but for those that want to try it out,
+ * then set the define below to be 1.
+ */
+#define STLI_EISAPROBE 0
+
+/*****************************************************************************/
+
+/*
+ * Define some important driver characteristics. Device major numbers
+ * allocated as per Linux Device Registry.
+ */
+#ifndef STL_SIOMEMMAJOR
+#define STL_SIOMEMMAJOR 28
+#endif
+#ifndef STL_SERIALMAJOR
+#define STL_SERIALMAJOR 24
+#endif
+#ifndef STL_CALLOUTMAJOR
+#define STL_CALLOUTMAJOR 25
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define our local driver identity first. Set up stuff to deal with
+ * all the local structures required by a serial tty driver.
+ */
+static char *stli_drvtitle = "Stallion Intelligent Multiport Serial Driver";
+static char *stli_drvname = "istallion";
+static char *stli_drvversion = "5.6.0";
+static char *stli_serialname = "ttyE";
+
+static struct tty_driver *stli_serial;
+
+/*
+ * We will need to allocate a temporary write buffer for chars that
+ * come direct from user space. The problem is that a copy from user
+ * space might cause a page fault (typically on a system that is
+ * swapping!). All ports will share one buffer - since if the system
+ * is already swapping a shared buffer won't make things any worse.
+ */
+static char *stli_tmpwritebuf;
+static DECLARE_MUTEX(stli_tmpwritesem);
+
+#define STLI_TXBUFSIZE 4096
+
+/*
+ * Use a fast local buffer for cooked characters. Typically a whole
+ * bunch of cooked characters come in for a port, 1 at a time. So we
+ * save those up into a local buffer, then write out the whole lot
+ * with a large memcpy. Just use 1 buffer for all ports, since its
+ * use it is only need for short periods of time by each port.
+ */
+static char *stli_txcookbuf;
+static int stli_txcooksize;
+static int stli_txcookrealsize;
+static struct tty_struct *stli_txcooktty;
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially. Basically all it defines is a raw port
+ * at 9600 baud, 8 data bits, no parity, 1 stop bit.
+ */
+static struct termios stli_deftermios = {
+ .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+ .c_cc = INIT_C_CC,
+};
+
+/*
+ * Define global stats structures. Not used often, and can be
+ * re-used for each stats call.
+ */
+static comstats_t stli_comstats;
+static combrd_t stli_brdstats;
+static asystats_t stli_cdkstats;
+static stlibrd_t stli_dummybrd;
+static stliport_t stli_dummyport;
+
+/*****************************************************************************/
+
+static stlibrd_t *stli_brds[STL_MAXBRDS];
+
+static int stli_shared;
+
+/*
+ * Per board state flags. Used with the state field of the board struct.
+ * Not really much here... All we need to do is keep track of whether
+ * the board has been detected, and whether it is actually running a slave
+ * or not.
+ */
+#define BST_FOUND 0x1
+#define BST_STARTED 0x2
+
+/*
+ * Define the set of port state flags. These are marked for internal
+ * state purposes only, usually to do with the state of communications
+ * with the slave. Most of them need to be updated atomically, so always
+ * use the bit setting operations (unless protected by cli/sti).
+ */
+#define ST_INITIALIZING 1
+#define ST_OPENING 2
+#define ST_CLOSING 3
+#define ST_CMDING 4
+#define ST_TXBUSY 5
+#define ST_RXING 6
+#define ST_DOFLUSHRX 7
+#define ST_DOFLUSHTX 8
+#define ST_DOSIGS 9
+#define ST_RXSTOP 10
+#define ST_GETSIGS 11
+
+/*
+ * Define an array of board names as printable strings. Handy for
+ * referencing boards when printing trace and stuff.
+ */
+static char *stli_brdnames[] = {
+ "Unknown",
+ "Stallion",
+ "Brumby",
+ "ONboard-MC",
+ "ONboard",
+ "Brumby",
+ "Brumby",
+ "ONboard-EI",
+ (char *) NULL,
+ "ONboard",
+ "ONboard-MC",
+ "ONboard-MC",
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ "EasyIO",
+ "EC8/32-AT",
+ "EC8/32-MC",
+ "EC8/64-AT",
+ "EC8/64-EI",
+ "EC8/64-MC",
+ "EC8/32-PCI",
+ "EC8/64-PCI",
+ "EasyIO-PCI",
+ "EC/RA-PCI",
+};
+
+/*****************************************************************************/
+
+#ifdef MODULE
+/*
+ * Define some string labels for arguments passed from the module
+ * load line. These allow for easy board definitions, and easy
+ * modification of the io, memory and irq resoucres.
+ */
+
+static char *board0[8];
+static char *board1[8];
+static char *board2[8];
+static char *board3[8];
+
+static char **stli_brdsp[] = {
+ (char **) &board0,
+ (char **) &board1,
+ (char **) &board2,
+ (char **) &board3
+};
+
+/*
+ * Define a set of common board names, and types. This is used to
+ * parse any module arguments.
+ */
+
+typedef struct stlibrdtype {
+ char *name;
+ int type;
+} stlibrdtype_t;
+
+static stlibrdtype_t stli_brdstr[] = {
+ { "stallion", BRD_STALLION },
+ { "1", BRD_STALLION },
+ { "brumby", BRD_BRUMBY },
+ { "brumby4", BRD_BRUMBY },
+ { "brumby/4", BRD_BRUMBY },
+ { "brumby-4", BRD_BRUMBY },
+ { "brumby8", BRD_BRUMBY },
+ { "brumby/8", BRD_BRUMBY },
+ { "brumby-8", BRD_BRUMBY },
+ { "brumby16", BRD_BRUMBY },
+ { "brumby/16", BRD_BRUMBY },
+ { "brumby-16", BRD_BRUMBY },
+ { "2", BRD_BRUMBY },
+ { "onboard2", BRD_ONBOARD2 },
+ { "onboard-2", BRD_ONBOARD2 },
+ { "onboard/2", BRD_ONBOARD2 },
+ { "onboard-mc", BRD_ONBOARD2 },
+ { "onboard/mc", BRD_ONBOARD2 },
+ { "onboard-mca", BRD_ONBOARD2 },
+ { "onboard/mca", BRD_ONBOARD2 },
+ { "3", BRD_ONBOARD2 },
+ { "onboard", BRD_ONBOARD },
+ { "onboardat", BRD_ONBOARD },
+ { "4", BRD_ONBOARD },
+ { "onboarde", BRD_ONBOARDE },
+ { "onboard-e", BRD_ONBOARDE },
+ { "onboard/e", BRD_ONBOARDE },
+ { "onboard-ei", BRD_ONBOARDE },
+ { "onboard/ei", BRD_ONBOARDE },
+ { "7", BRD_ONBOARDE },
+ { "ecp", BRD_ECP },
+ { "ecpat", BRD_ECP },
+ { "ec8/64", BRD_ECP },
+ { "ec8/64-at", BRD_ECP },
+ { "ec8/64-isa", BRD_ECP },
+ { "23", BRD_ECP },
+ { "ecpe", BRD_ECPE },
+ { "ecpei", BRD_ECPE },
+ { "ec8/64-e", BRD_ECPE },
+ { "ec8/64-ei", BRD_ECPE },
+ { "24", BRD_ECPE },
+ { "ecpmc", BRD_ECPMC },
+ { "ec8/64-mc", BRD_ECPMC },
+ { "ec8/64-mca", BRD_ECPMC },
+ { "25", BRD_ECPMC },
+ { "ecppci", BRD_ECPPCI },
+ { "ec/ra", BRD_ECPPCI },
+ { "ec/ra-pc", BRD_ECPPCI },
+ { "ec/ra-pci", BRD_ECPPCI },
+ { "29", BRD_ECPPCI },
+};
+
+/*
+ * Define the module agruments.
+ */
+MODULE_AUTHOR("Greg Ungerer");
+MODULE_DESCRIPTION("Stallion Intelligent Multiport Serial Driver");
+MODULE_LICENSE("GPL");
+
+
+MODULE_PARM(board0, "1-3s");
+MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board1, "1-3s");
+MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board2, "1-3s");
+MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board3, "1-3s");
+MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,memaddr]");
+
+#endif
+
+/*
+ * Set up a default memory address table for EISA board probing.
+ * The default addresses are all bellow 1Mbyte, which has to be the
+ * case anyway. They should be safe, since we only read values from
+ * them, and interrupts are disabled while we do it. If the higher
+ * memory support is compiled in then we also try probing around
+ * the 1Gb, 2Gb and 3Gb areas as well...
+ */
+static unsigned long stli_eisamemprobeaddrs[] = {
+ 0xc0000, 0xd0000, 0xe0000, 0xf0000,
+ 0x80000000, 0x80010000, 0x80020000, 0x80030000,
+ 0x40000000, 0x40010000, 0x40020000, 0x40030000,
+ 0xc0000000, 0xc0010000, 0xc0020000, 0xc0030000,
+ 0xff000000, 0xff010000, 0xff020000, 0xff030000,
+};
+
+static int stli_eisamempsize = sizeof(stli_eisamemprobeaddrs) / sizeof(unsigned long);
+int stli_eisaprobe = STLI_EISAPROBE;
+
+/*
+ * Define the Stallion PCI vendor and device IDs.
+ */
+#ifdef CONFIG_PCI
+#ifndef PCI_VENDOR_ID_STALLION
+#define PCI_VENDOR_ID_STALLION 0x124d
+#endif
+#ifndef PCI_DEVICE_ID_ECRA
+#define PCI_DEVICE_ID_ECRA 0x0004
+#endif
+
+static struct pci_device_id istallion_pci_tbl[] = {
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECRA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, istallion_pci_tbl);
+
+#endif /* CONFIG_PCI */
+
+/*****************************************************************************/
+
+/*
+ * Hardware configuration info for ECP boards. These defines apply
+ * to the directly accessible io ports of the ECP. There is a set of
+ * defines for each ECP board type, ISA, EISA, MCA and PCI.
+ */
+#define ECP_IOSIZE 4
+
+#define ECP_MEMSIZE (128 * 1024)
+#define ECP_PCIMEMSIZE (256 * 1024)
+
+#define ECP_ATPAGESIZE (4 * 1024)
+#define ECP_MCPAGESIZE (4 * 1024)
+#define ECP_EIPAGESIZE (64 * 1024)
+#define ECP_PCIPAGESIZE (64 * 1024)
+
+#define STL_EISAID 0x8c4e
+
+/*
+ * Important defines for the ISA class of ECP board.
+ */
+#define ECP_ATIREG 0
+#define ECP_ATCONFR 1
+#define ECP_ATMEMAR 2
+#define ECP_ATMEMPR 3
+#define ECP_ATSTOP 0x1
+#define ECP_ATINTENAB 0x10
+#define ECP_ATENABLE 0x20
+#define ECP_ATDISABLE 0x00
+#define ECP_ATADDRMASK 0x3f000
+#define ECP_ATADDRSHFT 12
+
+/*
+ * Important defines for the EISA class of ECP board.
+ */
+#define ECP_EIIREG 0
+#define ECP_EIMEMARL 1
+#define ECP_EICONFR 2
+#define ECP_EIMEMARH 3
+#define ECP_EIENABLE 0x1
+#define ECP_EIDISABLE 0x0
+#define ECP_EISTOP 0x4
+#define ECP_EIEDGE 0x00
+#define ECP_EILEVEL 0x80
+#define ECP_EIADDRMASKL 0x00ff0000
+#define ECP_EIADDRSHFTL 16
+#define ECP_EIADDRMASKH 0xff000000
+#define ECP_EIADDRSHFTH 24
+#define ECP_EIBRDENAB 0xc84
+
+#define ECP_EISAID 0x4
+
+/*
+ * Important defines for the Micro-channel class of ECP board.
+ * (It has a lot in common with the ISA boards.)
+ */
+#define ECP_MCIREG 0
+#define ECP_MCCONFR 1
+#define ECP_MCSTOP 0x20
+#define ECP_MCENABLE 0x80
+#define ECP_MCDISABLE 0x00
+
+/*
+ * Important defines for the PCI class of ECP board.
+ * (It has a lot in common with the other ECP boards.)
+ */
+#define ECP_PCIIREG 0
+#define ECP_PCICONFR 1
+#define ECP_PCISTOP 0x01
+
+/*
+ * Hardware configuration info for ONboard and Brumby boards. These
+ * defines apply to the directly accessible io ports of these boards.
+ */
+#define ONB_IOSIZE 16
+#define ONB_MEMSIZE (64 * 1024)
+#define ONB_ATPAGESIZE (64 * 1024)
+#define ONB_MCPAGESIZE (64 * 1024)
+#define ONB_EIMEMSIZE (128 * 1024)
+#define ONB_EIPAGESIZE (64 * 1024)
+
+/*
+ * Important defines for the ISA class of ONboard board.
+ */
+#define ONB_ATIREG 0
+#define ONB_ATMEMAR 1
+#define ONB_ATCONFR 2
+#define ONB_ATSTOP 0x4
+#define ONB_ATENABLE 0x01
+#define ONB_ATDISABLE 0x00
+#define ONB_ATADDRMASK 0xff0000
+#define ONB_ATADDRSHFT 16
+
+#define ONB_MEMENABLO 0
+#define ONB_MEMENABHI 0x02
+
+/*
+ * Important defines for the EISA class of ONboard board.
+ */
+#define ONB_EIIREG 0
+#define ONB_EIMEMARL 1
+#define ONB_EICONFR 2
+#define ONB_EIMEMARH 3
+#define ONB_EIENABLE 0x1
+#define ONB_EIDISABLE 0x0
+#define ONB_EISTOP 0x4
+#define ONB_EIEDGE 0x00
+#define ONB_EILEVEL 0x80
+#define ONB_EIADDRMASKL 0x00ff0000
+#define ONB_EIADDRSHFTL 16
+#define ONB_EIADDRMASKH 0xff000000
+#define ONB_EIADDRSHFTH 24
+#define ONB_EIBRDENAB 0xc84
+
+#define ONB_EISAID 0x1
+
+/*
+ * Important defines for the Brumby boards. They are pretty simple,
+ * there is not much that is programmably configurable.
+ */
+#define BBY_IOSIZE 16
+#define BBY_MEMSIZE (64 * 1024)
+#define BBY_PAGESIZE (16 * 1024)
+
+#define BBY_ATIREG 0
+#define BBY_ATCONFR 1
+#define BBY_ATSTOP 0x4
+
+/*
+ * Important defines for the Stallion boards. They are pretty simple,
+ * there is not much that is programmably configurable.
+ */
+#define STAL_IOSIZE 16
+#define STAL_MEMSIZE (64 * 1024)
+#define STAL_PAGESIZE (64 * 1024)
+
+/*
+ * Define the set of status register values for EasyConnection panels.
+ * The signature will return with the status value for each panel. From
+ * this we can determine what is attached to the board - before we have
+ * actually down loaded any code to it.
+ */
+#define ECH_PNLSTATUS 2
+#define ECH_PNL16PORT 0x20
+#define ECH_PNLIDMASK 0x07
+#define ECH_PNLXPID 0x40
+#define ECH_PNLINTRPEND 0x80
+
+/*
+ * Define some macros to do things to the board. Even those these boards
+ * are somewhat related there is often significantly different ways of
+ * doing some operation on it (like enable, paging, reset, etc). So each
+ * board class has a set of functions which do the commonly required
+ * operations. The macros below basically just call these functions,
+ * generally checking for a NULL function - which means that the board
+ * needs nothing done to it to achieve this operation!
+ */
+#define EBRDINIT(brdp) \
+ if (brdp->init != NULL) \
+ (* brdp->init)(brdp)
+
+#define EBRDENABLE(brdp) \
+ if (brdp->enable != NULL) \
+ (* brdp->enable)(brdp);
+
+#define EBRDDISABLE(brdp) \
+ if (brdp->disable != NULL) \
+ (* brdp->disable)(brdp);
+
+#define EBRDINTR(brdp) \
+ if (brdp->intr != NULL) \
+ (* brdp->intr)(brdp);
+
+#define EBRDRESET(brdp) \
+ if (brdp->reset != NULL) \
+ (* brdp->reset)(brdp);
+
+#define EBRDGETMEMPTR(brdp,offset) \
+ (* brdp->getmemptr)(brdp, offset, __LINE__)
+
+/*
+ * Define the maximal baud rate, and the default baud base for ports.
+ */
+#define STL_MAXBAUD 460800
+#define STL_BAUDBASE 115200
+#define STL_CLOSEDELAY (5 * HZ / 10)
+
+/*****************************************************************************/
+
+/*
+ * Define macros to extract a brd or port number from a minor number.
+ */
+#define MINOR2BRD(min) (((min) & 0xc0) >> 6)
+#define MINOR2PORT(min) ((min) & 0x3f)
+
+/*
+ * Define a baud rate table that converts termios baud rate selector
+ * into the actual baud rate value. All baud rate calculations are based
+ * on the actual baud rate required.
+ */
+static unsigned int stli_baudrates[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+/*****************************************************************************/
+
+/*
+ * Define some handy local macros...
+ */
+#undef MIN
+#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
+
+#undef TOLOWER
+#define TOLOWER(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x))
+
+/*****************************************************************************/
+
+/*
+ * Prototype all functions in this driver!
+ */
+
+#ifdef MODULE
+static void stli_argbrds(void);
+static int stli_parsebrd(stlconf_t *confp, char **argp);
+
+static unsigned long stli_atol(char *str);
+#endif
+
+int stli_init(void);
+static int stli_open(struct tty_struct *tty, struct file *filp);
+static void stli_close(struct tty_struct *tty, struct file *filp);
+static int stli_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void stli_putchar(struct tty_struct *tty, unsigned char ch);
+static void stli_flushchars(struct tty_struct *tty);
+static int stli_writeroom(struct tty_struct *tty);
+static int stli_charsinbuffer(struct tty_struct *tty);
+static int stli_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void stli_settermios(struct tty_struct *tty, struct termios *old);
+static void stli_throttle(struct tty_struct *tty);
+static void stli_unthrottle(struct tty_struct *tty);
+static void stli_stop(struct tty_struct *tty);
+static void stli_start(struct tty_struct *tty);
+static void stli_flushbuffer(struct tty_struct *tty);
+static void stli_breakctl(struct tty_struct *tty, int state);
+static void stli_waituntilsent(struct tty_struct *tty, int timeout);
+static void stli_sendxchar(struct tty_struct *tty, char ch);
+static void stli_hangup(struct tty_struct *tty);
+static int stli_portinfo(stlibrd_t *brdp, stliport_t *portp, int portnr, char *pos);
+
+static int stli_brdinit(stlibrd_t *brdp);
+static int stli_startbrd(stlibrd_t *brdp);
+static ssize_t stli_memread(struct file *fp, char __user *buf, size_t count, loff_t *offp);
+static ssize_t stli_memwrite(struct file *fp, const char __user *buf, size_t count, loff_t *offp);
+static int stli_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static void stli_brdpoll(stlibrd_t *brdp, volatile cdkhdr_t *hdrp);
+static void stli_poll(unsigned long arg);
+static int stli_hostcmd(stlibrd_t *brdp, stliport_t *portp);
+static int stli_initopen(stlibrd_t *brdp, stliport_t *portp);
+static int stli_rawopen(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait);
+static int stli_rawclose(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait);
+static int stli_waitcarrier(stlibrd_t *brdp, stliport_t *portp, struct file *filp);
+static void stli_dohangup(void *arg);
+static int stli_setport(stliport_t *portp);
+static int stli_cmdwait(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback);
+static void stli_sendcmd(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback);
+static void stli_dodelaycmd(stliport_t *portp, volatile cdkctrl_t *cp);
+static void stli_mkasyport(stliport_t *portp, asyport_t *pp, struct termios *tiosp);
+static void stli_mkasysigs(asysigs_t *sp, int dtr, int rts);
+static long stli_mktiocm(unsigned long sigvalue);
+static void stli_read(stlibrd_t *brdp, stliport_t *portp);
+static int stli_getserial(stliport_t *portp, struct serial_struct __user *sp);
+static int stli_setserial(stliport_t *portp, struct serial_struct __user *sp);
+static int stli_getbrdstats(combrd_t __user *bp);
+static int stli_getportstats(stliport_t *portp, comstats_t __user *cp);
+static int stli_portcmdstats(stliport_t *portp);
+static int stli_clrportstats(stliport_t *portp, comstats_t __user *cp);
+static int stli_getportstruct(stliport_t __user *arg);
+static int stli_getbrdstruct(stlibrd_t __user *arg);
+static void *stli_memalloc(int len);
+static stlibrd_t *stli_allocbrd(void);
+
+static void stli_ecpinit(stlibrd_t *brdp);
+static void stli_ecpenable(stlibrd_t *brdp);
+static void stli_ecpdisable(stlibrd_t *brdp);
+static char *stli_ecpgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_ecpreset(stlibrd_t *brdp);
+static void stli_ecpintr(stlibrd_t *brdp);
+static void stli_ecpeiinit(stlibrd_t *brdp);
+static void stli_ecpeienable(stlibrd_t *brdp);
+static void stli_ecpeidisable(stlibrd_t *brdp);
+static char *stli_ecpeigetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_ecpeireset(stlibrd_t *brdp);
+static void stli_ecpmcenable(stlibrd_t *brdp);
+static void stli_ecpmcdisable(stlibrd_t *brdp);
+static char *stli_ecpmcgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_ecpmcreset(stlibrd_t *brdp);
+static void stli_ecppciinit(stlibrd_t *brdp);
+static char *stli_ecppcigetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_ecppcireset(stlibrd_t *brdp);
+
+static void stli_onbinit(stlibrd_t *brdp);
+static void stli_onbenable(stlibrd_t *brdp);
+static void stli_onbdisable(stlibrd_t *brdp);
+static char *stli_onbgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_onbreset(stlibrd_t *brdp);
+static void stli_onbeinit(stlibrd_t *brdp);
+static void stli_onbeenable(stlibrd_t *brdp);
+static void stli_onbedisable(stlibrd_t *brdp);
+static char *stli_onbegetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_onbereset(stlibrd_t *brdp);
+static void stli_bbyinit(stlibrd_t *brdp);
+static char *stli_bbygetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_bbyreset(stlibrd_t *brdp);
+static void stli_stalinit(stlibrd_t *brdp);
+static char *stli_stalgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void stli_stalreset(stlibrd_t *brdp);
+
+static stliport_t *stli_getport(int brdnr, int panelnr, int portnr);
+
+static int stli_initecp(stlibrd_t *brdp);
+static int stli_initonb(stlibrd_t *brdp);
+static int stli_eisamemprobe(stlibrd_t *brdp);
+static int stli_initports(stlibrd_t *brdp);
+
+#ifdef CONFIG_PCI
+static int stli_initpcibrd(int brdtype, struct pci_dev *devp);
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define the driver info for a user level shared memory device. This
+ * device will work sort of like the /dev/kmem device - except that it
+ * will give access to the shared memory on the Stallion intelligent
+ * board. This is also a very useful debugging tool.
+ */
+static struct file_operations stli_fsiomem = {
+ .owner = THIS_MODULE,
+ .read = stli_memread,
+ .write = stli_memwrite,
+ .ioctl = stli_memioctl,
+};
+
+/*****************************************************************************/
+
+/*
+ * Define a timer_list entry for our poll routine. The slave board
+ * is polled every so often to see if anything needs doing. This is
+ * much cheaper on host cpu than using interrupts. It turns out to
+ * not increase character latency by much either...
+ */
+static struct timer_list stli_timerlist = TIMER_INITIALIZER(stli_poll, 0, 0);
+
+static int stli_timeron;
+
+/*
+ * Define the calculation for the timeout routine.
+ */
+#define STLI_TIMEOUT (jiffies + 1)
+
+/*****************************************************************************/
+
+static struct class_simple *istallion_class;
+
+#ifdef MODULE
+
+/*
+ * Loadable module initialization stuff.
+ */
+
+static int __init istallion_module_init(void)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("init_module()\n");
+#endif
+
+ save_flags(flags);
+ cli();
+ stli_init();
+ restore_flags(flags);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+static void __exit istallion_module_exit(void)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ unsigned long flags;
+ int i, j;
+
+#ifdef DEBUG
+ printk("cleanup_module()\n");
+#endif
+
+ printk(KERN_INFO "Unloading %s: version %s\n", stli_drvtitle,
+ stli_drvversion);
+
+ save_flags(flags);
+ cli();
+
+/*
+ * Free up all allocated resources used by the ports. This includes
+ * memory and interrupts.
+ */
+ if (stli_timeron) {
+ stli_timeron = 0;
+ del_timer(&stli_timerlist);
+ }
+
+ i = tty_unregister_driver(stli_serial);
+ if (i) {
+ printk("STALLION: failed to un-register tty driver, "
+ "errno=%d\n", -i);
+ restore_flags(flags);
+ return;
+ }
+ put_tty_driver(stli_serial);
+ for (i = 0; i < 4; i++) {
+ devfs_remove("staliomem/%d", i);
+ class_simple_device_remove(MKDEV(STL_SIOMEMMAJOR, i));
+ }
+ devfs_remove("staliomem");
+ class_simple_destroy(istallion_class);
+ if ((i = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem")))
+ printk("STALLION: failed to un-register serial memory device, "
+ "errno=%d\n", -i);
+ if (stli_tmpwritebuf != (char *) NULL)
+ kfree(stli_tmpwritebuf);
+ if (stli_txcookbuf != (char *) NULL)
+ kfree(stli_txcookbuf);
+
+ for (i = 0; (i < stli_nrbrds); i++) {
+ if ((brdp = stli_brds[i]) == (stlibrd_t *) NULL)
+ continue;
+ for (j = 0; (j < STL_MAXPORTS); j++) {
+ portp = brdp->ports[j];
+ if (portp != (stliport_t *) NULL) {
+ if (portp->tty != (struct tty_struct *) NULL)
+ tty_hangup(portp->tty);
+ kfree(portp);
+ }
+ }
+
+ iounmap(brdp->membase);
+ if (brdp->iosize > 0)
+ release_region(brdp->iobase, brdp->iosize);
+ kfree(brdp);
+ stli_brds[i] = (stlibrd_t *) NULL;
+ }
+
+ restore_flags(flags);
+}
+
+module_init(istallion_module_init);
+module_exit(istallion_module_exit);
+
+/*****************************************************************************/
+
+/*
+ * Check for any arguments passed in on the module load command line.
+ */
+
+static void stli_argbrds(void)
+{
+ stlconf_t conf;
+ stlibrd_t *brdp;
+ int nrargs, i;
+
+#ifdef DEBUG
+ printk("stli_argbrds()\n");
+#endif
+
+ nrargs = sizeof(stli_brdsp) / sizeof(char **);
+
+ for (i = stli_nrbrds; (i < nrargs); i++) {
+ memset(&conf, 0, sizeof(conf));
+ if (stli_parsebrd(&conf, stli_brdsp[i]) == 0)
+ continue;
+ if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+ continue;
+ stli_nrbrds = i + 1;
+ brdp->brdnr = i;
+ brdp->brdtype = conf.brdtype;
+ brdp->iobase = conf.ioaddr1;
+ brdp->memaddr = conf.memaddr;
+ stli_brdinit(brdp);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Convert an ascii string number into an unsigned long.
+ */
+
+static unsigned long stli_atol(char *str)
+{
+ unsigned long val;
+ int base, c;
+ char *sp;
+
+ val = 0;
+ sp = str;
+ if ((*sp == '0') && (*(sp+1) == 'x')) {
+ base = 16;
+ sp += 2;
+ } else if (*sp == '0') {
+ base = 8;
+ sp++;
+ } else {
+ base = 10;
+ }
+
+ for (; (*sp != 0); sp++) {
+ c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0');
+ if ((c < 0) || (c >= base)) {
+ printk("STALLION: invalid argument %s\n", str);
+ val = 0;
+ break;
+ }
+ val = (val * base) + c;
+ }
+ return(val);
+}
+
+/*****************************************************************************/
+
+/*
+ * Parse the supplied argument string, into the board conf struct.
+ */
+
+static int stli_parsebrd(stlconf_t *confp, char **argp)
+{
+ char *sp;
+ int nrbrdnames, i;
+
+#ifdef DEBUG
+ printk("stli_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp);
+#endif
+
+ if ((argp[0] == (char *) NULL) || (*argp[0] == 0))
+ return(0);
+
+ for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++)
+ *sp = TOLOWER(*sp);
+
+ nrbrdnames = sizeof(stli_brdstr) / sizeof(stlibrdtype_t);
+ for (i = 0; (i < nrbrdnames); i++) {
+ if (strcmp(stli_brdstr[i].name, argp[0]) == 0)
+ break;
+ }
+ if (i >= nrbrdnames) {
+ printk("STALLION: unknown board name, %s?\n", argp[0]);
+ return(0);
+ }
+
+ confp->brdtype = stli_brdstr[i].type;
+ if ((argp[1] != (char *) NULL) && (*argp[1] != 0))
+ confp->ioaddr1 = stli_atol(argp[1]);
+ if ((argp[2] != (char *) NULL) && (*argp[2] != 0))
+ confp->memaddr = stli_atol(argp[2]);
+ return(1);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Local driver kernel malloc routine.
+ */
+
+static void *stli_memalloc(int len)
+{
+ return((void *) kmalloc(len, GFP_KERNEL));
+}
+
+/*****************************************************************************/
+
+static int stli_open(struct tty_struct *tty, struct file *filp)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ unsigned int minordev;
+ int brdnr, portnr, rc;
+
+#ifdef DEBUG
+ printk("stli_open(tty=%x,filp=%x): device=%s\n", (int) tty,
+ (int) filp, tty->name);
+#endif
+
+ minordev = tty->index;
+ brdnr = MINOR2BRD(minordev);
+ if (brdnr >= stli_nrbrds)
+ return(-ENODEV);
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+ if ((brdp->state & BST_STARTED) == 0)
+ return(-ENODEV);
+ portnr = MINOR2PORT(minordev);
+ if ((portnr < 0) || (portnr > brdp->nrports))
+ return(-ENODEV);
+
+ portp = brdp->ports[portnr];
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ if (portp->devnr < 1)
+ return(-ENODEV);
+
+
+/*
+ * Check if this port is in the middle of closing. If so then wait
+ * until it is closed then return error status based on flag settings.
+ * The sleep here does not need interrupt protection since the wakeup
+ * for it is done with the same context.
+ */
+ if (portp->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&portp->close_wait);
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ return(-EAGAIN);
+ return(-ERESTARTSYS);
+ }
+
+/*
+ * On the first open of the device setup the port hardware, and
+ * initialize the per port data structure. Since initializing the port
+ * requires several commands to the board we will need to wait for any
+ * other open that is already initializing the port.
+ */
+ portp->tty = tty;
+ tty->driver_data = portp;
+ portp->refcount++;
+
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_INITIALIZING, &portp->state));
+ if (signal_pending(current))
+ return(-ERESTARTSYS);
+
+ if ((portp->flags & ASYNC_INITIALIZED) == 0) {
+ set_bit(ST_INITIALIZING, &portp->state);
+ if ((rc = stli_initopen(brdp, portp)) >= 0) {
+ portp->flags |= ASYNC_INITIALIZED;
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+ }
+ clear_bit(ST_INITIALIZING, &portp->state);
+ wake_up_interruptible(&portp->raw_wait);
+ if (rc < 0)
+ return(rc);
+ }
+
+/*
+ * Check if this port is in the middle of closing. If so then wait
+ * until it is closed then return error status, based on flag settings.
+ * The sleep here does not need interrupt protection since the wakeup
+ * for it is done with the same context.
+ */
+ if (portp->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&portp->close_wait);
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ return(-EAGAIN);
+ return(-ERESTARTSYS);
+ }
+
+/*
+ * Based on type of open being done check if it can overlap with any
+ * previous opens still in effect. If we are a normal serial device
+ * then also we might have to wait for carrier.
+ */
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ if ((rc = stli_waitcarrier(brdp, portp, filp)) != 0)
+ return(rc);
+ }
+ portp->flags |= ASYNC_NORMAL_ACTIVE;
+ return(0);
+}
+
+/*****************************************************************************/
+
+static void stli_close(struct tty_struct *tty, struct file *filp)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_close(tty=%x,filp=%x)\n", (int) tty, (int) filp);
+#endif
+
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ if (tty_hung_up_p(filp)) {
+ restore_flags(flags);
+ return;
+ }
+ if ((tty->count == 1) && (portp->refcount != 1))
+ portp->refcount = 1;
+ if (portp->refcount-- > 1) {
+ restore_flags(flags);
+ return;
+ }
+
+ portp->flags |= ASYNC_CLOSING;
+
+/*
+ * May want to wait for data to drain before closing. The BUSY flag
+ * keeps track of whether we are still transmitting or not. It is
+ * updated by messages from the slave - indicating when all chars
+ * really have drained.
+ */
+ if (tty == stli_txcooktty)
+ stli_flushchars(tty);
+ tty->closing = 1;
+ if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, portp->closing_wait);
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+ brdp = stli_brds[portp->brdnr];
+ stli_rawclose(brdp, portp, 0, 0);
+ if (tty->termios->c_cflag & HUPCL) {
+ stli_mkasysigs(&portp->asig, 0, 0);
+ if (test_bit(ST_CMDING, &portp->state))
+ set_bit(ST_DOSIGS, &portp->state);
+ else
+ stli_sendcmd(brdp, portp, A_SETSIGNALS, &portp->asig,
+ sizeof(asysigs_t), 0);
+ }
+ clear_bit(ST_TXBUSY, &portp->state);
+ clear_bit(ST_RXSTOP, &portp->state);
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ if (tty->ldisc.flush_buffer)
+ (tty->ldisc.flush_buffer)(tty);
+ set_bit(ST_DOFLUSHRX, &portp->state);
+ stli_flushbuffer(tty);
+
+ tty->closing = 0;
+ portp->tty = (struct tty_struct *) NULL;
+
+ if (portp->openwaitcnt) {
+ if (portp->close_delay)
+ msleep_interruptible(jiffies_to_msecs(portp->close_delay));
+ wake_up_interruptible(&portp->open_wait);
+ }
+
+ portp->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&portp->close_wait);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Carry out first open operations on a port. This involves a number of
+ * commands to be sent to the slave. We need to open the port, set the
+ * notification events, set the initial port settings, get and set the
+ * initial signal values. We sleep and wait in between each one. But
+ * this still all happens pretty quickly.
+ */
+
+static int stli_initopen(stlibrd_t *brdp, stliport_t *portp)
+{
+ struct tty_struct *tty;
+ asynotify_t nt;
+ asyport_t aport;
+ int rc;
+
+#ifdef DEBUG
+ printk("stli_initopen(brdp=%x,portp=%x)\n", (int) brdp, (int) portp);
+#endif
+
+ if ((rc = stli_rawopen(brdp, portp, 0, 1)) < 0)
+ return(rc);
+
+ memset(&nt, 0, sizeof(asynotify_t));
+ nt.data = (DT_TXLOW | DT_TXEMPTY | DT_RXBUSY | DT_RXBREAK);
+ nt.signal = SG_DCD;
+ if ((rc = stli_cmdwait(brdp, portp, A_SETNOTIFY, &nt,
+ sizeof(asynotify_t), 0)) < 0)
+ return(rc);
+
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ stli_mkasyport(portp, &aport, tty->termios);
+ if ((rc = stli_cmdwait(brdp, portp, A_SETPORT, &aport,
+ sizeof(asyport_t), 0)) < 0)
+ return(rc);
+
+ set_bit(ST_GETSIGS, &portp->state);
+ if ((rc = stli_cmdwait(brdp, portp, A_GETSIGNALS, &portp->asig,
+ sizeof(asysigs_t), 1)) < 0)
+ return(rc);
+ if (test_and_clear_bit(ST_GETSIGS, &portp->state))
+ portp->sigs = stli_mktiocm(portp->asig.sigvalue);
+ stli_mkasysigs(&portp->asig, 1, 1);
+ if ((rc = stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+ sizeof(asysigs_t), 0)) < 0)
+ return(rc);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send an open message to the slave. This will sleep waiting for the
+ * acknowledgement, so must have user context. We need to co-ordinate
+ * with close events here, since we don't want open and close events
+ * to overlap.
+ */
+
+static int stli_rawopen(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait)
+{
+ volatile cdkhdr_t *hdrp;
+ volatile cdkctrl_t *cp;
+ volatile unsigned char *bits;
+ unsigned long flags;
+ int rc;
+
+#ifdef DEBUG
+ printk("stli_rawopen(brdp=%x,portp=%x,arg=%x,wait=%d)\n",
+ (int) brdp, (int) portp, (int) arg, wait);
+#endif
+
+/*
+ * Send a message to the slave to open this port.
+ */
+ save_flags(flags);
+ cli();
+
+/*
+ * Slave is already closing this port. This can happen if a hangup
+ * occurs on this port. So we must wait until it is complete. The
+ * order of opens and closes may not be preserved across shared
+ * memory, so we must wait until it is complete.
+ */
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_CLOSING, &portp->state));
+ if (signal_pending(current)) {
+ restore_flags(flags);
+ return -ERESTARTSYS;
+ }
+
+/*
+ * Everything is ready now, so write the open message into shared
+ * memory. Once the message is in set the service bits to say that
+ * this port wants service.
+ */
+ EBRDENABLE(brdp);
+ cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+ cp->openarg = arg;
+ cp->open = 1;
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+ portp->portidx;
+ *bits |= portp->portbit;
+ EBRDDISABLE(brdp);
+
+ if (wait == 0) {
+ restore_flags(flags);
+ return(0);
+ }
+
+/*
+ * Slave is in action, so now we must wait for the open acknowledgment
+ * to come back.
+ */
+ rc = 0;
+ set_bit(ST_OPENING, &portp->state);
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_OPENING, &portp->state));
+ if (signal_pending(current))
+ rc = -ERESTARTSYS;
+ restore_flags(flags);
+
+ if ((rc == 0) && (portp->rc != 0))
+ rc = -EIO;
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a close message to the slave. Normally this will sleep waiting
+ * for the acknowledgement, but if wait parameter is 0 it will not. If
+ * wait is true then must have user context (to sleep).
+ */
+
+static int stli_rawclose(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait)
+{
+ volatile cdkhdr_t *hdrp;
+ volatile cdkctrl_t *cp;
+ volatile unsigned char *bits;
+ unsigned long flags;
+ int rc;
+
+#ifdef DEBUG
+ printk("stli_rawclose(brdp=%x,portp=%x,arg=%x,wait=%d)\n",
+ (int) brdp, (int) portp, (int) arg, wait);
+#endif
+
+ save_flags(flags);
+ cli();
+
+/*
+ * Slave is already closing this port. This can happen if a hangup
+ * occurs on this port.
+ */
+ if (wait) {
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_CLOSING, &portp->state));
+ if (signal_pending(current)) {
+ restore_flags(flags);
+ return -ERESTARTSYS;
+ }
+ }
+
+/*
+ * Write the close command into shared memory.
+ */
+ EBRDENABLE(brdp);
+ cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+ cp->closearg = arg;
+ cp->close = 1;
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+ portp->portidx;
+ *bits |= portp->portbit;
+ EBRDDISABLE(brdp);
+
+ set_bit(ST_CLOSING, &portp->state);
+ if (wait == 0) {
+ restore_flags(flags);
+ return(0);
+ }
+
+/*
+ * Slave is in action, so now we must wait for the open acknowledgment
+ * to come back.
+ */
+ rc = 0;
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_CLOSING, &portp->state));
+ if (signal_pending(current))
+ rc = -ERESTARTSYS;
+ restore_flags(flags);
+
+ if ((rc == 0) && (portp->rc != 0))
+ rc = -EIO;
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a command to the slave and wait for the response. This must
+ * have user context (it sleeps). This routine is generic in that it
+ * can send any type of command. Its purpose is to wait for that command
+ * to complete (as opposed to initiating the command then returning).
+ */
+
+static int stli_cmdwait(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_cmdwait(brdp=%x,portp=%x,cmd=%x,arg=%x,size=%d,"
+ "copyback=%d)\n", (int) brdp, (int) portp, (int) cmd,
+ (int) arg, size, copyback);
+#endif
+
+ save_flags(flags);
+ cli();
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_CMDING, &portp->state));
+ if (signal_pending(current)) {
+ restore_flags(flags);
+ return -ERESTARTSYS;
+ }
+
+ stli_sendcmd(brdp, portp, cmd, arg, size, copyback);
+
+ wait_event_interruptible(portp->raw_wait,
+ !test_bit(ST_CMDING, &portp->state));
+ if (signal_pending(current)) {
+ restore_flags(flags);
+ return -ERESTARTSYS;
+ }
+ restore_flags(flags);
+
+ if (portp->rc != 0)
+ return(-EIO);
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send the termios settings for this port to the slave. This sleeps
+ * waiting for the command to complete - so must have user context.
+ */
+
+static int stli_setport(stliport_t *portp)
+{
+ stlibrd_t *brdp;
+ asyport_t aport;
+
+#ifdef DEBUG
+ printk("stli_setport(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ if (portp->tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ if ((portp->brdnr < 0) && (portp->brdnr >= stli_nrbrds))
+ return(-ENODEV);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+
+ stli_mkasyport(portp, &aport, portp->tty->termios);
+ return(stli_cmdwait(brdp, portp, A_SETPORT, &aport, sizeof(asyport_t), 0));
+}
+
+/*****************************************************************************/
+
+/*
+ * Possibly need to wait for carrier (DCD signal) to come high. Say
+ * maybe because if we are clocal then we don't need to wait...
+ */
+
+static int stli_waitcarrier(stlibrd_t *brdp, stliport_t *portp, struct file *filp)
+{
+ unsigned long flags;
+ int rc, doclocal;
+
+#ifdef DEBUG
+ printk("stli_waitcarrier(brdp=%x,portp=%x,filp=%x)\n",
+ (int) brdp, (int) portp, (int) filp);
+#endif
+
+ rc = 0;
+ doclocal = 0;
+
+ if (portp->tty->termios->c_cflag & CLOCAL)
+ doclocal++;
+
+ save_flags(flags);
+ cli();
+ portp->openwaitcnt++;
+ if (! tty_hung_up_p(filp))
+ portp->refcount--;
+
+ for (;;) {
+ stli_mkasysigs(&portp->asig, 1, 1);
+ if ((rc = stli_cmdwait(brdp, portp, A_SETSIGNALS,
+ &portp->asig, sizeof(asysigs_t), 0)) < 0)
+ break;
+ if (tty_hung_up_p(filp) ||
+ ((portp->flags & ASYNC_INITIALIZED) == 0)) {
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ rc = -EBUSY;
+ else
+ rc = -ERESTARTSYS;
+ break;
+ }
+ if (((portp->flags & ASYNC_CLOSING) == 0) &&
+ (doclocal || (portp->sigs & TIOCM_CD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+ interruptible_sleep_on(&portp->open_wait);
+ }
+
+ if (! tty_hung_up_p(filp))
+ portp->refcount++;
+ portp->openwaitcnt--;
+ restore_flags(flags);
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Write routine. Take the data and put it in the shared memory ring
+ * queue. If port is not already sending chars then need to mark the
+ * service bits for this port.
+ */
+
+static int stli_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ volatile cdkasy_t *ap;
+ volatile cdkhdr_t *hdrp;
+ volatile unsigned char *bits;
+ unsigned char *shbuf, *chbuf;
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned int len, stlen, head, tail, size;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_write(tty=%x,buf=%x,count=%d)\n",
+ (int) tty, (int) buf, count);
+#endif
+
+ if ((tty == (struct tty_struct *) NULL) ||
+ (stli_tmpwritebuf == (char *) NULL))
+ return(0);
+ if (tty == stli_txcooktty)
+ stli_flushchars(tty);
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return(0);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+ chbuf = (unsigned char *) buf;
+
+/*
+ * All data is now local, shove as much as possible into shared memory.
+ */
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ head = (unsigned int) ap->txq.head;
+ tail = (unsigned int) ap->txq.tail;
+ if (tail != ((unsigned int) ap->txq.tail))
+ tail = (unsigned int) ap->txq.tail;
+ size = portp->txsize;
+ if (head >= tail) {
+ len = size - (head - tail) - 1;
+ stlen = size - head;
+ } else {
+ len = tail - head - 1;
+ stlen = len;
+ }
+
+ len = MIN(len, count);
+ count = 0;
+ shbuf = (char *) EBRDGETMEMPTR(brdp, portp->txoffset);
+
+ while (len > 0) {
+ stlen = MIN(len, stlen);
+ memcpy((shbuf + head), chbuf, stlen);
+ chbuf += stlen;
+ len -= stlen;
+ count += stlen;
+ head += stlen;
+ if (head >= size) {
+ head = 0;
+ stlen = tail;
+ }
+ }
+
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ ap->txq.head = head;
+ if (test_bit(ST_TXBUSY, &portp->state)) {
+ if (ap->changed.data & DT_TXEMPTY)
+ ap->changed.data &= ~DT_TXEMPTY;
+ }
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+ portp->portidx;
+ *bits |= portp->portbit;
+ set_bit(ST_TXBUSY, &portp->state);
+ EBRDDISABLE(brdp);
+
+ restore_flags(flags);
+
+ return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ * Output a single character. We put it into a temporary local buffer
+ * (for speed) then write out that buffer when the flushchars routine
+ * is called. There is a safety catch here so that if some other port
+ * writes chars before the current buffer has been, then we write them
+ * first them do the new ports.
+ */
+
+static void stli_putchar(struct tty_struct *tty, unsigned char ch)
+{
+#ifdef DEBUG
+ printk("stli_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ if (tty != stli_txcooktty) {
+ if (stli_txcooktty != (struct tty_struct *) NULL)
+ stli_flushchars(stli_txcooktty);
+ stli_txcooktty = tty;
+ }
+
+ stli_txcookbuf[stli_txcooksize++] = ch;
+}
+
+/*****************************************************************************/
+
+/*
+ * Transfer characters from the local TX cooking buffer to the board.
+ * We sort of ignore the tty that gets passed in here. We rely on the
+ * info stored with the TX cook buffer to tell us which port to flush
+ * the data on. In any case we clean out the TX cook buffer, for re-use
+ * by someone else.
+ */
+
+static void stli_flushchars(struct tty_struct *tty)
+{
+ volatile cdkhdr_t *hdrp;
+ volatile unsigned char *bits;
+ volatile cdkasy_t *ap;
+ struct tty_struct *cooktty;
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned int len, stlen, head, tail, size, count, cooksize;
+ unsigned char *buf, *shbuf;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_flushchars(tty=%x)\n", (int) tty);
+#endif
+
+ cooksize = stli_txcooksize;
+ cooktty = stli_txcooktty;
+ stli_txcooksize = 0;
+ stli_txcookrealsize = 0;
+ stli_txcooktty = (struct tty_struct *) NULL;
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ if (cooktty == (struct tty_struct *) NULL)
+ return;
+ if (tty != cooktty)
+ tty = cooktty;
+ if (cooksize == 0)
+ return;
+
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ head = (unsigned int) ap->txq.head;
+ tail = (unsigned int) ap->txq.tail;
+ if (tail != ((unsigned int) ap->txq.tail))
+ tail = (unsigned int) ap->txq.tail;
+ size = portp->txsize;
+ if (head >= tail) {
+ len = size - (head - tail) - 1;
+ stlen = size - head;
+ } else {
+ len = tail - head - 1;
+ stlen = len;
+ }
+
+ len = MIN(len, cooksize);
+ count = 0;
+ shbuf = (char *) EBRDGETMEMPTR(brdp, portp->txoffset);
+ buf = stli_txcookbuf;
+
+ while (len > 0) {
+ stlen = MIN(len, stlen);
+ memcpy((shbuf + head), buf, stlen);
+ buf += stlen;
+ len -= stlen;
+ count += stlen;
+ head += stlen;
+ if (head >= size) {
+ head = 0;
+ stlen = tail;
+ }
+ }
+
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ ap->txq.head = head;
+
+ if (test_bit(ST_TXBUSY, &portp->state)) {
+ if (ap->changed.data & DT_TXEMPTY)
+ ap->changed.data &= ~DT_TXEMPTY;
+ }
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+ portp->portidx;
+ *bits |= portp->portbit;
+ set_bit(ST_TXBUSY, &portp->state);
+
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static int stli_writeroom(struct tty_struct *tty)
+{
+ volatile cdkasyrq_t *rp;
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned int head, tail, len;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_writeroom(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ if (tty == stli_txcooktty) {
+ if (stli_txcookrealsize != 0) {
+ len = stli_txcookrealsize - stli_txcooksize;
+ return(len);
+ }
+ }
+
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return(0);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->txq;
+ head = (unsigned int) rp->head;
+ tail = (unsigned int) rp->tail;
+ if (tail != ((unsigned int) rp->tail))
+ tail = (unsigned int) rp->tail;
+ len = (head >= tail) ? (portp->txsize - (head - tail)) : (tail - head);
+ len--;
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+
+ if (tty == stli_txcooktty) {
+ stli_txcookrealsize = len;
+ len -= stli_txcooksize;
+ }
+ return(len);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the number of characters in the transmit buffer. Normally we
+ * will return the number of chars in the shared memory ring queue.
+ * We need to kludge around the case where the shared memory buffer is
+ * empty but not all characters have drained yet, for this case just
+ * return that there is 1 character in the buffer!
+ */
+
+static int stli_charsinbuffer(struct tty_struct *tty)
+{
+ volatile cdkasyrq_t *rp;
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned int head, tail, len;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stli_charsinbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ if (tty == stli_txcooktty)
+ stli_flushchars(tty);
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return(0);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->txq;
+ head = (unsigned int) rp->head;
+ tail = (unsigned int) rp->tail;
+ if (tail != ((unsigned int) rp->tail))
+ tail = (unsigned int) rp->tail;
+ len = (head >= tail) ? (head - tail) : (portp->txsize - (tail - head));
+ if ((len == 0) && test_bit(ST_TXBUSY, &portp->state))
+ len = 1;
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+
+ return(len);
+}
+
+/*****************************************************************************/
+
+/*
+ * Generate the serial struct info.
+ */
+
+static int stli_getserial(stliport_t *portp, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+ stlibrd_t *brdp;
+
+#ifdef DEBUG
+ printk("stli_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+ memset(&sio, 0, sizeof(struct serial_struct));
+ sio.type = PORT_UNKNOWN;
+ sio.line = portp->portnr;
+ sio.irq = 0;
+ sio.flags = portp->flags;
+ sio.baud_base = portp->baud_base;
+ sio.close_delay = portp->close_delay;
+ sio.closing_wait = portp->closing_wait;
+ sio.custom_divisor = portp->custom_divisor;
+ sio.xmit_fifo_size = 0;
+ sio.hub6 = 0;
+
+ brdp = stli_brds[portp->brdnr];
+ if (brdp != (stlibrd_t *) NULL)
+ sio.port = brdp->iobase;
+
+ return copy_to_user(sp, &sio, sizeof(struct serial_struct)) ?
+ -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Set port according to the serial struct info.
+ * At this point we do not do any auto-configure stuff, so we will
+ * just quietly ignore any requests to change irq, etc.
+ */
+
+static int stli_setserial(stliport_t *portp, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+ int rc;
+
+#ifdef DEBUG
+ printk("stli_setserial(portp=%p,sp=%p)\n", portp, sp);
+#endif
+
+ if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+ return -EFAULT;
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((sio.baud_base != portp->baud_base) ||
+ (sio.close_delay != portp->close_delay) ||
+ ((sio.flags & ~ASYNC_USR_MASK) !=
+ (portp->flags & ~ASYNC_USR_MASK)))
+ return(-EPERM);
+ }
+
+ portp->flags = (portp->flags & ~ASYNC_USR_MASK) |
+ (sio.flags & ASYNC_USR_MASK);
+ portp->baud_base = sio.baud_base;
+ portp->close_delay = sio.close_delay;
+ portp->closing_wait = sio.closing_wait;
+ portp->custom_divisor = sio.custom_divisor;
+
+ if ((rc = stli_setport(portp)) < 0)
+ return(rc);
+ return(0);
+}
+
+/*****************************************************************************/
+
+static int stli_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ stliport_t *portp = tty->driver_data;
+ stlibrd_t *brdp;
+ int rc;
+
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+
+ if ((rc = stli_cmdwait(brdp, portp, A_GETSIGNALS,
+ &portp->asig, sizeof(asysigs_t), 1)) < 0)
+ return(rc);
+
+ return stli_mktiocm(portp->asig.sigvalue);
+}
+
+static int stli_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ stliport_t *portp = tty->driver_data;
+ stlibrd_t *brdp;
+ int rts = -1, dtr = -1;
+
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+
+ if (set & TIOCM_RTS)
+ rts = 1;
+ if (set & TIOCM_DTR)
+ dtr = 1;
+ if (clear & TIOCM_RTS)
+ rts = 0;
+ if (clear & TIOCM_DTR)
+ dtr = 0;
+
+ stli_mkasysigs(&portp->asig, dtr, rts);
+
+ return stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+ sizeof(asysigs_t), 0);
+}
+
+static int stli_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned int ival;
+ int rc;
+ void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+ printk("stli_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n",
+ (int) tty, (int) file, cmd, (int) arg);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return(0);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(0);
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+ }
+
+ rc = 0;
+
+ switch (cmd) {
+ case TIOCGSOFTCAR:
+ rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+ (unsigned __user *) arg);
+ break;
+ case TIOCSSOFTCAR:
+ if ((rc = get_user(ival, (unsigned __user *) arg)) == 0)
+ tty->termios->c_cflag =
+ (tty->termios->c_cflag & ~CLOCAL) |
+ (ival ? CLOCAL : 0);
+ break;
+ case TIOCGSERIAL:
+ rc = stli_getserial(portp, argp);
+ break;
+ case TIOCSSERIAL:
+ rc = stli_setserial(portp, argp);
+ break;
+ case STL_GETPFLAG:
+ rc = put_user(portp->pflag, (unsigned __user *)argp);
+ break;
+ case STL_SETPFLAG:
+ if ((rc = get_user(portp->pflag, (unsigned __user *)argp)) == 0)
+ stli_setport(portp);
+ break;
+ case COM_GETPORTSTATS:
+ rc = stli_getportstats(portp, argp);
+ break;
+ case COM_CLRPORTSTATS:
+ rc = stli_clrportstats(portp, argp);
+ break;
+ case TIOCSERCONFIG:
+ case TIOCSERGWILD:
+ case TIOCSERSWILD:
+ case TIOCSERGETLSR:
+ case TIOCSERGSTRUCT:
+ case TIOCSERGETMULTI:
+ case TIOCSERSETMULTI:
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * This routine assumes that we have user context and can sleep.
+ * Looks like it is true for the current ttys implementation..!!
+ */
+
+static void stli_settermios(struct tty_struct *tty, struct termios *old)
+{
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ struct termios *tiosp;
+ asyport_t aport;
+
+#ifdef DEBUG
+ printk("stli_settermios(tty=%x,old=%x)\n", (int) tty, (int) old);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ tiosp = tty->termios;
+ if ((tiosp->c_cflag == old->c_cflag) &&
+ (tiosp->c_iflag == old->c_iflag))
+ return;
+
+ stli_mkasyport(portp, &aport, tiosp);
+ stli_cmdwait(brdp, portp, A_SETPORT, &aport, sizeof(asyport_t), 0);
+ stli_mkasysigs(&portp->asig, ((tiosp->c_cflag & CBAUD) ? 1 : 0), -1);
+ stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+ sizeof(asysigs_t), 0);
+ if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0))
+ tty->hw_stopped = 0;
+ if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL))
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ * Attempt to flow control who ever is sending us data. We won't really
+ * do any flow control action here. We can't directly, and even if we
+ * wanted to we would have to send a command to the slave. The slave
+ * knows how to flow control, and will do so when its buffers reach its
+ * internal high water marks. So what we will do is set a local state
+ * bit that will stop us sending any RX data up from the poll routine
+ * (which is the place where RX data from the slave is handled).
+ */
+
+static void stli_throttle(struct tty_struct *tty)
+{
+ stliport_t *portp;
+
+#ifdef DEBUG
+ printk("stli_throttle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+
+ set_bit(ST_RXSTOP, &portp->state);
+}
+
+/*****************************************************************************/
+
+/*
+ * Unflow control the device sending us data... That means that all
+ * we have to do is clear the RXSTOP state bit. The next poll call
+ * will then be able to pass the RX data back up.
+ */
+
+static void stli_unthrottle(struct tty_struct *tty)
+{
+ stliport_t *portp;
+
+#ifdef DEBUG
+ printk("stli_unthrottle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+
+ clear_bit(ST_RXSTOP, &portp->state);
+}
+
+/*****************************************************************************/
+
+/*
+ * Stop the transmitter. Basically to do this we will just turn TX
+ * interrupts off.
+ */
+
+static void stli_stop(struct tty_struct *tty)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ asyctrl_t actrl;
+
+#ifdef DEBUG
+ printk("stli_stop(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ memset(&actrl, 0, sizeof(asyctrl_t));
+ actrl.txctrl = CT_STOPFLOW;
+#if 0
+ stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+#endif
+}
+
+/*****************************************************************************/
+
+/*
+ * Start the transmitter again. Just turn TX interrupts back on.
+ */
+
+static void stli_start(struct tty_struct *tty)
+{
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ asyctrl_t actrl;
+
+#ifdef DEBUG
+ printk("stli_start(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ memset(&actrl, 0, sizeof(asyctrl_t));
+ actrl.txctrl = CT_STARTFLOW;
+#if 0
+ stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+#endif
+}
+
+/*****************************************************************************/
+
+/*
+ * Scheduler called hang up routine. This is called from the scheduler,
+ * not direct from the driver "poll" routine. We can't call it there
+ * since the real local hangup code will enable/disable the board and
+ * other things that we can't do while handling the poll. Much easier
+ * to deal with it some time later (don't really care when, hangups
+ * aren't that time critical).
+ */
+
+static void stli_dohangup(void *arg)
+{
+ stliport_t *portp;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_dohangup(portp=%x)\n", (int) arg);
+#endif
+
+ /*
+ * FIXME: There's a module removal race here: tty_hangup
+ * calls schedule_work which will call into this
+ * driver later.
+ */
+ portp = (stliport_t *) arg;
+ if (portp != (stliport_t *) NULL) {
+ if (portp->tty != (struct tty_struct *) NULL) {
+ tty_hangup(portp->tty);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Hangup this port. This is pretty much like closing the port, only
+ * a little more brutal. No waiting for data to drain. Shutdown the
+ * port and maybe drop signals. This is rather tricky really. We want
+ * to close the port as well.
+ */
+
+static void stli_hangup(struct tty_struct *tty)
+{
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_hangup(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+
+ save_flags(flags);
+ cli();
+ if (! test_bit(ST_CLOSING, &portp->state))
+ stli_rawclose(brdp, portp, 0, 0);
+ if (tty->termios->c_cflag & HUPCL) {
+ stli_mkasysigs(&portp->asig, 0, 0);
+ if (test_bit(ST_CMDING, &portp->state)) {
+ set_bit(ST_DOSIGS, &portp->state);
+ set_bit(ST_DOFLUSHTX, &portp->state);
+ set_bit(ST_DOFLUSHRX, &portp->state);
+ } else {
+ stli_sendcmd(brdp, portp, A_SETSIGNALSF,
+ &portp->asig, sizeof(asysigs_t), 0);
+ }
+ }
+ restore_flags(flags);
+
+ clear_bit(ST_TXBUSY, &portp->state);
+ clear_bit(ST_RXSTOP, &portp->state);
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ portp->tty = (struct tty_struct *) NULL;
+ portp->flags &= ~ASYNC_NORMAL_ACTIVE;
+ portp->refcount = 0;
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ * Flush characters from the lower buffer. We may not have user context
+ * so we cannot sleep waiting for it to complete. Also we need to check
+ * if there is chars for this port in the TX cook buffer, and flush them
+ * as well.
+ */
+
+static void stli_flushbuffer(struct tty_struct *tty)
+{
+ stliport_t *portp;
+ stlibrd_t *brdp;
+ unsigned long ftype, flags;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_flushbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ if (tty == stli_txcooktty) {
+ stli_txcooktty = (struct tty_struct *) NULL;
+ stli_txcooksize = 0;
+ stli_txcookrealsize = 0;
+ }
+ if (test_bit(ST_CMDING, &portp->state)) {
+ set_bit(ST_DOFLUSHTX, &portp->state);
+ } else {
+ ftype = FLUSHTX;
+ if (test_bit(ST_DOFLUSHRX, &portp->state)) {
+ ftype |= FLUSHRX;
+ clear_bit(ST_DOFLUSHRX, &portp->state);
+ }
+ stli_sendcmd(brdp, portp, A_FLUSH, &ftype,
+ sizeof(unsigned long), 0);
+ }
+ restore_flags(flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup)
+ (tty->ldisc.write_wakeup)(tty);
+}
+
+/*****************************************************************************/
+
+static void stli_breakctl(struct tty_struct *tty, int state)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ long arg;
+ /* long savestate, savetime; */
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_breakctl(tty=%x,state=%d)\n", (int) tty, state);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+/*
+ * Due to a bug in the tty send_break() code we need to preserve
+ * the current process state and timeout...
+ savetime = current->timeout;
+ savestate = current->state;
+ */
+
+ arg = (state == -1) ? BREAKON : BREAKOFF;
+ stli_cmdwait(brdp, portp, A_BREAK, &arg, sizeof(long), 0);
+
+/*
+ *
+ current->timeout = savetime;
+ current->state = savestate;
+ */
+}
+
+/*****************************************************************************/
+
+static void stli_waituntilsent(struct tty_struct *tty, int timeout)
+{
+ stliport_t *portp;
+ unsigned long tend;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_waituntilsent(tty=%x,timeout=%x)\n", (int) tty, timeout);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+
+ if (timeout == 0)
+ timeout = HZ;
+ tend = jiffies + timeout;
+
+ while (test_bit(ST_TXBUSY, &portp->state)) {
+ if (signal_pending(current))
+ break;
+ msleep_interruptible(20);
+ if (time_after_eq(jiffies, tend))
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static void stli_sendxchar(struct tty_struct *tty, char ch)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ asyctrl_t actrl;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stliport_t *) NULL)
+ return;
+ if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+ return;
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return;
+
+ memset(&actrl, 0, sizeof(asyctrl_t));
+ if (ch == STOP_CHAR(tty)) {
+ actrl.rxctrl = CT_STOPFLOW;
+ } else if (ch == START_CHAR(tty)) {
+ actrl.rxctrl = CT_STARTFLOW;
+ } else {
+ actrl.txctrl = CT_SENDCHR;
+ actrl.tximdch = ch;
+ }
+
+ stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+}
+
+/*****************************************************************************/
+
+#define MAXLINE 80
+
+/*
+ * Format info for a specified port. The line is deliberately limited
+ * to 80 characters. (If it is too long it will be truncated, if too
+ * short then padded with spaces).
+ */
+
+static int stli_portinfo(stlibrd_t *brdp, stliport_t *portp, int portnr, char *pos)
+{
+ char *sp, *uart;
+ int rc, cnt;
+
+ rc = stli_portcmdstats(portp);
+
+ uart = "UNKNOWN";
+ if (brdp->state & BST_STARTED) {
+ switch (stli_comstats.hwid) {
+ case 0: uart = "2681"; break;
+ case 1: uart = "SC26198"; break;
+ default: uart = "CD1400"; break;
+ }
+ }
+
+ sp = pos;
+ sp += sprintf(sp, "%d: uart:%s ", portnr, uart);
+
+ if ((brdp->state & BST_STARTED) && (rc >= 0)) {
+ sp += sprintf(sp, "tx:%d rx:%d", (int) stli_comstats.txtotal,
+ (int) stli_comstats.rxtotal);
+
+ if (stli_comstats.rxframing)
+ sp += sprintf(sp, " fe:%d",
+ (int) stli_comstats.rxframing);
+ if (stli_comstats.rxparity)
+ sp += sprintf(sp, " pe:%d",
+ (int) stli_comstats.rxparity);
+ if (stli_comstats.rxbreaks)
+ sp += sprintf(sp, " brk:%d",
+ (int) stli_comstats.rxbreaks);
+ if (stli_comstats.rxoverrun)
+ sp += sprintf(sp, " oe:%d",
+ (int) stli_comstats.rxoverrun);
+
+ cnt = sprintf(sp, "%s%s%s%s%s ",
+ (stli_comstats.signals & TIOCM_RTS) ? "|RTS" : "",
+ (stli_comstats.signals & TIOCM_CTS) ? "|CTS" : "",
+ (stli_comstats.signals & TIOCM_DTR) ? "|DTR" : "",
+ (stli_comstats.signals & TIOCM_CD) ? "|DCD" : "",
+ (stli_comstats.signals & TIOCM_DSR) ? "|DSR" : "");
+ *sp = ' ';
+ sp += cnt;
+ }
+
+ for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++)
+ *sp++ = ' ';
+ if (cnt >= MAXLINE)
+ pos[(MAXLINE - 2)] = '+';
+ pos[(MAXLINE - 1)] = '\n';
+
+ return(MAXLINE);
+}
+
+/*****************************************************************************/
+
+/*
+ * Port info, read from the /proc file system.
+ */
+
+static int stli_readproc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ stlibrd_t *brdp;
+ stliport_t *portp;
+ int brdnr, portnr, totalport;
+ int curoff, maxoff;
+ char *pos;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x,"
+ "data=%x\n", (int) page, (int) start, (int) off, count,
+ (int) eof, (int) data);
+#endif
+
+ pos = page;
+ totalport = 0;
+ curoff = 0;
+
+ if (off == 0) {
+ pos += sprintf(pos, "%s: version %s", stli_drvtitle,
+ stli_drvversion);
+ while (pos < (page + MAXLINE - 1))
+ *pos++ = ' ';
+ *pos++ = '\n';
+ }
+ curoff = MAXLINE;
+
+/*
+ * We scan through for each board, panel and port. The offset is
+ * calculated on the fly, and irrelevant ports are skipped.
+ */
+ for (brdnr = 0; (brdnr < stli_nrbrds); brdnr++) {
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ continue;
+ if (brdp->state == 0)
+ continue;
+
+ maxoff = curoff + (brdp->nrports * MAXLINE);
+ if (off >= maxoff) {
+ curoff = maxoff;
+ continue;
+ }
+
+ totalport = brdnr * STL_MAXPORTS;
+ for (portnr = 0; (portnr < brdp->nrports); portnr++,
+ totalport++) {
+ portp = brdp->ports[portnr];
+ if (portp == (stliport_t *) NULL)
+ continue;
+ if (off >= (curoff += MAXLINE))
+ continue;
+ if ((pos - page + MAXLINE) > count)
+ goto stli_readdone;
+ pos += stli_portinfo(brdp, portp, totalport, pos);
+ }
+ }
+
+ *eof = 1;
+
+stli_readdone:
+ *start = page;
+ return(pos - page);
+}
+
+/*****************************************************************************/
+
+/*
+ * Generic send command routine. This will send a message to the slave,
+ * of the specified type with the specified argument. Must be very
+ * careful of data that will be copied out from shared memory -
+ * containing command results. The command completion is all done from
+ * a poll routine that does not have user context. Therefore you cannot
+ * copy back directly into user space, or to the kernel stack of a
+ * process. This routine does not sleep, so can be called from anywhere.
+ */
+
+static void stli_sendcmd(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback)
+{
+ volatile cdkhdr_t *hdrp;
+ volatile cdkctrl_t *cp;
+ volatile unsigned char *bits;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_sendcmd(brdp=%x,portp=%x,cmd=%x,arg=%x,size=%d,"
+ "copyback=%d)\n", (int) brdp, (int) portp, (int) cmd,
+ (int) arg, size, copyback);
+#endif
+
+ save_flags(flags);
+ cli();
+
+ if (test_bit(ST_CMDING, &portp->state)) {
+ printk(KERN_ERR "STALLION: command already busy, cmd=%x!\n",
+ (int) cmd);
+ restore_flags(flags);
+ return;
+ }
+
+ EBRDENABLE(brdp);
+ cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+ if (size > 0) {
+ memcpy((void *) &(cp->args[0]), arg, size);
+ if (copyback) {
+ portp->argp = arg;
+ portp->argsize = size;
+ }
+ }
+ cp->status = 0;
+ cp->cmd = cmd;
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+ portp->portidx;
+ *bits |= portp->portbit;
+ set_bit(ST_CMDING, &portp->state);
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Read data from shared memory. This assumes that the shared memory
+ * is enabled and that interrupts are off. Basically we just empty out
+ * the shared memory buffer into the tty buffer. Must be careful to
+ * handle the case where we fill up the tty buffer, but still have
+ * more chars to unload.
+ */
+
+static void stli_read(stlibrd_t *brdp, stliport_t *portp)
+{
+ volatile cdkasyrq_t *rp;
+ volatile char *shbuf;
+ struct tty_struct *tty;
+ unsigned int head, tail, size;
+ unsigned int len, stlen;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_read(brdp=%x,portp=%d)\n",
+ (int) brdp, (int) portp);
+#endif
+
+ if (test_bit(ST_RXSTOP, &portp->state))
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->rxq;
+ head = (unsigned int) rp->head;
+ if (head != ((unsigned int) rp->head))
+ head = (unsigned int) rp->head;
+ tail = (unsigned int) rp->tail;
+ size = portp->rxsize;
+ if (head >= tail) {
+ len = head - tail;
+ stlen = len;
+ } else {
+ len = size - (tail - head);
+ stlen = size - tail;
+ }
+
+ len = MIN(len, (TTY_FLIPBUF_SIZE - tty->flip.count));
+ shbuf = (volatile char *) EBRDGETMEMPTR(brdp, portp->rxoffset);
+
+ while (len > 0) {
+ stlen = MIN(len, stlen);
+ memcpy(tty->flip.char_buf_ptr, (char *) (shbuf + tail), stlen);
+ memset(tty->flip.flag_buf_ptr, 0, stlen);
+ tty->flip.char_buf_ptr += stlen;
+ tty->flip.flag_buf_ptr += stlen;
+ tty->flip.count += stlen;
+
+ len -= stlen;
+ tail += stlen;
+ if (tail >= size) {
+ tail = 0;
+ stlen = head;
+ }
+ }
+ rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->rxq;
+ rp->tail = tail;
+
+ if (head != tail)
+ set_bit(ST_RXING, &portp->state);
+
+ tty_schedule_flip(tty);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set up and carry out any delayed commands. There is only a small set
+ * of slave commands that can be done "off-level". So it is not too
+ * difficult to deal with them here.
+ */
+
+static void stli_dodelaycmd(stliport_t *portp, volatile cdkctrl_t *cp)
+{
+ int cmd;
+
+ if (test_bit(ST_DOSIGS, &portp->state)) {
+ if (test_bit(ST_DOFLUSHTX, &portp->state) &&
+ test_bit(ST_DOFLUSHRX, &portp->state))
+ cmd = A_SETSIGNALSF;
+ else if (test_bit(ST_DOFLUSHTX, &portp->state))
+ cmd = A_SETSIGNALSFTX;
+ else if (test_bit(ST_DOFLUSHRX, &portp->state))
+ cmd = A_SETSIGNALSFRX;
+ else
+ cmd = A_SETSIGNALS;
+ clear_bit(ST_DOFLUSHTX, &portp->state);
+ clear_bit(ST_DOFLUSHRX, &portp->state);
+ clear_bit(ST_DOSIGS, &portp->state);
+ memcpy((void *) &(cp->args[0]), (void *) &portp->asig,
+ sizeof(asysigs_t));
+ cp->status = 0;
+ cp->cmd = cmd;
+ set_bit(ST_CMDING, &portp->state);
+ } else if (test_bit(ST_DOFLUSHTX, &portp->state) ||
+ test_bit(ST_DOFLUSHRX, &portp->state)) {
+ cmd = ((test_bit(ST_DOFLUSHTX, &portp->state)) ? FLUSHTX : 0);
+ cmd |= ((test_bit(ST_DOFLUSHRX, &portp->state)) ? FLUSHRX : 0);
+ clear_bit(ST_DOFLUSHTX, &portp->state);
+ clear_bit(ST_DOFLUSHRX, &portp->state);
+ memcpy((void *) &(cp->args[0]), (void *) &cmd, sizeof(int));
+ cp->status = 0;
+ cp->cmd = A_FLUSH;
+ set_bit(ST_CMDING, &portp->state);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Host command service checking. This handles commands or messages
+ * coming from the slave to the host. Must have board shared memory
+ * enabled and interrupts off when called. Notice that by servicing the
+ * read data last we don't need to change the shared memory pointer
+ * during processing (which is a slow IO operation).
+ * Return value indicates if this port is still awaiting actions from
+ * the slave (like open, command, or even TX data being sent). If 0
+ * then port is still busy, otherwise no longer busy.
+ */
+
+static int stli_hostcmd(stlibrd_t *brdp, stliport_t *portp)
+{
+ volatile cdkasy_t *ap;
+ volatile cdkctrl_t *cp;
+ struct tty_struct *tty;
+ asynotify_t nt;
+ unsigned long oldsigs;
+ int rc, donerx;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_hostcmd(brdp=%x,channr=%d)\n",
+ (int) brdp, channr);
+#endif
+
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ cp = &ap->ctrl;
+
+/*
+ * Check if we are waiting for an open completion message.
+ */
+ if (test_bit(ST_OPENING, &portp->state)) {
+ rc = (int) cp->openarg;
+ if ((cp->open == 0) && (rc != 0)) {
+ if (rc > 0)
+ rc--;
+ cp->openarg = 0;
+ portp->rc = rc;
+ clear_bit(ST_OPENING, &portp->state);
+ wake_up_interruptible(&portp->raw_wait);
+ }
+ }
+
+/*
+ * Check if we are waiting for a close completion message.
+ */
+ if (test_bit(ST_CLOSING, &portp->state)) {
+ rc = (int) cp->closearg;
+ if ((cp->close == 0) && (rc != 0)) {
+ if (rc > 0)
+ rc--;
+ cp->closearg = 0;
+ portp->rc = rc;
+ clear_bit(ST_CLOSING, &portp->state);
+ wake_up_interruptible(&portp->raw_wait);
+ }
+ }
+
+/*
+ * Check if we are waiting for a command completion message. We may
+ * need to copy out the command results associated with this command.
+ */
+ if (test_bit(ST_CMDING, &portp->state)) {
+ rc = cp->status;
+ if ((cp->cmd == 0) && (rc != 0)) {
+ if (rc > 0)
+ rc--;
+ if (portp->argp != (void *) NULL) {
+ memcpy(portp->argp, (void *) &(cp->args[0]),
+ portp->argsize);
+ portp->argp = (void *) NULL;
+ }
+ cp->status = 0;
+ portp->rc = rc;
+ clear_bit(ST_CMDING, &portp->state);
+ stli_dodelaycmd(portp, cp);
+ wake_up_interruptible(&portp->raw_wait);
+ }
+ }
+
+/*
+ * Check for any notification messages ready. This includes lots of
+ * different types of events - RX chars ready, RX break received,
+ * TX data low or empty in the slave, modem signals changed state.
+ */
+ donerx = 0;
+
+ if (ap->notify) {
+ nt = ap->changed;
+ ap->notify = 0;
+ tty = portp->tty;
+
+ if (nt.signal & SG_DCD) {
+ oldsigs = portp->sigs;
+ portp->sigs = stli_mktiocm(nt.sigvalue);
+ clear_bit(ST_GETSIGS, &portp->state);
+ if ((portp->sigs & TIOCM_CD) &&
+ ((oldsigs & TIOCM_CD) == 0))
+ wake_up_interruptible(&portp->open_wait);
+ if ((oldsigs & TIOCM_CD) &&
+ ((portp->sigs & TIOCM_CD) == 0)) {
+ if (portp->flags & ASYNC_CHECK_CD) {
+ if (tty)
+ schedule_work(&portp->tqhangup);
+ }
+ }
+ }
+
+ if (nt.data & DT_TXEMPTY)
+ clear_bit(ST_TXBUSY, &portp->state);
+ if (nt.data & (DT_TXEMPTY | DT_TXLOW)) {
+ if (tty != (struct tty_struct *) NULL) {
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ tty->ldisc.write_wakeup) {
+ (tty->ldisc.write_wakeup)(tty);
+ EBRDENABLE(brdp);
+ }
+ wake_up_interruptible(&tty->write_wait);
+ }
+ }
+
+ if ((nt.data & DT_RXBREAK) && (portp->rxmarkmsk & BRKINT)) {
+ if (tty != (struct tty_struct *) NULL) {
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ *tty->flip.char_buf_ptr++ = 0;
+ if (portp->flags & ASYNC_SAK) {
+ do_SAK(tty);
+ EBRDENABLE(brdp);
+ }
+ tty_schedule_flip(tty);
+ }
+ }
+ }
+
+ if (nt.data & DT_RXBUSY) {
+ donerx++;
+ stli_read(brdp, portp);
+ }
+ }
+
+/*
+ * It might seem odd that we are checking for more RX chars here.
+ * But, we need to handle the case where the tty buffer was previously
+ * filled, but we had more characters to pass up. The slave will not
+ * send any more RX notify messages until the RX buffer has been emptied.
+ * But it will leave the service bits on (since the buffer is not empty).
+ * So from here we can try to process more RX chars.
+ */
+ if ((!donerx) && test_bit(ST_RXING, &portp->state)) {
+ clear_bit(ST_RXING, &portp->state);
+ stli_read(brdp, portp);
+ }
+
+ return((test_bit(ST_OPENING, &portp->state) ||
+ test_bit(ST_CLOSING, &portp->state) ||
+ test_bit(ST_CMDING, &portp->state) ||
+ test_bit(ST_TXBUSY, &portp->state) ||
+ test_bit(ST_RXING, &portp->state)) ? 0 : 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Service all ports on a particular board. Assumes that the boards
+ * shared memory is enabled, and that the page pointer is pointed
+ * at the cdk header structure.
+ */
+
+static void stli_brdpoll(stlibrd_t *brdp, volatile cdkhdr_t *hdrp)
+{
+ stliport_t *portp;
+ unsigned char hostbits[(STL_MAXCHANS / 8) + 1];
+ unsigned char slavebits[(STL_MAXCHANS / 8) + 1];
+ unsigned char *slavep;
+ int bitpos, bitat, bitsize;
+ int channr, nrdevs, slavebitchange;
+
+ bitsize = brdp->bitsize;
+ nrdevs = brdp->nrdevs;
+
+/*
+ * Check if slave wants any service. Basically we try to do as
+ * little work as possible here. There are 2 levels of service
+ * bits. So if there is nothing to do we bail early. We check
+ * 8 service bits at a time in the inner loop, so we can bypass
+ * the lot if none of them want service.
+ */
+ memcpy(&hostbits[0], (((unsigned char *) hdrp) + brdp->hostoffset),
+ bitsize);
+
+ memset(&slavebits[0], 0, bitsize);
+ slavebitchange = 0;
+
+ for (bitpos = 0; (bitpos < bitsize); bitpos++) {
+ if (hostbits[bitpos] == 0)
+ continue;
+ channr = bitpos * 8;
+ for (bitat = 0x1; (channr < nrdevs); channr++, bitat <<= 1) {
+ if (hostbits[bitpos] & bitat) {
+ portp = brdp->ports[(channr - 1)];
+ if (stli_hostcmd(brdp, portp)) {
+ slavebitchange++;
+ slavebits[bitpos] |= bitat;
+ }
+ }
+ }
+ }
+
+/*
+ * If any of the ports are no longer busy then update them in the
+ * slave request bits. We need to do this after, since a host port
+ * service may initiate more slave requests.
+ */
+ if (slavebitchange) {
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ slavep = ((unsigned char *) hdrp) + brdp->slaveoffset;
+ for (bitpos = 0; (bitpos < bitsize); bitpos++) {
+ if (slavebits[bitpos])
+ slavep[bitpos] &= ~slavebits[bitpos];
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Driver poll routine. This routine polls the boards in use and passes
+ * messages back up to host when necessary. This is actually very
+ * CPU efficient, since we will always have the kernel poll clock, it
+ * adds only a few cycles when idle (since board service can be
+ * determined very easily), but when loaded generates no interrupts
+ * (with their expensive associated context change).
+ */
+
+static void stli_poll(unsigned long arg)
+{
+ volatile cdkhdr_t *hdrp;
+ stlibrd_t *brdp;
+ int brdnr;
+
+ stli_timerlist.expires = STLI_TIMEOUT;
+ add_timer(&stli_timerlist);
+
+/*
+ * Check each board and do any servicing required.
+ */
+ for (brdnr = 0; (brdnr < stli_nrbrds); brdnr++) {
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ continue;
+ if ((brdp->state & BST_STARTED) == 0)
+ continue;
+
+ EBRDENABLE(brdp);
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ if (hdrp->hostreq)
+ stli_brdpoll(brdp, hdrp);
+ EBRDDISABLE(brdp);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Translate the termios settings into the port setting structure of
+ * the slave.
+ */
+
+static void stli_mkasyport(stliport_t *portp, asyport_t *pp, struct termios *tiosp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_mkasyport(portp=%x,pp=%x,tiosp=%d)\n",
+ (int) portp, (int) pp, (int) tiosp);
+#endif
+
+ memset(pp, 0, sizeof(asyport_t));
+
+/*
+ * Start of by setting the baud, char size, parity and stop bit info.
+ */
+ pp->baudout = tiosp->c_cflag & CBAUD;
+ if (pp->baudout & CBAUDEX) {
+ pp->baudout &= ~CBAUDEX;
+ if ((pp->baudout < 1) || (pp->baudout > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ pp->baudout += 15;
+ }
+ pp->baudout = stli_baudrates[pp->baudout];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ pp->baudout = 57600;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ pp->baudout = 115200;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ pp->baudout = 230400;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ pp->baudout = 460800;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ pp->baudout = (portp->baud_base / portp->custom_divisor);
+ }
+ if (pp->baudout > STL_MAXBAUD)
+ pp->baudout = STL_MAXBAUD;
+ pp->baudin = pp->baudout;
+
+ switch (tiosp->c_cflag & CSIZE) {
+ case CS5:
+ pp->csize = 5;
+ break;
+ case CS6:
+ pp->csize = 6;
+ break;
+ case CS7:
+ pp->csize = 7;
+ break;
+ default:
+ pp->csize = 8;
+ break;
+ }
+
+ if (tiosp->c_cflag & CSTOPB)
+ pp->stopbs = PT_STOP2;
+ else
+ pp->stopbs = PT_STOP1;
+
+ if (tiosp->c_cflag & PARENB) {
+ if (tiosp->c_cflag & PARODD)
+ pp->parity = PT_ODDPARITY;
+ else
+ pp->parity = PT_EVENPARITY;
+ } else {
+ pp->parity = PT_NOPARITY;
+ }
+
+/*
+ * Set up any flow control options enabled.
+ */
+ if (tiosp->c_iflag & IXON) {
+ pp->flow |= F_IXON;
+ if (tiosp->c_iflag & IXANY)
+ pp->flow |= F_IXANY;
+ }
+ if (tiosp->c_cflag & CRTSCTS)
+ pp->flow |= (F_RTSFLOW | F_CTSFLOW);
+
+ pp->startin = tiosp->c_cc[VSTART];
+ pp->stopin = tiosp->c_cc[VSTOP];
+ pp->startout = tiosp->c_cc[VSTART];
+ pp->stopout = tiosp->c_cc[VSTOP];
+
+/*
+ * Set up the RX char marking mask with those RX error types we must
+ * catch. We can get the slave to help us out a little here, it will
+ * ignore parity errors and breaks for us, and mark parity errors in
+ * the data stream.
+ */
+ if (tiosp->c_iflag & IGNPAR)
+ pp->iflag |= FI_IGNRXERRS;
+ if (tiosp->c_iflag & IGNBRK)
+ pp->iflag |= FI_IGNBREAK;
+
+ portp->rxmarkmsk = 0;
+ if (tiosp->c_iflag & (INPCK | PARMRK))
+ pp->iflag |= FI_1MARKRXERRS;
+ if (tiosp->c_iflag & BRKINT)
+ portp->rxmarkmsk |= BRKINT;
+
+/*
+ * Set up clocal processing as required.
+ */
+ if (tiosp->c_cflag & CLOCAL)
+ portp->flags &= ~ASYNC_CHECK_CD;
+ else
+ portp->flags |= ASYNC_CHECK_CD;
+
+/*
+ * Transfer any persistent flags into the asyport structure.
+ */
+ pp->pflag = (portp->pflag & 0xffff);
+ pp->vmin = (portp->pflag & P_RXIMIN) ? 1 : 0;
+ pp->vtime = (portp->pflag & P_RXITIME) ? 1 : 0;
+ pp->cc[1] = (portp->pflag & P_RXTHOLD) ? 1 : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Construct a slave signals structure for setting the DTR and RTS
+ * signals as specified.
+ */
+
+static void stli_mkasysigs(asysigs_t *sp, int dtr, int rts)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_mkasysigs(sp=%x,dtr=%d,rts=%d)\n",
+ (int) sp, dtr, rts);
+#endif
+
+ memset(sp, 0, sizeof(asysigs_t));
+ if (dtr >= 0) {
+ sp->signal |= SG_DTR;
+ sp->sigvalue |= ((dtr > 0) ? SG_DTR : 0);
+ }
+ if (rts >= 0) {
+ sp->signal |= SG_RTS;
+ sp->sigvalue |= ((rts > 0) ? SG_RTS : 0);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Convert the signals returned from the slave into a local TIOCM type
+ * signals value. We keep them locally in TIOCM format.
+ */
+
+static long stli_mktiocm(unsigned long sigvalue)
+{
+ long tiocm;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_mktiocm(sigvalue=%x)\n", (int) sigvalue);
+#endif
+
+ tiocm = 0;
+ tiocm |= ((sigvalue & SG_DCD) ? TIOCM_CD : 0);
+ tiocm |= ((sigvalue & SG_CTS) ? TIOCM_CTS : 0);
+ tiocm |= ((sigvalue & SG_RI) ? TIOCM_RI : 0);
+ tiocm |= ((sigvalue & SG_DSR) ? TIOCM_DSR : 0);
+ tiocm |= ((sigvalue & SG_DTR) ? TIOCM_DTR : 0);
+ tiocm |= ((sigvalue & SG_RTS) ? TIOCM_RTS : 0);
+ return(tiocm);
+}
+
+/*****************************************************************************/
+
+/*
+ * All panels and ports actually attached have been worked out. All
+ * we need to do here is set up the appropriate per port data structures.
+ */
+
+static int stli_initports(stlibrd_t *brdp)
+{
+ stliport_t *portp;
+ int i, panelnr, panelport;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_initports(brdp=%x)\n", (int) brdp);
+#endif
+
+ for (i = 0, panelnr = 0, panelport = 0; (i < brdp->nrports); i++) {
+ portp = (stliport_t *) stli_memalloc(sizeof(stliport_t));
+ if (portp == (stliport_t *) NULL) {
+ printk("STALLION: failed to allocate port structure\n");
+ continue;
+ }
+
+ memset(portp, 0, sizeof(stliport_t));
+ portp->magic = STLI_PORTMAGIC;
+ portp->portnr = i;
+ portp->brdnr = brdp->brdnr;
+ portp->panelnr = panelnr;
+ portp->baud_base = STL_BAUDBASE;
+ portp->close_delay = STL_CLOSEDELAY;
+ portp->closing_wait = 30 * HZ;
+ INIT_WORK(&portp->tqhangup, stli_dohangup, portp);
+ init_waitqueue_head(&portp->open_wait);
+ init_waitqueue_head(&portp->close_wait);
+ init_waitqueue_head(&portp->raw_wait);
+ panelport++;
+ if (panelport >= brdp->panels[panelnr]) {
+ panelport = 0;
+ panelnr++;
+ }
+ brdp->ports[i] = portp;
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * All the following routines are board specific hardware operations.
+ */
+
+static void stli_ecpinit(stlibrd_t *brdp)
+{
+ unsigned long memconf;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpinit(brdp=%d)\n", (int) brdp);
+#endif
+
+ outb(ECP_ATSTOP, (brdp->iobase + ECP_ATCONFR));
+ udelay(10);
+ outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+ udelay(100);
+
+ memconf = (brdp->memaddr & ECP_ATADDRMASK) >> ECP_ATADDRSHFT;
+ outb(memconf, (brdp->iobase + ECP_ATMEMAR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpenable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpenable(brdp=%x)\n", (int) brdp);
+#endif
+ outb(ECP_ATENABLE, (brdp->iobase + ECP_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpdisable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpdisable(brdp=%x)\n", (int) brdp);
+#endif
+ outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+ (int) offset);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % ECP_ATPAGESIZE);
+ val = (unsigned char) (offset / ECP_ATPAGESIZE);
+ }
+ outb(val, (brdp->iobase + ECP_ATMEMPR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpreset(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpreset(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(ECP_ATSTOP, (brdp->iobase + ECP_ATCONFR));
+ udelay(10);
+ outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+ udelay(500);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpintr(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpintr(brdp=%x)\n", (int) brdp);
+#endif
+ outb(0x1, brdp->iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following set of functions act on ECP EISA boards.
+ */
+
+static void stli_ecpeiinit(stlibrd_t *brdp)
+{
+ unsigned long memconf;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpeiinit(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(0x1, (brdp->iobase + ECP_EIBRDENAB));
+ outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+ udelay(10);
+ outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+ udelay(500);
+
+ memconf = (brdp->memaddr & ECP_EIADDRMASKL) >> ECP_EIADDRSHFTL;
+ outb(memconf, (brdp->iobase + ECP_EIMEMARL));
+ memconf = (brdp->memaddr & ECP_EIADDRMASKH) >> ECP_EIADDRSHFTH;
+ outb(memconf, (brdp->iobase + ECP_EIMEMARH));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeienable(stlibrd_t *brdp)
+{
+ outb(ECP_EIENABLE, (brdp->iobase + ECP_EICONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeidisable(stlibrd_t *brdp)
+{
+ outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpeigetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecpeigetmemptr(brdp=%x,offset=%x,line=%d)\n",
+ (int) brdp, (int) offset, line);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % ECP_EIPAGESIZE);
+ if (offset < ECP_EIPAGESIZE)
+ val = ECP_EIENABLE;
+ else
+ val = ECP_EIENABLE | 0x40;
+ }
+ outb(val, (brdp->iobase + ECP_EICONFR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeireset(stlibrd_t *brdp)
+{
+ outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+ udelay(10);
+ outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+ udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following set of functions act on ECP MCA boards.
+ */
+
+static void stli_ecpmcenable(stlibrd_t *brdp)
+{
+ outb(ECP_MCENABLE, (brdp->iobase + ECP_MCCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpmcdisable(stlibrd_t *brdp)
+{
+ outb(ECP_MCDISABLE, (brdp->iobase + ECP_MCCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpmcgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % ECP_MCPAGESIZE);
+ val = ((unsigned char) (offset / ECP_MCPAGESIZE)) | ECP_MCENABLE;
+ }
+ outb(val, (brdp->iobase + ECP_MCCONFR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpmcreset(stlibrd_t *brdp)
+{
+ outb(ECP_MCSTOP, (brdp->iobase + ECP_MCCONFR));
+ udelay(10);
+ outb(ECP_MCDISABLE, (brdp->iobase + ECP_MCCONFR));
+ udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following set of functions act on ECP PCI boards.
+ */
+
+static void stli_ecppciinit(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecppciinit(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(ECP_PCISTOP, (brdp->iobase + ECP_PCICONFR));
+ udelay(10);
+ outb(0, (brdp->iobase + ECP_PCICONFR));
+ udelay(500);
+}
+
+/*****************************************************************************/
+
+static char *stli_ecppcigetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_ecppcigetmemptr(brdp=%x,offset=%x,line=%d)\n",
+ (int) brdp, (int) offset, line);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), board=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % ECP_PCIPAGESIZE);
+ val = (offset / ECP_PCIPAGESIZE) << 1;
+ }
+ outb(val, (brdp->iobase + ECP_PCICONFR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecppcireset(stlibrd_t *brdp)
+{
+ outb(ECP_PCISTOP, (brdp->iobase + ECP_PCICONFR));
+ udelay(10);
+ outb(0, (brdp->iobase + ECP_PCICONFR));
+ udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following routines act on ONboards.
+ */
+
+static void stli_onbinit(stlibrd_t *brdp)
+{
+ unsigned long memconf;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbinit(brdp=%d)\n", (int) brdp);
+#endif
+
+ outb(ONB_ATSTOP, (brdp->iobase + ONB_ATCONFR));
+ udelay(10);
+ outb(ONB_ATDISABLE, (brdp->iobase + ONB_ATCONFR));
+ mdelay(1000);
+
+ memconf = (brdp->memaddr & ONB_ATADDRMASK) >> ONB_ATADDRSHFT;
+ outb(memconf, (brdp->iobase + ONB_ATMEMAR));
+ outb(0x1, brdp->iobase);
+ mdelay(1);
+}
+
+/*****************************************************************************/
+
+static void stli_onbenable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbenable(brdp=%x)\n", (int) brdp);
+#endif
+ outb((brdp->enabval | ONB_ATENABLE), (brdp->iobase + ONB_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_onbdisable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbdisable(brdp=%x)\n", (int) brdp);
+#endif
+ outb((brdp->enabval | ONB_ATDISABLE), (brdp->iobase + ONB_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_onbgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+ (int) offset);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ } else {
+ ptr = brdp->membase + (offset % ONB_ATPAGESIZE);
+ }
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_onbreset(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbreset(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(ONB_ATSTOP, (brdp->iobase + ONB_ATCONFR));
+ udelay(10);
+ outb(ONB_ATDISABLE, (brdp->iobase + ONB_ATCONFR));
+ mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following routines act on ONboard EISA.
+ */
+
+static void stli_onbeinit(stlibrd_t *brdp)
+{
+ unsigned long memconf;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbeinit(brdp=%d)\n", (int) brdp);
+#endif
+
+ outb(0x1, (brdp->iobase + ONB_EIBRDENAB));
+ outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+ udelay(10);
+ outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+ mdelay(1000);
+
+ memconf = (brdp->memaddr & ONB_EIADDRMASKL) >> ONB_EIADDRSHFTL;
+ outb(memconf, (brdp->iobase + ONB_EIMEMARL));
+ memconf = (brdp->memaddr & ONB_EIADDRMASKH) >> ONB_EIADDRSHFTH;
+ outb(memconf, (brdp->iobase + ONB_EIMEMARH));
+ outb(0x1, brdp->iobase);
+ mdelay(1);
+}
+
+/*****************************************************************************/
+
+static void stli_onbeenable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbeenable(brdp=%x)\n", (int) brdp);
+#endif
+ outb(ONB_EIENABLE, (brdp->iobase + ONB_EICONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_onbedisable(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbedisable(brdp=%x)\n", (int) brdp);
+#endif
+ outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_onbegetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_onbegetmemptr(brdp=%x,offset=%x,line=%d)\n",
+ (int) brdp, (int) offset, line);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % ONB_EIPAGESIZE);
+ if (offset < ONB_EIPAGESIZE)
+ val = ONB_EIENABLE;
+ else
+ val = ONB_EIENABLE | 0x40;
+ }
+ outb(val, (brdp->iobase + ONB_EICONFR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_onbereset(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+ printk(KERN_ERR "stli_onbereset(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+ udelay(10);
+ outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+ mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following routines act on Brumby boards.
+ */
+
+static void stli_bbyinit(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+ printk(KERN_ERR "stli_bbyinit(brdp=%d)\n", (int) brdp);
+#endif
+
+ outb(BBY_ATSTOP, (brdp->iobase + BBY_ATCONFR));
+ udelay(10);
+ outb(0, (brdp->iobase + BBY_ATCONFR));
+ mdelay(1000);
+ outb(0x1, brdp->iobase);
+ mdelay(1);
+}
+
+/*****************************************************************************/
+
+static char *stli_bbygetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+ unsigned char val;
+
+#ifdef DEBUG
+ printk(KERN_ERR "stli_bbygetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+ (int) offset);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ val = 0;
+ } else {
+ ptr = brdp->membase + (offset % BBY_PAGESIZE);
+ val = (unsigned char) (offset / BBY_PAGESIZE);
+ }
+ outb(val, (brdp->iobase + BBY_ATCONFR));
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_bbyreset(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_bbyreset(brdp=%x)\n", (int) brdp);
+#endif
+
+ outb(BBY_ATSTOP, (brdp->iobase + BBY_ATCONFR));
+ udelay(10);
+ outb(0, (brdp->iobase + BBY_ATCONFR));
+ mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ * The following routines act on original old Stallion boards.
+ */
+
+static void stli_stalinit(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_stalinit(brdp=%d)\n", (int) brdp);
+#endif
+
+ outb(0x1, brdp->iobase);
+ mdelay(1000);
+}
+
+/*****************************************************************************/
+
+static char *stli_stalgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{
+ void *ptr;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_stalgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+ (int) offset);
+#endif
+
+ if (offset > brdp->memsize) {
+ printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+ "range at line=%d(%d), brd=%d\n",
+ (int) offset, line, __LINE__, brdp->brdnr);
+ ptr = NULL;
+ } else {
+ ptr = brdp->membase + (offset % STAL_PAGESIZE);
+ }
+ return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_stalreset(stlibrd_t *brdp)
+{
+ volatile unsigned long *vecp;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_stalreset(brdp=%x)\n", (int) brdp);
+#endif
+
+ vecp = (volatile unsigned long *) (brdp->membase + 0x30);
+ *vecp = 0xffff0000;
+ outb(0, brdp->iobase);
+ mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find an ECP board and initialize it. This handles only ECP
+ * board types.
+ */
+
+static int stli_initecp(stlibrd_t *brdp)
+{
+ cdkecpsig_t sig;
+ cdkecpsig_t *sigsp;
+ unsigned int status, nxtid;
+ char *name;
+ int panelnr, nrports;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_initecp(brdp=%x)\n", (int) brdp);
+#endif
+
+ if (!request_region(brdp->iobase, brdp->iosize, "istallion"))
+ return -EIO;
+
+ if ((brdp->iobase == 0) || (brdp->memaddr == 0))
+ {
+ release_region(brdp->iobase, brdp->iosize);
+ return(-ENODEV);
+ }
+
+ brdp->iosize = ECP_IOSIZE;
+
+/*
+ * Based on the specific board type setup the common vars to access
+ * and enable shared memory. Set all board specific information now
+ * as well.
+ */
+ switch (brdp->brdtype) {
+ case BRD_ECP:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ECP_MEMSIZE;
+ brdp->pagesize = ECP_ATPAGESIZE;
+ brdp->init = stli_ecpinit;
+ brdp->enable = stli_ecpenable;
+ brdp->reenable = stli_ecpenable;
+ brdp->disable = stli_ecpdisable;
+ brdp->getmemptr = stli_ecpgetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_ecpreset;
+ name = "serial(EC8/64)";
+ break;
+
+ case BRD_ECPE:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ECP_MEMSIZE;
+ brdp->pagesize = ECP_EIPAGESIZE;
+ brdp->init = stli_ecpeiinit;
+ brdp->enable = stli_ecpeienable;
+ brdp->reenable = stli_ecpeienable;
+ brdp->disable = stli_ecpeidisable;
+ brdp->getmemptr = stli_ecpeigetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_ecpeireset;
+ name = "serial(EC8/64-EI)";
+ break;
+
+ case BRD_ECPMC:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ECP_MEMSIZE;
+ brdp->pagesize = ECP_MCPAGESIZE;
+ brdp->init = NULL;
+ brdp->enable = stli_ecpmcenable;
+ brdp->reenable = stli_ecpmcenable;
+ brdp->disable = stli_ecpmcdisable;
+ brdp->getmemptr = stli_ecpmcgetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_ecpmcreset;
+ name = "serial(EC8/64-MCA)";
+ break;
+
+ case BRD_ECPPCI:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ECP_PCIMEMSIZE;
+ brdp->pagesize = ECP_PCIPAGESIZE;
+ brdp->init = stli_ecppciinit;
+ brdp->enable = NULL;
+ brdp->reenable = NULL;
+ brdp->disable = NULL;
+ brdp->getmemptr = stli_ecppcigetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_ecppcireset;
+ name = "serial(EC/RA-PCI)";
+ break;
+
+ default:
+ release_region(brdp->iobase, brdp->iosize);
+ return(-EINVAL);
+ }
+
+/*
+ * The per-board operations structure is all set up, so now let's go
+ * and get the board operational. Firstly initialize board configuration
+ * registers. Set the memory mapping info so we can get at the boards
+ * shared memory.
+ */
+ EBRDINIT(brdp);
+
+ brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+ if (brdp->membase == (void *) NULL)
+ {
+ release_region(brdp->iobase, brdp->iosize);
+ return(-ENOMEM);
+ }
+
+/*
+ * Now that all specific code is set up, enable the shared memory and
+ * look for the a signature area that will tell us exactly what board
+ * this is, and what it is connected to it.
+ */
+ EBRDENABLE(brdp);
+ sigsp = (cdkecpsig_t *) EBRDGETMEMPTR(brdp, CDK_SIGADDR);
+ memcpy(&sig, sigsp, sizeof(cdkecpsig_t));
+ EBRDDISABLE(brdp);
+
+#if 0
+ printk("%s(%d): sig-> magic=%x rom=%x panel=%x,%x,%x,%x,%x,%x,%x,%x\n",
+ __FILE__, __LINE__, (int) sig.magic, sig.romver, sig.panelid[0],
+ (int) sig.panelid[1], (int) sig.panelid[2],
+ (int) sig.panelid[3], (int) sig.panelid[4],
+ (int) sig.panelid[5], (int) sig.panelid[6],
+ (int) sig.panelid[7]);
+#endif
+
+ if (sig.magic != ECP_MAGIC)
+ {
+ release_region(brdp->iobase, brdp->iosize);
+ return(-ENODEV);
+ }
+
+/*
+ * Scan through the signature looking at the panels connected to the
+ * board. Calculate the total number of ports as we go.
+ */
+ for (panelnr = 0, nxtid = 0; (panelnr < STL_MAXPANELS); panelnr++) {
+ status = sig.panelid[nxtid];
+ if ((status & ECH_PNLIDMASK) != nxtid)
+ break;
+
+ brdp->panelids[panelnr] = status;
+ nrports = (status & ECH_PNL16PORT) ? 16 : 8;
+ if ((nrports == 16) && ((status & ECH_PNLXPID) == 0))
+ nxtid++;
+ brdp->panels[panelnr] = nrports;
+ brdp->nrports += nrports;
+ nxtid++;
+ brdp->nrpanels++;
+ }
+
+
+ brdp->state |= BST_FOUND;
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find an ONboard, Brumby or Stallion board and initialize it.
+ * This handles only these board types.
+ */
+
+static int stli_initonb(stlibrd_t *brdp)
+{
+ cdkonbsig_t sig;
+ cdkonbsig_t *sigsp;
+ char *name;
+ int i;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_initonb(brdp=%x)\n", (int) brdp);
+#endif
+
+/*
+ * Do a basic sanity check on the IO and memory addresses.
+ */
+ if ((brdp->iobase == 0) || (brdp->memaddr == 0))
+ return(-ENODEV);
+
+ brdp->iosize = ONB_IOSIZE;
+
+ if (!request_region(brdp->iobase, brdp->iosize, "istallion"))
+ return -EIO;
+
+/*
+ * Based on the specific board type setup the common vars to access
+ * and enable shared memory. Set all board specific information now
+ * as well.
+ */
+ switch (brdp->brdtype) {
+ case BRD_ONBOARD:
+ case BRD_ONBOARD32:
+ case BRD_ONBOARD2:
+ case BRD_ONBOARD2_32:
+ case BRD_ONBOARDRS:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ONB_MEMSIZE;
+ brdp->pagesize = ONB_ATPAGESIZE;
+ brdp->init = stli_onbinit;
+ brdp->enable = stli_onbenable;
+ brdp->reenable = stli_onbenable;
+ brdp->disable = stli_onbdisable;
+ brdp->getmemptr = stli_onbgetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_onbreset;
+ if (brdp->memaddr > 0x100000)
+ brdp->enabval = ONB_MEMENABHI;
+ else
+ brdp->enabval = ONB_MEMENABLO;
+ name = "serial(ONBoard)";
+ break;
+
+ case BRD_ONBOARDE:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = ONB_EIMEMSIZE;
+ brdp->pagesize = ONB_EIPAGESIZE;
+ brdp->init = stli_onbeinit;
+ brdp->enable = stli_onbeenable;
+ brdp->reenable = stli_onbeenable;
+ brdp->disable = stli_onbedisable;
+ brdp->getmemptr = stli_onbegetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_onbereset;
+ name = "serial(ONBoard/E)";
+ break;
+
+ case BRD_BRUMBY4:
+ case BRD_BRUMBY8:
+ case BRD_BRUMBY16:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = BBY_MEMSIZE;
+ brdp->pagesize = BBY_PAGESIZE;
+ brdp->init = stli_bbyinit;
+ brdp->enable = NULL;
+ brdp->reenable = NULL;
+ brdp->disable = NULL;
+ brdp->getmemptr = stli_bbygetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_bbyreset;
+ name = "serial(Brumby)";
+ break;
+
+ case BRD_STALLION:
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->memsize = STAL_MEMSIZE;
+ brdp->pagesize = STAL_PAGESIZE;
+ brdp->init = stli_stalinit;
+ brdp->enable = NULL;
+ brdp->reenable = NULL;
+ brdp->disable = NULL;
+ brdp->getmemptr = stli_stalgetmemptr;
+ brdp->intr = stli_ecpintr;
+ brdp->reset = stli_stalreset;
+ name = "serial(Stallion)";
+ break;
+
+ default:
+ release_region(brdp->iobase, brdp->iosize);
+ return(-EINVAL);
+ }
+
+/*
+ * The per-board operations structure is all set up, so now let's go
+ * and get the board operational. Firstly initialize board configuration
+ * registers. Set the memory mapping info so we can get at the boards
+ * shared memory.
+ */
+ EBRDINIT(brdp);
+
+ brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+ if (brdp->membase == (void *) NULL)
+ {
+ release_region(brdp->iobase, brdp->iosize);
+ return(-ENOMEM);
+ }
+
+/*
+ * Now that all specific code is set up, enable the shared memory and
+ * look for the a signature area that will tell us exactly what board
+ * this is, and how many ports.
+ */
+ EBRDENABLE(brdp);
+ sigsp = (cdkonbsig_t *) EBRDGETMEMPTR(brdp, CDK_SIGADDR);
+ memcpy(&sig, sigsp, sizeof(cdkonbsig_t));
+ EBRDDISABLE(brdp);
+
+#if 0
+ printk("%s(%d): sig-> magic=%x:%x:%x:%x romver=%x amask=%x:%x:%x\n",
+ __FILE__, __LINE__, sig.magic0, sig.magic1, sig.magic2,
+ sig.magic3, sig.romver, sig.amask0, sig.amask1, sig.amask2);
+#endif
+
+ if ((sig.magic0 != ONB_MAGIC0) || (sig.magic1 != ONB_MAGIC1) ||
+ (sig.magic2 != ONB_MAGIC2) || (sig.magic3 != ONB_MAGIC3))
+ {
+ release_region(brdp->iobase, brdp->iosize);
+ return(-ENODEV);
+ }
+
+/*
+ * Scan through the signature alive mask and calculate how many ports
+ * there are on this board.
+ */
+ brdp->nrpanels = 1;
+ if (sig.amask1) {
+ brdp->nrports = 32;
+ } else {
+ for (i = 0; (i < 16); i++) {
+ if (((sig.amask0 << i) & 0x8000) == 0)
+ break;
+ }
+ brdp->nrports = i;
+ }
+ brdp->panels[0] = brdp->nrports;
+
+
+ brdp->state |= BST_FOUND;
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start up a running board. This routine is only called after the
+ * code has been down loaded to the board and is operational. It will
+ * read in the memory map, and get the show on the road...
+ */
+
+static int stli_startbrd(stlibrd_t *brdp)
+{
+ volatile cdkhdr_t *hdrp;
+ volatile cdkmem_t *memp;
+ volatile cdkasy_t *ap;
+ unsigned long flags;
+ stliport_t *portp;
+ int portnr, nrdevs, i, rc;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_startbrd(brdp=%x)\n", (int) brdp);
+#endif
+
+ rc = 0;
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+ nrdevs = hdrp->nrdevs;
+
+#if 0
+ printk("%s(%d): CDK version %d.%d.%d --> "
+ "nrdevs=%d memp=%x hostp=%x slavep=%x\n",
+ __FILE__, __LINE__, hdrp->ver_release, hdrp->ver_modification,
+ hdrp->ver_fix, nrdevs, (int) hdrp->memp, (int) hdrp->hostp,
+ (int) hdrp->slavep);
+#endif
+
+ if (nrdevs < (brdp->nrports + 1)) {
+ printk(KERN_ERR "STALLION: slave failed to allocate memory for "
+ "all devices, devices=%d\n", nrdevs);
+ brdp->nrports = nrdevs - 1;
+ }
+ brdp->nrdevs = nrdevs;
+ brdp->hostoffset = hdrp->hostp - CDK_CDKADDR;
+ brdp->slaveoffset = hdrp->slavep - CDK_CDKADDR;
+ brdp->bitsize = (nrdevs + 7) / 8;
+ memp = (volatile cdkmem_t *) hdrp->memp;
+ if (((unsigned long) memp) > brdp->memsize) {
+ printk(KERN_ERR "STALLION: corrupted shared memory region?\n");
+ rc = -EIO;
+ goto stli_donestartup;
+ }
+ memp = (volatile cdkmem_t *) EBRDGETMEMPTR(brdp, (unsigned long) memp);
+ if (memp->dtype != TYP_ASYNCTRL) {
+ printk(KERN_ERR "STALLION: no slave control device found\n");
+ goto stli_donestartup;
+ }
+ memp++;
+
+/*
+ * Cycle through memory allocation of each port. We are guaranteed to
+ * have all ports inside the first page of slave window, so no need to
+ * change pages while reading memory map.
+ */
+ for (i = 1, portnr = 0; (i < nrdevs); i++, portnr++, memp++) {
+ if (memp->dtype != TYP_ASYNC)
+ break;
+ portp = brdp->ports[portnr];
+ if (portp == (stliport_t *) NULL)
+ break;
+ portp->devnr = i;
+ portp->addr = memp->offset;
+ portp->reqbit = (unsigned char) (0x1 << (i * 8 / nrdevs));
+ portp->portidx = (unsigned char) (i / 8);
+ portp->portbit = (unsigned char) (0x1 << (i % 8));
+ }
+
+ hdrp->slavereq = 0xff;
+
+/*
+ * For each port setup a local copy of the RX and TX buffer offsets
+ * and sizes. We do this separate from the above, because we need to
+ * move the shared memory page...
+ */
+ for (i = 1, portnr = 0; (i < nrdevs); i++, portnr++) {
+ portp = brdp->ports[portnr];
+ if (portp == (stliport_t *) NULL)
+ break;
+ if (portp->addr == 0)
+ break;
+ ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+ if (ap != (volatile cdkasy_t *) NULL) {
+ portp->rxsize = ap->rxq.size;
+ portp->txsize = ap->txq.size;
+ portp->rxoffset = ap->rxq.offset;
+ portp->txoffset = ap->txq.offset;
+ }
+ }
+
+stli_donestartup:
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+
+ if (rc == 0)
+ brdp->state |= BST_STARTED;
+
+ if (! stli_timeron) {
+ stli_timeron++;
+ stli_timerlist.expires = STLI_TIMEOUT;
+ add_timer(&stli_timerlist);
+ }
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Probe and initialize the specified board.
+ */
+
+static int __init stli_brdinit(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_brdinit(brdp=%x)\n", (int) brdp);
+#endif
+
+ stli_brds[brdp->brdnr] = brdp;
+
+ switch (brdp->brdtype) {
+ case BRD_ECP:
+ case BRD_ECPE:
+ case BRD_ECPMC:
+ case BRD_ECPPCI:
+ stli_initecp(brdp);
+ break;
+ case BRD_ONBOARD:
+ case BRD_ONBOARDE:
+ case BRD_ONBOARD2:
+ case BRD_ONBOARD32:
+ case BRD_ONBOARD2_32:
+ case BRD_ONBOARDRS:
+ case BRD_BRUMBY4:
+ case BRD_BRUMBY8:
+ case BRD_BRUMBY16:
+ case BRD_STALLION:
+ stli_initonb(brdp);
+ break;
+ case BRD_EASYIO:
+ case BRD_ECH:
+ case BRD_ECHMC:
+ case BRD_ECHPCI:
+ printk(KERN_ERR "STALLION: %s board type not supported in "
+ "this driver\n", stli_brdnames[brdp->brdtype]);
+ return(ENODEV);
+ default:
+ printk(KERN_ERR "STALLION: board=%d is unknown board "
+ "type=%d\n", brdp->brdnr, brdp->brdtype);
+ return(ENODEV);
+ }
+
+ if ((brdp->state & BST_FOUND) == 0) {
+ printk(KERN_ERR "STALLION: %s board not found, board=%d "
+ "io=%x mem=%x\n",
+ stli_brdnames[brdp->brdtype], brdp->brdnr,
+ brdp->iobase, (int) brdp->memaddr);
+ return(ENODEV);
+ }
+
+ stli_initports(brdp);
+ printk(KERN_INFO "STALLION: %s found, board=%d io=%x mem=%x "
+ "nrpanels=%d nrports=%d\n", stli_brdnames[brdp->brdtype],
+ brdp->brdnr, brdp->iobase, (int) brdp->memaddr,
+ brdp->nrpanels, brdp->nrports);
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Probe around trying to find where the EISA boards shared memory
+ * might be. This is a bit if hack, but it is the best we can do.
+ */
+
+static int stli_eisamemprobe(stlibrd_t *brdp)
+{
+ cdkecpsig_t ecpsig, *ecpsigp;
+ cdkonbsig_t onbsig, *onbsigp;
+ int i, foundit;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_eisamemprobe(brdp=%x)\n", (int) brdp);
+#endif
+
+/*
+ * First up we reset the board, to get it into a known state. There
+ * is only 2 board types here we need to worry about. Don;t use the
+ * standard board init routine here, it programs up the shared
+ * memory address, and we don't know it yet...
+ */
+ if (brdp->brdtype == BRD_ECPE) {
+ outb(0x1, (brdp->iobase + ECP_EIBRDENAB));
+ outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+ udelay(10);
+ outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+ udelay(500);
+ stli_ecpeienable(brdp);
+ } else if (brdp->brdtype == BRD_ONBOARDE) {
+ outb(0x1, (brdp->iobase + ONB_EIBRDENAB));
+ outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+ udelay(10);
+ outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+ mdelay(100);
+ outb(0x1, brdp->iobase);
+ mdelay(1);
+ stli_onbeenable(brdp);
+ } else {
+ return(-ENODEV);
+ }
+
+ foundit = 0;
+ brdp->memsize = ECP_MEMSIZE;
+
+/*
+ * Board shared memory is enabled, so now we have a poke around and
+ * see if we can find it.
+ */
+ for (i = 0; (i < stli_eisamempsize); i++) {
+ brdp->memaddr = stli_eisamemprobeaddrs[i];
+ brdp->membase = (void *) brdp->memaddr;
+ brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+ if (brdp->membase == (void *) NULL)
+ continue;
+
+ if (brdp->brdtype == BRD_ECPE) {
+ ecpsigp = (cdkecpsig_t *) stli_ecpeigetmemptr(brdp,
+ CDK_SIGADDR, __LINE__);
+ memcpy(&ecpsig, ecpsigp, sizeof(cdkecpsig_t));
+ if (ecpsig.magic == ECP_MAGIC)
+ foundit = 1;
+ } else {
+ onbsigp = (cdkonbsig_t *) stli_onbegetmemptr(brdp,
+ CDK_SIGADDR, __LINE__);
+ memcpy(&onbsig, onbsigp, sizeof(cdkonbsig_t));
+ if ((onbsig.magic0 == ONB_MAGIC0) &&
+ (onbsig.magic1 == ONB_MAGIC1) &&
+ (onbsig.magic2 == ONB_MAGIC2) &&
+ (onbsig.magic3 == ONB_MAGIC3))
+ foundit = 1;
+ }
+
+ iounmap(brdp->membase);
+ if (foundit)
+ break;
+ }
+
+/*
+ * Regardless of whether we found the shared memory or not we must
+ * disable the region. After that return success or failure.
+ */
+ if (brdp->brdtype == BRD_ECPE)
+ stli_ecpeidisable(brdp);
+ else
+ stli_onbedisable(brdp);
+
+ if (! foundit) {
+ brdp->memaddr = 0;
+ brdp->membase = NULL;
+ printk(KERN_ERR "STALLION: failed to probe shared memory "
+ "region for %s in EISA slot=%d\n",
+ stli_brdnames[brdp->brdtype], (brdp->iobase >> 12));
+ return(-ENODEV);
+ }
+ return(0);
+}
+
+static int stli_getbrdnr(void)
+{
+ int i;
+
+ for (i = 0; i < STL_MAXBRDS; i++) {
+ if (!stli_brds[i]) {
+ if (i >= stli_nrbrds)
+ stli_nrbrds = i + 1;
+ return i;
+ }
+ }
+ return -1;
+}
+
+/*****************************************************************************/
+
+/*
+ * Probe around and try to find any EISA boards in system. The biggest
+ * problem here is finding out what memory address is associated with
+ * an EISA board after it is found. The registers of the ECPE and
+ * ONboardE are not readable - so we can't read them from there. We
+ * don't have access to the EISA CMOS (or EISA BIOS) so we don't
+ * actually have any way to find out the real value. The best we can
+ * do is go probing around in the usual places hoping we can find it.
+ */
+
+static int stli_findeisabrds(void)
+{
+ stlibrd_t *brdp;
+ unsigned int iobase, eid;
+ int i;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_findeisabrds()\n");
+#endif
+
+/*
+ * Firstly check if this is an EISA system. Do this by probing for
+ * the system board EISA ID. If this is not an EISA system then
+ * don't bother going any further!
+ */
+ outb(0xff, 0xc80);
+ if (inb(0xc80) == 0xff)
+ return(0);
+
+/*
+ * Looks like an EISA system, so go searching for EISA boards.
+ */
+ for (iobase = 0x1000; (iobase <= 0xc000); iobase += 0x1000) {
+ outb(0xff, (iobase + 0xc80));
+ eid = inb(iobase + 0xc80);
+ eid |= inb(iobase + 0xc81) << 8;
+ if (eid != STL_EISAID)
+ continue;
+
+/*
+ * We have found a board. Need to check if this board was
+ * statically configured already (just in case!).
+ */
+ for (i = 0; (i < STL_MAXBRDS); i++) {
+ brdp = stli_brds[i];
+ if (brdp == (stlibrd_t *) NULL)
+ continue;
+ if (brdp->iobase == iobase)
+ break;
+ }
+ if (i < STL_MAXBRDS)
+ continue;
+
+/*
+ * We have found a Stallion board and it is not configured already.
+ * Allocate a board structure and initialize it.
+ */
+ if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+ return(-ENOMEM);
+ if ((brdp->brdnr = stli_getbrdnr()) < 0)
+ return(-ENOMEM);
+ eid = inb(iobase + 0xc82);
+ if (eid == ECP_EISAID)
+ brdp->brdtype = BRD_ECPE;
+ else if (eid == ONB_EISAID)
+ brdp->brdtype = BRD_ONBOARDE;
+ else
+ brdp->brdtype = BRD_UNKNOWN;
+ brdp->iobase = iobase;
+ outb(0x1, (iobase + 0xc84));
+ if (stli_eisamemprobe(brdp))
+ outb(0, (iobase + 0xc84));
+ stli_brdinit(brdp);
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find the next available board number that is free.
+ */
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ * We have a Stallion board. Allocate a board structure and
+ * initialize it. Read its IO and MEMORY resources from PCI
+ * configuration space.
+ */
+
+static int stli_initpcibrd(int brdtype, struct pci_dev *devp)
+{
+ stlibrd_t *brdp;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n",
+ brdtype, dev->bus->number, dev->devfn);
+#endif
+
+ if (pci_enable_device(devp))
+ return(-EIO);
+ if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+ return(-ENOMEM);
+ if ((brdp->brdnr = stli_getbrdnr()) < 0) {
+ printk(KERN_INFO "STALLION: too many boards found, "
+ "maximum supported %d\n", STL_MAXBRDS);
+ return(0);
+ }
+ brdp->brdtype = brdtype;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "%s(%d): BAR[]=%lx,%lx,%lx,%lx\n", __FILE__, __LINE__,
+ pci_resource_start(devp, 0),
+ pci_resource_start(devp, 1),
+ pci_resource_start(devp, 2),
+ pci_resource_start(devp, 3));
+#endif
+
+/*
+ * We have all resources from the board, so lets setup the actual
+ * board structure now.
+ */
+ brdp->iobase = pci_resource_start(devp, 3);
+ brdp->memaddr = pci_resource_start(devp, 2);
+ stli_brdinit(brdp);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find all Stallion PCI boards that might be installed. Initialize each
+ * one as it is found.
+ */
+
+static int stli_findpcibrds(void)
+{
+ struct pci_dev *dev = NULL;
+ int rc;
+
+#ifdef DEBUG
+ printk("stli_findpcibrds()\n");
+#endif
+
+ while ((dev = pci_find_device(PCI_VENDOR_ID_STALLION,
+ PCI_DEVICE_ID_ECRA, dev))) {
+ if ((rc = stli_initpcibrd(BRD_ECPPCI, dev)))
+ return(rc);
+ }
+
+ return(0);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Allocate a new board structure. Fill out the basic info in it.
+ */
+
+static stlibrd_t *stli_allocbrd(void)
+{
+ stlibrd_t *brdp;
+
+ brdp = (stlibrd_t *) stli_memalloc(sizeof(stlibrd_t));
+ if (brdp == (stlibrd_t *) NULL) {
+ printk(KERN_ERR "STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlibrd_t));
+ return((stlibrd_t *) NULL);
+ }
+
+ memset(brdp, 0, sizeof(stlibrd_t));
+ brdp->magic = STLI_BOARDMAGIC;
+ return(brdp);
+}
+
+/*****************************************************************************/
+
+/*
+ * Scan through all the boards in the configuration and see what we
+ * can find.
+ */
+
+static int stli_initbrds(void)
+{
+ stlibrd_t *brdp, *nxtbrdp;
+ stlconf_t *confp;
+ int i, j;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_initbrds()\n");
+#endif
+
+ if (stli_nrbrds > STL_MAXBRDS) {
+ printk(KERN_INFO "STALLION: too many boards in configuration "
+ "table, truncating to %d\n", STL_MAXBRDS);
+ stli_nrbrds = STL_MAXBRDS;
+ }
+
+/*
+ * Firstly scan the list of static boards configured. Allocate
+ * resources and initialize the boards as found. If this is a
+ * module then let the module args override static configuration.
+ */
+ for (i = 0; (i < stli_nrbrds); i++) {
+ confp = &stli_brdconf[i];
+#ifdef MODULE
+ stli_parsebrd(confp, stli_brdsp[i]);
+#endif
+ if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+ return(-ENOMEM);
+ brdp->brdnr = i;
+ brdp->brdtype = confp->brdtype;
+ brdp->iobase = confp->ioaddr1;
+ brdp->memaddr = confp->memaddr;
+ stli_brdinit(brdp);
+ }
+
+/*
+ * Static configuration table done, so now use dynamic methods to
+ * see if any more boards should be configured.
+ */
+#ifdef MODULE
+ stli_argbrds();
+#endif
+ if (stli_eisaprobe)
+ stli_findeisabrds();
+#ifdef CONFIG_PCI
+ stli_findpcibrds();
+#endif
+
+/*
+ * All found boards are initialized. Now for a little optimization, if
+ * no boards are sharing the "shared memory" regions then we can just
+ * leave them all enabled. This is in fact the usual case.
+ */
+ stli_shared = 0;
+ if (stli_nrbrds > 1) {
+ for (i = 0; (i < stli_nrbrds); i++) {
+ brdp = stli_brds[i];
+ if (brdp == (stlibrd_t *) NULL)
+ continue;
+ for (j = i + 1; (j < stli_nrbrds); j++) {
+ nxtbrdp = stli_brds[j];
+ if (nxtbrdp == (stlibrd_t *) NULL)
+ continue;
+ if ((brdp->membase >= nxtbrdp->membase) &&
+ (brdp->membase <= (nxtbrdp->membase +
+ nxtbrdp->memsize - 1))) {
+ stli_shared++;
+ break;
+ }
+ }
+ }
+ }
+
+ if (stli_shared == 0) {
+ for (i = 0; (i < stli_nrbrds); i++) {
+ brdp = stli_brds[i];
+ if (brdp == (stlibrd_t *) NULL)
+ continue;
+ if (brdp->state & BST_FOUND) {
+ EBRDENABLE(brdp);
+ brdp->enable = NULL;
+ brdp->disable = NULL;
+ }
+ }
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Code to handle an "staliomem" read operation. This device is the
+ * contents of the board shared memory. It is used for down loading
+ * the slave image (and debugging :-)
+ */
+
+static ssize_t stli_memread(struct file *fp, char __user *buf, size_t count, loff_t *offp)
+{
+ unsigned long flags;
+ void *memptr;
+ stlibrd_t *brdp;
+ int brdnr, size, n;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_memread(fp=%x,buf=%x,count=%x,offp=%x)\n",
+ (int) fp, (int) buf, count, (int) offp);
+#endif
+
+ brdnr = iminor(fp->f_dentry->d_inode);
+ if (brdnr >= stli_nrbrds)
+ return(-ENODEV);
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+ if (brdp->state == 0)
+ return(-ENODEV);
+ if (fp->f_pos >= brdp->memsize)
+ return(0);
+
+ size = MIN(count, (brdp->memsize - fp->f_pos));
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ while (size > 0) {
+ memptr = (void *) EBRDGETMEMPTR(brdp, fp->f_pos);
+ n = MIN(size, (brdp->pagesize - (((unsigned long) fp->f_pos) % brdp->pagesize)));
+ if (copy_to_user(buf, memptr, n)) {
+ count = -EFAULT;
+ goto out;
+ }
+ fp->f_pos += n;
+ buf += n;
+ size -= n;
+ }
+out:
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+
+ return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ * Code to handle an "staliomem" write operation. This device is the
+ * contents of the board shared memory. It is used for down loading
+ * the slave image (and debugging :-)
+ */
+
+static ssize_t stli_memwrite(struct file *fp, const char __user *buf, size_t count, loff_t *offp)
+{
+ unsigned long flags;
+ void *memptr;
+ stlibrd_t *brdp;
+ char __user *chbuf;
+ int brdnr, size, n;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_memwrite(fp=%x,buf=%x,count=%x,offp=%x)\n",
+ (int) fp, (int) buf, count, (int) offp);
+#endif
+
+ brdnr = iminor(fp->f_dentry->d_inode);
+ if (brdnr >= stli_nrbrds)
+ return(-ENODEV);
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+ if (brdp->state == 0)
+ return(-ENODEV);
+ if (fp->f_pos >= brdp->memsize)
+ return(0);
+
+ chbuf = (char __user *) buf;
+ size = MIN(count, (brdp->memsize - fp->f_pos));
+
+ save_flags(flags);
+ cli();
+ EBRDENABLE(brdp);
+ while (size > 0) {
+ memptr = (void *) EBRDGETMEMPTR(brdp, fp->f_pos);
+ n = MIN(size, (brdp->pagesize - (((unsigned long) fp->f_pos) % brdp->pagesize)));
+ if (copy_from_user(memptr, chbuf, n)) {
+ count = -EFAULT;
+ goto out;
+ }
+ fp->f_pos += n;
+ chbuf += n;
+ size -= n;
+ }
+out:
+ EBRDDISABLE(brdp);
+ restore_flags(flags);
+
+ return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the board stats structure to user app.
+ */
+
+static int stli_getbrdstats(combrd_t __user *bp)
+{
+ stlibrd_t *brdp;
+ int i;
+
+ if (copy_from_user(&stli_brdstats, bp, sizeof(combrd_t)))
+ return -EFAULT;
+ if (stli_brdstats.brd >= STL_MAXBRDS)
+ return(-ENODEV);
+ brdp = stli_brds[stli_brdstats.brd];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+
+ memset(&stli_brdstats, 0, sizeof(combrd_t));
+ stli_brdstats.brd = brdp->brdnr;
+ stli_brdstats.type = brdp->brdtype;
+ stli_brdstats.hwid = 0;
+ stli_brdstats.state = brdp->state;
+ stli_brdstats.ioaddr = brdp->iobase;
+ stli_brdstats.memaddr = brdp->memaddr;
+ stli_brdstats.nrpanels = brdp->nrpanels;
+ stli_brdstats.nrports = brdp->nrports;
+ for (i = 0; (i < brdp->nrpanels); i++) {
+ stli_brdstats.panels[i].panel = i;
+ stli_brdstats.panels[i].hwid = brdp->panelids[i];
+ stli_brdstats.panels[i].nrports = brdp->panels[i];
+ }
+
+ if (copy_to_user(bp, &stli_brdstats, sizeof(combrd_t)))
+ return -EFAULT;
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Resolve the referenced port number into a port struct pointer.
+ */
+
+static stliport_t *stli_getport(int brdnr, int panelnr, int portnr)
+{
+ stlibrd_t *brdp;
+ int i;
+
+ if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+ return((stliport_t *) NULL);
+ brdp = stli_brds[brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return((stliport_t *) NULL);
+ for (i = 0; (i < panelnr); i++)
+ portnr += brdp->panels[i];
+ if ((portnr < 0) || (portnr >= brdp->nrports))
+ return((stliport_t *) NULL);
+ return(brdp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the port stats structure to user app. A NULL port struct
+ * pointer passed in means that we need to find out from the app
+ * what port to get stats for (used through board control device).
+ */
+
+static int stli_portcmdstats(stliport_t *portp)
+{
+ unsigned long flags;
+ stlibrd_t *brdp;
+ int rc;
+
+ memset(&stli_comstats, 0, sizeof(comstats_t));
+
+ if (portp == (stliport_t *) NULL)
+ return(-ENODEV);
+ brdp = stli_brds[portp->brdnr];
+ if (brdp == (stlibrd_t *) NULL)
+ return(-ENODEV);
+
+ if (brdp->state & BST_STARTED) {
+ if ((rc = stli_cmdwait(brdp, portp, A_GETSTATS,
+ &stli_cdkstats, sizeof(asystats_t), 1)) < 0)
+ return(rc);
+ } else {
+ memset(&stli_cdkstats, 0, sizeof(asystats_t));
+ }
+
+ stli_comstats.brd = portp->brdnr;
+ stli_comstats.panel = portp->panelnr;
+ stli_comstats.port = portp->portnr;
+ stli_comstats.state = portp->state;
+ stli_comstats.flags = portp->flags;
+
+ save_flags(flags);
+ cli();
+ if (portp->tty != (struct tty_struct *) NULL) {
+ if (portp->tty->driver_data == portp) {
+ stli_comstats.ttystate = portp->tty->flags;
+ stli_comstats.rxbuffered = portp->tty->flip.count;
+ if (portp->tty->termios != (struct termios *) NULL) {
+ stli_comstats.cflags = portp->tty->termios->c_cflag;
+ stli_comstats.iflags = portp->tty->termios->c_iflag;
+ stli_comstats.oflags = portp->tty->termios->c_oflag;
+ stli_comstats.lflags = portp->tty->termios->c_lflag;
+ }
+ }
+ }
+ restore_flags(flags);
+
+ stli_comstats.txtotal = stli_cdkstats.txchars;
+ stli_comstats.rxtotal = stli_cdkstats.rxchars + stli_cdkstats.ringover;
+ stli_comstats.txbuffered = stli_cdkstats.txringq;
+ stli_comstats.rxbuffered += stli_cdkstats.rxringq;
+ stli_comstats.rxoverrun = stli_cdkstats.overruns;
+ stli_comstats.rxparity = stli_cdkstats.parity;
+ stli_comstats.rxframing = stli_cdkstats.framing;
+ stli_comstats.rxlost = stli_cdkstats.ringover;
+ stli_comstats.rxbreaks = stli_cdkstats.rxbreaks;
+ stli_comstats.txbreaks = stli_cdkstats.txbreaks;
+ stli_comstats.txxon = stli_cdkstats.txstart;
+ stli_comstats.txxoff = stli_cdkstats.txstop;
+ stli_comstats.rxxon = stli_cdkstats.rxstart;
+ stli_comstats.rxxoff = stli_cdkstats.rxstop;
+ stli_comstats.rxrtsoff = stli_cdkstats.rtscnt / 2;
+ stli_comstats.rxrtson = stli_cdkstats.rtscnt - stli_comstats.rxrtsoff;
+ stli_comstats.modem = stli_cdkstats.dcdcnt;
+ stli_comstats.hwid = stli_cdkstats.hwid;
+ stli_comstats.signals = stli_mktiocm(stli_cdkstats.signals);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the port stats structure to user app. A NULL port struct
+ * pointer passed in means that we need to find out from the app
+ * what port to get stats for (used through board control device).
+ */
+
+static int stli_getportstats(stliport_t *portp, comstats_t __user *cp)
+{
+ stlibrd_t *brdp;
+ int rc;
+
+ if (!portp) {
+ if (copy_from_user(&stli_comstats, cp, sizeof(comstats_t)))
+ return -EFAULT;
+ portp = stli_getport(stli_comstats.brd, stli_comstats.panel,
+ stli_comstats.port);
+ if (!portp)
+ return -ENODEV;
+ }
+
+ brdp = stli_brds[portp->brdnr];
+ if (!brdp)
+ return -ENODEV;
+
+ if ((rc = stli_portcmdstats(portp)) < 0)
+ return rc;
+
+ return copy_to_user(cp, &stli_comstats, sizeof(comstats_t)) ?
+ -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Clear the port stats structure. We also return it zeroed out...
+ */
+
+static int stli_clrportstats(stliport_t *portp, comstats_t __user *cp)
+{
+ stlibrd_t *brdp;
+ int rc;
+
+ if (!portp) {
+ if (copy_from_user(&stli_comstats, cp, sizeof(comstats_t)))
+ return -EFAULT;
+ portp = stli_getport(stli_comstats.brd, stli_comstats.panel,
+ stli_comstats.port);
+ if (!portp)
+ return -ENODEV;
+ }
+
+ brdp = stli_brds[portp->brdnr];
+ if (!brdp)
+ return -ENODEV;
+
+ if (brdp->state & BST_STARTED) {
+ if ((rc = stli_cmdwait(brdp, portp, A_CLEARSTATS, NULL, 0, 0)) < 0)
+ return rc;
+ }
+
+ memset(&stli_comstats, 0, sizeof(comstats_t));
+ stli_comstats.brd = portp->brdnr;
+ stli_comstats.panel = portp->panelnr;
+ stli_comstats.port = portp->portnr;
+
+ if (copy_to_user(cp, &stli_comstats, sizeof(comstats_t)))
+ return -EFAULT;
+ return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver ports structure to a user app.
+ */
+
+static int stli_getportstruct(stliport_t __user *arg)
+{
+ stliport_t *portp;
+
+ if (copy_from_user(&stli_dummyport, arg, sizeof(stliport_t)))
+ return -EFAULT;
+ portp = stli_getport(stli_dummyport.brdnr, stli_dummyport.panelnr,
+ stli_dummyport.portnr);
+ if (!portp)
+ return -ENODEV;
+ if (copy_to_user(arg, portp, sizeof(stliport_t)))
+ return -EFAULT;
+ return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver board structure to a user app.
+ */
+
+static int stli_getbrdstruct(stlibrd_t __user *arg)
+{
+ stlibrd_t *brdp;
+
+ if (copy_from_user(&stli_dummybrd, arg, sizeof(stlibrd_t)))
+ return -EFAULT;
+ if ((stli_dummybrd.brdnr < 0) || (stli_dummybrd.brdnr >= STL_MAXBRDS))
+ return -ENODEV;
+ brdp = stli_brds[stli_dummybrd.brdnr];
+ if (!brdp)
+ return -ENODEV;
+ if (copy_to_user(arg, brdp, sizeof(stlibrd_t)))
+ return -EFAULT;
+ return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * The "staliomem" device is also required to do some special operations on
+ * the board. We need to be able to send an interrupt to the board,
+ * reset it, and start/stop it.
+ */
+
+static int stli_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ stlibrd_t *brdp;
+ int brdnr, rc, done;
+ void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "stli_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n",
+ (int) ip, (int) fp, cmd, (int) arg);
+#endif
+
+/*
+ * First up handle the board independent ioctls.
+ */
+ done = 0;
+ rc = 0;
+
+ switch (cmd) {
+ case COM_GETPORTSTATS:
+ rc = stli_getportstats(NULL, argp);
+ done++;
+ break;
+ case COM_CLRPORTSTATS:
+ rc = stli_clrportstats(NULL, argp);
+ done++;
+ break;
+ case COM_GETBRDSTATS:
+ rc = stli_getbrdstats(argp);
+ done++;
+ break;
+ case COM_READPORT:
+ rc = stli_getportstruct(argp);
+ done++;
+ break;
+ case COM_READBOARD:
+ rc = stli_getbrdstruct(argp);
+ done++;
+ break;
+ }
+
+ if (done)
+ return(rc);
+
+/*
+ * Now handle the board specific ioctls. These all depend on the
+ * minor number of the device they were called from.
+ */
+ brdnr = iminor(ip);
+ if (brdnr >= STL_MAXBRDS)
+ return(-ENODEV);
+ brdp = stli_brds[brdnr];
+ if (!brdp)
+ return(-ENODEV);
+ if (brdp->state == 0)
+ return(-ENODEV);
+
+ switch (cmd) {
+ case STL_BINTR:
+ EBRDINTR(brdp);
+ break;
+ case STL_BSTART:
+ rc = stli_startbrd(brdp);
+ break;
+ case STL_BSTOP:
+ brdp->state &= ~BST_STARTED;
+ break;
+ case STL_BRESET:
+ brdp->state &= ~BST_STARTED;
+ EBRDRESET(brdp);
+ if (stli_shared == 0) {
+ if (brdp->reenable != NULL)
+ (* brdp->reenable)(brdp);
+ }
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+static struct tty_operations stli_ops = {
+ .open = stli_open,
+ .close = stli_close,
+ .write = stli_write,
+ .put_char = stli_putchar,
+ .flush_chars = stli_flushchars,
+ .write_room = stli_writeroom,
+ .chars_in_buffer = stli_charsinbuffer,
+ .ioctl = stli_ioctl,
+ .set_termios = stli_settermios,
+ .throttle = stli_throttle,
+ .unthrottle = stli_unthrottle,
+ .stop = stli_stop,
+ .start = stli_start,
+ .hangup = stli_hangup,
+ .flush_buffer = stli_flushbuffer,
+ .break_ctl = stli_breakctl,
+ .wait_until_sent = stli_waituntilsent,
+ .send_xchar = stli_sendxchar,
+ .read_proc = stli_readproc,
+ .tiocmget = stli_tiocmget,
+ .tiocmset = stli_tiocmset,
+};
+
+/*****************************************************************************/
+
+int __init stli_init(void)
+{
+ int i;
+ printk(KERN_INFO "%s: version %s\n", stli_drvtitle, stli_drvversion);
+
+ stli_initbrds();
+
+ stli_serial = alloc_tty_driver(STL_MAXBRDS * STL_MAXPORTS);
+ if (!stli_serial)
+ return -ENOMEM;
+
+/*
+ * Allocate a temporary write buffer.
+ */
+ stli_tmpwritebuf = (char *) stli_memalloc(STLI_TXBUFSIZE);
+ if (stli_tmpwritebuf == (char *) NULL)
+ printk(KERN_ERR "STALLION: failed to allocate memory "
+ "(size=%d)\n", STLI_TXBUFSIZE);
+ stli_txcookbuf = stli_memalloc(STLI_TXBUFSIZE);
+ if (stli_txcookbuf == (char *) NULL)
+ printk(KERN_ERR "STALLION: failed to allocate memory "
+ "(size=%d)\n", STLI_TXBUFSIZE);
+
+/*
+ * Set up a character driver for the shared memory region. We need this
+ * to down load the slave code image. Also it is a useful debugging tool.
+ */
+ if (register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stli_fsiomem))
+ printk(KERN_ERR "STALLION: failed to register serial memory "
+ "device\n");
+
+ devfs_mk_dir("staliomem");
+ istallion_class = class_simple_create(THIS_MODULE, "staliomem");
+ for (i = 0; i < 4; i++) {
+ devfs_mk_cdev(MKDEV(STL_SIOMEMMAJOR, i),
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ "staliomem/%d", i);
+ class_simple_device_add(istallion_class, MKDEV(STL_SIOMEMMAJOR, i),
+ NULL, "staliomem%d", i);
+ }
+
+/*
+ * Set up the tty driver structure and register us as a driver.
+ */
+ stli_serial->owner = THIS_MODULE;
+ stli_serial->driver_name = stli_drvname;
+ stli_serial->name = stli_serialname;
+ stli_serial->major = STL_SERIALMAJOR;
+ stli_serial->minor_start = 0;
+ stli_serial->type = TTY_DRIVER_TYPE_SERIAL;
+ stli_serial->subtype = SERIAL_TYPE_NORMAL;
+ stli_serial->init_termios = stli_deftermios;
+ stli_serial->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(stli_serial, &stli_ops);
+
+ if (tty_register_driver(stli_serial)) {
+ put_tty_driver(stli_serial);
+ printk(KERN_ERR "STALLION: failed to register serial driver\n");
+ return -EBUSY;
+ }
+ return(0);
+}
+
+/*****************************************************************************/
diff --git a/drivers/char/ite_gpio.c b/drivers/char/ite_gpio.c
new file mode 100644
index 000000000000..d1ed6ac950d1
--- /dev/null
+++ b/drivers/char/ite_gpio.c
@@ -0,0 +1,419 @@
+/*
+ * FILE NAME ite_gpio.c
+ *
+ * BRIEF MODULE DESCRIPTION
+ * API for ITE GPIO device.
+ * Driver for ITE GPIO device.
+ *
+ * Author: MontaVista Software, Inc. <source@mvista.com>
+ * Hai-Pao Fan <haipao@mvista.com>
+ *
+ * Copyright 2001 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <asm/uaccess.h>
+#include <asm/addrspace.h>
+#include <asm/it8172/it8172_int.h>
+#include <linux/sched.h>
+#include <linux/ite_gpio.h>
+
+#define ite_gpio_base 0x14013800
+
+#define ITE_GPADR (*(volatile __u8 *)(0x14013800 + KSEG1))
+#define ITE_GPBDR (*(volatile __u8 *)(0x14013808 + KSEG1))
+#define ITE_GPCDR (*(volatile __u8 *)(0x14013810 + KSEG1))
+#define ITE_GPACR (*(volatile __u16 *)(0x14013802 + KSEG1))
+#define ITE_GPBCR (*(volatile __u16 *)(0x1401380a + KSEG1))
+#define ITE_GPCCR (*(volatile __u16 *)(0x14013812 + KSEG1))
+#define ITE_GPAICR (*(volatile __u16 *)(0x14013804 + KSEG1))
+#define ITE_GPBICR (*(volatile __u16 *)(0x1401380c + KSEG1))
+#define ITE_GPCICR (*(volatile __u16 *)(0x14013814 + KSEG1))
+#define ITE_GPAISR (*(volatile __u8 *)(0x14013806 + KSEG1))
+#define ITE_GPBISR (*(volatile __u8 *)(0x1401380e + KSEG1))
+#define ITE_GPCISR (*(volatile __u8 *)(0x14013816 + KSEG1))
+#define ITE_GCR (*(volatile __u8 *)(0x14013818 + KSEG1))
+
+#define MAX_GPIO_LINE 21
+static int ite_gpio_irq=IT8172_GPIO_IRQ;
+
+static long ite_irq_counter[MAX_GPIO_LINE];
+wait_queue_head_t ite_gpio_wait[MAX_GPIO_LINE];
+static int ite_gpio_irq_pending[MAX_GPIO_LINE];
+
+static int ite_gpio_debug=0;
+#define DEB(x) if (ite_gpio_debug>=1) x
+
+int ite_gpio_in(__u32 device, __u32 mask, volatile __u32 *data)
+{
+ DEB(printk("ite_gpio_in mask=0x%x\n",mask));
+
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ ITE_GPACR = (__u16)mask; /* 0xffff */
+ *data = ITE_GPADR;
+ break;
+ case ITE_GPIO_PORTB:
+ ITE_GPBCR = (__u16)mask; /* 0xffff */
+ *data = ITE_GPBDR;
+ break;
+ case ITE_GPIO_PORTC:
+ ITE_GPCCR = (__u16)mask; /* 0x03ff */
+ *data = ITE_GPCDR;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+
+int ite_gpio_out(__u32 device, __u32 mask, __u32 data)
+{
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ ITE_GPACR = (__u16)mask; /* 0x5555 */
+ ITE_GPADR = (__u8)data;
+ break;
+ case ITE_GPIO_PORTB:
+ ITE_GPBCR = (__u16)mask; /* 0x5555 */
+ ITE_GPBDR = (__u8)data;
+ break;
+ case ITE_GPIO_PORTC:
+ ITE_GPCCR = (__u16)mask; /* 0x0155 */
+ ITE_GPCDR = (__u8)data;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+int ite_gpio_int_ctrl(__u32 device, __u32 mask, __u32 data)
+{
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ ITE_GPAICR = (ITE_GPAICR & ~mask) | (data & mask);
+ break;
+ case ITE_GPIO_PORTB:
+ ITE_GPBICR = (ITE_GPBICR & ~mask) | (data & mask);
+ break;
+ case ITE_GPIO_PORTC:
+ ITE_GPCICR = (ITE_GPCICR & ~mask) | (data & mask);
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+int ite_gpio_in_status(__u32 device, __u32 mask, volatile __u32 *data)
+{
+ int ret=-1;
+
+ if ((MAX_GPIO_LINE > *data) && (*data >= 0))
+ ret=ite_gpio_irq_pending[*data];
+
+ DEB(printk("ite_gpio_in_status %d ret=%d\n",*data, ret));
+
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ *data = ITE_GPAISR & mask;
+ break;
+ case ITE_GPIO_PORTB:
+ *data = ITE_GPBISR & mask;
+ break;
+ case ITE_GPIO_PORTC:
+ *data = ITE_GPCISR & mask;
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return ret;
+}
+
+int ite_gpio_out_status(__u32 device, __u32 mask, __u32 data)
+{
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ ITE_GPAISR = (ITE_GPAISR & ~mask) | (data & mask);
+ break;
+ case ITE_GPIO_PORTB:
+ ITE_GPBISR = (ITE_GPBISR & ~mask) | (data & mask);
+ break;
+ case ITE_GPIO_PORTC:
+ ITE_GPCISR = (ITE_GPCISR & ~mask) | (data & mask);
+ break;
+ default:
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+int ite_gpio_gen_ctrl(__u32 device, __u32 mask, __u32 data)
+{
+ ITE_GCR = (ITE_GCR & ~mask) | (data & mask);
+
+ return 0;
+}
+
+int ite_gpio_int_wait (__u32 device, __u32 mask, __u32 data)
+{
+ int i,line=0, ret=0;
+ unsigned long flags;
+
+ switch (device) {
+ case ITE_GPIO_PORTA:
+ line = data & mask;
+ break;
+ case ITE_GPIO_PORTB:
+ line = (data & mask) <<8;
+ break;
+ case ITE_GPIO_PORTC:
+ line = (data & mask) <<16;
+ break;
+ }
+ for (i=MAX_GPIO_LINE-1; i >= 0; i--) {
+ if ( (line) & (1 << i))
+ break;
+ }
+
+ DEB(printk("wait device=0x%d mask=0x%x data=0x%x index %d\n",
+ device, mask, data, i));
+
+ if (line & ~(1<<i))
+ return -EFAULT;
+
+ if (ite_gpio_irq_pending[i]==1)
+ return -EFAULT;
+
+ save_flags (flags);
+ cli();
+ ite_gpio_irq_pending[i] = 1;
+ ret = interruptible_sleep_on_timeout(&ite_gpio_wait[i], 3*HZ);
+ restore_flags (flags);
+ ite_gpio_irq_pending[i] = 0;
+
+ return ret;
+}
+
+EXPORT_SYMBOL(ite_gpio_in);
+EXPORT_SYMBOL(ite_gpio_out);
+EXPORT_SYMBOL(ite_gpio_int_ctrl);
+EXPORT_SYMBOL(ite_gpio_in_status);
+EXPORT_SYMBOL(ite_gpio_out_status);
+EXPORT_SYMBOL(ite_gpio_gen_ctrl);
+EXPORT_SYMBOL(ite_gpio_int_wait);
+
+static int ite_gpio_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+
+static int ite_gpio_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+
+static int ite_gpio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ static struct ite_gpio_ioctl_data ioctl_data;
+
+ if (copy_from_user(&ioctl_data, (struct ite_gpio_ioctl_data *)arg,
+ sizeof(ioctl_data)))
+ return -EFAULT;
+ if ((ioctl_data.device < ITE_GPIO_PORTA) ||
+ (ioctl_data.device > ITE_GPIO_PORTC) )
+ return -EFAULT;
+
+ switch(cmd) {
+ case ITE_GPIO_IN:
+ if (ite_gpio_in(ioctl_data.device, ioctl_data.mask,
+ &ioctl_data.data))
+ return -EFAULT;
+
+ if (copy_to_user((struct ite_gpio_ioctl_data *)arg,
+ &ioctl_data, sizeof(ioctl_data)))
+ return -EFAULT;
+ break;
+
+ case ITE_GPIO_OUT:
+ return ite_gpio_out(ioctl_data.device,
+ ioctl_data.mask, ioctl_data.data);
+ break;
+
+ case ITE_GPIO_INT_CTRL:
+ return ite_gpio_int_ctrl(ioctl_data.device,
+ ioctl_data.mask, ioctl_data.data);
+ break;
+
+ case ITE_GPIO_IN_STATUS:
+ if (ite_gpio_in_status(ioctl_data.device, ioctl_data.mask,
+ &ioctl_data.data))
+ return -EFAULT;
+ if (copy_to_user((struct ite_gpio_ioctl_data *)arg,
+ &ioctl_data, sizeof(ioctl_data)))
+ return -EFAULT;
+ break;
+
+ case ITE_GPIO_OUT_STATUS:
+ return ite_gpio_out_status(ioctl_data.device,
+ ioctl_data.mask, ioctl_data.data);
+ break;
+
+ case ITE_GPIO_GEN_CTRL:
+ return ite_gpio_gen_ctrl(ioctl_data.device,
+ ioctl_data.mask, ioctl_data.data);
+ break;
+
+ case ITE_GPIO_INT_WAIT:
+ return ite_gpio_int_wait(ioctl_data.device,
+ ioctl_data.mask, ioctl_data.data);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+
+ }
+
+ return 0;
+}
+
+static void ite_gpio_irq_handler(int this_irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ int i,line;
+
+ line = ITE_GPCISR & 0x1f;
+ for (i=4; i >=0; i--) {
+ if ( line & (1 << i)) {
+ ++ite_irq_counter[i+16];
+ ite_gpio_irq_pending[i+16] = 2;
+ wake_up_interruptible(&ite_gpio_wait[i+16]);
+
+DEB(printk("interrupt 0x%x %d\n", &ite_gpio_wait[i+16], i+16));
+
+ ITE_GPCISR = ITE_GPCISR & (1<<i);
+ return;
+ }
+ }
+ line = ITE_GPBISR;
+ for (i=7; i >= 0; i--) {
+ if ( line & (1 << i)) {
+ ++ite_irq_counter[i+8];
+ ite_gpio_irq_pending[i+8] = 2;
+ wake_up_interruptible(&ite_gpio_wait[i+8]);
+
+DEB(printk("interrupt 0x%x %d\n",ITE_GPBISR, i+8));
+
+ ITE_GPBISR = ITE_GPBISR & (1<<i);
+ return;
+ }
+ }
+ line = ITE_GPAISR;
+ for (i=7; i >= 0; i--) {
+ if ( line & (1 << i)) {
+ ++ite_irq_counter[i];
+ ite_gpio_irq_pending[i] = 2;
+ wake_up_interruptible(&ite_gpio_wait[i]);
+
+DEB(printk("interrupt 0x%x %d\n",ITE_GPAISR, i));
+
+ ITE_GPAISR = ITE_GPAISR & (1<<i);
+ return;
+ }
+ }
+}
+
+static struct file_operations ite_gpio_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = ite_gpio_ioctl,
+ .open = ite_gpio_open,
+ .release = ite_gpio_release,
+};
+
+static struct miscdevice ite_gpio_miscdev = {
+ MISC_DYNAMIC_MINOR,
+ "ite_gpio",
+ &ite_gpio_fops
+};
+
+int __init ite_gpio_init(void)
+{
+ int i;
+
+ if (misc_register(&ite_gpio_miscdev))
+ return -ENODEV;
+
+ if (!request_region(ite_gpio_base, 0x1c, "ITE GPIO"))
+ {
+ misc_deregister(&ite_gpio_miscdev);
+ return -EIO;
+ }
+
+ /* initialize registers */
+ ITE_GPACR = 0xffff;
+ ITE_GPBCR = 0xffff;
+ ITE_GPCCR = 0xffff;
+ ITE_GPAICR = 0x00ff;
+ ITE_GPBICR = 0x00ff;
+ ITE_GPCICR = 0x00ff;
+ ITE_GCR = 0;
+
+ for (i = 0; i < MAX_GPIO_LINE; i++) {
+ ite_gpio_irq_pending[i]=0;
+ init_waitqueue_head(&ite_gpio_wait[i]);
+ }
+
+ if (request_irq(ite_gpio_irq, ite_gpio_irq_handler, SA_SHIRQ, "gpio", 0) < 0) {
+ misc_deregister(&ite_gpio_miscdev);
+ release_region(ite_gpio_base, 0x1c);
+ return 0;
+ }
+
+ printk("GPIO at 0x%x (irq = %d)\n", ite_gpio_base, ite_gpio_irq);
+
+ return 0;
+}
+
+static void __exit ite_gpio_exit(void)
+{
+ misc_deregister(&ite_gpio_miscdev);
+}
+
+module_init(ite_gpio_init);
+module_exit(ite_gpio_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c
new file mode 100644
index 000000000000..3ce51c6a1b18
--- /dev/null
+++ b/drivers/char/keyboard.c
@@ -0,0 +1,1254 @@
+/*
+ * linux/drivers/char/keyboard.c
+ *
+ * Written for linux by Johan Myreen as a translation from
+ * the assembly version by Linus (with diacriticals added)
+ *
+ * Some additional features added by Christoph Niemann (ChN), March 1993
+ *
+ * Loadable keymaps by Risto Kankkunen, May 1993
+ *
+ * Diacriticals redone & other small changes, aeb@cwi.nl, June 1993
+ * Added decr/incr_console, dynamic keymaps, Unicode support,
+ * dynamic function/string keys, led setting, Sept 1994
+ * `Sticky' modifier keys, 951006.
+ *
+ * 11-11-96: SAK should now work in the raw mode (Martin Mares)
+ *
+ * Modified to provide 'generic' keyboard support by Hamish Macdonald
+ * Merge with the m68k keyboard driver and split-off of the PC low-level
+ * parts by Geert Uytterhoeven, May 1997
+ *
+ * 27-05-97: Added support for the Magic SysRq Key (Martin Mares)
+ * 30-07-98: Dead keys redone, aeb@cwi.nl.
+ * 21-08-02: Converted to input API, major cleanup. (Vojtech Pavlik)
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/kbd_diacr.h>
+#include <linux/vt_kern.h>
+#include <linux/sysrq.h>
+#include <linux/input.h>
+
+static void kbd_disconnect(struct input_handle *handle);
+extern void ctrl_alt_del(void);
+
+/*
+ * Exported functions/variables
+ */
+
+#define KBD_DEFMODE ((1 << VC_REPEAT) | (1 << VC_META))
+
+/*
+ * Some laptops take the 789uiojklm,. keys as number pad when NumLock is on.
+ * This seems a good reason to start with NumLock off. On HIL keyboards
+ * of PARISC machines however there is no NumLock key and everyone expects the keypad
+ * to be used for numbers.
+ */
+
+#if defined(CONFIG_PARISC) && (defined(CONFIG_KEYBOARD_HIL) || defined(CONFIG_KEYBOARD_HIL_OLD))
+#define KBD_DEFLEDS (1 << VC_NUMLOCK)
+#else
+#define KBD_DEFLEDS 0
+#endif
+
+#define KBD_DEFLOCK 0
+
+void compute_shiftstate(void);
+
+/*
+ * Handler Tables.
+ */
+
+#define K_HANDLERS\
+ k_self, k_fn, k_spec, k_pad,\
+ k_dead, k_cons, k_cur, k_shift,\
+ k_meta, k_ascii, k_lock, k_lowercase,\
+ k_slock, k_dead2, k_ignore, k_ignore
+
+typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value,
+ char up_flag, struct pt_regs *regs);
+static k_handler_fn K_HANDLERS;
+static k_handler_fn *k_handler[16] = { K_HANDLERS };
+
+#define FN_HANDLERS\
+ fn_null, fn_enter, fn_show_ptregs, fn_show_mem,\
+ fn_show_state, fn_send_intr, fn_lastcons, fn_caps_toggle,\
+ fn_num, fn_hold, fn_scroll_forw, fn_scroll_back,\
+ fn_boot_it, fn_caps_on, fn_compose, fn_SAK,\
+ fn_dec_console, fn_inc_console, fn_spawn_con, fn_bare_num
+
+typedef void (fn_handler_fn)(struct vc_data *vc, struct pt_regs *regs);
+static fn_handler_fn FN_HANDLERS;
+static fn_handler_fn *fn_handler[] = { FN_HANDLERS };
+
+/*
+ * Variables exported for vt_ioctl.c
+ */
+
+/* maximum values each key_handler can handle */
+const int max_vals[] = {
+ 255, ARRAY_SIZE(func_table) - 1, ARRAY_SIZE(fn_handler) - 1, NR_PAD - 1,
+ NR_DEAD - 1, 255, 3, NR_SHIFT - 1, 255, NR_ASCII - 1, NR_LOCK - 1,
+ 255, NR_LOCK - 1, 255
+};
+
+const int NR_TYPES = ARRAY_SIZE(max_vals);
+
+struct kbd_struct kbd_table[MAX_NR_CONSOLES];
+static struct kbd_struct *kbd = kbd_table;
+static struct kbd_struct kbd0;
+
+int spawnpid, spawnsig;
+
+/*
+ * Variables exported for vt.c
+ */
+
+int shift_state = 0;
+
+/*
+ * Internal Data.
+ */
+
+static struct input_handler kbd_handler;
+static unsigned long key_down[NBITS(KEY_MAX)]; /* keyboard key bitmap */
+static unsigned char shift_down[NR_SHIFT]; /* shift state counters.. */
+static int dead_key_next;
+static int npadch = -1; /* -1 or number assembled on pad */
+static unsigned char diacr;
+static char rep; /* flag telling character repeat */
+
+static unsigned char ledstate = 0xff; /* undefined */
+static unsigned char ledioctl;
+
+static struct ledptr {
+ unsigned int *addr;
+ unsigned int mask;
+ unsigned char valid:1;
+} ledptrs[3];
+
+/* Simple translation table for the SysRq keys */
+
+#ifdef CONFIG_MAGIC_SYSRQ
+unsigned char kbd_sysrq_xlate[KEY_MAX + 1] =
+ "\000\0331234567890-=\177\t" /* 0x00 - 0x0f */
+ "qwertyuiop[]\r\000as" /* 0x10 - 0x1f */
+ "dfghjkl;'`\000\\zxcv" /* 0x20 - 0x2f */
+ "bnm,./\000*\000 \000\201\202\203\204\205" /* 0x30 - 0x3f */
+ "\206\207\210\211\212\000\000789-456+1" /* 0x40 - 0x4f */
+ "230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */
+ "\r\000/"; /* 0x60 - 0x6f */
+static int sysrq_down;
+#endif
+static int sysrq_alt;
+
+/*
+ * Translation of scancodes to keycodes. We set them on only the first attached
+ * keyboard - for per-keyboard setting, /dev/input/event is more useful.
+ */
+int getkeycode(unsigned int scancode)
+{
+ struct list_head * node;
+ struct input_dev *dev = NULL;
+
+ list_for_each(node,&kbd_handler.h_list) {
+ struct input_handle * handle = to_handle_h(node);
+ if (handle->dev->keycodesize) {
+ dev = handle->dev;
+ break;
+ }
+ }
+
+ if (!dev)
+ return -ENODEV;
+
+ if (scancode >= dev->keycodemax)
+ return -EINVAL;
+
+ return INPUT_KEYCODE(dev, scancode);
+}
+
+int setkeycode(unsigned int scancode, unsigned int keycode)
+{
+ struct list_head * node;
+ struct input_dev *dev = NULL;
+ unsigned int i, oldkey;
+
+ list_for_each(node,&kbd_handler.h_list) {
+ struct input_handle *handle = to_handle_h(node);
+ if (handle->dev->keycodesize) {
+ dev = handle->dev;
+ break;
+ }
+ }
+
+ if (!dev)
+ return -ENODEV;
+
+ if (scancode >= dev->keycodemax)
+ return -EINVAL;
+ if (keycode > KEY_MAX)
+ return -EINVAL;
+ if (keycode < 0 || keycode > KEY_MAX)
+ return -EINVAL;
+
+ oldkey = SET_INPUT_KEYCODE(dev, scancode, keycode);
+
+ clear_bit(oldkey, dev->keybit);
+ set_bit(keycode, dev->keybit);
+
+ for (i = 0; i < dev->keycodemax; i++)
+ if (INPUT_KEYCODE(dev,i) == oldkey)
+ set_bit(oldkey, dev->keybit);
+
+ return 0;
+}
+
+/*
+ * Making beeps and bells.
+ */
+static void kd_nosound(unsigned long ignored)
+{
+ struct list_head * node;
+
+ list_for_each(node,&kbd_handler.h_list) {
+ struct input_handle *handle = to_handle_h(node);
+ if (test_bit(EV_SND, handle->dev->evbit)) {
+ if (test_bit(SND_TONE, handle->dev->sndbit))
+ input_event(handle->dev, EV_SND, SND_TONE, 0);
+ if (test_bit(SND_BELL, handle->dev->sndbit))
+ input_event(handle->dev, EV_SND, SND_BELL, 0);
+ }
+ }
+}
+
+static struct timer_list kd_mksound_timer =
+ TIMER_INITIALIZER(kd_nosound, 0, 0);
+
+void kd_mksound(unsigned int hz, unsigned int ticks)
+{
+ struct list_head * node;
+
+ del_timer(&kd_mksound_timer);
+
+ if (hz) {
+ list_for_each_prev(node,&kbd_handler.h_list) {
+ struct input_handle *handle = to_handle_h(node);
+ if (test_bit(EV_SND, handle->dev->evbit)) {
+ if (test_bit(SND_TONE, handle->dev->sndbit)) {
+ input_event(handle->dev, EV_SND, SND_TONE, hz);
+ break;
+ }
+ if (test_bit(SND_BELL, handle->dev->sndbit)) {
+ input_event(handle->dev, EV_SND, SND_BELL, 1);
+ break;
+ }
+ }
+ }
+ if (ticks)
+ mod_timer(&kd_mksound_timer, jiffies + ticks);
+ } else
+ kd_nosound(0);
+}
+
+/*
+ * Setting the keyboard rate.
+ */
+
+int kbd_rate(struct kbd_repeat *rep)
+{
+ struct list_head *node;
+ unsigned int d = 0;
+ unsigned int p = 0;
+
+ list_for_each(node,&kbd_handler.h_list) {
+ struct input_handle *handle = to_handle_h(node);
+ struct input_dev *dev = handle->dev;
+
+ if (test_bit(EV_REP, dev->evbit)) {
+ if (rep->delay > 0)
+ input_event(dev, EV_REP, REP_DELAY, rep->delay);
+ if (rep->period > 0)
+ input_event(dev, EV_REP, REP_PERIOD, rep->period);
+ d = dev->rep[REP_DELAY];
+ p = dev->rep[REP_PERIOD];
+ }
+ }
+ rep->delay = d;
+ rep->period = p;
+ return 0;
+}
+
+/*
+ * Helper Functions.
+ */
+static void put_queue(struct vc_data *vc, int ch)
+{
+ struct tty_struct *tty = vc->vc_tty;
+
+ if (tty) {
+ tty_insert_flip_char(tty, ch, 0);
+ con_schedule_flip(tty);
+ }
+}
+
+static void puts_queue(struct vc_data *vc, char *cp)
+{
+ struct tty_struct *tty = vc->vc_tty;
+
+ if (!tty)
+ return;
+
+ while (*cp) {
+ tty_insert_flip_char(tty, *cp, 0);
+ cp++;
+ }
+ con_schedule_flip(tty);
+}
+
+static void applkey(struct vc_data *vc, int key, char mode)
+{
+ static char buf[] = { 0x1b, 'O', 0x00, 0x00 };
+
+ buf[1] = (mode ? 'O' : '[');
+ buf[2] = key;
+ puts_queue(vc, buf);
+}
+
+/*
+ * Many other routines do put_queue, but I think either
+ * they produce ASCII, or they produce some user-assigned
+ * string, and in both cases we might assume that it is
+ * in utf-8 already. UTF-8 is defined for words of up to 31 bits,
+ * but we need only 16 bits here
+ */
+static void to_utf8(struct vc_data *vc, ushort c)
+{
+ if (c < 0x80)
+ /* 0******* */
+ put_queue(vc, c);
+ else if (c < 0x800) {
+ /* 110***** 10****** */
+ put_queue(vc, 0xc0 | (c >> 6));
+ put_queue(vc, 0x80 | (c & 0x3f));
+ } else {
+ /* 1110**** 10****** 10****** */
+ put_queue(vc, 0xe0 | (c >> 12));
+ put_queue(vc, 0x80 | ((c >> 6) & 0x3f));
+ put_queue(vc, 0x80 | (c & 0x3f));
+ }
+}
+
+/*
+ * Called after returning from RAW mode or when changing consoles - recompute
+ * shift_down[] and shift_state from key_down[] maybe called when keymap is
+ * undefined, so that shiftkey release is seen
+ */
+void compute_shiftstate(void)
+{
+ unsigned int i, j, k, sym, val;
+
+ shift_state = 0;
+ memset(shift_down, 0, sizeof(shift_down));
+
+ for (i = 0; i < ARRAY_SIZE(key_down); i++) {
+
+ if (!key_down[i])
+ continue;
+
+ k = i * BITS_PER_LONG;
+
+ for (j = 0; j < BITS_PER_LONG; j++, k++) {
+
+ if (!test_bit(k, key_down))
+ continue;
+
+ sym = U(key_maps[0][k]);
+ if (KTYP(sym) != KT_SHIFT && KTYP(sym) != KT_SLOCK)
+ continue;
+
+ val = KVAL(sym);
+ if (val == KVAL(K_CAPSSHIFT))
+ val = KVAL(K_SHIFT);
+
+ shift_down[val]++;
+ shift_state |= (1 << val);
+ }
+ }
+}
+
+/*
+ * We have a combining character DIACR here, followed by the character CH.
+ * If the combination occurs in the table, return the corresponding value.
+ * Otherwise, if CH is a space or equals DIACR, return DIACR.
+ * Otherwise, conclude that DIACR was not combining after all,
+ * queue it and return CH.
+ */
+static unsigned char handle_diacr(struct vc_data *vc, unsigned char ch)
+{
+ int d = diacr;
+ unsigned int i;
+
+ diacr = 0;
+
+ for (i = 0; i < accent_table_size; i++) {
+ if (accent_table[i].diacr == d && accent_table[i].base == ch)
+ return accent_table[i].result;
+ }
+
+ if (ch == ' ' || ch == d)
+ return d;
+
+ put_queue(vc, d);
+ return ch;
+}
+
+/*
+ * Special function handlers
+ */
+static void fn_enter(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (diacr) {
+ put_queue(vc, diacr);
+ diacr = 0;
+ }
+ put_queue(vc, 13);
+ if (vc_kbd_mode(kbd, VC_CRLF))
+ put_queue(vc, 10);
+}
+
+static void fn_caps_toggle(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (rep)
+ return;
+ chg_vc_kbd_led(kbd, VC_CAPSLOCK);
+}
+
+static void fn_caps_on(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (rep)
+ return;
+ set_vc_kbd_led(kbd, VC_CAPSLOCK);
+}
+
+static void fn_show_ptregs(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (regs)
+ show_regs(regs);
+}
+
+static void fn_hold(struct vc_data *vc, struct pt_regs *regs)
+{
+ struct tty_struct *tty = vc->vc_tty;
+
+ if (rep || !tty)
+ return;
+
+ /*
+ * Note: SCROLLOCK will be set (cleared) by stop_tty (start_tty);
+ * these routines are also activated by ^S/^Q.
+ * (And SCROLLOCK can also be set by the ioctl KDSKBLED.)
+ */
+ if (tty->stopped)
+ start_tty(tty);
+ else
+ stop_tty(tty);
+}
+
+static void fn_num(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (vc_kbd_mode(kbd,VC_APPLIC))
+ applkey(vc, 'P', 1);
+ else
+ fn_bare_num(vc, regs);
+}
+
+/*
+ * Bind this to Shift-NumLock if you work in application keypad mode
+ * but want to be able to change the NumLock flag.
+ * Bind this to NumLock if you prefer that the NumLock key always
+ * changes the NumLock flag.
+ */
+static void fn_bare_num(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (!rep)
+ chg_vc_kbd_led(kbd, VC_NUMLOCK);
+}
+
+static void fn_lastcons(struct vc_data *vc, struct pt_regs *regs)
+{
+ /* switch to the last used console, ChN */
+ set_console(last_console);
+}
+
+static void fn_dec_console(struct vc_data *vc, struct pt_regs *regs)
+{
+ int i, cur = fg_console;
+
+ /* Currently switching? Queue this next switch relative to that. */
+ if (want_console != -1)
+ cur = want_console;
+
+ for (i = cur-1; i != cur; i--) {
+ if (i == -1)
+ i = MAX_NR_CONSOLES-1;
+ if (vc_cons_allocated(i))
+ break;
+ }
+ set_console(i);
+}
+
+static void fn_inc_console(struct vc_data *vc, struct pt_regs *regs)
+{
+ int i, cur = fg_console;
+
+ /* Currently switching? Queue this next switch relative to that. */
+ if (want_console != -1)
+ cur = want_console;
+
+ for (i = cur+1; i != cur; i++) {
+ if (i == MAX_NR_CONSOLES)
+ i = 0;
+ if (vc_cons_allocated(i))
+ break;
+ }
+ set_console(i);
+}
+
+static void fn_send_intr(struct vc_data *vc, struct pt_regs *regs)
+{
+ struct tty_struct *tty = vc->vc_tty;
+
+ if (!tty)
+ return;
+ tty_insert_flip_char(tty, 0, TTY_BREAK);
+ con_schedule_flip(tty);
+}
+
+static void fn_scroll_forw(struct vc_data *vc, struct pt_regs *regs)
+{
+ scrollfront(vc, 0);
+}
+
+static void fn_scroll_back(struct vc_data *vc, struct pt_regs *regs)
+{
+ scrollback(vc, 0);
+}
+
+static void fn_show_mem(struct vc_data *vc, struct pt_regs *regs)
+{
+ show_mem();
+}
+
+static void fn_show_state(struct vc_data *vc, struct pt_regs *regs)
+{
+ show_state();
+}
+
+static void fn_boot_it(struct vc_data *vc, struct pt_regs *regs)
+{
+ ctrl_alt_del();
+}
+
+static void fn_compose(struct vc_data *vc, struct pt_regs *regs)
+{
+ dead_key_next = 1;
+}
+
+static void fn_spawn_con(struct vc_data *vc, struct pt_regs *regs)
+{
+ if (spawnpid)
+ if(kill_proc(spawnpid, spawnsig, 1))
+ spawnpid = 0;
+}
+
+static void fn_SAK(struct vc_data *vc, struct pt_regs *regs)
+{
+ struct tty_struct *tty = vc->vc_tty;
+
+ /*
+ * SAK should also work in all raw modes and reset
+ * them properly.
+ */
+ if (tty)
+ do_SAK(tty);
+ reset_vc(vc);
+}
+
+static void fn_null(struct vc_data *vc, struct pt_regs *regs)
+{
+ compute_shiftstate();
+}
+
+/*
+ * Special key handlers
+ */
+static void k_ignore(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+}
+
+static void k_spec(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag)
+ return;
+ if (value >= ARRAY_SIZE(fn_handler))
+ return;
+ if ((kbd->kbdmode == VC_RAW ||
+ kbd->kbdmode == VC_MEDIUMRAW) &&
+ value != KVAL(K_SAK))
+ return; /* SAK is allowed even in raw mode */
+ fn_handler[value](vc, regs);
+}
+
+static void k_lowercase(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ printk(KERN_ERR "keyboard.c: k_lowercase was called - impossible\n");
+}
+
+static void k_self(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag)
+ return; /* no action, if this is a key release */
+
+ if (diacr)
+ value = handle_diacr(vc, value);
+
+ if (dead_key_next) {
+ dead_key_next = 0;
+ diacr = value;
+ return;
+ }
+ put_queue(vc, value);
+}
+
+/*
+ * Handle dead key. Note that we now may have several
+ * dead keys modifying the same character. Very useful
+ * for Vietnamese.
+ */
+static void k_dead2(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag)
+ return;
+ diacr = (diacr ? handle_diacr(vc, value) : value);
+}
+
+/*
+ * Obsolete - for backwards compatibility only
+ */
+static void k_dead(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ static unsigned char ret_diacr[NR_DEAD] = {'`', '\'', '^', '~', '"', ',' };
+ value = ret_diacr[value];
+ k_dead2(vc, value, up_flag, regs);
+}
+
+static void k_cons(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag)
+ return;
+ set_console(value);
+}
+
+static void k_fn(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ unsigned v;
+
+ if (up_flag)
+ return;
+ v = value;
+ if (v < ARRAY_SIZE(func_table)) {
+ if (func_table[value])
+ puts_queue(vc, func_table[value]);
+ } else
+ printk(KERN_ERR "k_fn called with value=%d\n", value);
+}
+
+static void k_cur(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ static const char *cur_chars = "BDCA";
+
+ if (up_flag)
+ return;
+ applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE));
+}
+
+static void k_pad(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ static const char *pad_chars = "0123456789+-*/\015,.?()#";
+ static const char *app_map = "pqrstuvwxylSRQMnnmPQS";
+
+ if (up_flag)
+ return; /* no action, if this is a key release */
+
+ /* kludge... shift forces cursor/number keys */
+ if (vc_kbd_mode(kbd, VC_APPLIC) && !shift_down[KG_SHIFT]) {
+ applkey(vc, app_map[value], 1);
+ return;
+ }
+
+ if (!vc_kbd_led(kbd, VC_NUMLOCK))
+ switch (value) {
+ case KVAL(K_PCOMMA):
+ case KVAL(K_PDOT):
+ k_fn(vc, KVAL(K_REMOVE), 0, regs);
+ return;
+ case KVAL(K_P0):
+ k_fn(vc, KVAL(K_INSERT), 0, regs);
+ return;
+ case KVAL(K_P1):
+ k_fn(vc, KVAL(K_SELECT), 0, regs);
+ return;
+ case KVAL(K_P2):
+ k_cur(vc, KVAL(K_DOWN), 0, regs);
+ return;
+ case KVAL(K_P3):
+ k_fn(vc, KVAL(K_PGDN), 0, regs);
+ return;
+ case KVAL(K_P4):
+ k_cur(vc, KVAL(K_LEFT), 0, regs);
+ return;
+ case KVAL(K_P6):
+ k_cur(vc, KVAL(K_RIGHT), 0, regs);
+ return;
+ case KVAL(K_P7):
+ k_fn(vc, KVAL(K_FIND), 0, regs);
+ return;
+ case KVAL(K_P8):
+ k_cur(vc, KVAL(K_UP), 0, regs);
+ return;
+ case KVAL(K_P9):
+ k_fn(vc, KVAL(K_PGUP), 0, regs);
+ return;
+ case KVAL(K_P5):
+ applkey(vc, 'G', vc_kbd_mode(kbd, VC_APPLIC));
+ return;
+ }
+
+ put_queue(vc, pad_chars[value]);
+ if (value == KVAL(K_PENTER) && vc_kbd_mode(kbd, VC_CRLF))
+ put_queue(vc, 10);
+}
+
+static void k_shift(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ int old_state = shift_state;
+
+ if (rep)
+ return;
+ /*
+ * Mimic typewriter:
+ * a CapsShift key acts like Shift but undoes CapsLock
+ */
+ if (value == KVAL(K_CAPSSHIFT)) {
+ value = KVAL(K_SHIFT);
+ if (!up_flag)
+ clr_vc_kbd_led(kbd, VC_CAPSLOCK);
+ }
+
+ if (up_flag) {
+ /*
+ * handle the case that two shift or control
+ * keys are depressed simultaneously
+ */
+ if (shift_down[value])
+ shift_down[value]--;
+ } else
+ shift_down[value]++;
+
+ if (shift_down[value])
+ shift_state |= (1 << value);
+ else
+ shift_state &= ~(1 << value);
+
+ /* kludge */
+ if (up_flag && shift_state != old_state && npadch != -1) {
+ if (kbd->kbdmode == VC_UNICODE)
+ to_utf8(vc, npadch & 0xffff);
+ else
+ put_queue(vc, npadch & 0xff);
+ npadch = -1;
+ }
+}
+
+static void k_meta(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag)
+ return;
+
+ if (vc_kbd_mode(kbd, VC_META)) {
+ put_queue(vc, '\033');
+ put_queue(vc, value);
+ } else
+ put_queue(vc, value | 0x80);
+}
+
+static void k_ascii(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ int base;
+
+ if (up_flag)
+ return;
+
+ if (value < 10) {
+ /* decimal input of code, while Alt depressed */
+ base = 10;
+ } else {
+ /* hexadecimal input of code, while AltGr depressed */
+ value -= 10;
+ base = 16;
+ }
+
+ if (npadch == -1)
+ npadch = value;
+ else
+ npadch = npadch * base + value;
+}
+
+static void k_lock(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ if (up_flag || rep)
+ return;
+ chg_vc_kbd_lock(kbd, value);
+}
+
+static void k_slock(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+ k_shift(vc, value, up_flag, regs);
+ if (up_flag || rep)
+ return;
+ chg_vc_kbd_slock(kbd, value);
+ /* try to make Alt, oops, AltGr and such work */
+ if (!key_maps[kbd->lockstate ^ kbd->slockstate]) {
+ kbd->slockstate = 0;
+ chg_vc_kbd_slock(kbd, value);
+ }
+}
+
+/*
+ * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
+ * or (ii) whatever pattern of lights people want to show using KDSETLED,
+ * or (iii) specified bits of specified words in kernel memory.
+ */
+unsigned char getledstate(void)
+{
+ return ledstate;
+}
+
+void setledstate(struct kbd_struct *kbd, unsigned int led)
+{
+ if (!(led & ~7)) {
+ ledioctl = led;
+ kbd->ledmode = LED_SHOW_IOCTL;
+ } else
+ kbd->ledmode = LED_SHOW_FLAGS;
+ set_leds();
+}
+
+static inline unsigned char getleds(void)
+{
+ struct kbd_struct *kbd = kbd_table + fg_console;
+ unsigned char leds;
+ int i;
+
+ if (kbd->ledmode == LED_SHOW_IOCTL)
+ return ledioctl;
+
+ leds = kbd->ledflagstate;
+
+ if (kbd->ledmode == LED_SHOW_MEM) {
+ for (i = 0; i < 3; i++)
+ if (ledptrs[i].valid) {
+ if (*ledptrs[i].addr & ledptrs[i].mask)
+ leds |= (1 << i);
+ else
+ leds &= ~(1 << i);
+ }
+ }
+ return leds;
+}
+
+/*
+ * This routine is the bottom half of the keyboard interrupt
+ * routine, and runs with all interrupts enabled. It does
+ * console changing, led setting and copy_to_cooked, which can
+ * take a reasonably long time.
+ *
+ * Aside from timing (which isn't really that important for
+ * keyboard interrupts as they happen often), using the software
+ * interrupt routines for this thing allows us to easily mask
+ * this when we don't want any of the above to happen.
+ * This allows for easy and efficient race-condition prevention
+ * for kbd_refresh_leds => input_event(dev, EV_LED, ...) => ...
+ */
+
+static void kbd_bh(unsigned long dummy)
+{
+ struct list_head * node;
+ unsigned char leds = getleds();
+
+ if (leds != ledstate) {
+ list_for_each(node,&kbd_handler.h_list) {
+ struct input_handle * handle = to_handle_h(node);
+ input_event(handle->dev, EV_LED, LED_SCROLLL, !!(leds & 0x01));
+ input_event(handle->dev, EV_LED, LED_NUML, !!(leds & 0x02));
+ input_event(handle->dev, EV_LED, LED_CAPSL, !!(leds & 0x04));
+ input_sync(handle->dev);
+ }
+ }
+
+ ledstate = leds;
+}
+
+DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
+
+/*
+ * This allows a newly plugged keyboard to pick the LED state.
+ */
+static void kbd_refresh_leds(struct input_handle *handle)
+{
+ unsigned char leds = ledstate;
+
+ tasklet_disable(&keyboard_tasklet);
+ if (leds != 0xff) {
+ input_event(handle->dev, EV_LED, LED_SCROLLL, !!(leds & 0x01));
+ input_event(handle->dev, EV_LED, LED_NUML, !!(leds & 0x02));
+ input_event(handle->dev, EV_LED, LED_CAPSL, !!(leds & 0x04));
+ input_sync(handle->dev);
+ }
+ tasklet_enable(&keyboard_tasklet);
+}
+
+#if defined(CONFIG_X86) || defined(CONFIG_IA64) || defined(CONFIG_ALPHA) ||\
+ defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_SPARC32) ||\
+ defined(CONFIG_SPARC64) || defined(CONFIG_PARISC) || defined(CONFIG_SUPERH) ||\
+ (defined(CONFIG_ARM) && defined(CONFIG_KEYBOARD_ATKBD) && !defined(CONFIG_ARCH_RPC))
+
+#define HW_RAW(dev) (test_bit(EV_MSC, dev->evbit) && test_bit(MSC_RAW, dev->mscbit) &&\
+ ((dev)->id.bustype == BUS_I8042) && ((dev)->id.vendor == 0x0001) && ((dev)->id.product == 0x0001))
+
+static unsigned short x86_keycodes[256] =
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84,118, 86, 87, 88,115,120,119,121,112,123, 92,
+ 284,285,309,298,312, 91,327,328,329,331,333,335,336,337,338,339,
+ 367,288,302,304,350, 89,334,326,267,126,268,269,125,347,348,349,
+ 360,261,262,263,268,376,100,101,321,316,373,286,289,102,351,355,
+ 103,104,105,275,287,279,306,106,274,107,294,364,358,363,362,361,
+ 291,108,381,281,290,272,292,305,280, 99,112,257,258,359,113,114,
+ 264,117,271,374,379,265,266, 93, 94, 95, 85,259,375,260, 90,116,
+ 377,109,111,277,278,282,283,295,296,297,299,300,301,293,303,307,
+ 308,310,313,314,315,317,318,319,320,357,322,323,324,325,276,330,
+ 332,340,365,342,343,344,345,346,356,270,341,368,369,370,371,372 };
+
+#ifdef CONFIG_MAC_EMUMOUSEBTN
+extern int mac_hid_mouse_emulate_buttons(int, int, int);
+#endif /* CONFIG_MAC_EMUMOUSEBTN */
+
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+static int sparc_l1_a_state = 0;
+extern void sun_do_break(void);
+#endif
+
+static int emulate_raw(struct vc_data *vc, unsigned int keycode,
+ unsigned char up_flag)
+{
+ if (keycode > 255 || !x86_keycodes[keycode])
+ return -1;
+
+ switch (keycode) {
+ case KEY_PAUSE:
+ put_queue(vc, 0xe1);
+ put_queue(vc, 0x1d | up_flag);
+ put_queue(vc, 0x45 | up_flag);
+ return 0;
+ case KEY_HANGUEL:
+ if (!up_flag) put_queue(vc, 0xf1);
+ return 0;
+ case KEY_HANJA:
+ if (!up_flag) put_queue(vc, 0xf2);
+ return 0;
+ }
+
+ if (keycode == KEY_SYSRQ && sysrq_alt) {
+ put_queue(vc, 0x54 | up_flag);
+ return 0;
+ }
+
+ if (x86_keycodes[keycode] & 0x100)
+ put_queue(vc, 0xe0);
+
+ put_queue(vc, (x86_keycodes[keycode] & 0x7f) | up_flag);
+
+ if (keycode == KEY_SYSRQ) {
+ put_queue(vc, 0xe0);
+ put_queue(vc, 0x37 | up_flag);
+ }
+
+ return 0;
+}
+
+#else
+
+#define HW_RAW(dev) 0
+
+#warning "Cannot generate rawmode keyboard for your architecture yet."
+
+static int emulate_raw(struct vc_data *vc, unsigned int keycode, unsigned char up_flag)
+{
+ if (keycode > 127)
+ return -1;
+
+ put_queue(vc, keycode | up_flag);
+ return 0;
+}
+#endif
+
+static void kbd_rawcode(unsigned char data)
+{
+ struct vc_data *vc = vc_cons[fg_console].d;
+ kbd = kbd_table + fg_console;
+ if (kbd->kbdmode == VC_RAW)
+ put_queue(vc, data);
+}
+
+void kbd_keycode(unsigned int keycode, int down, int hw_raw, struct pt_regs *regs)
+{
+ struct vc_data *vc = vc_cons[fg_console].d;
+ unsigned short keysym, *key_map;
+ unsigned char type, raw_mode;
+ struct tty_struct *tty;
+ int shift_final;
+
+ tty = vc->vc_tty;
+
+ if (tty && (!tty->driver_data)) {
+ /* No driver data? Strange. Okay we fix it then. */
+ tty->driver_data = vc;
+ }
+
+ kbd = kbd_table + fg_console;
+
+ if (keycode == KEY_LEFTALT || keycode == KEY_RIGHTALT)
+ sysrq_alt = down;
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+ if (keycode == KEY_STOP)
+ sparc_l1_a_state = down;
+#endif
+
+ rep = (down == 2);
+
+#ifdef CONFIG_MAC_EMUMOUSEBTN
+ if (mac_hid_mouse_emulate_buttons(1, keycode, down))
+ return;
+#endif /* CONFIG_MAC_EMUMOUSEBTN */
+
+ if ((raw_mode = (kbd->kbdmode == VC_RAW)) && !hw_raw)
+ if (emulate_raw(vc, keycode, !down << 7))
+ if (keycode < BTN_MISC)
+ printk(KERN_WARNING "keyboard.c: can't emulate rawmode for keycode %d\n", keycode);
+
+#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */
+ if (keycode == KEY_SYSRQ && (sysrq_down || (down == 1 && sysrq_alt))) {
+ sysrq_down = down;
+ return;
+ }
+ if (sysrq_down && down && !rep) {
+ handle_sysrq(kbd_sysrq_xlate[keycode], regs, tty);
+ return;
+ }
+#endif
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+ if (keycode == KEY_A && sparc_l1_a_state) {
+ sparc_l1_a_state = 0;
+ sun_do_break();
+ }
+#endif
+
+ if (kbd->kbdmode == VC_MEDIUMRAW) {
+ /*
+ * This is extended medium raw mode, with keys above 127
+ * encoded as 0, high 7 bits, low 7 bits, with the 0 bearing
+ * the 'up' flag if needed. 0 is reserved, so this shouldn't
+ * interfere with anything else. The two bytes after 0 will
+ * always have the up flag set not to interfere with older
+ * applications. This allows for 16384 different keycodes,
+ * which should be enough.
+ */
+ if (keycode < 128) {
+ put_queue(vc, keycode | (!down << 7));
+ } else {
+ put_queue(vc, !down << 7);
+ put_queue(vc, (keycode >> 7) | 0x80);
+ put_queue(vc, keycode | 0x80);
+ }
+ raw_mode = 1;
+ }
+
+ if (down)
+ set_bit(keycode, key_down);
+ else
+ clear_bit(keycode, key_down);
+
+ if (rep && (!vc_kbd_mode(kbd, VC_REPEAT) || (tty &&
+ (!L_ECHO(tty) && tty->driver->chars_in_buffer(tty))))) {
+ /*
+ * Don't repeat a key if the input buffers are not empty and the
+ * characters get aren't echoed locally. This makes key repeat
+ * usable with slow applications and under heavy loads.
+ */
+ return;
+ }
+
+ shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
+ key_map = key_maps[shift_final];
+
+ if (!key_map) {
+ compute_shiftstate();
+ kbd->slockstate = 0;
+ return;
+ }
+
+ if (keycode > NR_KEYS)
+ return;
+
+ keysym = key_map[keycode];
+ type = KTYP(keysym);
+
+ if (type < 0xf0) {
+ if (down && !raw_mode) to_utf8(vc, keysym);
+ return;
+ }
+
+ type -= 0xf0;
+
+ if (raw_mode && type != KT_SPEC && type != KT_SHIFT)
+ return;
+
+ if (type == KT_LETTER) {
+ type = KT_LATIN;
+ if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
+ key_map = key_maps[shift_final ^ (1 << KG_SHIFT)];
+ if (key_map)
+ keysym = key_map[keycode];
+ }
+ }
+
+ (*k_handler[type])(vc, keysym & 0xff, !down, regs);
+
+ if (type != KT_SLOCK)
+ kbd->slockstate = 0;
+}
+
+static void kbd_event(struct input_handle *handle, unsigned int event_type,
+ unsigned int event_code, int value)
+{
+ if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
+ kbd_rawcode(value);
+ if (event_type == EV_KEY)
+ kbd_keycode(event_code, value, HW_RAW(handle->dev), handle->dev->regs);
+ tasklet_schedule(&keyboard_tasklet);
+ do_poke_blanked_console = 1;
+ schedule_console_callback();
+}
+
+static char kbd_name[] = "kbd";
+
+/*
+ * When a keyboard (or other input device) is found, the kbd_connect
+ * function is called. The function then looks at the device, and if it
+ * likes it, it can open it and get events from it. In this (kbd_connect)
+ * function, we should decide which VT to bind that keyboard to initially.
+ */
+static struct input_handle *kbd_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int i;
+
+ for (i = KEY_RESERVED; i < BTN_MISC; i++)
+ if (test_bit(i, dev->keybit)) break;
+
+ if ((i == BTN_MISC) && !test_bit(EV_SND, dev->evbit))
+ return NULL;
+
+ if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL)))
+ return NULL;
+ memset(handle, 0, sizeof(struct input_handle));
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = kbd_name;
+
+ input_open_device(handle);
+ kbd_refresh_leds(handle);
+
+ return handle;
+}
+
+static void kbd_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ kfree(handle);
+}
+
+static struct input_device_id kbd_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT(EV_KEY) },
+ },
+
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT(EV_SND) },
+ },
+
+ { }, /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, kbd_ids);
+
+static struct input_handler kbd_handler = {
+ .event = kbd_event,
+ .connect = kbd_connect,
+ .disconnect = kbd_disconnect,
+ .name = "kbd",
+ .id_table = kbd_ids,
+};
+
+int __init kbd_init(void)
+{
+ int i;
+
+ kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS;
+ kbd0.ledmode = LED_SHOW_FLAGS;
+ kbd0.lockstate = KBD_DEFLOCK;
+ kbd0.slockstate = 0;
+ kbd0.modeflags = KBD_DEFMODE;
+ kbd0.kbdmode = VC_XLATE;
+
+ for (i = 0 ; i < MAX_NR_CONSOLES ; i++)
+ kbd_table[i] = kbd0;
+
+ input_register_handler(&kbd_handler);
+
+ tasklet_enable(&keyboard_tasklet);
+ tasklet_schedule(&keyboard_tasklet);
+
+ return 0;
+}
diff --git a/drivers/char/lcd.c b/drivers/char/lcd.c
new file mode 100644
index 000000000000..cf01a720eb2e
--- /dev/null
+++ b/drivers/char/lcd.c
@@ -0,0 +1,683 @@
+/*
+ * LCD, LED and Button interface for Cobalt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1996, 1997 by Andrew Bose
+ *
+ * Linux kernel version history:
+ * March 2001: Ported from 2.0.34 by Liam Davies
+ *
+ */
+
+#define RTC_IO_EXTENT 0x10 /*Only really two ports, but... */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/delay.h>
+
+#include "lcd.h"
+
+static DEFINE_SPINLOCK(lcd_lock);
+
+static int lcd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static unsigned int lcd_present = 1;
+
+/* used in arch/mips/cobalt/reset.c */
+int led_state = 0;
+
+#if defined(CONFIG_TULIP) && 0
+
+#define MAX_INTERFACES 8
+static linkcheck_func_t linkcheck_callbacks[MAX_INTERFACES];
+static void *linkcheck_cookies[MAX_INTERFACES];
+
+int lcd_register_linkcheck_func(int iface_num, void *func, void *cookie)
+{
+ if (iface_num < 0 ||
+ iface_num >= MAX_INTERFACES ||
+ linkcheck_callbacks[iface_num] != NULL)
+ return -1;
+ linkcheck_callbacks[iface_num] = (linkcheck_func_t) func;
+ linkcheck_cookies[iface_num] = cookie;
+ return 0;
+}
+#endif
+
+static int lcd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct lcd_display button_display;
+ unsigned long address, a;
+
+ switch (cmd) {
+ case LCD_On:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x0F);
+ break;
+
+ case LCD_Off:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x08);
+ break;
+
+ case LCD_Reset:
+ udelay(150);
+ LCDWriteInst(0x3F);
+ udelay(150);
+ LCDWriteInst(0x3F);
+ udelay(150);
+ LCDWriteInst(0x3F);
+ udelay(150);
+ LCDWriteInst(0x3F);
+ udelay(150);
+ LCDWriteInst(0x01);
+ udelay(150);
+ LCDWriteInst(0x06);
+ break;
+
+ case LCD_Clear:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x01);
+ break;
+
+ case LCD_Cursor_Left:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x10);
+ break;
+
+ case LCD_Cursor_Right:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x14);
+ break;
+
+ case LCD_Cursor_Off:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x0C);
+ break;
+
+ case LCD_Cursor_On:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x0F);
+ break;
+
+ case LCD_Blink_Off:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x0E);
+ break;
+
+ case LCD_Get_Cursor_Pos:{
+ struct lcd_display display;
+
+ udelay(150);
+ BusyCheck();
+ display.cursor_address = (LCDReadInst);
+ display.cursor_address =
+ (display.cursor_address & 0x07F);
+ if (copy_to_user
+ ((struct lcd_display *) arg, &display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ break;
+ }
+
+
+ case LCD_Set_Cursor_Pos:{
+ struct lcd_display display;
+
+ if (copy_from_user
+ (&display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ a = (display.cursor_address | kLCD_Addr);
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(a);
+
+ break;
+ }
+
+ case LCD_Get_Cursor:{
+ struct lcd_display display;
+
+ udelay(150);
+ BusyCheck();
+ display.character = LCDReadData;
+
+ if (copy_to_user
+ ((struct lcd_display *) arg, &display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x10);
+
+ break;
+ }
+
+ case LCD_Set_Cursor:{
+ struct lcd_display display;
+
+ if (copy_from_user
+ (&display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteData(display.character);
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x10);
+
+ break;
+ }
+
+
+ case LCD_Disp_Left:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x18);
+ break;
+
+ case LCD_Disp_Right:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x1C);
+ break;
+
+ case LCD_Home:
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x02);
+ break;
+
+ case LCD_Write:{
+ struct lcd_display display;
+ unsigned int index;
+
+
+ if (copy_from_user
+ (&display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0x80);
+ udelay(150);
+ BusyCheck();
+
+ for (index = 0; index < (display.size1); index++) {
+ udelay(150);
+ BusyCheck();
+ LCDWriteData(display.line1[index]);
+ BusyCheck();
+ }
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(0xC0);
+ udelay(150);
+ BusyCheck();
+ for (index = 0; index < (display.size2); index++) {
+ udelay(150);
+ BusyCheck();
+ LCDWriteData(display.line2[index]);
+ }
+
+ break;
+ }
+
+ case LCD_Read:{
+ struct lcd_display display;
+
+ BusyCheck();
+ for (address = kDD_R00; address <= kDD_R01;
+ address++) {
+ a = (address | kLCD_Addr);
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(a);
+ udelay(150);
+ BusyCheck();
+ display.line1[address] = LCDReadData;
+ }
+
+ display.line1[0x27] = '\0';
+
+ for (address = kDD_R10; address <= kDD_R11;
+ address++) {
+ a = (address | kLCD_Addr);
+
+ udelay(150);
+ BusyCheck();
+ LCDWriteInst(a);
+
+ udelay(150);
+ BusyCheck();
+ display.line2[address - 0x40] =
+ LCDReadData;
+ }
+
+ display.line2[0x27] = '\0';
+
+ if (copy_to_user
+ ((struct lcd_display *) arg, &display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ break;
+ }
+
+// set all GPIO leds to led_display.leds
+
+ case LED_Set:{
+ struct lcd_display led_display;
+
+
+ if (copy_from_user
+ (&led_display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ led_state = led_display.leds;
+ LEDSet(led_state);
+
+ break;
+ }
+
+
+// set only bit led_display.leds
+
+ case LED_Bit_Set:{
+ unsigned int i;
+ int bit = 1;
+ struct lcd_display led_display;
+
+
+ if (copy_from_user
+ (&led_display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ for (i = 0; i < (int) led_display.leds; i++) {
+ bit = 2 * bit;
+ }
+
+ led_state = led_state | bit;
+ LEDSet(led_state);
+ break;
+ }
+
+// clear only bit led_display.leds
+
+ case LED_Bit_Clear:{
+ unsigned int i;
+ int bit = 1;
+ struct lcd_display led_display;
+
+
+ if (copy_from_user
+ (&led_display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+
+ for (i = 0; i < (int) led_display.leds; i++) {
+ bit = 2 * bit;
+ }
+
+ led_state = led_state & ~bit;
+ LEDSet(led_state);
+ break;
+ }
+
+
+ case BUTTON_Read:{
+ button_display.buttons = GPIRead;
+ if (copy_to_user
+ ((struct lcd_display *) arg, &button_display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ break;
+ }
+
+ case LINK_Check:{
+ button_display.buttons =
+ *((volatile unsigned long *) (0xB0100060));
+ if (copy_to_user
+ ((struct lcd_display *) arg, &button_display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ break;
+ }
+
+ case LINK_Check_2:{
+ int iface_num;
+
+ /* panel-utils should pass in the desired interface status is wanted for
+ * in "buttons" of the structure. We will set this to non-zero if the
+ * link is in fact up for the requested interface. --DaveM
+ */
+ if (copy_from_user
+ (&button_display, (struct lcd_display *) arg,
+ sizeof(button_display)))
+ return -EFAULT;
+ iface_num = button_display.buttons;
+#if defined(CONFIG_TULIP) && 0
+ if (iface_num >= 0 &&
+ iface_num < MAX_INTERFACES &&
+ linkcheck_callbacks[iface_num] != NULL) {
+ button_display.buttons =
+ linkcheck_callbacks[iface_num]
+ (linkcheck_cookies[iface_num]);
+ } else
+#endif
+ button_display.buttons = 0;
+
+ if (__copy_to_user
+ ((struct lcd_display *) arg, &button_display,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ break;
+ }
+
+// Erase the flash
+
+ case FLASH_Erase:{
+
+ int ctr = 0;
+
+ if ( !capable(CAP_SYS_ADMIN) ) return -EPERM;
+
+ pr_info(LCD "Erasing Flash\n");
+
+ // Chip Erase Sequence
+ WRITE_FLASH(kFlash_Addr1, kFlash_Data1);
+ WRITE_FLASH(kFlash_Addr2, kFlash_Data2);
+ WRITE_FLASH(kFlash_Addr1, kFlash_Erase3);
+ WRITE_FLASH(kFlash_Addr1, kFlash_Data1);
+ WRITE_FLASH(kFlash_Addr2, kFlash_Data2);
+ WRITE_FLASH(kFlash_Addr1, kFlash_Erase6);
+
+ while ((!dqpoll(0x00000000, 0xFF))
+ && (!timeout(0x00000000))) {
+ ctr++;
+ }
+
+ if (READ_FLASH(0x07FFF0) == 0xFF) {
+ pr_info(LCD "Erase Successful\n");
+ } else if (timeout) {
+ pr_info(LCD "Erase Timed Out\n");
+ }
+
+ break;
+ }
+
+// burn the flash
+
+ case FLASH_Burn:{
+
+ volatile unsigned long burn_addr;
+ unsigned long flags;
+ unsigned int i, index;
+ unsigned char *rom;
+
+
+ struct lcd_display display;
+
+ if ( !capable(CAP_SYS_ADMIN) ) return -EPERM;
+
+ if (copy_from_user
+ (&display, (struct lcd_display *) arg,
+ sizeof(struct lcd_display)))
+ return -EFAULT;
+ rom = (unsigned char *) kmalloc((128), GFP_ATOMIC);
+ if (rom == NULL) {
+ printk(KERN_ERR LCD "kmalloc() failed in %s\n",
+ __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ pr_info(LCD "Starting Flash burn\n");
+ for (i = 0; i < FLASH_SIZE; i = i + 128) {
+
+ if (copy_from_user
+ (rom, display.RomImage + i, 128)) {
+ kfree(rom);
+ return -EFAULT;
+ }
+ burn_addr = kFlashBase + i;
+ spin_lock_irqsave(&lcd_lock, flags);
+ for (index = 0; index < (128); index++) {
+
+ WRITE_FLASH(kFlash_Addr1,
+ kFlash_Data1);
+ WRITE_FLASH(kFlash_Addr2,
+ kFlash_Data2);
+ WRITE_FLASH(kFlash_Addr1,
+ kFlash_Prog);
+ *((volatile unsigned char *)burn_addr) =
+ (volatile unsigned char) rom[index];
+
+ while ((!dqpoll (burn_addr,
+ (volatile unsigned char)
+ rom[index])) &&
+ (!timeout(burn_addr))) { }
+ burn_addr++;
+ }
+ spin_unlock_irqrestore(&lcd_lock, flags);
+ if (* ((volatile unsigned char *)
+ (burn_addr - 1)) ==
+ (volatile unsigned char)
+ rom[index - 1]) {
+ } else if (timeout) {
+ pr_info(LCD "Flash burn timed out\n");
+ }
+
+
+ }
+ kfree(rom);
+
+ pr_info(LCD "Flash successfully burned\n");
+
+ break;
+ }
+
+// read the flash all at once
+
+ case FLASH_Read:{
+
+ unsigned char *user_bytes;
+ volatile unsigned long read_addr;
+ unsigned int i;
+
+ user_bytes =
+ &(((struct lcd_display *) arg)->RomImage[0]);
+
+ if (!access_ok
+ (VERIFY_WRITE, user_bytes, FLASH_SIZE))
+ return -EFAULT;
+
+ pr_info(LCD "Reading Flash");
+ for (i = 0; i < FLASH_SIZE; i++) {
+ unsigned char tmp_byte;
+ read_addr = kFlashBase + i;
+ tmp_byte =
+ *((volatile unsigned char *)
+ read_addr);
+ if (__put_user(tmp_byte, &user_bytes[i]))
+ return -EFAULT;
+ }
+
+
+ break;
+ }
+
+ default:
+ return -EINVAL;
+
+ }
+
+ return 0;
+
+}
+
+static int lcd_open(struct inode *inode, struct file *file)
+{
+ if (!lcd_present)
+ return -ENXIO;
+ else
+ return 0;
+}
+
+/* Only RESET or NEXT counts as button pressed */
+
+static inline int button_pressed(void)
+{
+ unsigned long buttons = GPIRead;
+
+ if ((buttons == BUTTON_Next) || (buttons == BUTTON_Next_B)
+ || (buttons == BUTTON_Reset_B))
+ return buttons;
+ return 0;
+}
+
+/* LED daemon sits on this and we wake him up once a key is pressed. */
+
+static int lcd_waiters = 0;
+
+static long lcd_read(struct inode *inode, struct file *file, char *buf,
+ unsigned long count)
+{
+ long buttons_now;
+
+ if (lcd_waiters > 0)
+ return -EINVAL;
+
+ lcd_waiters++;
+ while (((buttons_now = (long) button_pressed()) == 0) &&
+ !(signal_pending(current))) {
+ msleep_interruptible(2000);
+ }
+ lcd_waiters--;
+
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ return buttons_now;
+}
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations lcd_fops = {
+ .read = lcd_read,
+ .ioctl = lcd_ioctl,
+ .open = lcd_open,
+};
+
+static struct miscdevice lcd_dev = {
+ MISC_DYNAMIC_MINOR,
+ "lcd",
+ &lcd_fops
+};
+
+static int lcd_init(void)
+{
+ unsigned long data;
+
+ pr_info("%s\n", LCD_DRIVER);
+ misc_register(&lcd_dev);
+
+ /* Check region? Naaah! Just snarf it up. */
+/* request_region(RTC_PORT(0), RTC_IO_EXTENT, "lcd");*/
+
+ udelay(150);
+ data = LCDReadData;
+ if ((data & 0x000000FF) == (0x00)) {
+ lcd_present = 0;
+ pr_info(LCD "LCD Not Present\n");
+ } else {
+ lcd_present = 1;
+ WRITE_GAL(kGal_DevBank2PReg, kGal_DevBank2Cfg);
+ WRITE_GAL(kGal_DevBank3PReg, kGal_DevBank3Cfg);
+ }
+
+ return 0;
+}
+
+static void __exit lcd_exit(void)
+{
+ misc_deregister(&lcd_dev);
+}
+
+//
+// Function: dqpoll
+//
+// Description: Polls the data lines to see if the flash is busy
+//
+// In: address, byte data
+//
+// Out: 0 = busy, 1 = write or erase complete
+//
+//
+
+static int dqpoll(volatile unsigned long address, volatile unsigned char data)
+{
+ volatile unsigned char dq7;
+
+ dq7 = data & 0x80;
+
+ return ((READ_FLASH(address) & 0x80) == dq7);
+}
+
+//
+// Function: timeout
+//
+// Description: Checks to see if erase or write has timed out
+// By polling dq5
+//
+// In: address
+//
+//
+// Out: 0 = not timed out, 1 = timed out
+
+static int timeout(volatile unsigned long address)
+{
+ return (READ_FLASH(address) & 0x20) == 0x20;
+}
+
+module_init(lcd_init);
+module_exit(lcd_exit);
+
+MODULE_AUTHOR("Andrew Bose");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/lcd.h b/drivers/char/lcd.h
new file mode 100644
index 000000000000..878a95280e87
--- /dev/null
+++ b/drivers/char/lcd.h
@@ -0,0 +1,186 @@
+/*
+ * LED, LCD and Button panel driver for Cobalt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1996, 1997 by Andrew Bose
+ *
+ * Linux kernel version history:
+ * March 2001: Ported from 2.0.34 by Liam Davies
+ *
+ */
+
+// function headers
+
+static int dqpoll(volatile unsigned long, volatile unsigned char );
+static int timeout(volatile unsigned long);
+
+#define LCD_CHARS_PER_LINE 40
+#define FLASH_SIZE 524288
+#define MAX_IDLE_TIME 120
+
+struct lcd_display {
+ unsigned long buttons;
+ int size1;
+ int size2;
+ unsigned char line1[LCD_CHARS_PER_LINE];
+ unsigned char line2[LCD_CHARS_PER_LINE];
+ unsigned char cursor_address;
+ unsigned char character;
+ unsigned char leds;
+ unsigned char *RomImage;
+};
+
+
+
+#define LCD_DRIVER "Cobalt LCD Driver v2.10"
+
+#define LCD "lcd: "
+
+#define kLCD_IR 0x0F000000
+#define kLCD_DR 0x0F000010
+#define kGPI 0x0D000000
+#define kLED 0x0C000000
+
+#define kDD_R00 0x00
+#define kDD_R01 0x27
+#define kDD_R10 0x40
+#define kDD_R11 0x67
+
+#define kLCD_Addr 0x00000080
+
+#define LCDTimeoutValue 0xfff
+
+
+// Flash definitions AMD 29F040
+#define kFlashBase 0x0FC00000
+
+#define kFlash_Addr1 0x5555
+#define kFlash_Addr2 0x2AAA
+#define kFlash_Data1 0xAA
+#define kFlash_Data2 0x55
+#define kFlash_Prog 0xA0
+#define kFlash_Erase3 0x80
+#define kFlash_Erase6 0x10
+#define kFlash_Read 0xF0
+
+#define kFlash_ID 0x90
+#define kFlash_VenAddr 0x00
+#define kFlash_DevAddr 0x01
+#define kFlash_VenID 0x01
+#define kFlash_DevID 0xA4 // 29F040
+//#define kFlash_DevID 0xAD // 29F016
+
+
+// Macros
+
+#define LCDWriteData(x) outl((x << 24), kLCD_DR)
+#define LCDWriteInst(x) outl((x << 24), kLCD_IR)
+
+#define LCDReadData (inl(kLCD_DR) >> 24)
+#define LCDReadInst (inl(kLCD_IR) >> 24)
+
+#define GPIRead (inl(kGPI) >> 24)
+
+#define LEDSet(x) outb((char)x, kLED)
+
+#define WRITE_GAL(x,y) outl(y, 0x04000000 | (x))
+#define BusyCheck() while ((LCDReadInst & 0x80) == 0x80)
+
+#define WRITE_FLASH(x,y) outb((char)y, kFlashBase | (x))
+#define READ_FLASH(x) (inb(kFlashBase | (x)))
+
+
+
+/*
+ * Function command codes for io_ctl.
+ */
+#define LCD_On 1
+#define LCD_Off 2
+#define LCD_Clear 3
+#define LCD_Reset 4
+#define LCD_Cursor_Left 5
+#define LCD_Cursor_Right 6
+#define LCD_Disp_Left 7
+#define LCD_Disp_Right 8
+#define LCD_Get_Cursor 9
+#define LCD_Set_Cursor 10
+#define LCD_Home 11
+#define LCD_Read 12
+#define LCD_Write 13
+#define LCD_Cursor_Off 14
+#define LCD_Cursor_On 15
+#define LCD_Get_Cursor_Pos 16
+#define LCD_Set_Cursor_Pos 17
+#define LCD_Blink_Off 18
+
+#define LED_Set 40
+#define LED_Bit_Set 41
+#define LED_Bit_Clear 42
+
+
+// Button defs
+#define BUTTON_Read 50
+
+// Flash command codes
+#define FLASH_Erase 60
+#define FLASH_Burn 61
+#define FLASH_Read 62
+
+
+// Ethernet LINK check hackaroo
+#define LINK_Check 90
+#define LINK_Check_2 91
+
+// Button patterns _B - single layer lcd boards
+
+#define BUTTON_NONE 0x3F
+#define BUTTON_NONE_B 0xFE
+
+#define BUTTON_Left 0x3B
+#define BUTTON_Left_B 0xFA
+
+#define BUTTON_Right 0x37
+#define BUTTON_Right_B 0xDE
+
+#define BUTTON_Up 0x2F
+#define BUTTON_Up_B 0xF6
+
+#define BUTTON_Down 0x1F
+#define BUTTON_Down_B 0xEE
+
+#define BUTTON_Next 0x3D
+#define BUTTON_Next_B 0x7E
+
+#define BUTTON_Enter 0x3E
+#define BUTTON_Enter_B 0xBE
+
+#define BUTTON_Reset_B 0xFC
+
+
+// debounce constants
+
+#define BUTTON_SENSE 160000
+#define BUTTON_DEBOUNCE 5000
+
+
+// Galileo register stuff
+
+#define kGal_DevBank2Cfg 0x1466DB33
+#define kGal_DevBank2PReg 0x464
+#define kGal_DevBank3Cfg 0x146FDFFB
+#define kGal_DevBank3PReg 0x468
+
+// Network
+
+#define kIPADDR 1
+#define kNETMASK 2
+#define kGATEWAY 3
+#define kDNS 4
+
+#define kClassA 5
+#define kClassB 6
+#define kClassC 7
+
diff --git a/drivers/char/lp.c b/drivers/char/lp.c
new file mode 100644
index 000000000000..4dee945031d4
--- /dev/null
+++ b/drivers/char/lp.c
@@ -0,0 +1,995 @@
+/*
+ * Generic parallel printer driver
+ *
+ * Copyright (C) 1992 by Jim Weigand and Linus Torvalds
+ * Copyright (C) 1992,1993 by Michael K. Johnson
+ * - Thanks much to Gunter Windau for pointing out to me where the error
+ * checking ought to be.
+ * Copyright (C) 1993 by Nigel Gamble (added interrupt code)
+ * Copyright (C) 1994 by Alan Cox (Modularised it)
+ * LPCAREFUL, LPABORT, LPGETSTATUS added by Chris Metcalf, metcalf@lcs.mit.edu
+ * Statistics and support for slow printers by Rob Janssen, rob@knoware.nl
+ * "lp=" command line parameters added by Grant Guenther, grant@torque.net
+ * lp_read (Status readback) support added by Carsten Gross,
+ * carsten@sol.wohnheim.uni-ulm.de
+ * Support for parport by Philip Blundell <philb@gnu.org>
+ * Parport sharing hacking by Andrea Arcangeli
+ * Fixed kernel_(to/from)_user memory copy to check for errors
+ * by Riccardo Facchetti <fizban@tin.it>
+ * 22-JAN-1998 Added support for devfs Richard Gooch <rgooch@atnf.csiro.au>
+ * Redesigned interrupt handling for handle printers with buggy handshake
+ * by Andrea Arcangeli, 11 May 1998
+ * Full efficient handling of printer with buggy irq handshake (now I have
+ * understood the meaning of the strange handshake). This is done sending new
+ * characters if the interrupt is just happened, even if the printer say to
+ * be still BUSY. This is needed at least with Epson Stylus Color. To enable
+ * the new TRUST_IRQ mode read the `LP OPTIMIZATION' section below...
+ * Fixed the irq on the rising edge of the strobe case.
+ * Obsoleted the CAREFUL flag since a printer that doesn' t work with
+ * CAREFUL will block a bit after in lp_check_status().
+ * Andrea Arcangeli, 15 Oct 1998
+ * Obsoleted and removed all the lowlevel stuff implemented in the last
+ * month to use the IEEE1284 functions (that handle the _new_ compatibilty
+ * mode fine).
+ */
+
+/* This driver should, in theory, work with any parallel port that has an
+ * appropriate low-level driver; all I/O is done through the parport
+ * abstraction layer.
+ *
+ * If this driver is built into the kernel, you can configure it using the
+ * kernel command-line. For example:
+ *
+ * lp=parport1,none,parport2 (bind lp0 to parport1, disable lp1 and
+ * bind lp2 to parport2)
+ *
+ * lp=auto (assign lp devices to all ports that
+ * have printers attached, as determined
+ * by the IEEE-1284 autoprobe)
+ *
+ * lp=reset (reset the printer during
+ * initialisation)
+ *
+ * lp=off (disable the printer driver entirely)
+ *
+ * If the driver is loaded as a module, similar functionality is available
+ * using module parameters. The equivalent of the above commands would be:
+ *
+ * # insmod lp.o parport=1,none,2
+ *
+ * # insmod lp.o parport=auto
+ *
+ * # insmod lp.o reset=1
+ */
+
+/* COMPATIBILITY WITH OLD KERNELS
+ *
+ * Under Linux 2.0 and previous versions, lp devices were bound to ports at
+ * particular I/O addresses, as follows:
+ *
+ * lp0 0x3bc
+ * lp1 0x378
+ * lp2 0x278
+ *
+ * The new driver, by default, binds lp devices to parport devices as it
+ * finds them. This means that if you only have one port, it will be bound
+ * to lp0 regardless of its I/O address. If you need the old behaviour, you
+ * can force it using the parameters described above.
+ */
+
+/*
+ * The new interrupt handling code take care of the buggy handshake
+ * of some HP and Epson printer:
+ * ___
+ * ACK _______________ ___________
+ * |__|
+ * ____
+ * BUSY _________ _______
+ * |____________|
+ *
+ * I discovered this using the printer scanner that you can find at:
+ *
+ * ftp://e-mind.com/pub/linux/pscan/
+ *
+ * 11 May 98, Andrea Arcangeli
+ *
+ * My printer scanner run on an Epson Stylus Color show that such printer
+ * generates the irq on the _rising_ edge of the STROBE. Now lp handle
+ * this case fine too.
+ *
+ * 15 Oct 1998, Andrea Arcangeli
+ *
+ * The so called `buggy' handshake is really the well documented
+ * compatibility mode IEEE1284 handshake. They changed the well known
+ * Centronics handshake acking in the middle of busy expecting to not
+ * break drivers or legacy application, while they broken linux lp
+ * until I fixed it reverse engineering the protocol by hand some
+ * month ago...
+ *
+ * 14 Dec 1998, Andrea Arcangeli
+ *
+ * Copyright (C) 2000 by Tim Waugh (added LPSETTIMEOUT ioctl)
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/console.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+
+#include <linux/parport.h>
+#undef LP_STATS
+#include <linux/lp.h>
+
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* if you have more than 8 printers, remember to increase LP_NO */
+#define LP_NO 8
+
+/* ROUND_UP macro from fs/select.c */
+#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
+
+static struct lp_struct lp_table[LP_NO];
+
+static unsigned int lp_count = 0;
+static struct class_simple *lp_class;
+
+#ifdef CONFIG_LP_CONSOLE
+static struct parport *console_registered; // initially NULL
+#endif /* CONFIG_LP_CONSOLE */
+
+#undef LP_DEBUG
+
+/* Bits used to manage claiming the parport device */
+#define LP_PREEMPT_REQUEST 1
+#define LP_PARPORT_CLAIMED 2
+
+/* --- low-level port access ----------------------------------- */
+
+#define r_dtr(x) (parport_read_data(lp_table[(x)].dev->port))
+#define r_str(x) (parport_read_status(lp_table[(x)].dev->port))
+#define w_ctr(x,y) do { parport_write_control(lp_table[(x)].dev->port, (y)); } while (0)
+#define w_dtr(x,y) do { parport_write_data(lp_table[(x)].dev->port, (y)); } while (0)
+
+/* Claim the parport or block trying unless we've already claimed it */
+static void lp_claim_parport_or_block(struct lp_struct *this_lp)
+{
+ if (!test_and_set_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
+ parport_claim_or_block (this_lp->dev);
+ }
+}
+
+/* Claim the parport or block trying unless we've already claimed it */
+static void lp_release_parport(struct lp_struct *this_lp)
+{
+ if (test_and_clear_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
+ parport_release (this_lp->dev);
+ }
+}
+
+
+
+static int lp_preempt(void *handle)
+{
+ struct lp_struct *this_lp = (struct lp_struct *)handle;
+ set_bit(LP_PREEMPT_REQUEST, &this_lp->bits);
+ return (1);
+}
+
+
+/*
+ * Try to negotiate to a new mode; if unsuccessful negotiate to
+ * compatibility mode. Return the mode we ended up in.
+ */
+static int lp_negotiate(struct parport * port, int mode)
+{
+ if (parport_negotiate (port, mode) != 0) {
+ mode = IEEE1284_MODE_COMPAT;
+ parport_negotiate (port, mode);
+ }
+
+ return (mode);
+}
+
+static int lp_reset(int minor)
+{
+ int retval;
+ lp_claim_parport_or_block (&lp_table[minor]);
+ w_ctr(minor, LP_PSELECP);
+ udelay (LP_DELAY);
+ w_ctr(minor, LP_PSELECP | LP_PINITP);
+ retval = r_str(minor);
+ lp_release_parport (&lp_table[minor]);
+ return retval;
+}
+
+static void lp_error (int minor)
+{
+ DEFINE_WAIT(wait);
+ int polling;
+
+ if (LP_F(minor) & LP_ABORT)
+ return;
+
+ polling = lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE;
+ if (polling) lp_release_parport (&lp_table[minor]);
+ prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
+ schedule_timeout(LP_TIMEOUT_POLLED);
+ finish_wait(&lp_table[minor].waitq, &wait);
+ if (polling) lp_claim_parport_or_block (&lp_table[minor]);
+ else parport_yield_blocking (lp_table[minor].dev);
+}
+
+static int lp_check_status(int minor)
+{
+ int error = 0;
+ unsigned int last = lp_table[minor].last_error;
+ unsigned char status = r_str(minor);
+ if ((status & LP_PERRORP) && !(LP_F(minor) & LP_CAREFUL))
+ /* No error. */
+ last = 0;
+ else if ((status & LP_POUTPA)) {
+ if (last != LP_POUTPA) {
+ last = LP_POUTPA;
+ printk(KERN_INFO "lp%d out of paper\n", minor);
+ }
+ error = -ENOSPC;
+ } else if (!(status & LP_PSELECD)) {
+ if (last != LP_PSELECD) {
+ last = LP_PSELECD;
+ printk(KERN_INFO "lp%d off-line\n", minor);
+ }
+ error = -EIO;
+ } else if (!(status & LP_PERRORP)) {
+ if (last != LP_PERRORP) {
+ last = LP_PERRORP;
+ printk(KERN_INFO "lp%d on fire\n", minor);
+ }
+ error = -EIO;
+ } else {
+ last = 0; /* Come here if LP_CAREFUL is set and no
+ errors are reported. */
+ }
+
+ lp_table[minor].last_error = last;
+
+ if (last != 0)
+ lp_error(minor);
+
+ return error;
+}
+
+static int lp_wait_ready(int minor, int nonblock)
+{
+ int error = 0;
+
+ /* If we're not in compatibility mode, we're ready now! */
+ if (lp_table[minor].current_mode != IEEE1284_MODE_COMPAT) {
+ return (0);
+ }
+
+ do {
+ error = lp_check_status (minor);
+ if (error && (nonblock || (LP_F(minor) & LP_ABORT)))
+ break;
+ if (signal_pending (current)) {
+ error = -EINTR;
+ break;
+ }
+ } while (error);
+ return error;
+}
+
+static ssize_t lp_write(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode);
+ struct parport *port = lp_table[minor].dev->port;
+ char *kbuf = lp_table[minor].lp_buffer;
+ ssize_t retv = 0;
+ ssize_t written;
+ size_t copy_size = count;
+ int nonblock = ((file->f_flags & O_NONBLOCK) ||
+ (LP_F(minor) & LP_ABORT));
+
+#ifdef LP_STATS
+ if (jiffies-lp_table[minor].lastcall > LP_TIME(minor))
+ lp_table[minor].runchars = 0;
+
+ lp_table[minor].lastcall = jiffies;
+#endif
+
+ /* Need to copy the data from user-space. */
+ if (copy_size > LP_BUFFER_SIZE)
+ copy_size = LP_BUFFER_SIZE;
+
+ if (down_interruptible (&lp_table[minor].port_mutex))
+ return -EINTR;
+
+ if (copy_from_user (kbuf, buf, copy_size)) {
+ retv = -EFAULT;
+ goto out_unlock;
+ }
+
+ /* Claim Parport or sleep until it becomes available
+ */
+ lp_claim_parport_or_block (&lp_table[minor]);
+ /* Go to the proper mode. */
+ lp_table[minor].current_mode = lp_negotiate (port,
+ lp_table[minor].best_mode);
+
+ parport_set_timeout (lp_table[minor].dev,
+ (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
+ : lp_table[minor].timeout));
+
+ if ((retv = lp_wait_ready (minor, nonblock)) == 0)
+ do {
+ /* Write the data. */
+ written = parport_write (port, kbuf, copy_size);
+ if (written > 0) {
+ copy_size -= written;
+ count -= written;
+ buf += written;
+ retv += written;
+ }
+
+ if (signal_pending (current)) {
+ if (retv == 0)
+ retv = -EINTR;
+
+ break;
+ }
+
+ if (copy_size > 0) {
+ /* incomplete write -> check error ! */
+ int error;
+
+ parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_COMPAT);
+ lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+
+ error = lp_wait_ready (minor, nonblock);
+
+ if (error) {
+ if (retv == 0)
+ retv = error;
+ break;
+ } else if (nonblock) {
+ if (retv == 0)
+ retv = -EAGAIN;
+ break;
+ }
+
+ parport_yield_blocking (lp_table[minor].dev);
+ lp_table[minor].current_mode
+ = lp_negotiate (port,
+ lp_table[minor].best_mode);
+
+ } else if (need_resched())
+ schedule ();
+
+ if (count) {
+ copy_size = count;
+ if (copy_size > LP_BUFFER_SIZE)
+ copy_size = LP_BUFFER_SIZE;
+
+ if (copy_from_user(kbuf, buf, copy_size)) {
+ if (retv == 0)
+ retv = -EFAULT;
+ break;
+ }
+ }
+ } while (count > 0);
+
+ if (test_and_clear_bit(LP_PREEMPT_REQUEST,
+ &lp_table[minor].bits)) {
+ printk(KERN_INFO "lp%d releasing parport\n", minor);
+ parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_COMPAT);
+ lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+ lp_release_parport (&lp_table[minor]);
+ }
+out_unlock:
+ up (&lp_table[minor].port_mutex);
+
+ return retv;
+}
+
+#ifdef CONFIG_PARPORT_1284
+
+/* Status readback conforming to ieee1284 */
+static ssize_t lp_read(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ DEFINE_WAIT(wait);
+ unsigned int minor=iminor(file->f_dentry->d_inode);
+ struct parport *port = lp_table[minor].dev->port;
+ ssize_t retval = 0;
+ char *kbuf = lp_table[minor].lp_buffer;
+ int nonblock = ((file->f_flags & O_NONBLOCK) ||
+ (LP_F(minor) & LP_ABORT));
+
+ if (count > LP_BUFFER_SIZE)
+ count = LP_BUFFER_SIZE;
+
+ if (down_interruptible (&lp_table[minor].port_mutex))
+ return -EINTR;
+
+ lp_claim_parport_or_block (&lp_table[minor]);
+
+ parport_set_timeout (lp_table[minor].dev,
+ (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
+ : lp_table[minor].timeout));
+
+ parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+ if (parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_NIBBLE)) {
+ retval = -EIO;
+ goto out;
+ }
+
+ while (retval == 0) {
+ retval = parport_read (port, kbuf, count);
+
+ if (retval > 0)
+ break;
+
+ if (nonblock) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ /* Wait for data. */
+
+ if (lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE) {
+ parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_COMPAT);
+ lp_error (minor);
+ if (parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_NIBBLE)) {
+ retval = -EIO;
+ goto out;
+ }
+ } else {
+ prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
+ schedule_timeout(LP_TIMEOUT_POLLED);
+ finish_wait(&lp_table[minor].waitq, &wait);
+ }
+
+ if (signal_pending (current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ cond_resched ();
+ }
+ parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+ out:
+ lp_release_parport (&lp_table[minor]);
+
+ if (retval > 0 && copy_to_user (buf, kbuf, retval))
+ retval = -EFAULT;
+
+ up (&lp_table[minor].port_mutex);
+
+ return retval;
+}
+
+#endif /* IEEE 1284 support */
+
+static int lp_open(struct inode * inode, struct file * file)
+{
+ unsigned int minor = iminor(inode);
+
+ if (minor >= LP_NO)
+ return -ENXIO;
+ if ((LP_F(minor) & LP_EXIST) == 0)
+ return -ENXIO;
+ if (test_and_set_bit(LP_BUSY_BIT_POS, &LP_F(minor)))
+ return -EBUSY;
+
+ /* If ABORTOPEN is set and the printer is offline or out of paper,
+ we may still want to open it to perform ioctl()s. Therefore we
+ have commandeered O_NONBLOCK, even though it is being used in
+ a non-standard manner. This is strictly a Linux hack, and
+ should most likely only ever be used by the tunelp application. */
+ if ((LP_F(minor) & LP_ABORTOPEN) && !(file->f_flags & O_NONBLOCK)) {
+ int status;
+ lp_claim_parport_or_block (&lp_table[minor]);
+ status = r_str(minor);
+ lp_release_parport (&lp_table[minor]);
+ if (status & LP_POUTPA) {
+ printk(KERN_INFO "lp%d out of paper\n", minor);
+ LP_F(minor) &= ~LP_BUSY;
+ return -ENOSPC;
+ } else if (!(status & LP_PSELECD)) {
+ printk(KERN_INFO "lp%d off-line\n", minor);
+ LP_F(minor) &= ~LP_BUSY;
+ return -EIO;
+ } else if (!(status & LP_PERRORP)) {
+ printk(KERN_ERR "lp%d printer error\n", minor);
+ LP_F(minor) &= ~LP_BUSY;
+ return -EIO;
+ }
+ }
+ lp_table[minor].lp_buffer = (char *) kmalloc(LP_BUFFER_SIZE, GFP_KERNEL);
+ if (!lp_table[minor].lp_buffer) {
+ LP_F(minor) &= ~LP_BUSY;
+ return -ENOMEM;
+ }
+ /* Determine if the peripheral supports ECP mode */
+ lp_claim_parport_or_block (&lp_table[minor]);
+ if ( (lp_table[minor].dev->port->modes & PARPORT_MODE_ECP) &&
+ !parport_negotiate (lp_table[minor].dev->port,
+ IEEE1284_MODE_ECP)) {
+ printk (KERN_INFO "lp%d: ECP mode\n", minor);
+ lp_table[minor].best_mode = IEEE1284_MODE_ECP;
+ } else {
+ lp_table[minor].best_mode = IEEE1284_MODE_COMPAT;
+ }
+ /* Leave peripheral in compatibility mode */
+ parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+ lp_release_parport (&lp_table[minor]);
+ lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+ return 0;
+}
+
+static int lp_release(struct inode * inode, struct file * file)
+{
+ unsigned int minor = iminor(inode);
+
+ lp_claim_parport_or_block (&lp_table[minor]);
+ parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+ lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+ lp_release_parport (&lp_table[minor]);
+ kfree(lp_table[minor].lp_buffer);
+ lp_table[minor].lp_buffer = NULL;
+ LP_F(minor) &= ~LP_BUSY;
+ return 0;
+}
+
+static int lp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int minor = iminor(inode);
+ int status;
+ int retval = 0;
+ void __user *argp = (void __user *)arg;
+
+#ifdef LP_DEBUG
+ printk(KERN_DEBUG "lp%d ioctl, cmd: 0x%x, arg: 0x%lx\n", minor, cmd, arg);
+#endif
+ if (minor >= LP_NO)
+ return -ENODEV;
+ if ((LP_F(minor) & LP_EXIST) == 0)
+ return -ENODEV;
+ switch ( cmd ) {
+ struct timeval par_timeout;
+ long to_jiffies;
+
+ case LPTIME:
+ LP_TIME(minor) = arg * HZ/100;
+ break;
+ case LPCHAR:
+ LP_CHAR(minor) = arg;
+ break;
+ case LPABORT:
+ if (arg)
+ LP_F(minor) |= LP_ABORT;
+ else
+ LP_F(minor) &= ~LP_ABORT;
+ break;
+ case LPABORTOPEN:
+ if (arg)
+ LP_F(minor) |= LP_ABORTOPEN;
+ else
+ LP_F(minor) &= ~LP_ABORTOPEN;
+ break;
+ case LPCAREFUL:
+ if (arg)
+ LP_F(minor) |= LP_CAREFUL;
+ else
+ LP_F(minor) &= ~LP_CAREFUL;
+ break;
+ case LPWAIT:
+ LP_WAIT(minor) = arg;
+ break;
+ case LPSETIRQ:
+ return -EINVAL;
+ break;
+ case LPGETIRQ:
+ if (copy_to_user(argp, &LP_IRQ(minor),
+ sizeof(int)))
+ return -EFAULT;
+ break;
+ case LPGETSTATUS:
+ lp_claim_parport_or_block (&lp_table[minor]);
+ status = r_str(minor);
+ lp_release_parport (&lp_table[minor]);
+
+ if (copy_to_user(argp, &status, sizeof(int)))
+ return -EFAULT;
+ break;
+ case LPRESET:
+ lp_reset(minor);
+ break;
+#ifdef LP_STATS
+ case LPGETSTATS:
+ if (copy_to_user(argp, &LP_STAT(minor),
+ sizeof(struct lp_stats)))
+ return -EFAULT;
+ if (capable(CAP_SYS_ADMIN))
+ memset(&LP_STAT(minor), 0,
+ sizeof(struct lp_stats));
+ break;
+#endif
+ case LPGETFLAGS:
+ status = LP_F(minor);
+ if (copy_to_user(argp, &status, sizeof(int)))
+ return -EFAULT;
+ break;
+
+ case LPSETTIMEOUT:
+ if (copy_from_user (&par_timeout, argp,
+ sizeof (struct timeval))) {
+ return -EFAULT;
+ }
+ /* Convert to jiffies, place in lp_table */
+ if ((par_timeout.tv_sec < 0) ||
+ (par_timeout.tv_usec < 0)) {
+ return -EINVAL;
+ }
+ to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
+ to_jiffies += par_timeout.tv_sec * (long) HZ;
+ if (to_jiffies <= 0) {
+ return -EINVAL;
+ }
+ lp_table[minor].timeout = to_jiffies;
+ break;
+
+ default:
+ retval = -EINVAL;
+ }
+ return retval;
+}
+
+static struct file_operations lp_fops = {
+ .owner = THIS_MODULE,
+ .write = lp_write,
+ .ioctl = lp_ioctl,
+ .open = lp_open,
+ .release = lp_release,
+#ifdef CONFIG_PARPORT_1284
+ .read = lp_read,
+#endif
+};
+
+/* --- support for console on the line printer ----------------- */
+
+#ifdef CONFIG_LP_CONSOLE
+
+#define CONSOLE_LP 0
+
+/* If the printer is out of paper, we can either lose the messages or
+ * stall until the printer is happy again. Define CONSOLE_LP_STRICT
+ * non-zero to get the latter behaviour. */
+#define CONSOLE_LP_STRICT 1
+
+/* The console must be locked when we get here. */
+
+static void lp_console_write (struct console *co, const char *s,
+ unsigned count)
+{
+ struct pardevice *dev = lp_table[CONSOLE_LP].dev;
+ struct parport *port = dev->port;
+ ssize_t written;
+
+ if (parport_claim (dev))
+ /* Nothing we can do. */
+ return;
+
+ parport_set_timeout (dev, 0);
+
+ /* Go to compatibility mode. */
+ parport_negotiate (port, IEEE1284_MODE_COMPAT);
+
+ do {
+ /* Write the data, converting LF->CRLF as we go. */
+ ssize_t canwrite = count;
+ char *lf = memchr (s, '\n', count);
+ if (lf)
+ canwrite = lf - s;
+
+ if (canwrite > 0) {
+ written = parport_write (port, s, canwrite);
+
+ if (written <= 0)
+ continue;
+
+ s += written;
+ count -= written;
+ canwrite -= written;
+ }
+
+ if (lf && canwrite <= 0) {
+ const char *crlf = "\r\n";
+ int i = 2;
+
+ /* Dodge the original '\n', and put '\r\n' instead. */
+ s++;
+ count--;
+ do {
+ written = parport_write (port, crlf, i);
+ if (written > 0)
+ i -= written, crlf += written;
+ } while (i > 0 && (CONSOLE_LP_STRICT || written > 0));
+ }
+ } while (count > 0 && (CONSOLE_LP_STRICT || written > 0));
+
+ parport_release (dev);
+}
+
+static struct console lpcons = {
+ .name = "lp",
+ .write = lp_console_write,
+ .flags = CON_PRINTBUFFER,
+};
+
+#endif /* console on line printer */
+
+/* --- initialisation code ------------------------------------- */
+
+static int parport_nr[LP_NO] = { [0 ... LP_NO-1] = LP_PARPORT_UNSPEC };
+static char *parport[LP_NO] = { NULL, };
+static int reset = 0;
+
+module_param_array(parport, charp, NULL, 0);
+module_param(reset, bool, 0);
+
+#ifndef MODULE
+static int __init lp_setup (char *str)
+{
+ static int parport_ptr; // initially zero
+ int x;
+
+ if (get_option (&str, &x)) {
+ if (x == 0) {
+ /* disable driver on "lp=" or "lp=0" */
+ parport_nr[0] = LP_PARPORT_OFF;
+ } else {
+ printk(KERN_WARNING "warning: 'lp=0x%x' is deprecated, ignored\n", x);
+ return 0;
+ }
+ } else if (!strncmp(str, "parport", 7)) {
+ int n = simple_strtoul(str+7, NULL, 10);
+ if (parport_ptr < LP_NO)
+ parport_nr[parport_ptr++] = n;
+ else
+ printk(KERN_INFO "lp: too many ports, %s ignored.\n",
+ str);
+ } else if (!strcmp(str, "auto")) {
+ parport_nr[0] = LP_PARPORT_AUTO;
+ } else if (!strcmp(str, "none")) {
+ parport_nr[parport_ptr++] = LP_PARPORT_NONE;
+ } else if (!strcmp(str, "reset")) {
+ reset = 1;
+ }
+ return 1;
+}
+#endif
+
+static int lp_register(int nr, struct parport *port)
+{
+ lp_table[nr].dev = parport_register_device(port, "lp",
+ lp_preempt, NULL, NULL, 0,
+ (void *) &lp_table[nr]);
+ if (lp_table[nr].dev == NULL)
+ return 1;
+ lp_table[nr].flags |= LP_EXIST;
+
+ if (reset)
+ lp_reset(nr);
+
+ class_simple_device_add(lp_class, MKDEV(LP_MAJOR, nr), NULL,
+ "lp%d", nr);
+ devfs_mk_cdev(MKDEV(LP_MAJOR, nr), S_IFCHR | S_IRUGO | S_IWUGO,
+ "printers/%d", nr);
+
+ printk(KERN_INFO "lp%d: using %s (%s).\n", nr, port->name,
+ (port->irq == PARPORT_IRQ_NONE)?"polling":"interrupt-driven");
+
+#ifdef CONFIG_LP_CONSOLE
+ if (!nr) {
+ if (port->modes & PARPORT_MODE_SAFEININT) {
+ register_console (&lpcons);
+ console_registered = port;
+ printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);
+ } else
+ printk (KERN_ERR "lp%d: cannot run console on %s\n",
+ CONSOLE_LP, port->name);
+ }
+#endif
+
+ return 0;
+}
+
+static void lp_attach (struct parport *port)
+{
+ unsigned int i;
+
+ switch (parport_nr[0])
+ {
+ case LP_PARPORT_UNSPEC:
+ case LP_PARPORT_AUTO:
+ if (parport_nr[0] == LP_PARPORT_AUTO &&
+ port->probe_info[0].class != PARPORT_CLASS_PRINTER)
+ return;
+ if (lp_count == LP_NO) {
+ printk(KERN_INFO "lp: ignoring parallel port (max. %d)\n",LP_NO);
+ return;
+ }
+ if (!lp_register(lp_count, port))
+ lp_count++;
+ break;
+
+ default:
+ for (i = 0; i < LP_NO; i++) {
+ if (port->number == parport_nr[i]) {
+ if (!lp_register(i, port))
+ lp_count++;
+ break;
+ }
+ }
+ break;
+ }
+}
+
+static void lp_detach (struct parport *port)
+{
+ /* Write this some day. */
+#ifdef CONFIG_LP_CONSOLE
+ if (console_registered == port) {
+ unregister_console (&lpcons);
+ console_registered = NULL;
+ }
+#endif /* CONFIG_LP_CONSOLE */
+}
+
+static struct parport_driver lp_driver = {
+ .name = "lp",
+ .attach = lp_attach,
+ .detach = lp_detach,
+};
+
+static int __init lp_init (void)
+{
+ int i, err = 0;
+
+ if (parport_nr[0] == LP_PARPORT_OFF)
+ return 0;
+
+ for (i = 0; i < LP_NO; i++) {
+ lp_table[i].dev = NULL;
+ lp_table[i].flags = 0;
+ lp_table[i].chars = LP_INIT_CHAR;
+ lp_table[i].time = LP_INIT_TIME;
+ lp_table[i].wait = LP_INIT_WAIT;
+ lp_table[i].lp_buffer = NULL;
+#ifdef LP_STATS
+ lp_table[i].lastcall = 0;
+ lp_table[i].runchars = 0;
+ memset (&lp_table[i].stats, 0, sizeof (struct lp_stats));
+#endif
+ lp_table[i].last_error = 0;
+ init_waitqueue_head (&lp_table[i].waitq);
+ init_waitqueue_head (&lp_table[i].dataq);
+ init_MUTEX (&lp_table[i].port_mutex);
+ lp_table[i].timeout = 10 * HZ;
+ }
+
+ if (register_chrdev (LP_MAJOR, "lp", &lp_fops)) {
+ printk (KERN_ERR "lp: unable to get major %d\n", LP_MAJOR);
+ return -EIO;
+ }
+
+ devfs_mk_dir("printers");
+ lp_class = class_simple_create(THIS_MODULE, "printer");
+ if (IS_ERR(lp_class)) {
+ err = PTR_ERR(lp_class);
+ goto out_devfs;
+ }
+
+ if (parport_register_driver (&lp_driver)) {
+ printk (KERN_ERR "lp: unable to register with parport\n");
+ err = -EIO;
+ goto out_class;
+ }
+
+ if (!lp_count) {
+ printk (KERN_INFO "lp: driver loaded but no devices found\n");
+#ifndef CONFIG_PARPORT_1284
+ if (parport_nr[0] == LP_PARPORT_AUTO)
+ printk (KERN_INFO "lp: (is IEEE 1284 support enabled?)\n");
+#endif
+ }
+
+ return 0;
+
+out_class:
+ class_simple_destroy(lp_class);
+out_devfs:
+ devfs_remove("printers");
+ unregister_chrdev(LP_MAJOR, "lp");
+ return err;
+}
+
+static int __init lp_init_module (void)
+{
+ if (parport[0]) {
+ /* The user gave some parameters. Let's see what they were. */
+ if (!strncmp(parport[0], "auto", 4))
+ parport_nr[0] = LP_PARPORT_AUTO;
+ else {
+ int n;
+ for (n = 0; n < LP_NO && parport[n]; n++) {
+ if (!strncmp(parport[n], "none", 4))
+ parport_nr[n] = LP_PARPORT_NONE;
+ else {
+ char *ep;
+ unsigned long r = simple_strtoul(parport[n], &ep, 0);
+ if (ep != parport[n])
+ parport_nr[n] = r;
+ else {
+ printk(KERN_ERR "lp: bad port specifier `%s'\n", parport[n]);
+ return -ENODEV;
+ }
+ }
+ }
+ }
+ }
+
+ return lp_init();
+}
+
+static void lp_cleanup_module (void)
+{
+ unsigned int offset;
+
+ parport_unregister_driver (&lp_driver);
+
+#ifdef CONFIG_LP_CONSOLE
+ unregister_console (&lpcons);
+#endif
+
+ unregister_chrdev(LP_MAJOR, "lp");
+ for (offset = 0; offset < LP_NO; offset++) {
+ if (lp_table[offset].dev == NULL)
+ continue;
+ parport_unregister_device(lp_table[offset].dev);
+ devfs_remove("printers/%d", offset);
+ class_simple_device_remove(MKDEV(LP_MAJOR, offset));
+ }
+ devfs_remove("printers");
+ class_simple_destroy(lp_class);
+}
+
+__setup("lp=", lp_setup);
+module_init(lp_init_module);
+module_exit(lp_cleanup_module);
+
+MODULE_ALIAS_CHARDEV_MAJOR(LP_MAJOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
new file mode 100644
index 000000000000..947cb3cef816
--- /dev/null
+++ b/drivers/char/mem.c
@@ -0,0 +1,880 @@
+/*
+ * linux/drivers/char/mem.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Added devfs support.
+ * Jan-11-1998, C. Scott Ananian <cananian@alumni.princeton.edu>
+ * Shared /dev/zero mmaping support, Feb 2000, Kanoj Sarcar <kanoj@sgi.com>
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mman.h>
+#include <linux/random.h>
+#include <linux/init.h>
+#include <linux/raw.h>
+#include <linux/tty.h>
+#include <linux/capability.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ptrace.h>
+#include <linux/device.h>
+#include <linux/backing-dev.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_IA64
+# include <linux/efi.h>
+#endif
+
+#if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_CHAR)
+extern void tapechar_init(void);
+#endif
+
+/*
+ * Architectures vary in how they handle caching for addresses
+ * outside of main memory.
+ *
+ */
+static inline int uncached_access(struct file *file, unsigned long addr)
+{
+#if defined(__i386__)
+ /*
+ * On the PPro and successors, the MTRRs are used to set
+ * memory types for physical addresses outside main memory,
+ * so blindly setting PCD or PWT on those pages is wrong.
+ * For Pentiums and earlier, the surround logic should disable
+ * caching for the high addresses through the KEN pin, but
+ * we maintain the tradition of paranoia in this code.
+ */
+ if (file->f_flags & O_SYNC)
+ return 1;
+ return !( test_bit(X86_FEATURE_MTRR, boot_cpu_data.x86_capability) ||
+ test_bit(X86_FEATURE_K6_MTRR, boot_cpu_data.x86_capability) ||
+ test_bit(X86_FEATURE_CYRIX_ARR, boot_cpu_data.x86_capability) ||
+ test_bit(X86_FEATURE_CENTAUR_MCR, boot_cpu_data.x86_capability) )
+ && addr >= __pa(high_memory);
+#elif defined(__x86_64__)
+ /*
+ * This is broken because it can generate memory type aliases,
+ * which can cause cache corruptions
+ * But it is only available for root and we have to be bug-to-bug
+ * compatible with i386.
+ */
+ if (file->f_flags & O_SYNC)
+ return 1;
+ /* same behaviour as i386. PAT always set to cached and MTRRs control the
+ caching behaviour.
+ Hopefully a full PAT implementation will fix that soon. */
+ return 0;
+#elif defined(CONFIG_IA64)
+ /*
+ * On ia64, we ignore O_SYNC because we cannot tolerate memory attribute aliases.
+ */
+ return !(efi_mem_attributes(addr) & EFI_MEMORY_WB);
+#else
+ /*
+ * Accessing memory above the top the kernel knows about or through a file pointer
+ * that was marked O_SYNC will be done non-cached.
+ */
+ if (file->f_flags & O_SYNC)
+ return 1;
+ return addr >= __pa(high_memory);
+#endif
+}
+
+#ifndef ARCH_HAS_VALID_PHYS_ADDR_RANGE
+static inline int valid_phys_addr_range(unsigned long addr, size_t *count)
+{
+ unsigned long end_mem;
+
+ end_mem = __pa(high_memory);
+ if (addr >= end_mem)
+ return 0;
+
+ if (*count > end_mem - addr)
+ *count = end_mem - addr;
+
+ return 1;
+}
+#endif
+
+/*
+ * This funcion reads the *physical* memory. The f_pos points directly to the
+ * memory location.
+ */
+static ssize_t read_mem(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ ssize_t read, sz;
+ char *ptr;
+
+ if (!valid_phys_addr_range(p, &count))
+ return -EFAULT;
+ read = 0;
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+ /* we don't have page 0 mapped on sparc and m68k.. */
+ if (p < PAGE_SIZE) {
+ sz = PAGE_SIZE - p;
+ if (sz > count)
+ sz = count;
+ if (sz > 0) {
+ if (clear_user(buf, sz))
+ return -EFAULT;
+ buf += sz;
+ p += sz;
+ count -= sz;
+ read += sz;
+ }
+ }
+#endif
+
+ while (count > 0) {
+ /*
+ * Handle first page in case it's not aligned
+ */
+ if (-p & (PAGE_SIZE - 1))
+ sz = -p & (PAGE_SIZE - 1);
+ else
+ sz = PAGE_SIZE;
+
+ sz = min_t(unsigned long, sz, count);
+
+ /*
+ * On ia64 if a page has been mapped somewhere as
+ * uncached, then it must also be accessed uncached
+ * by the kernel or data corruption may occur
+ */
+ ptr = xlate_dev_mem_ptr(p);
+
+ if (copy_to_user(buf, ptr, sz))
+ return -EFAULT;
+ buf += sz;
+ p += sz;
+ count -= sz;
+ read += sz;
+ }
+
+ *ppos += read;
+ return read;
+}
+
+static ssize_t write_mem(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ ssize_t written, sz;
+ unsigned long copied;
+ void *ptr;
+
+ if (!valid_phys_addr_range(p, &count))
+ return -EFAULT;
+
+ written = 0;
+
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+ /* we don't have page 0 mapped on sparc and m68k.. */
+ if (p < PAGE_SIZE) {
+ unsigned long sz = PAGE_SIZE - p;
+ if (sz > count)
+ sz = count;
+ /* Hmm. Do something? */
+ buf += sz;
+ p += sz;
+ count -= sz;
+ written += sz;
+ }
+#endif
+
+ while (count > 0) {
+ /*
+ * Handle first page in case it's not aligned
+ */
+ if (-p & (PAGE_SIZE - 1))
+ sz = -p & (PAGE_SIZE - 1);
+ else
+ sz = PAGE_SIZE;
+
+ sz = min_t(unsigned long, sz, count);
+
+ /*
+ * On ia64 if a page has been mapped somewhere as
+ * uncached, then it must also be accessed uncached
+ * by the kernel or data corruption may occur
+ */
+ ptr = xlate_dev_mem_ptr(p);
+
+ copied = copy_from_user(ptr, buf, sz);
+ if (copied) {
+ ssize_t ret;
+
+ ret = written + (sz - copied);
+ if (ret)
+ return ret;
+ return -EFAULT;
+ }
+ buf += sz;
+ p += sz;
+ count -= sz;
+ written += sz;
+ }
+
+ *ppos += written;
+ return written;
+}
+
+static int mmap_mem(struct file * file, struct vm_area_struct * vma)
+{
+#if defined(__HAVE_PHYS_MEM_ACCESS_PROT)
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ vma->vm_page_prot = phys_mem_access_prot(file, offset,
+ vma->vm_end - vma->vm_start,
+ vma->vm_page_prot);
+#elif defined(pgprot_noncached)
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ int uncached;
+
+ uncached = uncached_access(file, offset);
+ if (uncached)
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+#endif
+
+ /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
+ if (remap_pfn_range(vma,
+ vma->vm_start,
+ vma->vm_pgoff,
+ vma->vm_end-vma->vm_start,
+ vma->vm_page_prot))
+ return -EAGAIN;
+ return 0;
+}
+
+static int mmap_kmem(struct file * file, struct vm_area_struct * vma)
+{
+ unsigned long long val;
+ /*
+ * RED-PEN: on some architectures there is more mapped memory
+ * than available in mem_map which pfn_valid checks
+ * for. Perhaps should add a new macro here.
+ *
+ * RED-PEN: vmalloc is not supported right now.
+ */
+ if (!pfn_valid(vma->vm_pgoff))
+ return -EIO;
+ val = (u64)vma->vm_pgoff << PAGE_SHIFT;
+ vma->vm_pgoff = __pa(val) >> PAGE_SHIFT;
+ return mmap_mem(file, vma);
+}
+
+extern long vread(char *buf, char *addr, unsigned long count);
+extern long vwrite(char *buf, char *addr, unsigned long count);
+
+/*
+ * This function reads the *virtual* memory as seen by the kernel.
+ */
+static ssize_t read_kmem(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ ssize_t low_count, read, sz;
+ char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */
+
+ read = 0;
+ if (p < (unsigned long) high_memory) {
+ low_count = count;
+ if (count > (unsigned long) high_memory - p)
+ low_count = (unsigned long) high_memory - p;
+
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+ /* we don't have page 0 mapped on sparc and m68k.. */
+ if (p < PAGE_SIZE && low_count > 0) {
+ size_t tmp = PAGE_SIZE - p;
+ if (tmp > low_count) tmp = low_count;
+ if (clear_user(buf, tmp))
+ return -EFAULT;
+ buf += tmp;
+ p += tmp;
+ read += tmp;
+ low_count -= tmp;
+ count -= tmp;
+ }
+#endif
+ while (low_count > 0) {
+ /*
+ * Handle first page in case it's not aligned
+ */
+ if (-p & (PAGE_SIZE - 1))
+ sz = -p & (PAGE_SIZE - 1);
+ else
+ sz = PAGE_SIZE;
+
+ sz = min_t(unsigned long, sz, low_count);
+
+ /*
+ * On ia64 if a page has been mapped somewhere as
+ * uncached, then it must also be accessed uncached
+ * by the kernel or data corruption may occur
+ */
+ kbuf = xlate_dev_kmem_ptr((char *)p);
+
+ if (copy_to_user(buf, kbuf, sz))
+ return -EFAULT;
+ buf += sz;
+ p += sz;
+ read += sz;
+ low_count -= sz;
+ count -= sz;
+ }
+ }
+
+ if (count > 0) {
+ kbuf = (char *)__get_free_page(GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+ while (count > 0) {
+ int len = count;
+
+ if (len > PAGE_SIZE)
+ len = PAGE_SIZE;
+ len = vread(kbuf, (char *)p, len);
+ if (!len)
+ break;
+ if (copy_to_user(buf, kbuf, len)) {
+ free_page((unsigned long)kbuf);
+ return -EFAULT;
+ }
+ count -= len;
+ buf += len;
+ read += len;
+ p += len;
+ }
+ free_page((unsigned long)kbuf);
+ }
+ *ppos = p;
+ return read;
+}
+
+
+static inline ssize_t
+do_write_kmem(void *p, unsigned long realp, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t written, sz;
+ unsigned long copied;
+
+ written = 0;
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+ /* we don't have page 0 mapped on sparc and m68k.. */
+ if (realp < PAGE_SIZE) {
+ unsigned long sz = PAGE_SIZE - realp;
+ if (sz > count)
+ sz = count;
+ /* Hmm. Do something? */
+ buf += sz;
+ p += sz;
+ realp += sz;
+ count -= sz;
+ written += sz;
+ }
+#endif
+
+ while (count > 0) {
+ char *ptr;
+ /*
+ * Handle first page in case it's not aligned
+ */
+ if (-realp & (PAGE_SIZE - 1))
+ sz = -realp & (PAGE_SIZE - 1);
+ else
+ sz = PAGE_SIZE;
+
+ sz = min_t(unsigned long, sz, count);
+
+ /*
+ * On ia64 if a page has been mapped somewhere as
+ * uncached, then it must also be accessed uncached
+ * by the kernel or data corruption may occur
+ */
+ ptr = xlate_dev_kmem_ptr(p);
+
+ copied = copy_from_user(ptr, buf, sz);
+ if (copied) {
+ ssize_t ret;
+
+ ret = written + (sz - copied);
+ if (ret)
+ return ret;
+ return -EFAULT;
+ }
+ buf += sz;
+ p += sz;
+ realp += sz;
+ count -= sz;
+ written += sz;
+ }
+
+ *ppos += written;
+ return written;
+}
+
+
+/*
+ * This function writes to the *virtual* memory as seen by the kernel.
+ */
+static ssize_t write_kmem(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ ssize_t wrote = 0;
+ ssize_t virtr = 0;
+ ssize_t written;
+ char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */
+
+ if (p < (unsigned long) high_memory) {
+
+ wrote = count;
+ if (count > (unsigned long) high_memory - p)
+ wrote = (unsigned long) high_memory - p;
+
+ written = do_write_kmem((void*)p, p, buf, wrote, ppos);
+ if (written != wrote)
+ return written;
+ wrote = written;
+ p += wrote;
+ buf += wrote;
+ count -= wrote;
+ }
+
+ if (count > 0) {
+ kbuf = (char *)__get_free_page(GFP_KERNEL);
+ if (!kbuf)
+ return wrote ? wrote : -ENOMEM;
+ while (count > 0) {
+ int len = count;
+
+ if (len > PAGE_SIZE)
+ len = PAGE_SIZE;
+ if (len) {
+ written = copy_from_user(kbuf, buf, len);
+ if (written) {
+ ssize_t ret;
+
+ free_page((unsigned long)kbuf);
+ ret = wrote + virtr + (len - written);
+ return ret ? ret : -EFAULT;
+ }
+ }
+ len = vwrite(kbuf, (char *)p, len);
+ count -= len;
+ buf += len;
+ virtr += len;
+ p += len;
+ }
+ free_page((unsigned long)kbuf);
+ }
+
+ *ppos = p;
+ return virtr + wrote;
+}
+
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+static ssize_t read_port(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long i = *ppos;
+ char __user *tmp = buf;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+ while (count-- > 0 && i < 65536) {
+ if (__put_user(inb(i),tmp) < 0)
+ return -EFAULT;
+ i++;
+ tmp++;
+ }
+ *ppos = i;
+ return tmp-buf;
+}
+
+static ssize_t write_port(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long i = *ppos;
+ const char __user * tmp = buf;
+
+ if (!access_ok(VERIFY_READ,buf,count))
+ return -EFAULT;
+ while (count-- > 0 && i < 65536) {
+ char c;
+ if (__get_user(c, tmp))
+ return -EFAULT;
+ outb(c,i);
+ i++;
+ tmp++;
+ }
+ *ppos = i;
+ return tmp-buf;
+}
+#endif
+
+static ssize_t read_null(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return 0;
+}
+
+static ssize_t write_null(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return count;
+}
+
+#ifdef CONFIG_MMU
+/*
+ * For fun, we are using the MMU for this.
+ */
+static inline size_t read_zero_pagealigned(char __user * buf, size_t size)
+{
+ struct mm_struct *mm;
+ struct vm_area_struct * vma;
+ unsigned long addr=(unsigned long)buf;
+
+ mm = current->mm;
+ /* Oops, this was forgotten before. -ben */
+ down_read(&mm->mmap_sem);
+
+ /* For private mappings, just map in zero pages. */
+ for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) {
+ unsigned long count;
+
+ if (vma->vm_start > addr || (vma->vm_flags & VM_WRITE) == 0)
+ goto out_up;
+ if (vma->vm_flags & (VM_SHARED | VM_HUGETLB))
+ break;
+ count = vma->vm_end - addr;
+ if (count > size)
+ count = size;
+
+ zap_page_range(vma, addr, count, NULL);
+ zeromap_page_range(vma, addr, count, PAGE_COPY);
+
+ size -= count;
+ buf += count;
+ addr += count;
+ if (size == 0)
+ goto out_up;
+ }
+
+ up_read(&mm->mmap_sem);
+
+ /* The shared case is hard. Let's do the conventional zeroing. */
+ do {
+ unsigned long unwritten = clear_user(buf, PAGE_SIZE);
+ if (unwritten)
+ return size + unwritten - PAGE_SIZE;
+ cond_resched();
+ buf += PAGE_SIZE;
+ size -= PAGE_SIZE;
+ } while (size);
+
+ return size;
+out_up:
+ up_read(&mm->mmap_sem);
+ return size;
+}
+
+static ssize_t read_zero(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long left, unwritten, written = 0;
+
+ if (!count)
+ return 0;
+
+ if (!access_ok(VERIFY_WRITE, buf, count))
+ return -EFAULT;
+
+ left = count;
+
+ /* do we want to be clever? Arbitrary cut-off */
+ if (count >= PAGE_SIZE*4) {
+ unsigned long partial;
+
+ /* How much left of the page? */
+ partial = (PAGE_SIZE-1) & -(unsigned long) buf;
+ unwritten = clear_user(buf, partial);
+ written = partial - unwritten;
+ if (unwritten)
+ goto out;
+ left -= partial;
+ buf += partial;
+ unwritten = read_zero_pagealigned(buf, left & PAGE_MASK);
+ written += (left & PAGE_MASK) - unwritten;
+ if (unwritten)
+ goto out;
+ buf += left & PAGE_MASK;
+ left &= ~PAGE_MASK;
+ }
+ unwritten = clear_user(buf, left);
+ written += left - unwritten;
+out:
+ return written ? written : -EFAULT;
+}
+
+static int mmap_zero(struct file * file, struct vm_area_struct * vma)
+{
+ if (vma->vm_flags & VM_SHARED)
+ return shmem_zero_setup(vma);
+ if (zeromap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_page_prot))
+ return -EAGAIN;
+ return 0;
+}
+#else /* CONFIG_MMU */
+static ssize_t read_zero(struct file * file, char * buf,
+ size_t count, loff_t *ppos)
+{
+ size_t todo = count;
+
+ while (todo) {
+ size_t chunk = todo;
+
+ if (chunk > 4096)
+ chunk = 4096; /* Just for latency reasons */
+ if (clear_user(buf, chunk))
+ return -EFAULT;
+ buf += chunk;
+ todo -= chunk;
+ cond_resched();
+ }
+ return count;
+}
+
+static int mmap_zero(struct file * file, struct vm_area_struct * vma)
+{
+ return -ENOSYS;
+}
+#endif /* CONFIG_MMU */
+
+static ssize_t write_full(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return -ENOSPC;
+}
+
+/*
+ * Special lseek() function for /dev/null and /dev/zero. Most notably, you
+ * can fopen() both devices with "a" now. This was previously impossible.
+ * -- SRB.
+ */
+
+static loff_t null_lseek(struct file * file, loff_t offset, int orig)
+{
+ return file->f_pos = 0;
+}
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t memory_lseek(struct file * file, loff_t offset, int orig)
+{
+ loff_t ret;
+
+ down(&file->f_dentry->d_inode->i_sem);
+ switch (orig) {
+ case 0:
+ file->f_pos = offset;
+ ret = file->f_pos;
+ force_successful_syscall_return();
+ break;
+ case 1:
+ file->f_pos += offset;
+ ret = file->f_pos;
+ force_successful_syscall_return();
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ up(&file->f_dentry->d_inode->i_sem);
+ return ret;
+}
+
+static int open_port(struct inode * inode, struct file * filp)
+{
+ return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
+}
+
+#define zero_lseek null_lseek
+#define full_lseek null_lseek
+#define write_zero write_null
+#define read_full read_zero
+#define open_mem open_port
+#define open_kmem open_mem
+
+static struct file_operations mem_fops = {
+ .llseek = memory_lseek,
+ .read = read_mem,
+ .write = write_mem,
+ .mmap = mmap_mem,
+ .open = open_mem,
+};
+
+static struct file_operations kmem_fops = {
+ .llseek = memory_lseek,
+ .read = read_kmem,
+ .write = write_kmem,
+ .mmap = mmap_kmem,
+ .open = open_kmem,
+};
+
+static struct file_operations null_fops = {
+ .llseek = null_lseek,
+ .read = read_null,
+ .write = write_null,
+};
+
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+static struct file_operations port_fops = {
+ .llseek = memory_lseek,
+ .read = read_port,
+ .write = write_port,
+ .open = open_port,
+};
+#endif
+
+static struct file_operations zero_fops = {
+ .llseek = zero_lseek,
+ .read = read_zero,
+ .write = write_zero,
+ .mmap = mmap_zero,
+};
+
+static struct backing_dev_info zero_bdi = {
+ .capabilities = BDI_CAP_MAP_COPY,
+};
+
+static struct file_operations full_fops = {
+ .llseek = full_lseek,
+ .read = read_full,
+ .write = write_full,
+};
+
+static ssize_t kmsg_write(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ char *tmp;
+ int ret;
+
+ tmp = kmalloc(count + 1, GFP_KERNEL);
+ if (tmp == NULL)
+ return -ENOMEM;
+ ret = -EFAULT;
+ if (!copy_from_user(tmp, buf, count)) {
+ tmp[count] = 0;
+ ret = printk("%s", tmp);
+ }
+ kfree(tmp);
+ return ret;
+}
+
+static struct file_operations kmsg_fops = {
+ .write = kmsg_write,
+};
+
+static int memory_open(struct inode * inode, struct file * filp)
+{
+ switch (iminor(inode)) {
+ case 1:
+ filp->f_op = &mem_fops;
+ break;
+ case 2:
+ filp->f_op = &kmem_fops;
+ break;
+ case 3:
+ filp->f_op = &null_fops;
+ break;
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+ case 4:
+ filp->f_op = &port_fops;
+ break;
+#endif
+ case 5:
+ filp->f_mapping->backing_dev_info = &zero_bdi;
+ filp->f_op = &zero_fops;
+ break;
+ case 7:
+ filp->f_op = &full_fops;
+ break;
+ case 8:
+ filp->f_op = &random_fops;
+ break;
+ case 9:
+ filp->f_op = &urandom_fops;
+ break;
+ case 11:
+ filp->f_op = &kmsg_fops;
+ break;
+ default:
+ return -ENXIO;
+ }
+ if (filp->f_op && filp->f_op->open)
+ return filp->f_op->open(inode,filp);
+ return 0;
+}
+
+static struct file_operations memory_fops = {
+ .open = memory_open, /* just a selector for the real open */
+};
+
+static const struct {
+ unsigned int minor;
+ char *name;
+ umode_t mode;
+ struct file_operations *fops;
+} devlist[] = { /* list of minor devices */
+ {1, "mem", S_IRUSR | S_IWUSR | S_IRGRP, &mem_fops},
+ {2, "kmem", S_IRUSR | S_IWUSR | S_IRGRP, &kmem_fops},
+ {3, "null", S_IRUGO | S_IWUGO, &null_fops},
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+ {4, "port", S_IRUSR | S_IWUSR | S_IRGRP, &port_fops},
+#endif
+ {5, "zero", S_IRUGO | S_IWUGO, &zero_fops},
+ {7, "full", S_IRUGO | S_IWUGO, &full_fops},
+ {8, "random", S_IRUGO | S_IWUSR, &random_fops},
+ {9, "urandom", S_IRUGO | S_IWUSR, &urandom_fops},
+ {11,"kmsg", S_IRUGO | S_IWUSR, &kmsg_fops},
+};
+
+static struct class_simple *mem_class;
+
+static int __init chr_dev_init(void)
+{
+ int i;
+
+ if (register_chrdev(MEM_MAJOR,"mem",&memory_fops))
+ printk("unable to get major %d for memory devs\n", MEM_MAJOR);
+
+ mem_class = class_simple_create(THIS_MODULE, "mem");
+ for (i = 0; i < ARRAY_SIZE(devlist); i++) {
+ class_simple_device_add(mem_class,
+ MKDEV(MEM_MAJOR, devlist[i].minor),
+ NULL, devlist[i].name);
+ devfs_mk_cdev(MKDEV(MEM_MAJOR, devlist[i].minor),
+ S_IFCHR | devlist[i].mode, devlist[i].name);
+ }
+
+ return 0;
+}
+
+fs_initcall(chr_dev_init);
diff --git a/drivers/char/misc.c b/drivers/char/misc.c
new file mode 100644
index 000000000000..0937544762da
--- /dev/null
+++ b/drivers/char/misc.c
@@ -0,0 +1,331 @@
+/*
+ * linux/drivers/char/misc.c
+ *
+ * Generic misc open routine by Johan Myreen
+ *
+ * Based on code from Linus
+ *
+ * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's
+ * changes incorporated into 0.97pl4
+ * by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)
+ * See busmouse.c for particulars.
+ *
+ * Made things a lot mode modular - easy to compile in just one or two
+ * of the misc drivers, as they are now completely independent. Linus.
+ *
+ * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
+ *
+ * Fixed a failing symbol register to free the device registration
+ * Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96
+ *
+ * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96
+ *
+ * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96
+ *
+ * Handling of mouse minor numbers for kerneld:
+ * Idea by Jacques Gelinas <jack@solucorp.qc.ca>,
+ * adapted by Bjorn Ekwall <bj0rn@blox.se>
+ * corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Changes for kmod (from kerneld):
+ * Cyrus Durgin <cider@speakeasy.org>
+ *
+ * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au> 10-Jan-1998
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/kmod.h>
+
+/*
+ * Head entry for the doubly linked miscdevice list
+ */
+static LIST_HEAD(misc_list);
+static DECLARE_MUTEX(misc_sem);
+
+/*
+ * Assigned numbers, used for dynamic minors
+ */
+#define DYNAMIC_MINORS 64 /* like dynamic majors */
+static unsigned char misc_minors[DYNAMIC_MINORS / 8];
+
+extern int rtc_DP8570A_init(void);
+extern int rtc_MK48T08_init(void);
+extern int pmu_device_init(void);
+extern int tosh_init(void);
+extern int i8k_init(void);
+
+#ifdef CONFIG_PROC_FS
+static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ struct miscdevice *p;
+ loff_t off = 0;
+
+ down(&misc_sem);
+ list_for_each_entry(p, &misc_list, list) {
+ if (*pos == off++)
+ return p;
+ }
+ return NULL;
+}
+
+static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct list_head *n = ((struct miscdevice *)v)->list.next;
+
+ ++*pos;
+
+ return (n != &misc_list) ? list_entry(n, struct miscdevice, list)
+ : NULL;
+}
+
+static void misc_seq_stop(struct seq_file *seq, void *v)
+{
+ up(&misc_sem);
+}
+
+static int misc_seq_show(struct seq_file *seq, void *v)
+{
+ const struct miscdevice *p = v;
+
+ seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
+ return 0;
+}
+
+
+static struct seq_operations misc_seq_ops = {
+ .start = misc_seq_start,
+ .next = misc_seq_next,
+ .stop = misc_seq_stop,
+ .show = misc_seq_show,
+};
+
+static int misc_seq_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &misc_seq_ops);
+}
+
+static struct file_operations misc_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = misc_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+#endif
+
+static int misc_open(struct inode * inode, struct file * file)
+{
+ int minor = iminor(inode);
+ struct miscdevice *c;
+ int err = -ENODEV;
+ struct file_operations *old_fops, *new_fops = NULL;
+
+ down(&misc_sem);
+
+ list_for_each_entry(c, &misc_list, list) {
+ if (c->minor == minor) {
+ new_fops = fops_get(c->fops);
+ break;
+ }
+ }
+
+ if (!new_fops) {
+ up(&misc_sem);
+ request_module("char-major-%d-%d", MISC_MAJOR, minor);
+ down(&misc_sem);
+
+ list_for_each_entry(c, &misc_list, list) {
+ if (c->minor == minor) {
+ new_fops = fops_get(c->fops);
+ break;
+ }
+ }
+ if (!new_fops)
+ goto fail;
+ }
+
+ err = 0;
+ old_fops = file->f_op;
+ file->f_op = new_fops;
+ if (file->f_op->open) {
+ err=file->f_op->open(inode,file);
+ if (err) {
+ fops_put(file->f_op);
+ file->f_op = fops_get(old_fops);
+ }
+ }
+ fops_put(old_fops);
+fail:
+ up(&misc_sem);
+ return err;
+}
+
+/*
+ * TODO for 2.7:
+ * - add a struct class_device to struct miscdevice and make all usages of
+ * them dynamic.
+ */
+static struct class_simple *misc_class;
+
+static struct file_operations misc_fops = {
+ .owner = THIS_MODULE,
+ .open = misc_open,
+};
+
+
+/**
+ * misc_register - register a miscellaneous device
+ * @misc: device structure
+ *
+ * Register a miscellaneous device with the kernel. If the minor
+ * number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
+ * and placed in the minor field of the structure. For other cases
+ * the minor number requested is used.
+ *
+ * The structure passed is linked into the kernel and may not be
+ * destroyed until it has been unregistered.
+ *
+ * A zero is returned on success and a negative errno code for
+ * failure.
+ */
+
+int misc_register(struct miscdevice * misc)
+{
+ struct miscdevice *c;
+ dev_t dev;
+ int err;
+
+ down(&misc_sem);
+ list_for_each_entry(c, &misc_list, list) {
+ if (c->minor == misc->minor) {
+ up(&misc_sem);
+ return -EBUSY;
+ }
+ }
+
+ if (misc->minor == MISC_DYNAMIC_MINOR) {
+ int i = DYNAMIC_MINORS;
+ while (--i >= 0)
+ if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
+ break;
+ if (i<0) {
+ up(&misc_sem);
+ return -EBUSY;
+ }
+ misc->minor = i;
+ }
+
+ if (misc->minor < DYNAMIC_MINORS)
+ misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
+ if (misc->devfs_name[0] == '\0') {
+ snprintf(misc->devfs_name, sizeof(misc->devfs_name),
+ "misc/%s", misc->name);
+ }
+ dev = MKDEV(MISC_MAJOR, misc->minor);
+
+ misc->class = class_simple_device_add(misc_class, dev,
+ misc->dev, misc->name);
+ if (IS_ERR(misc->class)) {
+ err = PTR_ERR(misc->class);
+ goto out;
+ }
+
+ err = devfs_mk_cdev(dev, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP,
+ misc->devfs_name);
+ if (err) {
+ class_simple_device_remove(dev);
+ goto out;
+ }
+
+ /*
+ * Add it to the front, so that later devices can "override"
+ * earlier defaults
+ */
+ list_add(&misc->list, &misc_list);
+ out:
+ up(&misc_sem);
+ return err;
+}
+
+/**
+ * misc_deregister - unregister a miscellaneous device
+ * @misc: device to unregister
+ *
+ * Unregister a miscellaneous device that was previously
+ * successfully registered with misc_register(). Success
+ * is indicated by a zero return, a negative errno code
+ * indicates an error.
+ */
+
+int misc_deregister(struct miscdevice * misc)
+{
+ int i = misc->minor;
+
+ if (list_empty(&misc->list))
+ return -EINVAL;
+
+ down(&misc_sem);
+ list_del(&misc->list);
+ class_simple_device_remove(MKDEV(MISC_MAJOR, misc->minor));
+ devfs_remove(misc->devfs_name);
+ if (i < DYNAMIC_MINORS && i>0) {
+ misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
+ }
+ up(&misc_sem);
+ return 0;
+}
+
+EXPORT_SYMBOL(misc_register);
+EXPORT_SYMBOL(misc_deregister);
+
+static int __init misc_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ struct proc_dir_entry *ent;
+
+ ent = create_proc_entry("misc", 0, NULL);
+ if (ent)
+ ent->proc_fops = &misc_proc_fops;
+#endif
+ misc_class = class_simple_create(THIS_MODULE, "misc");
+ if (IS_ERR(misc_class))
+ return PTR_ERR(misc_class);
+#ifdef CONFIG_MVME16x
+ rtc_MK48T08_init();
+#endif
+#ifdef CONFIG_BVME6000
+ rtc_DP8570A_init();
+#endif
+#ifdef CONFIG_PMAC_PBOOK
+ pmu_device_init();
+#endif
+#ifdef CONFIG_TOSHIBA
+ tosh_init();
+#endif
+#ifdef CONFIG_I8K
+ i8k_init();
+#endif
+ if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) {
+ printk("unable to get major %d for misc devices\n",
+ MISC_MAJOR);
+ class_simple_destroy(misc_class);
+ return -EIO;
+ }
+ return 0;
+}
+subsys_initcall(misc_init);
diff --git a/drivers/char/mmtimer.c b/drivers/char/mmtimer.c
new file mode 100644
index 000000000000..58eddfdd3110
--- /dev/null
+++ b/drivers/char/mmtimer.c
@@ -0,0 +1,725 @@
+/*
+ * Intel Multimedia Timer device implementation for SGI SN platforms.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2001-2004 Silicon Graphics, Inc. All rights reserved.
+ *
+ * This driver exports an API that should be supportable by any HPET or IA-PC
+ * multimedia timer. The code below is currently specific to the SGI Altix
+ * SHub RTC, however.
+ *
+ * 11/01/01 - jbarnes - initial revision
+ * 9/10/04 - Christoph Lameter - remove interrupt support for kernel inclusion
+ * 10/1/04 - Christoph Lameter - provide posix clock CLOCK_SGI_CYCLE
+ * 10/13/04 - Christoph Lameter, Dimitri Sivanich - provide timer interrupt
+ * support via the posix timer interface
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/mmtimer.h>
+#include <linux/miscdevice.h>
+#include <linux/posix-timers.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/shub_mmr.h>
+#include <asm/sn/nodepda.h>
+#include <asm/sn/shubio.h>
+
+MODULE_AUTHOR("Jesse Barnes <jbarnes@sgi.com>");
+MODULE_DESCRIPTION("SGI Altix RTC Timer");
+MODULE_LICENSE("GPL");
+
+/* name of the device, usually in /dev */
+#define MMTIMER_NAME "mmtimer"
+#define MMTIMER_DESC "SGI Altix RTC Timer"
+#define MMTIMER_VERSION "2.0"
+
+#define RTC_BITS 55 /* 55 bits for this implementation */
+
+extern unsigned long sn_rtc_cycles_per_second;
+
+#define RTC_COUNTER_ADDR ((long *)LOCAL_MMR_ADDR(SH_RTC))
+
+#define rtc_time() (*RTC_COUNTER_ADDR)
+
+static int mmtimer_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma);
+
+/*
+ * Period in femtoseconds (10^-15 s)
+ */
+static unsigned long mmtimer_femtoperiod = 0;
+
+static struct file_operations mmtimer_fops = {
+ .owner = THIS_MODULE,
+ .mmap = mmtimer_mmap,
+ .ioctl = mmtimer_ioctl,
+};
+
+/*
+ * We only have comparison registers RTC1-4 currently available per
+ * node. RTC0 is used by SAL.
+ */
+#define NUM_COMPARATORS 3
+/* Check for an RTC interrupt pending */
+static int inline mmtimer_int_pending(int comparator)
+{
+ if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) &
+ SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator)
+ return 1;
+ else
+ return 0;
+}
+/* Clear the RTC interrupt pending bit */
+static void inline mmtimer_clr_int_pending(int comparator)
+{
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS),
+ SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator);
+}
+
+/* Setup timer on comparator RTC1 */
+static void inline mmtimer_setup_int_0(u64 expires)
+{
+ u64 val;
+
+ /* Disable interrupt */
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 0UL);
+
+ /* Initialize comparator value */
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), -1L);
+
+ /* Clear pending bit */
+ mmtimer_clr_int_pending(0);
+
+ val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC1_INT_CONFIG_IDX_SHFT) |
+ ((u64)cpu_physical_id(smp_processor_id()) <<
+ SH_RTC1_INT_CONFIG_PID_SHFT);
+
+ /* Set configuration */
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_CONFIG), val);
+
+ /* Enable RTC interrupts */
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 1UL);
+
+ /* Initialize comparator value */
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), expires);
+
+
+}
+
+/* Setup timer on comparator RTC2 */
+static void inline mmtimer_setup_int_1(u64 expires)
+{
+ u64 val;
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 0UL);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), -1L);
+
+ mmtimer_clr_int_pending(1);
+
+ val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC2_INT_CONFIG_IDX_SHFT) |
+ ((u64)cpu_physical_id(smp_processor_id()) <<
+ SH_RTC2_INT_CONFIG_PID_SHFT);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_CONFIG), val);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 1UL);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), expires);
+}
+
+/* Setup timer on comparator RTC3 */
+static void inline mmtimer_setup_int_2(u64 expires)
+{
+ u64 val;
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 0UL);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), -1L);
+
+ mmtimer_clr_int_pending(2);
+
+ val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC3_INT_CONFIG_IDX_SHFT) |
+ ((u64)cpu_physical_id(smp_processor_id()) <<
+ SH_RTC3_INT_CONFIG_PID_SHFT);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_CONFIG), val);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 1UL);
+
+ HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), expires);
+}
+
+/*
+ * This function must be called with interrupts disabled and preemption off
+ * in order to insure that the setup succeeds in a deterministic time frame.
+ * It will check if the interrupt setup succeeded.
+ */
+static int inline mmtimer_setup(int comparator, unsigned long expires)
+{
+
+ switch (comparator) {
+ case 0:
+ mmtimer_setup_int_0(expires);
+ break;
+ case 1:
+ mmtimer_setup_int_1(expires);
+ break;
+ case 2:
+ mmtimer_setup_int_2(expires);
+ break;
+ }
+ /* We might've missed our expiration time */
+ if (rtc_time() < expires)
+ return 1;
+
+ /*
+ * If an interrupt is already pending then its okay
+ * if not then we failed
+ */
+ return mmtimer_int_pending(comparator);
+}
+
+static int inline mmtimer_disable_int(long nasid, int comparator)
+{
+ switch (comparator) {
+ case 0:
+ nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE),
+ 0UL) : REMOTE_HUB_S(nasid, SH_RTC1_INT_ENABLE, 0UL);
+ break;
+ case 1:
+ nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE),
+ 0UL) : REMOTE_HUB_S(nasid, SH_RTC2_INT_ENABLE, 0UL);
+ break;
+ case 2:
+ nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE),
+ 0UL) : REMOTE_HUB_S(nasid, SH_RTC3_INT_ENABLE, 0UL);
+ break;
+ default:
+ return -EFAULT;
+ }
+ return 0;
+}
+
+#define TIMER_OFF 0xbadcabLL
+
+/* There is one of these for each comparator */
+typedef struct mmtimer {
+ spinlock_t lock ____cacheline_aligned;
+ struct k_itimer *timer;
+ int i;
+ int cpu;
+ struct tasklet_struct tasklet;
+} mmtimer_t;
+
+/*
+ * Total number of comparators is comparators/node * MAX nodes/running kernel
+ */
+static mmtimer_t timers[NUM_COMPARATORS*MAX_COMPACT_NODES];
+
+/**
+ * mmtimer_ioctl - ioctl interface for /dev/mmtimer
+ * @inode: inode of the device
+ * @file: file structure for the device
+ * @cmd: command to execute
+ * @arg: optional argument to command
+ *
+ * Executes the command specified by @cmd. Returns 0 for success, < 0 for
+ * failure.
+ *
+ * Valid commands:
+ *
+ * %MMTIMER_GETOFFSET - Should return the offset (relative to the start
+ * of the page where the registers are mapped) for the counter in question.
+ *
+ * %MMTIMER_GETRES - Returns the resolution of the clock in femto (10^-15)
+ * seconds
+ *
+ * %MMTIMER_GETFREQ - Copies the frequency of the clock in Hz to the address
+ * specified by @arg
+ *
+ * %MMTIMER_GETBITS - Returns the number of bits in the clock's counter
+ *
+ * %MMTIMER_MMAPAVAIL - Returns 1 if the registers can be mmap'd into userspace
+ *
+ * %MMTIMER_GETCOUNTER - Gets the current value in the counter and places it
+ * in the address specified by @arg.
+ */
+static int mmtimer_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case MMTIMER_GETOFFSET: /* offset of the counter */
+ /*
+ * SN RTC registers are on their own 64k page
+ */
+ if(PAGE_SIZE <= (1 << 16))
+ ret = (((long)RTC_COUNTER_ADDR) & (PAGE_SIZE-1)) / 8;
+ else
+ ret = -ENOSYS;
+ break;
+
+ case MMTIMER_GETRES: /* resolution of the clock in 10^-15 s */
+ if(copy_to_user((unsigned long __user *)arg,
+ &mmtimer_femtoperiod, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+
+ case MMTIMER_GETFREQ: /* frequency in Hz */
+ if(copy_to_user((unsigned long __user *)arg,
+ &sn_rtc_cycles_per_second,
+ sizeof(unsigned long)))
+ return -EFAULT;
+ ret = 0;
+ break;
+
+ case MMTIMER_GETBITS: /* number of bits in the clock */
+ ret = RTC_BITS;
+ break;
+
+ case MMTIMER_MMAPAVAIL: /* can we mmap the clock into userspace? */
+ ret = (PAGE_SIZE <= (1 << 16)) ? 1 : 0;
+ break;
+
+ case MMTIMER_GETCOUNTER:
+ if(copy_to_user((unsigned long __user *)arg,
+ RTC_COUNTER_ADDR, sizeof(unsigned long)))
+ return -EFAULT;
+ break;
+ default:
+ ret = -ENOSYS;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * mmtimer_mmap - maps the clock's registers into userspace
+ * @file: file structure for the device
+ * @vma: VMA to map the registers into
+ *
+ * Calls remap_pfn_range() to map the clock's registers into
+ * the calling process' address space.
+ */
+static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ unsigned long mmtimer_addr;
+
+ if (vma->vm_end - vma->vm_start != PAGE_SIZE)
+ return -EINVAL;
+
+ if (vma->vm_flags & VM_WRITE)
+ return -EPERM;
+
+ if (PAGE_SIZE > (1 << 16))
+ return -ENOSYS;
+
+ vma->vm_flags |= (VM_IO | VM_SHM | VM_LOCKED );
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ mmtimer_addr = __pa(RTC_COUNTER_ADDR);
+ mmtimer_addr &= ~(PAGE_SIZE - 1);
+ mmtimer_addr &= 0xfffffffffffffffUL;
+
+ if (remap_pfn_range(vma, vma->vm_start, mmtimer_addr >> PAGE_SHIFT,
+ PAGE_SIZE, vma->vm_page_prot)) {
+ printk(KERN_ERR "remap_pfn_range failed in mmtimer.c\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static struct miscdevice mmtimer_miscdev = {
+ SGI_MMTIMER,
+ MMTIMER_NAME,
+ &mmtimer_fops
+};
+
+static struct timespec sgi_clock_offset;
+static int sgi_clock_period;
+
+/*
+ * Posix Timer Interface
+ */
+
+static struct timespec sgi_clock_offset;
+static int sgi_clock_period;
+
+static int sgi_clock_get(clockid_t clockid, struct timespec *tp)
+{
+ u64 nsec;
+
+ nsec = rtc_time() * sgi_clock_period
+ + sgi_clock_offset.tv_nsec;
+ tp->tv_sec = div_long_long_rem(nsec, NSEC_PER_SEC, &tp->tv_nsec)
+ + sgi_clock_offset.tv_sec;
+ return 0;
+};
+
+static int sgi_clock_set(clockid_t clockid, struct timespec *tp)
+{
+
+ u64 nsec;
+ u64 rem;
+
+ nsec = rtc_time() * sgi_clock_period;
+
+ sgi_clock_offset.tv_sec = tp->tv_sec - div_long_long_rem(nsec, NSEC_PER_SEC, &rem);
+
+ if (rem <= tp->tv_nsec)
+ sgi_clock_offset.tv_nsec = tp->tv_sec - rem;
+ else {
+ sgi_clock_offset.tv_nsec = tp->tv_sec + NSEC_PER_SEC - rem;
+ sgi_clock_offset.tv_sec--;
+ }
+ return 0;
+}
+
+/*
+ * Schedule the next periodic interrupt. This function will attempt
+ * to schedule a periodic interrupt later if necessary. If the scheduling
+ * of an interrupt fails then the time to skip is lengthened
+ * exponentially in order to ensure that the next interrupt
+ * can be properly scheduled..
+ */
+static int inline reschedule_periodic_timer(mmtimer_t *x)
+{
+ int n;
+ struct k_itimer *t = x->timer;
+
+ t->it.mmtimer.clock = x->i;
+ t->it_overrun--;
+
+ n = 0;
+ do {
+
+ t->it.mmtimer.expires += t->it.mmtimer.incr << n;
+ t->it_overrun += 1 << n;
+ n++;
+ if (n > 20)
+ return 1;
+
+ } while (!mmtimer_setup(x->i, t->it.mmtimer.expires));
+
+ return 0;
+}
+
+/**
+ * mmtimer_interrupt - timer interrupt handler
+ * @irq: irq received
+ * @dev_id: device the irq came from
+ * @regs: register state upon receipt of the interrupt
+ *
+ * Called when one of the comarators matches the counter, This
+ * routine will send signals to processes that have requested
+ * them.
+ *
+ * This interrupt is run in an interrupt context
+ * by the SHUB. It is therefore safe to locally access SHub
+ * registers.
+ */
+static irqreturn_t
+mmtimer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ int i;
+ mmtimer_t *base = timers + cpuid_to_cnodeid(smp_processor_id()) *
+ NUM_COMPARATORS;
+ unsigned long expires = 0;
+ int result = IRQ_NONE;
+
+ /*
+ * Do this once for each comparison register
+ */
+ for (i = 0; i < NUM_COMPARATORS; i++) {
+ /* Make sure this doesn't get reused before tasklet_sched */
+ spin_lock(&base[i].lock);
+ if (base[i].cpu == smp_processor_id()) {
+ if (base[i].timer)
+ expires = base[i].timer->it.mmtimer.expires;
+ /* expires test won't work with shared irqs */
+ if ((mmtimer_int_pending(i) > 0) ||
+ (expires && (expires < rtc_time()))) {
+ mmtimer_clr_int_pending(i);
+ tasklet_schedule(&base[i].tasklet);
+ result = IRQ_HANDLED;
+ }
+ }
+ spin_unlock(&base[i].lock);
+ expires = 0;
+ }
+ return result;
+}
+
+void mmtimer_tasklet(unsigned long data) {
+ mmtimer_t *x = (mmtimer_t *)data;
+ struct k_itimer *t = x->timer;
+ unsigned long flags;
+
+ if (t == NULL)
+ return;
+
+ /* Send signal and deal with periodic signals */
+ spin_lock_irqsave(&t->it_lock, flags);
+ spin_lock(&x->lock);
+ /* If timer was deleted between interrupt and here, leave */
+ if (t != x->timer)
+ goto out;
+ t->it_overrun = 0;
+
+ if (tasklist_lock.write_lock || posix_timer_event(t, 0) != 0) {
+
+ // printk(KERN_WARNING "mmtimer: cannot deliver signal.\n");
+
+ t->it_overrun++;
+ }
+ if(t->it.mmtimer.incr) {
+ /* Periodic timer */
+ if (reschedule_periodic_timer(x)) {
+ printk(KERN_WARNING "mmtimer: unable to reschedule\n");
+ x->timer = NULL;
+ }
+ } else {
+ /* Ensure we don't false trigger in mmtimer_interrupt */
+ t->it.mmtimer.expires = 0;
+ }
+ t->it_overrun_last = t->it_overrun;
+out:
+ spin_unlock(&x->lock);
+ spin_unlock_irqrestore(&t->it_lock, flags);
+}
+
+static int sgi_timer_create(struct k_itimer *timer)
+{
+ /* Insure that a newly created timer is off */
+ timer->it.mmtimer.clock = TIMER_OFF;
+ return 0;
+}
+
+/* This does not really delete a timer. It just insures
+ * that the timer is not active
+ *
+ * Assumption: it_lock is already held with irq's disabled
+ */
+static int sgi_timer_del(struct k_itimer *timr)
+{
+ int i = timr->it.mmtimer.clock;
+ cnodeid_t nodeid = timr->it.mmtimer.node;
+ mmtimer_t *t = timers + nodeid * NUM_COMPARATORS +i;
+ unsigned long irqflags;
+
+ if (i != TIMER_OFF) {
+ spin_lock_irqsave(&t->lock, irqflags);
+ mmtimer_disable_int(cnodeid_to_nasid(nodeid),i);
+ t->timer = NULL;
+ timr->it.mmtimer.clock = TIMER_OFF;
+ timr->it.mmtimer.expires = 0;
+ spin_unlock_irqrestore(&t->lock, irqflags);
+ }
+ return 0;
+}
+
+#define timespec_to_ns(x) ((x).tv_nsec + (x).tv_sec * NSEC_PER_SEC)
+#define ns_to_timespec(ts, nsec) (ts).tv_sec = div_long_long_rem(nsec, NSEC_PER_SEC, &(ts).tv_nsec)
+
+/* Assumption: it_lock is already held with irq's disabled */
+static void sgi_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting)
+{
+
+ if (timr->it.mmtimer.clock == TIMER_OFF) {
+ cur_setting->it_interval.tv_nsec = 0;
+ cur_setting->it_interval.tv_sec = 0;
+ cur_setting->it_value.tv_nsec = 0;
+ cur_setting->it_value.tv_sec =0;
+ return;
+ }
+
+ ns_to_timespec(cur_setting->it_interval, timr->it.mmtimer.incr * sgi_clock_period);
+ ns_to_timespec(cur_setting->it_value, (timr->it.mmtimer.expires - rtc_time())* sgi_clock_period);
+ return;
+}
+
+
+static int sgi_timer_set(struct k_itimer *timr, int flags,
+ struct itimerspec * new_setting,
+ struct itimerspec * old_setting)
+{
+
+ int i;
+ unsigned long when, period, irqflags;
+ int err = 0;
+ cnodeid_t nodeid;
+ mmtimer_t *base;
+
+ if (old_setting)
+ sgi_timer_get(timr, old_setting);
+
+ sgi_timer_del(timr);
+ when = timespec_to_ns(new_setting->it_value);
+ period = timespec_to_ns(new_setting->it_interval);
+
+ if (when == 0)
+ /* Clear timer */
+ return 0;
+
+ if (flags & TIMER_ABSTIME) {
+ struct timespec n;
+ unsigned long now;
+
+ getnstimeofday(&n);
+ now = timespec_to_ns(n);
+ if (when > now)
+ when -= now;
+ else
+ /* Fire the timer immediately */
+ when = 0;
+ }
+
+ /*
+ * Convert to sgi clock period. Need to keep rtc_time() as near as possible
+ * to getnstimeofday() in order to be as faithful as possible to the time
+ * specified.
+ */
+ when = (when + sgi_clock_period - 1) / sgi_clock_period + rtc_time();
+ period = (period + sgi_clock_period - 1) / sgi_clock_period;
+
+ /*
+ * We are allocating a local SHub comparator. If we would be moved to another
+ * cpu then another SHub may be local to us. Prohibit that by switching off
+ * preemption.
+ */
+ preempt_disable();
+
+ nodeid = cpuid_to_cnodeid(smp_processor_id());
+ base = timers + nodeid * NUM_COMPARATORS;
+retry:
+ /* Don't use an allocated timer, or a deleted one that's pending */
+ for(i = 0; i< NUM_COMPARATORS; i++) {
+ if (!base[i].timer && !base[i].tasklet.state) {
+ break;
+ }
+ }
+
+ if (i == NUM_COMPARATORS) {
+ preempt_enable();
+ return -EBUSY;
+ }
+
+ spin_lock_irqsave(&base[i].lock, irqflags);
+
+ if (base[i].timer || base[i].tasklet.state != 0) {
+ spin_unlock_irqrestore(&base[i].lock, irqflags);
+ goto retry;
+ }
+ base[i].timer = timr;
+ base[i].cpu = smp_processor_id();
+
+ timr->it.mmtimer.clock = i;
+ timr->it.mmtimer.node = nodeid;
+ timr->it.mmtimer.incr = period;
+ timr->it.mmtimer.expires = when;
+
+ if (period == 0) {
+ if (!mmtimer_setup(i, when)) {
+ mmtimer_disable_int(-1, i);
+ posix_timer_event(timr, 0);
+ timr->it.mmtimer.expires = 0;
+ }
+ } else {
+ timr->it.mmtimer.expires -= period;
+ if (reschedule_periodic_timer(base+i))
+ err = -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&base[i].lock, irqflags);
+
+ preempt_enable();
+
+ return err;
+}
+
+static struct k_clock sgi_clock = {
+ .res = 0,
+ .clock_set = sgi_clock_set,
+ .clock_get = sgi_clock_get,
+ .timer_create = sgi_timer_create,
+ .nsleep = do_posix_clock_nonanosleep,
+ .timer_set = sgi_timer_set,
+ .timer_del = sgi_timer_del,
+ .timer_get = sgi_timer_get
+};
+
+/**
+ * mmtimer_init - device initialization routine
+ *
+ * Does initial setup for the mmtimer device.
+ */
+static int __init mmtimer_init(void)
+{
+ unsigned i;
+
+ if (!ia64_platform_is("sn2"))
+ return -1;
+
+ /*
+ * Sanity check the cycles/sec variable
+ */
+ if (sn_rtc_cycles_per_second < 100000) {
+ printk(KERN_ERR "%s: unable to determine clock frequency\n",
+ MMTIMER_NAME);
+ return -1;
+ }
+
+ mmtimer_femtoperiod = ((unsigned long)1E15 + sn_rtc_cycles_per_second /
+ 2) / sn_rtc_cycles_per_second;
+
+ for (i=0; i< NUM_COMPARATORS*MAX_COMPACT_NODES; i++) {
+ spin_lock_init(&timers[i].lock);
+ timers[i].timer = NULL;
+ timers[i].cpu = 0;
+ timers[i].i = i % NUM_COMPARATORS;
+ tasklet_init(&timers[i].tasklet, mmtimer_tasklet, (unsigned long) (timers+i));
+ }
+
+ if (request_irq(SGI_MMTIMER_VECTOR, mmtimer_interrupt, SA_PERCPU_IRQ, MMTIMER_NAME, NULL)) {
+ printk(KERN_WARNING "%s: unable to allocate interrupt.",
+ MMTIMER_NAME);
+ return -1;
+ }
+
+ strcpy(mmtimer_miscdev.devfs_name, MMTIMER_NAME);
+ if (misc_register(&mmtimer_miscdev)) {
+ printk(KERN_ERR "%s: failed to register device\n",
+ MMTIMER_NAME);
+ return -1;
+ }
+
+ sgi_clock_period = sgi_clock.res = NSEC_PER_SEC / sn_rtc_cycles_per_second;
+ register_posix_clock(CLOCK_SGI_CYCLE, &sgi_clock);
+
+ printk(KERN_INFO "%s: v%s, %ld MHz\n", MMTIMER_DESC, MMTIMER_VERSION,
+ sn_rtc_cycles_per_second/(unsigned long)1E6);
+
+ return 0;
+}
+
+module_init(mmtimer_init);
+
diff --git a/drivers/char/moxa.c b/drivers/char/moxa.c
new file mode 100644
index 000000000000..7c24fbe831f8
--- /dev/null
+++ b/drivers/char/moxa.c
@@ -0,0 +1,3243 @@
+/*****************************************************************************/
+/*
+ * moxa.c -- MOXA Intellio family multiport serial driver.
+ *
+ * Copyright (C) 1999-2000 Moxa Technologies (support@moxa.com.tw).
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * MOXA Intellio Series Driver
+ * for : LINUX
+ * date : 1999/1/7
+ * version : 5.1
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MOXA_VERSION "5.1k"
+
+#define MOXAMAJOR 172
+#define MOXACUMAJOR 173
+
+#define put_to_user(arg1, arg2) put_user(arg1, (unsigned long *)arg2)
+#define get_from_user(arg1, arg2) get_user(arg1, (unsigned int *)arg2)
+
+#define MAX_BOARDS 4 /* Don't change this value */
+#define MAX_PORTS_PER_BOARD 32 /* Don't change this value */
+#define MAX_PORTS 128 /* Don't change this value */
+
+/*
+ * Define the Moxa PCI vendor and device IDs.
+ */
+#define MOXA_BUS_TYPE_ISA 0
+#define MOXA_BUS_TYPE_PCI 1
+
+#ifndef PCI_VENDOR_ID_MOXA
+#define PCI_VENDOR_ID_MOXA 0x1393
+#endif
+#ifndef PCI_DEVICE_ID_CP204J
+#define PCI_DEVICE_ID_CP204J 0x2040
+#endif
+#ifndef PCI_DEVICE_ID_C218
+#define PCI_DEVICE_ID_C218 0x2180
+#endif
+#ifndef PCI_DEVICE_ID_C320
+#define PCI_DEVICE_ID_C320 0x3200
+#endif
+
+enum {
+ MOXA_BOARD_C218_PCI = 1,
+ MOXA_BOARD_C218_ISA,
+ MOXA_BOARD_C320_PCI,
+ MOXA_BOARD_C320_ISA,
+ MOXA_BOARD_CP204J,
+};
+
+static char *moxa_brdname[] =
+{
+ "C218 Turbo PCI series",
+ "C218 Turbo ISA series",
+ "C320 Turbo PCI series",
+ "C320 Turbo ISA series",
+ "CP-204J series",
+};
+
+#ifdef CONFIG_PCI
+static struct pci_device_id moxa_pcibrds[] = {
+ { PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_C218, PCI_ANY_ID, PCI_ANY_ID,
+ 0, 0, MOXA_BOARD_C218_PCI },
+ { PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_C320, PCI_ANY_ID, PCI_ANY_ID,
+ 0, 0, MOXA_BOARD_C320_PCI },
+ { PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_CP204J, PCI_ANY_ID, PCI_ANY_ID,
+ 0, 0, MOXA_BOARD_CP204J },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, moxa_pcibrds);
+#endif /* CONFIG_PCI */
+
+typedef struct _moxa_isa_board_conf {
+ int boardType;
+ int numPorts;
+ unsigned long baseAddr;
+} moxa_isa_board_conf;
+
+static moxa_isa_board_conf moxa_isa_boards[] =
+{
+/* {MOXA_BOARD_C218_ISA,8,0xDC000}, */
+};
+
+typedef struct _moxa_pci_devinfo {
+ ushort busNum;
+ ushort devNum;
+} moxa_pci_devinfo;
+
+typedef struct _moxa_board_conf {
+ int boardType;
+ int numPorts;
+ unsigned long baseAddr;
+ int busType;
+ moxa_pci_devinfo pciInfo;
+} moxa_board_conf;
+
+static moxa_board_conf moxa_boards[MAX_BOARDS];
+static void __iomem *moxaBaseAddr[MAX_BOARDS];
+
+struct moxa_str {
+ int type;
+ int port;
+ int close_delay;
+ unsigned short closing_wait;
+ int count;
+ int blocked_open;
+ long event; /* long req'd for set_bit --RR */
+ int asyncflags;
+ unsigned long statusflags;
+ struct tty_struct *tty;
+ int cflag;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct work_struct tqueue;
+};
+
+struct mxser_mstatus {
+ tcflag_t cflag;
+ int cts;
+ int dsr;
+ int ri;
+ int dcd;
+};
+
+static struct mxser_mstatus GMStatus[MAX_PORTS];
+
+/* statusflags */
+#define TXSTOPPED 0x1
+#define LOWWAIT 0x2
+#define EMPTYWAIT 0x4
+#define THROTTLE 0x8
+
+/* event */
+#define MOXA_EVENT_HANGUP 1
+
+#define SERIAL_DO_RESTART
+
+
+#define SERIAL_TYPE_NORMAL 1
+
+#define WAKEUP_CHARS 256
+
+#define PORTNO(x) ((x)->index)
+
+static int verbose = 0;
+static int ttymajor = MOXAMAJOR;
+/* Variables for insmod */
+#ifdef MODULE
+static int baseaddr[] = {0, 0, 0, 0};
+static int type[] = {0, 0, 0, 0};
+static int numports[] = {0, 0, 0, 0};
+#endif
+
+MODULE_AUTHOR("William Chen");
+MODULE_DESCRIPTION("MOXA Intellio Family Multiport Board Device Driver");
+MODULE_LICENSE("GPL");
+#ifdef MODULE
+module_param_array(type, int, NULL, 0);
+module_param_array(baseaddr, int, NULL, 0);
+module_param_array(numports, int, NULL, 0);
+#endif
+module_param(ttymajor, int, 0);
+module_param(verbose, bool, 0644);
+
+static struct tty_driver *moxaDriver;
+static struct moxa_str moxaChannels[MAX_PORTS];
+static unsigned char *moxaXmitBuff;
+static int moxaTimer_on;
+static struct timer_list moxaTimer;
+static int moxaEmptyTimer_on[MAX_PORTS];
+static struct timer_list moxaEmptyTimer[MAX_PORTS];
+static struct semaphore moxaBuffSem;
+
+/*
+ * static functions:
+ */
+static void do_moxa_softint(void *);
+static int moxa_open(struct tty_struct *, struct file *);
+static void moxa_close(struct tty_struct *, struct file *);
+static int moxa_write(struct tty_struct *, const unsigned char *, int);
+static int moxa_write_room(struct tty_struct *);
+static void moxa_flush_buffer(struct tty_struct *);
+static int moxa_chars_in_buffer(struct tty_struct *);
+static void moxa_flush_chars(struct tty_struct *);
+static void moxa_put_char(struct tty_struct *, unsigned char);
+static int moxa_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);
+static void moxa_throttle(struct tty_struct *);
+static void moxa_unthrottle(struct tty_struct *);
+static void moxa_set_termios(struct tty_struct *, struct termios *);
+static void moxa_stop(struct tty_struct *);
+static void moxa_start(struct tty_struct *);
+static void moxa_hangup(struct tty_struct *);
+static int moxa_tiocmget(struct tty_struct *tty, struct file *file);
+static int moxa_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static void moxa_poll(unsigned long);
+static void set_tty_param(struct tty_struct *);
+static int block_till_ready(struct tty_struct *, struct file *,
+ struct moxa_str *);
+static void setup_empty_event(struct tty_struct *);
+static void check_xmit_empty(unsigned long);
+static void shut_down(struct moxa_str *);
+static void receive_data(struct moxa_str *);
+/*
+ * moxa board interface functions:
+ */
+static void MoxaDriverInit(void);
+static int MoxaDriverIoctl(unsigned int, unsigned long, int);
+static int MoxaDriverPoll(void);
+static int MoxaPortsOfCard(int);
+static int MoxaPortIsValid(int);
+static void MoxaPortEnable(int);
+static void MoxaPortDisable(int);
+static long MoxaPortGetMaxBaud(int);
+static long MoxaPortSetBaud(int, long);
+static int MoxaPortSetTermio(int, struct termios *);
+static int MoxaPortGetLineOut(int, int *, int *);
+static void MoxaPortLineCtrl(int, int, int);
+static void MoxaPortFlowCtrl(int, int, int, int, int, int);
+static int MoxaPortLineStatus(int);
+static int MoxaPortDCDChange(int);
+static int MoxaPortDCDON(int);
+static void MoxaPortFlushData(int, int);
+static int MoxaPortWriteData(int, unsigned char *, int);
+static int MoxaPortReadData(int, unsigned char *, int);
+static int MoxaPortTxQueue(int);
+static int MoxaPortRxQueue(int);
+static int MoxaPortTxFree(int);
+static void MoxaPortTxDisable(int);
+static void MoxaPortTxEnable(int);
+static int MoxaPortResetBrkCnt(int);
+static void MoxaPortSendBreak(int, int);
+static int moxa_get_serial_info(struct moxa_str *, struct serial_struct __user *);
+static int moxa_set_serial_info(struct moxa_str *, struct serial_struct __user *);
+static void MoxaSetFifo(int port, int enable);
+
+static struct tty_operations moxa_ops = {
+ .open = moxa_open,
+ .close = moxa_close,
+ .write = moxa_write,
+ .write_room = moxa_write_room,
+ .flush_buffer = moxa_flush_buffer,
+ .chars_in_buffer = moxa_chars_in_buffer,
+ .flush_chars = moxa_flush_chars,
+ .put_char = moxa_put_char,
+ .ioctl = moxa_ioctl,
+ .throttle = moxa_throttle,
+ .unthrottle = moxa_unthrottle,
+ .set_termios = moxa_set_termios,
+ .stop = moxa_stop,
+ .start = moxa_start,
+ .hangup = moxa_hangup,
+ .tiocmget = moxa_tiocmget,
+ .tiocmset = moxa_tiocmset,
+};
+
+#ifdef CONFIG_PCI
+static int moxa_get_PCI_conf(struct pci_dev *p, int board_type, moxa_board_conf * board)
+{
+ board->baseAddr = pci_resource_start (p, 2);
+ board->boardType = board_type;
+ switch (board_type) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ board->numPorts = 8;
+ break;
+
+ case MOXA_BOARD_CP204J:
+ board->numPorts = 4;
+ break;
+ default:
+ board->numPorts = 0;
+ break;
+ }
+ board->busType = MOXA_BUS_TYPE_PCI;
+ board->pciInfo.busNum = p->bus->number;
+ board->pciInfo.devNum = p->devfn >> 3;
+
+ return (0);
+}
+#endif /* CONFIG_PCI */
+
+static int __init moxa_init(void)
+{
+ int i, numBoards;
+ struct moxa_str *ch;
+
+ printk(KERN_INFO "MOXA Intellio family driver version %s\n", MOXA_VERSION);
+ moxaDriver = alloc_tty_driver(MAX_PORTS + 1);
+ if (!moxaDriver)
+ return -ENOMEM;
+
+ init_MUTEX(&moxaBuffSem);
+ moxaDriver->owner = THIS_MODULE;
+ moxaDriver->name = "ttya";
+ moxaDriver->devfs_name = "tts/a";
+ moxaDriver->major = ttymajor;
+ moxaDriver->minor_start = 0;
+ moxaDriver->type = TTY_DRIVER_TYPE_SERIAL;
+ moxaDriver->subtype = SERIAL_TYPE_NORMAL;
+ moxaDriver->init_termios = tty_std_termios;
+ moxaDriver->init_termios.c_iflag = 0;
+ moxaDriver->init_termios.c_oflag = 0;
+ moxaDriver->init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+ moxaDriver->init_termios.c_lflag = 0;
+ moxaDriver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(moxaDriver, &moxa_ops);
+
+ moxaXmitBuff = NULL;
+
+ for (i = 0, ch = moxaChannels; i < MAX_PORTS; i++, ch++) {
+ ch->type = PORT_16550A;
+ ch->port = i;
+ INIT_WORK(&ch->tqueue, do_moxa_softint, ch);
+ ch->tty = NULL;
+ ch->close_delay = 5 * HZ / 10;
+ ch->closing_wait = 30 * HZ;
+ ch->count = 0;
+ ch->blocked_open = 0;
+ ch->cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+ init_waitqueue_head(&ch->open_wait);
+ init_waitqueue_head(&ch->close_wait);
+ }
+
+ for (i = 0; i < MAX_BOARDS; i++) {
+ moxa_boards[i].boardType = 0;
+ moxa_boards[i].numPorts = 0;
+ moxa_boards[i].baseAddr = 0;
+ moxa_boards[i].busType = 0;
+ moxa_boards[i].pciInfo.busNum = 0;
+ moxa_boards[i].pciInfo.devNum = 0;
+ }
+ MoxaDriverInit();
+ printk("Tty devices major number = %d\n", ttymajor);
+
+ if (tty_register_driver(moxaDriver)) {
+ printk(KERN_ERR "Couldn't install MOXA Smartio family driver !\n");
+ put_tty_driver(moxaDriver);
+ return -1;
+ }
+ for (i = 0; i < MAX_PORTS; i++) {
+ init_timer(&moxaEmptyTimer[i]);
+ moxaEmptyTimer[i].function = check_xmit_empty;
+ moxaEmptyTimer[i].data = (unsigned long) & moxaChannels[i];
+ moxaEmptyTimer_on[i] = 0;
+ }
+
+ init_timer(&moxaTimer);
+ moxaTimer.function = moxa_poll;
+ moxaTimer.expires = jiffies + (HZ / 50);
+ moxaTimer_on = 1;
+ add_timer(&moxaTimer);
+
+ /* Find the boards defined in source code */
+ numBoards = 0;
+ for (i = 0; i < MAX_BOARDS; i++) {
+ if ((moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA) ||
+ (moxa_isa_boards[i].boardType == MOXA_BOARD_C320_ISA)) {
+ moxa_boards[numBoards].boardType = moxa_isa_boards[i].boardType;
+ if (moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA)
+ moxa_boards[numBoards].numPorts = 8;
+ else
+ moxa_boards[numBoards].numPorts = moxa_isa_boards[i].numPorts;
+ moxa_boards[numBoards].busType = MOXA_BUS_TYPE_ISA;
+ moxa_boards[numBoards].baseAddr = moxa_isa_boards[i].baseAddr;
+ if (verbose)
+ printk("Board %2d: %s board(baseAddr=%lx)\n",
+ numBoards + 1,
+ moxa_brdname[moxa_boards[numBoards].boardType - 1],
+ moxa_boards[numBoards].baseAddr);
+ numBoards++;
+ }
+ }
+ /* Find the boards defined form module args. */
+#ifdef MODULE
+ for (i = 0; i < MAX_BOARDS; i++) {
+ if ((type[i] == MOXA_BOARD_C218_ISA) ||
+ (type[i] == MOXA_BOARD_C320_ISA)) {
+ if (verbose)
+ printk("Board %2d: %s board(baseAddr=%lx)\n",
+ numBoards + 1,
+ moxa_brdname[type[i] - 1],
+ (unsigned long) baseaddr[i]);
+ if (numBoards >= MAX_BOARDS) {
+ if (verbose)
+ printk("More than %d MOXA Intellio family boards found. Board is ignored.", MAX_BOARDS);
+ continue;
+ }
+ moxa_boards[numBoards].boardType = type[i];
+ if (moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA)
+ moxa_boards[numBoards].numPorts = 8;
+ else
+ moxa_boards[numBoards].numPorts = numports[i];
+ moxa_boards[numBoards].busType = MOXA_BUS_TYPE_ISA;
+ moxa_boards[numBoards].baseAddr = baseaddr[i];
+ numBoards++;
+ }
+ }
+#endif
+ /* Find PCI boards here */
+#ifdef CONFIG_PCI
+ {
+ struct pci_dev *p = NULL;
+ int n = (sizeof(moxa_pcibrds) / sizeof(moxa_pcibrds[0])) - 1;
+ i = 0;
+ while (i < n) {
+ while ((p = pci_find_device(moxa_pcibrds[i].vendor, moxa_pcibrds[i].device, p))!=NULL)
+ {
+ if (pci_enable_device(p))
+ continue;
+ if (numBoards >= MAX_BOARDS) {
+ if (verbose)
+ printk("More than %d MOXA Intellio family boards found. Board is ignored.", MAX_BOARDS);
+ } else {
+ moxa_get_PCI_conf(p, moxa_pcibrds[i].driver_data,
+ &moxa_boards[numBoards]);
+ numBoards++;
+ }
+ }
+ i++;
+ }
+ }
+#endif
+ for (i = 0; i < numBoards; i++) {
+ moxaBaseAddr[i] = ioremap((unsigned long) moxa_boards[i].baseAddr, 0x4000);
+ }
+
+ return (0);
+}
+
+static void __exit moxa_exit(void)
+{
+ int i;
+
+ if (verbose)
+ printk("Unloading module moxa ...\n");
+
+ if (moxaTimer_on)
+ del_timer(&moxaTimer);
+
+ for (i = 0; i < MAX_PORTS; i++)
+ if (moxaEmptyTimer_on[i])
+ del_timer(&moxaEmptyTimer[i]);
+
+ if (tty_unregister_driver(moxaDriver))
+ printk("Couldn't unregister MOXA Intellio family serial driver\n");
+ put_tty_driver(moxaDriver);
+ if (verbose)
+ printk("Done\n");
+}
+
+module_init(moxa_init);
+module_exit(moxa_exit);
+
+static void do_moxa_softint(void *private_)
+{
+ struct moxa_str *ch = (struct moxa_str *) private_;
+ struct tty_struct *tty;
+
+ if (ch && (tty = ch->tty)) {
+ if (test_and_clear_bit(MOXA_EVENT_HANGUP, &ch->event)) {
+ tty_hangup(tty); /* FIXME: module removal race here - AKPM */
+ wake_up_interruptible(&ch->open_wait);
+ ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+ }
+ }
+}
+
+static int moxa_open(struct tty_struct *tty, struct file *filp)
+{
+ struct moxa_str *ch;
+ int port;
+ int retval;
+ unsigned long page;
+
+ port = PORTNO(tty);
+ if (port == MAX_PORTS) {
+ return (0);
+ }
+ if (!MoxaPortIsValid(port)) {
+ tty->driver_data = NULL;
+ return (-ENODEV);
+ }
+ down(&moxaBuffSem);
+ if (!moxaXmitBuff) {
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+ up(&moxaBuffSem);
+ return (-ENOMEM);
+ }
+ /* This test is guarded by the BuffSem so no longer needed
+ delete me in 2.5 */
+ if (moxaXmitBuff)
+ free_page(page);
+ else
+ moxaXmitBuff = (unsigned char *) page;
+ }
+ up(&moxaBuffSem);
+
+ ch = &moxaChannels[port];
+ ch->count++;
+ tty->driver_data = ch;
+ ch->tty = tty;
+ if (!(ch->asyncflags & ASYNC_INITIALIZED)) {
+ ch->statusflags = 0;
+ set_tty_param(tty);
+ MoxaPortLineCtrl(ch->port, 1, 1);
+ MoxaPortEnable(ch->port);
+ ch->asyncflags |= ASYNC_INITIALIZED;
+ }
+ retval = block_till_ready(tty, filp, ch);
+
+ moxa_unthrottle(tty);
+
+ if (ch->type == PORT_16550A) {
+ MoxaSetFifo(ch->port, 1);
+ } else {
+ MoxaSetFifo(ch->port, 0);
+ }
+
+ return (retval);
+}
+
+static void moxa_close(struct tty_struct *tty, struct file *filp)
+{
+ struct moxa_str *ch;
+ int port;
+
+ port = PORTNO(tty);
+ if (port == MAX_PORTS) {
+ return;
+ }
+ if (!MoxaPortIsValid(port)) {
+#ifdef SERIAL_DEBUG_CLOSE
+ printk("Invalid portno in moxa_close\n");
+#endif
+ tty->driver_data = NULL;
+ return;
+ }
+ if (tty->driver_data == NULL) {
+ return;
+ }
+ if (tty_hung_up_p(filp)) {
+ return;
+ }
+ ch = (struct moxa_str *) tty->driver_data;
+
+ if ((tty->count == 1) && (ch->count != 1)) {
+ printk("moxa_close: bad serial port count; tty->count is 1, "
+ "ch->count is %d\n", ch->count);
+ ch->count = 1;
+ }
+ if (--ch->count < 0) {
+ printk("moxa_close: bad serial port count, device=%s\n",
+ tty->name);
+ ch->count = 0;
+ }
+ if (ch->count) {
+ return;
+ }
+ ch->asyncflags |= ASYNC_CLOSING;
+
+ ch->cflag = tty->termios->c_cflag;
+ if (ch->asyncflags & ASYNC_INITIALIZED) {
+ setup_empty_event(tty);
+ tty_wait_until_sent(tty, 30 * HZ); /* 30 seconds timeout */
+ moxaEmptyTimer_on[ch->port] = 0;
+ del_timer(&moxaEmptyTimer[ch->port]);
+ }
+ shut_down(ch);
+ MoxaPortFlushData(port, 2);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ tty->closing = 0;
+ ch->event = 0;
+ ch->tty = NULL;
+ if (ch->blocked_open) {
+ if (ch->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(ch->close_delay));
+ }
+ wake_up_interruptible(&ch->open_wait);
+ }
+ ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+ wake_up_interruptible(&ch->close_wait);
+}
+
+static int moxa_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct moxa_str *ch;
+ int len, port;
+ unsigned long flags;
+
+ ch = (struct moxa_str *) tty->driver_data;
+ if (ch == NULL)
+ return (0);
+ port = ch->port;
+ save_flags(flags);
+ cli();
+ len = MoxaPortWriteData(port, (unsigned char *) buf, count);
+ restore_flags(flags);
+
+ /*********************************************
+ if ( !(ch->statusflags & LOWWAIT) &&
+ ((len != count) || (MoxaPortTxFree(port) <= 100)) )
+ ************************************************/
+ ch->statusflags |= LOWWAIT;
+ return (len);
+}
+
+static int moxa_write_room(struct tty_struct *tty)
+{
+ struct moxa_str *ch;
+
+ if (tty->stopped)
+ return (0);
+ ch = (struct moxa_str *) tty->driver_data;
+ if (ch == NULL)
+ return (0);
+ return (MoxaPortTxFree(ch->port));
+}
+
+static void moxa_flush_buffer(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ if (ch == NULL)
+ return;
+ MoxaPortFlushData(ch->port, 1);
+ tty_wakeup(tty);
+}
+
+static int moxa_chars_in_buffer(struct tty_struct *tty)
+{
+ int chars;
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ /*
+ * Sigh...I have to check if driver_data is NULL here, because
+ * if an open() fails, the TTY subsystem eventually calls
+ * tty_wait_until_sent(), which calls the driver's chars_in_buffer()
+ * routine. And since the open() failed, we return 0 here. TDJ
+ */
+ if (ch == NULL)
+ return (0);
+ chars = MoxaPortTxQueue(ch->port);
+ if (chars) {
+ /*
+ * Make it possible to wakeup anything waiting for output
+ * in tty_ioctl.c, etc.
+ */
+ if (!(ch->statusflags & EMPTYWAIT))
+ setup_empty_event(tty);
+ }
+ return (chars);
+}
+
+static void moxa_flush_chars(struct tty_struct *tty)
+{
+ /*
+ * Don't think I need this, because this is called to empty the TX
+ * buffer for the 16450, 16550, etc.
+ */
+}
+
+static void moxa_put_char(struct tty_struct *tty, unsigned char c)
+{
+ struct moxa_str *ch;
+ int port;
+ unsigned long flags;
+
+ ch = (struct moxa_str *) tty->driver_data;
+ if (ch == NULL)
+ return;
+ port = ch->port;
+ save_flags(flags);
+ cli();
+ moxaXmitBuff[0] = c;
+ MoxaPortWriteData(port, moxaXmitBuff, 1);
+ restore_flags(flags);
+ /************************************************
+ if ( !(ch->statusflags & LOWWAIT) && (MoxaPortTxFree(port) <= 100) )
+ *************************************************/
+ ch->statusflags |= LOWWAIT;
+}
+
+static int moxa_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+ int port;
+ int flag = 0, dtr, rts;
+
+ port = PORTNO(tty);
+ if ((port != MAX_PORTS) && (!ch))
+ return (-EINVAL);
+
+ MoxaPortGetLineOut(ch->port, &dtr, &rts);
+ if (dtr)
+ flag |= TIOCM_DTR;
+ if (rts)
+ flag |= TIOCM_RTS;
+ dtr = MoxaPortLineStatus(ch->port);
+ if (dtr & 1)
+ flag |= TIOCM_CTS;
+ if (dtr & 2)
+ flag |= TIOCM_DSR;
+ if (dtr & 4)
+ flag |= TIOCM_CD;
+ return flag;
+}
+
+static int moxa_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+ int port;
+ int dtr, rts;
+
+ port = PORTNO(tty);
+ if ((port != MAX_PORTS) && (!ch))
+ return (-EINVAL);
+
+ MoxaPortGetLineOut(ch->port, &dtr, &rts);
+ if (set & TIOCM_RTS)
+ rts = 1;
+ if (set & TIOCM_DTR)
+ dtr = 1;
+ if (clear & TIOCM_RTS)
+ rts = 0;
+ if (clear & TIOCM_DTR)
+ dtr = 0;
+ MoxaPortLineCtrl(ch->port, dtr, rts);
+ return 0;
+}
+
+static int moxa_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+ register int port;
+ void __user *argp = (void __user *)arg;
+ int retval;
+
+ port = PORTNO(tty);
+ if ((port != MAX_PORTS) && (!ch))
+ return (-EINVAL);
+
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ retval = tty_check_change(tty);
+ if (retval)
+ return (retval);
+ setup_empty_event(tty);
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ MoxaPortSendBreak(ch->port, 0);
+ return (0);
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ retval = tty_check_change(tty);
+ if (retval)
+ return (retval);
+ setup_empty_event(tty);
+ tty_wait_until_sent(tty, 0);
+ MoxaPortSendBreak(ch->port, arg);
+ return (0);
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) argp);
+ case TIOCSSOFTCAR:
+ if(get_user(retval, (unsigned long __user *) argp))
+ return -EFAULT;
+ arg = retval;
+ tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ if (C_CLOCAL(tty))
+ ch->asyncflags &= ~ASYNC_CHECK_CD;
+ else
+ ch->asyncflags |= ASYNC_CHECK_CD;
+ return (0);
+ case TIOCGSERIAL:
+ return moxa_get_serial_info(ch, argp);
+
+ case TIOCSSERIAL:
+ return moxa_set_serial_info(ch, argp);
+ default:
+ retval = MoxaDriverIoctl(cmd, arg, port);
+ }
+ return (retval);
+}
+
+static void moxa_throttle(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ ch->statusflags |= THROTTLE;
+}
+
+static void moxa_unthrottle(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ ch->statusflags &= ~THROTTLE;
+}
+
+static void moxa_set_termios(struct tty_struct *tty,
+ struct termios *old_termios)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ if (ch == NULL)
+ return;
+ set_tty_param(tty);
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&ch->open_wait);
+}
+
+static void moxa_stop(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ if (ch == NULL)
+ return;
+ MoxaPortTxDisable(ch->port);
+ ch->statusflags |= TXSTOPPED;
+}
+
+
+static void moxa_start(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ if (ch == NULL)
+ return;
+
+ if (!(ch->statusflags & TXSTOPPED))
+ return;
+
+ MoxaPortTxEnable(ch->port);
+ ch->statusflags &= ~TXSTOPPED;
+}
+
+static void moxa_hangup(struct tty_struct *tty)
+{
+ struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+ moxa_flush_buffer(tty);
+ shut_down(ch);
+ ch->event = 0;
+ ch->count = 0;
+ ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+ ch->tty = NULL;
+ wake_up_interruptible(&ch->open_wait);
+}
+
+static void moxa_poll(unsigned long ignored)
+{
+ register int card;
+ struct moxa_str *ch;
+ struct tty_struct *tp;
+ int i, ports;
+
+ moxaTimer_on = 0;
+ del_timer(&moxaTimer);
+
+ if (MoxaDriverPoll() < 0) {
+ moxaTimer.function = moxa_poll;
+ moxaTimer.expires = jiffies + (HZ / 50);
+ moxaTimer_on = 1;
+ add_timer(&moxaTimer);
+ return;
+ }
+ for (card = 0; card < MAX_BOARDS; card++) {
+ if ((ports = MoxaPortsOfCard(card)) <= 0)
+ continue;
+ ch = &moxaChannels[card * MAX_PORTS_PER_BOARD];
+ for (i = 0; i < ports; i++, ch++) {
+ if ((ch->asyncflags & ASYNC_INITIALIZED) == 0)
+ continue;
+ if (!(ch->statusflags & THROTTLE) &&
+ (MoxaPortRxQueue(ch->port) > 0))
+ receive_data(ch);
+ if ((tp = ch->tty) == 0)
+ continue;
+ if (ch->statusflags & LOWWAIT) {
+ if (MoxaPortTxQueue(ch->port) <= WAKEUP_CHARS) {
+ if (!tp->stopped) {
+ ch->statusflags &= ~LOWWAIT;
+ tty_wakeup(tp);
+ }
+ }
+ }
+ if (!I_IGNBRK(tp) && (MoxaPortResetBrkCnt(ch->port) > 0)) {
+ tty_insert_flip_char(tp, 0, TTY_BREAK);
+ tty_schedule_flip(tp);
+ }
+ if (MoxaPortDCDChange(ch->port)) {
+ if (ch->asyncflags & ASYNC_CHECK_CD) {
+ if (MoxaPortDCDON(ch->port))
+ wake_up_interruptible(&ch->open_wait);
+ else {
+ set_bit(MOXA_EVENT_HANGUP, &ch->event);
+ schedule_work(&ch->tqueue);
+ }
+ }
+ }
+ }
+ }
+
+ moxaTimer.function = moxa_poll;
+ moxaTimer.expires = jiffies + (HZ / 50);
+ moxaTimer_on = 1;
+ add_timer(&moxaTimer);
+}
+
+/******************************************************************************/
+
+static void set_tty_param(struct tty_struct *tty)
+{
+ register struct termios *ts;
+ struct moxa_str *ch;
+ int rts, cts, txflow, rxflow, xany;
+
+ ch = (struct moxa_str *) tty->driver_data;
+ ts = tty->termios;
+ if (ts->c_cflag & CLOCAL)
+ ch->asyncflags &= ~ASYNC_CHECK_CD;
+ else
+ ch->asyncflags |= ASYNC_CHECK_CD;
+ rts = cts = txflow = rxflow = xany = 0;
+ if (ts->c_cflag & CRTSCTS)
+ rts = cts = 1;
+ if (ts->c_iflag & IXON)
+ txflow = 1;
+ if (ts->c_iflag & IXOFF)
+ rxflow = 1;
+ if (ts->c_iflag & IXANY)
+ xany = 1;
+ MoxaPortFlowCtrl(ch->port, rts, cts, txflow, rxflow, xany);
+ MoxaPortSetTermio(ch->port, ts);
+}
+
+static int block_till_ready(struct tty_struct *tty, struct file *filp,
+ struct moxa_str *ch)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ unsigned long flags;
+ int retval;
+ int do_clocal = C_CLOCAL(tty);
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || (ch->asyncflags & ASYNC_CLOSING)) {
+ if (ch->asyncflags & ASYNC_CLOSING)
+ interruptible_sleep_on(&ch->close_wait);
+#ifdef SERIAL_DO_RESTART
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ return (-EAGAIN);
+ else
+ return (-ERESTARTSYS);
+#else
+ return (-EAGAIN);
+#endif
+ }
+ /*
+ * If non-blocking mode is set, then make the check up front
+ * and then exit.
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+ return (0);
+ }
+ /*
+ * Block waiting for the carrier detect and the line to become free
+ */
+ retval = 0;
+ add_wait_queue(&ch->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready before block: ttys%d, count = %d\n",
+ ch->line, ch->count);
+#endif
+ save_flags(flags);
+ cli();
+ if (!tty_hung_up_p(filp))
+ ch->count--;
+ restore_flags(flags);
+ ch->blocked_open++;
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(ch->asyncflags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+ if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+#else
+ retval = -EAGAIN;
+#endif
+ break;
+ }
+ if (!(ch->asyncflags & ASYNC_CLOSING) && (do_clocal ||
+ MoxaPortDCDON(ch->port)))
+ break;
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&ch->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ ch->count++;
+ ch->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready after blocking: ttys%d, count = %d\n",
+ ch->line, ch->count);
+#endif
+ if (retval)
+ return (retval);
+ ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+ return (0);
+}
+
+static void setup_empty_event(struct tty_struct *tty)
+{
+ struct moxa_str *ch = tty->driver_data;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ ch->statusflags |= EMPTYWAIT;
+ moxaEmptyTimer_on[ch->port] = 0;
+ del_timer(&moxaEmptyTimer[ch->port]);
+ moxaEmptyTimer[ch->port].expires = jiffies + HZ;
+ moxaEmptyTimer_on[ch->port] = 1;
+ add_timer(&moxaEmptyTimer[ch->port]);
+ restore_flags(flags);
+}
+
+static void check_xmit_empty(unsigned long data)
+{
+ struct moxa_str *ch;
+
+ ch = (struct moxa_str *) data;
+ moxaEmptyTimer_on[ch->port] = 0;
+ del_timer(&moxaEmptyTimer[ch->port]);
+ if (ch->tty && (ch->statusflags & EMPTYWAIT)) {
+ if (MoxaPortTxQueue(ch->port) == 0) {
+ ch->statusflags &= ~EMPTYWAIT;
+ tty_wakeup(ch->tty);
+ return;
+ }
+ moxaEmptyTimer[ch->port].expires = jiffies + HZ;
+ moxaEmptyTimer_on[ch->port] = 1;
+ add_timer(&moxaEmptyTimer[ch->port]);
+ } else
+ ch->statusflags &= ~EMPTYWAIT;
+}
+
+static void shut_down(struct moxa_str *ch)
+{
+ struct tty_struct *tp;
+
+ if (!(ch->asyncflags & ASYNC_INITIALIZED))
+ return;
+
+ tp = ch->tty;
+
+ MoxaPortDisable(ch->port);
+
+ /*
+ * If we're a modem control device and HUPCL is on, drop RTS & DTR.
+ */
+ if (tp->termios->c_cflag & HUPCL)
+ MoxaPortLineCtrl(ch->port, 0, 0);
+
+ ch->asyncflags &= ~ASYNC_INITIALIZED;
+}
+
+static void receive_data(struct moxa_str *ch)
+{
+ struct tty_struct *tp;
+ struct termios *ts;
+ int i, count, rc, space;
+ unsigned char *charptr, *flagptr;
+ unsigned long flags;
+
+ ts = NULL;
+ tp = ch->tty;
+ if (tp)
+ ts = tp->termios;
+ /**************************************************
+ if ( !tp || !ts || !(ts->c_cflag & CREAD) ) {
+ *****************************************************/
+ if (!tp || !ts) {
+ MoxaPortFlushData(ch->port, 0);
+ return;
+ }
+ space = TTY_FLIPBUF_SIZE - tp->flip.count;
+ if (space <= 0)
+ return;
+ charptr = tp->flip.char_buf_ptr;
+ flagptr = tp->flip.flag_buf_ptr;
+ rc = tp->flip.count;
+ save_flags(flags);
+ cli();
+ count = MoxaPortReadData(ch->port, charptr, space);
+ restore_flags(flags);
+ for (i = 0; i < count; i++)
+ *flagptr++ = 0;
+ charptr += count;
+ rc += count;
+ tp->flip.count = rc;
+ tp->flip.char_buf_ptr = charptr;
+ tp->flip.flag_buf_ptr = flagptr;
+ tty_schedule_flip(ch->tty);
+}
+
+#define Magic_code 0x404
+
+/*
+ * System Configuration
+ */
+/*
+ * for C218 BIOS initialization
+ */
+#define C218_ConfBase 0x800
+#define C218_status (C218_ConfBase + 0) /* BIOS running status */
+#define C218_diag (C218_ConfBase + 2) /* diagnostic status */
+#define C218_key (C218_ConfBase + 4) /* WORD (0x218 for C218) */
+#define C218DLoad_len (C218_ConfBase + 6) /* WORD */
+#define C218check_sum (C218_ConfBase + 8) /* BYTE */
+#define C218chksum_ok (C218_ConfBase + 0x0a) /* BYTE (1:ok) */
+#define C218_TestRx (C218_ConfBase + 0x10) /* 8 bytes for 8 ports */
+#define C218_TestTx (C218_ConfBase + 0x18) /* 8 bytes for 8 ports */
+#define C218_RXerr (C218_ConfBase + 0x20) /* 8 bytes for 8 ports */
+#define C218_ErrFlag (C218_ConfBase + 0x28) /* 8 bytes for 8 ports */
+
+#define C218_LoadBuf 0x0F00
+#define C218_KeyCode 0x218
+#define CP204J_KeyCode 0x204
+
+/*
+ * for C320 BIOS initialization
+ */
+#define C320_ConfBase 0x800
+#define C320_LoadBuf 0x0f00
+#define STS_init 0x05 /* for C320_status */
+
+#define C320_status C320_ConfBase + 0 /* BIOS running status */
+#define C320_diag C320_ConfBase + 2 /* diagnostic status */
+#define C320_key C320_ConfBase + 4 /* WORD (0320H for C320) */
+#define C320DLoad_len C320_ConfBase + 6 /* WORD */
+#define C320check_sum C320_ConfBase + 8 /* WORD */
+#define C320chksum_ok C320_ConfBase + 0x0a /* WORD (1:ok) */
+#define C320bapi_len C320_ConfBase + 0x0c /* WORD */
+#define C320UART_no C320_ConfBase + 0x0e /* WORD */
+
+#define C320_KeyCode 0x320
+
+#define FixPage_addr 0x0000 /* starting addr of static page */
+#define DynPage_addr 0x2000 /* starting addr of dynamic page */
+#define C218_start 0x3000 /* starting addr of C218 BIOS prg */
+#define Control_reg 0x1ff0 /* select page and reset control */
+#define HW_reset 0x80
+
+/*
+ * Function Codes
+ */
+#define FC_CardReset 0x80
+#define FC_ChannelReset 1 /* C320 firmware not supported */
+#define FC_EnableCH 2
+#define FC_DisableCH 3
+#define FC_SetParam 4
+#define FC_SetMode 5
+#define FC_SetRate 6
+#define FC_LineControl 7
+#define FC_LineStatus 8
+#define FC_XmitControl 9
+#define FC_FlushQueue 10
+#define FC_SendBreak 11
+#define FC_StopBreak 12
+#define FC_LoopbackON 13
+#define FC_LoopbackOFF 14
+#define FC_ClrIrqTable 15
+#define FC_SendXon 16
+#define FC_SetTermIrq 17 /* C320 firmware not supported */
+#define FC_SetCntIrq 18 /* C320 firmware not supported */
+#define FC_SetBreakIrq 19
+#define FC_SetLineIrq 20
+#define FC_SetFlowCtl 21
+#define FC_GenIrq 22
+#define FC_InCD180 23
+#define FC_OutCD180 24
+#define FC_InUARTreg 23
+#define FC_OutUARTreg 24
+#define FC_SetXonXoff 25
+#define FC_OutCD180CCR 26
+#define FC_ExtIQueue 27
+#define FC_ExtOQueue 28
+#define FC_ClrLineIrq 29
+#define FC_HWFlowCtl 30
+#define FC_GetClockRate 35
+#define FC_SetBaud 36
+#define FC_SetDataMode 41
+#define FC_GetCCSR 43
+#define FC_GetDataError 45
+#define FC_RxControl 50
+#define FC_ImmSend 51
+#define FC_SetXonState 52
+#define FC_SetXoffState 53
+#define FC_SetRxFIFOTrig 54
+#define FC_SetTxFIFOCnt 55
+#define FC_UnixRate 56
+#define FC_UnixResetTimer 57
+
+#define RxFIFOTrig1 0
+#define RxFIFOTrig4 1
+#define RxFIFOTrig8 2
+#define RxFIFOTrig14 3
+
+/*
+ * Dual-Ported RAM
+ */
+#define DRAM_global 0
+#define INT_data (DRAM_global + 0)
+#define Config_base (DRAM_global + 0x108)
+
+#define IRQindex (INT_data + 0)
+#define IRQpending (INT_data + 4)
+#define IRQtable (INT_data + 8)
+
+/*
+ * Interrupt Status
+ */
+#define IntrRx 0x01 /* receiver data O.K. */
+#define IntrTx 0x02 /* transmit buffer empty */
+#define IntrFunc 0x04 /* function complete */
+#define IntrBreak 0x08 /* received break */
+#define IntrLine 0x10 /* line status change
+ for transmitter */
+#define IntrIntr 0x20 /* received INTR code */
+#define IntrQuit 0x40 /* received QUIT code */
+#define IntrEOF 0x80 /* received EOF code */
+
+#define IntrRxTrigger 0x100 /* rx data count reach tigger value */
+#define IntrTxTrigger 0x200 /* tx data count below trigger value */
+
+#define Magic_no (Config_base + 0)
+#define Card_model_no (Config_base + 2)
+#define Total_ports (Config_base + 4)
+#define Module_cnt (Config_base + 8)
+#define Module_no (Config_base + 10)
+#define Timer_10ms (Config_base + 14)
+#define Disable_IRQ (Config_base + 20)
+#define TMS320_PORT1 (Config_base + 22)
+#define TMS320_PORT2 (Config_base + 24)
+#define TMS320_CLOCK (Config_base + 26)
+
+/*
+ * DATA BUFFER in DRAM
+ */
+#define Extern_table 0x400 /* Base address of the external table
+ (24 words * 64) total 3K bytes
+ (24 words * 128) total 6K bytes */
+#define Extern_size 0x60 /* 96 bytes */
+#define RXrptr 0x00 /* read pointer for RX buffer */
+#define RXwptr 0x02 /* write pointer for RX buffer */
+#define TXrptr 0x04 /* read pointer for TX buffer */
+#define TXwptr 0x06 /* write pointer for TX buffer */
+#define HostStat 0x08 /* IRQ flag and general flag */
+#define FlagStat 0x0A
+#define FlowControl 0x0C /* B7 B6 B5 B4 B3 B2 B1 B0 */
+ /* x x x x | | | | */
+ /* | | | + CTS flow */
+ /* | | +--- RTS flow */
+ /* | +------ TX Xon/Xoff */
+ /* +--------- RX Xon/Xoff */
+#define Break_cnt 0x0E /* received break count */
+#define CD180TXirq 0x10 /* if non-0: enable TX irq */
+#define RX_mask 0x12
+#define TX_mask 0x14
+#define Ofs_rxb 0x16
+#define Ofs_txb 0x18
+#define Page_rxb 0x1A
+#define Page_txb 0x1C
+#define EndPage_rxb 0x1E
+#define EndPage_txb 0x20
+#define Data_error 0x22
+#define RxTrigger 0x28
+#define TxTrigger 0x2a
+
+#define rRXwptr 0x34
+#define Low_water 0x36
+
+#define FuncCode 0x40
+#define FuncArg 0x42
+#define FuncArg1 0x44
+
+#define C218rx_size 0x2000 /* 8K bytes */
+#define C218tx_size 0x8000 /* 32K bytes */
+
+#define C218rx_mask (C218rx_size - 1)
+#define C218tx_mask (C218tx_size - 1)
+
+#define C320p8rx_size 0x2000
+#define C320p8tx_size 0x8000
+#define C320p8rx_mask (C320p8rx_size - 1)
+#define C320p8tx_mask (C320p8tx_size - 1)
+
+#define C320p16rx_size 0x2000
+#define C320p16tx_size 0x4000
+#define C320p16rx_mask (C320p16rx_size - 1)
+#define C320p16tx_mask (C320p16tx_size - 1)
+
+#define C320p24rx_size 0x2000
+#define C320p24tx_size 0x2000
+#define C320p24rx_mask (C320p24rx_size - 1)
+#define C320p24tx_mask (C320p24tx_size - 1)
+
+#define C320p32rx_size 0x1000
+#define C320p32tx_size 0x1000
+#define C320p32rx_mask (C320p32rx_size - 1)
+#define C320p32tx_mask (C320p32tx_size - 1)
+
+#define Page_size 0x2000
+#define Page_mask (Page_size - 1)
+#define C218rx_spage 3
+#define C218tx_spage 4
+#define C218rx_pageno 1
+#define C218tx_pageno 4
+#define C218buf_pageno 5
+
+#define C320p8rx_spage 3
+#define C320p8tx_spage 4
+#define C320p8rx_pgno 1
+#define C320p8tx_pgno 4
+#define C320p8buf_pgno 5
+
+#define C320p16rx_spage 3
+#define C320p16tx_spage 4
+#define C320p16rx_pgno 1
+#define C320p16tx_pgno 2
+#define C320p16buf_pgno 3
+
+#define C320p24rx_spage 3
+#define C320p24tx_spage 4
+#define C320p24rx_pgno 1
+#define C320p24tx_pgno 1
+#define C320p24buf_pgno 2
+
+#define C320p32rx_spage 3
+#define C320p32tx_ofs C320p32rx_size
+#define C320p32tx_spage 3
+#define C320p32buf_pgno 1
+
+/*
+ * Host Status
+ */
+#define WakeupRx 0x01
+#define WakeupTx 0x02
+#define WakeupBreak 0x08
+#define WakeupLine 0x10
+#define WakeupIntr 0x20
+#define WakeupQuit 0x40
+#define WakeupEOF 0x80 /* used in VTIME control */
+#define WakeupRxTrigger 0x100
+#define WakeupTxTrigger 0x200
+/*
+ * Flag status
+ */
+#define Rx_over 0x01
+#define Xoff_state 0x02
+#define Tx_flowOff 0x04
+#define Tx_enable 0x08
+#define CTS_state 0x10
+#define DSR_state 0x20
+#define DCD_state 0x80
+/*
+ * FlowControl
+ */
+#define CTS_FlowCtl 1
+#define RTS_FlowCtl 2
+#define Tx_FlowCtl 4
+#define Rx_FlowCtl 8
+#define IXM_IXANY 0x10
+
+#define LowWater 128
+
+#define DTR_ON 1
+#define RTS_ON 2
+#define CTS_ON 1
+#define DSR_ON 2
+#define DCD_ON 8
+
+/* mode definition */
+#define MX_CS8 0x03
+#define MX_CS7 0x02
+#define MX_CS6 0x01
+#define MX_CS5 0x00
+
+#define MX_STOP1 0x00
+#define MX_STOP15 0x04
+#define MX_STOP2 0x08
+
+#define MX_PARNONE 0x00
+#define MX_PAREVEN 0x40
+#define MX_PARODD 0xC0
+
+/*
+ * Query
+ */
+#define QueryPort MAX_PORTS
+
+
+
+struct mon_str {
+ int tick;
+ int rxcnt[MAX_PORTS];
+ int txcnt[MAX_PORTS];
+};
+typedef struct mon_str mon_st;
+
+#define DCD_changed 0x01
+#define DCD_oldstate 0x80
+
+static unsigned char moxaBuff[10240];
+static void __iomem *moxaIntNdx[MAX_BOARDS];
+static void __iomem *moxaIntPend[MAX_BOARDS];
+static void __iomem *moxaIntTable[MAX_BOARDS];
+static char moxaChkPort[MAX_PORTS];
+static char moxaLineCtrl[MAX_PORTS];
+static void __iomem *moxaTableAddr[MAX_PORTS];
+static long moxaCurBaud[MAX_PORTS];
+static char moxaDCDState[MAX_PORTS];
+static char moxaLowChkFlag[MAX_PORTS];
+static int moxaLowWaterChk;
+static int moxaCard;
+static mon_st moxaLog;
+static int moxaFuncTout;
+static ushort moxaBreakCnt[MAX_PORTS];
+
+static void moxadelay(int);
+static void moxafunc(void __iomem *, int, ushort);
+static void wait_finish(void __iomem *);
+static void low_water_check(void __iomem *);
+static int moxaloadbios(int, unsigned char __user *, int);
+static int moxafindcard(int);
+static int moxaload320b(int, unsigned char __user *, int);
+static int moxaloadcode(int, unsigned char __user *, int);
+static int moxaloadc218(int, void __iomem *, int);
+static int moxaloadc320(int, void __iomem *, int, int *);
+
+/*****************************************************************************
+ * Driver level functions: *
+ * 1. MoxaDriverInit(void); *
+ * 2. MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port); *
+ * 3. MoxaDriverPoll(void); *
+ *****************************************************************************/
+void MoxaDriverInit(void)
+{
+ int i;
+
+ moxaFuncTout = HZ / 2; /* 500 mini-seconds */
+ moxaCard = 0;
+ moxaLog.tick = 0;
+ moxaLowWaterChk = 0;
+ for (i = 0; i < MAX_PORTS; i++) {
+ moxaChkPort[i] = 0;
+ moxaLowChkFlag[i] = 0;
+ moxaLineCtrl[i] = 0;
+ moxaLog.rxcnt[i] = 0;
+ moxaLog.txcnt[i] = 0;
+ }
+}
+
+#define MOXA 0x400
+#define MOXA_GET_IQUEUE (MOXA + 1) /* get input buffered count */
+#define MOXA_GET_OQUEUE (MOXA + 2) /* get output buffered count */
+#define MOXA_INIT_DRIVER (MOXA + 6) /* moxaCard=0 */
+#define MOXA_LOAD_BIOS (MOXA + 9) /* download BIOS */
+#define MOXA_FIND_BOARD (MOXA + 10) /* Check if MOXA card exist? */
+#define MOXA_LOAD_C320B (MOXA + 11) /* download 320B firmware */
+#define MOXA_LOAD_CODE (MOXA + 12) /* download firmware */
+#define MOXA_GETDATACOUNT (MOXA + 23)
+#define MOXA_GET_IOQUEUE (MOXA + 27)
+#define MOXA_FLUSH_QUEUE (MOXA + 28)
+#define MOXA_GET_CONF (MOXA + 35) /* configuration */
+#define MOXA_GET_MAJOR (MOXA + 63)
+#define MOXA_GET_CUMAJOR (MOXA + 64)
+#define MOXA_GETMSTATUS (MOXA + 65)
+
+
+struct moxaq_str {
+ int inq;
+ int outq;
+};
+
+struct dl_str {
+ char __user *buf;
+ int len;
+ int cardno;
+};
+
+static struct moxaq_str temp_queue[MAX_PORTS];
+static struct dl_str dltmp;
+
+void MoxaPortFlushData(int port, int mode)
+{
+ void __iomem *ofsAddr;
+ if ((mode < 0) || (mode > 2))
+ return;
+ ofsAddr = moxaTableAddr[port];
+ moxafunc(ofsAddr, FC_FlushQueue, mode);
+ if (mode != 1) {
+ moxaLowChkFlag[port] = 0;
+ low_water_check(ofsAddr);
+ }
+}
+
+int MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port)
+{
+ int i;
+ int status;
+ int MoxaPortTxQueue(int), MoxaPortRxQueue(int);
+ void __user *argp = (void __user *)arg;
+
+ if (port == QueryPort) {
+ if ((cmd != MOXA_GET_CONF) && (cmd != MOXA_INIT_DRIVER) &&
+ (cmd != MOXA_LOAD_BIOS) && (cmd != MOXA_FIND_BOARD) && (cmd != MOXA_LOAD_C320B) &&
+ (cmd != MOXA_LOAD_CODE) && (cmd != MOXA_GETDATACOUNT) &&
+ (cmd != MOXA_GET_IOQUEUE) && (cmd != MOXA_GET_MAJOR) &&
+ (cmd != MOXA_GET_CUMAJOR) && (cmd != MOXA_GETMSTATUS))
+ return (-EINVAL);
+ }
+ switch (cmd) {
+ case MOXA_GET_CONF:
+ if(copy_to_user(argp, &moxa_boards, MAX_BOARDS * sizeof(moxa_board_conf)))
+ return -EFAULT;
+ return (0);
+ case MOXA_INIT_DRIVER:
+ if ((int) arg == 0x404)
+ MoxaDriverInit();
+ return (0);
+ case MOXA_GETDATACOUNT:
+ moxaLog.tick = jiffies;
+ if(copy_to_user(argp, &moxaLog, sizeof(mon_st)))
+ return -EFAULT;
+ return (0);
+ case MOXA_FLUSH_QUEUE:
+ MoxaPortFlushData(port, arg);
+ return (0);
+ case MOXA_GET_IOQUEUE:
+ for (i = 0; i < MAX_PORTS; i++) {
+ if (moxaChkPort[i]) {
+ temp_queue[i].inq = MoxaPortRxQueue(i);
+ temp_queue[i].outq = MoxaPortTxQueue(i);
+ }
+ }
+ if(copy_to_user(argp, temp_queue, sizeof(struct moxaq_str) * MAX_PORTS))
+ return -EFAULT;
+ return (0);
+ case MOXA_GET_OQUEUE:
+ i = MoxaPortTxQueue(port);
+ return put_user(i, (unsigned long __user *)argp);
+ case MOXA_GET_IQUEUE:
+ i = MoxaPortRxQueue(port);
+ return put_user(i, (unsigned long __user *)argp);
+ case MOXA_GET_MAJOR:
+ if(copy_to_user(argp, &ttymajor, sizeof(int)))
+ return -EFAULT;
+ return 0;
+ case MOXA_GET_CUMAJOR:
+ i = 0;
+ if(copy_to_user(argp, &i, sizeof(int)))
+ return -EFAULT;
+ return 0;
+ case MOXA_GETMSTATUS:
+ for (i = 0; i < MAX_PORTS; i++) {
+ GMStatus[i].ri = 0;
+ GMStatus[i].dcd = 0;
+ GMStatus[i].dsr = 0;
+ GMStatus[i].cts = 0;
+ if (!moxaChkPort[i]) {
+ continue;
+ } else {
+ status = MoxaPortLineStatus(moxaChannels[i].port);
+ if (status & 1)
+ GMStatus[i].cts = 1;
+ if (status & 2)
+ GMStatus[i].dsr = 1;
+ if (status & 4)
+ GMStatus[i].dcd = 1;
+ }
+
+ if (!moxaChannels[i].tty || !moxaChannels[i].tty->termios)
+ GMStatus[i].cflag = moxaChannels[i].cflag;
+ else
+ GMStatus[i].cflag = moxaChannels[i].tty->termios->c_cflag;
+ }
+ if(copy_to_user(argp, GMStatus, sizeof(struct mxser_mstatus) * MAX_PORTS))
+ return -EFAULT;
+ return 0;
+ default:
+ return (-ENOIOCTLCMD);
+ case MOXA_LOAD_BIOS:
+ case MOXA_FIND_BOARD:
+ case MOXA_LOAD_C320B:
+ case MOXA_LOAD_CODE:
+ break;
+ }
+
+ if(copy_from_user(&dltmp, argp, sizeof(struct dl_str)))
+ return -EFAULT;
+ if(dltmp.cardno < 0 || dltmp.cardno >= MAX_BOARDS)
+ return -EINVAL;
+
+ switch(cmd)
+ {
+ case MOXA_LOAD_BIOS:
+ i = moxaloadbios(dltmp.cardno, dltmp.buf, dltmp.len);
+ return (i);
+ case MOXA_FIND_BOARD:
+ return moxafindcard(dltmp.cardno);
+ case MOXA_LOAD_C320B:
+ moxaload320b(dltmp.cardno, dltmp.buf, dltmp.len);
+ default: /* to keep gcc happy */
+ return (0);
+ case MOXA_LOAD_CODE:
+ i = moxaloadcode(dltmp.cardno, dltmp.buf, dltmp.len);
+ if (i == -1)
+ return (-EFAULT);
+ return (i);
+
+ }
+}
+
+int MoxaDriverPoll(void)
+{
+ register ushort temp;
+ register int card;
+ void __iomem *ofsAddr;
+ void __iomem *ip;
+ int port, p, ports;
+
+ if (moxaCard == 0)
+ return (-1);
+ for (card = 0; card < MAX_BOARDS; card++) {
+ if ((ports = moxa_boards[card].numPorts) == 0)
+ continue;
+ if (readb(moxaIntPend[card]) == 0xff) {
+ ip = moxaIntTable[card] + readb(moxaIntNdx[card]);
+ p = card * MAX_PORTS_PER_BOARD;
+ ports <<= 1;
+ for (port = 0; port < ports; port += 2, p++) {
+ if ((temp = readw(ip + port)) != 0) {
+ writew(0, ip + port);
+ ofsAddr = moxaTableAddr[p];
+ if (temp & IntrTx)
+ writew(readw(ofsAddr + HostStat) & ~WakeupTx, ofsAddr + HostStat);
+ if (temp & IntrBreak) {
+ moxaBreakCnt[p]++;
+ }
+ if (temp & IntrLine) {
+ if (readb(ofsAddr + FlagStat) & DCD_state) {
+ if ((moxaDCDState[p] & DCD_oldstate) == 0)
+ moxaDCDState[p] = (DCD_oldstate |
+ DCD_changed);
+ } else {
+ if (moxaDCDState[p] & DCD_oldstate)
+ moxaDCDState[p] = DCD_changed;
+ }
+ }
+ }
+ }
+ writeb(0, moxaIntPend[card]);
+ }
+ if (moxaLowWaterChk) {
+ p = card * MAX_PORTS_PER_BOARD;
+ for (port = 0; port < ports; port++, p++) {
+ if (moxaLowChkFlag[p]) {
+ moxaLowChkFlag[p] = 0;
+ ofsAddr = moxaTableAddr[p];
+ low_water_check(ofsAddr);
+ }
+ }
+ }
+ }
+ moxaLowWaterChk = 0;
+ return (0);
+}
+
+/*****************************************************************************
+ * Card level function: *
+ * 1. MoxaPortsOfCard(int cardno); *
+ *****************************************************************************/
+int MoxaPortsOfCard(int cardno)
+{
+
+ if (moxa_boards[cardno].boardType == 0)
+ return (0);
+ return (moxa_boards[cardno].numPorts);
+}
+
+/*****************************************************************************
+ * Port level functions: *
+ * 1. MoxaPortIsValid(int port); *
+ * 2. MoxaPortEnable(int port); *
+ * 3. MoxaPortDisable(int port); *
+ * 4. MoxaPortGetMaxBaud(int port); *
+ * 5. MoxaPortGetCurBaud(int port); *
+ * 6. MoxaPortSetBaud(int port, long baud); *
+ * 7. MoxaPortSetMode(int port, int databit, int stopbit, int parity); *
+ * 8. MoxaPortSetTermio(int port, unsigned char *termio); *
+ * 9. MoxaPortGetLineOut(int port, int *dtrState, int *rtsState); *
+ * 10. MoxaPortLineCtrl(int port, int dtrState, int rtsState); *
+ * 11. MoxaPortFlowCtrl(int port, int rts, int cts, int rx, int tx,int xany); *
+ * 12. MoxaPortLineStatus(int port); *
+ * 13. MoxaPortDCDChange(int port); *
+ * 14. MoxaPortDCDON(int port); *
+ * 15. MoxaPortFlushData(int port, int mode); *
+ * 16. MoxaPortWriteData(int port, unsigned char * buffer, int length); *
+ * 17. MoxaPortReadData(int port, unsigned char * buffer, int length); *
+ * 18. MoxaPortTxBufSize(int port); *
+ * 19. MoxaPortRxBufSize(int port); *
+ * 20. MoxaPortTxQueue(int port); *
+ * 21. MoxaPortTxFree(int port); *
+ * 22. MoxaPortRxQueue(int port); *
+ * 23. MoxaPortRxFree(int port); *
+ * 24. MoxaPortTxDisable(int port); *
+ * 25. MoxaPortTxEnable(int port); *
+ * 26. MoxaPortGetBrkCnt(int port); *
+ * 27. MoxaPortResetBrkCnt(int port); *
+ * 28. MoxaPortSetXonXoff(int port, int xonValue, int xoffValue); *
+ * 29. MoxaPortIsTxHold(int port); *
+ * 30. MoxaPortSendBreak(int port, int ticks); *
+ *****************************************************************************/
+/*
+ * Moxa Port Number Description:
+ *
+ * MOXA serial driver supports up to 4 MOXA-C218/C320 boards. And,
+ * the port number using in MOXA driver functions will be 0 to 31 for
+ * first MOXA board, 32 to 63 for second, 64 to 95 for third and 96
+ * to 127 for fourth. For example, if you setup three MOXA boards,
+ * first board is C218, second board is C320-16 and third board is
+ * C320-32. The port number of first board (C218 - 8 ports) is from
+ * 0 to 7. The port number of second board (C320 - 16 ports) is form
+ * 32 to 47. The port number of third board (C320 - 32 ports) is from
+ * 64 to 95. And those port numbers form 8 to 31, 48 to 63 and 96 to
+ * 127 will be invalid.
+ *
+ *
+ * Moxa Functions Description:
+ *
+ * Function 1: Driver initialization routine, this routine must be
+ * called when initialized driver.
+ * Syntax:
+ * void MoxaDriverInit();
+ *
+ *
+ * Function 2: Moxa driver private IOCTL command processing.
+ * Syntax:
+ * int MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port);
+ *
+ * unsigned int cmd : IOCTL command
+ * unsigned long arg : IOCTL argument
+ * int port : port number (0 - 127)
+ *
+ * return: 0 (OK)
+ * -EINVAL
+ * -ENOIOCTLCMD
+ *
+ *
+ * Function 3: Moxa driver polling process routine.
+ * Syntax:
+ * int MoxaDriverPoll(void);
+ *
+ * return: 0 ; polling O.K.
+ * -1 : no any Moxa card.
+ *
+ *
+ * Function 4: Get the ports of this card.
+ * Syntax:
+ * int MoxaPortsOfCard(int cardno);
+ *
+ * int cardno : card number (0 - 3)
+ *
+ * return: 0 : this card is invalid
+ * 8/16/24/32
+ *
+ *
+ * Function 5: Check this port is valid or invalid
+ * Syntax:
+ * int MoxaPortIsValid(int port);
+ * int port : port number (0 - 127, ref port description)
+ *
+ * return: 0 : this port is invalid
+ * 1 : this port is valid
+ *
+ *
+ * Function 6: Enable this port to start Tx/Rx data.
+ * Syntax:
+ * void MoxaPortEnable(int port);
+ * int port : port number (0 - 127)
+ *
+ *
+ * Function 7: Disable this port
+ * Syntax:
+ * void MoxaPortDisable(int port);
+ * int port : port number (0 - 127)
+ *
+ *
+ * Function 8: Get the maximun available baud rate of this port.
+ * Syntax:
+ * long MoxaPortGetMaxBaud(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 : this port is invalid
+ * 38400/57600/115200 bps
+ *
+ *
+ * Function 9: Get the current baud rate of this port.
+ * Syntax:
+ * long MoxaPortGetCurBaud(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 : this port is invalid
+ * 50 - 115200 bps
+ *
+ *
+ * Function 10: Setting baud rate of this port.
+ * Syntax:
+ * long MoxaPortSetBaud(int port, long baud);
+ * int port : port number (0 - 127)
+ * long baud : baud rate (50 - 115200)
+ *
+ * return: 0 : this port is invalid or baud < 50
+ * 50 - 115200 : the real baud rate set to the port, if
+ * the argument baud is large than maximun
+ * available baud rate, the real setting
+ * baud rate will be the maximun baud rate.
+ *
+ *
+ * Function 11: Setting the data-bits/stop-bits/parity of this port
+ * Syntax:
+ * int MoxaPortSetMode(int port, int databits, int stopbits, int parity);
+ * int port : port number (0 - 127)
+ * int databits : data bits (8/7/6/5)
+ * int stopbits : stop bits (2/1/0, 0 show 1.5 stop bits)
+ int parity : parity (0:None,1:Odd,2:Even,3:Mark,4:Space)
+ *
+ * return: -1 : invalid parameter
+ * 0 : setting O.K.
+ *
+ *
+ * Function 12: Configure the port.
+ * Syntax:
+ * int MoxaPortSetTermio(int port, struct termios *termio);
+ * int port : port number (0 - 127)
+ * struct termios * termio : termio structure pointer
+ *
+ * return: -1 : this port is invalid or termio == NULL
+ * 0 : setting O.K.
+ *
+ *
+ * Function 13: Get the DTR/RTS state of this port.
+ * Syntax:
+ * int MoxaPortGetLineOut(int port, int *dtrState, int *rtsState);
+ * int port : port number (0 - 127)
+ * int * dtrState : pointer to INT to receive the current DTR
+ * state. (if NULL, this function will not
+ * write to this address)
+ * int * rtsState : pointer to INT to receive the current RTS
+ * state. (if NULL, this function will not
+ * write to this address)
+ *
+ * return: -1 : this port is invalid
+ * 0 : O.K.
+ *
+ *
+ * Function 14: Setting the DTR/RTS output state of this port.
+ * Syntax:
+ * void MoxaPortLineCtrl(int port, int dtrState, int rtsState);
+ * int port : port number (0 - 127)
+ * int dtrState : DTR output state (0: off, 1: on)
+ * int rtsState : RTS output state (0: off, 1: on)
+ *
+ *
+ * Function 15: Setting the flow control of this port.
+ * Syntax:
+ * void MoxaPortFlowCtrl(int port, int rtsFlow, int ctsFlow, int rxFlow,
+ * int txFlow,int xany);
+ * int port : port number (0 - 127)
+ * int rtsFlow : H/W RTS flow control (0: no, 1: yes)
+ * int ctsFlow : H/W CTS flow control (0: no, 1: yes)
+ * int rxFlow : S/W Rx XON/XOFF flow control (0: no, 1: yes)
+ * int txFlow : S/W Tx XON/XOFF flow control (0: no, 1: yes)
+ * int xany : S/W XANY flow control (0: no, 1: yes)
+ *
+ *
+ * Function 16: Get ths line status of this port
+ * Syntax:
+ * int MoxaPortLineStatus(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: Bit 0 - CTS state (0: off, 1: on)
+ * Bit 1 - DSR state (0: off, 1: on)
+ * Bit 2 - DCD state (0: off, 1: on)
+ *
+ *
+ * Function 17: Check the DCD state has changed since the last read
+ * of this function.
+ * Syntax:
+ * int MoxaPortDCDChange(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 : no changed
+ * 1 : DCD has changed
+ *
+ *
+ * Function 18: Check ths current DCD state is ON or not.
+ * Syntax:
+ * int MoxaPortDCDON(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 : DCD off
+ * 1 : DCD on
+ *
+ *
+ * Function 19: Flush the Rx/Tx buffer data of this port.
+ * Syntax:
+ * void MoxaPortFlushData(int port, int mode);
+ * int port : port number (0 - 127)
+ * int mode
+ * 0 : flush the Rx buffer
+ * 1 : flush the Tx buffer
+ * 2 : flush the Rx and Tx buffer
+ *
+ *
+ * Function 20: Write data.
+ * Syntax:
+ * int MoxaPortWriteData(int port, unsigned char * buffer, int length);
+ * int port : port number (0 - 127)
+ * unsigned char * buffer : pointer to write data buffer.
+ * int length : write data length
+ *
+ * return: 0 - length : real write data length
+ *
+ *
+ * Function 21: Read data.
+ * Syntax:
+ * int MoxaPortReadData(int port, unsigned char * buffer, int length);
+ * int port : port number (0 - 127)
+ * unsigned char * buffer : pointer to read data buffer.
+ * int length : read data buffer length
+ *
+ * return: 0 - length : real read data length
+ *
+ *
+ * Function 22: Get the Tx buffer size of this port
+ * Syntax:
+ * int MoxaPortTxBufSize(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Tx buffer size
+ *
+ *
+ * Function 23: Get the Rx buffer size of this port
+ * Syntax:
+ * int MoxaPortRxBufSize(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Rx buffer size
+ *
+ *
+ * Function 24: Get the Tx buffer current queued data bytes
+ * Syntax:
+ * int MoxaPortTxQueue(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Tx buffer current queued data bytes
+ *
+ *
+ * Function 25: Get the Tx buffer current free space
+ * Syntax:
+ * int MoxaPortTxFree(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Tx buffer current free space
+ *
+ *
+ * Function 26: Get the Rx buffer current queued data bytes
+ * Syntax:
+ * int MoxaPortRxQueue(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Rx buffer current queued data bytes
+ *
+ *
+ * Function 27: Get the Rx buffer current free space
+ * Syntax:
+ * int MoxaPortRxFree(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: .. : Rx buffer current free space
+ *
+ *
+ * Function 28: Disable port data transmission.
+ * Syntax:
+ * void MoxaPortTxDisable(int port);
+ * int port : port number (0 - 127)
+ *
+ *
+ * Function 29: Enable port data transmission.
+ * Syntax:
+ * void MoxaPortTxEnable(int port);
+ * int port : port number (0 - 127)
+ *
+ *
+ * Function 30: Get the received BREAK signal count.
+ * Syntax:
+ * int MoxaPortGetBrkCnt(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 - .. : BREAK signal count
+ *
+ *
+ * Function 31: Get the received BREAK signal count and reset it.
+ * Syntax:
+ * int MoxaPortResetBrkCnt(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 - .. : BREAK signal count
+ *
+ *
+ * Function 32: Set the S/W flow control new XON/XOFF value, default
+ * XON is 0x11 & XOFF is 0x13.
+ * Syntax:
+ * void MoxaPortSetXonXoff(int port, int xonValue, int xoffValue);
+ * int port : port number (0 - 127)
+ * int xonValue : new XON value (0 - 255)
+ * int xoffValue : new XOFF value (0 - 255)
+ *
+ *
+ * Function 33: Check this port's transmission is hold by remote site
+ * because the flow control.
+ * Syntax:
+ * int MoxaPortIsTxHold(int port);
+ * int port : port number (0 - 127)
+ *
+ * return: 0 : normal
+ * 1 : hold by remote site
+ *
+ *
+ * Function 34: Send out a BREAK signal.
+ * Syntax:
+ * void MoxaPortSendBreak(int port, int ms100);
+ * int port : port number (0 - 127)
+ * int ms100 : break signal time interval.
+ * unit: 100 mini-second. if ms100 == 0, it will
+ * send out a about 250 ms BREAK signal.
+ *
+ */
+int MoxaPortIsValid(int port)
+{
+
+ if (moxaCard == 0)
+ return (0);
+ if (moxaChkPort[port] == 0)
+ return (0);
+ return (1);
+}
+
+void MoxaPortEnable(int port)
+{
+ void __iomem *ofsAddr;
+ int MoxaPortLineStatus(int);
+ short lowwater = 512;
+
+ ofsAddr = moxaTableAddr[port];
+ writew(lowwater, ofsAddr + Low_water);
+ moxaBreakCnt[port] = 0;
+ if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+ (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+ moxafunc(ofsAddr, FC_SetBreakIrq, 0);
+ } else {
+ writew(readw(ofsAddr + HostStat) | WakeupBreak, ofsAddr + HostStat);
+ }
+
+ moxafunc(ofsAddr, FC_SetLineIrq, Magic_code);
+ moxafunc(ofsAddr, FC_FlushQueue, 2);
+
+ moxafunc(ofsAddr, FC_EnableCH, Magic_code);
+ MoxaPortLineStatus(port);
+}
+
+void MoxaPortDisable(int port)
+{
+ void __iomem *ofsAddr = moxaTableAddr[port];
+
+ moxafunc(ofsAddr, FC_SetFlowCtl, 0); /* disable flow control */
+ moxafunc(ofsAddr, FC_ClrLineIrq, Magic_code);
+ writew(0, ofsAddr + HostStat);
+ moxafunc(ofsAddr, FC_DisableCH, Magic_code);
+}
+
+long MoxaPortGetMaxBaud(int port)
+{
+ if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+ (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI))
+ return (460800L);
+ else
+ return (921600L);
+}
+
+
+long MoxaPortSetBaud(int port, long baud)
+{
+ void __iomem *ofsAddr;
+ long max, clock;
+ unsigned int val;
+
+ if ((baud < 50L) || ((max = MoxaPortGetMaxBaud(port)) == 0))
+ return (0);
+ ofsAddr = moxaTableAddr[port];
+ if (baud > max)
+ baud = max;
+ if (max == 38400L)
+ clock = 614400L; /* for 9.8304 Mhz : max. 38400 bps */
+ else if (max == 57600L)
+ clock = 691200L; /* for 11.0592 Mhz : max. 57600 bps */
+ else
+ clock = 921600L; /* for 14.7456 Mhz : max. 115200 bps */
+ val = clock / baud;
+ moxafunc(ofsAddr, FC_SetBaud, val);
+ baud = clock / val;
+ moxaCurBaud[port] = baud;
+ return (baud);
+}
+
+int MoxaPortSetTermio(int port, struct termios *termio)
+{
+ void __iomem *ofsAddr;
+ tcflag_t cflag;
+ long baud;
+ tcflag_t mode = 0;
+
+ if (moxaChkPort[port] == 0 || termio == 0)
+ return (-1);
+ ofsAddr = moxaTableAddr[port];
+ cflag = termio->c_cflag; /* termio->c_cflag */
+
+ mode = termio->c_cflag & CSIZE;
+ if (mode == CS5)
+ mode = MX_CS5;
+ else if (mode == CS6)
+ mode = MX_CS6;
+ else if (mode == CS7)
+ mode = MX_CS7;
+ else if (mode == CS8)
+ mode = MX_CS8;
+
+ if (termio->c_cflag & CSTOPB) {
+ if (mode == MX_CS5)
+ mode |= MX_STOP15;
+ else
+ mode |= MX_STOP2;
+ } else
+ mode |= MX_STOP1;
+
+ if (termio->c_cflag & PARENB) {
+ if (termio->c_cflag & PARODD)
+ mode |= MX_PARODD;
+ else
+ mode |= MX_PAREVEN;
+ } else
+ mode |= MX_PARNONE;
+
+ moxafunc(ofsAddr, FC_SetDataMode, (ushort) mode);
+
+ cflag &= (CBAUD | CBAUDEX);
+#ifndef B921600
+#define B921600 (B460800+1)
+#endif
+ switch (cflag) {
+ case B921600:
+ baud = 921600L;
+ break;
+ case B460800:
+ baud = 460800L;
+ break;
+ case B230400:
+ baud = 230400L;
+ break;
+ case B115200:
+ baud = 115200L;
+ break;
+ case B57600:
+ baud = 57600L;
+ break;
+ case B38400:
+ baud = 38400L;
+ break;
+ case B19200:
+ baud = 19200L;
+ break;
+ case B9600:
+ baud = 9600L;
+ break;
+ case B4800:
+ baud = 4800L;
+ break;
+ case B2400:
+ baud = 2400L;
+ break;
+ case B1800:
+ baud = 1800L;
+ break;
+ case B1200:
+ baud = 1200L;
+ break;
+ case B600:
+ baud = 600L;
+ break;
+ case B300:
+ baud = 300L;
+ break;
+ case B200:
+ baud = 200L;
+ break;
+ case B150:
+ baud = 150L;
+ break;
+ case B134:
+ baud = 134L;
+ break;
+ case B110:
+ baud = 110L;
+ break;
+ case B75:
+ baud = 75L;
+ break;
+ case B50:
+ baud = 50L;
+ break;
+ default:
+ baud = 0;
+ }
+ if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+ (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+ if (baud == 921600L)
+ return (-1);
+ }
+ MoxaPortSetBaud(port, baud);
+
+ if (termio->c_iflag & (IXON | IXOFF | IXANY)) {
+ writeb(termio->c_cc[VSTART], ofsAddr + FuncArg);
+ writeb(termio->c_cc[VSTOP], ofsAddr + FuncArg1);
+ writeb(FC_SetXonXoff, ofsAddr + FuncCode);
+ wait_finish(ofsAddr);
+
+ }
+ return (0);
+}
+
+int MoxaPortGetLineOut(int port, int *dtrState, int *rtsState)
+{
+
+ if (!MoxaPortIsValid(port))
+ return (-1);
+ if (dtrState) {
+ if (moxaLineCtrl[port] & DTR_ON)
+ *dtrState = 1;
+ else
+ *dtrState = 0;
+ }
+ if (rtsState) {
+ if (moxaLineCtrl[port] & RTS_ON)
+ *rtsState = 1;
+ else
+ *rtsState = 0;
+ }
+ return (0);
+}
+
+void MoxaPortLineCtrl(int port, int dtr, int rts)
+{
+ void __iomem *ofsAddr;
+ int mode;
+
+ ofsAddr = moxaTableAddr[port];
+ mode = 0;
+ if (dtr)
+ mode |= DTR_ON;
+ if (rts)
+ mode |= RTS_ON;
+ moxaLineCtrl[port] = mode;
+ moxafunc(ofsAddr, FC_LineControl, mode);
+}
+
+void MoxaPortFlowCtrl(int port, int rts, int cts, int txflow, int rxflow, int txany)
+{
+ void __iomem *ofsAddr;
+ int mode;
+
+ ofsAddr = moxaTableAddr[port];
+ mode = 0;
+ if (rts)
+ mode |= RTS_FlowCtl;
+ if (cts)
+ mode |= CTS_FlowCtl;
+ if (txflow)
+ mode |= Tx_FlowCtl;
+ if (rxflow)
+ mode |= Rx_FlowCtl;
+ if (txany)
+ mode |= IXM_IXANY;
+ moxafunc(ofsAddr, FC_SetFlowCtl, mode);
+}
+
+int MoxaPortLineStatus(int port)
+{
+ void __iomem *ofsAddr;
+ int val;
+
+ ofsAddr = moxaTableAddr[port];
+ if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+ (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+ moxafunc(ofsAddr, FC_LineStatus, 0);
+ val = readw(ofsAddr + FuncArg);
+ } else {
+ val = readw(ofsAddr + FlagStat) >> 4;
+ }
+ val &= 0x0B;
+ if (val & 8) {
+ val |= 4;
+ if ((moxaDCDState[port] & DCD_oldstate) == 0)
+ moxaDCDState[port] = (DCD_oldstate | DCD_changed);
+ } else {
+ if (moxaDCDState[port] & DCD_oldstate)
+ moxaDCDState[port] = DCD_changed;
+ }
+ val &= 7;
+ return (val);
+}
+
+int MoxaPortDCDChange(int port)
+{
+ int n;
+
+ if (moxaChkPort[port] == 0)
+ return (0);
+ n = moxaDCDState[port];
+ moxaDCDState[port] &= ~DCD_changed;
+ n &= DCD_changed;
+ return (n);
+}
+
+int MoxaPortDCDON(int port)
+{
+ int n;
+
+ if (moxaChkPort[port] == 0)
+ return (0);
+ if (moxaDCDState[port] & DCD_oldstate)
+ n = 1;
+ else
+ n = 0;
+ return (n);
+}
+
+
+/*
+ int MoxaDumpMem(int port, unsigned char * buffer, int len)
+ {
+ int i;
+ unsigned long baseAddr,ofsAddr,ofs;
+
+ baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+ ofs = baseAddr + DynPage_addr + pageofs;
+ if (len > 0x2000L)
+ len = 0x2000L;
+ for (i = 0; i < len; i++)
+ buffer[i] = readb(ofs+i);
+ }
+ */
+
+
+int MoxaPortWriteData(int port, unsigned char * buffer, int len)
+{
+ int c, total, i;
+ ushort tail;
+ int cnt;
+ ushort head, tx_mask, spage, epage;
+ ushort pageno, pageofs, bufhead;
+ void __iomem *baseAddr, *ofsAddr, *ofs;
+
+ ofsAddr = moxaTableAddr[port];
+ baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+ tx_mask = readw(ofsAddr + TX_mask);
+ spage = readw(ofsAddr + Page_txb);
+ epage = readw(ofsAddr + EndPage_txb);
+ tail = readw(ofsAddr + TXwptr);
+ head = readw(ofsAddr + TXrptr);
+ c = (head > tail) ? (head - tail - 1)
+ : (head - tail + tx_mask);
+ if (c > len)
+ c = len;
+ moxaLog.txcnt[port] += c;
+ total = c;
+ if (spage == epage) {
+ bufhead = readw(ofsAddr + Ofs_txb);
+ writew(spage, baseAddr + Control_reg);
+ while (c > 0) {
+ if (head > tail)
+ len = head - tail - 1;
+ else
+ len = tx_mask + 1 - tail;
+ len = (c > len) ? len : c;
+ ofs = baseAddr + DynPage_addr + bufhead + tail;
+ for (i = 0; i < len; i++)
+ writeb(*buffer++, ofs + i);
+ tail = (tail + len) & tx_mask;
+ c -= len;
+ }
+ writew(tail, ofsAddr + TXwptr);
+ } else {
+ len = c;
+ pageno = spage + (tail >> 13);
+ pageofs = tail & Page_mask;
+ do {
+ cnt = Page_size - pageofs;
+ if (cnt > c)
+ cnt = c;
+ c -= cnt;
+ writeb(pageno, baseAddr + Control_reg);
+ ofs = baseAddr + DynPage_addr + pageofs;
+ for (i = 0; i < cnt; i++)
+ writeb(*buffer++, ofs + i);
+ if (c == 0) {
+ writew((tail + len) & tx_mask, ofsAddr + TXwptr);
+ break;
+ }
+ if (++pageno == epage)
+ pageno = spage;
+ pageofs = 0;
+ } while (1);
+ }
+ writeb(1, ofsAddr + CD180TXirq); /* start to send */
+ return (total);
+}
+
+int MoxaPortReadData(int port, unsigned char * buffer, int space)
+{
+ register ushort head, pageofs;
+ int i, count, cnt, len, total, remain;
+ ushort tail, rx_mask, spage, epage;
+ ushort pageno, bufhead;
+ void __iomem *baseAddr, *ofsAddr, *ofs;
+
+ ofsAddr = moxaTableAddr[port];
+ baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+ head = readw(ofsAddr + RXrptr);
+ tail = readw(ofsAddr + RXwptr);
+ rx_mask = readw(ofsAddr + RX_mask);
+ spage = readw(ofsAddr + Page_rxb);
+ epage = readw(ofsAddr + EndPage_rxb);
+ count = (tail >= head) ? (tail - head)
+ : (tail - head + rx_mask + 1);
+ if (count == 0)
+ return (0);
+
+ total = (space > count) ? count : space;
+ remain = count - total;
+ moxaLog.rxcnt[port] += total;
+ count = total;
+ if (spage == epage) {
+ bufhead = readw(ofsAddr + Ofs_rxb);
+ writew(spage, baseAddr + Control_reg);
+ while (count > 0) {
+ if (tail >= head)
+ len = tail - head;
+ else
+ len = rx_mask + 1 - head;
+ len = (count > len) ? len : count;
+ ofs = baseAddr + DynPage_addr + bufhead + head;
+ for (i = 0; i < len; i++)
+ *buffer++ = readb(ofs + i);
+ head = (head + len) & rx_mask;
+ count -= len;
+ }
+ writew(head, ofsAddr + RXrptr);
+ } else {
+ len = count;
+ pageno = spage + (head >> 13);
+ pageofs = head & Page_mask;
+ do {
+ cnt = Page_size - pageofs;
+ if (cnt > count)
+ cnt = count;
+ count -= cnt;
+ writew(pageno, baseAddr + Control_reg);
+ ofs = baseAddr + DynPage_addr + pageofs;
+ for (i = 0; i < cnt; i++)
+ *buffer++ = readb(ofs + i);
+ if (count == 0) {
+ writew((head + len) & rx_mask, ofsAddr + RXrptr);
+ break;
+ }
+ if (++pageno == epage)
+ pageno = spage;
+ pageofs = 0;
+ } while (1);
+ }
+ if ((readb(ofsAddr + FlagStat) & Xoff_state) && (remain < LowWater)) {
+ moxaLowWaterChk = 1;
+ moxaLowChkFlag[port] = 1;
+ }
+ return (total);
+}
+
+
+int MoxaPortTxQueue(int port)
+{
+ void __iomem *ofsAddr;
+ ushort rptr, wptr, mask;
+ int len;
+
+ ofsAddr = moxaTableAddr[port];
+ rptr = readw(ofsAddr + TXrptr);
+ wptr = readw(ofsAddr + TXwptr);
+ mask = readw(ofsAddr + TX_mask);
+ len = (wptr - rptr) & mask;
+ return (len);
+}
+
+int MoxaPortTxFree(int port)
+{
+ void __iomem *ofsAddr;
+ ushort rptr, wptr, mask;
+ int len;
+
+ ofsAddr = moxaTableAddr[port];
+ rptr = readw(ofsAddr + TXrptr);
+ wptr = readw(ofsAddr + TXwptr);
+ mask = readw(ofsAddr + TX_mask);
+ len = mask - ((wptr - rptr) & mask);
+ return (len);
+}
+
+int MoxaPortRxQueue(int port)
+{
+ void __iomem *ofsAddr;
+ ushort rptr, wptr, mask;
+ int len;
+
+ ofsAddr = moxaTableAddr[port];
+ rptr = readw(ofsAddr + RXrptr);
+ wptr = readw(ofsAddr + RXwptr);
+ mask = readw(ofsAddr + RX_mask);
+ len = (wptr - rptr) & mask;
+ return (len);
+}
+
+
+void MoxaPortTxDisable(int port)
+{
+ void __iomem *ofsAddr;
+
+ ofsAddr = moxaTableAddr[port];
+ moxafunc(ofsAddr, FC_SetXoffState, Magic_code);
+}
+
+void MoxaPortTxEnable(int port)
+{
+ void __iomem *ofsAddr;
+
+ ofsAddr = moxaTableAddr[port];
+ moxafunc(ofsAddr, FC_SetXonState, Magic_code);
+}
+
+
+int MoxaPortResetBrkCnt(int port)
+{
+ ushort cnt;
+ cnt = moxaBreakCnt[port];
+ moxaBreakCnt[port] = 0;
+ return (cnt);
+}
+
+
+void MoxaPortSendBreak(int port, int ms100)
+{
+ void __iomem *ofsAddr;
+
+ ofsAddr = moxaTableAddr[port];
+ if (ms100) {
+ moxafunc(ofsAddr, FC_SendBreak, Magic_code);
+ moxadelay(ms100 * (HZ / 10));
+ } else {
+ moxafunc(ofsAddr, FC_SendBreak, Magic_code);
+ moxadelay(HZ / 4); /* 250 ms */
+ }
+ moxafunc(ofsAddr, FC_StopBreak, Magic_code);
+}
+
+static int moxa_get_serial_info(struct moxa_str *info,
+ struct serial_struct __user *retinfo)
+{
+ struct serial_struct tmp;
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = info->type;
+ tmp.line = info->port;
+ tmp.port = 0;
+ tmp.irq = 0;
+ tmp.flags = info->asyncflags;
+ tmp.baud_base = 921600;
+ tmp.close_delay = info->close_delay;
+ tmp.closing_wait = info->closing_wait;
+ tmp.custom_divisor = 0;
+ tmp.hub6 = 0;
+ if(copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+ return (0);
+}
+
+
+static int moxa_set_serial_info(struct moxa_str *info,
+ struct serial_struct __user *new_info)
+{
+ struct serial_struct new_serial;
+
+ if(copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+ return -EFAULT;
+
+ if ((new_serial.irq != 0) ||
+ (new_serial.port != 0) ||
+// (new_serial.type != info->type) ||
+ (new_serial.custom_divisor != 0) ||
+ (new_serial.baud_base != 921600))
+ return (-EPERM);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if (((new_serial.flags & ~ASYNC_USR_MASK) !=
+ (info->asyncflags & ~ASYNC_USR_MASK)))
+ return (-EPERM);
+ } else {
+ info->close_delay = new_serial.close_delay * HZ / 100;
+ info->closing_wait = new_serial.closing_wait * HZ / 100;
+ }
+
+ new_serial.flags = (new_serial.flags & ~ASYNC_FLAGS);
+ new_serial.flags |= (info->asyncflags & ASYNC_FLAGS);
+
+ if (new_serial.type == PORT_16550A) {
+ MoxaSetFifo(info->port, 1);
+ } else {
+ MoxaSetFifo(info->port, 0);
+ }
+
+ info->type = new_serial.type;
+ return (0);
+}
+
+
+
+/*****************************************************************************
+ * Static local functions: *
+ *****************************************************************************/
+/*
+ * moxadelay - delays a specified number ticks
+ */
+static void moxadelay(int tick)
+{
+ unsigned long st, et;
+
+ st = jiffies;
+ et = st + tick;
+ while (time_before(jiffies, et));
+}
+
+static void moxafunc(void __iomem *ofsAddr, int cmd, ushort arg)
+{
+
+ writew(arg, ofsAddr + FuncArg);
+ writew(cmd, ofsAddr + FuncCode);
+ wait_finish(ofsAddr);
+}
+
+static void wait_finish(void __iomem *ofsAddr)
+{
+ unsigned long i, j;
+
+ i = jiffies;
+ while (readw(ofsAddr + FuncCode) != 0) {
+ j = jiffies;
+ if ((j - i) > moxaFuncTout) {
+ return;
+ }
+ }
+}
+
+static void low_water_check(void __iomem *ofsAddr)
+{
+ int len;
+ ushort rptr, wptr, mask;
+
+ if (readb(ofsAddr + FlagStat) & Xoff_state) {
+ rptr = readw(ofsAddr + RXrptr);
+ wptr = readw(ofsAddr + RXwptr);
+ mask = readw(ofsAddr + RX_mask);
+ len = (wptr - rptr) & mask;
+ if (len <= Low_water)
+ moxafunc(ofsAddr, FC_SendXon, 0);
+ }
+}
+
+static int moxaloadbios(int cardno, unsigned char __user *tmp, int len)
+{
+ void __iomem *baseAddr;
+ int i;
+
+ if(copy_from_user(moxaBuff, tmp, len))
+ return -EFAULT;
+ baseAddr = moxaBaseAddr[cardno];
+ writeb(HW_reset, baseAddr + Control_reg); /* reset */
+ moxadelay(1); /* delay 10 ms */
+ for (i = 0; i < 4096; i++)
+ writeb(0, baseAddr + i); /* clear fix page */
+ for (i = 0; i < len; i++)
+ writeb(moxaBuff[i], baseAddr + i); /* download BIOS */
+ writeb(0, baseAddr + Control_reg); /* restart */
+ return (0);
+}
+
+static int moxafindcard(int cardno)
+{
+ void __iomem *baseAddr;
+ ushort tmp;
+
+ baseAddr = moxaBaseAddr[cardno];
+ switch (moxa_boards[cardno].boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ if ((tmp = readw(baseAddr + C218_key)) != C218_KeyCode) {
+ return (-1);
+ }
+ break;
+ case MOXA_BOARD_CP204J:
+ if ((tmp = readw(baseAddr + C218_key)) != CP204J_KeyCode) {
+ return (-1);
+ }
+ break;
+ default:
+ if ((tmp = readw(baseAddr + C320_key)) != C320_KeyCode) {
+ return (-1);
+ }
+ if ((tmp = readw(baseAddr + C320_status)) != STS_init) {
+ return (-2);
+ }
+ }
+ return (0);
+}
+
+static int moxaload320b(int cardno, unsigned char __user *tmp, int len)
+{
+ void __iomem *baseAddr;
+ int i;
+
+ if(len > sizeof(moxaBuff))
+ return -EINVAL;
+ if(copy_from_user(moxaBuff, tmp, len))
+ return -EFAULT;
+ baseAddr = moxaBaseAddr[cardno];
+ writew(len - 7168 - 2, baseAddr + C320bapi_len);
+ writeb(1, baseAddr + Control_reg); /* Select Page 1 */
+ for (i = 0; i < 7168; i++)
+ writeb(moxaBuff[i], baseAddr + DynPage_addr + i);
+ writeb(2, baseAddr + Control_reg); /* Select Page 2 */
+ for (i = 0; i < (len - 7168); i++)
+ writeb(moxaBuff[i + 7168], baseAddr + DynPage_addr + i);
+ return (0);
+}
+
+static int moxaloadcode(int cardno, unsigned char __user *tmp, int len)
+{
+ void __iomem *baseAddr, *ofsAddr;
+ int retval, port, i;
+
+ if(copy_from_user(moxaBuff, tmp, len))
+ return -EFAULT;
+ baseAddr = moxaBaseAddr[cardno];
+ switch (moxa_boards[cardno].boardType) {
+ case MOXA_BOARD_C218_ISA:
+ case MOXA_BOARD_C218_PCI:
+ case MOXA_BOARD_CP204J:
+ retval = moxaloadc218(cardno, baseAddr, len);
+ if (retval)
+ return (retval);
+ port = cardno * MAX_PORTS_PER_BOARD;
+ for (i = 0; i < moxa_boards[cardno].numPorts; i++, port++) {
+ moxaChkPort[port] = 1;
+ moxaCurBaud[port] = 9600L;
+ moxaDCDState[port] = 0;
+ moxaTableAddr[port] = baseAddr + Extern_table + Extern_size * i;
+ ofsAddr = moxaTableAddr[port];
+ writew(C218rx_mask, ofsAddr + RX_mask);
+ writew(C218tx_mask, ofsAddr + TX_mask);
+ writew(C218rx_spage + i * C218buf_pageno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C218rx_pageno, ofsAddr + EndPage_rxb);
+
+ writew(C218tx_spage + i * C218buf_pageno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C218tx_pageno, ofsAddr + EndPage_txb);
+
+ }
+ break;
+ default:
+ retval = moxaloadc320(cardno, baseAddr, len,
+ &moxa_boards[cardno].numPorts);
+ if (retval)
+ return (retval);
+ port = cardno * MAX_PORTS_PER_BOARD;
+ for (i = 0; i < moxa_boards[cardno].numPorts; i++, port++) {
+ moxaChkPort[port] = 1;
+ moxaCurBaud[port] = 9600L;
+ moxaDCDState[port] = 0;
+ moxaTableAddr[port] = baseAddr + Extern_table + Extern_size * i;
+ ofsAddr = moxaTableAddr[port];
+ if (moxa_boards[cardno].numPorts == 8) {
+ writew(C320p8rx_mask, ofsAddr + RX_mask);
+ writew(C320p8tx_mask, ofsAddr + TX_mask);
+ writew(C320p8rx_spage + i * C320p8buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p8rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p8tx_spage + i * C320p8buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C320p8tx_pgno, ofsAddr + EndPage_txb);
+
+ } else if (moxa_boards[cardno].numPorts == 16) {
+ writew(C320p16rx_mask, ofsAddr + RX_mask);
+ writew(C320p16tx_mask, ofsAddr + TX_mask);
+ writew(C320p16rx_spage + i * C320p16buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p16rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p16tx_spage + i * C320p16buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb) + C320p16tx_pgno, ofsAddr + EndPage_txb);
+
+ } else if (moxa_boards[cardno].numPorts == 24) {
+ writew(C320p24rx_mask, ofsAddr + RX_mask);
+ writew(C320p24tx_mask, ofsAddr + TX_mask);
+ writew(C320p24rx_spage + i * C320p24buf_pgno, ofsAddr + Page_rxb);
+ writew(readw(ofsAddr + Page_rxb) + C320p24rx_pgno, ofsAddr + EndPage_rxb);
+ writew(C320p24tx_spage + i * C320p24buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+ } else if (moxa_boards[cardno].numPorts == 32) {
+ writew(C320p32rx_mask, ofsAddr + RX_mask);
+ writew(C320p32tx_mask, ofsAddr + TX_mask);
+ writew(C320p32tx_ofs, ofsAddr + Ofs_txb);
+ writew(C320p32rx_spage + i * C320p32buf_pgno, ofsAddr + Page_rxb);
+ writew(readb(ofsAddr + Page_rxb), ofsAddr + EndPage_rxb);
+ writew(C320p32tx_spage + i * C320p32buf_pgno, ofsAddr + Page_txb);
+ writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+ }
+ }
+ break;
+ }
+ return (0);
+}
+
+static int moxaloadc218(int cardno, void __iomem *baseAddr, int len)
+{
+ char retry;
+ int i, j, len1, len2;
+ ushort usum, *ptr, keycode;
+
+ if (moxa_boards[cardno].boardType == MOXA_BOARD_CP204J)
+ keycode = CP204J_KeyCode;
+ else
+ keycode = C218_KeyCode;
+ usum = 0;
+ len1 = len >> 1;
+ ptr = (ushort *) moxaBuff;
+ for (i = 0; i < len1; i++)
+ usum += *(ptr + i);
+ retry = 0;
+ do {
+ len1 = len >> 1;
+ j = 0;
+ while (len1) {
+ len2 = (len1 > 2048) ? 2048 : len1;
+ len1 -= len2;
+ for (i = 0; i < len2 << 1; i++)
+ writeb(moxaBuff[i + j], baseAddr + C218_LoadBuf + i);
+ j += i;
+
+ writew(len2, baseAddr + C218DLoad_len);
+ writew(0, baseAddr + C218_key);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + C218_key) == keycode)
+ break;
+ moxadelay(1); /* delay 10 ms */
+ }
+ if (readw(baseAddr + C218_key) != keycode) {
+ return (-1);
+ }
+ }
+ writew(0, baseAddr + C218DLoad_len);
+ writew(usum, baseAddr + C218check_sum);
+ writew(0, baseAddr + C218_key);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + C218_key) == keycode)
+ break;
+ moxadelay(1); /* delay 10 ms */
+ }
+ retry++;
+ } while ((readb(baseAddr + C218chksum_ok) != 1) && (retry < 3));
+ if (readb(baseAddr + C218chksum_ok) != 1) {
+ return (-1);
+ }
+ writew(0, baseAddr + C218_key);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ moxadelay(1); /* delay 10 ms */
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code) {
+ return (-1);
+ }
+ writew(1, baseAddr + Disable_IRQ);
+ writew(0, baseAddr + Magic_no);
+ for (i = 0; i < 100; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ moxadelay(1); /* delay 10 ms */
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code) {
+ return (-1);
+ }
+ moxaCard = 1;
+ moxaIntNdx[cardno] = baseAddr + IRQindex;
+ moxaIntPend[cardno] = baseAddr + IRQpending;
+ moxaIntTable[cardno] = baseAddr + IRQtable;
+ return (0);
+}
+
+static int moxaloadc320(int cardno, void __iomem *baseAddr, int len, int *numPorts)
+{
+ ushort usum;
+ int i, j, wlen, len2, retry;
+ ushort *uptr;
+
+ usum = 0;
+ wlen = len >> 1;
+ uptr = (ushort *) moxaBuff;
+ for (i = 0; i < wlen; i++)
+ usum += uptr[i];
+ retry = 0;
+ j = 0;
+ do {
+ while (wlen) {
+ if (wlen > 2048)
+ len2 = 2048;
+ else
+ len2 = wlen;
+ wlen -= len2;
+ len2 <<= 1;
+ for (i = 0; i < len2; i++)
+ writeb(moxaBuff[j + i], baseAddr + C320_LoadBuf + i);
+ len2 >>= 1;
+ j += i;
+ writew(len2, baseAddr + C320DLoad_len);
+ writew(0, baseAddr + C320_key);
+ for (i = 0; i < 10; i++) {
+ if (readw(baseAddr + C320_key) == C320_KeyCode)
+ break;
+ moxadelay(1);
+ }
+ if (readw(baseAddr + C320_key) != C320_KeyCode)
+ return (-1);
+ }
+ writew(0, baseAddr + C320DLoad_len);
+ writew(usum, baseAddr + C320check_sum);
+ writew(0, baseAddr + C320_key);
+ for (i = 0; i < 10; i++) {
+ if (readw(baseAddr + C320_key) == C320_KeyCode)
+ break;
+ moxadelay(1);
+ }
+ retry++;
+ } while ((readb(baseAddr + C320chksum_ok) != 1) && (retry < 3));
+ if (readb(baseAddr + C320chksum_ok) != 1)
+ return (-1);
+ writew(0, baseAddr + C320_key);
+ for (i = 0; i < 600; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ moxadelay(1);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return (-100);
+
+ if (moxa_boards[cardno].busType == MOXA_BUS_TYPE_PCI) { /* ASIC board */
+ writew(0x3800, baseAddr + TMS320_PORT1);
+ writew(0x3900, baseAddr + TMS320_PORT2);
+ writew(28499, baseAddr + TMS320_CLOCK);
+ } else {
+ writew(0x3200, baseAddr + TMS320_PORT1);
+ writew(0x3400, baseAddr + TMS320_PORT2);
+ writew(19999, baseAddr + TMS320_CLOCK);
+ }
+ writew(1, baseAddr + Disable_IRQ);
+ writew(0, baseAddr + Magic_no);
+ for (i = 0; i < 500; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ moxadelay(1);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return (-102);
+
+ j = readw(baseAddr + Module_cnt);
+ if (j <= 0)
+ return (-101);
+ *numPorts = j * 8;
+ writew(j, baseAddr + Module_no);
+ writew(0, baseAddr + Magic_no);
+ for (i = 0; i < 600; i++) {
+ if (readw(baseAddr + Magic_no) == Magic_code)
+ break;
+ moxadelay(1);
+ }
+ if (readw(baseAddr + Magic_no) != Magic_code)
+ return (-102);
+ moxaCard = 1;
+ moxaIntNdx[cardno] = baseAddr + IRQindex;
+ moxaIntPend[cardno] = baseAddr + IRQpending;
+ moxaIntTable[cardno] = baseAddr + IRQtable;
+ return (0);
+}
+
+#if 0
+long MoxaPortGetCurBaud(int port)
+{
+
+ if (moxaChkPort[port] == 0)
+ return (0);
+ return (moxaCurBaud[port]);
+}
+#endif /* 0 */
+
+static void MoxaSetFifo(int port, int enable)
+{
+ void __iomem *ofsAddr = moxaTableAddr[port];
+
+ if (!enable) {
+ moxafunc(ofsAddr, FC_SetRxFIFOTrig, 0);
+ moxafunc(ofsAddr, FC_SetTxFIFOCnt, 1);
+ } else {
+ moxafunc(ofsAddr, FC_SetRxFIFOTrig, 3);
+ moxafunc(ofsAddr, FC_SetTxFIFOCnt, 16);
+ }
+}
+
+#if 0
+int MoxaPortSetMode(int port, int databits, int stopbits, int parity)
+{
+ void __iomem *ofsAddr;
+ int val;
+
+ val = 0;
+ switch (databits) {
+ case 5:
+ val |= 0;
+ break;
+ case 6:
+ val |= 1;
+ break;
+ case 7:
+ val |= 2;
+ break;
+ case 8:
+ val |= 3;
+ break;
+ default:
+ return (-1);
+ }
+ switch (stopbits) {
+ case 0:
+ val |= 0;
+ break; /* stop bits 1.5 */
+ case 1:
+ val |= 0;
+ break;
+ case 2:
+ val |= 4;
+ break;
+ default:
+ return (-1);
+ }
+ switch (parity) {
+ case 0:
+ val |= 0x00;
+ break; /* None */
+ case 1:
+ val |= 0x08;
+ break; /* Odd */
+ case 2:
+ val |= 0x18;
+ break; /* Even */
+ case 3:
+ val |= 0x28;
+ break; /* Mark */
+ case 4:
+ val |= 0x38;
+ break; /* Space */
+ default:
+ return (-1);
+ }
+ ofsAddr = moxaTableAddr[port];
+ moxafunc(ofsAddr, FC_SetMode, val);
+ return (0);
+}
+
+int MoxaPortTxBufSize(int port)
+{
+ void __iomem *ofsAddr;
+ int size;
+
+ ofsAddr = moxaTableAddr[port];
+ size = readw(ofsAddr + TX_mask);
+ return (size);
+}
+
+int MoxaPortRxBufSize(int port)
+{
+ void __iomem *ofsAddr;
+ int size;
+
+ ofsAddr = moxaTableAddr[port];
+ size = readw(ofsAddr + RX_mask);
+ return (size);
+}
+
+int MoxaPortRxFree(int port)
+{
+ void __iomem *ofsAddr;
+ ushort rptr, wptr, mask;
+ int len;
+
+ ofsAddr = moxaTableAddr[port];
+ rptr = readw(ofsAddr + RXrptr);
+ wptr = readw(ofsAddr + RXwptr);
+ mask = readw(ofsAddr + RX_mask);
+ len = mask - ((wptr - rptr) & mask);
+ return (len);
+}
+int MoxaPortGetBrkCnt(int port)
+{
+ return (moxaBreakCnt[port]);
+}
+
+void MoxaPortSetXonXoff(int port, int xonValue, int xoffValue)
+{
+ void __iomem *ofsAddr;
+
+ ofsAddr = moxaTableAddr[port];
+ writew(xonValue, ofsAddr + FuncArg);
+ writew(xoffValue, ofsAddr + FuncArg1);
+ writew(FC_SetXonXoff, ofsAddr + FuncCode);
+ wait_finish(ofsAddr);
+}
+
+int MoxaPortIsTxHold(int port)
+{
+ void __iomem *ofsAddr;
+ int val;
+
+ ofsAddr = moxaTableAddr[port];
+ if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+ (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+ moxafunc(ofsAddr, FC_GetCCSR, 0);
+ val = readw(ofsAddr + FuncArg);
+ if (val & 0x04)
+ return (1);
+ } else {
+ if (readw(ofsAddr + FlagStat) & Tx_flowOff)
+ return (1);
+ }
+ return (0);
+}
+#endif
diff --git a/drivers/char/mwave/3780i.c b/drivers/char/mwave/3780i.c
new file mode 100644
index 000000000000..ab00f51475df
--- /dev/null
+++ b/drivers/char/mwave/3780i.c
@@ -0,0 +1,727 @@
+/*
+*
+* 3780i.c -- helper routines for the 3780i DSP
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/unistd.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "3780i.h"
+
+static DEFINE_SPINLOCK(dsp_lock);
+static unsigned long flags;
+
+
+static void PaceMsaAccess(unsigned short usDspBaseIO)
+{
+ cond_resched();
+ udelay(100);
+ cond_resched();
+}
+
+unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO,
+ unsigned long ulMsaAddr)
+{
+ unsigned short val;
+
+ PRINTK_3(TRACE_3780I,
+ "3780i::dsp3780I_ReadMsaCfg entry usDspBaseIO %x ulMsaAddr %lx\n",
+ usDspBaseIO, ulMsaAddr);
+
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16));
+ val = InWordDsp(DSP_MsaDataDSISHigh);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ PRINTK_2(TRACE_3780I, "3780i::dsp3780I_ReadMsaCfg exit val %x\n", val);
+
+ return val;
+}
+
+void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO,
+ unsigned long ulMsaAddr, unsigned short usValue)
+{
+
+ PRINTK_4(TRACE_3780I,
+ "3780i::dsp3780i_WriteMsaCfg entry usDspBaseIO %x ulMsaAddr %lx usValue %x\n",
+ usDspBaseIO, ulMsaAddr, usValue);
+
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16));
+ OutWordDsp(DSP_MsaDataDSISHigh, usValue);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+}
+
+void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex,
+ unsigned char ucValue)
+{
+ DSP_ISA_SLAVE_CONTROL rSlaveControl;
+ DSP_ISA_SLAVE_CONTROL rSlaveControl_Save;
+
+
+ PRINTK_4(TRACE_3780I,
+ "3780i::dsp3780i_WriteGenCfg entry usDspBaseIO %x uIndex %x ucValue %x\n",
+ usDspBaseIO, uIndex, ucValue);
+
+ MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl);
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_WriteGenCfg rSlaveControl %x\n",
+ MKBYTE(rSlaveControl));
+
+ rSlaveControl_Save = rSlaveControl;
+ rSlaveControl.ConfigMode = TRUE;
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_WriteGenCfg entry rSlaveControl+ConfigMode %x\n",
+ MKBYTE(rSlaveControl));
+
+ OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl));
+ OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex);
+ OutByteDsp(DSP_ConfigData, ucValue);
+ OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save));
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_WriteGenCfg exit\n");
+
+
+}
+
+unsigned char dsp3780I_ReadGenCfg(unsigned short usDspBaseIO,
+ unsigned uIndex)
+{
+ DSP_ISA_SLAVE_CONTROL rSlaveControl;
+ DSP_ISA_SLAVE_CONTROL rSlaveControl_Save;
+ unsigned char ucValue;
+
+
+ PRINTK_3(TRACE_3780I,
+ "3780i::dsp3780i_ReadGenCfg entry usDspBaseIO %x uIndex %x\n",
+ usDspBaseIO, uIndex);
+
+ MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl);
+ rSlaveControl_Save = rSlaveControl;
+ rSlaveControl.ConfigMode = TRUE;
+ OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl));
+ OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex);
+ ucValue = InByteDsp(DSP_ConfigData);
+ OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save));
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_ReadGenCfg exit ucValue %x\n", ucValue);
+
+
+ return ucValue;
+}
+
+int dsp3780I_EnableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings,
+ unsigned short *pIrqMap,
+ unsigned short *pDmaMap)
+{
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ int i;
+ DSP_UART_CFG_1 rUartCfg1;
+ DSP_UART_CFG_2 rUartCfg2;
+ DSP_HBRIDGE_CFG_1 rHBridgeCfg1;
+ DSP_HBRIDGE_CFG_2 rHBridgeCfg2;
+ DSP_BUSMASTER_CFG_1 rBusmasterCfg1;
+ DSP_BUSMASTER_CFG_2 rBusmasterCfg2;
+ DSP_ISA_PROT_CFG rIsaProtCfg;
+ DSP_POWER_MGMT_CFG rPowerMgmtCfg;
+ DSP_HBUS_TIMER_CFG rHBusTimerCfg;
+ DSP_LBUS_TIMEOUT_DISABLE rLBusTimeoutDisable;
+ DSP_CHIP_RESET rChipReset;
+ DSP_CLOCK_CONTROL_1 rClockControl1;
+ DSP_CLOCK_CONTROL_2 rClockControl2;
+ DSP_ISA_SLAVE_CONTROL rSlaveControl;
+ DSP_HBRIDGE_CONTROL rHBridgeControl;
+ unsigned short ChipID = 0;
+ unsigned short tval;
+
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780I_EnableDSP entry pSettings->bDSPEnabled %x\n",
+ pSettings->bDSPEnabled);
+
+
+ if (!pSettings->bDSPEnabled) {
+ PRINTK_ERROR( KERN_ERR "3780i::dsp3780I_EnableDSP: Error: DSP not enabled. Aborting.\n" );
+ return -EIO;
+ }
+
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP entry pSettings->bModemEnabled %x\n",
+ pSettings->bModemEnabled);
+
+ if (pSettings->bModemEnabled) {
+ rUartCfg1.Reserved = rUartCfg2.Reserved = 0;
+ rUartCfg1.IrqActiveLow = pSettings->bUartIrqActiveLow;
+ rUartCfg1.IrqPulse = pSettings->bUartIrqPulse;
+ rUartCfg1.Irq =
+ (unsigned char) pIrqMap[pSettings->usUartIrq];
+ switch (pSettings->usUartBaseIO) {
+ case 0x03F8:
+ rUartCfg1.BaseIO = 0;
+ break;
+ case 0x02F8:
+ rUartCfg1.BaseIO = 1;
+ break;
+ case 0x03E8:
+ rUartCfg1.BaseIO = 2;
+ break;
+ case 0x02E8:
+ rUartCfg1.BaseIO = 3;
+ break;
+ }
+ rUartCfg2.Enable = TRUE;
+ }
+
+ rHBridgeCfg1.Reserved = rHBridgeCfg2.Reserved = 0;
+ rHBridgeCfg1.IrqActiveLow = pSettings->bDspIrqActiveLow;
+ rHBridgeCfg1.IrqPulse = pSettings->bDspIrqPulse;
+ rHBridgeCfg1.Irq = (unsigned char) pIrqMap[pSettings->usDspIrq];
+ rHBridgeCfg1.AccessMode = 1;
+ rHBridgeCfg2.Enable = TRUE;
+
+
+ rBusmasterCfg2.Reserved = 0;
+ rBusmasterCfg1.Dma = (unsigned char) pDmaMap[pSettings->usDspDma];
+ rBusmasterCfg1.NumTransfers =
+ (unsigned char) pSettings->usNumTransfers;
+ rBusmasterCfg1.ReRequest = (unsigned char) pSettings->usReRequest;
+ rBusmasterCfg1.MEMCS16 = pSettings->bEnableMEMCS16;
+ rBusmasterCfg2.IsaMemCmdWidth =
+ (unsigned char) pSettings->usIsaMemCmdWidth;
+
+
+ rIsaProtCfg.Reserved = 0;
+ rIsaProtCfg.GateIOCHRDY = pSettings->bGateIOCHRDY;
+
+ rPowerMgmtCfg.Reserved = 0;
+ rPowerMgmtCfg.Enable = pSettings->bEnablePwrMgmt;
+
+ rHBusTimerCfg.LoadValue =
+ (unsigned char) pSettings->usHBusTimerLoadValue;
+
+ rLBusTimeoutDisable.Reserved = 0;
+ rLBusTimeoutDisable.DisableTimeout =
+ pSettings->bDisableLBusTimeout;
+
+ MKWORD(rChipReset) = ~pSettings->usChipletEnable;
+
+ rClockControl1.Reserved1 = rClockControl1.Reserved2 = 0;
+ rClockControl1.N_Divisor = pSettings->usN_Divisor;
+ rClockControl1.M_Multiplier = pSettings->usM_Multiplier;
+
+ rClockControl2.Reserved = 0;
+ rClockControl2.PllBypass = pSettings->bPllBypass;
+
+ /* Issue a soft reset to the chip */
+ /* Note: Since we may be coming in with 3780i clocks suspended, we must keep
+ * soft-reset active for 10ms.
+ */
+ rSlaveControl.ClockControl = 0;
+ rSlaveControl.SoftReset = TRUE;
+ rSlaveControl.ConfigMode = FALSE;
+ rSlaveControl.Reserved = 0;
+
+ PRINTK_4(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP usDspBaseIO %x index %x taddr %x\n",
+ usDspBaseIO, DSP_IsaSlaveControl,
+ usDspBaseIO + DSP_IsaSlaveControl);
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP rSlaveContrl %x\n",
+ MKWORD(rSlaveControl));
+
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+ MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl);
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP rSlaveControl 2 %x\n", tval);
+
+
+ for (i = 0; i < 11; i++)
+ udelay(2000);
+
+ rSlaveControl.SoftReset = FALSE;
+ OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+
+ MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl);
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP rSlaveControl 3 %x\n", tval);
+
+
+ /* Program our general configuration registers */
+ WriteGenCfg(DSP_HBridgeCfg1Index, MKBYTE(rHBridgeCfg1));
+ WriteGenCfg(DSP_HBridgeCfg2Index, MKBYTE(rHBridgeCfg2));
+ WriteGenCfg(DSP_BusMasterCfg1Index, MKBYTE(rBusmasterCfg1));
+ WriteGenCfg(DSP_BusMasterCfg2Index, MKBYTE(rBusmasterCfg2));
+ WriteGenCfg(DSP_IsaProtCfgIndex, MKBYTE(rIsaProtCfg));
+ WriteGenCfg(DSP_PowerMgCfgIndex, MKBYTE(rPowerMgmtCfg));
+ WriteGenCfg(DSP_HBusTimerCfgIndex, MKBYTE(rHBusTimerCfg));
+
+ if (pSettings->bModemEnabled) {
+ WriteGenCfg(DSP_UartCfg1Index, MKBYTE(rUartCfg1));
+ WriteGenCfg(DSP_UartCfg2Index, MKBYTE(rUartCfg2));
+ }
+
+
+ rHBridgeControl.EnableDspInt = FALSE;
+ rHBridgeControl.MemAutoInc = TRUE;
+ rHBridgeControl.IoAutoInc = FALSE;
+ rHBridgeControl.DiagnosticMode = FALSE;
+
+ PRINTK_3(TRACE_3780I,
+ "3780i::dsp3780i_EnableDSP DSP_HBridgeControl %x rHBridgeControl %x\n",
+ DSP_HBridgeControl, MKWORD(rHBridgeControl));
+
+ OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+ WriteMsaCfg(DSP_LBusTimeoutDisable, MKWORD(rLBusTimeoutDisable));
+ WriteMsaCfg(DSP_ClockControl_1, MKWORD(rClockControl1));
+ WriteMsaCfg(DSP_ClockControl_2, MKWORD(rClockControl2));
+ WriteMsaCfg(DSP_ChipReset, MKWORD(rChipReset));
+
+ ChipID = ReadMsaCfg(DSP_ChipID);
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780I_EnableDSP exiting bRC=TRUE, ChipID %x\n",
+ ChipID);
+
+ return 0;
+}
+
+int dsp3780I_DisableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ DSP_ISA_SLAVE_CONTROL rSlaveControl;
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_DisableDSP entry\n");
+
+ rSlaveControl.ClockControl = 0;
+ rSlaveControl.SoftReset = TRUE;
+ rSlaveControl.ConfigMode = FALSE;
+ rSlaveControl.Reserved = 0;
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+
+ udelay(5);
+
+ rSlaveControl.ClockControl = 1;
+ OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ udelay(5);
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_DisableDSP exit\n");
+
+ return 0;
+}
+
+int dsp3780I_Reset(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ DSP_BOOT_DOMAIN rBootDomain;
+ DSP_HBRIDGE_CONTROL rHBridgeControl;
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Reset entry\n");
+
+ spin_lock_irqsave(&dsp_lock, flags);
+ /* Mask DSP to PC interrupt */
+ MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+
+ PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Reset rHBridgeControl %x\n",
+ MKWORD(rHBridgeControl));
+
+ rHBridgeControl.EnableDspInt = FALSE;
+ OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Reset the core via the boot domain register */
+ rBootDomain.ResetCore = TRUE;
+ rBootDomain.Halt = TRUE;
+ rBootDomain.NMI = TRUE;
+ rBootDomain.Reserved = 0;
+
+ PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Reset rBootDomain %x\n",
+ MKWORD(rBootDomain));
+
+ WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+
+ /* Reset all the chiplets and then reactivate them */
+ WriteMsaCfg(DSP_ChipReset, 0xFFFF);
+ udelay(5);
+ WriteMsaCfg(DSP_ChipReset,
+ (unsigned short) (~pSettings->usChipletEnable));
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Reset exit bRC=0\n");
+
+ return 0;
+}
+
+
+int dsp3780I_Run(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ DSP_BOOT_DOMAIN rBootDomain;
+ DSP_HBRIDGE_CONTROL rHBridgeControl;
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Run entry\n");
+
+
+ /* Transition the core to a running state */
+ rBootDomain.ResetCore = TRUE;
+ rBootDomain.Halt = FALSE;
+ rBootDomain.NMI = TRUE;
+ rBootDomain.Reserved = 0;
+ WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+
+ udelay(5);
+
+ rBootDomain.ResetCore = FALSE;
+ WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+ udelay(5);
+
+ rBootDomain.NMI = FALSE;
+ WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+ udelay(5);
+
+ /* Enable DSP to PC interrupt */
+ spin_lock_irqsave(&dsp_lock, flags);
+ MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+ rHBridgeControl.EnableDspInt = TRUE;
+
+ PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Run rHBridgeControl %x\n",
+ MKWORD(rHBridgeControl));
+
+ OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+
+ PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Run exit bRC=TRUE\n");
+
+ return 0;
+}
+
+
+int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr)
+{
+ unsigned short __user *pusBuffer = pvBuffer;
+ unsigned short val;
+
+
+ PRINTK_5(TRACE_3780I,
+ "3780i::dsp3780I_ReadDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+ /* Set the initial MSA address. No adjustments need to be made to data store addresses */
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Transfer the memory block */
+ while (uCount-- != 0) {
+ spin_lock_irqsave(&dsp_lock, flags);
+ val = InWordDsp(DSP_MsaDataDSISHigh);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+ if(put_user(val, pusBuffer++))
+ return -EFAULT;
+
+ PRINTK_3(TRACE_3780I,
+ "3780I::dsp3780I_ReadDStore uCount %x val %x\n",
+ uCount, val);
+
+ PaceMsaAccess(usDspBaseIO);
+ }
+
+
+ PRINTK_1(TRACE_3780I,
+ "3780I::dsp3780I_ReadDStore exit bRC=TRUE\n");
+
+ return 0;
+}
+
+int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO,
+ void __user *pvBuffer, unsigned uCount,
+ unsigned long ulDSPAddr)
+{
+ unsigned short __user *pusBuffer = pvBuffer;
+ unsigned short val;
+
+
+ PRINTK_5(TRACE_3780I,
+ "3780i::dsp3780I_ReadAndDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+ /* Set the initial MSA address. No adjustments need to be made to data store addresses */
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Transfer the memory block */
+ while (uCount-- != 0) {
+ spin_lock_irqsave(&dsp_lock, flags);
+ val = InWordDsp(DSP_ReadAndClear);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+ if(put_user(val, pusBuffer++))
+ return -EFAULT;
+
+ PRINTK_3(TRACE_3780I,
+ "3780I::dsp3780I_ReadAndCleanDStore uCount %x val %x\n",
+ uCount, val);
+
+ PaceMsaAccess(usDspBaseIO);
+ }
+
+
+ PRINTK_1(TRACE_3780I,
+ "3780I::dsp3780I_ReadAndClearDStore exit bRC=TRUE\n");
+
+ return 0;
+}
+
+
+int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr)
+{
+ unsigned short __user *pusBuffer = pvBuffer;
+
+
+ PRINTK_5(TRACE_3780I,
+ "3780i::dsp3780D_WriteDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+ /* Set the initial MSA address. No adjustments need to be made to data store addresses */
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Transfer the memory block */
+ while (uCount-- != 0) {
+ unsigned short val;
+ if(get_user(val, pusBuffer++))
+ return -EFAULT;
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaDataDSISHigh, val);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ PRINTK_3(TRACE_3780I,
+ "3780I::dsp3780I_WriteDStore uCount %x val %x\n",
+ uCount, val);
+
+ PaceMsaAccess(usDspBaseIO);
+ }
+
+
+ PRINTK_1(TRACE_3780I,
+ "3780I::dsp3780D_WriteDStore exit bRC=TRUE\n");
+
+ return 0;
+}
+
+
+int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr)
+{
+ unsigned short __user *pusBuffer = pvBuffer;
+
+ PRINTK_5(TRACE_3780I,
+ "3780i::dsp3780I_ReadIStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+ /*
+ * Set the initial MSA address. To convert from an instruction store
+ * address to an MSA address
+ * shift the address two bits to the left and set bit 22
+ */
+ ulDSPAddr = (ulDSPAddr << 2) | (1 << 22);
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Transfer the memory block */
+ while (uCount-- != 0) {
+ unsigned short val_lo, val_hi;
+ spin_lock_irqsave(&dsp_lock, flags);
+ val_lo = InWordDsp(DSP_MsaDataISLow);
+ val_hi = InWordDsp(DSP_MsaDataDSISHigh);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+ if(put_user(val_lo, pusBuffer++))
+ return -EFAULT;
+ if(put_user(val_hi, pusBuffer++))
+ return -EFAULT;
+
+ PRINTK_4(TRACE_3780I,
+ "3780I::dsp3780I_ReadIStore uCount %x val_lo %x val_hi %x\n",
+ uCount, val_lo, val_hi);
+
+ PaceMsaAccess(usDspBaseIO);
+
+ }
+
+ PRINTK_1(TRACE_3780I,
+ "3780I::dsp3780I_ReadIStore exit bRC=TRUE\n");
+
+ return 0;
+}
+
+
+int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr)
+{
+ unsigned short __user *pusBuffer = pvBuffer;
+
+ PRINTK_5(TRACE_3780I,
+ "3780i::dsp3780I_WriteIStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+ /*
+ * Set the initial MSA address. To convert from an instruction store
+ * address to an MSA address
+ * shift the address two bits to the left and set bit 22
+ */
+ ulDSPAddr = (ulDSPAddr << 2) | (1 << 22);
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+ OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ /* Transfer the memory block */
+ while (uCount-- != 0) {
+ unsigned short val_lo, val_hi;
+ if(get_user(val_lo, pusBuffer++))
+ return -EFAULT;
+ if(get_user(val_hi, pusBuffer++))
+ return -EFAULT;
+ spin_lock_irqsave(&dsp_lock, flags);
+ OutWordDsp(DSP_MsaDataISLow, val_lo);
+ OutWordDsp(DSP_MsaDataDSISHigh, val_hi);
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+ PRINTK_4(TRACE_3780I,
+ "3780I::dsp3780I_WriteIStore uCount %x val_lo %x val_hi %x\n",
+ uCount, val_lo, val_hi);
+
+ PaceMsaAccess(usDspBaseIO);
+
+ }
+
+ PRINTK_1(TRACE_3780I,
+ "3780I::dsp3780I_WriteIStore exit bRC=TRUE\n");
+
+ return 0;
+}
+
+
+int dsp3780I_GetIPCSource(unsigned short usDspBaseIO,
+ unsigned short *pusIPCSource)
+{
+ DSP_HBRIDGE_CONTROL rHBridgeControl;
+ unsigned short temp;
+
+
+ PRINTK_3(TRACE_3780I,
+ "3780i::dsp3780I_GetIPCSource entry usDspBaseIO %x pusIPCSource %p\n",
+ usDspBaseIO, pusIPCSource);
+
+ /*
+ * Disable DSP to PC interrupts, read the interrupt register,
+ * clear the pending IPC bits, and reenable DSP to PC interrupts
+ */
+ spin_lock_irqsave(&dsp_lock, flags);
+ MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+ rHBridgeControl.EnableDspInt = FALSE;
+ OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+
+ *pusIPCSource = InWordDsp(DSP_Interrupt);
+ temp = (unsigned short) ~(*pusIPCSource);
+
+ PRINTK_3(TRACE_3780I,
+ "3780i::dsp3780I_GetIPCSource, usIPCSource %x ~ %x\n",
+ *pusIPCSource, temp);
+
+ OutWordDsp(DSP_Interrupt, (unsigned short) ~(*pusIPCSource));
+
+ rHBridgeControl.EnableDspInt = TRUE;
+ OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+ spin_unlock_irqrestore(&dsp_lock, flags);
+
+
+ PRINTK_2(TRACE_3780I,
+ "3780i::dsp3780I_GetIPCSource exit usIPCSource %x\n",
+ *pusIPCSource);
+
+ return 0;
+}
diff --git a/drivers/char/mwave/3780i.h b/drivers/char/mwave/3780i.h
new file mode 100644
index 000000000000..3e7d020d1bf4
--- /dev/null
+++ b/drivers/char/mwave/3780i.h
@@ -0,0 +1,362 @@
+/*
+*
+* 3780i.h -- declarations for 3780i.c
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#ifndef _LINUX_3780I_H
+#define _LINUX_3780I_H
+
+#include <asm/io.h>
+
+/* DSP I/O port offsets and definitions */
+#define DSP_IsaSlaveControl 0x0000 /* ISA slave control register */
+#define DSP_IsaSlaveStatus 0x0001 /* ISA slave status register */
+#define DSP_ConfigAddress 0x0002 /* General config address register */
+#define DSP_ConfigData 0x0003 /* General config data register */
+#define DSP_HBridgeControl 0x0002 /* HBridge control register */
+#define DSP_MsaAddrLow 0x0004 /* MSP System Address, low word */
+#define DSP_MsaAddrHigh 0x0006 /* MSP System Address, high word */
+#define DSP_MsaDataDSISHigh 0x0008 /* MSA data register: d-store word or high byte of i-store */
+#define DSP_MsaDataISLow 0x000A /* MSA data register: low word of i-store */
+#define DSP_ReadAndClear 0x000C /* MSA read and clear data register */
+#define DSP_Interrupt 0x000E /* Interrupt register (IPC source) */
+
+typedef struct {
+ unsigned char ClockControl:1; /* RW: Clock control: 0=normal, 1=stop 3780i clocks */
+ unsigned char SoftReset:1; /* RW: Soft reset 0=normal, 1=soft reset active */
+ unsigned char ConfigMode:1; /* RW: Configuration mode, 0=normal, 1=config mode */
+ unsigned char Reserved:5; /* 0: Reserved */
+} DSP_ISA_SLAVE_CONTROL;
+
+
+typedef struct {
+ unsigned short EnableDspInt:1; /* RW: Enable DSP to X86 ISA interrupt 0=mask it, 1=enable it */
+ unsigned short MemAutoInc:1; /* RW: Memory address auto increment, 0=disable, 1=enable */
+ unsigned short IoAutoInc:1; /* RW: I/O address auto increment, 0=disable, 1=enable */
+ unsigned short DiagnosticMode:1; /* RW: Disgnostic mode 0=nromal, 1=diagnostic mode */
+ unsigned short IsaPacingTimer:12; /* R: ISA access pacing timer: count of core cycles stolen */
+} DSP_HBRIDGE_CONTROL;
+
+
+/* DSP register indexes used with the configuration register address (index) register */
+#define DSP_UartCfg1Index 0x0003 /* UART config register 1 */
+#define DSP_UartCfg2Index 0x0004 /* UART config register 2 */
+#define DSP_HBridgeCfg1Index 0x0007 /* HBridge config register 1 */
+#define DSP_HBridgeCfg2Index 0x0008 /* HBridge config register 2 */
+#define DSP_BusMasterCfg1Index 0x0009 /* ISA bus master config register 1 */
+#define DSP_BusMasterCfg2Index 0x000A /* ISA bus master config register 2 */
+#define DSP_IsaProtCfgIndex 0x000F /* ISA protocol control register */
+#define DSP_PowerMgCfgIndex 0x0010 /* Low poser suspend/resume enable */
+#define DSP_HBusTimerCfgIndex 0x0011 /* HBUS timer load value */
+
+typedef struct {
+ unsigned char IrqActiveLow:1; /* RW: IRQ active high or low: 0=high, 1=low */
+ unsigned char IrqPulse:1; /* RW: IRQ pulse or level: 0=level, 1=pulse */
+ unsigned char Irq:3; /* RW: IRQ selection */
+ unsigned char BaseIO:2; /* RW: Base I/O selection */
+ unsigned char Reserved:1; /* 0: Reserved */
+} DSP_UART_CFG_1;
+
+typedef struct {
+ unsigned char Enable:1; /* RW: Enable I/O and IRQ: 0=FALSE, 1=TRUE */
+ unsigned char Reserved:7; /* 0: Reserved */
+} DSP_UART_CFG_2;
+
+typedef struct {
+ unsigned char IrqActiveLow:1; /* RW: IRQ active high=0 or low=1 */
+ unsigned char IrqPulse:1; /* RW: IRQ pulse=1 or level=0 */
+ unsigned char Irq:3; /* RW: IRQ selection */
+ unsigned char AccessMode:1; /* RW: 16-bit register access method 0=byte, 1=word */
+ unsigned char Reserved:2; /* 0: Reserved */
+} DSP_HBRIDGE_CFG_1;
+
+typedef struct {
+ unsigned char Enable:1; /* RW: enable I/O and IRQ: 0=FALSE, 1=TRUE */
+ unsigned char Reserved:7; /* 0: Reserved */
+} DSP_HBRIDGE_CFG_2;
+
+
+typedef struct {
+ unsigned char Dma:3; /* RW: DMA channel selection */
+ unsigned char NumTransfers:2; /* RW: Maximum # of transfers once being granted the ISA bus */
+ unsigned char ReRequest:2; /* RW: Minumum delay between releasing the ISA bus and requesting it again */
+ unsigned char MEMCS16:1; /* RW: ISA signal MEMCS16: 0=disabled, 1=enabled */
+} DSP_BUSMASTER_CFG_1;
+
+typedef struct {
+ unsigned char IsaMemCmdWidth:2; /* RW: ISA memory command width */
+ unsigned char Reserved:6; /* 0: Reserved */
+} DSP_BUSMASTER_CFG_2;
+
+
+typedef struct {
+ unsigned char GateIOCHRDY:1; /* RW: Enable IOCHRDY gating: 0=FALSE, 1=TRUE */
+ unsigned char Reserved:7; /* 0: Reserved */
+} DSP_ISA_PROT_CFG;
+
+typedef struct {
+ unsigned char Enable:1; /* RW: Enable low power suspend/resume 0=FALSE, 1=TRUE */
+ unsigned char Reserved:7; /* 0: Reserved */
+} DSP_POWER_MGMT_CFG;
+
+typedef struct {
+ unsigned char LoadValue:8; /* RW: HBUS timer load value */
+} DSP_HBUS_TIMER_CFG;
+
+
+
+/* DSP registers that exist in MSA I/O space */
+#define DSP_ChipID 0x80000000
+#define DSP_MspBootDomain 0x80000580
+#define DSP_LBusTimeoutDisable 0x80000580
+#define DSP_ClockControl_1 0x8000058A
+#define DSP_ClockControl_2 0x8000058C
+#define DSP_ChipReset 0x80000588
+#define DSP_GpioModeControl_15_8 0x80000082
+#define DSP_GpioDriverEnable_15_8 0x80000076
+#define DSP_GpioOutputData_15_8 0x80000072
+
+typedef struct {
+ unsigned short NMI:1; /* RW: non maskable interrupt */
+ unsigned short Halt:1; /* RW: Halt MSP clock */
+ unsigned short ResetCore:1; /* RW: Reset MSP core interface */
+ unsigned short Reserved:13; /* 0: Reserved */
+} DSP_BOOT_DOMAIN;
+
+typedef struct {
+ unsigned short DisableTimeout:1; /* RW: Disable LBus timeout */
+ unsigned short Reserved:15; /* 0: Reserved */
+} DSP_LBUS_TIMEOUT_DISABLE;
+
+typedef struct {
+ unsigned short Memory:1; /* RW: Reset memory interface */
+ unsigned short SerialPort1:1; /* RW: Reset serial port 1 interface */
+ unsigned short SerialPort2:1; /* RW: Reset serial port 2 interface */
+ unsigned short SerialPort3:1; /* RW: Reset serial port 3 interface */
+ unsigned short Gpio:1; /* RW: Reset GPIO interface */
+ unsigned short Dma:1; /* RW: Reset DMA interface */
+ unsigned short SoundBlaster:1; /* RW: Reset soundblaster interface */
+ unsigned short Uart:1; /* RW: Reset UART interface */
+ unsigned short Midi:1; /* RW: Reset MIDI interface */
+ unsigned short IsaMaster:1; /* RW: Reset ISA master interface */
+ unsigned short Reserved:6; /* 0: Reserved */
+} DSP_CHIP_RESET;
+
+typedef struct {
+ unsigned short N_Divisor:6; /* RW: (N) PLL output clock divisor */
+ unsigned short Reserved1:2; /* 0: reserved */
+ unsigned short M_Multiplier:6; /* RW: (M) PLL feedback clock multiplier */
+ unsigned short Reserved2:2; /* 0: reserved */
+} DSP_CLOCK_CONTROL_1;
+
+typedef struct {
+ unsigned short PllBypass:1; /* RW: PLL Bypass */
+ unsigned short Reserved:15; /* 0: Reserved */
+} DSP_CLOCK_CONTROL_2;
+
+typedef struct {
+ unsigned short Latch8:1;
+ unsigned short Latch9:1;
+ unsigned short Latch10:1;
+ unsigned short Latch11:1;
+ unsigned short Latch12:1;
+ unsigned short Latch13:1;
+ unsigned short Latch14:1;
+ unsigned short Latch15:1;
+ unsigned short Mask8:1;
+ unsigned short Mask9:1;
+ unsigned short Mask10:1;
+ unsigned short Mask11:1;
+ unsigned short Mask12:1;
+ unsigned short Mask13:1;
+ unsigned short Mask14:1;
+ unsigned short Mask15:1;
+} DSP_GPIO_OUTPUT_DATA_15_8;
+
+typedef struct {
+ unsigned short Enable8:1;
+ unsigned short Enable9:1;
+ unsigned short Enable10:1;
+ unsigned short Enable11:1;
+ unsigned short Enable12:1;
+ unsigned short Enable13:1;
+ unsigned short Enable14:1;
+ unsigned short Enable15:1;
+ unsigned short Mask8:1;
+ unsigned short Mask9:1;
+ unsigned short Mask10:1;
+ unsigned short Mask11:1;
+ unsigned short Mask12:1;
+ unsigned short Mask13:1;
+ unsigned short Mask14:1;
+ unsigned short Mask15:1;
+} DSP_GPIO_DRIVER_ENABLE_15_8;
+
+typedef struct {
+ unsigned short GpioMode8:2;
+ unsigned short GpioMode9:2;
+ unsigned short GpioMode10:2;
+ unsigned short GpioMode11:2;
+ unsigned short GpioMode12:2;
+ unsigned short GpioMode13:2;
+ unsigned short GpioMode14:2;
+ unsigned short GpioMode15:2;
+} DSP_GPIO_MODE_15_8;
+
+/* Component masks that are defined in dspmgr.h */
+#define MW_ADC_MASK 0x0001
+#define MW_AIC2_MASK 0x0006
+#define MW_MIDI_MASK 0x0008
+#define MW_CDDAC_MASK 0x8001
+#define MW_AIC1_MASK 0xE006
+#define MW_UART_MASK 0xE00A
+#define MW_ACI_MASK 0xE00B
+
+/*
+* Definition of 3780i configuration structure. Unless otherwise stated,
+* these values are provided as input to the 3780i support layer. At present,
+* the only values maintained by the 3780i support layer are the saved UART
+* registers.
+*/
+typedef struct _DSP_3780I_CONFIG_SETTINGS {
+
+ /* Location of base configuration register */
+ unsigned short usBaseConfigIO;
+
+ /* Enables for various DSP components */
+ int bDSPEnabled;
+ int bModemEnabled;
+ int bInterruptClaimed;
+
+ /* IRQ, DMA, and Base I/O addresses for various DSP components */
+ unsigned short usDspIrq;
+ unsigned short usDspDma;
+ unsigned short usDspBaseIO;
+ unsigned short usUartIrq;
+ unsigned short usUartBaseIO;
+
+ /* IRQ modes for various DSP components */
+ int bDspIrqActiveLow;
+ int bUartIrqActiveLow;
+ int bDspIrqPulse;
+ int bUartIrqPulse;
+
+ /* Card abilities */
+ unsigned uIps;
+ unsigned uDStoreSize;
+ unsigned uIStoreSize;
+ unsigned uDmaBandwidth;
+
+ /* Adapter specific 3780i settings */
+ unsigned short usNumTransfers;
+ unsigned short usReRequest;
+ int bEnableMEMCS16;
+ unsigned short usIsaMemCmdWidth;
+ int bGateIOCHRDY;
+ int bEnablePwrMgmt;
+ unsigned short usHBusTimerLoadValue;
+ int bDisableLBusTimeout;
+ unsigned short usN_Divisor;
+ unsigned short usM_Multiplier;
+ int bPllBypass;
+ unsigned short usChipletEnable; /* Used with the chip reset register to enable specific chiplets */
+
+ /* Saved UART registers. These are maintained by the 3780i support layer. */
+ int bUartSaved; /* True after a successful save of the UART registers */
+ unsigned char ucIER; /* Interrupt enable register */
+ unsigned char ucFCR; /* FIFO control register */
+ unsigned char ucLCR; /* Line control register */
+ unsigned char ucMCR; /* Modem control register */
+ unsigned char ucSCR; /* Scratch register */
+ unsigned char ucDLL; /* Divisor latch, low byte */
+ unsigned char ucDLM; /* Divisor latch, high byte */
+} DSP_3780I_CONFIG_SETTINGS;
+
+
+/* 3780i support functions */
+int dsp3780I_EnableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings,
+ unsigned short *pIrqMap,
+ unsigned short *pDmaMap);
+int dsp3780I_DisableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_Reset(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_Run(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO,
+ void __user *pvBuffer, unsigned uCount,
+ unsigned long ulDSPAddr);
+int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+ unsigned uCount, unsigned long ulDSPAddr);
+unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO,
+ unsigned long ulMsaAddr);
+void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO,
+ unsigned long ulMsaAddr, unsigned short usValue);
+void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex,
+ unsigned char ucValue);
+unsigned char dsp3780I_ReadGenCfg(unsigned short usDspBaseIO,
+ unsigned uIndex);
+int dsp3780I_GetIPCSource(unsigned short usDspBaseIO,
+ unsigned short *pusIPCSource);
+
+/* I/O port access macros */
+#define MKWORD(var) (*((unsigned short *)(&var)))
+#define MKBYTE(var) (*((unsigned char *)(&var)))
+
+#define WriteMsaCfg(addr,value) dsp3780I_WriteMsaCfg(usDspBaseIO,addr,value)
+#define ReadMsaCfg(addr) dsp3780I_ReadMsaCfg(usDspBaseIO,addr)
+#define WriteGenCfg(index,value) dsp3780I_WriteGenCfg(usDspBaseIO,index,value)
+#define ReadGenCfg(index) dsp3780I_ReadGenCfg(usDspBaseIO,index)
+
+#define InWordDsp(index) inw(usDspBaseIO+index)
+#define InByteDsp(index) inb(usDspBaseIO+index)
+#define OutWordDsp(index,value) outw(value,usDspBaseIO+index)
+#define OutByteDsp(index,value) outb(value,usDspBaseIO+index)
+
+#endif
diff --git a/drivers/char/mwave/Makefile b/drivers/char/mwave/Makefile
new file mode 100644
index 000000000000..754c9e2058ed
--- /dev/null
+++ b/drivers/char/mwave/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ACP Modem (Mwave).
+#
+# See the README file in this directory for more info. <paulsch@us.ibm.com>
+#
+
+obj-$(CONFIG_MWAVE) += mwave.o
+
+mwave-objs := mwavedd.o smapi.o tp3780i.o 3780i.o
+
+# To have the mwave driver disable other uarts if necessary
+# EXTRA_CFLAGS += -DMWAVE_FUTZ_WITH_OTHER_DEVICES
+
+# To compile in lots (~20 KiB) of run-time enablable printk()s for debugging:
+EXTRA_CFLAGS += -DMW_TRACE
diff --git a/drivers/char/mwave/README b/drivers/char/mwave/README
new file mode 100644
index 000000000000..70f8d19fb79f
--- /dev/null
+++ b/drivers/char/mwave/README
@@ -0,0 +1,50 @@
+Module options
+--------------
+
+The mwave module takes the following options. Note that these options
+are not saved by the BIOS and so do not persist after unload and reload.
+
+ mwave_debug=value, where value is bitwise OR of trace flags:
+ 0x0001 mwavedd api tracing
+ 0x0002 smapi api tracing
+ 0x0004 3780i tracing
+ 0x0008 tp3780i tracing
+
+ Tracing only occurs if the driver has been compiled with the
+ MW_TRACE macro #defined (i.e. let EXTRA_CFLAGS += -DMW_TRACE
+ in the Makefile).
+
+ mwave_3780i_irq=5/7/10/11/15
+ If the dsp irq has not been setup and stored in bios by the
+ thinkpad configuration utility then this parameter allows the
+ irq used by the dsp to be configured.
+
+ mwave_3780i_io=0x130/0x350/0x0070/0xDB0
+ If the dsp io range has not been setup and stored in bios by the
+ thinkpad configuration utility then this parameter allows the
+ io range used by the dsp to be configured.
+
+ mwave_uart_irq=3/4
+ If the mwave's uart irq has not been setup and stored in bios by the
+ thinkpad configuration utility then this parameter allows the
+ irq used by the mwave uart to be configured.
+
+ mwave_uart_io=0x3f8/0x2f8/0x3E8/0x2E8
+ If the uart io range has not been setup and stored in bios by the
+ thinkpad configuration utility then this parameter allows the
+ io range used by the mwave uart to be configured.
+
+Example to enable the 3780i DSP using ttyS1 resources:
+
+ insmod mwave mwave_3780i_irq=10 mwave_3780i_io=0x0130 mwave_uart_irq=3 mwave_uart_io=0x2f8
+
+Accessing the driver
+--------------------
+
+You must also create a node for the driver. Without devfs:
+ mkdir -p /dev/modems
+ mknod --mode=660 /dev/modems/mwave c 10 219
+With devfs:
+ mkdir -p /dev/modems
+ ln -s ../misc/mwave /dev/modems/mwave
+
diff --git a/drivers/char/mwave/mwavedd.c b/drivers/char/mwave/mwavedd.c
new file mode 100644
index 000000000000..d37625d47746
--- /dev/null
+++ b/drivers/char/mwave/mwavedd.c
@@ -0,0 +1,674 @@
+/*
+*
+* mwavedd.c -- mwave device driver
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/major.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/serial.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "3780i.h"
+#include "tp3780i.h"
+
+MODULE_DESCRIPTION("3780i Advanced Communications Processor (Mwave) driver");
+MODULE_AUTHOR("Mike Sullivan and Paul Schroeder");
+MODULE_LICENSE("GPL");
+
+/*
+* These parameters support the setting of MWave resources. Note that no
+* checks are made against other devices (ie. superio) for conflicts.
+* We'll depend on users using the tpctl utility to do that for now
+*/
+int mwave_debug = 0;
+int mwave_3780i_irq = 0;
+int mwave_3780i_io = 0;
+int mwave_uart_irq = 0;
+int mwave_uart_io = 0;
+module_param(mwave_debug, int, 0);
+module_param(mwave_3780i_irq, int, 0);
+module_param(mwave_3780i_io, int, 0);
+module_param(mwave_uart_irq, int, 0);
+module_param(mwave_uart_io, int, 0);
+
+static int mwave_open(struct inode *inode, struct file *file);
+static int mwave_close(struct inode *inode, struct file *file);
+static int mwave_ioctl(struct inode *inode, struct file *filp,
+ unsigned int iocmd, unsigned long ioarg);
+
+MWAVE_DEVICE_DATA mwave_s_mdd;
+
+static int mwave_open(struct inode *inode, struct file *file)
+{
+ unsigned int retval = 0;
+
+ PRINTK_3(TRACE_MWAVE,
+ "mwavedd::mwave_open, entry inode %p file %p\n",
+ inode, file);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_open, exit return retval %x\n", retval);
+
+ return retval;
+}
+
+static int mwave_close(struct inode *inode, struct file *file)
+{
+ unsigned int retval = 0;
+
+ PRINTK_3(TRACE_MWAVE,
+ "mwavedd::mwave_close, entry inode %p file %p\n",
+ inode, file);
+
+ PRINTK_2(TRACE_MWAVE, "mwavedd::mwave_close, exit retval %x\n",
+ retval);
+
+ return retval;
+}
+
+static int mwave_ioctl(struct inode *inode, struct file *file,
+ unsigned int iocmd, unsigned long ioarg)
+{
+ unsigned int retval = 0;
+ pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+ void __user *arg = (void __user *)ioarg;
+
+ PRINTK_5(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, entry inode %p file %p cmd %x arg %x\n",
+ inode, file, iocmd, (int) ioarg);
+
+ switch (iocmd) {
+
+ case IOCTL_MW_RESET:
+ PRINTK_1(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_RESET"
+ " calling tp3780I_ResetDSP\n");
+ retval = tp3780I_ResetDSP(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_RESET"
+ " retval %x from tp3780I_ResetDSP\n",
+ retval);
+ break;
+
+ case IOCTL_MW_RUN:
+ PRINTK_1(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_RUN"
+ " calling tp3780I_StartDSP\n");
+ retval = tp3780I_StartDSP(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_RUN"
+ " retval %x from tp3780I_StartDSP\n",
+ retval);
+ break;
+
+ case IOCTL_MW_DSP_ABILITIES: {
+ MW_ABILITIES rAbilities;
+
+ PRINTK_1(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl,"
+ " IOCTL_MW_DSP_ABILITIES calling"
+ " tp3780I_QueryAbilities\n");
+ retval = tp3780I_QueryAbilities(&pDrvData->rBDData,
+ &rAbilities);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
+ " retval %x from tp3780I_QueryAbilities\n",
+ retval);
+ if (retval == 0) {
+ if( copy_to_user(arg, &rAbilities,
+ sizeof(MW_ABILITIES)) )
+ return -EFAULT;
+ }
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
+ " exit retval %x\n",
+ retval);
+ }
+ break;
+
+ case IOCTL_MW_READ_DATA:
+ case IOCTL_MW_READCLEAR_DATA: {
+ MW_READWRITE rReadData;
+ unsigned short __user *pusBuffer = NULL;
+
+ if( copy_from_user(&rReadData, arg,
+ sizeof(MW_READWRITE)) )
+ return -EFAULT;
+ pusBuffer = (unsigned short __user *) (rReadData.pBuf);
+
+ PRINTK_4(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_READ_DATA,"
+ " size %lx, ioarg %lx pusBuffer %p\n",
+ rReadData.ulDataLength, ioarg, pusBuffer);
+ retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+ iocmd,
+ pusBuffer,
+ rReadData.ulDataLength,
+ rReadData.usDspAddress);
+ }
+ break;
+
+ case IOCTL_MW_READ_INST: {
+ MW_READWRITE rReadData;
+ unsigned short __user *pusBuffer = NULL;
+
+ if( copy_from_user(&rReadData, arg,
+ sizeof(MW_READWRITE)) )
+ return -EFAULT;
+ pusBuffer = (unsigned short __user *) (rReadData.pBuf);
+
+ PRINTK_4(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_READ_INST,"
+ " size %lx, ioarg %lx pusBuffer %p\n",
+ rReadData.ulDataLength / 2, ioarg,
+ pusBuffer);
+ retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+ iocmd, pusBuffer,
+ rReadData.ulDataLength / 2,
+ rReadData.usDspAddress);
+ }
+ break;
+
+ case IOCTL_MW_WRITE_DATA: {
+ MW_READWRITE rWriteData;
+ unsigned short __user *pusBuffer = NULL;
+
+ if( copy_from_user(&rWriteData, arg,
+ sizeof(MW_READWRITE)) )
+ return -EFAULT;
+ pusBuffer = (unsigned short __user *) (rWriteData.pBuf);
+
+ PRINTK_4(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_WRITE_DATA,"
+ " size %lx, ioarg %lx pusBuffer %p\n",
+ rWriteData.ulDataLength, ioarg,
+ pusBuffer);
+ retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+ iocmd, pusBuffer,
+ rWriteData.ulDataLength,
+ rWriteData.usDspAddress);
+ }
+ break;
+
+ case IOCTL_MW_WRITE_INST: {
+ MW_READWRITE rWriteData;
+ unsigned short __user *pusBuffer = NULL;
+
+ if( copy_from_user(&rWriteData, arg,
+ sizeof(MW_READWRITE)) )
+ return -EFAULT;
+ pusBuffer = (unsigned short __user *)(rWriteData.pBuf);
+
+ PRINTK_4(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_WRITE_INST,"
+ " size %lx, ioarg %lx pusBuffer %p\n",
+ rWriteData.ulDataLength, ioarg,
+ pusBuffer);
+ retval = tp3780I_ReadWriteDspIStore(&pDrvData->rBDData,
+ iocmd, pusBuffer,
+ rWriteData.ulDataLength,
+ rWriteData.usDspAddress);
+ }
+ break;
+
+ case IOCTL_MW_REGISTER_IPC: {
+ unsigned int ipcnum = (unsigned int) ioarg;
+
+ PRINTK_3(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
+ " ipcnum %x entry usIntCount %x\n",
+ ipcnum,
+ pDrvData->IPCs[ipcnum].usIntCount);
+
+ if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::mwave_ioctl:"
+ " IOCTL_MW_REGISTER_IPC:"
+ " Error: Invalid ipcnum %x\n",
+ ipcnum);
+ return -EINVAL;
+ }
+ pDrvData->IPCs[ipcnum].bIsHere = FALSE;
+ pDrvData->IPCs[ipcnum].bIsEnabled = TRUE;
+
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
+ " ipcnum %x exit\n",
+ ipcnum);
+ }
+ break;
+
+ case IOCTL_MW_GET_IPC: {
+ unsigned int ipcnum = (unsigned int) ioarg;
+
+ PRINTK_3(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_GET_IPC"
+ " ipcnum %x, usIntCount %x\n",
+ ipcnum,
+ pDrvData->IPCs[ipcnum].usIntCount);
+ if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::mwave_ioctl:"
+ " IOCTL_MW_GET_IPC: Error:"
+ " Invalid ipcnum %x\n", ipcnum);
+ return -EINVAL;
+ }
+
+ if (pDrvData->IPCs[ipcnum].bIsEnabled == TRUE) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl, thread for"
+ " ipc %x going to sleep\n",
+ ipcnum);
+ add_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
+ pDrvData->IPCs[ipcnum].bIsHere = TRUE;
+ set_current_state(TASK_INTERRUPTIBLE);
+ /* check whether an event was signalled by */
+ /* the interrupt handler while we were gone */
+ if (pDrvData->IPCs[ipcnum].usIntCount == 1) { /* first int has occurred (race condition) */
+ pDrvData->IPCs[ipcnum].usIntCount = 2; /* first int has been handled */
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl"
+ " IOCTL_MW_GET_IPC ipcnum %x"
+ " handling first int\n",
+ ipcnum);
+ } else { /* either 1st int has not yet occurred, or we have already handled the first int */
+ schedule();
+ if (pDrvData->IPCs[ipcnum].usIntCount == 1) {
+ pDrvData->IPCs[ipcnum].usIntCount = 2;
+ }
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl"
+ " IOCTL_MW_GET_IPC ipcnum %x"
+ " woke up and returning to"
+ " application\n",
+ ipcnum);
+ }
+ pDrvData->IPCs[ipcnum].bIsHere = FALSE;
+ remove_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
+ set_current_state(TASK_RUNNING);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_GET_IPC,"
+ " returning thread for ipc %x"
+ " processing\n",
+ ipcnum);
+ }
+ }
+ break;
+
+ case IOCTL_MW_UNREGISTER_IPC: {
+ unsigned int ipcnum = (unsigned int) ioarg;
+
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_ioctl IOCTL_MW_UNREGISTER_IPC"
+ " ipcnum %x\n",
+ ipcnum);
+ if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::mwave_ioctl:"
+ " IOCTL_MW_UNREGISTER_IPC:"
+ " Error: Invalid ipcnum %x\n",
+ ipcnum);
+ return -EINVAL;
+ }
+ if (pDrvData->IPCs[ipcnum].bIsEnabled == TRUE) {
+ pDrvData->IPCs[ipcnum].bIsEnabled = FALSE;
+ if (pDrvData->IPCs[ipcnum].bIsHere == TRUE) {
+ wake_up_interruptible(&pDrvData->IPCs[ipcnum].ipc_wait_queue);
+ }
+ }
+ }
+ break;
+
+ default:
+ PRINTK_ERROR(KERN_ERR_MWAVE "mwavedd::mwave_ioctl:"
+ " Error: Unrecognized iocmd %x\n",
+ iocmd);
+ return -ENOTTY;
+ break;
+ } /* switch */
+
+ PRINTK_2(TRACE_MWAVE, "mwavedd::mwave_ioctl, exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+static ssize_t mwave_read(struct file *file, char __user *buf, size_t count,
+ loff_t * ppos)
+{
+ PRINTK_5(TRACE_MWAVE,
+ "mwavedd::mwave_read entry file %p, buf %p, count %zx ppos %p\n",
+ file, buf, count, ppos);
+
+ return -EINVAL;
+}
+
+
+static ssize_t mwave_write(struct file *file, const char __user *buf,
+ size_t count, loff_t * ppos)
+{
+ PRINTK_5(TRACE_MWAVE,
+ "mwavedd::mwave_write entry file %p, buf %p,"
+ " count %zx ppos %p\n",
+ file, buf, count, ppos);
+
+ return -EINVAL;
+}
+
+
+static int register_serial_portandirq(unsigned int port, int irq)
+{
+ struct serial_struct serial;
+
+ switch ( port ) {
+ case 0x3f8:
+ case 0x2f8:
+ case 0x3e8:
+ case 0x2e8:
+ /* OK */
+ break;
+ default:
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::register_serial_portandirq:"
+ " Error: Illegal port %x\n", port );
+ return -1;
+ } /* switch */
+ /* port is okay */
+
+ switch ( irq ) {
+ case 3:
+ case 4:
+ case 5:
+ case 7:
+ /* OK */
+ break;
+ default:
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::register_serial_portandirq:"
+ " Error: Illegal irq %x\n", irq );
+ return -1;
+ } /* switch */
+ /* irq is okay */
+
+ memset(&serial, 0, sizeof(serial));
+ serial.port = port;
+ serial.irq = irq;
+ serial.flags = ASYNC_SHARE_IRQ;
+
+ return register_serial(&serial);
+}
+
+
+static struct file_operations mwave_fops = {
+ .owner = THIS_MODULE,
+ .read = mwave_read,
+ .write = mwave_write,
+ .ioctl = mwave_ioctl,
+ .open = mwave_open,
+ .release = mwave_close
+};
+
+
+static struct miscdevice mwave_misc_dev = { MWAVE_MINOR, "mwave", &mwave_fops };
+
+#if 0 /* totally b0rked */
+/*
+ * sysfs support <paulsch@us.ibm.com>
+ */
+
+struct device mwave_device;
+
+/* Prevent code redundancy, create a macro for mwave_show_* functions. */
+#define mwave_show_function(attr_name, format_string, field) \
+static ssize_t mwave_show_##attr_name(struct device *dev, char *buf) \
+{ \
+ DSP_3780I_CONFIG_SETTINGS *pSettings = \
+ &mwave_s_mdd.rBDData.rDspSettings; \
+ return sprintf(buf, format_string, pSettings->field); \
+}
+
+/* All of our attributes are read attributes. */
+#define mwave_dev_rd_attr(attr_name, format_string, field) \
+ mwave_show_function(attr_name, format_string, field) \
+static DEVICE_ATTR(attr_name, S_IRUGO, mwave_show_##attr_name, NULL)
+
+mwave_dev_rd_attr (3780i_dma, "%i\n", usDspDma);
+mwave_dev_rd_attr (3780i_irq, "%i\n", usDspIrq);
+mwave_dev_rd_attr (3780i_io, "%#.4x\n", usDspBaseIO);
+mwave_dev_rd_attr (uart_irq, "%i\n", usUartIrq);
+mwave_dev_rd_attr (uart_io, "%#.4x\n", usUartBaseIO);
+
+static struct device_attribute * const mwave_dev_attrs[] = {
+ &dev_attr_3780i_dma,
+ &dev_attr_3780i_irq,
+ &dev_attr_3780i_io,
+ &dev_attr_uart_irq,
+ &dev_attr_uart_io,
+};
+#endif
+
+/*
+* mwave_init is called on module load
+*
+* mwave_exit is called on module unload
+* mwave_exit is also used to clean up after an aborted mwave_init
+*/
+static void mwave_exit(void)
+{
+ pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+
+ PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit entry\n");
+
+#if 0
+ for (i = 0; i < pDrvData->nr_registered_attrs; i++)
+ device_remove_file(&mwave_device, mwave_dev_attrs[i]);
+ pDrvData->nr_registered_attrs = 0;
+
+ if (pDrvData->device_registered) {
+ device_unregister(&mwave_device);
+ pDrvData->device_registered = FALSE;
+ }
+#endif
+
+ if ( pDrvData->sLine >= 0 ) {
+ unregister_serial(pDrvData->sLine);
+ }
+ if (pDrvData->bMwaveDevRegistered) {
+ misc_deregister(&mwave_misc_dev);
+ }
+ if (pDrvData->bDSPEnabled) {
+ tp3780I_DisableDSP(&pDrvData->rBDData);
+ }
+ if (pDrvData->bResourcesClaimed) {
+ tp3780I_ReleaseResources(&pDrvData->rBDData);
+ }
+ if (pDrvData->bBDInitialized) {
+ tp3780I_Cleanup(&pDrvData->rBDData);
+ }
+
+ PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit exit\n");
+}
+
+module_exit(mwave_exit);
+
+static int __init mwave_init(void)
+{
+ int i;
+ int retval = 0;
+ pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+
+ PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_init entry\n");
+
+ memset(&mwave_s_mdd, 0, sizeof(MWAVE_DEVICE_DATA));
+
+ pDrvData->bBDInitialized = FALSE;
+ pDrvData->bResourcesClaimed = FALSE;
+ pDrvData->bDSPEnabled = FALSE;
+ pDrvData->bDSPReset = FALSE;
+ pDrvData->bMwaveDevRegistered = FALSE;
+ pDrvData->sLine = -1;
+
+ for (i = 0; i < ARRAY_SIZE(pDrvData->IPCs); i++) {
+ pDrvData->IPCs[i].bIsEnabled = FALSE;
+ pDrvData->IPCs[i].bIsHere = FALSE;
+ pDrvData->IPCs[i].usIntCount = 0; /* no ints received yet */
+ init_waitqueue_head(&pDrvData->IPCs[i].ipc_wait_queue);
+ }
+
+ retval = tp3780I_InitializeBoardData(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_init, return from tp3780I_InitializeBoardData"
+ " retval %x\n",
+ retval);
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::mwave_init: Error:"
+ " Failed to initialize board data\n");
+ goto cleanup_error;
+ }
+ pDrvData->bBDInitialized = TRUE;
+
+ retval = tp3780I_CalcResources(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_init, return from tp3780I_CalcResources"
+ " retval %x\n",
+ retval);
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to calculate resources\n");
+ goto cleanup_error;
+ }
+
+ retval = tp3780I_ClaimResources(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_init, return from tp3780I_ClaimResources"
+ " retval %x\n",
+ retval);
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to claim resources\n");
+ goto cleanup_error;
+ }
+ pDrvData->bResourcesClaimed = TRUE;
+
+ retval = tp3780I_EnableDSP(&pDrvData->rBDData);
+ PRINTK_2(TRACE_MWAVE,
+ "mwavedd::mwave_init, return from tp3780I_EnableDSP"
+ " retval %x\n",
+ retval);
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to enable DSP\n");
+ goto cleanup_error;
+ }
+ pDrvData->bDSPEnabled = TRUE;
+
+ if (misc_register(&mwave_misc_dev) < 0) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to register misc device\n");
+ goto cleanup_error;
+ }
+ pDrvData->bMwaveDevRegistered = TRUE;
+
+ pDrvData->sLine = register_serial_portandirq(
+ pDrvData->rBDData.rDspSettings.usUartBaseIO,
+ pDrvData->rBDData.rDspSettings.usUartIrq
+ );
+ if (pDrvData->sLine < 0) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to register serial driver\n");
+ goto cleanup_error;
+ }
+ /* uart is registered */
+
+#if 0
+ /* sysfs */
+ memset(&mwave_device, 0, sizeof (struct device));
+ snprintf(mwave_device.bus_id, BUS_ID_SIZE, "mwave");
+
+ if (device_register(&mwave_device))
+ goto cleanup_error;
+ pDrvData->device_registered = TRUE;
+ for (i = 0; i < ARRAY_SIZE(mwave_dev_attrs); i++) {
+ if(device_create_file(&mwave_device, mwave_dev_attrs[i])) {
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd:mwave_init: Error:"
+ " Failed to create sysfs file %s\n",
+ mwave_dev_attrs[i]->attr.name);
+ goto cleanup_error;
+ }
+ pDrvData->nr_registered_attrs++;
+ }
+#endif
+
+ /* SUCCESS! */
+ return 0;
+
+cleanup_error:
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "mwavedd::mwave_init: Error:"
+ " Failed to initialize\n");
+ mwave_exit(); /* clean up */
+
+ return -EIO;
+}
+
+module_init(mwave_init);
+
diff --git a/drivers/char/mwave/mwavedd.h b/drivers/char/mwave/mwavedd.h
new file mode 100644
index 000000000000..8eca61e0a19c
--- /dev/null
+++ b/drivers/char/mwave/mwavedd.h
@@ -0,0 +1,150 @@
+/*
+*
+* mwavedd.h -- declarations for mwave device driver
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#ifndef _LINUX_MWAVEDD_H
+#define _LINUX_MWAVEDD_H
+#include "3780i.h"
+#include "tp3780i.h"
+#include "smapi.h"
+#include "mwavepub.h"
+#include <linux/ioctl.h>
+#include <asm/uaccess.h>
+#include <linux/wait.h>
+
+extern int mwave_debug;
+extern int mwave_3780i_irq;
+extern int mwave_3780i_io;
+extern int mwave_uart_irq;
+extern int mwave_uart_io;
+
+#define PRINTK_ERROR printk
+#define KERN_ERR_MWAVE KERN_ERR "mwave: "
+
+#define TRACE_MWAVE 0x0001
+#define TRACE_SMAPI 0x0002
+#define TRACE_3780I 0x0004
+#define TRACE_TP3780I 0x0008
+
+#ifdef MW_TRACE
+#define PRINTK_1(f,s) \
+ if (f & (mwave_debug)) { \
+ printk(s); \
+ }
+
+#define PRINTK_2(f,s,v1) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1); \
+ }
+
+#define PRINTK_3(f,s,v1,v2) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2); \
+ }
+
+#define PRINTK_4(f,s,v1,v2,v3) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2,v3); \
+ }
+
+#define PRINTK_5(f,s,v1,v2,v3,v4) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2,v3,v4); \
+ }
+
+#define PRINTK_6(f,s,v1,v2,v3,v4,v5) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2,v3,v4,v5); \
+ }
+
+#define PRINTK_7(f,s,v1,v2,v3,v4,v5,v6) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2,v3,v4,v5,v6); \
+ }
+
+#define PRINTK_8(f,s,v1,v2,v3,v4,v5,v6,v7) \
+ if (f & (mwave_debug)) { \
+ printk(s,v1,v2,v3,v4,v5,v6,v7); \
+ }
+
+#else
+#define PRINTK_1(f,s)
+#define PRINTK_2(f,s,v1)
+#define PRINTK_3(f,s,v1,v2)
+#define PRINTK_4(f,s,v1,v2,v3)
+#define PRINTK_5(f,s,v1,v2,v3,v4)
+#define PRINTK_6(f,s,v1,v2,v3,v4,v5)
+#define PRINTK_7(f,s,v1,v2,v3,v4,v5,v6)
+#define PRINTK_8(f,s,v1,v2,v3,v4,v5,v6,v7)
+#endif
+
+
+typedef struct _MWAVE_IPC {
+ unsigned short usIntCount; /* 0=none, 1=first, 2=greater than 1st */
+ BOOLEAN bIsEnabled;
+ BOOLEAN bIsHere;
+ /* entry spin lock */
+ wait_queue_head_t ipc_wait_queue;
+} MWAVE_IPC;
+
+typedef struct _MWAVE_DEVICE_DATA {
+ THINKPAD_BD_DATA rBDData; /* board driver's data area */
+ unsigned long ulIPCSource_ISR; /* IPC source bits for recently processed intr, set during ISR processing */
+ unsigned long ulIPCSource_DPC; /* IPC source bits for recently processed intr, set during DPC processing */
+ BOOLEAN bBDInitialized;
+ BOOLEAN bResourcesClaimed;
+ BOOLEAN bDSPEnabled;
+ BOOLEAN bDSPReset;
+ MWAVE_IPC IPCs[16];
+ BOOLEAN bMwaveDevRegistered;
+ short sLine;
+ int nr_registered_attrs;
+ int device_registered;
+
+} MWAVE_DEVICE_DATA, *pMWAVE_DEVICE_DATA;
+
+#endif
diff --git a/drivers/char/mwave/mwavepub.h b/drivers/char/mwave/mwavepub.h
new file mode 100644
index 000000000000..f1f9da7a65c1
--- /dev/null
+++ b/drivers/char/mwave/mwavepub.h
@@ -0,0 +1,89 @@
+/*
+*
+* mwavepub.h -- PUBLIC declarations for the mwave driver
+* and applications using it
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#ifndef _LINUX_MWAVEPUB_H
+#define _LINUX_MWAVEPUB_H
+
+#include <linux/miscdevice.h>
+
+
+typedef struct _MW_ABILITIES {
+ unsigned long instr_per_sec;
+ unsigned long data_size;
+ unsigned long inst_size;
+ unsigned long bus_dma_bw;
+ unsigned short uart_enable;
+ short component_count;
+ unsigned long component_list[7];
+ char mwave_os_name[16];
+ char bios_task_name[16];
+} MW_ABILITIES, *pMW_ABILITIES;
+
+
+typedef struct _MW_READWRITE {
+ unsigned short usDspAddress; /* The dsp address */
+ unsigned long ulDataLength; /* The size in bytes of the data or user buffer */
+ void *pBuf; /* Input:variable sized buffer */
+} MW_READWRITE, *pMW_READWRITE;
+
+#define IOCTL_MW_RESET _IO(MWAVE_MINOR,1)
+#define IOCTL_MW_RUN _IO(MWAVE_MINOR,2)
+#define IOCTL_MW_DSP_ABILITIES _IOR(MWAVE_MINOR,3,MW_ABILITIES)
+#define IOCTL_MW_READ_DATA _IOR(MWAVE_MINOR,4,MW_READWRITE)
+#define IOCTL_MW_READCLEAR_DATA _IOR(MWAVE_MINOR,5,MW_READWRITE)
+#define IOCTL_MW_READ_INST _IOR(MWAVE_MINOR,6,MW_READWRITE)
+#define IOCTL_MW_WRITE_DATA _IOW(MWAVE_MINOR,7,MW_READWRITE)
+#define IOCTL_MW_WRITE_INST _IOW(MWAVE_MINOR,8,MW_READWRITE)
+#define IOCTL_MW_REGISTER_IPC _IOW(MWAVE_MINOR,9,int)
+#define IOCTL_MW_UNREGISTER_IPC _IOW(MWAVE_MINOR,10,int)
+#define IOCTL_MW_GET_IPC _IOW(MWAVE_MINOR,11,int)
+#define IOCTL_MW_TRACE _IOR(MWAVE_MINOR,12,MW_READWRITE)
+
+
+#endif
diff --git a/drivers/char/mwave/smapi.c b/drivers/char/mwave/smapi.c
new file mode 100644
index 000000000000..6187fd14b3fe
--- /dev/null
+++ b/drivers/char/mwave/smapi.c
@@ -0,0 +1,570 @@
+/*
+*
+* smapi.c -- SMAPI interface routines
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#include <linux/kernel.h>
+#include <linux/mc146818rtc.h> /* CMOS defines */
+#include "smapi.h"
+#include "mwavedd.h"
+
+static unsigned short g_usSmapiPort = 0;
+
+
+static int smapi_request(unsigned short inBX, unsigned short inCX,
+ unsigned short inDI, unsigned short inSI,
+ unsigned short *outAX, unsigned short *outBX,
+ unsigned short *outCX, unsigned short *outDX,
+ unsigned short *outDI, unsigned short *outSI)
+{
+ unsigned short myoutAX = 2, *pmyoutAX = &myoutAX;
+ unsigned short myoutBX = 3, *pmyoutBX = &myoutBX;
+ unsigned short myoutCX = 4, *pmyoutCX = &myoutCX;
+ unsigned short myoutDX = 5, *pmyoutDX = &myoutDX;
+ unsigned short myoutDI = 6, *pmyoutDI = &myoutDI;
+ unsigned short myoutSI = 7, *pmyoutSI = &myoutSI;
+ unsigned short usSmapiOK = -EIO, *pusSmapiOK = &usSmapiOK;
+ unsigned int inBXCX = (inBX << 16) | inCX;
+ unsigned int inDISI = (inDI << 16) | inSI;
+ int retval = 0;
+
+ PRINTK_5(TRACE_SMAPI, "inBX %x inCX %x inDI %x inSI %x\n",
+ inBX, inCX, inDI, inSI);
+
+ __asm__ __volatile__("movw $0x5380,%%ax\n\t"
+ "movl %7,%%ebx\n\t"
+ "shrl $16, %%ebx\n\t"
+ "movw %7,%%cx\n\t"
+ "movl %8,%%edi\n\t"
+ "shrl $16,%%edi\n\t"
+ "movw %8,%%si\n\t"
+ "movw %9,%%dx\n\t"
+ "out %%al,%%dx\n\t"
+ "out %%al,$0x4F\n\t"
+ "cmpb $0x53,%%ah\n\t"
+ "je 2f\n\t"
+ "1:\n\t"
+ "orb %%ah,%%ah\n\t"
+ "jnz 2f\n\t"
+ "movw %%ax,%0\n\t"
+ "movw %%bx,%1\n\t"
+ "movw %%cx,%2\n\t"
+ "movw %%dx,%3\n\t"
+ "movw %%di,%4\n\t"
+ "movw %%si,%5\n\t"
+ "movw $1,%6\n\t"
+ "2:\n\t":"=m"(*(unsigned short *) pmyoutAX),
+ "=m"(*(unsigned short *) pmyoutBX),
+ "=m"(*(unsigned short *) pmyoutCX),
+ "=m"(*(unsigned short *) pmyoutDX),
+ "=m"(*(unsigned short *) pmyoutDI),
+ "=m"(*(unsigned short *) pmyoutSI),
+ "=m"(*(unsigned short *) pusSmapiOK)
+ :"m"(inBXCX), "m"(inDISI), "m"(g_usSmapiPort)
+ :"%eax", "%ebx", "%ecx", "%edx", "%edi",
+ "%esi");
+
+ PRINTK_8(TRACE_SMAPI,
+ "myoutAX %x myoutBX %x myoutCX %x myoutDX %x myoutDI %x myoutSI %x usSmapiOK %x\n",
+ myoutAX, myoutBX, myoutCX, myoutDX, myoutDI, myoutSI,
+ usSmapiOK);
+ *outAX = myoutAX;
+ *outBX = myoutBX;
+ *outCX = myoutCX;
+ *outDX = myoutDX;
+ *outDI = myoutDI;
+ *outSI = myoutSI;
+
+ retval = (usSmapiOK == 1) ? 0 : -EIO;
+ PRINTK_2(TRACE_SMAPI, "smapi::smapi_request exit retval %x\n", retval);
+ return retval;
+}
+
+
+int smapi_query_DSP_cfg(SMAPI_DSP_SETTINGS * pSettings)
+{
+ int bRC = -EIO;
+ unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+ unsigned short ausDspBases[] = { 0x0030, 0x4E30, 0x8E30, 0xCE30, 0x0130, 0x0350, 0x0070, 0x0DB0 };
+ unsigned short ausUartBases[] = { 0x03F8, 0x02F8, 0x03E8, 0x02E8 };
+ unsigned short numDspBases = 8;
+ unsigned short numUartBases = 4;
+
+ PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg entry\n");
+
+ bRC = smapi_request(0x1802, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Error: Could not get DSP Settings. Aborting.\n");
+ return bRC;
+ }
+
+ PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg, smapi_request OK\n");
+
+ pSettings->bDSPPresent = ((usBX & 0x0100) != 0);
+ pSettings->bDSPEnabled = ((usCX & 0x0001) != 0);
+ pSettings->usDspIRQ = usSI & 0x00FF;
+ pSettings->usDspDMA = (usSI & 0xFF00) >> 8;
+ if ((usDI & 0x00FF) < numDspBases) {
+ pSettings->usDspBaseIO = ausDspBases[usDI & 0x00FF];
+ } else {
+ pSettings->usDspBaseIO = 0;
+ }
+ PRINTK_6(TRACE_SMAPI,
+ "smapi::smapi_query_DSP_cfg get DSP Settings bDSPPresent %x bDSPEnabled %x usDspIRQ %x usDspDMA %x usDspBaseIO %x\n",
+ pSettings->bDSPPresent, pSettings->bDSPEnabled,
+ pSettings->usDspIRQ, pSettings->usDspDMA,
+ pSettings->usDspBaseIO);
+
+ /* check for illegal values */
+ if ( pSettings->usDspBaseIO == 0 )
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: DSP base I/O address is 0\n");
+ if ( pSettings->usDspIRQ == 0 )
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: DSP IRQ line is 0\n");
+
+ bRC = smapi_request(0x1804, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) {
+ PRINTK_ERROR("smapi::smapi_query_DSP_cfg: Error: Could not get DSP modem settings. Aborting.\n");
+ return bRC;
+ }
+
+ PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg, smapi_request OK\n");
+
+ pSettings->bModemEnabled = ((usCX & 0x0001) != 0);
+ pSettings->usUartIRQ = usSI & 0x000F;
+ if (((usSI & 0xFF00) >> 8) < numUartBases) {
+ pSettings->usUartBaseIO = ausUartBases[(usSI & 0xFF00) >> 8];
+ } else {
+ pSettings->usUartBaseIO = 0;
+ }
+
+ PRINTK_4(TRACE_SMAPI,
+ "smapi::smapi_query_DSP_cfg get DSP modem settings bModemEnabled %x usUartIRQ %x usUartBaseIO %x\n",
+ pSettings->bModemEnabled,
+ pSettings->usUartIRQ,
+ pSettings->usUartBaseIO);
+
+ /* check for illegal values */
+ if ( pSettings->usUartBaseIO == 0 )
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: UART base I/O address is 0\n");
+ if ( pSettings->usUartIRQ == 0 )
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: UART IRQ line is 0\n");
+
+ PRINTK_2(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg exit bRC %x\n", bRC);
+
+ return bRC;
+}
+
+
+int smapi_set_DSP_cfg(void)
+{
+ int bRC = -EIO;
+ int i;
+ unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+ unsigned short ausDspBases[] = { 0x0030, 0x4E30, 0x8E30, 0xCE30, 0x0130, 0x0350, 0x0070, 0x0DB0 };
+ unsigned short ausUartBases[] = { 0x03F8, 0x02F8, 0x03E8, 0x02E8 };
+ unsigned short ausDspIrqs[] = { 5, 7, 10, 11, 15 };
+ unsigned short ausUartIrqs[] = { 3, 4 };
+
+ unsigned short numDspBases = 8;
+ unsigned short numUartBases = 4;
+ unsigned short numDspIrqs = 5;
+ unsigned short numUartIrqs = 2;
+ unsigned short dspio_index = 0, uartio_index = 0;
+
+ PRINTK_5(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg entry mwave_3780i_irq %x mwave_3780i_io %x mwave_uart_irq %x mwave_uart_io %x\n",
+ mwave_3780i_irq, mwave_3780i_io, mwave_uart_irq, mwave_uart_io);
+
+ if (mwave_3780i_io) {
+ for (i = 0; i < numDspBases; i++) {
+ if (mwave_3780i_io == ausDspBases[i])
+ break;
+ }
+ if (i == numDspBases) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_3780i_io address %x. Aborting.\n", mwave_3780i_io);
+ return bRC;
+ }
+ dspio_index = i;
+ }
+
+ if (mwave_3780i_irq) {
+ for (i = 0; i < numDspIrqs; i++) {
+ if (mwave_3780i_irq == ausDspIrqs[i])
+ break;
+ }
+ if (i == numDspIrqs) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_3780i_irq %x. Aborting.\n", mwave_3780i_irq);
+ return bRC;
+ }
+ }
+
+ if (mwave_uart_io) {
+ for (i = 0; i < numUartBases; i++) {
+ if (mwave_uart_io == ausUartBases[i])
+ break;
+ }
+ if (i == numUartBases) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_uart_io address %x. Aborting.\n", mwave_uart_io);
+ return bRC;
+ }
+ uartio_index = i;
+ }
+
+
+ if (mwave_uart_irq) {
+ for (i = 0; i < numUartIrqs; i++) {
+ if (mwave_uart_irq == ausUartIrqs[i])
+ break;
+ }
+ if (i == numUartIrqs) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_uart_irq %x. Aborting.\n", mwave_uart_irq);
+ return bRC;
+ }
+ }
+
+ if (mwave_uart_irq || mwave_uart_io) {
+
+ /* Check serial port A */
+ bRC = smapi_request(0x1402, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ /* bRC == 0 */
+ if (usBX & 0x0100) { /* serial port A is present */
+ if (usCX & 1) { /* serial port is enabled */
+ if ((usSI & 0xFF) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: Serial port A irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: Serial port A irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting serial port\n");
+ bRC = smapi_request(0x1403, 0x0100, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1402, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ } else {
+ if ((usSI >> 8) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting serial port A\n");
+ bRC = smapi_request (0x1403, 0x0100, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request (0x1402, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ }
+ }
+ }
+ }
+
+ /* Check serial port B */
+ bRC = smapi_request(0x1404, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ /* bRC == 0 */
+ if (usBX & 0x0100) { /* serial port B is present */
+ if (usCX & 1) { /* serial port is enabled */
+ if ((usSI & 0xFF) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: Serial port B irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: Serial port B irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting serial port B\n");
+ bRC = smapi_request(0x1405, 0x0100, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1404, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ } else {
+ if ((usSI >> 8) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1 (TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting serial port B\n");
+ bRC = smapi_request (0x1405, 0x0100, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request (0x1404, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ }
+ }
+ }
+ }
+
+ /* Check IR port */
+ bRC = smapi_request(0x1700, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1704, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ /* bRC == 0 */
+ if ((usCX & 0xff) != 0xff) { /* IR port not disabled */
+ if ((usCX & 0xff) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: IR port irq %x conflicts with mwave_uart_irq %x\n", usCX & 0xff, mwave_uart_irq);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: IR port irq %x conflicts with mwave_uart_irq %x\n", usCX & 0xff, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting IR port\n");
+ bRC = smapi_request(0x1701, 0x0100, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1700, 0, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1705, 0x01ff, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1704, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ } else {
+ if ((usSI & 0xff) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_ERROR(KERN_ERR_MWAVE
+ "smapi::smapi_set_DSP_cfg: IR port base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI & 0xff], ausUartBases[uartio_index]);
+#else
+ PRINTK_3(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg: IR port base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI & 0xff], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+ PRINTK_1(TRACE_SMAPI,
+ "smapi::smapi_set_DSP_cfg Disabling conflicting IR port\n");
+ bRC = smapi_request(0x1701, 0x0100, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1700, 0, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1705, 0x01ff, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+ bRC = smapi_request(0x1704, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+#else
+ goto exit_conflict;
+#endif
+ }
+ }
+ }
+ }
+
+ bRC = smapi_request(0x1802, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+ if (mwave_3780i_io) {
+ usDI = dspio_index;
+ }
+ if (mwave_3780i_irq) {
+ usSI = (usSI & 0xff00) | mwave_3780i_irq;
+ }
+
+ bRC = smapi_request(0x1803, 0x0101, usDI, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+ bRC = smapi_request(0x1804, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+ if (mwave_uart_io) {
+ usSI = (usSI & 0x00ff) | (uartio_index << 8);
+ }
+ if (mwave_uart_irq) {
+ usSI = (usSI & 0xff00) | mwave_uart_irq;
+ }
+ bRC = smapi_request(0x1805, 0x0101, 0, usSI,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+ bRC = smapi_request(0x1802, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+ bRC = smapi_request(0x1804, 0x0000, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+ if (bRC) goto exit_smapi_request_error;
+
+/* normal exit: */
+ PRINTK_1(TRACE_SMAPI, "smapi::smapi_set_DSP_cfg exit\n");
+ return 0;
+
+exit_conflict:
+ /* Message has already been printed */
+ return -EIO;
+
+exit_smapi_request_error:
+ PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg exit on smapi_request error bRC %x\n", bRC);
+ return bRC;
+}
+
+
+int smapi_set_DSP_power_state(BOOLEAN bOn)
+{
+ int bRC = -EIO;
+ unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+ unsigned short usPowerFunction;
+
+ PRINTK_2(TRACE_SMAPI, "smapi::smapi_set_DSP_power_state entry bOn %x\n", bOn);
+
+ usPowerFunction = (bOn) ? 1 : 0;
+
+ bRC = smapi_request(0x4901, 0x0000, 0, usPowerFunction,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+
+ PRINTK_2(TRACE_SMAPI, "smapi::smapi_set_DSP_power_state exit bRC %x\n", bRC);
+
+ return bRC;
+}
+
+#if 0
+static int SmapiQuerySystemID(void)
+{
+ int bRC = -EIO;
+ unsigned short usAX = 0xffff, usBX = 0xffff, usCX = 0xffff,
+ usDX = 0xffff, usDI = 0xffff, usSI = 0xffff;
+
+ printk("smapi::SmapiQUerySystemID entry\n");
+ bRC = smapi_request(0x0000, 0, 0, 0,
+ &usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+
+ if (bRC == 0) {
+ printk("AX=%x, BX=%x, CX=%x, DX=%x, DI=%x, SI=%x\n",
+ usAX, usBX, usCX, usDX, usDI, usSI);
+ } else {
+ printk("smapi::SmapiQuerySystemID smapi_request error\n");
+ }
+
+ return bRC;
+}
+#endif /* 0 */
+
+int smapi_init(void)
+{
+ int retval = -EIO;
+ unsigned short usSmapiID = 0;
+ unsigned long flags;
+
+ PRINTK_1(TRACE_SMAPI, "smapi::smapi_init entry\n");
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ usSmapiID = CMOS_READ(0x7C);
+ usSmapiID |= (CMOS_READ(0x7D) << 8);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+ PRINTK_2(TRACE_SMAPI, "smapi::smapi_init usSmapiID %x\n", usSmapiID);
+
+ if (usSmapiID == 0x5349) {
+ spin_lock_irqsave(&rtc_lock, flags);
+ g_usSmapiPort = CMOS_READ(0x7E);
+ g_usSmapiPort |= (CMOS_READ(0x7F) << 8);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+ if (g_usSmapiPort == 0) {
+ PRINTK_ERROR("smapi::smapi_init, ERROR unable to read from SMAPI port\n");
+ } else {
+ PRINTK_2(TRACE_SMAPI,
+ "smapi::smapi_init, exit TRUE g_usSmapiPort %x\n",
+ g_usSmapiPort);
+ retval = 0;
+ //SmapiQuerySystemID();
+ }
+ } else {
+ PRINTK_ERROR("smapi::smapi_init, ERROR invalid usSmapiID\n");
+ retval = -ENXIO;
+ }
+
+ return retval;
+}
diff --git a/drivers/char/mwave/smapi.h b/drivers/char/mwave/smapi.h
new file mode 100644
index 000000000000..64b2ec1420e3
--- /dev/null
+++ b/drivers/char/mwave/smapi.h
@@ -0,0 +1,80 @@
+/*
+*
+* smapi.h -- declarations for SMAPI interface routines
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#ifndef _LINUX_SMAPI_H
+#define _LINUX_SMAPI_H
+
+#define TRUE 1
+#define FALSE 0
+#define BOOLEAN int
+
+typedef struct {
+ int bDSPPresent;
+ int bDSPEnabled;
+ int bModemEnabled;
+ int bMIDIEnabled;
+ int bSblstEnabled;
+ unsigned short usDspIRQ;
+ unsigned short usDspDMA;
+ unsigned short usDspBaseIO;
+ unsigned short usUartIRQ;
+ unsigned short usUartBaseIO;
+ unsigned short usMidiIRQ;
+ unsigned short usMidiBaseIO;
+ unsigned short usSndblstIRQ;
+ unsigned short usSndblstDMA;
+ unsigned short usSndblstBaseIO;
+} SMAPI_DSP_SETTINGS;
+
+int smapi_init(void);
+int smapi_query_DSP_cfg(SMAPI_DSP_SETTINGS * pSettings);
+int smapi_set_DSP_cfg(void);
+int smapi_set_DSP_power_state(BOOLEAN bOn);
+
+
+#endif
diff --git a/drivers/char/mwave/tp3780i.c b/drivers/char/mwave/tp3780i.c
new file mode 100644
index 000000000000..ab650cd6efc0
--- /dev/null
+++ b/drivers/char/mwave/tp3780i.c
@@ -0,0 +1,592 @@
+/*
+*
+* tp3780i.c -- board driver for 3780i on ThinkPads
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "tp3780i.h"
+#include "3780i.h"
+#include "mwavepub.h"
+
+extern MWAVE_DEVICE_DATA mwave_s_mdd;
+
+static unsigned short s_ausThinkpadIrqToField[16] =
+ { 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0x0002, 0x0003, 0xFFFF, 0x0004,
+ 0xFFFF, 0xFFFF, 0x0005, 0x0006, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007 };
+static unsigned short s_ausThinkpadDmaToField[8] =
+ { 0x0001, 0x0002, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0003, 0x0004 };
+static unsigned short s_numIrqs = 16, s_numDmas = 8;
+
+
+static void EnableSRAM(THINKPAD_BD_DATA * pBDData)
+{
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ DSP_GPIO_OUTPUT_DATA_15_8 rGpioOutputData;
+ DSP_GPIO_DRIVER_ENABLE_15_8 rGpioDriverEnable;
+ DSP_GPIO_MODE_15_8 rGpioMode;
+
+ PRINTK_1(TRACE_TP3780I, "tp3780i::EnableSRAM, entry\n");
+
+ MKWORD(rGpioMode) = ReadMsaCfg(DSP_GpioModeControl_15_8);
+ rGpioMode.GpioMode10 = 0;
+ WriteMsaCfg(DSP_GpioModeControl_15_8, MKWORD(rGpioMode));
+
+ MKWORD(rGpioDriverEnable) = 0;
+ rGpioDriverEnable.Enable10 = TRUE;
+ rGpioDriverEnable.Mask10 = TRUE;
+ WriteMsaCfg(DSP_GpioDriverEnable_15_8, MKWORD(rGpioDriverEnable));
+
+ MKWORD(rGpioOutputData) = 0;
+ rGpioOutputData.Latch10 = 0;
+ rGpioOutputData.Mask10 = TRUE;
+ WriteMsaCfg(DSP_GpioOutputData_15_8, MKWORD(rGpioOutputData));
+
+ PRINTK_1(TRACE_TP3780I, "tp3780i::EnableSRAM exit\n");
+}
+
+
+static irqreturn_t UartInterrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ PRINTK_3(TRACE_TP3780I,
+ "tp3780i::UartInterrupt entry irq %x dev_id %p\n", irq, dev_id);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t DspInterrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pDrvData->rBDData.rDspSettings;
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ unsigned short usIPCSource = 0, usIsolationMask, usPCNum;
+
+ PRINTK_3(TRACE_TP3780I,
+ "tp3780i::DspInterrupt entry irq %x dev_id %p\n", irq, dev_id);
+
+ if (dsp3780I_GetIPCSource(usDspBaseIO, &usIPCSource) == 0) {
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::DspInterrupt, return from dsp3780i_GetIPCSource, usIPCSource %x\n",
+ usIPCSource);
+ usIsolationMask = 1;
+ for (usPCNum = 1; usPCNum <= 16; usPCNum++) {
+ if (usIPCSource & usIsolationMask) {
+ usIPCSource &= ~usIsolationMask;
+ PRINTK_3(TRACE_TP3780I,
+ "tp3780i::DspInterrupt usPCNum %x usIPCSource %x\n",
+ usPCNum, usIPCSource);
+ if (pDrvData->IPCs[usPCNum - 1].usIntCount == 0) {
+ pDrvData->IPCs[usPCNum - 1].usIntCount = 1;
+ }
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::DspInterrupt usIntCount %x\n",
+ pDrvData->IPCs[usPCNum - 1].usIntCount);
+ if (pDrvData->IPCs[usPCNum - 1].bIsEnabled == TRUE) {
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::DspInterrupt, waking up usPCNum %x\n",
+ usPCNum - 1);
+ wake_up_interruptible(&pDrvData->IPCs[usPCNum - 1].ipc_wait_queue);
+ } else {
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::DspInterrupt, no one waiting for IPC %x\n",
+ usPCNum - 1);
+ }
+ }
+ if (usIPCSource == 0)
+ break;
+ /* try next IPC */
+ usIsolationMask = usIsolationMask << 1;
+ }
+ } else {
+ PRINTK_1(TRACE_TP3780I,
+ "tp3780i::DspInterrupt, return false from dsp3780i_GetIPCSource\n");
+ }
+ PRINTK_1(TRACE_TP3780I, "tp3780i::DspInterrupt exit\n");
+ return IRQ_HANDLED;
+}
+
+
+int tp3780I_InitializeBoardData(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_InitializeBoardData entry pBDData %p\n", pBDData);
+
+ pBDData->bDSPEnabled = FALSE;
+ pSettings->bInterruptClaimed = FALSE;
+
+ retval = smapi_init();
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_InitializeBoardData: Error: SMAPI is not available on this machine\n");
+ } else {
+ if (mwave_3780i_irq || mwave_3780i_io || mwave_uart_irq || mwave_uart_io) {
+ retval = smapi_set_DSP_cfg();
+ }
+ }
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_InitializeBoardData exit retval %x\n", retval);
+
+ return retval;
+}
+
+int tp3780I_Cleanup(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_Cleanup entry and exit pBDData %p\n", pBDData);
+
+ return retval;
+}
+
+int tp3780I_CalcResources(THINKPAD_BD_DATA * pBDData)
+{
+ SMAPI_DSP_SETTINGS rSmapiInfo;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_CalcResources entry pBDData %p\n", pBDData);
+
+ if (smapi_query_DSP_cfg(&rSmapiInfo)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_CalcResources: Error: Could not query DSP config. Aborting.\n");
+ return -EIO;
+ }
+
+ /* Sanity check */
+ if (
+ ( rSmapiInfo.usDspIRQ == 0 )
+ || ( rSmapiInfo.usDspBaseIO == 0 )
+ || ( rSmapiInfo.usUartIRQ == 0 )
+ || ( rSmapiInfo.usUartBaseIO == 0 )
+ ) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_CalcResources: Error: Illegal resource setting. Aborting.\n");
+ return -EIO;
+ }
+
+ pSettings->bDSPEnabled = (rSmapiInfo.bDSPEnabled && rSmapiInfo.bDSPPresent);
+ pSettings->bModemEnabled = rSmapiInfo.bModemEnabled;
+ pSettings->usDspIrq = rSmapiInfo.usDspIRQ;
+ pSettings->usDspDma = rSmapiInfo.usDspDMA;
+ pSettings->usDspBaseIO = rSmapiInfo.usDspBaseIO;
+ pSettings->usUartIrq = rSmapiInfo.usUartIRQ;
+ pSettings->usUartBaseIO = rSmapiInfo.usUartBaseIO;
+
+ pSettings->uDStoreSize = TP_ABILITIES_DATA_SIZE;
+ pSettings->uIStoreSize = TP_ABILITIES_INST_SIZE;
+ pSettings->uIps = TP_ABILITIES_INTS_PER_SEC;
+
+ if (pSettings->bDSPEnabled && pSettings->bModemEnabled && pSettings->usDspIrq == pSettings->usUartIrq) {
+ pBDData->bShareDspIrq = pBDData->bShareUartIrq = 1;
+ } else {
+ pBDData->bShareDspIrq = pBDData->bShareUartIrq = 0;
+ }
+
+ PRINTK_1(TRACE_TP3780I, "tp3780i::tp3780I_CalcResources exit\n");
+
+ return 0;
+}
+
+
+int tp3780I_ClaimResources(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
+ struct resource *pres;
+#endif
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_ClaimResources entry pBDData %p\n", pBDData);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
+ pres = request_region(pSettings->usDspBaseIO, 16, "mwave_3780i");
+ if ( pres == NULL ) retval = -EIO;
+#else
+ retval = check_region(pSettings->usDspBaseIO, 16);
+ if (!retval) request_region(pSettings->usDspBaseIO, 16, "mwave_3780i");
+#endif
+ if (retval) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_ClaimResources: Error: Could not claim I/O region starting at %x\n", pSettings->usDspBaseIO);
+ retval = -EIO;
+ }
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ClaimResources exit retval %x\n", retval);
+
+ return retval;
+}
+
+int tp3780I_ReleaseResources(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_ReleaseResources entry pBDData %p\n", pBDData);
+
+ release_region(pSettings->usDspBaseIO & (~3), 16);
+
+ if (pSettings->bInterruptClaimed) {
+ free_irq(pSettings->usDspIrq, NULL);
+ pSettings->bInterruptClaimed = FALSE;
+ }
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_ReleaseResources exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+
+int tp3780I_EnableDSP(THINKPAD_BD_DATA * pBDData)
+{
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+ BOOLEAN bDSPPoweredUp = FALSE, bDSPEnabled = FALSE, bInterruptAllocated = FALSE;
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_EnableDSP entry pBDData %p\n", pBDData);
+
+ if (pBDData->bDSPEnabled) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: DSP already enabled!\n");
+ goto exit_cleanup;
+ }
+
+ if (!pSettings->bDSPEnabled) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780::tp3780I_EnableDSP: Error: pSettings->bDSPEnabled not set\n");
+ goto exit_cleanup;
+ }
+
+ if (
+ (pSettings->usDspIrq >= s_numIrqs)
+ || (pSettings->usDspDma >= s_numDmas)
+ || (s_ausThinkpadIrqToField[pSettings->usDspIrq] == 0xFFFF)
+ || (s_ausThinkpadDmaToField[pSettings->usDspDma] == 0xFFFF)
+ ) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: invalid irq %x\n", pSettings->usDspIrq);
+ goto exit_cleanup;
+ }
+
+ if (
+ ((pSettings->usDspBaseIO & 0xF00F) != 0)
+ || (pSettings->usDspBaseIO & 0x0FF0) == 0
+ ) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Invalid DSP base I/O address %x\n", pSettings->usDspBaseIO);
+ goto exit_cleanup;
+ }
+
+ if (pSettings->bModemEnabled) {
+ if (
+ pSettings->usUartIrq >= s_numIrqs
+ || s_ausThinkpadIrqToField[pSettings->usUartIrq] == 0xFFFF
+ ) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Invalid UART IRQ %x\n", pSettings->usUartIrq);
+ goto exit_cleanup;
+ }
+ switch (pSettings->usUartBaseIO) {
+ case 0x03F8:
+ case 0x02F8:
+ case 0x03E8:
+ case 0x02E8:
+ break;
+
+ default:
+ PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: Invalid UART base I/O address %x\n", pSettings->usUartBaseIO);
+ goto exit_cleanup;
+ }
+ }
+
+ pSettings->bDspIrqActiveLow = pSettings->bDspIrqPulse = TRUE;
+ pSettings->bUartIrqActiveLow = pSettings->bUartIrqPulse = TRUE;
+
+ if (pBDData->bShareDspIrq) {
+ pSettings->bDspIrqActiveLow = FALSE;
+ }
+ if (pBDData->bShareUartIrq) {
+ pSettings->bUartIrqActiveLow = FALSE;
+ }
+
+ pSettings->usNumTransfers = TP_CFG_NumTransfers;
+ pSettings->usReRequest = TP_CFG_RerequestTimer;
+ pSettings->bEnableMEMCS16 = TP_CFG_MEMCS16;
+ pSettings->usIsaMemCmdWidth = TP_CFG_IsaMemCmdWidth;
+ pSettings->bGateIOCHRDY = TP_CFG_GateIOCHRDY;
+ pSettings->bEnablePwrMgmt = TP_CFG_EnablePwrMgmt;
+ pSettings->usHBusTimerLoadValue = TP_CFG_HBusTimerValue;
+ pSettings->bDisableLBusTimeout = TP_CFG_DisableLBusTimeout;
+ pSettings->usN_Divisor = TP_CFG_N_Divisor;
+ pSettings->usM_Multiplier = TP_CFG_M_Multiplier;
+ pSettings->bPllBypass = TP_CFG_PllBypass;
+ pSettings->usChipletEnable = TP_CFG_ChipletEnable;
+
+ if (request_irq(pSettings->usUartIrq, &UartInterrupt, 0, "mwave_uart", NULL)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Could not get UART IRQ %x\n", pSettings->usUartIrq);
+ goto exit_cleanup;
+ } else { /* no conflict just release */
+ free_irq(pSettings->usUartIrq, NULL);
+ }
+
+ if (request_irq(pSettings->usDspIrq, &DspInterrupt, 0, "mwave_3780i", NULL)) {
+ PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: Could not get 3780i IRQ %x\n", pSettings->usDspIrq);
+ goto exit_cleanup;
+ } else {
+ PRINTK_3(TRACE_TP3780I,
+ "tp3780i::tp3780I_EnableDSP, got interrupt %x bShareDspIrq %x\n",
+ pSettings->usDspIrq, pBDData->bShareDspIrq);
+ bInterruptAllocated = TRUE;
+ pSettings->bInterruptClaimed = TRUE;
+ }
+
+ smapi_set_DSP_power_state(FALSE);
+ if (smapi_set_DSP_power_state(TRUE)) {
+ PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: smapi_set_DSP_power_state(TRUE) failed\n");
+ goto exit_cleanup;
+ } else {
+ bDSPPoweredUp = TRUE;
+ }
+
+ if (dsp3780I_EnableDSP(pSettings, s_ausThinkpadIrqToField, s_ausThinkpadDmaToField)) {
+ PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: dsp7880I_EnableDSP() failed\n");
+ goto exit_cleanup;
+ } else {
+ bDSPEnabled = TRUE;
+ }
+
+ EnableSRAM(pBDData);
+
+ pBDData->bDSPEnabled = TRUE;
+
+ PRINTK_1(TRACE_TP3780I, "tp3780i::tp3780I_EnableDSP exit\n");
+
+ return 0;
+
+exit_cleanup:
+ PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Cleaning up\n");
+ if (bDSPEnabled)
+ dsp3780I_DisableDSP(pSettings);
+ if (bDSPPoweredUp)
+ smapi_set_DSP_power_state(FALSE);
+ if (bInterruptAllocated) {
+ free_irq(pSettings->usDspIrq, NULL);
+ pSettings->bInterruptClaimed = FALSE;
+ }
+ return -EIO;
+}
+
+
+int tp3780I_DisableDSP(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_DisableDSP entry pBDData %p\n", pBDData);
+
+ if (pBDData->bDSPEnabled) {
+ dsp3780I_DisableDSP(&pBDData->rDspSettings);
+ if (pSettings->bInterruptClaimed) {
+ free_irq(pSettings->usDspIrq, NULL);
+ pSettings->bInterruptClaimed = FALSE;
+ }
+ smapi_set_DSP_power_state(FALSE);
+ pBDData->bDSPEnabled = FALSE;
+ }
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_DisableDSP exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+int tp3780I_ResetDSP(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ResetDSP entry pBDData %p\n",
+ pBDData);
+
+ if (dsp3780I_Reset(pSettings) == 0) {
+ EnableSRAM(pBDData);
+ } else {
+ retval = -EIO;
+ }
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ResetDSP exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+int tp3780I_StartDSP(THINKPAD_BD_DATA * pBDData)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_StartDSP entry pBDData %p\n", pBDData);
+
+ if (dsp3780I_Run(pSettings) == 0) {
+ // @BUG @TBD EnableSRAM(pBDData);
+ } else {
+ retval = -EIO;
+ }
+
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_StartDSP exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+int tp3780I_QueryAbilities(THINKPAD_BD_DATA * pBDData, MW_ABILITIES * pAbilities)
+{
+ int retval = 0;
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_QueryAbilities entry pBDData %p\n", pBDData);
+
+ /* fill out standard constant fields */
+ pAbilities->instr_per_sec = pBDData->rDspSettings.uIps;
+ pAbilities->data_size = pBDData->rDspSettings.uDStoreSize;
+ pAbilities->inst_size = pBDData->rDspSettings.uIStoreSize;
+ pAbilities->bus_dma_bw = pBDData->rDspSettings.uDmaBandwidth;
+
+ /* fill out dynamically determined fields */
+ pAbilities->component_list[0] = 0x00010000 | MW_ADC_MASK;
+ pAbilities->component_list[1] = 0x00010000 | MW_ACI_MASK;
+ pAbilities->component_list[2] = 0x00010000 | MW_AIC1_MASK;
+ pAbilities->component_list[3] = 0x00010000 | MW_AIC2_MASK;
+ pAbilities->component_list[4] = 0x00010000 | MW_CDDAC_MASK;
+ pAbilities->component_list[5] = 0x00010000 | MW_MIDI_MASK;
+ pAbilities->component_list[6] = 0x00010000 | MW_UART_MASK;
+ pAbilities->component_count = 7;
+
+ /* Fill out Mwave OS and BIOS task names */
+
+ memcpy(pAbilities->mwave_os_name, TP_ABILITIES_MWAVEOS_NAME,
+ sizeof(TP_ABILITIES_MWAVEOS_NAME));
+ memcpy(pAbilities->bios_task_name, TP_ABILITIES_BIOSTASK_NAME,
+ sizeof(TP_ABILITIES_BIOSTASK_NAME));
+
+ PRINTK_1(TRACE_TP3780I,
+ "tp3780i::tp3780I_QueryAbilities exit retval=SUCCESSFUL\n");
+
+ return retval;
+}
+
+int tp3780I_ReadWriteDspDStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+ void __user *pvBuffer, unsigned int uCount,
+ unsigned long ulDSPAddr)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ BOOLEAN bRC = 0;
+
+ PRINTK_6(TRACE_TP3780I,
+ "tp3780i::tp3780I_ReadWriteDspDStore entry pBDData %p, uOpcode %x, pvBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ pBDData, uOpcode, pvBuffer, uCount, ulDSPAddr);
+
+ if (pBDData->bDSPEnabled) {
+ switch (uOpcode) {
+ case IOCTL_MW_READ_DATA:
+ bRC = dsp3780I_ReadDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+ break;
+
+ case IOCTL_MW_READCLEAR_DATA:
+ bRC = dsp3780I_ReadAndClearDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+ break;
+
+ case IOCTL_MW_WRITE_DATA:
+ bRC = dsp3780I_WriteDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+ break;
+ }
+ }
+
+ retval = (bRC) ? -EIO : 0;
+ PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ReadWriteDspDStore exit retval %x\n", retval);
+
+ return retval;
+}
+
+
+int tp3780I_ReadWriteDspIStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+ void __user *pvBuffer, unsigned int uCount,
+ unsigned long ulDSPAddr)
+{
+ int retval = 0;
+ DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+ unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+ BOOLEAN bRC = 0;
+
+ PRINTK_6(TRACE_TP3780I,
+ "tp3780i::tp3780I_ReadWriteDspIStore entry pBDData %p, uOpcode %x, pvBuffer %p, uCount %x, ulDSPAddr %lx\n",
+ pBDData, uOpcode, pvBuffer, uCount, ulDSPAddr);
+
+ if (pBDData->bDSPEnabled) {
+ switch (uOpcode) {
+ case IOCTL_MW_READ_INST:
+ bRC = dsp3780I_ReadIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+ break;
+
+ case IOCTL_MW_WRITE_INST:
+ bRC = dsp3780I_WriteIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+ break;
+ }
+ }
+
+ retval = (bRC) ? -EIO : 0;
+
+ PRINTK_2(TRACE_TP3780I,
+ "tp3780i::tp3780I_ReadWriteDspIStore exit retval %x\n", retval);
+
+ return retval;
+}
+
diff --git a/drivers/char/mwave/tp3780i.h b/drivers/char/mwave/tp3780i.h
new file mode 100644
index 000000000000..07685b68538f
--- /dev/null
+++ b/drivers/char/mwave/tp3780i.h
@@ -0,0 +1,103 @@
+/*
+*
+* tp3780i.h -- declarations for tp3780i.c
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software
+* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*
+*
+* 10/23/2000 - Alpha Release
+* First release to the public
+*/
+
+#ifndef _LINUX_TP3780I_H
+#define _LINUX_TP3780I_H
+
+#include <asm/io.h>
+#include "mwavepub.h"
+
+
+/* DSP abilities constants for 3780i based Thinkpads */
+#define TP_ABILITIES_INTS_PER_SEC 39160800
+#define TP_ABILITIES_DATA_SIZE 32768
+#define TP_ABILITIES_INST_SIZE 32768
+#define TP_ABILITIES_MWAVEOS_NAME "mwaveos0700.dsp"
+#define TP_ABILITIES_BIOSTASK_NAME "mwbio701.dsp"
+
+
+/* DSP configuration values for 3780i based Thinkpads */
+#define TP_CFG_NumTransfers 3 /* 16 transfers */
+#define TP_CFG_RerequestTimer 1 /* 2 usec */
+#define TP_CFG_MEMCS16 0 /* Disabled, 16-bit memory assumed */
+#define TP_CFG_IsaMemCmdWidth 3 /* 295 nsec (16-bit) */
+#define TP_CFG_GateIOCHRDY 0 /* No IOCHRDY gating */
+#define TP_CFG_EnablePwrMgmt 1 /* Enable low poser suspend/resume */
+#define TP_CFG_HBusTimerValue 255 /* HBus timer load value */
+#define TP_CFG_DisableLBusTimeout 0 /* Enable LBus timeout */
+#define TP_CFG_N_Divisor 32 /* Clock = 39.1608 Mhz */
+#define TP_CFG_M_Multiplier 37 /* " */
+#define TP_CFG_PllBypass 0 /* don't bypass */
+#define TP_CFG_ChipletEnable 0xFFFF /* Enable all chiplets */
+
+typedef struct {
+ int bDSPEnabled;
+ int bShareDspIrq;
+ int bShareUartIrq;
+ DSP_3780I_CONFIG_SETTINGS rDspSettings;
+} THINKPAD_BD_DATA;
+
+int tp3780I_InitializeBoardData(THINKPAD_BD_DATA * pBDData);
+int tp3780I_CalcResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ClaimResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ReleaseResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_EnableDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_DisableDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ResetDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_StartDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_QueryAbilities(THINKPAD_BD_DATA * pBDData, MW_ABILITIES * pAbilities);
+int tp3780I_Cleanup(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ReadWriteDspDStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+ void __user *pvBuffer, unsigned int uCount,
+ unsigned long ulDSPAddr);
+int tp3780I_ReadWriteDspIStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+ void __user *pvBuffer, unsigned int uCount,
+ unsigned long ulDSPAddr);
+
+
+#endif
diff --git a/drivers/char/mxser.c b/drivers/char/mxser.c
new file mode 100644
index 000000000000..7a245068e3e5
--- /dev/null
+++ b/drivers/char/mxser.c
@@ -0,0 +1,3170 @@
+/*
+ * mxser.c -- MOXA Smartio/Industio family multiport serial driver.
+ *
+ * Copyright (C) 1999-2001 Moxa Technologies (support@moxa.com.tw).
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Original release 10/26/00
+ *
+ * 02/06/01 Support MOXA Industio family boards.
+ * 02/06/01 Support TIOCGICOUNT.
+ * 02/06/01 Fix the problem for connecting to serial mouse.
+ * 02/06/01 Fix the problem for H/W flow control.
+ * 02/06/01 Fix the compling warning when CONFIG_PCI
+ * don't be defined.
+ *
+ * Fed through a cleanup, indent and remove of non 2.6 code by Alan Cox
+ * <alan@redhat.com>. The original 1.8 code is available on www.moxa.com.
+ * - Fixed x86_64 cleanness
+ * - Fixed sleep with spinlock held in mxser_send_break
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/autoconf.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/gfp.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+
+#include "mxser.h"
+
+#define MXSER_VERSION "1.8"
+#define MXSERMAJOR 174
+#define MXSERCUMAJOR 175
+
+#define MXSER_EVENT_TXLOW 1
+#define MXSER_EVENT_HANGUP 2
+
+#define MXSER_BOARDS 4 /* Max. boards */
+#define MXSER_PORTS 32 /* Max. ports */
+#define MXSER_PORTS_PER_BOARD 8 /* Max. ports per board */
+#define MXSER_ISR_PASS_LIMIT 256
+
+#define MXSER_ERR_IOADDR -1
+#define MXSER_ERR_IRQ -2
+#define MXSER_ERR_IRQ_CONFLIT -3
+#define MXSER_ERR_VECTOR -4
+
+#define SERIAL_TYPE_NORMAL 1
+#define SERIAL_TYPE_CALLOUT 2
+
+#define WAKEUP_CHARS 256
+
+#define UART_MCR_AFE 0x20
+#define UART_LSR_SPECIAL 0x1E
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|IXON|IXOFF))
+
+#define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT)
+
+#define C168_ASIC_ID 1
+#define C104_ASIC_ID 2
+#define C102_ASIC_ID 0xB
+#define CI132_ASIC_ID 4
+#define CI134_ASIC_ID 3
+#define CI104J_ASIC_ID 5
+
+enum {
+ MXSER_BOARD_C168_ISA = 1,
+ MXSER_BOARD_C104_ISA,
+ MXSER_BOARD_CI104J,
+ MXSER_BOARD_C168_PCI,
+ MXSER_BOARD_C104_PCI,
+ MXSER_BOARD_C102_ISA,
+ MXSER_BOARD_CI132,
+ MXSER_BOARD_CI134,
+ MXSER_BOARD_CP132,
+ MXSER_BOARD_CP114,
+ MXSER_BOARD_CT114,
+ MXSER_BOARD_CP102,
+ MXSER_BOARD_CP104U,
+ MXSER_BOARD_CP168U,
+ MXSER_BOARD_CP132U,
+ MXSER_BOARD_CP134U,
+ MXSER_BOARD_CP104JU,
+ MXSER_BOARD_RC7000,
+ MXSER_BOARD_CP118U,
+ MXSER_BOARD_CP102UL,
+ MXSER_BOARD_CP102U,
+};
+
+static char *mxser_brdname[] = {
+ "C168 series",
+ "C104 series",
+ "CI-104J series",
+ "C168H/PCI series",
+ "C104H/PCI series",
+ "C102 series",
+ "CI-132 series",
+ "CI-134 series",
+ "CP-132 series",
+ "CP-114 series",
+ "CT-114 series",
+ "CP-102 series",
+ "CP-104U series",
+ "CP-168U series",
+ "CP-132U series",
+ "CP-134U series",
+ "CP-104JU series",
+ "Moxa UC7000 Serial",
+ "CP-118U series",
+ "CP-102UL series",
+ "CP-102U series",
+};
+
+static int mxser_numports[] = {
+ 8, // C168-ISA
+ 4, // C104-ISA
+ 4, // CI104J
+ 8, // C168-PCI
+ 4, // C104-PCI
+ 2, // C102-ISA
+ 2, // CI132
+ 4, // CI134
+ 2, // CP132
+ 4, // CP114
+ 4, // CT114
+ 2, // CP102
+ 4, // CP104U
+ 8, // CP168U
+ 2, // CP132U
+ 4, // CP134U
+ 4, // CP104JU
+ 8, // RC7000
+ 8, // CP118U
+ 2, // CP102UL
+ 2, // CP102U
+};
+
+#define UART_TYPE_NUM 2
+
+static const unsigned int Gmoxa_uart_id[UART_TYPE_NUM] = {
+ MOXA_MUST_MU150_HWID,
+ MOXA_MUST_MU860_HWID
+};
+
+// This is only for PCI
+#define UART_INFO_NUM 3
+struct mxpciuart_info {
+ int type;
+ int tx_fifo;
+ int rx_fifo;
+ int xmit_fifo_size;
+ int rx_high_water;
+ int rx_trigger;
+ int rx_low_water;
+ long max_baud;
+};
+
+static const struct mxpciuart_info Gpci_uart_info[UART_INFO_NUM] = {
+ {MOXA_OTHER_UART, 16, 16, 16, 14, 14, 1, 921600L},
+ {MOXA_MUST_MU150_HWID, 64, 64, 64, 48, 48, 16, 230400L},
+ {MOXA_MUST_MU860_HWID, 128, 128, 128, 96, 96, 32, 921600L}
+};
+
+
+#ifdef CONFIG_PCI
+
+static struct pci_device_id mxser_pcibrds[] = {
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C168, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_C168_PCI},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C104, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_C104_PCI},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP132, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP132},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP114, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP114},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CT114, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CT114},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP104U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP104U},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP168U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP168U},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP132U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP132U},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP134U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP134U},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP104JU, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP104JU},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_RC7000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_RC7000},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP118U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP118U},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102UL, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102UL},
+ {PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102U},
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, mxser_pcibrds);
+
+
+#endif
+
+typedef struct _moxa_pci_info {
+ unsigned short busNum;
+ unsigned short devNum;
+ struct pci_dev *pdev; // add by Victor Yu. 06-23-2003
+} moxa_pci_info;
+
+static int ioaddr[MXSER_BOARDS] = { 0, 0, 0, 0 };
+static int ttymajor = MXSERMAJOR;
+static int calloutmajor = MXSERCUMAJOR;
+static int verbose = 0;
+
+/* Variables for insmod */
+
+MODULE_AUTHOR("Casper Yang");
+MODULE_DESCRIPTION("MOXA Smartio/Industio Family Multiport Board Device Driver");
+MODULE_PARM(ioaddr, "1-4i");
+MODULE_PARM(ttymajor, "i");
+MODULE_PARM(calloutmajor, "i");
+MODULE_PARM(verbose, "i");
+MODULE_LICENSE("GPL");
+
+struct mxser_log {
+ int tick;
+ unsigned long rxcnt[MXSER_PORTS];
+ unsigned long txcnt[MXSER_PORTS];
+};
+
+
+struct mxser_mon {
+ unsigned long rxcnt;
+ unsigned long txcnt;
+ unsigned long up_rxcnt;
+ unsigned long up_txcnt;
+ int modem_status;
+ unsigned char hold_reason;
+};
+
+struct mxser_mon_ext {
+ unsigned long rx_cnt[32];
+ unsigned long tx_cnt[32];
+ unsigned long up_rxcnt[32];
+ unsigned long up_txcnt[32];
+ int modem_status[32];
+
+ long baudrate[32];
+ int databits[32];
+ int stopbits[32];
+ int parity[32];
+ int flowctrl[32];
+ int fifo[32];
+ int iftype[32];
+};
+struct mxser_hwconf {
+ int board_type;
+ int ports;
+ int irq;
+ int vector;
+ int vector_mask;
+ int uart_type;
+ int ioaddr[MXSER_PORTS_PER_BOARD];
+ int baud_base[MXSER_PORTS_PER_BOARD];
+ moxa_pci_info pciInfo;
+ int IsMoxaMustChipFlag; // add by Victor Yu. 08-30-2002
+ int MaxCanSetBaudRate[MXSER_PORTS_PER_BOARD]; // add by Victor Yu. 09-04-2002
+ int opmode_ioaddr[MXSER_PORTS_PER_BOARD]; // add by Victor Yu. 01-05-2004
+};
+
+struct mxser_struct {
+ int port;
+ int base; /* port base address */
+ int irq; /* port using irq no. */
+ int vector; /* port irq vector */
+ int vectormask; /* port vector mask */
+ int rx_high_water;
+ int rx_trigger; /* Rx fifo trigger level */
+ int rx_low_water;
+ int baud_base; /* max. speed */
+ int flags; /* defined in tty.h */
+ int type; /* UART type */
+ struct tty_struct *tty;
+ int read_status_mask;
+ int ignore_status_mask;
+ int xmit_fifo_size;
+ int custom_divisor;
+ int x_char; /* xon/xoff character */
+ int close_delay;
+ unsigned short closing_wait;
+ int IER; /* Interrupt Enable Register */
+ int MCR; /* Modem control register */
+ unsigned long event;
+ int count; /* # of fd on device */
+ int blocked_open; /* # of blocked opens */
+ long session; /* Session of opening process */
+ long pgrp; /* pgrp of opening process */
+ unsigned char *xmit_buf;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ struct work_struct tqueue;
+ struct termios normal_termios;
+ struct termios callout_termios;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ wait_queue_head_t delta_msr_wait;
+ struct async_icount icount; /* kernel counters for the 4 input interrupts */
+ int timeout;
+ int IsMoxaMustChipFlag; // add by Victor Yu. 08-30-2002
+ int MaxCanSetBaudRate; // add by Victor Yu. 09-04-2002
+ int opmode_ioaddr; // add by Victor Yu. 01-05-2004
+ unsigned char stop_rx;
+ unsigned char ldisc_stop_rx;
+ long realbaud;
+ struct mxser_mon mon_data;
+ unsigned char err_shadow;
+ spinlock_t slock;
+};
+
+
+struct mxser_mstatus {
+ tcflag_t cflag;
+ int cts;
+ int dsr;
+ int ri;
+ int dcd;
+};
+
+static struct mxser_mstatus GMStatus[MXSER_PORTS];
+
+static int mxserBoardCAP[MXSER_BOARDS] = {
+ 0, 0, 0, 0
+ /* 0x180, 0x280, 0x200, 0x320 */
+};
+
+static struct tty_driver *mxvar_sdriver;
+static struct mxser_struct mxvar_table[MXSER_PORTS];
+static struct tty_struct *mxvar_tty[MXSER_PORTS + 1];
+static struct termios *mxvar_termios[MXSER_PORTS + 1];
+static struct termios *mxvar_termios_locked[MXSER_PORTS + 1];
+static struct mxser_log mxvar_log;
+static int mxvar_diagflag;
+static unsigned char mxser_msr[MXSER_PORTS + 1];
+static struct mxser_mon_ext mon_data_ext;
+static int mxser_set_baud_method[MXSER_PORTS + 1];
+static spinlock_t gm_lock;
+
+/*
+ * This is used to figure out the divisor speeds and the timeouts
+ */
+
+static struct mxser_hwconf mxsercfg[MXSER_BOARDS];
+
+/*
+ * static functions:
+ */
+
+static void mxser_getcfg(int board, struct mxser_hwconf *hwconf);
+static int mxser_init(void);
+
+//static void mxser_poll(unsigned long);
+static int mxser_get_ISA_conf(int, struct mxser_hwconf *);
+static int mxser_get_PCI_conf(int, int, int, struct mxser_hwconf *);
+static void mxser_do_softint(void *);
+static int mxser_open(struct tty_struct *, struct file *);
+static void mxser_close(struct tty_struct *, struct file *);
+static int mxser_write(struct tty_struct *, const unsigned char *, int);
+static int mxser_write_room(struct tty_struct *);
+static void mxser_flush_buffer(struct tty_struct *);
+static int mxser_chars_in_buffer(struct tty_struct *);
+static void mxser_flush_chars(struct tty_struct *);
+static void mxser_put_char(struct tty_struct *, unsigned char);
+static int mxser_ioctl(struct tty_struct *, struct file *, uint, ulong);
+static int mxser_ioctl_special(unsigned int, void __user *);
+static void mxser_throttle(struct tty_struct *);
+static void mxser_unthrottle(struct tty_struct *);
+static void mxser_set_termios(struct tty_struct *, struct termios *);
+static void mxser_stop(struct tty_struct *);
+static void mxser_start(struct tty_struct *);
+static void mxser_hangup(struct tty_struct *);
+static void mxser_rs_break(struct tty_struct *, int);
+static irqreturn_t mxser_interrupt(int, void *, struct pt_regs *);
+static void mxser_receive_chars(struct mxser_struct *, int *);
+static void mxser_transmit_chars(struct mxser_struct *);
+static void mxser_check_modem_status(struct mxser_struct *, int);
+static int mxser_block_til_ready(struct tty_struct *, struct file *, struct mxser_struct *);
+static int mxser_startup(struct mxser_struct *);
+static void mxser_shutdown(struct mxser_struct *);
+static int mxser_change_speed(struct mxser_struct *, struct termios *old_termios);
+static int mxser_get_serial_info(struct mxser_struct *, struct serial_struct __user *);
+static int mxser_set_serial_info(struct mxser_struct *, struct serial_struct __user *);
+static int mxser_get_lsr_info(struct mxser_struct *, unsigned int __user *);
+static void mxser_send_break(struct mxser_struct *, int);
+static int mxser_tiocmget(struct tty_struct *, struct file *);
+static int mxser_tiocmset(struct tty_struct *, struct file *, unsigned int, unsigned int);
+static int mxser_set_baud(struct mxser_struct *info, long newspd);
+static void mxser_wait_until_sent(struct tty_struct *tty, int timeout);
+
+static void mxser_startrx(struct tty_struct *tty);
+static void mxser_stoprx(struct tty_struct *tty);
+
+
+static int CheckIsMoxaMust(int io)
+{
+ u8 oldmcr, hwid;
+ int i;
+
+ outb(0, io + UART_LCR);
+ DISABLE_MOXA_MUST_ENCHANCE_MODE(io);
+ oldmcr = inb(io + UART_MCR);
+ outb(0, io + UART_MCR);
+ SET_MOXA_MUST_XON1_VALUE(io, 0x11);
+ if ((hwid = inb(io + UART_MCR)) != 0) {
+ outb(oldmcr, io + UART_MCR);
+ return (MOXA_OTHER_UART);
+ }
+
+ GET_MOXA_MUST_HARDWARE_ID(io, &hwid);
+ for (i = 0; i < UART_TYPE_NUM; i++) {
+ if (hwid == Gmoxa_uart_id[i])
+ return (int) hwid;
+ }
+ return MOXA_OTHER_UART;
+}
+
+// above is modified by Victor Yu. 08-15-2002
+
+static struct tty_operations mxser_ops = {
+ .open = mxser_open,
+ .close = mxser_close,
+ .write = mxser_write,
+ .put_char = mxser_put_char,
+ .flush_chars = mxser_flush_chars,
+ .write_room = mxser_write_room,
+ .chars_in_buffer = mxser_chars_in_buffer,
+ .flush_buffer = mxser_flush_buffer,
+ .ioctl = mxser_ioctl,
+ .throttle = mxser_throttle,
+ .unthrottle = mxser_unthrottle,
+ .set_termios = mxser_set_termios,
+ .stop = mxser_stop,
+ .start = mxser_start,
+ .hangup = mxser_hangup,
+ .tiocmget = mxser_tiocmget,
+ .tiocmset = mxser_tiocmset,
+};
+
+/*
+ * The MOXA Smartio/Industio serial driver boot-time initialization code!
+ */
+
+static int __init mxser_module_init(void)
+{
+ int ret;
+
+ if (verbose)
+ printk(KERN_DEBUG "Loading module mxser ...\n");
+ ret = mxser_init();
+ if (verbose)
+ printk(KERN_DEBUG "Done.\n");
+ return ret;
+}
+
+static void __exit mxser_module_exit(void)
+{
+ int i, err = 0;
+
+ if (verbose)
+ printk(KERN_DEBUG "Unloading module mxser ...\n");
+
+ if ((err |= tty_unregister_driver(mxvar_sdriver)))
+ printk(KERN_ERR "Couldn't unregister MOXA Smartio/Industio family serial driver\n");
+
+ for (i = 0; i < MXSER_BOARDS; i++) {
+ struct pci_dev *pdev;
+
+ if (mxsercfg[i].board_type == -1)
+ continue;
+ else {
+ pdev = mxsercfg[i].pciInfo.pdev;
+ free_irq(mxsercfg[i].irq, &mxvar_table[i * MXSER_PORTS_PER_BOARD]);
+ if (pdev != NULL) { //PCI
+ release_region(pci_resource_start(pdev, 2), pci_resource_len(pdev, 2));
+ release_region(pci_resource_start(pdev, 3), pci_resource_len(pdev, 3));
+ } else {
+ release_region(mxsercfg[i].ioaddr[0], 8 * mxsercfg[i].ports);
+ release_region(mxsercfg[i].vector, 1);
+ }
+ }
+ }
+ if (verbose)
+ printk(KERN_DEBUG "Done.\n");
+
+}
+
+static void process_txrx_fifo(struct mxser_struct *info)
+{
+ int i;
+
+ if ((info->type == PORT_16450) || (info->type == PORT_8250)) {
+ info->rx_trigger = 1;
+ info->rx_high_water = 1;
+ info->rx_low_water = 1;
+ info->xmit_fifo_size = 1;
+ } else {
+ for (i = 0; i < UART_INFO_NUM; i++) {
+ if (info->IsMoxaMustChipFlag == Gpci_uart_info[i].type) {
+ info->rx_trigger = Gpci_uart_info[i].rx_trigger;
+ info->rx_low_water = Gpci_uart_info[i].rx_low_water;
+ info->rx_high_water = Gpci_uart_info[i].rx_high_water;
+ info->xmit_fifo_size = Gpci_uart_info[i].xmit_fifo_size;
+ break;
+ }
+ }
+ }
+}
+
+static int mxser_initbrd(int board, struct mxser_hwconf *hwconf)
+{
+ struct mxser_struct *info;
+ int retval;
+ int i, n;
+
+ n = board * MXSER_PORTS_PER_BOARD;
+ info = &mxvar_table[n];
+ /*if (verbose) */ {
+ printk(KERN_DEBUG " ttyM%d - ttyM%d ", n, n + hwconf->ports - 1);
+ printk(" max. baud rate = %d bps.\n", hwconf->MaxCanSetBaudRate[0]);
+ }
+
+ for (i = 0; i < hwconf->ports; i++, n++, info++) {
+ info->port = n;
+ info->base = hwconf->ioaddr[i];
+ info->irq = hwconf->irq;
+ info->vector = hwconf->vector;
+ info->vectormask = hwconf->vector_mask;
+ info->opmode_ioaddr = hwconf->opmode_ioaddr[i]; // add by Victor Yu. 01-05-2004
+ info->stop_rx = 0;
+ info->ldisc_stop_rx = 0;
+
+ info->IsMoxaMustChipFlag = hwconf->IsMoxaMustChipFlag;
+ //Enhance mode enabled here
+ if (info->IsMoxaMustChipFlag != MOXA_OTHER_UART) {
+ ENABLE_MOXA_MUST_ENCHANCE_MODE(info->base);
+ }
+
+ info->flags = ASYNC_SHARE_IRQ;
+ info->type = hwconf->uart_type;
+ info->baud_base = hwconf->baud_base[i];
+
+ info->MaxCanSetBaudRate = hwconf->MaxCanSetBaudRate[i];
+
+ process_txrx_fifo(info);
+
+
+ info->custom_divisor = hwconf->baud_base[i] * 16;
+ info->close_delay = 5 * HZ / 10;
+ info->closing_wait = 30 * HZ;
+ INIT_WORK(&info->tqueue, mxser_do_softint, info);
+ info->normal_termios = mxvar_sdriver->init_termios;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->delta_msr_wait);
+ memset(&info->mon_data, 0, sizeof(struct mxser_mon));
+ info->err_shadow = 0;
+ spin_lock_init(&info->slock);
+ }
+ /*
+ * Allocate the IRQ if necessary
+ */
+
+
+ /* before set INT ISR, disable all int */
+ for (i = 0; i < hwconf->ports; i++) {
+ outb(inb(hwconf->ioaddr[i] + UART_IER) & 0xf0, hwconf->ioaddr[i] + UART_IER);
+ }
+
+ n = board * MXSER_PORTS_PER_BOARD;
+ info = &mxvar_table[n];
+
+ retval = request_irq(hwconf->irq, mxser_interrupt, IRQ_T(info), "mxser", info);
+ if (retval) {
+ printk(KERN_ERR "Board %d: %s", board, mxser_brdname[hwconf->board_type - 1]);
+ printk(" Request irq fail,IRQ (%d) may be conflit with another device.\n", info->irq);
+ return retval;
+ }
+ return 0;
+}
+
+
+static void mxser_getcfg(int board, struct mxser_hwconf *hwconf)
+{
+ mxsercfg[board] = *hwconf;
+}
+
+#ifdef CONFIG_PCI
+static int mxser_get_PCI_conf(int busnum, int devnum, int board_type, struct mxser_hwconf *hwconf)
+{
+ int i, j;
+// unsigned int val;
+ unsigned int ioaddress;
+ struct pci_dev *pdev = hwconf->pciInfo.pdev;
+
+ //io address
+ hwconf->board_type = board_type;
+ hwconf->ports = mxser_numports[board_type - 1];
+ ioaddress = pci_resource_start(pdev, 2);
+ request_region(pci_resource_start(pdev, 2), pci_resource_len(pdev, 2), "mxser(IO)");
+
+ for (i = 0; i < hwconf->ports; i++) {
+ hwconf->ioaddr[i] = ioaddress + 8 * i;
+ }
+
+ //vector
+ ioaddress = pci_resource_start(pdev, 3);
+ request_region(pci_resource_start(pdev, 3), pci_resource_len(pdev, 3), "mxser(vector)");
+ hwconf->vector = ioaddress;
+
+ //irq
+ hwconf->irq = hwconf->pciInfo.pdev->irq;
+
+ hwconf->IsMoxaMustChipFlag = CheckIsMoxaMust(hwconf->ioaddr[0]);
+ hwconf->uart_type = PORT_16550A;
+ hwconf->vector_mask = 0;
+
+
+ for (i = 0; i < hwconf->ports; i++) {
+ for (j = 0; j < UART_INFO_NUM; j++) {
+ if (Gpci_uart_info[j].type == hwconf->IsMoxaMustChipFlag) {
+ hwconf->MaxCanSetBaudRate[i] = Gpci_uart_info[j].max_baud;
+
+ //exception....CP-102
+ if (board_type == MXSER_BOARD_CP102)
+ hwconf->MaxCanSetBaudRate[i] = 921600;
+ break;
+ }
+ }
+ }
+
+ if (hwconf->IsMoxaMustChipFlag == MOXA_MUST_MU860_HWID) {
+ for (i = 0; i < hwconf->ports; i++) {
+ if (i < 4)
+ hwconf->opmode_ioaddr[i] = ioaddress + 4;
+ else
+ hwconf->opmode_ioaddr[i] = ioaddress + 0x0c;
+ }
+ outb(0, ioaddress + 4); // default set to RS232 mode
+ outb(0, ioaddress + 0x0c); //default set to RS232 mode
+ }
+
+ for (i = 0; i < hwconf->ports; i++) {
+ hwconf->vector_mask |= (1 << i);
+ hwconf->baud_base[i] = 921600;
+ }
+ return (0);
+}
+#endif
+
+static int mxser_init(void)
+{
+ int i, m, retval, b, n;
+ int ret1;
+ struct pci_dev *pdev = NULL;
+ int index;
+ unsigned char busnum, devnum;
+ struct mxser_hwconf hwconf;
+
+ mxvar_sdriver = alloc_tty_driver(MXSER_PORTS + 1);
+ if (!mxvar_sdriver)
+ return -ENOMEM;
+ spin_lock_init(&gm_lock);
+
+ for (i = 0; i < MXSER_BOARDS; i++) {
+ mxsercfg[i].board_type = -1;
+ }
+
+ printk(KERN_INFO "MOXA Smartio/Industio family driver version %s\n", MXSER_VERSION);
+
+ /* Initialize the tty_driver structure */
+ memset(mxvar_sdriver, 0, sizeof(struct tty_driver));
+ mxvar_sdriver->magic = TTY_DRIVER_MAGIC;
+ mxvar_sdriver->name = "ttyM";
+ mxvar_sdriver->major = ttymajor;
+ mxvar_sdriver->minor_start = 0;
+ mxvar_sdriver->num = MXSER_PORTS + 1;
+ mxvar_sdriver->type = TTY_DRIVER_TYPE_SERIAL;
+ mxvar_sdriver->subtype = SERIAL_TYPE_NORMAL;
+ mxvar_sdriver->init_termios = tty_std_termios;
+ mxvar_sdriver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ mxvar_sdriver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(mxvar_sdriver, &mxser_ops);
+ mxvar_sdriver->ttys = mxvar_tty;
+ mxvar_sdriver->termios = mxvar_termios;
+ mxvar_sdriver->termios_locked = mxvar_termios_locked;
+
+ mxvar_sdriver->open = mxser_open;
+ mxvar_sdriver->close = mxser_close;
+ mxvar_sdriver->write = mxser_write;
+ mxvar_sdriver->put_char = mxser_put_char;
+ mxvar_sdriver->flush_chars = mxser_flush_chars;
+ mxvar_sdriver->write_room = mxser_write_room;
+ mxvar_sdriver->chars_in_buffer = mxser_chars_in_buffer;
+ mxvar_sdriver->flush_buffer = mxser_flush_buffer;
+ mxvar_sdriver->ioctl = mxser_ioctl;
+ mxvar_sdriver->throttle = mxser_throttle;
+ mxvar_sdriver->unthrottle = mxser_unthrottle;
+ mxvar_sdriver->set_termios = mxser_set_termios;
+ mxvar_sdriver->stop = mxser_stop;
+ mxvar_sdriver->start = mxser_start;
+ mxvar_sdriver->hangup = mxser_hangup;
+ mxvar_sdriver->break_ctl = mxser_rs_break;
+ mxvar_sdriver->wait_until_sent = mxser_wait_until_sent;
+
+ mxvar_diagflag = 0;
+ memset(mxvar_table, 0, MXSER_PORTS * sizeof(struct mxser_struct));
+ memset(&mxvar_log, 0, sizeof(struct mxser_log));
+
+ memset(&mxser_msr, 0, sizeof(unsigned char) * (MXSER_PORTS + 1));
+ memset(&mon_data_ext, 0, sizeof(struct mxser_mon_ext));
+ memset(&mxser_set_baud_method, 0, sizeof(int) * (MXSER_PORTS + 1));
+ memset(&hwconf, 0, sizeof(struct mxser_hwconf));
+
+ m = 0;
+ /* Start finding ISA boards here */
+ for (b = 0; b < MXSER_BOARDS && m < MXSER_BOARDS; b++) {
+ int cap;
+ if (!(cap = mxserBoardCAP[b]))
+ continue;
+
+ retval = mxser_get_ISA_conf(cap, &hwconf);
+
+ if (retval != 0)
+ printk(KERN_INFO "Found MOXA %s board (CAP=0x%x)\n", mxser_brdname[hwconf.board_type - 1], ioaddr[b]);
+
+ if (retval <= 0) {
+ if (retval == MXSER_ERR_IRQ)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_IRQ_CONFLIT)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_VECTOR)
+ printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+ else if (retval == MXSER_ERR_IOADDR)
+ printk(KERN_ERR "Invalid I/O address,board not configured\n");
+
+ continue;
+ }
+
+ hwconf.pciInfo.busNum = 0;
+ hwconf.pciInfo.devNum = 0;
+ hwconf.pciInfo.pdev = NULL;
+
+ mxser_getcfg(m, &hwconf);
+ //init mxsercfg first, or mxsercfg data is not correct on ISR.
+ //mxser_initbrd will hook ISR.
+ if (mxser_initbrd(m, &hwconf) < 0)
+ continue;
+
+
+ m++;
+ }
+
+ /* Start finding ISA boards from module arg */
+ for (b = 0; b < MXSER_BOARDS && m < MXSER_BOARDS; b++) {
+ int cap;
+ if (!(cap = ioaddr[b]))
+ continue;
+
+ retval = mxser_get_ISA_conf(cap, &hwconf);
+
+ if (retval != 0)
+ printk(KERN_INFO "Found MOXA %s board (CAP=0x%x)\n", mxser_brdname[hwconf.board_type - 1], ioaddr[b]);
+
+ if (retval <= 0) {
+ if (retval == MXSER_ERR_IRQ)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_IRQ_CONFLIT)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_VECTOR)
+ printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+ else if (retval == MXSER_ERR_IOADDR)
+ printk(KERN_ERR "Invalid I/O address,board not configured\n");
+
+ continue;
+ }
+
+ hwconf.pciInfo.busNum = 0;
+ hwconf.pciInfo.devNum = 0;
+ hwconf.pciInfo.pdev = NULL;
+
+ mxser_getcfg(m, &hwconf);
+ //init mxsercfg first, or mxsercfg data is not correct on ISR.
+ //mxser_initbrd will hook ISR.
+ if (mxser_initbrd(m, &hwconf) < 0)
+ continue;
+
+ m++;
+ }
+
+ /* start finding PCI board here */
+#ifdef CONFIG_PCI
+ n = (sizeof(mxser_pcibrds) / sizeof(mxser_pcibrds[0])) - 1;
+ index = 0;
+ b = 0;
+ while (b < n) {
+ pdev = pci_find_device(mxser_pcibrds[b].vendor, mxser_pcibrds[b].device, pdev);
+ if (pdev == NULL) {
+ b++;
+ continue;
+ }
+ hwconf.pciInfo.busNum = busnum = pdev->bus->number;
+ hwconf.pciInfo.devNum = devnum = PCI_SLOT(pdev->devfn) << 3;
+ hwconf.pciInfo.pdev = pdev;
+ printk(KERN_INFO "Found MOXA %s board(BusNo=%d,DevNo=%d)\n", mxser_brdname[(int) (mxser_pcibrds[b].driver_data) - 1], busnum, devnum >> 3);
+ index++;
+ if (m >= MXSER_BOARDS) {
+ printk(KERN_ERR "Too many Smartio/Industio family boards find (maximum %d),board not configured\n", MXSER_BOARDS);
+ } else {
+ if (pci_enable_device(pdev)) {
+ printk(KERN_ERR "Moxa SmartI/O PCI enable fail !\n");
+ continue;
+ }
+ retval = mxser_get_PCI_conf(busnum, devnum, (int) mxser_pcibrds[b].driver_data, &hwconf);
+ if (retval < 0) {
+ if (retval == MXSER_ERR_IRQ)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_IRQ_CONFLIT)
+ printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+ else if (retval == MXSER_ERR_VECTOR)
+ printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+ else if (retval == MXSER_ERR_IOADDR)
+ printk(KERN_ERR "Invalid I/O address,board not configured\n");
+ continue;
+ }
+ mxser_getcfg(m, &hwconf);
+ //init mxsercfg first, or mxsercfg data is not correct on ISR.
+ //mxser_initbrd will hook ISR.
+ if (mxser_initbrd(m, &hwconf) < 0)
+ continue;
+ m++;
+ }
+ }
+#endif
+
+ ret1 = 0;
+ if (!(ret1 = tty_register_driver(mxvar_sdriver))) {
+ return 0;
+ } else
+ printk(KERN_ERR "Couldn't install MOXA Smartio/Industio family driver !\n");
+
+
+ if (ret1) {
+ for (i = 0; i < MXSER_BOARDS; i++) {
+ if (mxsercfg[i].board_type == -1)
+ continue;
+ else {
+ free_irq(mxsercfg[i].irq, &mxvar_table[i * MXSER_PORTS_PER_BOARD]);
+ //todo: release io, vector
+ }
+ }
+ return -1;
+ }
+
+ return (0);
+}
+
+static void mxser_do_softint(void *private_)
+{
+ struct mxser_struct *info = (struct mxser_struct *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+
+ if (tty) {
+ if (test_and_clear_bit(MXSER_EVENT_TXLOW, &info->event))
+ tty_wakeup(tty);
+ if (test_and_clear_bit(MXSER_EVENT_HANGUP, &info->event))
+ tty_hangup(tty);
+ }
+}
+
+static unsigned char mxser_get_msr(int baseaddr, int mode, int port, struct mxser_struct *info)
+{
+ unsigned char status = 0;
+
+ status = inb(baseaddr + UART_MSR);
+
+ mxser_msr[port] &= 0x0F;
+ mxser_msr[port] |= status;
+ status = mxser_msr[port];
+ if (mode)
+ mxser_msr[port] = 0;
+
+ return status;
+}
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain. It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int mxser_open(struct tty_struct *tty, struct file *filp)
+{
+ struct mxser_struct *info;
+ int retval, line;
+
+ line = tty->index;
+ if (line == MXSER_PORTS)
+ return 0;
+ if (line < 0 || line > MXSER_PORTS)
+ return -ENODEV;
+ info = mxvar_table + line;
+ if (!info->base)
+ return (-ENODEV);
+
+ tty->driver_data = info;
+ info->tty = tty;
+ /*
+ * Start up serial port
+ */
+ retval = mxser_startup(info);
+ if (retval)
+ return (retval);
+
+ retval = mxser_block_til_ready(tty, filp, info);
+ if (retval)
+ return (retval);
+
+ info->count++;
+
+ if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
+ if (tty->driver->subtype == SERIAL_TYPE_NORMAL)
+ *tty->termios = info->normal_termios;
+ else
+ *tty->termios = info->callout_termios;
+ mxser_change_speed(info, NULL);
+ }
+
+ info->session = current->signal->session;
+ info->pgrp = process_group(current);
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+
+ //status = mxser_get_msr(info->base, 0, info->port);
+ //mxser_check_modem_status(info, status);
+
+/* unmark here for very high baud rate (ex. 921600 bps) used
+*/
+ tty->low_latency = 1;
+ return 0;
+}
+
+/*
+ * This routine is called when the serial port gets closed. First, we
+ * wait for the last remaining data to be sent. Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ */
+static void mxser_close(struct tty_struct *tty, struct file *filp)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+
+ unsigned long timeout;
+ unsigned long flags;
+ struct tty_ldisc *ld;
+
+ if (tty->index == MXSER_PORTS)
+ return;
+ if (!info)
+ BUG();
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ if (tty_hung_up_p(filp)) {
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk(KERN_ERR "mxser_close: bad serial port count; tty->count is 1, " "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+ if (--info->count < 0) {
+ printk(KERN_ERR "mxser_close: bad serial port count for ttys%d: %d\n", info->port, info->count);
+ info->count = 0;
+ }
+ if (info->count) {
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+ info->flags |= ASYNC_CLOSING;
+ spin_unlock_irqrestore(&info->slock, flags);
+ /*
+ * Save the termios structure, since this port may have
+ * separate termios for callout and dialin.
+ */
+ if (info->flags & ASYNC_NORMAL_ACTIVE)
+ info->normal_termios = *tty->termios;
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, info->closing_wait);
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ info->IER &= ~UART_IER_RLSI;
+ if (info->IsMoxaMustChipFlag)
+ info->IER &= ~MOXA_MUST_RECV_ISR;
+/* by William
+ info->read_status_mask &= ~UART_LSR_DR;
+*/
+ if (info->flags & ASYNC_INITIALIZED) {
+ outb(info->IER, info->base + UART_IER);
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = jiffies + HZ;
+ while (!(inb(info->base + UART_LSR) & UART_LSR_TEMT)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(5);
+ if (time_after(jiffies, timeout))
+ break;
+ }
+ }
+ mxser_shutdown(info);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if(ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+
+ tty->closing = 0;
+ info->event = 0;
+ info->tty = NULL;
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(info->close_delay);
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+
+}
+
+static int mxser_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ int c, total = 0;
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ if (!tty || !info->xmit_buf)
+ return (0);
+
+ while (1) {
+ c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head));
+ if (c <= 0)
+ break;
+
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1);
+ info->xmit_cnt += c;
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+
+ }
+
+ if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
+ if (!tty->hw_stopped || (info->type == PORT_16550A) || (info->IsMoxaMustChipFlag)) {
+ spin_lock_irqsave(&info->slock, flags);
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+ }
+ return total;
+}
+
+static void mxser_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ if (!tty || !info->xmit_buf)
+ return;
+
+ if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
+ return;
+
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ info->xmit_cnt++;
+ spin_unlock_irqrestore(&info->slock, flags);
+ if (!tty->stopped && !(info->IER & UART_IER_THRI)) {
+ if (!tty->hw_stopped || (info->type == PORT_16550A) || info->IsMoxaMustChipFlag) {
+ spin_lock_irqsave(&info->slock, flags);
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+ }
+}
+
+
+static void mxser_flush_chars(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf || (tty->hw_stopped && (info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)))
+ return;
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static int mxser_write_room(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ int ret;
+
+ ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ return (ret);
+}
+
+static int mxser_chars_in_buffer(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ return info->xmit_cnt;
+}
+
+static void mxser_flush_buffer(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ char fcr;
+ unsigned long flags;
+
+
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+ /* below added by shinhay */
+ fcr = inb(info->base + UART_FCR);
+ outb((fcr | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+ outb(fcr, info->base + UART_FCR);
+
+ spin_unlock_irqrestore(&info->slock, flags);
+ /* above added by shinhay */
+
+ wake_up_interruptible(&tty->write_wait);
+ if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
+ (tty->ldisc.write_wakeup) (tty);
+}
+
+static int mxser_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ int retval;
+ struct async_icount cprev, cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser;
+ unsigned long templ;
+ unsigned long flags;
+ void __user *argp = (void __user *)arg;
+
+ if (tty->index == MXSER_PORTS)
+ return (mxser_ioctl_special(cmd, argp));
+
+ // following add by Victor Yu. 01-05-2004
+ if (cmd == MOXA_SET_OP_MODE || cmd == MOXA_GET_OP_MODE) {
+ int opmode, p;
+ static unsigned char ModeMask[] = { 0xfc, 0xf3, 0xcf, 0x3f };
+ int shiftbit;
+ unsigned char val, mask;
+
+ p = info->port % 4;
+ if (cmd == MOXA_SET_OP_MODE) {
+ if (get_user(opmode, (int __user *) argp))
+ return -EFAULT;
+ if (opmode != RS232_MODE && opmode != RS485_2WIRE_MODE && opmode != RS422_MODE && opmode != RS485_4WIRE_MODE)
+ return -EFAULT;
+ mask = ModeMask[p];
+ shiftbit = p * 2;
+ val = inb(info->opmode_ioaddr);
+ val &= mask;
+ val |= (opmode << shiftbit);
+ outb(val, info->opmode_ioaddr);
+ } else {
+ shiftbit = p * 2;
+ opmode = inb(info->opmode_ioaddr) >> shiftbit;
+ opmode &= OP_MODE_MASK;
+ if (copy_to_user(argp, &opmode, sizeof(int)))
+ return -EFAULT;
+ }
+ return 0;
+ }
+ // above add by Victor Yu. 01-05-2004
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return (-EIO);
+ }
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ retval = tty_check_change(tty);
+ if (retval)
+ return (retval);
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ mxser_send_break(info, HZ / 4); /* 1/4 second */
+ return (0);
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ retval = tty_check_change(tty);
+ if (retval)
+ return (retval);
+ tty_wait_until_sent(tty, 0);
+ mxser_send_break(info, arg ? arg * (HZ / 10) : HZ / 4);
+ return (0);
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) argp);
+ case TIOCSSOFTCAR:
+ if (get_user(templ, (unsigned long __user *) argp))
+ return -EFAULT;
+ arg = templ;
+ tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+ return (0);
+ case TIOCGSERIAL:
+ return mxser_get_serial_info(info, argp);
+ case TIOCSSERIAL:
+ return mxser_set_serial_info(info, argp);
+ case TIOCSERGETLSR: /* Get line status register */
+ return mxser_get_lsr_info(info, argp);
+ /*
+ * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+ * - mask passed in arg for lines of interest
+ * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+ * Caller should use TIOCGICOUNT to see which one it was
+ */
+ case TIOCMIWAIT:{
+ DECLARE_WAITQUEUE(wait, current);
+ int ret;
+ spin_lock_irqsave(&info->slock, flags);
+ cprev = info->icount; /* note the counters on entry */
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ add_wait_queue(&info->delta_msr_wait, &wait);
+ while (1) {
+ spin_lock_irqsave(&info->slock, flags);
+ cnow = info->icount; /* atomic copy */
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
+ ret = 0;
+ break;
+ }
+ /* see if a signal did it */
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+ cprev = cnow;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->delta_msr_wait, &wait);
+ break;
+ }
+ /* NOTREACHED */
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ spin_lock_irqsave(&info->slock, flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->slock, flags);
+ p_cuser = argp;
+ /* modified by casper 1/11/2000 */
+ if (put_user(cnow.frame, &p_cuser->frame))
+ return -EFAULT;
+ if (put_user(cnow.brk, &p_cuser->brk))
+ return -EFAULT;
+ if (put_user(cnow.overrun, &p_cuser->overrun))
+ return -EFAULT;
+ if (put_user(cnow.buf_overrun, &p_cuser->buf_overrun))
+ return -EFAULT;
+ if (put_user(cnow.parity, &p_cuser->parity))
+ return -EFAULT;
+ if (put_user(cnow.rx, &p_cuser->rx))
+ return -EFAULT;
+ if (put_user(cnow.tx, &p_cuser->tx))
+ return -EFAULT;
+ put_user(cnow.cts, &p_cuser->cts);
+ put_user(cnow.dsr, &p_cuser->dsr);
+ put_user(cnow.rng, &p_cuser->rng);
+ put_user(cnow.dcd, &p_cuser->dcd);
+
+/* */
+ return 0;
+ case MOXA_HighSpeedOn:
+ return put_user(info->baud_base != 115200 ? 1 : 0, (int __user *) argp);
+
+ case MOXA_SDS_RSTICOUNTER:{
+ info->mon_data.rxcnt = 0;
+ info->mon_data.txcnt = 0;
+ return 0;
+ }
+// (above) added by James.
+ case MOXA_ASPP_SETBAUD:{
+ long baud;
+ if (get_user(baud, (long __user *) argp))
+ return -EFAULT;
+ mxser_set_baud(info, baud);
+ return 0;
+ }
+ case MOXA_ASPP_GETBAUD:
+ if (copy_to_user(argp, &info->realbaud, sizeof(long)))
+ return -EFAULT;
+
+ return 0;
+
+ case MOXA_ASPP_OQUEUE:{
+ int len, lsr;
+
+ len = mxser_chars_in_buffer(tty);
+
+ lsr = inb(info->base + UART_LSR) & UART_LSR_TEMT;
+
+ len += (lsr ? 0 : 1);
+
+ if (copy_to_user(argp, &len, sizeof(int)))
+ return -EFAULT;
+
+ return 0;
+ }
+ case MOXA_ASPP_MON:{
+ int mcr, status;
+// info->mon_data.ser_param = tty->termios->c_cflag;
+
+ status = mxser_get_msr(info->base, 1, info->port, info);
+ mxser_check_modem_status(info, status);
+
+ mcr = inb(info->base + UART_MCR);
+ if (mcr & MOXA_MUST_MCR_XON_FLAG)
+ info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFHOLD;
+ else
+ info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFHOLD;
+
+ if (mcr & MOXA_MUST_MCR_TX_XON)
+ info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFXENT;
+ else
+ info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFXENT;
+
+ if (info->tty->hw_stopped)
+ info->mon_data.hold_reason |= NPPI_NOTIFY_CTSHOLD;
+ else
+ info->mon_data.hold_reason &= ~NPPI_NOTIFY_CTSHOLD;
+
+
+ if (copy_to_user(argp, &info->mon_data, sizeof(struct mxser_mon)))
+ return -EFAULT;
+
+ return 0;
+
+ }
+
+ case MOXA_ASPP_LSTATUS:{
+ if (copy_to_user(argp, &info->err_shadow, sizeof(unsigned char)))
+ return -EFAULT;
+
+ info->err_shadow = 0;
+ return 0;
+
+ }
+ case MOXA_SET_BAUD_METHOD:{
+ int method;
+ if (get_user(method, (int __user *) argp))
+ return -EFAULT;
+ mxser_set_baud_method[info->port] = method;
+ if (copy_to_user(argp, &method, sizeof(int)))
+ return -EFAULT;
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+#ifndef CMSPAR
+#define CMSPAR 010000000000
+#endif
+
+static int mxser_ioctl_special(unsigned int cmd, void __user *argp)
+{
+ int i, result, status;
+
+ switch (cmd) {
+ case MOXA_GET_CONF:
+ if (copy_to_user(argp, mxsercfg, sizeof(struct mxser_hwconf) * 4))
+ return -EFAULT;
+ return 0;
+ case MOXA_GET_MAJOR:
+ if (copy_to_user(argp, &ttymajor, sizeof(int)))
+ return -EFAULT;
+ return 0;
+
+ case MOXA_GET_CUMAJOR:
+ if (copy_to_user(argp, &calloutmajor, sizeof(int)))
+ return -EFAULT;
+ return 0;
+
+ case MOXA_CHKPORTENABLE:
+ result = 0;
+ for (i = 0; i < MXSER_PORTS; i++) {
+ if (mxvar_table[i].base)
+ result |= (1 << i);
+ }
+ return put_user(result, (unsigned long __user *) argp);
+ case MOXA_GETDATACOUNT:
+ if (copy_to_user(argp, &mxvar_log, sizeof(mxvar_log)))
+ return -EFAULT;
+ return (0);
+ case MOXA_GETMSTATUS:
+ for (i = 0; i < MXSER_PORTS; i++) {
+ GMStatus[i].ri = 0;
+ if (!mxvar_table[i].base) {
+ GMStatus[i].dcd = 0;
+ GMStatus[i].dsr = 0;
+ GMStatus[i].cts = 0;
+ continue;
+ }
+
+ if (!mxvar_table[i].tty || !mxvar_table[i].tty->termios)
+ GMStatus[i].cflag = mxvar_table[i].normal_termios.c_cflag;
+ else
+ GMStatus[i].cflag = mxvar_table[i].tty->termios->c_cflag;
+
+ status = inb(mxvar_table[i].base + UART_MSR);
+ if (status & 0x80 /*UART_MSR_DCD */ )
+ GMStatus[i].dcd = 1;
+ else
+ GMStatus[i].dcd = 0;
+
+ if (status & 0x20 /*UART_MSR_DSR */ )
+ GMStatus[i].dsr = 1;
+ else
+ GMStatus[i].dsr = 0;
+
+
+ if (status & 0x10 /*UART_MSR_CTS */ )
+ GMStatus[i].cts = 1;
+ else
+ GMStatus[i].cts = 0;
+ }
+ if (copy_to_user(argp, GMStatus, sizeof(struct mxser_mstatus) * MXSER_PORTS))
+ return -EFAULT;
+ return 0;
+ case MOXA_ASPP_MON_EXT:{
+ int status;
+ int opmode, p;
+ int shiftbit;
+ unsigned cflag, iflag;
+
+ for (i = 0; i < MXSER_PORTS; i++) {
+
+ if (!mxvar_table[i].base)
+ continue;
+
+ status = mxser_get_msr(mxvar_table[i].base, 0, i, &(mxvar_table[i]));
+// mxser_check_modem_status(&mxvar_table[i], status);
+ if (status & UART_MSR_TERI)
+ mxvar_table[i].icount.rng++;
+ if (status & UART_MSR_DDSR)
+ mxvar_table[i].icount.dsr++;
+ if (status & UART_MSR_DDCD)
+ mxvar_table[i].icount.dcd++;
+ if (status & UART_MSR_DCTS)
+ mxvar_table[i].icount.cts++;
+
+ mxvar_table[i].mon_data.modem_status = status;
+ mon_data_ext.rx_cnt[i] = mxvar_table[i].mon_data.rxcnt;
+ mon_data_ext.tx_cnt[i] = mxvar_table[i].mon_data.txcnt;
+ mon_data_ext.up_rxcnt[i] = mxvar_table[i].mon_data.up_rxcnt;
+ mon_data_ext.up_txcnt[i] = mxvar_table[i].mon_data.up_txcnt;
+ mon_data_ext.modem_status[i] = mxvar_table[i].mon_data.modem_status;
+ mon_data_ext.baudrate[i] = mxvar_table[i].realbaud;
+
+ if (!mxvar_table[i].tty || !mxvar_table[i].tty->termios) {
+ cflag = mxvar_table[i].normal_termios.c_cflag;
+ iflag = mxvar_table[i].normal_termios.c_iflag;
+ } else {
+ cflag = mxvar_table[i].tty->termios->c_cflag;
+ iflag = mxvar_table[i].tty->termios->c_iflag;
+ }
+
+ mon_data_ext.databits[i] = cflag & CSIZE;
+
+ mon_data_ext.stopbits[i] = cflag & CSTOPB;
+
+ mon_data_ext.parity[i] = cflag & (PARENB | PARODD | CMSPAR);
+
+ mon_data_ext.flowctrl[i] = 0x00;
+
+ if (cflag & CRTSCTS)
+ mon_data_ext.flowctrl[i] |= 0x03;
+
+ if (iflag & (IXON | IXOFF))
+ mon_data_ext.flowctrl[i] |= 0x0C;
+
+ if (mxvar_table[i].type == PORT_16550A)
+ mon_data_ext.fifo[i] = 1;
+ else
+ mon_data_ext.fifo[i] = 0;
+
+ p = i % 4;
+ shiftbit = p * 2;
+ opmode = inb(mxvar_table[i].opmode_ioaddr) >> shiftbit;
+ opmode &= OP_MODE_MASK;
+
+ mon_data_ext.iftype[i] = opmode;
+
+ }
+ if (copy_to_user(argp, &mon_data_ext, sizeof(struct mxser_mon_ext)))
+ return -EFAULT;
+
+ return 0;
+
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+
+static void mxser_stoprx(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ //unsigned long flags;
+
+
+ info->ldisc_stop_rx = 1;
+ if (I_IXOFF(tty)) {
+
+ //MX_LOCK(&info->slock);
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag) {
+ info->IER &= ~MOXA_MUST_RECV_ISR;
+ outb(info->IER, info->base + UART_IER);
+ } else {
+ // above add by Victor Yu. 09-02-2002
+
+ info->x_char = STOP_CHAR(tty);
+ // outb(info->IER, 0); // mask by Victor Yu. 09-02-2002
+ outb(0, info->base + UART_IER);
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER); /* force Tx interrupt */
+ } // add by Victor Yu. 09-02-2002
+ //MX_UNLOCK(&info->slock);
+ }
+
+ if (info->tty->termios->c_cflag & CRTSCTS) {
+ //MX_LOCK(&info->slock);
+ info->MCR &= ~UART_MCR_RTS;
+ outb(info->MCR, info->base + UART_MCR);
+ //MX_UNLOCK(&info->slock);
+ }
+}
+
+static void mxser_startrx(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ //unsigned long flags;
+
+ info->ldisc_stop_rx = 0;
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else {
+ //MX_LOCK(&info->slock);
+
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag) {
+ info->IER |= MOXA_MUST_RECV_ISR;
+ outb(info->IER, info->base + UART_IER);
+ } else {
+ // above add by Victor Yu. 09-02-2002
+
+ info->x_char = START_CHAR(tty);
+ // outb(info->IER, 0); // mask by Victor Yu. 09-02-2002
+ outb(0, info->base + UART_IER); // add by Victor Yu. 09-02-2002
+ info->IER |= UART_IER_THRI; /* force Tx interrupt */
+ outb(info->IER, info->base + UART_IER);
+ } // add by Victor Yu. 09-02-2002
+ //MX_UNLOCK(&info->slock);
+ }
+ }
+
+ if (info->tty->termios->c_cflag & CRTSCTS) {
+ //MX_LOCK(&info->slock);
+ info->MCR |= UART_MCR_RTS;
+ outb(info->MCR, info->base + UART_MCR);
+ //MX_UNLOCK(&info->slock);
+ }
+}
+
+/*
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ */
+static void mxser_throttle(struct tty_struct *tty)
+{
+ //struct mxser_struct *info = (struct mxser_struct *)tty->driver_data;
+ //unsigned long flags;
+ //MX_LOCK(&info->slock);
+ mxser_stoprx(tty);
+ //MX_UNLOCK(&info->slock);
+}
+
+static void mxser_unthrottle(struct tty_struct *tty)
+{
+ //struct mxser_struct *info = (struct mxser_struct *)tty->driver_data;
+ //unsigned long flags;
+ //MX_LOCK(&info->slock);
+ mxser_startrx(tty);
+ //MX_UNLOCK(&info->slock);
+}
+
+static void mxser_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ if ((tty->termios->c_cflag != old_termios->c_cflag) || (RELEVANT_IFLAG(tty->termios->c_iflag) != RELEVANT_IFLAG(old_termios->c_iflag))) {
+
+ mxser_change_speed(info, old_termios);
+
+ if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ mxser_start(tty);
+ }
+ }
+
+/* Handle sw stopped */
+ if ((old_termios->c_iflag & IXON) && !(tty->termios->c_iflag & IXON)) {
+ tty->stopped = 0;
+
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag) {
+ spin_lock_irqsave(&info->slock, flags);
+ DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+ // above add by Victor Yu. 09-02-2002
+
+ mxser_start(tty);
+ }
+}
+
+/*
+ * mxser_stop() and mxser_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ */
+static void mxser_stop(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+ if (info->IER & UART_IER_THRI) {
+ info->IER &= ~UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static void mxser_start(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+ if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) {
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * mxser_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void mxser_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+ int lsr;
+
+ if (info->type == PORT_UNKNOWN)
+ return;
+
+ if (info->xmit_fifo_size == 0)
+ return; /* Just in case.... */
+
+ orig_jiffies = jiffies;
+ /*
+ * Set the check interval to be 1/5 of the estimated time to
+ * send a single character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ *
+ * Note: we have to use pretty tight timings here to satisfy
+ * the NIST-PCTS.
+ */
+ char_time = (info->timeout - HZ / 50) / info->xmit_fifo_size;
+ char_time = char_time / 5;
+ if (char_time == 0)
+ char_time = 1;
+ if (timeout && timeout < char_time)
+ char_time = timeout;
+ /*
+ * If the transmitter hasn't cleared in twice the approximate
+ * amount of time to send the entire FIFO, it probably won't
+ * ever clear. This assumes the UART isn't doing flow
+ * control, which is currently the case. Hence, if it ever
+ * takes longer than info->timeout, this is probably due to a
+ * UART bug of some kind. So, we clamp the timeout parameter at
+ * 2*info->timeout.
+ */
+ if (!timeout || timeout > 2 * info->timeout)
+ timeout = 2 * info->timeout;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk(KERN_DEBUG "In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
+ printk("jiff=%lu...", jiffies);
+#endif
+ while (!((lsr = inb(info->base + UART_LSR)) & UART_LSR_TEMT)) {
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("lsr = %d (jiff=%lu)...", lsr, jiffies);
+#endif
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(char_time);
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ set_current_state(TASK_RUNNING);
+
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+ printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
+#endif
+}
+
+
+/*
+ * This routine is called by tty_hangup() when a hangup is signaled.
+ */
+void mxser_hangup(struct tty_struct *tty)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+
+ mxser_flush_buffer(tty);
+ mxser_shutdown(info);
+ info->event = 0;
+ info->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+ wake_up_interruptible(&info->open_wait);
+}
+
+
+// added by James 03-12-2004.
+/*
+ * mxser_rs_break() --- routine which turns the break handling on or off
+ */
+static void mxser_rs_break(struct tty_struct *tty, int break_state)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+ if (break_state == -1)
+ outb(inb(info->base + UART_LCR) | UART_LCR_SBC, info->base + UART_LCR);
+ else
+ outb(inb(info->base + UART_LCR) & ~UART_LCR_SBC, info->base + UART_LCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+// (above) added by James.
+
+
+/*
+ * This is the serial driver's generic interrupt routine
+ */
+static irqreturn_t mxser_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ int status, iir, i;
+ struct mxser_struct *info;
+ struct mxser_struct *port;
+ int max, irqbits, bits, msr;
+ int pass_counter = 0;
+ int handled = IRQ_NONE;
+
+ port = NULL;
+ //spin_lock(&gm_lock);
+
+ for (i = 0; i < MXSER_BOARDS; i++) {
+ if (dev_id == &(mxvar_table[i * MXSER_PORTS_PER_BOARD])) {
+ port = dev_id;
+ break;
+ }
+ }
+
+ if (i == MXSER_BOARDS) {
+ goto irq_stop;
+ }
+ if (port == 0) {
+ goto irq_stop;
+ }
+ max = mxser_numports[mxsercfg[i].board_type - 1];
+ while (1) {
+ irqbits = inb(port->vector) & port->vectormask;
+ if (irqbits == port->vectormask) {
+ break;
+ }
+
+ handled = IRQ_HANDLED;
+ for (i = 0, bits = 1; i < max; i++, irqbits |= bits, bits <<= 1) {
+ if (irqbits == port->vectormask) {
+ break;
+ }
+ if (bits & irqbits)
+ continue;
+ info = port + i;
+
+ // following add by Victor Yu. 09-13-2002
+ iir = inb(info->base + UART_IIR);
+ if (iir & UART_IIR_NO_INT)
+ continue;
+ iir &= MOXA_MUST_IIR_MASK;
+ if (!info->tty) {
+ status = inb(info->base + UART_LSR);
+ outb(0x27, info->base + UART_FCR);
+ inb(info->base + UART_MSR);
+ continue;
+ }
+ // above add by Victor Yu. 09-13-2002
+ /*
+ if ( info->tty->flip.count < TTY_FLIPBUF_SIZE/4 ){
+ info->IER |= MOXA_MUST_RECV_ISR;
+ outb(info->IER, info->base + UART_IER);
+ }
+ */
+
+
+ /* mask by Victor Yu. 09-13-2002
+ if ( !info->tty ||
+ (inb(info->base + UART_IIR) & UART_IIR_NO_INT) )
+ continue;
+ */
+ /* mask by Victor Yu. 09-02-2002
+ status = inb(info->base + UART_LSR) & info->read_status_mask;
+ */
+
+ // following add by Victor Yu. 09-02-2002
+ status = inb(info->base + UART_LSR);
+
+ if (status & UART_LSR_PE) {
+ info->err_shadow |= NPPI_NOTIFY_PARITY;
+ }
+ if (status & UART_LSR_FE) {
+ info->err_shadow |= NPPI_NOTIFY_FRAMING;
+ }
+ if (status & UART_LSR_OE) {
+ info->err_shadow |= NPPI_NOTIFY_HW_OVERRUN;
+ }
+ if (status & UART_LSR_BI)
+ info->err_shadow |= NPPI_NOTIFY_BREAK;
+
+ if (info->IsMoxaMustChipFlag) {
+ /*
+ if ( (status & 0x02) && !(status & 0x01) ) {
+ outb(info->base+UART_FCR, 0x23);
+ continue;
+ }
+ */
+ if (iir == MOXA_MUST_IIR_GDA || iir == MOXA_MUST_IIR_RDA || iir == MOXA_MUST_IIR_RTO || iir == MOXA_MUST_IIR_LSR)
+ mxser_receive_chars(info, &status);
+
+ } else {
+ // above add by Victor Yu. 09-02-2002
+
+ status &= info->read_status_mask;
+ if (status & UART_LSR_DR)
+ mxser_receive_chars(info, &status);
+ }
+ msr = inb(info->base + UART_MSR);
+ if (msr & UART_MSR_ANY_DELTA) {
+ mxser_check_modem_status(info, msr);
+ }
+ // following add by Victor Yu. 09-13-2002
+ if (info->IsMoxaMustChipFlag) {
+ if ((iir == 0x02) && (status & UART_LSR_THRE)) {
+ mxser_transmit_chars(info);
+ }
+ } else {
+ // above add by Victor Yu. 09-13-2002
+
+ if (status & UART_LSR_THRE) {
+/* 8-2-99 by William
+ if ( info->x_char || (info->xmit_cnt > 0) )
+*/
+ mxser_transmit_chars(info);
+ }
+ }
+ }
+ if (pass_counter++ > MXSER_ISR_PASS_LIMIT) {
+ break; /* Prevent infinite loops */
+ }
+ }
+
+ irq_stop:
+ //spin_unlock(&gm_lock);
+ return handled;
+}
+
+static void mxser_receive_chars(struct mxser_struct *info, int *status)
+{
+ struct tty_struct *tty = info->tty;
+ unsigned char ch, gdl;
+ int ignored = 0;
+ int cnt = 0;
+ unsigned char *cp;
+ char *fp;
+ int count;
+ int recv_room;
+ int max = 256;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ recv_room = tty->ldisc.receive_room(tty);
+ if ((recv_room == 0) && (!info->ldisc_stop_rx)) {
+ //mxser_throttle(tty);
+ mxser_stoprx(tty);
+ //return;
+ }
+
+ cp = tty->flip.char_buf;
+ fp = tty->flip.flag_buf;
+ count = 0;
+
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag != MOXA_OTHER_UART) {
+
+ if (*status & UART_LSR_SPECIAL) {
+ goto intr_old;
+ }
+ // following add by Victor Yu. 02-11-2004
+ if (info->IsMoxaMustChipFlag == MOXA_MUST_MU860_HWID && (*status & MOXA_MUST_LSR_RERR))
+ goto intr_old;
+ // above add by Victor Yu. 02-14-2004
+ if (*status & MOXA_MUST_LSR_RERR)
+ goto intr_old;
+
+ gdl = inb(info->base + MOXA_MUST_GDL_REGISTER);
+
+ if (info->IsMoxaMustChipFlag == MOXA_MUST_MU150_HWID) // add by Victor Yu. 02-11-2004
+ gdl &= MOXA_MUST_GDL_MASK;
+ if (gdl >= recv_room) {
+ if (!info->ldisc_stop_rx) {
+ //mxser_throttle(tty);
+ mxser_stoprx(tty);
+ }
+ //return;
+ }
+ while (gdl--) {
+ ch = inb(info->base + UART_RX);
+ count++;
+ *cp++ = ch;
+ *fp++ = 0;
+ cnt++;
+ /*
+ if((count>=HI_WATER) && (info->stop_rx==0)){
+ mxser_stoprx(tty);
+ info->stop_rx=1;
+ break;
+ } */
+ }
+ goto end_intr;
+ }
+intr_old:
+ // above add by Victor Yu. 09-02-2002
+
+ do {
+ if (max-- < 0)
+ break;
+ /*
+ if((count>=HI_WATER) && (info->stop_rx==0)){
+ mxser_stoprx(tty);
+ info->stop_rx=1;
+ break;
+ }
+ */
+
+ ch = inb(info->base + UART_RX);
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag && (*status & UART_LSR_OE) /*&& !(*status&UART_LSR_DR) */ )
+ outb(0x23, info->base + UART_FCR);
+ *status &= info->read_status_mask;
+ // above add by Victor Yu. 09-02-2002
+ if (*status & info->ignore_status_mask) {
+ if (++ignored > 100)
+ break;
+ } else {
+ count++;
+ if (*status & UART_LSR_SPECIAL) {
+ if (*status & UART_LSR_BI) {
+ *fp++ = TTY_BREAK;
+/* added by casper 1/11/2000 */
+ info->icount.brk++;
+
+/* */
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (*status & UART_LSR_PE) {
+ *fp++ = TTY_PARITY;
+/* added by casper 1/11/2000 */
+ info->icount.parity++;
+/* */
+ } else if (*status & UART_LSR_FE) {
+ *fp++ = TTY_FRAME;
+/* added by casper 1/11/2000 */
+ info->icount.frame++;
+/* */
+ } else if (*status & UART_LSR_OE) {
+ *fp++ = TTY_OVERRUN;
+/* added by casper 1/11/2000 */
+ info->icount.overrun++;
+/* */
+ } else
+ *fp++ = 0;
+ } else
+ *fp++ = 0;
+ *cp++ = ch;
+ cnt++;
+ if (cnt >= recv_room) {
+ if (!info->ldisc_stop_rx) {
+ //mxser_throttle(tty);
+ mxser_stoprx(tty);
+ }
+ break;
+ }
+
+ }
+
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag)
+ break;
+ // above add by Victor Yu. 09-02-2002
+
+ /* mask by Victor Yu. 09-02-2002
+ *status = inb(info->base + UART_LSR) & info->read_status_mask;
+ */
+ // following add by Victor Yu. 09-02-2002
+ *status = inb(info->base + UART_LSR);
+ // above add by Victor Yu. 09-02-2002
+ } while (*status & UART_LSR_DR);
+
+ end_intr: // add by Victor Yu. 09-02-2002
+
+ mxvar_log.rxcnt[info->port] += cnt;
+ info->mon_data.rxcnt += cnt;
+ info->mon_data.up_rxcnt += cnt;
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ tty_flip_buffer_push(tty);
+}
+
+static void mxser_transmit_chars(struct mxser_struct *info)
+{
+ int count, cnt;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ if (info->x_char) {
+ outb(info->x_char, info->base + UART_TX);
+ info->x_char = 0;
+ mxvar_log.txcnt[info->port]++;
+ info->mon_data.txcnt++;
+ info->mon_data.up_txcnt++;
+
+/* added by casper 1/11/2000 */
+ info->icount.tx++;
+/* */
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+
+ if (info->xmit_buf == 0) {
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+
+ if ((info->xmit_cnt <= 0) || info->tty->stopped || (info->tty->hw_stopped && (info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag))) {
+ info->IER &= ~UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+
+ cnt = info->xmit_cnt;
+ count = info->xmit_fifo_size;
+ do {
+ outb(info->xmit_buf[info->xmit_tail++], info->base + UART_TX);
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1);
+ if (--info->xmit_cnt <= 0)
+ break;
+ } while (--count > 0);
+ mxvar_log.txcnt[info->port] += (cnt - info->xmit_cnt);
+
+// added by James 03-12-2004.
+ info->mon_data.txcnt += (cnt - info->xmit_cnt);
+ info->mon_data.up_txcnt += (cnt - info->xmit_cnt);
+// (above) added by James.
+
+/* added by casper 1/11/2000 */
+ info->icount.tx += (cnt - info->xmit_cnt);
+/* */
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ set_bit(MXSER_EVENT_TXLOW, &info->event);
+ schedule_work(&info->tqueue);
+ }
+ if (info->xmit_cnt <= 0) {
+ info->IER &= ~UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static void mxser_check_modem_status(struct mxser_struct *info, int status)
+{
+ /* update input line counters */
+ if (status & UART_MSR_TERI)
+ info->icount.rng++;
+ if (status & UART_MSR_DDSR)
+ info->icount.dsr++;
+ if (status & UART_MSR_DDCD)
+ info->icount.dcd++;
+ if (status & UART_MSR_DCTS)
+ info->icount.cts++;
+ info->mon_data.modem_status = status;
+ wake_up_interruptible(&info->delta_msr_wait);
+
+
+ if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {
+ if (status & UART_MSR_DCD)
+ wake_up_interruptible(&info->open_wait);
+ schedule_work(&info->tqueue);
+ }
+
+ if (info->flags & ASYNC_CTS_FLOW) {
+ if (info->tty->hw_stopped) {
+ if (status & UART_MSR_CTS) {
+ info->tty->hw_stopped = 0;
+
+ if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ set_bit(MXSER_EVENT_TXLOW, &info->event);
+ schedule_work(&info->tqueue); }
+ } else {
+ if (!(status & UART_MSR_CTS)) {
+ info->tty->hw_stopped = 1;
+ if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+ info->IER &= ~UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ }
+ }
+ }
+}
+
+static int mxser_block_til_ready(struct tty_struct *tty, struct file *filp, struct mxser_struct *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0;
+ unsigned long flags;
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return (0);
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * mxser_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+
+ spin_lock_irqsave(&info->slock, flags);
+ if (!tty_hung_up_p(filp))
+ info->count--;
+ spin_unlock_irqrestore(&info->slock, flags);
+ info->blocked_open++;
+ while (1) {
+ spin_lock_irqsave(&info->slock, flags);
+ outb(inb(info->base + UART_MCR) | UART_MCR_DTR | UART_MCR_RTS, info->base + UART_MCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)) {
+ if (info->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(info->flags & ASYNC_CLOSING) && (do_clocal || (inb(info->base + UART_MSR) & UART_MSR_DCD)))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ info->count++;
+ info->blocked_open--;
+ if (retval)
+ return (retval);
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return (0);
+}
+
+static int mxser_startup(struct mxser_struct *info)
+{
+
+ unsigned long page;
+ unsigned long flags;
+
+ page = __get_free_page(GFP_KERNEL);
+ if (!page)
+ return (-ENOMEM);
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ if (info->flags & ASYNC_INITIALIZED) {
+ free_page(page);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return (0);
+ }
+
+ if (!info->base || !info->type) {
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ free_page(page);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return (0);
+ }
+ if (info->xmit_buf)
+ free_page(page);
+ else
+ info->xmit_buf = (unsigned char *) page;
+
+ /*
+ * Clear the FIFO buffers and disable them
+ * (they will be reenabled in mxser_change_speed())
+ */
+ if (info->IsMoxaMustChipFlag)
+ outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT | MOXA_MUST_FCR_GDA_MODE_ENABLE), info->base + UART_FCR);
+ else
+ outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+
+ /*
+ * At this point there's no way the LSR could still be 0xFF;
+ * if it is, then bail out, because there's likely no UART
+ * here.
+ */
+ if (inb(info->base + UART_LSR) == 0xff) {
+ spin_unlock_irqrestore(&info->slock, flags);
+ if (capable(CAP_SYS_ADMIN)) {
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ return (0);
+ } else
+ return (-ENODEV);
+ }
+
+ /*
+ * Clear the interrupt registers.
+ */
+ (void) inb(info->base + UART_LSR);
+ (void) inb(info->base + UART_RX);
+ (void) inb(info->base + UART_IIR);
+ (void) inb(info->base + UART_MSR);
+
+ /*
+ * Now, initialize the UART
+ */
+ outb(UART_LCR_WLEN8, info->base + UART_LCR); /* reset DLAB */
+ info->MCR = UART_MCR_DTR | UART_MCR_RTS;
+ outb(info->MCR, info->base + UART_MCR);
+
+ /*
+ * Finally, enable interrupts
+ */
+ info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
+// info->IER = UART_IER_RLSI | UART_IER_RDI;
+
+ // following add by Victor Yu. 08-30-2002
+ if (info->IsMoxaMustChipFlag)
+ info->IER |= MOXA_MUST_IER_EGDAI;
+ // above add by Victor Yu. 08-30-2002
+ outb(info->IER, info->base + UART_IER); /* enable interrupts */
+
+ /*
+ * And clear the interrupt registers again for luck.
+ */
+ (void) inb(info->base + UART_LSR);
+ (void) inb(info->base + UART_RX);
+ (void) inb(info->base + UART_IIR);
+ (void) inb(info->base + UART_MSR);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+ /*
+ * and set the speed of the serial port
+ */
+ spin_unlock_irqrestore(&info->slock, flags);
+ mxser_change_speed(info, NULL);
+
+ info->flags |= ASYNC_INITIALIZED;
+ return (0);
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts maybe disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void mxser_shutdown(struct mxser_struct *info)
+{
+ unsigned long flags;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ /*
+ * clear delta_msr_wait queue to avoid mem leaks: we may free the irq
+ * here so the queue might never be waken up
+ */
+ wake_up_interruptible(&info->delta_msr_wait);
+
+ /*
+ * Free the IRQ, if necessary
+ */
+ if (info->xmit_buf) {
+ free_page((unsigned long) info->xmit_buf);
+ info->xmit_buf = NULL;
+ }
+
+ info->IER = 0;
+ outb(0x00, info->base + UART_IER);
+
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+ info->MCR &= ~(UART_MCR_DTR | UART_MCR_RTS);
+ outb(info->MCR, info->base + UART_MCR);
+
+ /* clear Rx/Tx FIFO's */
+ // following add by Victor Yu. 08-30-2002
+ if (info->IsMoxaMustChipFlag)
+ outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT | MOXA_MUST_FCR_GDA_MODE_ENABLE), info->base + UART_FCR);
+ else
+ // above add by Victor Yu. 08-30-2002
+ outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+
+ /* read data port to reset things */
+ (void) inb(info->base + UART_RX);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+
+ // following add by Victor Yu. 09-23-2002
+ if (info->IsMoxaMustChipFlag) {
+ SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(info->base);
+ }
+ // above add by Victor Yu. 09-23-2002
+
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static int mxser_change_speed(struct mxser_struct *info, struct termios *old_termios)
+{
+ unsigned cflag, cval, fcr;
+ int ret = 0;
+ unsigned char status;
+ long baud;
+ unsigned long flags;
+
+
+ if (!info->tty || !info->tty->termios)
+ return ret;
+ cflag = info->tty->termios->c_cflag;
+ if (!(info->base))
+ return ret;
+
+
+#ifndef B921600
+#define B921600 (B460800 +1)
+#endif
+ if (mxser_set_baud_method[info->port] == 0) {
+ switch (cflag & (CBAUD | CBAUDEX)) {
+ case B921600:
+ baud = 921600;
+ break;
+ case B460800:
+ baud = 460800;
+ break;
+ case B230400:
+ baud = 230400;
+ break;
+ case B115200:
+ baud = 115200;
+ break;
+ case B57600:
+ baud = 57600;
+ break;
+ case B38400:
+ baud = 38400;
+ break;
+ case B19200:
+ baud = 19200;
+ break;
+ case B9600:
+ baud = 9600;
+ break;
+ case B4800:
+ baud = 4800;
+ break;
+ case B2400:
+ baud = 2400;
+ break;
+ case B1800:
+ baud = 1800;
+ break;
+ case B1200:
+ baud = 1200;
+ break;
+ case B600:
+ baud = 600;
+ break;
+ case B300:
+ baud = 300;
+ break;
+ case B200:
+ baud = 200;
+ break;
+ case B150:
+ baud = 150;
+ break;
+ case B134:
+ baud = 134;
+ break;
+ case B110:
+ baud = 110;
+ break;
+ case B75:
+ baud = 75;
+ break;
+ case B50:
+ baud = 50;
+ break;
+ default:
+ baud = 0;
+ break;
+ }
+ mxser_set_baud(info, baud);
+ }
+
+ /* byte size and parity */
+ switch (cflag & CSIZE) {
+ case CS5:
+ cval = 0x00;
+ break;
+ case CS6:
+ cval = 0x01;
+ break;
+ case CS7:
+ cval = 0x02;
+ break;
+ case CS8:
+ cval = 0x03;
+ break;
+ default:
+ cval = 0x00;
+ break; /* too keep GCC shut... */
+ }
+ if (cflag & CSTOPB)
+ cval |= 0x04;
+ if (cflag & PARENB)
+ cval |= UART_LCR_PARITY;
+ if (!(cflag & PARODD)) {
+ cval |= UART_LCR_EPAR;
+ }
+ if (cflag & CMSPAR)
+ cval |= UART_LCR_SPAR;
+
+ if ((info->type == PORT_8250) || (info->type == PORT_16450)) {
+ if (info->IsMoxaMustChipFlag) {
+ fcr = UART_FCR_ENABLE_FIFO;
+ fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
+ SET_MOXA_MUST_FIFO_VALUE(info);
+ } else
+ fcr = 0;
+ } else {
+ fcr = UART_FCR_ENABLE_FIFO;
+ // following add by Victor Yu. 08-30-2002
+ if (info->IsMoxaMustChipFlag) {
+ fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
+ SET_MOXA_MUST_FIFO_VALUE(info);
+ } else {
+ // above add by Victor Yu. 08-30-2002
+
+ switch (info->rx_trigger) {
+ case 1:
+ fcr |= UART_FCR_TRIGGER_1;
+ break;
+ case 4:
+ fcr |= UART_FCR_TRIGGER_4;
+ break;
+ case 8:
+ fcr |= UART_FCR_TRIGGER_8;
+ break;
+ default:
+ fcr |= UART_FCR_TRIGGER_14;
+ break;
+ }
+ }
+ }
+
+ /* CTS flow control flag and modem status interrupts */
+ info->IER &= ~UART_IER_MSI;
+ info->MCR &= ~UART_MCR_AFE;
+ if (cflag & CRTSCTS) {
+ info->flags |= ASYNC_CTS_FLOW;
+ info->IER |= UART_IER_MSI;
+ if ((info->type == PORT_16550A) || (info->IsMoxaMustChipFlag)) {
+ info->MCR |= UART_MCR_AFE;
+ //status = mxser_get_msr(info->base, 0, info->port);
+/* save_flags(flags);
+ cli();
+ status = inb(baseaddr + UART_MSR);
+ restore_flags(flags);*/
+ //mxser_check_modem_status(info, status);
+ } else {
+ //status = mxser_get_msr(info->base, 0, info->port);
+
+ //MX_LOCK(&info->slock);
+ status = inb(info->base + UART_MSR);
+ //MX_UNLOCK(&info->slock);
+ if (info->tty->hw_stopped) {
+ if (status & UART_MSR_CTS) {
+ info->tty->hw_stopped = 0;
+ if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+ info->IER |= UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ set_bit(MXSER_EVENT_TXLOW, &info->event);
+ schedule_work(&info->tqueue); }
+ } else {
+ if (!(status & UART_MSR_CTS)) {
+ info->tty->hw_stopped = 1;
+ if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+ info->IER &= ~UART_IER_THRI;
+ outb(info->IER, info->base + UART_IER);
+ }
+ }
+ }
+ }
+ } else {
+ info->flags &= ~ASYNC_CTS_FLOW;
+ }
+ outb(info->MCR, info->base + UART_MCR);
+ if (cflag & CLOCAL) {
+ info->flags &= ~ASYNC_CHECK_CD;
+ } else {
+ info->flags |= ASYNC_CHECK_CD;
+ info->IER |= UART_IER_MSI;
+ }
+ outb(info->IER, info->base + UART_IER);
+
+ /*
+ * Set up parity check flag
+ */
+ info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask |= UART_LSR_BI;
+
+ info->ignore_status_mask = 0;
+
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_BI;
+ info->read_status_mask |= UART_LSR_BI;
+ /*
+ * If we're ignore parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty)) {
+ info->ignore_status_mask |= UART_LSR_OE | UART_LSR_PE | UART_LSR_FE;
+ info->read_status_mask |= UART_LSR_OE | UART_LSR_PE | UART_LSR_FE;
+ }
+ }
+ // following add by Victor Yu. 09-02-2002
+ if (info->IsMoxaMustChipFlag) {
+ spin_lock_irqsave(&info->slock, flags);
+ SET_MOXA_MUST_XON1_VALUE(info->base, START_CHAR(info->tty));
+ SET_MOXA_MUST_XOFF1_VALUE(info->base, STOP_CHAR(info->tty));
+ if (I_IXON(info->tty)) {
+ ENABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+ } else {
+ DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+ }
+ if (I_IXOFF(info->tty)) {
+ ENABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(info->base);
+ } else {
+ DISABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(info->base);
+ }
+ /*
+ if ( I_IXANY(info->tty) ) {
+ info->MCR |= MOXA_MUST_MCR_XON_ANY;
+ ENABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(info->base);
+ } else {
+ info->MCR &= ~MOXA_MUST_MCR_XON_ANY;
+ DISABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(info->base);
+ }
+ */
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+ // above add by Victor Yu. 09-02-2002
+
+
+ outb(fcr, info->base + UART_FCR); /* set fcr */
+ outb(cval, info->base + UART_LCR);
+
+ return ret;
+}
+
+
+static int mxser_set_baud(struct mxser_struct *info, long newspd)
+{
+ int quot = 0;
+ unsigned char cval;
+ int ret = 0;
+ unsigned long flags;
+
+ if (!info->tty || !info->tty->termios)
+ return ret;
+
+ if (!(info->base))
+ return ret;
+
+ if (newspd > info->MaxCanSetBaudRate)
+ return 0;
+
+ info->realbaud = newspd;
+ if (newspd == 134) {
+ quot = (2 * info->baud_base / 269);
+ } else if (newspd) {
+ quot = info->baud_base / newspd;
+
+ if (quot == 0)
+ quot = 1;
+
+ } else {
+ quot = 0;
+ }
+
+ info->timeout = ((info->xmit_fifo_size * HZ * 10 * quot) / info->baud_base);
+ info->timeout += HZ / 50; /* Add .02 seconds of slop */
+
+ if (quot) {
+ spin_lock_irqsave(&info->slock, flags);
+ info->MCR |= UART_MCR_DTR;
+ outb(info->MCR, info->base + UART_MCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ } else {
+ spin_lock_irqsave(&info->slock, flags);
+ info->MCR &= ~UART_MCR_DTR;
+ outb(info->MCR, info->base + UART_MCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return ret;
+ }
+
+ cval = inb(info->base + UART_LCR);
+
+ outb(cval | UART_LCR_DLAB, info->base + UART_LCR); /* set DLAB */
+
+ outb(quot & 0xff, info->base + UART_DLL); /* LS of divisor */
+ outb(quot >> 8, info->base + UART_DLM); /* MS of divisor */
+ outb(cval, info->base + UART_LCR); /* reset DLAB */
+
+
+ return ret;
+}
+
+
+
+/*
+ * ------------------------------------------------------------
+ * friends of mxser_ioctl()
+ * ------------------------------------------------------------
+ */
+static int mxser_get_serial_info(struct mxser_struct *info, struct serial_struct __user *retinfo)
+{
+ struct serial_struct tmp;
+
+ if (!retinfo)
+ return (-EFAULT);
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = info->type;
+ tmp.line = info->port;
+ tmp.port = info->base;
+ tmp.irq = info->irq;
+ tmp.flags = info->flags;
+ tmp.baud_base = info->baud_base;
+ tmp.close_delay = info->close_delay;
+ tmp.closing_wait = info->closing_wait;
+ tmp.custom_divisor = info->custom_divisor;
+ tmp.hub6 = 0;
+ if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+ return -EFAULT;
+ return (0);
+}
+
+static int mxser_set_serial_info(struct mxser_struct *info, struct serial_struct __user *new_info)
+{
+ struct serial_struct new_serial;
+ unsigned int flags;
+ int retval = 0;
+
+ if (!new_info || !info->base)
+ return (-EFAULT);
+ if (copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+ return -EFAULT;
+
+ if ((new_serial.irq != info->irq) || (new_serial.port != info->base) || (new_serial.custom_divisor != info->custom_divisor) || (new_serial.baud_base != info->baud_base))
+ return (-EPERM);
+
+ flags = info->flags & ASYNC_SPD_MASK;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((new_serial.baud_base != info->baud_base) || (new_serial.close_delay != info->close_delay) || ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK)))
+ return (-EPERM);
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK));
+ } else {
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+ info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS));
+ info->close_delay = new_serial.close_delay * HZ / 100;
+ info->closing_wait = new_serial.closing_wait * HZ / 100;
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+ info->tty->low_latency = 0; //(info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+ }
+
+ /* added by casper, 3/17/2000, for mouse */
+ info->type = new_serial.type;
+
+ process_txrx_fifo(info);
+
+ /* */
+ if (info->flags & ASYNC_INITIALIZED) {
+ if (flags != (info->flags & ASYNC_SPD_MASK)) {
+ mxser_change_speed(info, NULL);
+ }
+ } else {
+ retval = mxser_startup(info);
+ }
+ return (retval);
+}
+
+/*
+ * mxser_get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * is emptied. On bus types like RS485, the transmitter must
+ * release the bus after transmitting. This must be done when
+ * the transmit shift register is empty, not be done when the
+ * transmit holding register is empty. This functionality
+ * allows an RS485 driver to be written in user space.
+ */
+static int mxser_get_lsr_info(struct mxser_struct *info, unsigned int __user *value)
+{
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->slock, flags);
+ status = inb(info->base + UART_LSR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
+ return put_user(result, value);
+}
+
+/*
+ * This routine sends a break character out the serial port.
+ */
+static void mxser_send_break(struct mxser_struct *info, int duration)
+{
+ unsigned long flags;
+
+ if (!info->base)
+ return;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_lock_irqsave(&info->slock, flags);
+ outb(inb(info->base + UART_LCR) | UART_LCR_SBC, info->base + UART_LCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ schedule_timeout(duration);
+ spin_lock_irqsave(&info->slock, flags);
+ outb(inb(info->base + UART_LCR) & ~UART_LCR_SBC, info->base + UART_LCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static int mxser_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned char control, status;
+ unsigned long flags;
+
+
+ if (tty->index == MXSER_PORTS)
+ return (-ENOIOCTLCMD);
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return (-EIO);
+
+ control = info->MCR;
+
+ spin_lock_irqsave(&info->slock, flags);
+ status = inb(info->base + UART_MSR);
+ if (status & UART_MSR_ANY_DELTA)
+ mxser_check_modem_status(info, status);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) |
+ ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
+}
+
+static int mxser_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear)
+{
+ struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+ unsigned long flags;
+
+
+ if (tty->index == MXSER_PORTS)
+ return -ENOIOCTLCMD;
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ if (set & TIOCM_RTS)
+ info->MCR |= UART_MCR_RTS;
+ if (set & TIOCM_DTR)
+ info->MCR |= UART_MCR_DTR;
+
+ if (clear & TIOCM_RTS)
+ info->MCR &= ~UART_MCR_RTS;
+ if (clear & TIOCM_DTR)
+ info->MCR &= ~UART_MCR_DTR;
+
+ outb(info->MCR, info->base + UART_MCR);
+ spin_unlock_irqrestore(&info->slock, flags);
+ return 0;
+}
+
+
+static int mxser_read_register(int, unsigned short *);
+static int mxser_program_mode(int);
+static void mxser_normal_mode(int);
+
+static int mxser_get_ISA_conf(int cap, struct mxser_hwconf *hwconf)
+{
+ int id, i, bits;
+ unsigned short regs[16], irq;
+ unsigned char scratch, scratch2;
+
+ hwconf->IsMoxaMustChipFlag = MOXA_OTHER_UART;
+
+ id = mxser_read_register(cap, regs);
+ if (id == C168_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_C168_ISA;
+ hwconf->ports = 8;
+ } else if (id == C104_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_C104_ISA;
+ hwconf->ports = 4;
+ } else if (id == C102_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_C102_ISA;
+ hwconf->ports = 2;
+ } else if (id == CI132_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_CI132;
+ hwconf->ports = 2;
+ } else if (id == CI134_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_CI134;
+ hwconf->ports = 4;
+ } else if (id == CI104J_ASIC_ID) {
+ hwconf->board_type = MXSER_BOARD_CI104J;
+ hwconf->ports = 4;
+ } else
+ return (0);
+
+ irq = 0;
+ if (hwconf->ports == 2) {
+ irq = regs[9] & 0xF000;
+ irq = irq | (irq >> 4);
+ if (irq != (regs[9] & 0xFF00))
+ return (MXSER_ERR_IRQ_CONFLIT);
+ } else if (hwconf->ports == 4) {
+ irq = regs[9] & 0xF000;
+ irq = irq | (irq >> 4);
+ irq = irq | (irq >> 8);
+ if (irq != regs[9])
+ return (MXSER_ERR_IRQ_CONFLIT);
+ } else if (hwconf->ports == 8) {
+ irq = regs[9] & 0xF000;
+ irq = irq | (irq >> 4);
+ irq = irq | (irq >> 8);
+ if ((irq != regs[9]) || (irq != regs[10]))
+ return (MXSER_ERR_IRQ_CONFLIT);
+ }
+
+ if (!irq) {
+ return (MXSER_ERR_IRQ);
+ }
+ hwconf->irq = ((int) (irq & 0xF000) >> 12);
+ for (i = 0; i < 8; i++)
+ hwconf->ioaddr[i] = (int) regs[i + 1] & 0xFFF8;
+ if ((regs[12] & 0x80) == 0) {
+ return (MXSER_ERR_VECTOR);
+ }
+ hwconf->vector = (int) regs[11]; /* interrupt vector */
+ if (id == 1)
+ hwconf->vector_mask = 0x00FF;
+ else
+ hwconf->vector_mask = 0x000F;
+ for (i = 7, bits = 0x0100; i >= 0; i--, bits <<= 1) {
+ if (regs[12] & bits) {
+ hwconf->baud_base[i] = 921600;
+ hwconf->MaxCanSetBaudRate[i] = 921600; // add by Victor Yu. 09-04-2002
+ } else {
+ hwconf->baud_base[i] = 115200;
+ hwconf->MaxCanSetBaudRate[i] = 115200; // add by Victor Yu. 09-04-2002
+ }
+ }
+ scratch2 = inb(cap + UART_LCR) & (~UART_LCR_DLAB);
+ outb(scratch2 | UART_LCR_DLAB, cap + UART_LCR);
+ outb(0, cap + UART_EFR); /* EFR is the same as FCR */
+ outb(scratch2, cap + UART_LCR);
+ outb(UART_FCR_ENABLE_FIFO, cap + UART_FCR);
+ scratch = inb(cap + UART_IIR);
+
+ if (scratch & 0xC0)
+ hwconf->uart_type = PORT_16550A;
+ else
+ hwconf->uart_type = PORT_16450;
+ if (id == 1)
+ hwconf->ports = 8;
+ else
+ hwconf->ports = 4;
+ request_region(hwconf->ioaddr[0], 8 * hwconf->ports, "mxser(IO)");
+ request_region(hwconf->vector, 1, "mxser(vector)");
+ return (hwconf->ports);
+}
+
+#define CHIP_SK 0x01 /* Serial Data Clock in Eprom */
+#define CHIP_DO 0x02 /* Serial Data Output in Eprom */
+#define CHIP_CS 0x04 /* Serial Chip Select in Eprom */
+#define CHIP_DI 0x08 /* Serial Data Input in Eprom */
+#define EN_CCMD 0x000 /* Chip's command register */
+#define EN0_RSARLO 0x008 /* Remote start address reg 0 */
+#define EN0_RSARHI 0x009 /* Remote start address reg 1 */
+#define EN0_RCNTLO 0x00A /* Remote byte count reg WR */
+#define EN0_RCNTHI 0x00B /* Remote byte count reg WR */
+#define EN0_DCFG 0x00E /* Data configuration reg WR */
+#define EN0_PORT 0x010 /* Rcv missed frame error counter RD */
+#define ENC_PAGE0 0x000 /* Select page 0 of chip registers */
+#define ENC_PAGE3 0x0C0 /* Select page 3 of chip registers */
+static int mxser_read_register(int port, unsigned short *regs)
+{
+ int i, k, value, id;
+ unsigned int j;
+
+ id = mxser_program_mode(port);
+ if (id < 0)
+ return (id);
+ for (i = 0; i < 14; i++) {
+ k = (i & 0x3F) | 0x180;
+ for (j = 0x100; j > 0; j >>= 1) {
+ outb(CHIP_CS, port);
+ if (k & j) {
+ outb(CHIP_CS | CHIP_DO, port);
+ outb(CHIP_CS | CHIP_DO | CHIP_SK, port); /* A? bit of read */
+ } else {
+ outb(CHIP_CS, port);
+ outb(CHIP_CS | CHIP_SK, port); /* A? bit of read */
+ }
+ }
+ (void) inb(port);
+ value = 0;
+ for (k = 0, j = 0x8000; k < 16; k++, j >>= 1) {
+ outb(CHIP_CS, port);
+ outb(CHIP_CS | CHIP_SK, port);
+ if (inb(port) & CHIP_DI)
+ value |= j;
+ }
+ regs[i] = value;
+ outb(0, port);
+ }
+ mxser_normal_mode(port);
+ return (id);
+}
+
+static int mxser_program_mode(int port)
+{
+ int id, i, j, n;
+ //unsigned long flags;
+
+ spin_lock(&gm_lock);
+ outb(0, port);
+ outb(0, port);
+ outb(0, port);
+ (void) inb(port);
+ (void) inb(port);
+ outb(0, port);
+ (void) inb(port);
+ //restore_flags(flags);
+ spin_unlock(&gm_lock);
+
+ id = inb(port + 1) & 0x1F;
+ if ((id != C168_ASIC_ID) && (id != C104_ASIC_ID) && (id != C102_ASIC_ID) && (id != CI132_ASIC_ID) && (id != CI134_ASIC_ID) && (id != CI104J_ASIC_ID))
+ return (-1);
+ for (i = 0, j = 0; i < 4; i++) {
+ n = inb(port + 2);
+ if (n == 'M') {
+ j = 1;
+ } else if ((j == 1) && (n == 1)) {
+ j = 2;
+ break;
+ } else
+ j = 0;
+ }
+ if (j != 2)
+ id = -2;
+ return (id);
+}
+
+static void mxser_normal_mode(int port)
+{
+ int i, n;
+
+ outb(0xA5, port + 1);
+ outb(0x80, port + 3);
+ outb(12, port + 0); /* 9600 bps */
+ outb(0, port + 1);
+ outb(0x03, port + 3); /* 8 data bits */
+ outb(0x13, port + 4); /* loop back mode */
+ for (i = 0; i < 16; i++) {
+ n = inb(port + 5);
+ if ((n & 0x61) == 0x60)
+ break;
+ if ((n & 1) == 1)
+ (void) inb(port);
+ }
+ outb(0x00, port + 4);
+}
+
+module_init(mxser_module_init);
+module_exit(mxser_module_exit);
diff --git a/drivers/char/mxser.h b/drivers/char/mxser.h
new file mode 100644
index 000000000000..e7fd0b08e0b7
--- /dev/null
+++ b/drivers/char/mxser.h
@@ -0,0 +1,450 @@
+#ifndef _MXSER_H
+#define _MXSER_H
+
+/*
+ * Semi-public control interfaces
+ */
+
+/*
+ * MOXA ioctls
+ */
+
+#define MOXA 0x400
+#define MOXA_GETDATACOUNT (MOXA + 23)
+#define MOXA_GET_CONF (MOXA + 35)
+#define MOXA_DIAGNOSE (MOXA + 50)
+#define MOXA_CHKPORTENABLE (MOXA + 60)
+#define MOXA_HighSpeedOn (MOXA + 61)
+#define MOXA_GET_MAJOR (MOXA + 63)
+#define MOXA_GET_CUMAJOR (MOXA + 64)
+#define MOXA_GETMSTATUS (MOXA + 65)
+#define MOXA_SET_OP_MODE (MOXA + 66)
+#define MOXA_GET_OP_MODE (MOXA + 67)
+
+#define RS232_MODE 0
+#define RS485_2WIRE_MODE 1
+#define RS422_MODE 2
+#define RS485_4WIRE_MODE 3
+#define OP_MODE_MASK 3
+// above add by Victor Yu. 01-05-2004
+
+#define TTY_THRESHOLD_THROTTLE 128
+
+#define LO_WATER (TTY_FLIPBUF_SIZE)
+#define HI_WATER (TTY_FLIPBUF_SIZE*2*3/4)
+
+// added by James. 03-11-2004.
+#define MOXA_SDS_GETICOUNTER (MOXA + 68)
+#define MOXA_SDS_RSTICOUNTER (MOXA + 69)
+// (above) added by James.
+
+#define MOXA_ASPP_OQUEUE (MOXA + 70)
+#define MOXA_ASPP_SETBAUD (MOXA + 71)
+#define MOXA_ASPP_GETBAUD (MOXA + 72)
+#define MOXA_ASPP_MON (MOXA + 73)
+#define MOXA_ASPP_LSTATUS (MOXA + 74)
+#define MOXA_ASPP_MON_EXT (MOXA + 75)
+#define MOXA_SET_BAUD_METHOD (MOXA + 76)
+
+
+/* --------------------------------------------------- */
+
+#define NPPI_NOTIFY_PARITY 0x01
+#define NPPI_NOTIFY_FRAMING 0x02
+#define NPPI_NOTIFY_HW_OVERRUN 0x04
+#define NPPI_NOTIFY_SW_OVERRUN 0x08
+#define NPPI_NOTIFY_BREAK 0x10
+
+#define NPPI_NOTIFY_CTSHOLD 0x01 // Tx hold by CTS low
+#define NPPI_NOTIFY_DSRHOLD 0x02 // Tx hold by DSR low
+#define NPPI_NOTIFY_XOFFHOLD 0x08 // Tx hold by Xoff received
+#define NPPI_NOTIFY_XOFFXENT 0x10 // Xoff Sent
+
+//CheckIsMoxaMust return value
+#define MOXA_OTHER_UART 0x00
+#define MOXA_MUST_MU150_HWID 0x01
+#define MOXA_MUST_MU860_HWID 0x02
+
+// follow just for Moxa Must chip define.
+//
+// when LCR register (offset 0x03) write following value,
+// the Must chip will enter enchance mode. And write value
+// on EFR (offset 0x02) bit 6,7 to change bank.
+#define MOXA_MUST_ENTER_ENCHANCE 0xBF
+
+// when enhance mode enable, access on general bank register
+#define MOXA_MUST_GDL_REGISTER 0x07
+#define MOXA_MUST_GDL_MASK 0x7F
+#define MOXA_MUST_GDL_HAS_BAD_DATA 0x80
+
+#define MOXA_MUST_LSR_RERR 0x80 // error in receive FIFO
+// enchance register bank select and enchance mode setting register
+// when LCR register equal to 0xBF
+#define MOXA_MUST_EFR_REGISTER 0x02
+// enchance mode enable
+#define MOXA_MUST_EFR_EFRB_ENABLE 0x10
+// enchance reister bank set 0, 1, 2
+#define MOXA_MUST_EFR_BANK0 0x00
+#define MOXA_MUST_EFR_BANK1 0x40
+#define MOXA_MUST_EFR_BANK2 0x80
+#define MOXA_MUST_EFR_BANK3 0xC0
+#define MOXA_MUST_EFR_BANK_MASK 0xC0
+
+// set XON1 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XON1_REGISTER 0x04
+
+// set XON2 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XON2_REGISTER 0x05
+
+// set XOFF1 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XOFF1_REGISTER 0x06
+
+// set XOFF2 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XOFF2_REGISTER 0x07
+
+#define MOXA_MUST_RBRTL_REGISTER 0x04
+#define MOXA_MUST_RBRTH_REGISTER 0x05
+#define MOXA_MUST_RBRTI_REGISTER 0x06
+#define MOXA_MUST_THRTL_REGISTER 0x07
+#define MOXA_MUST_ENUM_REGISTER 0x04
+#define MOXA_MUST_HWID_REGISTER 0x05
+#define MOXA_MUST_ECR_REGISTER 0x06
+#define MOXA_MUST_CSR_REGISTER 0x07
+
+// good data mode enable
+#define MOXA_MUST_FCR_GDA_MODE_ENABLE 0x20
+// only good data put into RxFIFO
+#define MOXA_MUST_FCR_GDA_ONLY_ENABLE 0x10
+
+// enable CTS interrupt
+#define MOXA_MUST_IER_ECTSI 0x80
+// eanble RTS interrupt
+#define MOXA_MUST_IER_ERTSI 0x40
+// enable Xon/Xoff interrupt
+#define MOXA_MUST_IER_XINT 0x20
+// enable GDA interrupt
+#define MOXA_MUST_IER_EGDAI 0x10
+
+#define MOXA_MUST_RECV_ISR (UART_IER_RDI | MOXA_MUST_IER_EGDAI)
+
+// GDA interrupt pending
+#define MOXA_MUST_IIR_GDA 0x1C
+#define MOXA_MUST_IIR_RDA 0x04
+#define MOXA_MUST_IIR_RTO 0x0C
+#define MOXA_MUST_IIR_LSR 0x06
+
+// recieved Xon/Xoff or specical interrupt pending
+#define MOXA_MUST_IIR_XSC 0x10
+
+// RTS/CTS change state interrupt pending
+#define MOXA_MUST_IIR_RTSCTS 0x20
+#define MOXA_MUST_IIR_MASK 0x3E
+
+#define MOXA_MUST_MCR_XON_FLAG 0x40
+#define MOXA_MUST_MCR_XON_ANY 0x80
+#define MOXA_MUST_MCR_TX_XON 0x08
+
+
+// software flow control on chip mask value
+#define MOXA_MUST_EFR_SF_MASK 0x0F
+// send Xon1/Xoff1
+#define MOXA_MUST_EFR_SF_TX1 0x08
+// send Xon2/Xoff2
+#define MOXA_MUST_EFR_SF_TX2 0x04
+// send Xon1,Xon2/Xoff1,Xoff2
+#define MOXA_MUST_EFR_SF_TX12 0x0C
+// don't send Xon/Xoff
+#define MOXA_MUST_EFR_SF_TX_NO 0x00
+// Tx software flow control mask
+#define MOXA_MUST_EFR_SF_TX_MASK 0x0C
+// don't receive Xon/Xoff
+#define MOXA_MUST_EFR_SF_RX_NO 0x00
+// receive Xon1/Xoff1
+#define MOXA_MUST_EFR_SF_RX1 0x02
+// receive Xon2/Xoff2
+#define MOXA_MUST_EFR_SF_RX2 0x01
+// receive Xon1,Xon2/Xoff1,Xoff2
+#define MOXA_MUST_EFR_SF_RX12 0x03
+// Rx software flow control mask
+#define MOXA_MUST_EFR_SF_RX_MASK 0x03
+
+//#define MOXA_MUST_MIN_XOFFLIMIT 66
+//#define MOXA_MUST_MIN_XONLIMIT 20
+//#define ID1_RX_TRIG 120
+
+
+#define CHECK_MOXA_MUST_XOFFLIMIT(info) { \
+ if ( (info)->IsMoxaMustChipFlag && \
+ (info)->HandFlow.XoffLimit < MOXA_MUST_MIN_XOFFLIMIT ) { \
+ (info)->HandFlow.XoffLimit = MOXA_MUST_MIN_XOFFLIMIT; \
+ (info)->HandFlow.XonLimit = MOXA_MUST_MIN_XONLIMIT; \
+ } \
+}
+
+#define ENABLE_MOXA_MUST_ENCHANCE_MODE(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr |= MOXA_MUST_EFR_EFRB_ENABLE; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define DISABLE_MOXA_MUST_ENCHANCE_MODE(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_EFRB_ENABLE; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_XON1_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK0; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_XON1_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_XON2_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK0; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_XON2_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_XOFF1_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK0; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_XOFF1_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_XOFF2_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK0; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_XOFF2_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_RBRTL_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_RBRTL_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_RBRTH_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_RBRTH_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_RBRTI_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_RBRTI_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_THRTL_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_THRTL_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+//#define MOXA_MUST_RBRL_VALUE 4
+#define SET_MOXA_MUST_FIFO_VALUE(info) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((info)->base+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (info)->base+UART_LCR); \
+ __efr = inb((info)->base+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK1; \
+ outb(__efr, (info)->base+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)((info)->rx_high_water), (info)->base+MOXA_MUST_RBRTH_REGISTER); \
+ outb((u8)((info)->rx_trigger), (info)->base+MOXA_MUST_RBRTI_REGISTER); \
+ outb((u8)((info)->rx_low_water), (info)->base+MOXA_MUST_RBRTL_REGISTER); \
+ outb(__oldlcr, (info)->base+UART_LCR); \
+}
+
+
+
+#define SET_MOXA_MUST_ENUM_VALUE(baseio, Value) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK2; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb((u8)(Value), (baseio)+MOXA_MUST_ENUM_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define GET_MOXA_MUST_HARDWARE_ID(baseio, pId) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_BANK_MASK; \
+ __efr |= MOXA_MUST_EFR_BANK2; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ *pId = inb((baseio)+MOXA_MUST_HWID_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_MASK; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_JUST_TX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_MASK; \
+ __efr |= MOXA_MUST_EFR_SF_TX1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define ENABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_TX_MASK; \
+ __efr |= MOXA_MUST_EFR_SF_TX1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define DISABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_TX_MASK; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define SET_MOXA_MUST_JUST_RX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_MASK; \
+ __efr |= MOXA_MUST_EFR_SF_RX1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define ENABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_RX_MASK; \
+ __efr |= MOXA_MUST_EFR_SF_RX1; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_RX_MASK; \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define ENABLE_MOXA_MUST_TX_RX_SOFTWARE_FLOW_CONTROL(baseio) { \
+ u8 __oldlcr, __efr; \
+ __oldlcr = inb((baseio)+UART_LCR); \
+ outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR); \
+ __efr = inb((baseio)+MOXA_MUST_EFR_REGISTER); \
+ __efr &= ~MOXA_MUST_EFR_SF_MASK; \
+ __efr |= (MOXA_MUST_EFR_SF_RX1|MOXA_MUST_EFR_SF_TX1); \
+ outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER); \
+ outb(__oldlcr, (baseio)+UART_LCR); \
+}
+
+#define ENABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(baseio) { \
+ u8 __oldmcr; \
+ __oldmcr = inb((baseio)+UART_MCR); \
+ __oldmcr |= MOXA_MUST_MCR_XON_ANY; \
+ outb(__oldmcr, (baseio)+UART_MCR); \
+}
+
+#define DISABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(baseio) { \
+ u8 __oldmcr; \
+ __oldmcr = inb((baseio)+UART_MCR); \
+ __oldmcr &= ~MOXA_MUST_MCR_XON_ANY; \
+ outb(__oldmcr, (baseio)+UART_MCR); \
+}
+
+#define READ_MOXA_MUST_GDL(baseio) inb((baseio)+MOXA_MUST_GDL_REGISTER)
+
+
+#ifndef INIT_WORK
+#define INIT_WORK(_work, _func, _data){ \
+ _data->tqueue.routine = _func;\
+ _data->tqueue.data = _data;\
+ }
+#endif
+
+#endif
diff --git a/drivers/char/n_hdlc.c b/drivers/char/n_hdlc.c
new file mode 100644
index 000000000000..b3dbff1cf967
--- /dev/null
+++ b/drivers/char/n_hdlc.c
@@ -0,0 +1,978 @@
+/* generic HDLC line discipline for Linux
+ *
+ * Written by Paul Fulghum paulkf@microgate.com
+ * for Microgate Corporation
+ *
+ * Microgate and SyncLink are registered trademarks of Microgate Corporation
+ *
+ * Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>,
+ * Al Longyear <longyear@netcom.com>,
+ * Paul Mackerras <Paul.Mackerras@cs.anu.edu.au>
+ *
+ * Original release 01/11/99
+ * $Id: n_hdlc.c,v 4.8 2003/05/06 21:18:51 paulkf Exp $
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * This module implements the tty line discipline N_HDLC for use with
+ * tty device drivers that support bit-synchronous HDLC communications.
+ *
+ * All HDLC data is frame oriented which means:
+ *
+ * 1. tty write calls represent one complete transmit frame of data
+ * The device driver should accept the complete frame or none of
+ * the frame (busy) in the write method. Each write call should have
+ * a byte count in the range of 2-65535 bytes (2 is min HDLC frame
+ * with 1 addr byte and 1 ctrl byte). The max byte count of 65535
+ * should include any crc bytes required. For example, when using
+ * CCITT CRC32, 4 crc bytes are required, so the maximum size frame
+ * the application may transmit is limited to 65531 bytes. For CCITT
+ * CRC16, the maximum application frame size would be 65533.
+ *
+ *
+ * 2. receive callbacks from the device driver represents
+ * one received frame. The device driver should bypass
+ * the tty flip buffer and call the line discipline receive
+ * callback directly to avoid fragmenting or concatenating
+ * multiple frames into a single receive callback.
+ *
+ * The HDLC line discipline queues the receive frames in separate
+ * buffers so complete receive frames can be returned by the
+ * tty read calls.
+ *
+ * 3. tty read calls returns an entire frame of data or nothing.
+ *
+ * 4. all send and receive data is considered raw. No processing
+ * or translation is performed by the line discipline, regardless
+ * of the tty flags
+ *
+ * 5. When line discipline is queried for the amount of receive
+ * data available (FIOC), 0 is returned if no data available,
+ * otherwise the count of the next available frame is returned.
+ * (instead of the sum of all received frame counts).
+ *
+ * These conventions allow the standard tty programming interface
+ * to be used for synchronous HDLC applications when used with
+ * this line discipline (or another line discipline that is frame
+ * oriented such as N_PPP).
+ *
+ * The SyncLink driver (synclink.c) implements both asynchronous
+ * (using standard line discipline N_TTY) and synchronous HDLC
+ * (using N_HDLC) communications, with the latter using the above
+ * conventions.
+ *
+ * This implementation is very basic and does not maintain
+ * any statistics. The main point is to enforce the raw data
+ * and frame orientation of HDLC communications.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define HDLC_MAGIC 0x239e
+#define HDLC_VERSION "$Revision: 4.8 $"
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+
+#undef VERSION
+#define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch))
+
+#include <linux/poll.h>
+#include <linux/in.h>
+#include <linux/ioctl.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h> /* used in new tty drivers */
+#include <linux/signal.h> /* used in new tty drivers */
+#include <linux/if.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/termios.h>
+#include <asm/uaccess.h>
+
+/*
+ * Buffers for individual HDLC frames
+ */
+#define MAX_HDLC_FRAME_SIZE 65535
+#define DEFAULT_RX_BUF_COUNT 10
+#define MAX_RX_BUF_COUNT 60
+#define DEFAULT_TX_BUF_COUNT 1
+
+struct n_hdlc_buf {
+ struct n_hdlc_buf *link;
+ int count;
+ char buf[1];
+};
+
+#define N_HDLC_BUF_SIZE (sizeof(struct n_hdlc_buf) + maxframe)
+
+struct n_hdlc_buf_list {
+ struct n_hdlc_buf *head;
+ struct n_hdlc_buf *tail;
+ int count;
+ spinlock_t spinlock;
+};
+
+/**
+ * struct n_hdlc - per device instance data structure
+ * @magic - magic value for structure
+ * @flags - miscellaneous control flags
+ * @tty - ptr to TTY structure
+ * @backup_tty - TTY to use if tty gets closed
+ * @tbusy - reentrancy flag for tx wakeup code
+ * @woke_up - FIXME: describe this field
+ * @tbuf - currently transmitting tx buffer
+ * @tx_buf_list - list of pending transmit frame buffers
+ * @rx_buf_list - list of received frame buffers
+ * @tx_free_buf_list - list unused transmit frame buffers
+ * @rx_free_buf_list - list unused received frame buffers
+ */
+struct n_hdlc {
+ int magic;
+ __u32 flags;
+ struct tty_struct *tty;
+ struct tty_struct *backup_tty;
+ int tbusy;
+ int woke_up;
+ struct n_hdlc_buf *tbuf;
+ struct n_hdlc_buf_list tx_buf_list;
+ struct n_hdlc_buf_list rx_buf_list;
+ struct n_hdlc_buf_list tx_free_buf_list;
+ struct n_hdlc_buf_list rx_free_buf_list;
+};
+
+/*
+ * HDLC buffer list manipulation functions
+ */
+static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list);
+static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
+ struct n_hdlc_buf *buf);
+static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list);
+
+/* Local functions */
+
+static struct n_hdlc *n_hdlc_alloc (void);
+
+/* debug level can be set by insmod for debugging purposes */
+#define DEBUG_LEVEL_INFO 1
+static int debuglevel;
+
+/* max frame size for memory allocations */
+static int maxframe = 4096;
+
+/* TTY callbacks */
+
+static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
+ __u8 __user *buf, size_t nr);
+static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *buf, size_t nr);
+static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
+ poll_table *wait);
+static int n_hdlc_tty_open(struct tty_struct *tty);
+static void n_hdlc_tty_close(struct tty_struct *tty);
+static int n_hdlc_tty_room(struct tty_struct *tty);
+static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *cp,
+ char *fp, int count);
+static void n_hdlc_tty_wakeup(struct tty_struct *tty);
+
+#define bset(p,b) ((p)[(b) >> 5] |= (1 << ((b) & 0x1f)))
+
+#define tty2n_hdlc(tty) ((struct n_hdlc *) ((tty)->disc_data))
+#define n_hdlc2tty(n_hdlc) ((n_hdlc)->tty)
+
+static struct tty_ldisc n_hdlc_ldisc = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "hdlc",
+ .open = n_hdlc_tty_open,
+ .close = n_hdlc_tty_close,
+ .read = n_hdlc_tty_read,
+ .write = n_hdlc_tty_write,
+ .ioctl = n_hdlc_tty_ioctl,
+ .poll = n_hdlc_tty_poll,
+ .receive_buf = n_hdlc_tty_receive,
+ .receive_room = n_hdlc_tty_room,
+ .write_wakeup = n_hdlc_tty_wakeup,
+};
+
+/**
+ * n_hdlc_release - release an n_hdlc per device line discipline info structure
+ * @n_hdlc - per device line discipline info structure
+ */
+static void n_hdlc_release(struct n_hdlc *n_hdlc)
+{
+ struct tty_struct *tty = n_hdlc2tty (n_hdlc);
+ struct n_hdlc_buf *buf;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_release() called\n",__FILE__,__LINE__);
+
+ /* Ensure that the n_hdlcd process is not hanging on select()/poll() */
+ wake_up_interruptible (&tty->read_wait);
+ wake_up_interruptible (&tty->write_wait);
+
+ if (tty != NULL && tty->disc_data == n_hdlc)
+ tty->disc_data = NULL; /* Break the tty->n_hdlc link */
+
+ /* Release transmit and receive buffers */
+ for(;;) {
+ buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
+ if (buf) {
+ kfree(buf);
+ } else
+ break;
+ }
+ for(;;) {
+ buf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list);
+ if (buf) {
+ kfree(buf);
+ } else
+ break;
+ }
+ for(;;) {
+ buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
+ if (buf) {
+ kfree(buf);
+ } else
+ break;
+ }
+ for(;;) {
+ buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+ if (buf) {
+ kfree(buf);
+ } else
+ break;
+ }
+ if (n_hdlc->tbuf)
+ kfree(n_hdlc->tbuf);
+ kfree(n_hdlc);
+
+} /* end of n_hdlc_release() */
+
+/**
+ * n_hdlc_tty_close - line discipline close
+ * @tty - pointer to tty info structure
+ *
+ * Called when the line discipline is changed to something
+ * else, the tty is closed, or the tty detects a hangup.
+ */
+static void n_hdlc_tty_close(struct tty_struct *tty)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_close() called\n",__FILE__,__LINE__);
+
+ if (n_hdlc != NULL) {
+ if (n_hdlc->magic != HDLC_MAGIC) {
+ printk (KERN_WARNING"n_hdlc: trying to close unopened tty!\n");
+ return;
+ }
+#if defined(TTY_NO_WRITE_SPLIT)
+ clear_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
+#endif
+ tty->disc_data = NULL;
+ if (tty == n_hdlc->backup_tty)
+ n_hdlc->backup_tty = NULL;
+ if (tty != n_hdlc->tty)
+ return;
+ if (n_hdlc->backup_tty) {
+ n_hdlc->tty = n_hdlc->backup_tty;
+ } else {
+ n_hdlc_release (n_hdlc);
+ }
+ }
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_close() success\n",__FILE__,__LINE__);
+
+} /* end of n_hdlc_tty_close() */
+
+/**
+ * n_hdlc_tty_open - called when line discipline changed to n_hdlc
+ * @tty - pointer to tty info structure
+ *
+ * Returns 0 if success, otherwise error code
+ */
+static int n_hdlc_tty_open (struct tty_struct *tty)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_open() called (device=%s)\n",
+ __FILE__,__LINE__,
+ tty->name);
+
+ /* There should not be an existing table for this slot. */
+ if (n_hdlc) {
+ printk (KERN_ERR"n_hdlc_tty_open:tty already associated!\n" );
+ return -EEXIST;
+ }
+
+ n_hdlc = n_hdlc_alloc();
+ if (!n_hdlc) {
+ printk (KERN_ERR "n_hdlc_alloc failed\n");
+ return -ENFILE;
+ }
+
+ tty->disc_data = n_hdlc;
+ n_hdlc->tty = tty;
+
+#if defined(TTY_NO_WRITE_SPLIT)
+ /* change tty_io write() to not split large writes into 8K chunks */
+ set_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
+#endif
+
+ /* Flush any pending characters in the driver and discipline. */
+
+ if (tty->ldisc.flush_buffer)
+ tty->ldisc.flush_buffer (tty);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer (tty);
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_open() success\n",__FILE__,__LINE__);
+
+ return 0;
+
+} /* end of n_tty_hdlc_open() */
+
+/**
+ * n_hdlc_send_frames - send frames on pending send buffer list
+ * @n_hdlc - pointer to ldisc instance data
+ * @tty - pointer to tty instance data
+ *
+ * Send frames on pending send buffer list until the driver does not accept a
+ * frame (busy) this function is called after adding a frame to the send buffer
+ * list and by the tty wakeup callback.
+ */
+static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
+{
+ register int actual;
+ unsigned long flags;
+ struct n_hdlc_buf *tbuf;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_send_frames() called\n",__FILE__,__LINE__);
+ check_again:
+
+ spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
+ if (n_hdlc->tbusy) {
+ n_hdlc->woke_up = 1;
+ spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
+ return;
+ }
+ n_hdlc->tbusy = 1;
+ n_hdlc->woke_up = 0;
+ spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
+
+ /* get current transmit buffer or get new transmit */
+ /* buffer from list of pending transmit buffers */
+
+ tbuf = n_hdlc->tbuf;
+ if (!tbuf)
+ tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+
+ while (tbuf) {
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)sending frame %p, count=%d\n",
+ __FILE__,__LINE__,tbuf,tbuf->count);
+
+ /* Send the next block of data to device */
+ tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+ actual = tty->driver->write(tty, tbuf->buf, tbuf->count);
+
+ /* if transmit error, throw frame away by */
+ /* pretending it was accepted by driver */
+ if (actual < 0)
+ actual = tbuf->count;
+
+ if (actual == tbuf->count) {
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)frame %p completed\n",
+ __FILE__,__LINE__,tbuf);
+
+ /* free current transmit buffer */
+ n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf);
+
+ /* this tx buffer is done */
+ n_hdlc->tbuf = NULL;
+
+ /* wait up sleeping writers */
+ wake_up_interruptible(&tty->write_wait);
+
+ /* get next pending transmit buffer */
+ tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+ } else {
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)frame %p pending\n",
+ __FILE__,__LINE__,tbuf);
+
+ /* buffer not accepted by driver */
+ /* set this buffer as pending buffer */
+ n_hdlc->tbuf = tbuf;
+ break;
+ }
+ }
+
+ if (!tbuf)
+ tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+
+ /* Clear the re-entry flag */
+ spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
+ n_hdlc->tbusy = 0;
+ spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
+
+ if (n_hdlc->woke_up)
+ goto check_again;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_send_frames() exit\n",__FILE__,__LINE__);
+
+} /* end of n_hdlc_send_frames() */
+
+/**
+ * n_hdlc_tty_wakeup - Callback for transmit wakeup
+ * @tty - pointer to associated tty instance data
+ *
+ * Called when low level device driver can accept more send data.
+ */
+static void n_hdlc_tty_wakeup(struct tty_struct *tty)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc(tty);
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_wakeup() called\n",__FILE__,__LINE__);
+
+ if (!n_hdlc)
+ return;
+
+ if (tty != n_hdlc->tty) {
+ tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+ return;
+ }
+
+ n_hdlc_send_frames (n_hdlc, tty);
+
+} /* end of n_hdlc_tty_wakeup() */
+
+/**
+ * n_hdlc_tty_room - Return the amount of space left in the receiver's buffer
+ * @tty - pointer to associated tty instance data
+ *
+ * Callback function from tty driver. Return the amount of space left in the
+ * receiver's buffer to decide if remote transmitter is to be throttled.
+ */
+static int n_hdlc_tty_room(struct tty_struct *tty)
+{
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_room() called\n",__FILE__,__LINE__);
+ /* always return a larger number to prevent */
+ /* throttling of remote transmitter. */
+ return 65536;
+} /* end of n_hdlc_tty_root() */
+
+/**
+ * n_hdlc_tty_receive - Called by tty driver when receive data is available
+ * @tty - pointer to tty instance data
+ * @data - pointer to received data
+ * @flags - pointer to flags for data
+ * @count - count of received data in bytes
+ *
+ * Called by tty low level driver when receive data is available. Data is
+ * interpreted as one HDLC frame.
+ */
+static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *data,
+ char *flags, int count)
+{
+ register struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+ register struct n_hdlc_buf *buf;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_receive() called count=%d\n",
+ __FILE__,__LINE__, count);
+
+ /* This can happen if stuff comes in on the backup tty */
+ if (n_hdlc == 0 || tty != n_hdlc->tty)
+ return;
+
+ /* verify line is using HDLC discipline */
+ if (n_hdlc->magic != HDLC_MAGIC) {
+ printk("%s(%d) line not using HDLC discipline\n",
+ __FILE__,__LINE__);
+ return;
+ }
+
+ if ( count>maxframe ) {
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d) rx count>maxframesize, data discarded\n",
+ __FILE__,__LINE__);
+ return;
+ }
+
+ /* get a free HDLC buffer */
+ buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
+ if (!buf) {
+ /* no buffers in free list, attempt to allocate another rx buffer */
+ /* unless the maximum count has been reached */
+ if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT)
+ buf = kmalloc(N_HDLC_BUF_SIZE, GFP_ATOMIC);
+ }
+
+ if (!buf) {
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d) no more rx buffers, data discarded\n",
+ __FILE__,__LINE__);
+ return;
+ }
+
+ /* copy received data to HDLC buffer */
+ memcpy(buf->buf,data,count);
+ buf->count=count;
+
+ /* add HDLC buffer to list of received frames */
+ n_hdlc_buf_put(&n_hdlc->rx_buf_list, buf);
+
+ /* wake up any blocked reads and perform async signalling */
+ wake_up_interruptible (&tty->read_wait);
+ if (n_hdlc->tty->fasync != NULL)
+ kill_fasync (&n_hdlc->tty->fasync, SIGIO, POLL_IN);
+
+} /* end of n_hdlc_tty_receive() */
+
+/**
+ * n_hdlc_tty_read - Called to retreive one frame of data (if available)
+ * @tty - pointer to tty instance data
+ * @file - pointer to open file object
+ * @buf - pointer to returned data buffer
+ * @nr - size of returned data buffer
+ *
+ * Returns the number of bytes returned or error code.
+ */
+static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
+ __u8 __user *buf, size_t nr)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc(tty);
+ int ret;
+ struct n_hdlc_buf *rbuf;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_read() called\n",__FILE__,__LINE__);
+
+ /* Validate the pointers */
+ if (!n_hdlc)
+ return -EIO;
+
+ /* verify user access to buffer */
+ if (!access_ok(VERIFY_WRITE, buf, nr)) {
+ printk(KERN_WARNING "%s(%d) n_hdlc_tty_read() can't verify user "
+ "buffer\n", __FILE__, __LINE__);
+ return -EFAULT;
+ }
+
+ for (;;) {
+ if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+ return -EIO;
+
+ n_hdlc = tty2n_hdlc (tty);
+ if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC ||
+ tty != n_hdlc->tty)
+ return 0;
+
+ rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
+ if (rbuf)
+ break;
+
+ /* no data */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ interruptible_sleep_on (&tty->read_wait);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+
+ if (rbuf->count > nr)
+ /* frame too large for caller's buffer (discard frame) */
+ ret = -EOVERFLOW;
+ else {
+ /* Copy the data to the caller's buffer */
+ if (copy_to_user(buf, rbuf->buf, rbuf->count))
+ ret = -EFAULT;
+ else
+ ret = rbuf->count;
+ }
+
+ /* return HDLC buffer to free list unless the free list */
+ /* count has exceeded the default value, in which case the */
+ /* buffer is freed back to the OS to conserve memory */
+ if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT)
+ kfree(rbuf);
+ else
+ n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,rbuf);
+
+ return ret;
+
+} /* end of n_hdlc_tty_read() */
+
+/**
+ * n_hdlc_tty_write - write a single frame of data to device
+ * @tty - pointer to associated tty device instance data
+ * @file - pointer to file object data
+ * @data - pointer to transmit data (one frame)
+ * @count - size of transmit frame in bytes
+ *
+ * Returns the number of bytes written (or error code).
+ */
+static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *data, size_t count)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+ int error = 0;
+ DECLARE_WAITQUEUE(wait, current);
+ struct n_hdlc_buf *tbuf;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_write() called count=%Zd\n",
+ __FILE__,__LINE__,count);
+
+ /* Verify pointers */
+ if (!n_hdlc)
+ return -EIO;
+
+ if (n_hdlc->magic != HDLC_MAGIC)
+ return -EIO;
+
+ /* verify frame size */
+ if (count > maxframe ) {
+ if (debuglevel & DEBUG_LEVEL_INFO)
+ printk (KERN_WARNING
+ "n_hdlc_tty_write: truncating user packet "
+ "from %lu to %d\n", (unsigned long) count,
+ maxframe );
+ count = maxframe;
+ }
+
+ add_wait_queue(&tty->write_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ /* Allocate transmit buffer */
+ /* sleep until transmit buffer available */
+ while (!(tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list))) {
+ schedule();
+
+ n_hdlc = tty2n_hdlc (tty);
+ if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC ||
+ tty != n_hdlc->tty) {
+ printk("n_hdlc_tty_write: %p invalid after wait!\n", n_hdlc);
+ error = -EIO;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ error = -EINTR;
+ break;
+ }
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&tty->write_wait, &wait);
+
+ if (!error) {
+ /* Retrieve the user's buffer */
+ memcpy(tbuf->buf, data, count);
+
+ /* Send the data */
+ tbuf->count = error = count;
+ n_hdlc_buf_put(&n_hdlc->tx_buf_list,tbuf);
+ n_hdlc_send_frames(n_hdlc,tty);
+ }
+
+ return error;
+
+} /* end of n_hdlc_tty_write() */
+
+/**
+ * n_hdlc_tty_ioctl - process IOCTL system call for the tty device.
+ * @tty - pointer to tty instance data
+ * @file - pointer to open file object for device
+ * @cmd - IOCTL command code
+ * @arg - argument for IOCTL call (cmd dependent)
+ *
+ * Returns command dependent result.
+ */
+static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+ int error = 0;
+ int count;
+ unsigned long flags;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_ioctl() called %d\n",
+ __FILE__,__LINE__,cmd);
+
+ /* Verify the status of the device */
+ if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC)
+ return -EBADF;
+
+ switch (cmd) {
+ case FIONREAD:
+ /* report count of read data available */
+ /* in next available frame (if any) */
+ spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock,flags);
+ if (n_hdlc->rx_buf_list.head)
+ count = n_hdlc->rx_buf_list.head->count;
+ else
+ count = 0;
+ spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags);
+ error = put_user(count, (int __user *)arg);
+ break;
+
+ case TIOCOUTQ:
+ /* get the pending tx byte count in the driver */
+ count = tty->driver->chars_in_buffer ?
+ tty->driver->chars_in_buffer(tty) : 0;
+ /* add size of next output frame in queue */
+ spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags);
+ if (n_hdlc->tx_buf_list.head)
+ count += n_hdlc->tx_buf_list.head->count;
+ spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags);
+ error = put_user(count, (int __user *)arg);
+ break;
+
+ default:
+ error = n_tty_ioctl (tty, file, cmd, arg);
+ break;
+ }
+ return error;
+
+} /* end of n_hdlc_tty_ioctl() */
+
+/**
+ * n_hdlc_tty_poll - TTY callback for poll system call
+ * @tty - pointer to tty instance data
+ * @filp - pointer to open file object for device
+ * @poll_table - wait queue for operations
+ *
+ * Determine which operations (read/write) will not block and return info
+ * to caller.
+ * Returns a bit mask containing info on which ops will not block.
+ */
+static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
+ poll_table *wait)
+{
+ struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+ unsigned int mask = 0;
+
+ if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_tty_poll() called\n",__FILE__,__LINE__);
+
+ if (n_hdlc && n_hdlc->magic == HDLC_MAGIC && tty == n_hdlc->tty) {
+ /* queue current process into any wait queue that */
+ /* may awaken in the future (read and write) */
+
+ poll_wait(filp, &tty->read_wait, wait);
+ poll_wait(filp, &tty->write_wait, wait);
+
+ /* set bits for operations that won't block */
+ if(n_hdlc->rx_buf_list.head)
+ mask |= POLLIN | POLLRDNORM; /* readable */
+ if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+ mask |= POLLHUP;
+ if(tty_hung_up_p(filp))
+ mask |= POLLHUP;
+ if(n_hdlc->tx_free_buf_list.head)
+ mask |= POLLOUT | POLLWRNORM; /* writable */
+ }
+ return mask;
+} /* end of n_hdlc_tty_poll() */
+
+/**
+ * n_hdlc_alloc - allocate an n_hdlc instance data structure
+ *
+ * Returns a pointer to newly created structure if success, otherwise %NULL
+ */
+static struct n_hdlc *n_hdlc_alloc(void)
+{
+ struct n_hdlc_buf *buf;
+ int i;
+ struct n_hdlc *n_hdlc = kmalloc(sizeof(*n_hdlc), GFP_KERNEL);
+
+ if (!n_hdlc)
+ return NULL;
+
+ memset(n_hdlc, 0, sizeof(*n_hdlc));
+
+ n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list);
+ n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list);
+ n_hdlc_buf_list_init(&n_hdlc->rx_buf_list);
+ n_hdlc_buf_list_init(&n_hdlc->tx_buf_list);
+
+ /* allocate free rx buffer list */
+ for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) {
+ buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL);
+ if (buf)
+ n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,buf);
+ else if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_alloc(), kalloc() failed for rx buffer %d\n",__FILE__,__LINE__, i);
+ }
+
+ /* allocate free tx buffer list */
+ for(i=0;i<DEFAULT_TX_BUF_COUNT;i++) {
+ buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL);
+ if (buf)
+ n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,buf);
+ else if (debuglevel >= DEBUG_LEVEL_INFO)
+ printk("%s(%d)n_hdlc_alloc(), kalloc() failed for tx buffer %d\n",__FILE__,__LINE__, i);
+ }
+
+ /* Initialize the control block */
+ n_hdlc->magic = HDLC_MAGIC;
+ n_hdlc->flags = 0;
+
+ return n_hdlc;
+
+} /* end of n_hdlc_alloc() */
+
+/**
+ * n_hdlc_buf_list_init - initialize specified HDLC buffer list
+ * @list - pointer to buffer list
+ */
+static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list)
+{
+ memset(list, 0, sizeof(*list));
+ spin_lock_init(&list->spinlock);
+} /* end of n_hdlc_buf_list_init() */
+
+/**
+ * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list
+ * @list - pointer to buffer list
+ * @buf - pointer to buffer
+ */
+static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
+ struct n_hdlc_buf *buf)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&list->spinlock,flags);
+
+ buf->link=NULL;
+ if(list->tail)
+ list->tail->link = buf;
+ else
+ list->head = buf;
+ list->tail = buf;
+ (list->count)++;
+
+ spin_unlock_irqrestore(&list->spinlock,flags);
+
+} /* end of n_hdlc_buf_put() */
+
+/**
+ * n_hdlc_buf_get - remove and return an HDLC buffer from list
+ * @list - pointer to HDLC buffer list
+ *
+ * Remove and return an HDLC buffer from the head of the specified HDLC buffer
+ * list.
+ * Returns a pointer to HDLC buffer if available, otherwise %NULL.
+ */
+static struct n_hdlc_buf* n_hdlc_buf_get(struct n_hdlc_buf_list *list)
+{
+ unsigned long flags;
+ struct n_hdlc_buf *buf;
+ spin_lock_irqsave(&list->spinlock,flags);
+
+ buf = list->head;
+ if (buf) {
+ list->head = buf->link;
+ (list->count)--;
+ }
+ if (!list->head)
+ list->tail = NULL;
+
+ spin_unlock_irqrestore(&list->spinlock,flags);
+ return buf;
+
+} /* end of n_hdlc_buf_get() */
+
+static char hdlc_banner[] __initdata =
+ KERN_INFO "HDLC line discipline: version " HDLC_VERSION
+ ", maxframe=%u\n";
+static char hdlc_register_ok[] __initdata =
+ KERN_INFO "N_HDLC line discipline registered.\n";
+static char hdlc_register_fail[] __initdata =
+ KERN_ERR "error registering line discipline: %d\n";
+static char hdlc_init_fail[] __initdata =
+ KERN_INFO "N_HDLC: init failure %d\n";
+
+static int __init n_hdlc_init(void)
+{
+ int status;
+
+ /* range check maxframe arg */
+ if (maxframe < 4096)
+ maxframe = 4096;
+ else if (maxframe > 65535)
+ maxframe = 65535;
+
+ printk(hdlc_banner, maxframe);
+
+ status = tty_register_ldisc(N_HDLC, &n_hdlc_ldisc);
+ if (!status)
+ printk(hdlc_register_ok);
+ else
+ printk(hdlc_register_fail, status);
+
+ if (status)
+ printk(hdlc_init_fail, status);
+ return status;
+
+} /* end of init_module() */
+
+static char hdlc_unregister_ok[] __exitdata =
+ KERN_INFO "N_HDLC: line discipline unregistered\n";
+static char hdlc_unregister_fail[] __exitdata =
+ KERN_ERR "N_HDLC: can't unregister line discipline (err = %d)\n";
+
+static void __exit n_hdlc_exit(void)
+{
+ /* Release tty registration of line discipline */
+ int status = tty_register_ldisc(N_HDLC, NULL);
+
+ if (status)
+ printk(hdlc_unregister_fail, status);
+ else
+ printk(hdlc_unregister_ok);
+}
+
+module_init(n_hdlc_init);
+module_exit(n_hdlc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Paul Fulghum paulkf@microgate.com");
+module_param(debuglevel, int, 0);
+module_param(maxframe, int, 0);
+MODULE_ALIAS_LDISC(N_HDLC);
diff --git a/drivers/char/n_r3964.c b/drivers/char/n_r3964.c
new file mode 100644
index 000000000000..3883073ab48f
--- /dev/null
+++ b/drivers/char/n_r3964.c
@@ -0,0 +1,1416 @@
+/* r3964 linediscipline for linux
+ *
+ * -----------------------------------------------------------
+ * Copyright by
+ * Philips Automation Projects
+ * Kassel (Germany)
+ * http://www.pap-philips.de
+ * -----------------------------------------------------------
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ *
+ * Author:
+ * L. Haag
+ *
+ * $Log: n_r3964.c,v $
+ * Revision 1.10 2001/03/18 13:02:24 dwmw2
+ * Fix timer usage, use spinlocks properly.
+ *
+ * Revision 1.9 2001/03/18 12:52:14 dwmw2
+ * Merge changes in 2.4.2
+ *
+ * Revision 1.8 2000/03/23 14:14:54 dwmw2
+ * Fix race in sleeping in r3964_read()
+ *
+ * Revision 1.7 1999/28/08 11:41:50 dwmw2
+ * Port to 2.3 kernel
+ *
+ * Revision 1.6 1998/09/30 00:40:40 dwmw2
+ * Fixed compilation on 2.0.x kernels
+ * Updated to newly registered tty-ldisc number 9
+ *
+ * Revision 1.5 1998/09/04 21:57:36 dwmw2
+ * Signal handling bug fixes, port to 2.1.x.
+ *
+ * Revision 1.4 1998/04/02 20:26:59 lhaag
+ * select, blocking, ...
+ *
+ * Revision 1.3 1998/02/12 18:58:43 root
+ * fixed some memory leaks
+ * calculation of checksum characters
+ *
+ * Revision 1.2 1998/02/07 13:03:34 root
+ * ioctl read_telegram
+ *
+ * Revision 1.1 1998/02/06 19:21:03 root
+ * Initial revision
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h> /* used in new tty drivers */
+#include <linux/signal.h> /* used in new tty drivers */
+#include <linux/ioctl.h>
+#include <linux/n_r3964.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+
+//#define DEBUG_QUEUE
+
+/* Log successful handshake and protocol operations */
+//#define DEBUG_PROTO_S
+
+/* Log handshake and protocol errors: */
+//#define DEBUG_PROTO_E
+
+/* Log Linediscipline operations (open, close, read, write...): */
+//#define DEBUG_LDISC
+
+/* Log module and memory operations (init, cleanup; kmalloc, kfree): */
+//#define DEBUG_MODUL
+
+/* Macro helpers for debug output: */
+#define TRACE(format, args...) printk("r3964: " format "\n" , ## args);
+
+#ifdef DEBUG_MODUL
+#define TRACE_M(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_M(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_PROTO_S
+#define TRACE_PS(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_PS(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_PROTO_E
+#define TRACE_PE(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_PE(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_LDISC
+#define TRACE_L(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_L(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_QUEUE
+#define TRACE_Q(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_Q(fmt, arg...) /**/
+#endif
+
+static void add_tx_queue(struct r3964_info *, struct r3964_block_header *);
+static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code);
+static void put_char(struct r3964_info *pInfo, unsigned char ch);
+static void trigger_transmit(struct r3964_info *pInfo);
+static void retry_transmit(struct r3964_info *pInfo);
+static void transmit_block(struct r3964_info *pInfo);
+static void receive_char(struct r3964_info *pInfo, const unsigned char c);
+static void receive_error(struct r3964_info *pInfo, const char flag);
+static void on_timeout(unsigned long priv);
+static int enable_signals(struct r3964_info *pInfo, pid_t pid, int arg);
+static int read_telegram(struct r3964_info *pInfo, pid_t pid, unsigned char __user *buf);
+static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
+ int error_code, struct r3964_block_header *pBlock);
+static struct r3964_message* remove_msg(struct r3964_info *pInfo,
+ struct r3964_client_info *pClient);
+static void remove_client_block(struct r3964_info *pInfo,
+ struct r3964_client_info *pClient);
+
+static int r3964_open(struct tty_struct *tty);
+static void r3964_close(struct tty_struct *tty);
+static ssize_t r3964_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t nr);
+static ssize_t r3964_write(struct tty_struct * tty, struct file * file,
+ const unsigned char * buf, size_t nr);
+static int r3964_ioctl(struct tty_struct * tty, struct file * file,
+ unsigned int cmd, unsigned long arg);
+static void r3964_set_termios(struct tty_struct *tty, struct termios * old);
+static unsigned int r3964_poll(struct tty_struct * tty, struct file * file,
+ struct poll_table_struct *wait);
+static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+ char *fp, int count);
+static int r3964_receive_room(struct tty_struct *tty);
+
+static struct tty_ldisc tty_ldisc_N_R3964 = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "R3964",
+ .open = r3964_open,
+ .close = r3964_close,
+ .read = r3964_read,
+ .write = r3964_write,
+ .ioctl = r3964_ioctl,
+ .set_termios = r3964_set_termios,
+ .poll = r3964_poll,
+ .receive_buf = r3964_receive_buf,
+ .receive_room = r3964_receive_room,
+};
+
+
+
+static void dump_block(const unsigned char *block, unsigned int length)
+{
+ unsigned int i,j;
+ char linebuf[16*3+1];
+
+ for(i=0;i<length;i+=16)
+ {
+ for(j=0;(j<16) && (j+i<length);j++)
+ {
+ sprintf(linebuf+3*j,"%02x ",block[i+j]);
+ }
+ linebuf[3*j]='\0';
+ TRACE_PS("%s",linebuf);
+ }
+}
+
+
+
+
+/*************************************************************
+ * Driver initialisation
+ *************************************************************/
+
+
+/*************************************************************
+ * Module support routines
+ *************************************************************/
+
+static void __exit r3964_exit(void)
+{
+ int status;
+
+ TRACE_M ("cleanup_module()");
+
+ status=tty_register_ldisc(N_R3964, NULL);
+
+ if(status!=0)
+ {
+ printk(KERN_ERR "r3964: error unregistering linediscipline: %d\n", status);
+ }
+ else
+ {
+ TRACE_L("linediscipline successfully unregistered");
+ }
+
+}
+
+static int __init r3964_init(void)
+{
+ int status;
+
+ printk ("r3964: Philips r3964 Driver $Revision: 1.10 $\n");
+
+ /*
+ * Register the tty line discipline
+ */
+
+ status = tty_register_ldisc (N_R3964, &tty_ldisc_N_R3964);
+ if (status == 0)
+ {
+ TRACE_L("line discipline %d registered", N_R3964);
+ TRACE_L("flags=%x num=%x", tty_ldisc_N_R3964.flags,
+ tty_ldisc_N_R3964.num);
+ TRACE_L("open=%x", (int)tty_ldisc_N_R3964.open);
+ TRACE_L("tty_ldisc_N_R3964 = %x", (int)&tty_ldisc_N_R3964);
+ }
+ else
+ {
+ printk (KERN_ERR "r3964: error registering line discipline: %d\n", status);
+ }
+ return status;
+}
+
+module_init(r3964_init);
+module_exit(r3964_exit);
+
+
+/*************************************************************
+ * Protocol implementation routines
+ *************************************************************/
+
+static void add_tx_queue(struct r3964_info *pInfo, struct r3964_block_header *pHeader)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pInfo->lock, flags);
+
+ pHeader->next = NULL;
+
+ if(pInfo->tx_last == NULL)
+ {
+ pInfo->tx_first = pInfo->tx_last = pHeader;
+ }
+ else
+ {
+ pInfo->tx_last->next = pHeader;
+ pInfo->tx_last = pHeader;
+ }
+
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ TRACE_Q("add_tx_queue %x, length %d, tx_first = %x",
+ (int)pHeader, pHeader->length, (int)pInfo->tx_first );
+}
+
+static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code)
+{
+ struct r3964_block_header *pHeader;
+ unsigned long flags;
+#ifdef DEBUG_QUEUE
+ struct r3964_block_header *pDump;
+#endif
+
+ pHeader = pInfo->tx_first;
+
+ if(pHeader==NULL)
+ return;
+
+#ifdef DEBUG_QUEUE
+ printk("r3964: remove_from_tx_queue: %x, length %d - ",
+ (int)pHeader, (int)pHeader->length );
+ for(pDump=pHeader;pDump;pDump=pDump->next)
+ printk("%x ", (int)pDump);
+ printk("\n");
+#endif
+
+
+ if(pHeader->owner)
+ {
+ if(error_code)
+ {
+ add_msg(pHeader->owner, R3964_MSG_ACK, 0,
+ error_code, NULL);
+ }
+ else
+ {
+ add_msg(pHeader->owner, R3964_MSG_ACK, pHeader->length,
+ error_code, NULL);
+ }
+ wake_up_interruptible (&pInfo->read_wait);
+ }
+
+ spin_lock_irqsave(&pInfo->lock, flags);
+
+ pInfo->tx_first = pHeader->next;
+ if(pInfo->tx_first==NULL)
+ {
+ pInfo->tx_last = NULL;
+ }
+
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ kfree(pHeader);
+ TRACE_M("remove_from_tx_queue - kfree %x",(int)pHeader);
+
+ TRACE_Q("remove_from_tx_queue: tx_first = %x, tx_last = %x",
+ (int)pInfo->tx_first, (int)pInfo->tx_last );
+}
+
+static void add_rx_queue(struct r3964_info *pInfo, struct r3964_block_header *pHeader)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pInfo->lock, flags);
+
+ pHeader->next = NULL;
+
+ if(pInfo->rx_last == NULL)
+ {
+ pInfo->rx_first = pInfo->rx_last = pHeader;
+ }
+ else
+ {
+ pInfo->rx_last->next = pHeader;
+ pInfo->rx_last = pHeader;
+ }
+ pInfo->blocks_in_rx_queue++;
+
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ TRACE_Q("add_rx_queue: %x, length = %d, rx_first = %x, count = %d",
+ (int)pHeader, pHeader->length,
+ (int)pInfo->rx_first, pInfo->blocks_in_rx_queue);
+}
+
+static void remove_from_rx_queue(struct r3964_info *pInfo,
+ struct r3964_block_header *pHeader)
+{
+ unsigned long flags;
+ struct r3964_block_header *pFind;
+
+ if(pHeader==NULL)
+ return;
+
+ TRACE_Q("remove_from_rx_queue: rx_first = %x, rx_last = %x, count = %d",
+ (int)pInfo->rx_first, (int)pInfo->rx_last, pInfo->blocks_in_rx_queue );
+ TRACE_Q("remove_from_rx_queue: %x, length %d",
+ (int)pHeader, (int)pHeader->length );
+
+ spin_lock_irqsave(&pInfo->lock, flags);
+
+ if(pInfo->rx_first == pHeader)
+ {
+ /* Remove the first block in the linked list: */
+ pInfo->rx_first = pHeader->next;
+
+ if(pInfo->rx_first==NULL)
+ {
+ pInfo->rx_last = NULL;
+ }
+ pInfo->blocks_in_rx_queue--;
+ }
+ else
+ {
+ /* Find block to remove: */
+ for(pFind=pInfo->rx_first; pFind; pFind=pFind->next)
+ {
+ if(pFind->next == pHeader)
+ {
+ /* Got it. */
+ pFind->next = pHeader->next;
+ pInfo->blocks_in_rx_queue--;
+ if(pFind->next==NULL)
+ {
+ /* Oh, removed the last one! */
+ pInfo->rx_last = pFind;
+ }
+ break;
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ kfree(pHeader);
+ TRACE_M("remove_from_rx_queue - kfree %x",(int)pHeader);
+
+ TRACE_Q("remove_from_rx_queue: rx_first = %x, rx_last = %x, count = %d",
+ (int)pInfo->rx_first, (int)pInfo->rx_last, pInfo->blocks_in_rx_queue );
+}
+
+static void put_char(struct r3964_info *pInfo, unsigned char ch)
+{
+ struct tty_struct *tty = pInfo->tty;
+
+ if(tty==NULL)
+ return;
+
+ if(tty->driver->put_char)
+ {
+ tty->driver->put_char(tty, ch);
+ }
+ pInfo->bcc ^= ch;
+}
+
+static void flush(struct r3964_info *pInfo)
+{
+ struct tty_struct *tty = pInfo->tty;
+
+ if(tty==NULL)
+ return;
+
+ if(tty->driver->flush_chars)
+ {
+ tty->driver->flush_chars(tty);
+ }
+}
+
+static void trigger_transmit(struct r3964_info *pInfo)
+{
+ unsigned long flags;
+
+
+ spin_lock_irqsave(&pInfo->lock, flags);
+
+ if((pInfo->state == R3964_IDLE) && (pInfo->tx_first!=NULL))
+ {
+ pInfo->state = R3964_TX_REQUEST;
+ pInfo->nRetry=0;
+ pInfo->flags &= ~R3964_ERROR;
+ mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ TRACE_PS("trigger_transmit - sent STX");
+
+ put_char(pInfo, STX);
+ flush(pInfo);
+
+ pInfo->bcc = 0;
+ }
+ else
+ {
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+ }
+}
+
+static void retry_transmit(struct r3964_info *pInfo)
+{
+ if(pInfo->nRetry<R3964_MAX_RETRIES)
+ {
+ TRACE_PE("transmission failed. Retry #%d",
+ pInfo->nRetry);
+ pInfo->bcc = 0;
+ put_char(pInfo, STX);
+ flush(pInfo);
+ pInfo->state = R3964_TX_REQUEST;
+ pInfo->nRetry++;
+ mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+ }
+ else
+ {
+ TRACE_PE("transmission failed after %d retries",
+ R3964_MAX_RETRIES);
+
+ remove_from_tx_queue(pInfo, R3964_TX_FAIL);
+
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ pInfo->state = R3964_IDLE;
+
+ trigger_transmit(pInfo);
+ }
+}
+
+
+static void transmit_block(struct r3964_info *pInfo)
+{
+ struct tty_struct *tty = pInfo->tty;
+ struct r3964_block_header *pBlock = pInfo->tx_first;
+ int room=0;
+
+ if((tty==NULL) || (pBlock==NULL))
+ {
+ return;
+ }
+
+ if(tty->driver->write_room)
+ room=tty->driver->write_room(tty);
+
+ TRACE_PS("transmit_block %x, room %d, length %d",
+ (int)pBlock, room, pBlock->length);
+
+ while(pInfo->tx_position < pBlock->length)
+ {
+ if(room<2)
+ break;
+
+ if(pBlock->data[pInfo->tx_position]==DLE)
+ {
+ /* send additional DLE char: */
+ put_char(pInfo, DLE);
+ }
+ put_char(pInfo, pBlock->data[pInfo->tx_position++]);
+
+ room--;
+ }
+
+ if((pInfo->tx_position == pBlock->length) && (room>=3))
+ {
+ put_char(pInfo, DLE);
+ put_char(pInfo, ETX);
+ if(pInfo->flags & R3964_BCC)
+ {
+ put_char(pInfo, pInfo->bcc);
+ }
+ pInfo->state = R3964_WAIT_FOR_TX_ACK;
+ mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+ }
+ flush(pInfo);
+}
+
+static void on_receive_block(struct r3964_info *pInfo)
+{
+ unsigned int length;
+ struct r3964_client_info *pClient;
+ struct r3964_block_header *pBlock;
+
+ length=pInfo->rx_position;
+
+ /* compare byte checksum characters: */
+ if(pInfo->flags & R3964_BCC)
+ {
+ if(pInfo->bcc!=pInfo->last_rx)
+ {
+ TRACE_PE("checksum error - got %x but expected %x",
+ pInfo->last_rx, pInfo->bcc);
+ pInfo->flags |= R3964_CHECKSUM;
+ }
+ }
+
+ /* check for errors (parity, overrun,...): */
+ if(pInfo->flags & R3964_ERROR)
+ {
+ TRACE_PE("on_receive_block - transmission failed error %x",
+ pInfo->flags & R3964_ERROR);
+
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ if(pInfo->nRetry<R3964_MAX_RETRIES)
+ {
+ pInfo->state=R3964_WAIT_FOR_RX_REPEAT;
+ pInfo->nRetry++;
+ mod_timer(&pInfo->tmr, jiffies + R3964_TO_RX_PANIC);
+ }
+ else
+ {
+ TRACE_PE("on_receive_block - failed after max retries");
+ pInfo->state=R3964_IDLE;
+ }
+ return;
+ }
+
+
+ /* received block; submit DLE: */
+ put_char(pInfo, DLE);
+ flush(pInfo);
+ del_timer_sync(&pInfo->tmr);
+ TRACE_PS(" rx success: got %d chars", length);
+
+ /* prepare struct r3964_block_header: */
+ pBlock = kmalloc(length+sizeof(struct r3964_block_header), GFP_KERNEL);
+ TRACE_M("on_receive_block - kmalloc %x",(int)pBlock);
+
+ if(pBlock==NULL)
+ return;
+
+ pBlock->length = length;
+ pBlock->data = ((unsigned char*)pBlock)+sizeof(struct r3964_block_header);
+ pBlock->locks = 0;
+ pBlock->next = NULL;
+ pBlock->owner = NULL;
+
+ memcpy(pBlock->data, pInfo->rx_buf, length);
+
+ /* queue block into rx_queue: */
+ add_rx_queue(pInfo, pBlock);
+
+ /* notify attached client processes: */
+ for(pClient=pInfo->firstClient; pClient; pClient=pClient->next)
+ {
+ if(pClient->sig_flags & R3964_SIG_DATA)
+ {
+ add_msg(pClient, R3964_MSG_DATA, length, R3964_OK, pBlock);
+ }
+ }
+ wake_up_interruptible (&pInfo->read_wait);
+
+ pInfo->state = R3964_IDLE;
+
+ trigger_transmit(pInfo);
+}
+
+
+static void receive_char(struct r3964_info *pInfo, const unsigned char c)
+{
+ switch(pInfo->state)
+ {
+ case R3964_TX_REQUEST:
+ if(c==DLE)
+ {
+ TRACE_PS("TX_REQUEST - got DLE");
+
+ pInfo->state = R3964_TRANSMITTING;
+ pInfo->tx_position = 0;
+
+ transmit_block(pInfo);
+ }
+ else if(c==STX)
+ {
+ if(pInfo->nRetry==0)
+ {
+ TRACE_PE("TX_REQUEST - init conflict");
+ if(pInfo->priority == R3964_SLAVE)
+ {
+ goto start_receiving;
+ }
+ }
+ else
+ {
+ TRACE_PE("TX_REQUEST - secondary init conflict!?"
+ " Switching to SLAVE mode for next rx.");
+ goto start_receiving;
+ }
+ }
+ else
+ {
+ TRACE_PE("TX_REQUEST - char != DLE: %x", c);
+ retry_transmit(pInfo);
+ }
+ break;
+ case R3964_TRANSMITTING:
+ if(c==NAK)
+ {
+ TRACE_PE("TRANSMITTING - got NAK");
+ retry_transmit(pInfo);
+ }
+ else
+ {
+ TRACE_PE("TRANSMITTING - got invalid char");
+
+ pInfo->state = R3964_WAIT_ZVZ_BEFORE_TX_RETRY;
+ mod_timer(&pInfo->tmr, jiffies + R3964_TO_ZVZ);
+ }
+ break;
+ case R3964_WAIT_FOR_TX_ACK:
+ if(c==DLE)
+ {
+ TRACE_PS("WAIT_FOR_TX_ACK - got DLE");
+ remove_from_tx_queue(pInfo, R3964_OK);
+
+ pInfo->state = R3964_IDLE;
+ trigger_transmit(pInfo);
+ }
+ else
+ {
+ retry_transmit(pInfo);
+ }
+ break;
+ case R3964_WAIT_FOR_RX_REPEAT:
+ /* FALLTROUGH */
+ case R3964_IDLE:
+ if(c==STX)
+ {
+ /* Prevent rx_queue from overflow: */
+ if(pInfo->blocks_in_rx_queue >= R3964_MAX_BLOCKS_IN_RX_QUEUE)
+ {
+ TRACE_PE("IDLE - got STX but no space in rx_queue!");
+ pInfo->state=R3964_WAIT_FOR_RX_BUF;
+ mod_timer(&pInfo->tmr, R3964_TO_NO_BUF);
+ break;
+ }
+start_receiving:
+ /* Ok, start receiving: */
+ TRACE_PS("IDLE - got STX");
+ pInfo->rx_position = 0;
+ pInfo->last_rx = 0;
+ pInfo->flags &= ~R3964_ERROR;
+ pInfo->state=R3964_RECEIVING;
+ mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+ pInfo->nRetry = 0;
+ put_char(pInfo, DLE);
+ flush(pInfo);
+ pInfo->bcc = 0;
+ }
+ break;
+ case R3964_RECEIVING:
+ if(pInfo->rx_position < RX_BUF_SIZE)
+ {
+ pInfo->bcc ^= c;
+
+ if(c==DLE)
+ {
+ if(pInfo->last_rx==DLE)
+ {
+ pInfo->last_rx = 0;
+ goto char_to_buf;
+ }
+ pInfo->last_rx = DLE;
+ break;
+ }
+ else if((c==ETX) && (pInfo->last_rx==DLE))
+ {
+ if(pInfo->flags & R3964_BCC)
+ {
+ pInfo->state = R3964_WAIT_FOR_BCC;
+ mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+ }
+ else
+ {
+ on_receive_block(pInfo);
+ }
+ }
+ else
+ {
+ pInfo->last_rx = c;
+char_to_buf:
+ pInfo->rx_buf[pInfo->rx_position++] = c;
+ mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+ }
+ }
+ /* else: overflow-msg? BUF_SIZE>MTU; should not happen? */
+ break;
+ case R3964_WAIT_FOR_BCC:
+ pInfo->last_rx = c;
+ on_receive_block(pInfo);
+ break;
+ }
+}
+
+static void receive_error(struct r3964_info *pInfo, const char flag)
+{
+ switch (flag)
+ {
+ case TTY_NORMAL:
+ break;
+ case TTY_BREAK:
+ TRACE_PE("received break")
+ pInfo->flags |= R3964_BREAK;
+ break;
+ case TTY_PARITY:
+ TRACE_PE("parity error")
+ pInfo->flags |= R3964_PARITY;
+ break;
+ case TTY_FRAME:
+ TRACE_PE("frame error")
+ pInfo->flags |= R3964_FRAME;
+ break;
+ case TTY_OVERRUN:
+ TRACE_PE("frame overrun")
+ pInfo->flags |= R3964_OVERRUN;
+ break;
+ default:
+ TRACE_PE("receive_error - unknown flag %d", flag);
+ pInfo->flags |= R3964_UNKNOWN;
+ break;
+ }
+}
+
+static void on_timeout(unsigned long priv)
+{
+ struct r3964_info *pInfo = (void *)priv;
+
+ switch(pInfo->state)
+ {
+ case R3964_TX_REQUEST:
+ TRACE_PE("TX_REQUEST - timeout");
+ retry_transmit(pInfo);
+ break;
+ case R3964_WAIT_ZVZ_BEFORE_TX_RETRY:
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ retry_transmit(pInfo);
+ break;
+ case R3964_WAIT_FOR_TX_ACK:
+ TRACE_PE("WAIT_FOR_TX_ACK - timeout");
+ retry_transmit(pInfo);
+ break;
+ case R3964_WAIT_FOR_RX_BUF:
+ TRACE_PE("WAIT_FOR_RX_BUF - timeout");
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ pInfo->state=R3964_IDLE;
+ break;
+ case R3964_RECEIVING:
+ TRACE_PE("RECEIVING - timeout after %d chars",
+ pInfo->rx_position);
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ pInfo->state=R3964_IDLE;
+ break;
+ case R3964_WAIT_FOR_RX_REPEAT:
+ TRACE_PE("WAIT_FOR_RX_REPEAT - timeout");
+ pInfo->state=R3964_IDLE;
+ break;
+ case R3964_WAIT_FOR_BCC:
+ TRACE_PE("WAIT_FOR_BCC - timeout");
+ put_char(pInfo, NAK);
+ flush(pInfo);
+ pInfo->state=R3964_IDLE;
+ break;
+ }
+}
+
+static struct r3964_client_info *findClient(
+ struct r3964_info *pInfo, pid_t pid)
+{
+ struct r3964_client_info *pClient;
+
+ for(pClient=pInfo->firstClient; pClient; pClient=pClient->next)
+ {
+ if(pClient->pid == pid)
+ {
+ return pClient;
+ }
+ }
+ return NULL;
+}
+
+static int enable_signals(struct r3964_info *pInfo, pid_t pid, int arg)
+{
+ struct r3964_client_info *pClient;
+ struct r3964_client_info **ppClient;
+ struct r3964_message *pMsg;
+
+ if((arg & R3964_SIG_ALL)==0)
+ {
+ /* Remove client from client list */
+ for(ppClient=&pInfo->firstClient; *ppClient; ppClient=&(*ppClient)->next)
+ {
+ pClient = *ppClient;
+
+ if(pClient->pid == pid)
+ {
+ TRACE_PS("removing client %d from client list", pid);
+ *ppClient = pClient->next;
+ while(pClient->msg_count)
+ {
+ pMsg=remove_msg(pInfo, pClient);
+ if(pMsg)
+ {
+ kfree(pMsg);
+ TRACE_M("enable_signals - msg kfree %x",(int)pMsg);
+ }
+ }
+ kfree(pClient);
+ TRACE_M("enable_signals - kfree %x",(int)pClient);
+ return 0;
+ }
+ }
+ return -EINVAL;
+ }
+ else
+ {
+ pClient=findClient(pInfo, pid);
+ if(pClient)
+ {
+ /* update signal options */
+ pClient->sig_flags=arg;
+ }
+ else
+ {
+ /* add client to client list */
+ pClient=kmalloc(sizeof(struct r3964_client_info), GFP_KERNEL);
+ TRACE_M("enable_signals - kmalloc %x",(int)pClient);
+ if(pClient==NULL)
+ return -ENOMEM;
+
+ TRACE_PS("add client %d to client list", pid);
+ spin_lock_init(&pClient->lock);
+ pClient->sig_flags=arg;
+ pClient->pid = pid;
+ pClient->next=pInfo->firstClient;
+ pClient->first_msg = NULL;
+ pClient->last_msg = NULL;
+ pClient->next_block_to_read = NULL;
+ pClient->msg_count = 0;
+ pInfo->firstClient=pClient;
+ }
+ }
+
+ return 0;
+}
+
+static int read_telegram(struct r3964_info *pInfo, pid_t pid, unsigned char __user *buf)
+{
+ struct r3964_client_info *pClient;
+ struct r3964_block_header *block;
+
+ if(!buf)
+ {
+ return -EINVAL;
+ }
+
+ pClient=findClient(pInfo,pid);
+ if(pClient==NULL)
+ {
+ return -EINVAL;
+ }
+
+ block=pClient->next_block_to_read;
+ if(!block)
+ {
+ return 0;
+ }
+ else
+ {
+ if (copy_to_user (buf, block->data, block->length))
+ return -EFAULT;
+
+ remove_client_block(pInfo, pClient);
+ return block->length;
+ }
+
+ return -EINVAL;
+}
+
+static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
+ int error_code, struct r3964_block_header *pBlock)
+{
+ struct r3964_message *pMsg;
+ unsigned long flags;
+
+ if(pClient->msg_count<R3964_MAX_MSG_COUNT-1)
+ {
+queue_the_message:
+
+ pMsg = kmalloc(sizeof(struct r3964_message), GFP_KERNEL);
+ TRACE_M("add_msg - kmalloc %x",(int)pMsg);
+ if(pMsg==NULL) {
+ return;
+ }
+
+ spin_lock_irqsave(&pClient->lock, flags);
+
+ pMsg->msg_id = msg_id;
+ pMsg->arg = arg;
+ pMsg->error_code = error_code;
+ pMsg->block = pBlock;
+ pMsg->next = NULL;
+
+ if(pClient->last_msg==NULL)
+ {
+ pClient->first_msg=pClient->last_msg=pMsg;
+ }
+ else
+ {
+ pClient->last_msg->next = pMsg;
+ pClient->last_msg=pMsg;
+ }
+
+ pClient->msg_count++;
+
+ if(pBlock!=NULL)
+ {
+ pBlock->locks++;
+ }
+ spin_unlock_irqrestore(&pClient->lock, flags);
+ }
+ else
+ {
+ if((pClient->last_msg->msg_id == R3964_MSG_ACK)
+ && (pClient->last_msg->error_code==R3964_OVERFLOW))
+ {
+ pClient->last_msg->arg++;
+ TRACE_PE("add_msg - inc prev OVERFLOW-msg");
+ }
+ else
+ {
+ msg_id = R3964_MSG_ACK;
+ arg = 0;
+ error_code = R3964_OVERFLOW;
+ pBlock = NULL;
+ TRACE_PE("add_msg - queue OVERFLOW-msg");
+ goto queue_the_message;
+ }
+ }
+ /* Send SIGIO signal to client process: */
+ if(pClient->sig_flags & R3964_USE_SIGIO)
+ {
+ kill_proc(pClient->pid, SIGIO, 1);
+ }
+}
+
+static struct r3964_message *remove_msg(struct r3964_info *pInfo,
+ struct r3964_client_info *pClient)
+{
+ struct r3964_message *pMsg=NULL;
+ unsigned long flags;
+
+ if(pClient->first_msg)
+ {
+ spin_lock_irqsave(&pClient->lock, flags);
+
+ pMsg = pClient->first_msg;
+ pClient->first_msg = pMsg->next;
+ if(pClient->first_msg==NULL)
+ {
+ pClient->last_msg = NULL;
+ }
+
+ pClient->msg_count--;
+ if(pMsg->block)
+ {
+ remove_client_block(pInfo, pClient);
+ pClient->next_block_to_read = pMsg->block;
+ }
+ spin_unlock_irqrestore(&pClient->lock, flags);
+ }
+ return pMsg;
+}
+
+static void remove_client_block(struct r3964_info *pInfo,
+ struct r3964_client_info *pClient)
+{
+ struct r3964_block_header *block;
+
+ TRACE_PS("remove_client_block PID %d", pClient->pid);
+
+ block=pClient->next_block_to_read;
+ if(block)
+ {
+ block->locks--;
+ if(block->locks==0)
+ {
+ remove_from_rx_queue(pInfo, block);
+ }
+ }
+ pClient->next_block_to_read = NULL;
+}
+
+
+/*************************************************************
+ * Line discipline routines
+ *************************************************************/
+
+static int r3964_open(struct tty_struct *tty)
+{
+ struct r3964_info *pInfo;
+
+ TRACE_L("open");
+ TRACE_L("tty=%x, PID=%d, disc_data=%x",
+ (int)tty, current->pid, (int)tty->disc_data);
+
+ pInfo=kmalloc(sizeof(struct r3964_info), GFP_KERNEL);
+ TRACE_M("r3964_open - info kmalloc %x",(int)pInfo);
+
+ if(!pInfo)
+ {
+ printk(KERN_ERR "r3964: failed to alloc info structure\n");
+ return -ENOMEM;
+ }
+
+ pInfo->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL);
+ TRACE_M("r3964_open - rx_buf kmalloc %x",(int)pInfo->rx_buf);
+
+ if(!pInfo->rx_buf)
+ {
+ printk(KERN_ERR "r3964: failed to alloc receive buffer\n");
+ kfree(pInfo);
+ TRACE_M("r3964_open - info kfree %x",(int)pInfo);
+ return -ENOMEM;
+ }
+
+ pInfo->tx_buf = kmalloc(TX_BUF_SIZE, GFP_KERNEL);
+ TRACE_M("r3964_open - tx_buf kmalloc %x",(int)pInfo->tx_buf);
+
+ if(!pInfo->tx_buf)
+ {
+ printk(KERN_ERR "r3964: failed to alloc transmit buffer\n");
+ kfree(pInfo->rx_buf);
+ TRACE_M("r3964_open - rx_buf kfree %x",(int)pInfo->rx_buf);
+ kfree(pInfo);
+ TRACE_M("r3964_open - info kfree %x",(int)pInfo);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&pInfo->lock);
+ pInfo->tty = tty;
+ init_waitqueue_head (&pInfo->read_wait);
+ pInfo->priority = R3964_MASTER;
+ pInfo->rx_first = pInfo->rx_last = NULL;
+ pInfo->tx_first = pInfo->tx_last = NULL;
+ pInfo->rx_position = 0;
+ pInfo->tx_position = 0;
+ pInfo->last_rx = 0;
+ pInfo->blocks_in_rx_queue = 0;
+ pInfo->firstClient=NULL;
+ pInfo->state=R3964_IDLE;
+ pInfo->flags = R3964_DEBUG;
+ pInfo->nRetry = 0;
+
+ tty->disc_data = pInfo;
+
+ init_timer(&pInfo->tmr);
+ pInfo->tmr.data = (unsigned long)pInfo;
+ pInfo->tmr.function = on_timeout;
+
+ return 0;
+}
+
+static void r3964_close(struct tty_struct *tty)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ struct r3964_client_info *pClient, *pNext;
+ struct r3964_message *pMsg;
+ struct r3964_block_header *pHeader, *pNextHeader;
+ unsigned long flags;
+
+ TRACE_L("close");
+
+ /*
+ * Make sure that our task queue isn't activated. If it
+ * is, take it out of the linked list.
+ */
+ del_timer_sync(&pInfo->tmr);
+
+ /* Remove client-structs and message queues: */
+ pClient=pInfo->firstClient;
+ while(pClient)
+ {
+ pNext=pClient->next;
+ while(pClient->msg_count)
+ {
+ pMsg=remove_msg(pInfo, pClient);
+ if(pMsg)
+ {
+ kfree(pMsg);
+ TRACE_M("r3964_close - msg kfree %x",(int)pMsg);
+ }
+ }
+ kfree(pClient);
+ TRACE_M("r3964_close - client kfree %x",(int)pClient);
+ pClient=pNext;
+ }
+ /* Remove jobs from tx_queue: */
+ spin_lock_irqsave(&pInfo->lock, flags);
+ pHeader=pInfo->tx_first;
+ pInfo->tx_first=pInfo->tx_last=NULL;
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+
+ while(pHeader)
+ {
+ pNextHeader=pHeader->next;
+ kfree(pHeader);
+ pHeader=pNextHeader;
+ }
+
+ /* Free buffers: */
+ wake_up_interruptible(&pInfo->read_wait);
+ kfree(pInfo->rx_buf);
+ TRACE_M("r3964_close - rx_buf kfree %x",(int)pInfo->rx_buf);
+ kfree(pInfo->tx_buf);
+ TRACE_M("r3964_close - tx_buf kfree %x",(int)pInfo->tx_buf);
+ kfree(pInfo);
+ TRACE_M("r3964_close - info kfree %x",(int)pInfo);
+}
+
+static ssize_t r3964_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t nr)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ struct r3964_client_info *pClient;
+ struct r3964_message *pMsg;
+ struct r3964_client_message theMsg;
+ DECLARE_WAITQUEUE (wait, current);
+
+ int pid = current->pid;
+ int count;
+
+ TRACE_L("read()");
+
+ pClient=findClient(pInfo, pid);
+ if(pClient)
+ {
+ pMsg = remove_msg(pInfo, pClient);
+ if(pMsg==NULL)
+ {
+ /* no messages available. */
+ if (file->f_flags & O_NONBLOCK)
+ {
+ return -EAGAIN;
+ }
+ /* block until there is a message: */
+ add_wait_queue(&pInfo->read_wait, &wait);
+repeat:
+ current->state = TASK_INTERRUPTIBLE;
+ pMsg = remove_msg(pInfo, pClient);
+ if (!pMsg && !signal_pending(current))
+ {
+ schedule();
+ goto repeat;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&pInfo->read_wait, &wait);
+ }
+
+ /* If we still haven't got a message, we must have been signalled */
+
+ if (!pMsg) return -EINTR;
+
+ /* deliver msg to client process: */
+ theMsg.msg_id = pMsg->msg_id;
+ theMsg.arg = pMsg->arg;
+ theMsg.error_code = pMsg->error_code;
+ count = sizeof(struct r3964_client_message);
+
+ kfree(pMsg);
+ TRACE_M("r3964_read - msg kfree %x",(int)pMsg);
+
+ if (copy_to_user(buf,&theMsg, count))
+ return -EFAULT;
+
+ TRACE_PS("read - return %d", count);
+ return count;
+ }
+ return -EPERM;
+}
+
+static ssize_t r3964_write(struct tty_struct * tty, struct file * file,
+ const unsigned char *data, size_t count)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ struct r3964_block_header *pHeader;
+ struct r3964_client_info *pClient;
+ unsigned char *new_data;
+ int pid;
+
+ TRACE_L("write request, %d characters", count);
+/*
+ * Verify the pointers
+ */
+
+ if(!pInfo)
+ return -EIO;
+
+/*
+ * Ensure that the caller does not wish to send too much.
+ */
+ if (count > R3964_MTU)
+ {
+ if (pInfo->flags & R3964_DEBUG)
+ {
+ TRACE_L (KERN_WARNING
+ "r3964_write: truncating user packet "
+ "from %u to mtu %d", count, R3964_MTU);
+ }
+ count = R3964_MTU;
+ }
+/*
+ * Allocate a buffer for the data and copy it from the buffer with header prepended
+ */
+ new_data = kmalloc (count+sizeof(struct r3964_block_header), GFP_KERNEL);
+ TRACE_M("r3964_write - kmalloc %x",(int)new_data);
+ if (new_data == NULL) {
+ if (pInfo->flags & R3964_DEBUG)
+ {
+ printk (KERN_ERR
+ "r3964_write: no memory\n");
+ }
+ return -ENOSPC;
+ }
+
+ pHeader = (struct r3964_block_header *)new_data;
+ pHeader->data = new_data + sizeof(struct r3964_block_header);
+ pHeader->length = count;
+ pHeader->locks = 0;
+ pHeader->owner = NULL;
+
+ pid=current->pid;
+
+ pClient=findClient(pInfo, pid);
+ if(pClient)
+ {
+ pHeader->owner = pClient;
+ }
+
+ memcpy(pHeader->data, data, count); /* We already verified this */
+
+ if(pInfo->flags & R3964_DEBUG)
+ {
+ dump_block(pHeader->data, count);
+ }
+
+/*
+ * Add buffer to transmit-queue:
+ */
+ add_tx_queue(pInfo, pHeader);
+ trigger_transmit(pInfo);
+
+ return 0;
+}
+
+static int r3964_ioctl(struct tty_struct * tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ if(pInfo==NULL)
+ return -EINVAL;
+ switch(cmd)
+ {
+ case R3964_ENABLE_SIGNALS:
+ return enable_signals(pInfo, current->pid, arg);
+ case R3964_SETPRIORITY:
+ if(arg<R3964_MASTER || arg>R3964_SLAVE)
+ return -EINVAL;
+ pInfo->priority = arg & 0xff;
+ return 0;
+ case R3964_USE_BCC:
+ if(arg)
+ pInfo->flags |= R3964_BCC;
+ else
+ pInfo->flags &= ~R3964_BCC;
+ return 0;
+ case R3964_READ_TELEGRAM:
+ return read_telegram(pInfo, current->pid, (unsigned char __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static void r3964_set_termios(struct tty_struct *tty, struct termios * old)
+{
+ TRACE_L("set_termios");
+}
+
+/* Called without the kernel lock held - fine */
+static unsigned int r3964_poll(struct tty_struct * tty, struct file * file,
+ struct poll_table_struct *wait)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ int pid=current->pid;
+ struct r3964_client_info *pClient;
+ struct r3964_message *pMsg=NULL;
+ unsigned long flags;
+ int result = POLLOUT;
+
+ TRACE_L("POLL");
+
+ pClient=findClient(pInfo,pid);
+ if(pClient)
+ {
+ poll_wait(file, &pInfo->read_wait, wait);
+ spin_lock_irqsave(&pInfo->lock, flags);
+ pMsg=pClient->first_msg;
+ spin_unlock_irqrestore(&pInfo->lock, flags);
+ if(pMsg)
+ result |= POLLIN | POLLRDNORM;
+ }
+ else
+ {
+ result = -EINVAL;
+ }
+ return result;
+}
+
+static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+ char *fp, int count)
+{
+ struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+ const unsigned char *p;
+ char *f, flags = 0;
+ int i;
+
+ for (i=count, p = cp, f = fp; i; i--, p++) {
+ if (f)
+ flags = *f++;
+ if(flags==TTY_NORMAL)
+ {
+ receive_char(pInfo, *p);
+ }
+ else
+ {
+ receive_error(pInfo, flags);
+ }
+
+ }
+}
+
+static int r3964_receive_room(struct tty_struct *tty)
+{
+ TRACE_L("receive_room");
+ return -1;
+}
+
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_R3964);
diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c
new file mode 100644
index 000000000000..edba5a35bf21
--- /dev/null
+++ b/drivers/char/n_tty.c
@@ -0,0 +1,1562 @@
+/*
+ * n_tty.c --- implements the N_TTY line discipline.
+ *
+ * This code used to be in tty_io.c, but things are getting hairy
+ * enough that it made sense to split things off. (The N_TTY
+ * processing has changed so much that it's hardly recognizable,
+ * anyway...)
+ *
+ * Note that the open routine for N_TTY is guaranteed never to return
+ * an error. This is because Linux will fall back to setting a line
+ * to N_TTY if it can not switch to any other line discipline.
+ *
+ * Written by Theodore Ts'o, Copyright 1994.
+ *
+ * This file also contains code originally written by Linus Torvalds,
+ * Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994.
+ *
+ * This file may be redistributed under the terms of the GNU General Public
+ * License.
+ *
+ * Reduced memory usage for older ARM systems - Russell King.
+ *
+ * 2000/01/20 Fixed SMP locking on put_tty_queue using bits of
+ * the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
+ * who actually finally proved there really was a race.
+ *
+ * 2002/03/18 Implemented n_tty_wakeup to send SIGIO POLL_OUTs to
+ * waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
+ * Also fixed a bug in BLOCKING mode where write_chan returns
+ * EAGAIN
+ */
+
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* number of characters left in xmit buffer before select has we have room */
+#define WAKEUP_CHARS 256
+
+/*
+ * This defines the low- and high-watermarks for throttling and
+ * unthrottling the TTY driver. These watermarks are used for
+ * controlling the space in the read buffer.
+ */
+#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
+#define TTY_THRESHOLD_UNTHROTTLE 128
+
+static inline unsigned char *alloc_buf(void)
+{
+ int prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
+
+ if (PAGE_SIZE != N_TTY_BUF_SIZE)
+ return kmalloc(N_TTY_BUF_SIZE, prio);
+ else
+ return (unsigned char *)__get_free_page(prio);
+}
+
+static inline void free_buf(unsigned char *buf)
+{
+ if (PAGE_SIZE != N_TTY_BUF_SIZE)
+ kfree(buf);
+ else
+ free_page((unsigned long) buf);
+}
+
+static inline void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty)
+{
+ if (tty->read_cnt < N_TTY_BUF_SIZE) {
+ tty->read_buf[tty->read_head] = c;
+ tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1);
+ tty->read_cnt++;
+ }
+}
+
+static inline void put_tty_queue(unsigned char c, struct tty_struct *tty)
+{
+ unsigned long flags;
+ /*
+ * The problem of stomping on the buffers ends here.
+ * Why didn't anyone see this one coming? --AJK
+ */
+ spin_lock_irqsave(&tty->read_lock, flags);
+ put_tty_queue_nolock(c, tty);
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+}
+
+/**
+ * check_unthrottle - allow new receive data
+ * @tty; tty device
+ *
+ * Check whether to call the driver.unthrottle function.
+ * We test the TTY_THROTTLED bit first so that it always
+ * indicates the current state. The decision about whether
+ * it is worth allowing more input has been taken by the caller.
+ * Can sleep, may be called under the atomic_read semaphore but
+ * this is not guaranteed.
+ */
+
+static void check_unthrottle(struct tty_struct * tty)
+{
+ if (tty->count &&
+ test_and_clear_bit(TTY_THROTTLED, &tty->flags) &&
+ tty->driver->unthrottle)
+ tty->driver->unthrottle(tty);
+}
+
+/**
+ * reset_buffer_flags - reset buffer state
+ * @tty: terminal to reset
+ *
+ * Reset the read buffer counters, clear the flags,
+ * and make sure the driver is unthrottled. Called
+ * from n_tty_open() and n_tty_flush_buffer().
+ */
+static void reset_buffer_flags(struct tty_struct *tty)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_head = tty->read_tail = tty->read_cnt = 0;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ tty->canon_head = tty->canon_data = tty->erasing = 0;
+ memset(&tty->read_flags, 0, sizeof tty->read_flags);
+ check_unthrottle(tty);
+}
+
+/**
+ * n_tty_flush_buffer - clean input queue
+ * @tty: terminal device
+ *
+ * Flush the input buffer. Called when the line discipline is
+ * being closed, when the tty layer wants the buffer flushed (eg
+ * at hangup) or when the N_TTY line discipline internally has to
+ * clean the pending queue (for example some signals).
+ *
+ * FIXME: tty->ctrl_status is not spinlocked and relies on
+ * lock_kernel() still.
+ */
+
+static void n_tty_flush_buffer(struct tty_struct * tty)
+{
+ /* clear everything and unthrottle the driver */
+ reset_buffer_flags(tty);
+
+ if (!tty->link)
+ return;
+
+ if (tty->link->packet) {
+ tty->ctrl_status |= TIOCPKT_FLUSHREAD;
+ wake_up_interruptible(&tty->link->read_wait);
+ }
+}
+
+/**
+ * n_tty_chars_in_buffer - report available bytes
+ * @tty: tty device
+ *
+ * Report the number of characters buffered to be delivered to user
+ * at this instant in time.
+ */
+
+static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ unsigned long flags;
+ ssize_t n = 0;
+
+ spin_lock_irqsave(&tty->read_lock, flags);
+ if (!tty->icanon) {
+ n = tty->read_cnt;
+ } else if (tty->canon_data) {
+ n = (tty->canon_head > tty->read_tail) ?
+ tty->canon_head - tty->read_tail :
+ tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail);
+ }
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ return n;
+}
+
+/**
+ * is_utf8_continuation - utf8 multibyte check
+ * @c: byte to check
+ *
+ * Returns true if the utf8 character 'c' is a multibyte continuation
+ * character. We use this to correctly compute the on screen size
+ * of the character when printing
+ */
+
+static inline int is_utf8_continuation(unsigned char c)
+{
+ return (c & 0xc0) == 0x80;
+}
+
+/**
+ * is_continuation - multibyte check
+ * @c: byte to check
+ *
+ * Returns true if the utf8 character 'c' is a multibyte continuation
+ * character and the terminal is in unicode mode.
+ */
+
+static inline int is_continuation(unsigned char c, struct tty_struct *tty)
+{
+ return I_IUTF8(tty) && is_utf8_continuation(c);
+}
+
+/**
+ * opost - output post processor
+ * @c: character (or partial unicode symbol)
+ * @tty: terminal device
+ *
+ * Perform OPOST processing. Returns -1 when the output device is
+ * full and the character must be retried. Note that Linux currently
+ * ignores TABDLY, CRDLY, VTDLY, FFDLY and NLDLY. They simply aren't
+ * relevant in the world today. If you ever need them, add them here.
+ *
+ * Called from both the receive and transmit sides and can be called
+ * re-entrantly. Relies on lock_kernel() still.
+ */
+
+static int opost(unsigned char c, struct tty_struct *tty)
+{
+ int space, spaces;
+
+ space = tty->driver->write_room(tty);
+ if (!space)
+ return -1;
+
+ if (O_OPOST(tty)) {
+ switch (c) {
+ case '\n':
+ if (O_ONLRET(tty))
+ tty->column = 0;
+ if (O_ONLCR(tty)) {
+ if (space < 2)
+ return -1;
+ tty->driver->put_char(tty, '\r');
+ tty->column = 0;
+ }
+ tty->canon_column = tty->column;
+ break;
+ case '\r':
+ if (O_ONOCR(tty) && tty->column == 0)
+ return 0;
+ if (O_OCRNL(tty)) {
+ c = '\n';
+ if (O_ONLRET(tty))
+ tty->canon_column = tty->column = 0;
+ break;
+ }
+ tty->canon_column = tty->column = 0;
+ break;
+ case '\t':
+ spaces = 8 - (tty->column & 7);
+ if (O_TABDLY(tty) == XTABS) {
+ if (space < spaces)
+ return -1;
+ tty->column += spaces;
+ tty->driver->write(tty, " ", spaces);
+ return 0;
+ }
+ tty->column += spaces;
+ break;
+ case '\b':
+ if (tty->column > 0)
+ tty->column--;
+ break;
+ default:
+ if (O_OLCUC(tty))
+ c = toupper(c);
+ if (!iscntrl(c) && !is_continuation(c, tty))
+ tty->column++;
+ break;
+ }
+ }
+ tty->driver->put_char(tty, c);
+ return 0;
+}
+
+/**
+ * opost_block - block postprocess
+ * @tty: terminal device
+ * @inbuf: user buffer
+ * @nr: number of bytes
+ *
+ * This path is used to speed up block console writes, among other
+ * things when processing blocks of output data. It handles only
+ * the simple cases normally found and helps to generate blocks of
+ * symbols for the console driver and thus improve performance.
+ *
+ * Called from write_chan under the tty layer write lock.
+ */
+
+static ssize_t opost_block(struct tty_struct * tty,
+ const unsigned char * buf, unsigned int nr)
+{
+ int space;
+ int i;
+ const unsigned char *cp;
+
+ space = tty->driver->write_room(tty);
+ if (!space)
+ return 0;
+ if (nr > space)
+ nr = space;
+
+ for (i = 0, cp = buf; i < nr; i++, cp++) {
+ switch (*cp) {
+ case '\n':
+ if (O_ONLRET(tty))
+ tty->column = 0;
+ if (O_ONLCR(tty))
+ goto break_out;
+ tty->canon_column = tty->column;
+ break;
+ case '\r':
+ if (O_ONOCR(tty) && tty->column == 0)
+ goto break_out;
+ if (O_OCRNL(tty))
+ goto break_out;
+ tty->canon_column = tty->column = 0;
+ break;
+ case '\t':
+ goto break_out;
+ case '\b':
+ if (tty->column > 0)
+ tty->column--;
+ break;
+ default:
+ if (O_OLCUC(tty))
+ goto break_out;
+ if (!iscntrl(*cp))
+ tty->column++;
+ break;
+ }
+ }
+break_out:
+ if (tty->driver->flush_chars)
+ tty->driver->flush_chars(tty);
+ i = tty->driver->write(tty, buf, i);
+ return i;
+}
+
+
+/**
+ * put_char - write character to driver
+ * @c: character (or part of unicode symbol)
+ * @tty: terminal device
+ *
+ * Queue a byte to the driver layer for output
+ */
+
+static inline void put_char(unsigned char c, struct tty_struct *tty)
+{
+ tty->driver->put_char(tty, c);
+}
+
+/**
+ * echo_char - echo characters
+ * @c: unicode byte to echo
+ * @tty: terminal device
+ *
+ * Echo user input back onto the screen. This must be called only when
+ * L_ECHO(tty) is true. Called from the driver receive_buf path.
+ */
+
+static void echo_char(unsigned char c, struct tty_struct *tty)
+{
+ if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') {
+ put_char('^', tty);
+ put_char(c ^ 0100, tty);
+ tty->column += 2;
+ } else
+ opost(c, tty);
+}
+
+static inline void finish_erasing(struct tty_struct *tty)
+{
+ if (tty->erasing) {
+ put_char('/', tty);
+ tty->column++;
+ tty->erasing = 0;
+ }
+}
+
+/**
+ * eraser - handle erase function
+ * @c: character input
+ * @tty: terminal device
+ *
+ * Perform erase and neccessary output when an erase character is
+ * present in the stream from the driver layer. Handles the complexities
+ * of UTF-8 multibyte symbols.
+ */
+
+static void eraser(unsigned char c, struct tty_struct *tty)
+{
+ enum { ERASE, WERASE, KILL } kill_type;
+ int head, seen_alnums, cnt;
+ unsigned long flags;
+
+ if (tty->read_head == tty->canon_head) {
+ /* opost('\a', tty); */ /* what do you think? */
+ return;
+ }
+ if (c == ERASE_CHAR(tty))
+ kill_type = ERASE;
+ else if (c == WERASE_CHAR(tty))
+ kill_type = WERASE;
+ else {
+ if (!L_ECHO(tty)) {
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_cnt -= ((tty->read_head - tty->canon_head) &
+ (N_TTY_BUF_SIZE - 1));
+ tty->read_head = tty->canon_head;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ return;
+ }
+ if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) {
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_cnt -= ((tty->read_head - tty->canon_head) &
+ (N_TTY_BUF_SIZE - 1));
+ tty->read_head = tty->canon_head;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ finish_erasing(tty);
+ echo_char(KILL_CHAR(tty), tty);
+ /* Add a newline if ECHOK is on and ECHOKE is off. */
+ if (L_ECHOK(tty))
+ opost('\n', tty);
+ return;
+ }
+ kill_type = KILL;
+ }
+
+ seen_alnums = 0;
+ while (tty->read_head != tty->canon_head) {
+ head = tty->read_head;
+
+ /* erase a single possibly multibyte character */
+ do {
+ head = (head - 1) & (N_TTY_BUF_SIZE-1);
+ c = tty->read_buf[head];
+ } while (is_continuation(c, tty) && head != tty->canon_head);
+
+ /* do not partially erase */
+ if (is_continuation(c, tty))
+ break;
+
+ if (kill_type == WERASE) {
+ /* Equivalent to BSD's ALTWERASE. */
+ if (isalnum(c) || c == '_')
+ seen_alnums++;
+ else if (seen_alnums)
+ break;
+ }
+ cnt = (tty->read_head - head) & (N_TTY_BUF_SIZE-1);
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_head = head;
+ tty->read_cnt -= cnt;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ if (L_ECHO(tty)) {
+ if (L_ECHOPRT(tty)) {
+ if (!tty->erasing) {
+ put_char('\\', tty);
+ tty->column++;
+ tty->erasing = 1;
+ }
+ /* if cnt > 1, output a multi-byte character */
+ echo_char(c, tty);
+ while (--cnt > 0) {
+ head = (head+1) & (N_TTY_BUF_SIZE-1);
+ put_char(tty->read_buf[head], tty);
+ }
+ } else if (kill_type == ERASE && !L_ECHOE(tty)) {
+ echo_char(ERASE_CHAR(tty), tty);
+ } else if (c == '\t') {
+ unsigned int col = tty->canon_column;
+ unsigned long tail = tty->canon_head;
+
+ /* Find the column of the last char. */
+ while (tail != tty->read_head) {
+ c = tty->read_buf[tail];
+ if (c == '\t')
+ col = (col | 7) + 1;
+ else if (iscntrl(c)) {
+ if (L_ECHOCTL(tty))
+ col += 2;
+ } else if (!is_continuation(c, tty))
+ col++;
+ tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+ }
+
+ /* should never happen */
+ if (tty->column > 0x80000000)
+ tty->column = 0;
+
+ /* Now backup to that column. */
+ while (tty->column > col) {
+ /* Can't use opost here. */
+ put_char('\b', tty);
+ if (tty->column > 0)
+ tty->column--;
+ }
+ } else {
+ if (iscntrl(c) && L_ECHOCTL(tty)) {
+ put_char('\b', tty);
+ put_char(' ', tty);
+ put_char('\b', tty);
+ if (tty->column > 0)
+ tty->column--;
+ }
+ if (!iscntrl(c) || L_ECHOCTL(tty)) {
+ put_char('\b', tty);
+ put_char(' ', tty);
+ put_char('\b', tty);
+ if (tty->column > 0)
+ tty->column--;
+ }
+ }
+ }
+ if (kill_type == ERASE)
+ break;
+ }
+ if (tty->read_head == tty->canon_head)
+ finish_erasing(tty);
+}
+
+/**
+ * isig - handle the ISIG optio
+ * @sig: signal
+ * @tty: terminal
+ * @flush: force flush
+ *
+ * Called when a signal is being sent due to terminal input. This
+ * may caus terminal flushing to take place according to the termios
+ * settings and character used. Called from the driver receive_buf
+ * path so serialized.
+ */
+
+static inline void isig(int sig, struct tty_struct *tty, int flush)
+{
+ if (tty->pgrp > 0)
+ kill_pg(tty->pgrp, sig, 1);
+ if (flush || !L_NOFLSH(tty)) {
+ n_tty_flush_buffer(tty);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ }
+}
+
+/**
+ * n_tty_receive_break - handle break
+ * @tty: terminal
+ *
+ * An RS232 break event has been hit in the incoming bitstream. This
+ * can cause a variety of events depending upon the termios settings.
+ *
+ * Called from the receive_buf path so single threaded.
+ */
+
+static inline void n_tty_receive_break(struct tty_struct *tty)
+{
+ if (I_IGNBRK(tty))
+ return;
+ if (I_BRKINT(tty)) {
+ isig(SIGINT, tty, 1);
+ return;
+ }
+ if (I_PARMRK(tty)) {
+ put_tty_queue('\377', tty);
+ put_tty_queue('\0', tty);
+ }
+ put_tty_queue('\0', tty);
+ wake_up_interruptible(&tty->read_wait);
+}
+
+/**
+ * n_tty_receive_overrun - handle overrun reporting
+ * @tty: terminal
+ *
+ * Data arrived faster than we could process it. While the tty
+ * driver has flagged this the bits that were missed are gone
+ * forever.
+ *
+ * Called from the receive_buf path so single threaded. Does not
+ * need locking as num_overrun and overrun_time are function
+ * private.
+ */
+
+static inline void n_tty_receive_overrun(struct tty_struct *tty)
+{
+ char buf[64];
+
+ tty->num_overrun++;
+ if (time_before(tty->overrun_time, jiffies - HZ) ||
+ time_after(tty->overrun_time, jiffies)) {
+ printk(KERN_WARNING "%s: %d input overrun(s)\n",
+ tty_name(tty, buf),
+ tty->num_overrun);
+ tty->overrun_time = jiffies;
+ tty->num_overrun = 0;
+ }
+}
+
+/**
+ * n_tty_receive_parity_error - error notifier
+ * @tty: terminal device
+ * @c: character
+ *
+ * Process a parity error and queue the right data to indicate
+ * the error case if neccessary. Locking as per n_tty_receive_buf.
+ */
+static inline void n_tty_receive_parity_error(struct tty_struct *tty,
+ unsigned char c)
+{
+ if (I_IGNPAR(tty)) {
+ return;
+ }
+ if (I_PARMRK(tty)) {
+ put_tty_queue('\377', tty);
+ put_tty_queue('\0', tty);
+ put_tty_queue(c, tty);
+ } else if (I_INPCK(tty))
+ put_tty_queue('\0', tty);
+ else
+ put_tty_queue(c, tty);
+ wake_up_interruptible(&tty->read_wait);
+}
+
+/**
+ * n_tty_receive_char - perform processing
+ * @tty: terminal device
+ * @c: character
+ *
+ * Process an individual character of input received from the driver.
+ * This is serialized with respect to itself by the rules for the
+ * driver above.
+ */
+
+static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
+{
+ unsigned long flags;
+
+ if (tty->raw) {
+ put_tty_queue(c, tty);
+ return;
+ }
+
+ if (tty->stopped && !tty->flow_stopped &&
+ I_IXON(tty) && I_IXANY(tty)) {
+ start_tty(tty);
+ return;
+ }
+
+ if (I_ISTRIP(tty))
+ c &= 0x7f;
+ if (I_IUCLC(tty) && L_IEXTEN(tty))
+ c=tolower(c);
+
+ if (tty->closing) {
+ if (I_IXON(tty)) {
+ if (c == START_CHAR(tty))
+ start_tty(tty);
+ else if (c == STOP_CHAR(tty))
+ stop_tty(tty);
+ }
+ return;
+ }
+
+ /*
+ * If the previous character was LNEXT, or we know that this
+ * character is not one of the characters that we'll have to
+ * handle specially, do shortcut processing to speed things
+ * up.
+ */
+ if (!test_bit(c, tty->process_char_map) || tty->lnext) {
+ finish_erasing(tty);
+ tty->lnext = 0;
+ if (L_ECHO(tty)) {
+ if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+ put_char('\a', tty); /* beep if no space */
+ return;
+ }
+ /* Record the column of first canon char. */
+ if (tty->canon_head == tty->read_head)
+ tty->canon_column = tty->column;
+ echo_char(c, tty);
+ }
+ if (I_PARMRK(tty) && c == (unsigned char) '\377')
+ put_tty_queue(c, tty);
+ put_tty_queue(c, tty);
+ return;
+ }
+
+ if (c == '\r') {
+ if (I_IGNCR(tty))
+ return;
+ if (I_ICRNL(tty))
+ c = '\n';
+ } else if (c == '\n' && I_INLCR(tty))
+ c = '\r';
+ if (I_IXON(tty)) {
+ if (c == START_CHAR(tty)) {
+ start_tty(tty);
+ return;
+ }
+ if (c == STOP_CHAR(tty)) {
+ stop_tty(tty);
+ return;
+ }
+ }
+ if (L_ISIG(tty)) {
+ int signal;
+ signal = SIGINT;
+ if (c == INTR_CHAR(tty))
+ goto send_signal;
+ signal = SIGQUIT;
+ if (c == QUIT_CHAR(tty))
+ goto send_signal;
+ signal = SIGTSTP;
+ if (c == SUSP_CHAR(tty)) {
+send_signal:
+ isig(signal, tty, 0);
+ return;
+ }
+ }
+ if (tty->icanon) {
+ if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) ||
+ (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) {
+ eraser(c, tty);
+ return;
+ }
+ if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) {
+ tty->lnext = 1;
+ if (L_ECHO(tty)) {
+ finish_erasing(tty);
+ if (L_ECHOCTL(tty)) {
+ put_char('^', tty);
+ put_char('\b', tty);
+ }
+ }
+ return;
+ }
+ if (c == REPRINT_CHAR(tty) && L_ECHO(tty) &&
+ L_IEXTEN(tty)) {
+ unsigned long tail = tty->canon_head;
+
+ finish_erasing(tty);
+ echo_char(c, tty);
+ opost('\n', tty);
+ while (tail != tty->read_head) {
+ echo_char(tty->read_buf[tail], tty);
+ tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+ }
+ return;
+ }
+ if (c == '\n') {
+ if (L_ECHO(tty) || L_ECHONL(tty)) {
+ if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+ put_char('\a', tty);
+ return;
+ }
+ opost('\n', tty);
+ }
+ goto handle_newline;
+ }
+ if (c == EOF_CHAR(tty)) {
+ if (tty->canon_head != tty->read_head)
+ set_bit(TTY_PUSH, &tty->flags);
+ c = __DISABLED_CHAR;
+ goto handle_newline;
+ }
+ if ((c == EOL_CHAR(tty)) ||
+ (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) {
+ /*
+ * XXX are EOL_CHAR and EOL2_CHAR echoed?!?
+ */
+ if (L_ECHO(tty)) {
+ if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+ put_char('\a', tty);
+ return;
+ }
+ /* Record the column of first canon char. */
+ if (tty->canon_head == tty->read_head)
+ tty->canon_column = tty->column;
+ echo_char(c, tty);
+ }
+ /*
+ * XXX does PARMRK doubling happen for
+ * EOL_CHAR and EOL2_CHAR?
+ */
+ if (I_PARMRK(tty) && c == (unsigned char) '\377')
+ put_tty_queue(c, tty);
+
+ handle_newline:
+ spin_lock_irqsave(&tty->read_lock, flags);
+ set_bit(tty->read_head, tty->read_flags);
+ put_tty_queue_nolock(c, tty);
+ tty->canon_head = tty->read_head;
+ tty->canon_data++;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ kill_fasync(&tty->fasync, SIGIO, POLL_IN);
+ if (waitqueue_active(&tty->read_wait))
+ wake_up_interruptible(&tty->read_wait);
+ return;
+ }
+ }
+
+ finish_erasing(tty);
+ if (L_ECHO(tty)) {
+ if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+ put_char('\a', tty); /* beep if no space */
+ return;
+ }
+ if (c == '\n')
+ opost('\n', tty);
+ else {
+ /* Record the column of first canon char. */
+ if (tty->canon_head == tty->read_head)
+ tty->canon_column = tty->column;
+ echo_char(c, tty);
+ }
+ }
+
+ if (I_PARMRK(tty) && c == (unsigned char) '\377')
+ put_tty_queue(c, tty);
+
+ put_tty_queue(c, tty);
+}
+
+/**
+ * n_tty_receive_room - receive space
+ * @tty: terminal
+ *
+ * Called by the driver to find out how much data it is
+ * permitted to feed to the line discipline without any being lost
+ * and thus to manage flow control. Not serialized. Answers for the
+ * "instant".
+ */
+
+static int n_tty_receive_room(struct tty_struct *tty)
+{
+ int left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
+
+ /*
+ * If we are doing input canonicalization, and there are no
+ * pending newlines, let characters through without limit, so
+ * that erase characters will be handled. Other excess
+ * characters will be beeped.
+ */
+ if (tty->icanon && !tty->canon_data)
+ return N_TTY_BUF_SIZE;
+
+ if (left > 0)
+ return left;
+ return 0;
+}
+
+/**
+ * n_tty_write_wakeup - asynchronous I/O notifier
+ * @tty: tty device
+ *
+ * Required for the ptys, serial driver etc. since processes
+ * that attach themselves to the master and rely on ASYNC
+ * IO must be woken up
+ */
+
+static void n_tty_write_wakeup(struct tty_struct *tty)
+{
+ if (tty->fasync)
+ {
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
+ }
+ return;
+}
+
+/**
+ * n_tty_receive_buf - data receive
+ * @tty: terminal device
+ * @cp: buffer
+ * @fp: flag buffer
+ * @count: characters
+ *
+ * Called by the terminal driver when a block of characters has
+ * been received. This function must be called from soft contexts
+ * not from interrupt context. The driver is responsible for making
+ * calls one at a time and in order (or using flush_to_ldisc)
+ */
+
+static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+ char *fp, int count)
+{
+ const unsigned char *p;
+ char *f, flags = TTY_NORMAL;
+ int i;
+ char buf[64];
+ unsigned long cpuflags;
+
+ if (!tty->read_buf)
+ return;
+
+ if (tty->real_raw) {
+ spin_lock_irqsave(&tty->read_lock, cpuflags);
+ i = min(N_TTY_BUF_SIZE - tty->read_cnt,
+ N_TTY_BUF_SIZE - tty->read_head);
+ i = min(count, i);
+ memcpy(tty->read_buf + tty->read_head, cp, i);
+ tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
+ tty->read_cnt += i;
+ cp += i;
+ count -= i;
+
+ i = min(N_TTY_BUF_SIZE - tty->read_cnt,
+ N_TTY_BUF_SIZE - tty->read_head);
+ i = min(count, i);
+ memcpy(tty->read_buf + tty->read_head, cp, i);
+ tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
+ tty->read_cnt += i;
+ spin_unlock_irqrestore(&tty->read_lock, cpuflags);
+ } else {
+ for (i=count, p = cp, f = fp; i; i--, p++) {
+ if (f)
+ flags = *f++;
+ switch (flags) {
+ case TTY_NORMAL:
+ n_tty_receive_char(tty, *p);
+ break;
+ case TTY_BREAK:
+ n_tty_receive_break(tty);
+ break;
+ case TTY_PARITY:
+ case TTY_FRAME:
+ n_tty_receive_parity_error(tty, *p);
+ break;
+ case TTY_OVERRUN:
+ n_tty_receive_overrun(tty);
+ break;
+ default:
+ printk("%s: unknown flag %d\n",
+ tty_name(tty, buf), flags);
+ break;
+ }
+ }
+ if (tty->driver->flush_chars)
+ tty->driver->flush_chars(tty);
+ }
+
+ if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {
+ kill_fasync(&tty->fasync, SIGIO, POLL_IN);
+ if (waitqueue_active(&tty->read_wait))
+ wake_up_interruptible(&tty->read_wait);
+ }
+
+ /*
+ * Check the remaining room for the input canonicalization
+ * mode. We don't want to throttle the driver if we're in
+ * canonical mode and don't have a newline yet!
+ */
+ if (n_tty_receive_room(tty) < TTY_THRESHOLD_THROTTLE) {
+ /* check TTY_THROTTLED first so it indicates our state */
+ if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) &&
+ tty->driver->throttle)
+ tty->driver->throttle(tty);
+ }
+}
+
+int is_ignored(int sig)
+{
+ return (sigismember(&current->blocked, sig) ||
+ current->sighand->action[sig-1].sa.sa_handler == SIG_IGN);
+}
+
+/**
+ * n_tty_set_termios - termios data changed
+ * @tty: terminal
+ * @old: previous data
+ *
+ * Called by the tty layer when the user changes termios flags so
+ * that the line discipline can plan ahead. This function cannot sleep
+ * and is protected from re-entry by the tty layer. The user is
+ * guaranteed that this function will not be re-entered or in progress
+ * when the ldisc is closed.
+ */
+
+static void n_tty_set_termios(struct tty_struct *tty, struct termios * old)
+{
+ if (!tty)
+ return;
+
+ tty->icanon = (L_ICANON(tty) != 0);
+ if (test_bit(TTY_HW_COOK_IN, &tty->flags)) {
+ tty->raw = 1;
+ tty->real_raw = 1;
+ return;
+ }
+ if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
+ I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
+ I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
+ I_PARMRK(tty)) {
+ memset(tty->process_char_map, 0, 256/8);
+
+ if (I_IGNCR(tty) || I_ICRNL(tty))
+ set_bit('\r', tty->process_char_map);
+ if (I_INLCR(tty))
+ set_bit('\n', tty->process_char_map);
+
+ if (L_ICANON(tty)) {
+ set_bit(ERASE_CHAR(tty), tty->process_char_map);
+ set_bit(KILL_CHAR(tty), tty->process_char_map);
+ set_bit(EOF_CHAR(tty), tty->process_char_map);
+ set_bit('\n', tty->process_char_map);
+ set_bit(EOL_CHAR(tty), tty->process_char_map);
+ if (L_IEXTEN(tty)) {
+ set_bit(WERASE_CHAR(tty),
+ tty->process_char_map);
+ set_bit(LNEXT_CHAR(tty),
+ tty->process_char_map);
+ set_bit(EOL2_CHAR(tty),
+ tty->process_char_map);
+ if (L_ECHO(tty))
+ set_bit(REPRINT_CHAR(tty),
+ tty->process_char_map);
+ }
+ }
+ if (I_IXON(tty)) {
+ set_bit(START_CHAR(tty), tty->process_char_map);
+ set_bit(STOP_CHAR(tty), tty->process_char_map);
+ }
+ if (L_ISIG(tty)) {
+ set_bit(INTR_CHAR(tty), tty->process_char_map);
+ set_bit(QUIT_CHAR(tty), tty->process_char_map);
+ set_bit(SUSP_CHAR(tty), tty->process_char_map);
+ }
+ clear_bit(__DISABLED_CHAR, tty->process_char_map);
+ tty->raw = 0;
+ tty->real_raw = 0;
+ } else {
+ tty->raw = 1;
+ if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
+ (I_IGNPAR(tty) || !I_INPCK(tty)) &&
+ (tty->driver->flags & TTY_DRIVER_REAL_RAW))
+ tty->real_raw = 1;
+ else
+ tty->real_raw = 0;
+ }
+}
+
+/**
+ * n_tty_close - close the ldisc for this tty
+ * @tty: device
+ *
+ * Called from the terminal layer when this line discipline is
+ * being shut down, either because of a close or becsuse of a
+ * discipline change. The function will not be called while other
+ * ldisc methods are in progress.
+ */
+
+static void n_tty_close(struct tty_struct *tty)
+{
+ n_tty_flush_buffer(tty);
+ if (tty->read_buf) {
+ free_buf(tty->read_buf);
+ tty->read_buf = NULL;
+ }
+}
+
+/**
+ * n_tty_open - open an ldisc
+ * @tty: terminal to open
+ *
+ * Called when this line discipline is being attached to the
+ * terminal device. Can sleep. Called serialized so that no
+ * other events will occur in parallel. No further open will occur
+ * until a close.
+ */
+
+static int n_tty_open(struct tty_struct *tty)
+{
+ if (!tty)
+ return -EINVAL;
+
+ /* This one is ugly. Currently a malloc failure here can panic */
+ if (!tty->read_buf) {
+ tty->read_buf = alloc_buf();
+ if (!tty->read_buf)
+ return -ENOMEM;
+ }
+ memset(tty->read_buf, 0, N_TTY_BUF_SIZE);
+ reset_buffer_flags(tty);
+ tty->column = 0;
+ n_tty_set_termios(tty, NULL);
+ tty->minimum_to_wake = 1;
+ tty->closing = 0;
+ return 0;
+}
+
+static inline int input_available_p(struct tty_struct *tty, int amt)
+{
+ if (tty->icanon) {
+ if (tty->canon_data)
+ return 1;
+ } else if (tty->read_cnt >= (amt ? amt : 1))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * copy_from_read_buf - copy read data directly
+ * @tty: terminal device
+ * @b: user data
+ * @nr: size of data
+ *
+ * Helper function to speed up read_chan. It is only called when
+ * ICANON is off; it copies characters straight from the tty queue to
+ * user space directly. It can be profitably called twice; once to
+ * drain the space from the tail pointer to the (physical) end of the
+ * buffer, and once to drain the space from the (physical) beginning of
+ * the buffer to head pointer.
+ *
+ * Called under the tty->atomic_read sem and with TTY_DONT_FLIP set
+ *
+ */
+
+static inline int copy_from_read_buf(struct tty_struct *tty,
+ unsigned char __user **b,
+ size_t *nr)
+
+{
+ int retval;
+ size_t n;
+ unsigned long flags;
+
+ retval = 0;
+ spin_lock_irqsave(&tty->read_lock, flags);
+ n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail);
+ n = min(*nr, n);
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ if (n) {
+ mb();
+ retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
+ n -= retval;
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
+ tty->read_cnt -= n;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+ *b += n;
+ *nr -= n;
+ }
+ return retval;
+}
+
+extern ssize_t redirected_tty_write(struct file *,const char *,size_t,loff_t *);
+
+/**
+ * job_control - check job control
+ * @tty: tty
+ * @file: file handle
+ *
+ * Perform job control management checks on this file/tty descriptor
+ * and if appropriate send any needed signals and return a negative
+ * error code if action should be taken.
+ */
+
+static int job_control(struct tty_struct *tty, struct file *file)
+{
+ /* Job control check -- must be done at start and after
+ every sleep (POSIX.1 7.1.1.4). */
+ /* NOTE: not yet done after every sleep pending a thorough
+ check of the logic of this change. -- jlc */
+ /* don't stop on /dev/console */
+ if (file->f_op->write != redirected_tty_write &&
+ current->signal->tty == tty) {
+ if (tty->pgrp <= 0)
+ printk("read_chan: tty->pgrp <= 0!\n");
+ else if (process_group(current) != tty->pgrp) {
+ if (is_ignored(SIGTTIN) ||
+ is_orphaned_pgrp(process_group(current)))
+ return -EIO;
+ kill_pg(process_group(current), SIGTTIN, 1);
+ return -ERESTARTSYS;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * read_chan - read function for tty
+ * @tty: tty device
+ * @file: file object
+ * @buf: userspace buffer pointer
+ * @nr: size of I/O
+ *
+ * Perform reads for the line discipline. We are guaranteed that the
+ * line discipline will not be closed under us but we may get multiple
+ * parallel readers and must handle this ourselves. We may also get
+ * a hangup. Always called in user context, may sleep.
+ *
+ * This code must be sure never to sleep through a hangup.
+ */
+
+static ssize_t read_chan(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t nr)
+{
+ unsigned char __user *b = buf;
+ DECLARE_WAITQUEUE(wait, current);
+ int c;
+ int minimum, time;
+ ssize_t retval = 0;
+ ssize_t size;
+ long timeout;
+ unsigned long flags;
+
+do_it_again:
+
+ if (!tty->read_buf) {
+ printk("n_tty_read_chan: called with read_buf == NULL?!?\n");
+ return -EIO;
+ }
+
+ c = job_control(tty, file);
+ if(c < 0)
+ return c;
+
+ minimum = time = 0;
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ if (!tty->icanon) {
+ time = (HZ / 10) * TIME_CHAR(tty);
+ minimum = MIN_CHAR(tty);
+ if (minimum) {
+ if (time)
+ tty->minimum_to_wake = 1;
+ else if (!waitqueue_active(&tty->read_wait) ||
+ (tty->minimum_to_wake > minimum))
+ tty->minimum_to_wake = minimum;
+ } else {
+ timeout = 0;
+ if (time) {
+ timeout = time;
+ time = 0;
+ }
+ tty->minimum_to_wake = minimum = 1;
+ }
+ }
+
+ /*
+ * Internal serialization of reads.
+ */
+ if (file->f_flags & O_NONBLOCK) {
+ if (down_trylock(&tty->atomic_read))
+ return -EAGAIN;
+ }
+ else {
+ if (down_interruptible(&tty->atomic_read))
+ return -ERESTARTSYS;
+ }
+
+ add_wait_queue(&tty->read_wait, &wait);
+ set_bit(TTY_DONT_FLIP, &tty->flags);
+ while (nr) {
+ /* First test for status change. */
+ if (tty->packet && tty->link->ctrl_status) {
+ unsigned char cs;
+ if (b != buf)
+ break;
+ cs = tty->link->ctrl_status;
+ tty->link->ctrl_status = 0;
+ if (put_user(cs, b++)) {
+ retval = -EFAULT;
+ b--;
+ break;
+ }
+ nr--;
+ break;
+ }
+ /* This statement must be first before checking for input
+ so that any interrupt will set the state back to
+ TASK_RUNNING. */
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
+ ((minimum - (b - buf)) >= 1))
+ tty->minimum_to_wake = (minimum - (b - buf));
+
+ if (!input_available_p(tty, 0)) {
+ if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
+ retval = -EIO;
+ break;
+ }
+ if (tty_hung_up_p(file))
+ break;
+ if (!timeout)
+ break;
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+ timeout = schedule_timeout(timeout);
+ set_bit(TTY_DONT_FLIP, &tty->flags);
+ continue;
+ }
+ __set_current_state(TASK_RUNNING);
+
+ /* Deal with packet mode. */
+ if (tty->packet && b == buf) {
+ if (put_user(TIOCPKT_DATA, b++)) {
+ retval = -EFAULT;
+ b--;
+ break;
+ }
+ nr--;
+ }
+
+ if (tty->icanon) {
+ /* N.B. avoid overrun if nr == 0 */
+ while (nr && tty->read_cnt) {
+ int eol;
+
+ eol = test_and_clear_bit(tty->read_tail,
+ tty->read_flags);
+ c = tty->read_buf[tty->read_tail];
+ spin_lock_irqsave(&tty->read_lock, flags);
+ tty->read_tail = ((tty->read_tail+1) &
+ (N_TTY_BUF_SIZE-1));
+ tty->read_cnt--;
+ if (eol) {
+ /* this test should be redundant:
+ * we shouldn't be reading data if
+ * canon_data is 0
+ */
+ if (--tty->canon_data < 0)
+ tty->canon_data = 0;
+ }
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+
+ if (!eol || (c != __DISABLED_CHAR)) {
+ if (put_user(c, b++)) {
+ retval = -EFAULT;
+ b--;
+ break;
+ }
+ nr--;
+ }
+ if (eol)
+ break;
+ }
+ if (retval)
+ break;
+ } else {
+ int uncopied;
+ uncopied = copy_from_read_buf(tty, &b, &nr);
+ uncopied += copy_from_read_buf(tty, &b, &nr);
+ if (uncopied) {
+ retval = -EFAULT;
+ break;
+ }
+ }
+
+ /* If there is enough space in the read buffer now, let the
+ * low-level driver know. We use n_tty_chars_in_buffer() to
+ * check the buffer, as it now knows about canonical mode.
+ * Otherwise, if the driver is throttled and the line is
+ * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
+ * we won't get any more characters.
+ */
+ if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE)
+ check_unthrottle(tty);
+
+ if (b - buf >= minimum)
+ break;
+ if (time)
+ timeout = time;
+ }
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+ up(&tty->atomic_read);
+ remove_wait_queue(&tty->read_wait, &wait);
+
+ if (!waitqueue_active(&tty->read_wait))
+ tty->minimum_to_wake = minimum;
+
+ __set_current_state(TASK_RUNNING);
+ size = b - buf;
+ if (size) {
+ retval = size;
+ if (nr)
+ clear_bit(TTY_PUSH, &tty->flags);
+ } else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
+ goto do_it_again;
+
+ return retval;
+}
+
+/**
+ * write_chan - write function for tty
+ * @tty: tty device
+ * @file: file object
+ * @buf: userspace buffer pointer
+ * @nr: size of I/O
+ *
+ * Write function of the terminal device. This is serialized with
+ * respect to other write callers but not to termios changes, reads
+ * and other such events. We must be careful with N_TTY as the receive
+ * code will echo characters, thus calling driver write methods.
+ *
+ * This code must be sure never to sleep through a hangup.
+ */
+
+static ssize_t write_chan(struct tty_struct * tty, struct file * file,
+ const unsigned char * buf, size_t nr)
+{
+ const unsigned char *b = buf;
+ DECLARE_WAITQUEUE(wait, current);
+ int c;
+ ssize_t retval = 0;
+
+ /* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
+ if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ }
+
+ add_wait_queue(&tty->write_wait, &wait);
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
+ retval = -EIO;
+ break;
+ }
+ if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
+ while (nr > 0) {
+ ssize_t num = opost_block(tty, b, nr);
+ if (num < 0) {
+ if (num == -EAGAIN)
+ break;
+ retval = num;
+ goto break_out;
+ }
+ b += num;
+ nr -= num;
+ if (nr == 0)
+ break;
+ c = *b;
+ if (opost(c, tty) < 0)
+ break;
+ b++; nr--;
+ }
+ if (tty->driver->flush_chars)
+ tty->driver->flush_chars(tty);
+ } else {
+ c = tty->driver->write(tty, b, nr);
+ if (c < 0) {
+ retval = c;
+ goto break_out;
+ }
+ b += c;
+ nr -= c;
+ }
+ if (!nr)
+ break;
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+ schedule();
+ }
+break_out:
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&tty->write_wait, &wait);
+ return (b - buf) ? b - buf : retval;
+}
+
+/**
+ * normal_poll - poll method for N_TTY
+ * @tty: terminal device
+ * @file: file accessing it
+ * @wait: poll table
+ *
+ * Called when the line discipline is asked to poll() for data or
+ * for special events. This code is not serialized with respect to
+ * other events save open/close.
+ *
+ * This code must be sure never to sleep through a hangup.
+ * Called without the kernel lock held - fine
+ *
+ * FIXME: if someone changes the VMIN or discipline settings for the
+ * terminal while another process is in poll() the poll does not
+ * recompute the new limits. Possibly set_termios should issue
+ * a read wakeup to fix this bug.
+ */
+
+static unsigned int normal_poll(struct tty_struct * tty, struct file * file, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ poll_wait(file, &tty->read_wait, wait);
+ poll_wait(file, &tty->write_wait, wait);
+ if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty)))
+ mask |= POLLIN | POLLRDNORM;
+ if (tty->packet && tty->link->ctrl_status)
+ mask |= POLLPRI | POLLIN | POLLRDNORM;
+ if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+ mask |= POLLHUP;
+ if (tty_hung_up_p(file))
+ mask |= POLLHUP;
+ if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {
+ if (MIN_CHAR(tty) && !TIME_CHAR(tty))
+ tty->minimum_to_wake = MIN_CHAR(tty);
+ else
+ tty->minimum_to_wake = 1;
+ }
+ if (tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS &&
+ tty->driver->write_room(tty) > 0)
+ mask |= POLLOUT | POLLWRNORM;
+ return mask;
+}
+
+struct tty_ldisc tty_ldisc_N_TTY = {
+ TTY_LDISC_MAGIC, /* magic */
+ "n_tty", /* name */
+ 0, /* num */
+ 0, /* flags */
+ n_tty_open, /* open */
+ n_tty_close, /* close */
+ n_tty_flush_buffer, /* flush_buffer */
+ n_tty_chars_in_buffer, /* chars_in_buffer */
+ read_chan, /* read */
+ write_chan, /* write */
+ n_tty_ioctl, /* ioctl */
+ n_tty_set_termios, /* set_termios */
+ normal_poll, /* poll */
+ NULL, /* hangup */
+ n_tty_receive_buf, /* receive_buf */
+ n_tty_receive_room, /* receive_room */
+ n_tty_write_wakeup /* write_wakeup */
+};
+
diff --git a/drivers/char/nvram.c b/drivers/char/nvram.c
new file mode 100644
index 000000000000..f63a3fd7ca6f
--- /dev/null
+++ b/drivers/char/nvram.c
@@ -0,0 +1,926 @@
+/*
+ * CMOS/NV-RAM driver for Linux
+ *
+ * Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ * idea by and with help from Richard Jelinek <rj@suse.de>
+ * Portions copyright (c) 2001,2002 Sun Microsystems (thockin@sun.com)
+ *
+ * This driver allows you to access the contents of the non-volatile memory in
+ * the mc146818rtc.h real-time clock. This chip is built into all PCs and into
+ * many Atari machines. In the former it's called "CMOS-RAM", in the latter
+ * "NVRAM" (NV stands for non-volatile).
+ *
+ * The data are supplied as a (seekable) character device, /dev/nvram. The
+ * size of this file is dependent on the controller. The usual size is 114,
+ * the number of freely available bytes in the memory (i.e., not used by the
+ * RTC itself).
+ *
+ * Checksums over the NVRAM contents are managed by this driver. In case of a
+ * bad checksum, reads and writes return -EIO. The checksum can be initialized
+ * to a sane state either by ioctl(NVRAM_INIT) (clear whole NVRAM) or
+ * ioctl(NVRAM_SETCKS) (doesn't change contents, just makes checksum valid
+ * again; use with care!)
+ *
+ * This file also provides some functions for other parts of the kernel that
+ * want to access the NVRAM: nvram_{read,write,check_checksum,set_checksum}.
+ * Obviously this can be used only if this driver is always configured into
+ * the kernel and is not a module. Since the functions are used by some Atari
+ * drivers, this is the case on the Atari.
+ *
+ *
+ * 1.1 Cesar Barros: SMP locking fixes
+ * added changelog
+ * 1.2 Erik Gilling: Cobalt Networks support
+ * Tim Hockin: general cleanup, Cobalt support
+ */
+
+#define NVRAM_VERSION "1.2"
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/nvram.h>
+
+#define PC 1
+#define ATARI 2
+#define COBALT 3
+
+/* select machine configuration */
+#if defined(CONFIG_ATARI)
+# define MACH ATARI
+#elif defined(__i386__) || defined(__x86_64__) || defined(__arm__) /* and others?? */
+#define MACH PC
+# if defined(CONFIG_COBALT)
+# include <linux/cobalt-nvram.h>
+# define MACH COBALT
+# else
+# define MACH PC
+# endif
+#else
+# error Cannot build nvram driver for this machine configuration.
+#endif
+
+#if MACH == PC
+
+/* RTC in a PC */
+#define CHECK_DRIVER_INIT() 1
+
+/* On PCs, the checksum is built only over bytes 2..31 */
+#define PC_CKS_RANGE_START 2
+#define PC_CKS_RANGE_END 31
+#define PC_CKS_LOC 32
+#define NVRAM_BYTES (128-NVRAM_FIRST_BYTE)
+
+#define mach_check_checksum pc_check_checksum
+#define mach_set_checksum pc_set_checksum
+#define mach_proc_infos pc_proc_infos
+
+#endif
+
+#if MACH == COBALT
+
+#define CHECK_DRIVER_INIT() 1
+
+#define NVRAM_BYTES (128-NVRAM_FIRST_BYTE)
+
+#define mach_check_checksum cobalt_check_checksum
+#define mach_set_checksum cobalt_set_checksum
+#define mach_proc_infos cobalt_proc_infos
+
+#endif
+
+#if MACH == ATARI
+
+/* Special parameters for RTC in Atari machines */
+#include <asm/atarihw.h>
+#include <asm/atariints.h>
+#define RTC_PORT(x) (TT_RTC_BAS + 2*(x))
+#define CHECK_DRIVER_INIT() (MACH_IS_ATARI && ATARIHW_PRESENT(TT_CLK))
+
+#define NVRAM_BYTES 50
+
+/* On Ataris, the checksum is over all bytes except the checksum bytes
+ * themselves; these are at the very end */
+#define ATARI_CKS_RANGE_START 0
+#define ATARI_CKS_RANGE_END 47
+#define ATARI_CKS_LOC 48
+
+#define mach_check_checksum atari_check_checksum
+#define mach_set_checksum atari_set_checksum
+#define mach_proc_infos atari_proc_infos
+
+#endif
+
+/* Note that *all* calls to CMOS_READ and CMOS_WRITE must be done with
+ * rtc_lock held. Due to the index-port/data-port design of the RTC, we
+ * don't want two different things trying to get to it at once. (e.g. the
+ * periodic 11 min sync from time.c vs. this driver.)
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static DEFINE_SPINLOCK(nvram_state_lock);
+static int nvram_open_cnt; /* #times opened */
+static int nvram_open_mode; /* special open modes */
+#define NVRAM_WRITE 1 /* opened for writing (exclusive) */
+#define NVRAM_EXCL 2 /* opened with O_EXCL */
+
+static int mach_check_checksum(void);
+static void mach_set_checksum(void);
+
+#ifdef CONFIG_PROC_FS
+static int mach_proc_infos(unsigned char *contents, char *buffer, int *len,
+ off_t *begin, off_t offset, int size);
+#endif
+
+/*
+ * These functions are provided to be called internally or by other parts of
+ * the kernel. It's up to the caller to ensure correct checksum before reading
+ * or after writing (needs to be done only once).
+ *
+ * It is worth noting that these functions all access bytes of general
+ * purpose memory in the NVRAM - that is to say, they all add the
+ * NVRAM_FIRST_BYTE offset. Pass them offsets into NVRAM as if you did not
+ * know about the RTC cruft.
+ */
+
+unsigned char
+__nvram_read_byte(int i)
+{
+ return CMOS_READ(NVRAM_FIRST_BYTE + i);
+}
+
+unsigned char
+nvram_read_byte(int i)
+{
+ unsigned long flags;
+ unsigned char c;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ c = __nvram_read_byte(i);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+ return c;
+}
+
+/* This races nicely with trying to read with checksum checking (nvram_read) */
+void
+__nvram_write_byte(unsigned char c, int i)
+{
+ CMOS_WRITE(c, NVRAM_FIRST_BYTE + i);
+}
+
+void
+nvram_write_byte(unsigned char c, int i)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ __nvram_write_byte(c, i);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+}
+
+int
+__nvram_check_checksum(void)
+{
+ return mach_check_checksum();
+}
+
+int
+nvram_check_checksum(void)
+{
+ unsigned long flags;
+ int rv;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ rv = __nvram_check_checksum();
+ spin_unlock_irqrestore(&rtc_lock, flags);
+ return rv;
+}
+
+void
+__nvram_set_checksum(void)
+{
+ mach_set_checksum();
+}
+
+void
+nvram_set_checksum(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ __nvram_set_checksum();
+ spin_unlock_irqrestore(&rtc_lock, flags);
+}
+
+/*
+ * The are the file operation function for user access to /dev/nvram
+ */
+
+static loff_t nvram_llseek(struct file *file,loff_t offset, int origin )
+{
+ lock_kernel();
+ switch (origin) {
+ case 0:
+ /* nothing to do */
+ break;
+ case 1:
+ offset += file->f_pos;
+ break;
+ case 2:
+ offset += NVRAM_BYTES;
+ break;
+ }
+ unlock_kernel();
+ return (offset >= 0) ? (file->f_pos = offset) : -EINVAL;
+}
+
+static ssize_t
+nvram_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ unsigned char contents[NVRAM_BYTES];
+ unsigned i = *ppos;
+ unsigned char *tmp;
+
+ spin_lock_irq(&rtc_lock);
+
+ if (!__nvram_check_checksum())
+ goto checksum_err;
+
+ for (tmp = contents; count-- > 0 && i < NVRAM_BYTES; ++i, ++tmp)
+ *tmp = __nvram_read_byte(i);
+
+ spin_unlock_irq(&rtc_lock);
+
+ if (copy_to_user(buf, contents, tmp - contents))
+ return -EFAULT;
+
+ *ppos = i;
+
+ return tmp - contents;
+
+ checksum_err:
+ spin_unlock_irq(&rtc_lock);
+ return -EIO;
+}
+
+static ssize_t
+nvram_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ unsigned char contents[NVRAM_BYTES];
+ unsigned i = *ppos;
+ unsigned char *tmp;
+ int len;
+
+ len = (NVRAM_BYTES - i) < count ? (NVRAM_BYTES - i) : count;
+ if (copy_from_user(contents, buf, len))
+ return -EFAULT;
+
+ spin_lock_irq(&rtc_lock);
+
+ if (!__nvram_check_checksum())
+ goto checksum_err;
+
+ for (tmp = contents; count-- > 0 && i < NVRAM_BYTES; ++i, ++tmp)
+ __nvram_write_byte(*tmp, i);
+
+ __nvram_set_checksum();
+
+ spin_unlock_irq(&rtc_lock);
+
+ *ppos = i;
+
+ return tmp - contents;
+
+ checksum_err:
+ spin_unlock_irq(&rtc_lock);
+ return -EIO;
+}
+
+static int
+nvram_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int i;
+
+ switch (cmd) {
+
+ case NVRAM_INIT:
+ /* initialize NVRAM contents and checksum */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ spin_lock_irq(&rtc_lock);
+
+ for (i = 0; i < NVRAM_BYTES; ++i)
+ __nvram_write_byte(0, i);
+ __nvram_set_checksum();
+
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+
+ case NVRAM_SETCKS:
+ /* just set checksum, contents unchanged (maybe useful after
+ * checksum garbaged somehow...) */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ spin_lock_irq(&rtc_lock);
+ __nvram_set_checksum();
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+
+ default:
+ return -ENOTTY;
+ }
+}
+
+static int
+nvram_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&nvram_state_lock);
+
+ if ((nvram_open_cnt && (file->f_flags & O_EXCL)) ||
+ (nvram_open_mode & NVRAM_EXCL) ||
+ ((file->f_mode & 2) && (nvram_open_mode & NVRAM_WRITE))) {
+ spin_unlock(&nvram_state_lock);
+ return -EBUSY;
+ }
+
+ if (file->f_flags & O_EXCL)
+ nvram_open_mode |= NVRAM_EXCL;
+ if (file->f_mode & 2)
+ nvram_open_mode |= NVRAM_WRITE;
+ nvram_open_cnt++;
+
+ spin_unlock(&nvram_state_lock);
+
+ return 0;
+}
+
+static int
+nvram_release(struct inode *inode, struct file *file)
+{
+ spin_lock(&nvram_state_lock);
+
+ nvram_open_cnt--;
+
+ /* if only one instance is open, clear the EXCL bit */
+ if (nvram_open_mode & NVRAM_EXCL)
+ nvram_open_mode &= ~NVRAM_EXCL;
+ if (file->f_mode & 2)
+ nvram_open_mode &= ~NVRAM_WRITE;
+
+ spin_unlock(&nvram_state_lock);
+
+ return 0;
+}
+
+#ifndef CONFIG_PROC_FS
+static int
+nvram_read_proc(char *buffer, char **start, off_t offset,
+ int size, int *eof, void *data)
+{
+ return 0;
+}
+#else
+
+static int
+nvram_read_proc(char *buffer, char **start, off_t offset,
+ int size, int *eof, void *data)
+{
+ unsigned char contents[NVRAM_BYTES];
+ int i, len = 0;
+ off_t begin = 0;
+
+ spin_lock_irq(&rtc_lock);
+ for (i = 0; i < NVRAM_BYTES; ++i)
+ contents[i] = __nvram_read_byte(i);
+ spin_unlock_irq(&rtc_lock);
+
+ *eof = mach_proc_infos(contents, buffer, &len, &begin, offset, size);
+
+ if (offset >= begin + len)
+ return 0;
+ *start = buffer + (offset - begin);
+ return (size < begin + len - offset) ? size : begin + len - offset;
+
+}
+
+/* This macro frees the machine specific function from bounds checking and
+ * this like that... */
+#define PRINT_PROC(fmt,args...) \
+ do { \
+ *len += sprintf(buffer+*len, fmt, ##args); \
+ if (*begin + *len > offset + size) \
+ return 0; \
+ if (*begin + *len < offset) { \
+ *begin += *len; \
+ *len = 0; \
+ } \
+ } while(0)
+
+#endif /* CONFIG_PROC_FS */
+
+static struct file_operations nvram_fops = {
+ .owner = THIS_MODULE,
+ .llseek = nvram_llseek,
+ .read = nvram_read,
+ .write = nvram_write,
+ .ioctl = nvram_ioctl,
+ .open = nvram_open,
+ .release = nvram_release,
+};
+
+static struct miscdevice nvram_dev = {
+ NVRAM_MINOR,
+ "nvram",
+ &nvram_fops
+};
+
+static int __init
+nvram_init(void)
+{
+ int ret;
+
+ /* First test whether the driver should init at all */
+ if (!CHECK_DRIVER_INIT())
+ return -ENXIO;
+
+ ret = misc_register(&nvram_dev);
+ if (ret) {
+ printk(KERN_ERR "nvram: can't misc_register on minor=%d\n",
+ NVRAM_MINOR);
+ goto out;
+ }
+ if (!create_proc_read_entry("driver/nvram", 0, NULL, nvram_read_proc,
+ NULL)) {
+ printk(KERN_ERR "nvram: can't create /proc/driver/nvram\n");
+ ret = -ENOMEM;
+ goto outmisc;
+ }
+ ret = 0;
+ printk(KERN_INFO "Non-volatile memory driver v" NVRAM_VERSION "\n");
+ out:
+ return ret;
+ outmisc:
+ misc_deregister(&nvram_dev);
+ goto out;
+}
+
+static void __exit
+nvram_cleanup_module(void)
+{
+ remove_proc_entry("driver/nvram", NULL);
+ misc_deregister(&nvram_dev);
+}
+
+module_init(nvram_init);
+module_exit(nvram_cleanup_module);
+
+/*
+ * Machine specific functions
+ */
+
+#if MACH == PC
+
+static int
+pc_check_checksum(void)
+{
+ int i;
+ unsigned short sum = 0;
+ unsigned short expect;
+
+ for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
+ sum += __nvram_read_byte(i);
+ expect = __nvram_read_byte(PC_CKS_LOC)<<8 |
+ __nvram_read_byte(PC_CKS_LOC+1);
+ return ((sum & 0xffff) == expect);
+}
+
+static void
+pc_set_checksum(void)
+{
+ int i;
+ unsigned short sum = 0;
+
+ for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
+ sum += __nvram_read_byte(i);
+ __nvram_write_byte(sum >> 8, PC_CKS_LOC);
+ __nvram_write_byte(sum & 0xff, PC_CKS_LOC + 1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static char *floppy_types[] = {
+ "none", "5.25'' 360k", "5.25'' 1.2M", "3.5'' 720k", "3.5'' 1.44M",
+ "3.5'' 2.88M", "3.5'' 2.88M"
+};
+
+static char *gfx_types[] = {
+ "EGA, VGA, ... (with BIOS)",
+ "CGA (40 cols)",
+ "CGA (80 cols)",
+ "monochrome",
+};
+
+static int
+pc_proc_infos(unsigned char *nvram, char *buffer, int *len,
+ off_t *begin, off_t offset, int size)
+{
+ int checksum;
+ int type;
+
+ spin_lock_irq(&rtc_lock);
+ checksum = __nvram_check_checksum();
+ spin_unlock_irq(&rtc_lock);
+
+ PRINT_PROC("Checksum status: %svalid\n", checksum ? "" : "not ");
+
+ PRINT_PROC("# floppies : %d\n",
+ (nvram[6] & 1) ? (nvram[6] >> 6) + 1 : 0);
+ PRINT_PROC("Floppy 0 type : ");
+ type = nvram[2] >> 4;
+ if (type < sizeof (floppy_types) / sizeof (*floppy_types))
+ PRINT_PROC("%s\n", floppy_types[type]);
+ else
+ PRINT_PROC("%d (unknown)\n", type);
+ PRINT_PROC("Floppy 1 type : ");
+ type = nvram[2] & 0x0f;
+ if (type < sizeof (floppy_types) / sizeof (*floppy_types))
+ PRINT_PROC("%s\n", floppy_types[type]);
+ else
+ PRINT_PROC("%d (unknown)\n", type);
+
+ PRINT_PROC("HD 0 type : ");
+ type = nvram[4] >> 4;
+ if (type)
+ PRINT_PROC("%02x\n", type == 0x0f ? nvram[11] : type);
+ else
+ PRINT_PROC("none\n");
+
+ PRINT_PROC("HD 1 type : ");
+ type = nvram[4] & 0x0f;
+ if (type)
+ PRINT_PROC("%02x\n", type == 0x0f ? nvram[12] : type);
+ else
+ PRINT_PROC("none\n");
+
+ PRINT_PROC("HD type 48 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
+ nvram[18] | (nvram[19] << 8),
+ nvram[20], nvram[25],
+ nvram[21] | (nvram[22] << 8), nvram[23] | (nvram[24] << 8));
+ PRINT_PROC("HD type 49 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
+ nvram[39] | (nvram[40] << 8),
+ nvram[41], nvram[46],
+ nvram[42] | (nvram[43] << 8), nvram[44] | (nvram[45] << 8));
+
+ PRINT_PROC("DOS base memory: %d kB\n", nvram[7] | (nvram[8] << 8));
+ PRINT_PROC("Extended memory: %d kB (configured), %d kB (tested)\n",
+ nvram[9] | (nvram[10] << 8), nvram[34] | (nvram[35] << 8));
+
+ PRINT_PROC("Gfx adapter : %s\n", gfx_types[(nvram[6] >> 4) & 3]);
+
+ PRINT_PROC("FPU : %sinstalled\n",
+ (nvram[6] & 2) ? "" : "not ");
+
+ return 1;
+}
+#endif
+
+#endif /* MACH == PC */
+
+#if MACH == COBALT
+
+/* the cobalt CMOS has a wider range of its checksum */
+static int cobalt_check_checksum(void)
+{
+ int i;
+ unsigned short sum = 0;
+ unsigned short expect;
+
+ for (i = COBT_CMOS_CKS_START; i <= COBT_CMOS_CKS_END; ++i) {
+ if ((i == COBT_CMOS_CHECKSUM) || (i == (COBT_CMOS_CHECKSUM+1)))
+ continue;
+
+ sum += __nvram_read_byte(i);
+ }
+ expect = __nvram_read_byte(COBT_CMOS_CHECKSUM) << 8 |
+ __nvram_read_byte(COBT_CMOS_CHECKSUM+1);
+ return ((sum & 0xffff) == expect);
+}
+
+static void cobalt_set_checksum(void)
+{
+ int i;
+ unsigned short sum = 0;
+
+ for (i = COBT_CMOS_CKS_START; i <= COBT_CMOS_CKS_END; ++i) {
+ if ((i == COBT_CMOS_CHECKSUM) || (i == (COBT_CMOS_CHECKSUM+1)))
+ continue;
+
+ sum += __nvram_read_byte(i);
+ }
+
+ __nvram_write_byte(sum >> 8, COBT_CMOS_CHECKSUM);
+ __nvram_write_byte(sum & 0xff, COBT_CMOS_CHECKSUM+1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static int cobalt_proc_infos(unsigned char *nvram, char *buffer, int *len,
+ off_t *begin, off_t offset, int size)
+{
+ int i;
+ unsigned int checksum;
+ unsigned int flags;
+ char sernum[14];
+ char *key = "cNoEbTaWlOtR!";
+ unsigned char bto_csum;
+
+ spin_lock_irq(&rtc_lock);
+ checksum = __nvram_check_checksum();
+ spin_unlock_irq(&rtc_lock);
+
+ PRINT_PROC("Checksum status: %svalid\n", checksum ? "" : "not ");
+
+ flags = nvram[COBT_CMOS_FLAG_BYTE_0] << 8
+ | nvram[COBT_CMOS_FLAG_BYTE_1];
+
+ PRINT_PROC("Console: %s\n",
+ flags & COBT_CMOS_CONSOLE_FLAG ? "on": "off");
+
+ PRINT_PROC("Firmware Debug Messages: %s\n",
+ flags & COBT_CMOS_DEBUG_FLAG ? "on": "off");
+
+ PRINT_PROC("Auto Prompt: %s\n",
+ flags & COBT_CMOS_AUTO_PROMPT_FLAG ? "on": "off");
+
+ PRINT_PROC("Shutdown Status: %s\n",
+ flags & COBT_CMOS_CLEAN_BOOT_FLAG ? "clean": "dirty");
+
+ PRINT_PROC("Hardware Probe: %s\n",
+ flags & COBT_CMOS_HW_NOPROBE_FLAG ? "partial": "full");
+
+ PRINT_PROC("System Fault: %sdetected\n",
+ flags & COBT_CMOS_SYSFAULT_FLAG ? "": "not ");
+
+ PRINT_PROC("Panic on OOPS: %s\n",
+ flags & COBT_CMOS_OOPSPANIC_FLAG ? "yes": "no");
+
+ PRINT_PROC("Delayed Cache Initialization: %s\n",
+ flags & COBT_CMOS_DELAY_CACHE_FLAG ? "yes": "no");
+
+ PRINT_PROC("Show Logo at Boot: %s\n",
+ flags & COBT_CMOS_NOLOGO_FLAG ? "no": "yes");
+
+ PRINT_PROC("Boot Method: ");
+ switch (nvram[COBT_CMOS_BOOT_METHOD]) {
+ case COBT_CMOS_BOOT_METHOD_DISK:
+ PRINT_PROC("disk\n");
+ break;
+
+ case COBT_CMOS_BOOT_METHOD_ROM:
+ PRINT_PROC("rom\n");
+ break;
+
+ case COBT_CMOS_BOOT_METHOD_NET:
+ PRINT_PROC("net\n");
+ break;
+
+ default:
+ PRINT_PROC("unknown\n");
+ break;
+ }
+
+ PRINT_PROC("Primary Boot Device: %d:%d\n",
+ nvram[COBT_CMOS_BOOT_DEV0_MAJ],
+ nvram[COBT_CMOS_BOOT_DEV0_MIN] );
+ PRINT_PROC("Secondary Boot Device: %d:%d\n",
+ nvram[COBT_CMOS_BOOT_DEV1_MAJ],
+ nvram[COBT_CMOS_BOOT_DEV1_MIN] );
+ PRINT_PROC("Tertiary Boot Device: %d:%d\n",
+ nvram[COBT_CMOS_BOOT_DEV2_MAJ],
+ nvram[COBT_CMOS_BOOT_DEV2_MIN] );
+
+ PRINT_PROC("Uptime: %d\n",
+ nvram[COBT_CMOS_UPTIME_0] << 24 |
+ nvram[COBT_CMOS_UPTIME_1] << 16 |
+ nvram[COBT_CMOS_UPTIME_2] << 8 |
+ nvram[COBT_CMOS_UPTIME_3]);
+
+ PRINT_PROC("Boot Count: %d\n",
+ nvram[COBT_CMOS_BOOTCOUNT_0] << 24 |
+ nvram[COBT_CMOS_BOOTCOUNT_1] << 16 |
+ nvram[COBT_CMOS_BOOTCOUNT_2] << 8 |
+ nvram[COBT_CMOS_BOOTCOUNT_3]);
+
+ /* 13 bytes of serial num */
+ for (i=0 ; i<13 ; i++) {
+ sernum[i] = nvram[COBT_CMOS_SYS_SERNUM_0 + i];
+ }
+ sernum[13] = '\0';
+
+ checksum = 0;
+ for (i=0 ; i<13 ; i++) {
+ checksum += sernum[i] ^ key[i];
+ }
+ checksum = ((checksum & 0x7f) ^ (0xd6)) & 0xff;
+
+ PRINT_PROC("Serial Number: %s", sernum);
+ if (checksum != nvram[COBT_CMOS_SYS_SERNUM_CSUM]) {
+ PRINT_PROC(" (invalid checksum)");
+ }
+ PRINT_PROC("\n");
+
+ PRINT_PROC("Rom Revison: %d.%d.%d\n", nvram[COBT_CMOS_ROM_REV_MAJ],
+ nvram[COBT_CMOS_ROM_REV_MIN], nvram[COBT_CMOS_ROM_REV_REV]);
+
+ PRINT_PROC("BTO Server: %d.%d.%d.%d", nvram[COBT_CMOS_BTO_IP_0],
+ nvram[COBT_CMOS_BTO_IP_1], nvram[COBT_CMOS_BTO_IP_2],
+ nvram[COBT_CMOS_BTO_IP_3]);
+ bto_csum = nvram[COBT_CMOS_BTO_IP_0] + nvram[COBT_CMOS_BTO_IP_1]
+ + nvram[COBT_CMOS_BTO_IP_2] + nvram[COBT_CMOS_BTO_IP_3];
+ if (bto_csum != nvram[COBT_CMOS_BTO_IP_CSUM]) {
+ PRINT_PROC(" (invalid checksum)");
+ }
+ PRINT_PROC("\n");
+
+ if (flags & COBT_CMOS_VERSION_FLAG
+ && nvram[COBT_CMOS_VERSION] >= COBT_CMOS_VER_BTOCODE) {
+ PRINT_PROC("BTO Code: 0x%x\n",
+ nvram[COBT_CMOS_BTO_CODE_0] << 24 |
+ nvram[COBT_CMOS_BTO_CODE_1] << 16 |
+ nvram[COBT_CMOS_BTO_CODE_2] << 8 |
+ nvram[COBT_CMOS_BTO_CODE_3]);
+ }
+
+ return 1;
+}
+#endif /* CONFIG_PROC_FS */
+
+#endif /* MACH == COBALT */
+
+#if MACH == ATARI
+
+static int
+atari_check_checksum(void)
+{
+ int i;
+ unsigned char sum = 0;
+
+ for (i = ATARI_CKS_RANGE_START; i <= ATARI_CKS_RANGE_END; ++i)
+ sum += __nvram_read_byte(i);
+ return (__nvram_read_byte(ATARI_CKS_LOC) == (~sum & 0xff) &&
+ __nvram_read_byte(ATARI_CKS_LOC + 1) == (sum & 0xff));
+}
+
+static void
+atari_set_checksum(void)
+{
+ int i;
+ unsigned char sum = 0;
+
+ for (i = ATARI_CKS_RANGE_START; i <= ATARI_CKS_RANGE_END; ++i)
+ sum += __nvram_read_byte(i);
+ __nvram_write_byte(~sum, ATARI_CKS_LOC);
+ __nvram_write_byte(sum, ATARI_CKS_LOC + 1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static struct {
+ unsigned char val;
+ char *name;
+} boot_prefs[] = {
+ { 0x80, "TOS" },
+ { 0x40, "ASV" },
+ { 0x20, "NetBSD (?)" },
+ { 0x10, "Linux" },
+ { 0x00, "unspecified" }
+};
+
+static char *languages[] = {
+ "English (US)",
+ "German",
+ "French",
+ "English (UK)",
+ "Spanish",
+ "Italian",
+ "6 (undefined)",
+ "Swiss (French)",
+ "Swiss (German)"
+};
+
+static char *dateformat[] = {
+ "MM%cDD%cYY",
+ "DD%cMM%cYY",
+ "YY%cMM%cDD",
+ "YY%cDD%cMM",
+ "4 (undefined)",
+ "5 (undefined)",
+ "6 (undefined)",
+ "7 (undefined)"
+};
+
+static char *colors[] = {
+ "2", "4", "16", "256", "65536", "??", "??", "??"
+};
+
+#define fieldsize(a) (sizeof(a)/sizeof(*a))
+
+static int
+atari_proc_infos(unsigned char *nvram, char *buffer, int *len,
+ off_t *begin, off_t offset, int size)
+{
+ int checksum = nvram_check_checksum();
+ int i;
+ unsigned vmode;
+
+ PRINT_PROC("Checksum status : %svalid\n", checksum ? "" : "not ");
+
+ PRINT_PROC("Boot preference : ");
+ for (i = fieldsize(boot_prefs) - 1; i >= 0; --i) {
+ if (nvram[1] == boot_prefs[i].val) {
+ PRINT_PROC("%s\n", boot_prefs[i].name);
+ break;
+ }
+ }
+ if (i < 0)
+ PRINT_PROC("0x%02x (undefined)\n", nvram[1]);
+
+ PRINT_PROC("SCSI arbitration : %s\n",
+ (nvram[16] & 0x80) ? "on" : "off");
+ PRINT_PROC("SCSI host ID : ");
+ if (nvram[16] & 0x80)
+ PRINT_PROC("%d\n", nvram[16] & 7);
+ else
+ PRINT_PROC("n/a\n");
+
+ /* the following entries are defined only for the Falcon */
+ if ((atari_mch_cookie >> 16) != ATARI_MCH_FALCON)
+ return 1;
+
+ PRINT_PROC("OS language : ");
+ if (nvram[6] < fieldsize(languages))
+ PRINT_PROC("%s\n", languages[nvram[6]]);
+ else
+ PRINT_PROC("%u (undefined)\n", nvram[6]);
+ PRINT_PROC("Keyboard language: ");
+ if (nvram[7] < fieldsize(languages))
+ PRINT_PROC("%s\n", languages[nvram[7]]);
+ else
+ PRINT_PROC("%u (undefined)\n", nvram[7]);
+ PRINT_PROC("Date format : ");
+ PRINT_PROC(dateformat[nvram[8] & 7],
+ nvram[9] ? nvram[9] : '/', nvram[9] ? nvram[9] : '/');
+ PRINT_PROC(", %dh clock\n", nvram[8] & 16 ? 24 : 12);
+ PRINT_PROC("Boot delay : ");
+ if (nvram[10] == 0)
+ PRINT_PROC("default");
+ else
+ PRINT_PROC("%ds%s\n", nvram[10],
+ nvram[10] < 8 ? ", no memory test" : "");
+
+ vmode = (nvram[14] << 8) || nvram[15];
+ PRINT_PROC("Video mode : %s colors, %d columns, %s %s monitor\n",
+ colors[vmode & 7],
+ vmode & 8 ? 80 : 40,
+ vmode & 16 ? "VGA" : "TV", vmode & 32 ? "PAL" : "NTSC");
+ PRINT_PROC(" %soverscan, compat. mode %s%s\n",
+ vmode & 64 ? "" : "no ",
+ vmode & 128 ? "on" : "off",
+ vmode & 256 ?
+ (vmode & 16 ? ", line doubling" : ", half screen") : "");
+
+ return 1;
+}
+#endif
+
+#endif /* MACH == ATARI */
+
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(__nvram_read_byte);
+EXPORT_SYMBOL(nvram_read_byte);
+EXPORT_SYMBOL(__nvram_write_byte);
+EXPORT_SYMBOL(nvram_write_byte);
+EXPORT_SYMBOL(__nvram_check_checksum);
+EXPORT_SYMBOL(nvram_check_checksum);
+EXPORT_SYMBOL(__nvram_set_checksum);
+EXPORT_SYMBOL(nvram_set_checksum);
+MODULE_ALIAS_MISCDEV(NVRAM_MINOR);
diff --git a/drivers/char/nwbutton.c b/drivers/char/nwbutton.c
new file mode 100644
index 000000000000..4083b781adbf
--- /dev/null
+++ b/drivers/char/nwbutton.c
@@ -0,0 +1,248 @@
+/*
+ * NetWinder Button Driver-
+ * Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+
+#define __NWBUTTON_C /* Tell the header file who we are */
+#include "nwbutton.h"
+
+static int button_press_count; /* The count of button presses */
+static struct timer_list button_timer; /* Times for the end of a sequence */
+static DECLARE_WAIT_QUEUE_HEAD(button_wait_queue); /* Used for blocking read */
+static char button_output_buffer[32]; /* Stores data to write out of device */
+static int bcount; /* The number of bytes in the buffer */
+static int bdelay = BUTTON_DELAY; /* The delay, in jiffies */
+static struct button_callback button_callback_list[32]; /* The callback list */
+static int callback_count; /* The number of callbacks registered */
+static int reboot_count = NUM_PRESSES_REBOOT; /* Number of presses to reboot */
+
+/*
+ * This function is called by other drivers to register a callback function
+ * to be called when a particular number of button presses occurs.
+ * The callback list is a static array of 32 entries (I somehow doubt many
+ * people are ever going to want to register more than 32 different actions
+ * to be performed by the kernel on different numbers of button presses ;).
+ * However, if an attempt to register a 33rd entry (perhaps a stuck loop
+ * somewhere registering the same entry over and over?) it will fail to
+ * do so and return -ENOMEM. If an attempt is made to register a null pointer,
+ * it will fail to do so and return -EINVAL.
+ * Because callbacks can be unregistered at random the list can become
+ * fragmented, so we need to search through the list until we find the first
+ * free entry.
+ *
+ * FIXME: Has anyone spotted any locking functions int his code recently ??
+ */
+
+int button_add_callback (void (*callback) (void), int count)
+{
+ int lp = 0;
+ if (callback_count == 32) {
+ return -ENOMEM;
+ }
+ if (!callback) {
+ return -EINVAL;
+ }
+ callback_count++;
+ for (; (button_callback_list [lp].callback); lp++);
+ button_callback_list [lp].callback = callback;
+ button_callback_list [lp].count = count;
+ return 0;
+}
+
+/*
+ * This function is called by other drivers to deregister a callback function.
+ * If you attempt to unregister a callback which does not exist, it will fail
+ * with -EINVAL. If there is more than one entry with the same address,
+ * because it searches the list from end to beginning, it will unregister the
+ * last one to be registered first (FILO- First In Last Out).
+ * Note that this is not neccessarily true if the entries are not submitted
+ * at the same time, because another driver could have unregistered a callback
+ * between the submissions creating a gap earlier in the list, which would
+ * be filled first at submission time.
+ */
+
+int button_del_callback (void (*callback) (void))
+{
+ int lp = 31;
+ if (!callback) {
+ return -EINVAL;
+ }
+ while (lp >= 0) {
+ if ((button_callback_list [lp].callback) == callback) {
+ button_callback_list [lp].callback = NULL;
+ button_callback_list [lp].count = 0;
+ callback_count--;
+ return 0;
+ };
+ lp--;
+ };
+ return -EINVAL;
+}
+
+/*
+ * This function is called by button_sequence_finished to search through the
+ * list of callback functions, and call any of them whose count argument
+ * matches the current count of button presses. It starts at the beginning
+ * of the list and works up to the end. It will refuse to follow a null
+ * pointer (which should never happen anyway).
+ */
+
+static void button_consume_callbacks (int bpcount)
+{
+ int lp = 0;
+ for (; lp <= 31; lp++) {
+ if ((button_callback_list [lp].count) == bpcount) {
+ if (button_callback_list [lp].callback) {
+ button_callback_list[lp].callback();
+ }
+ }
+ }
+}
+
+/*
+ * This function is called when the button_timer times out.
+ * ie. When you don't press the button for bdelay jiffies, this is taken to
+ * mean you have ended the sequence of key presses, and this function is
+ * called to wind things up (write the press_count out to /dev/button, call
+ * any matching registered function callbacks, initiate reboot, etc.).
+ */
+
+static void button_sequence_finished (unsigned long parameters)
+{
+#ifdef CONFIG_NWBUTTON_REBOOT /* Reboot using button is enabled */
+ if (button_press_count == reboot_count) {
+ kill_proc (1, SIGINT, 1); /* Ask init to reboot us */
+ }
+#endif /* CONFIG_NWBUTTON_REBOOT */
+ button_consume_callbacks (button_press_count);
+ bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
+ button_press_count = 0; /* Reset the button press counter */
+ wake_up_interruptible (&button_wait_queue);
+}
+
+/*
+ * This handler is called when the orange button is pressed (GPIO 10 of the
+ * SuperIO chip, which maps to logical IRQ 26). If the press_count is 0,
+ * this is the first press, so it starts a timer and increments the counter.
+ * If it is higher than 0, it deletes the old timer, starts a new one, and
+ * increments the counter.
+ */
+
+static irqreturn_t button_handler (int irq, void *dev_id, struct pt_regs *regs)
+{
+ if (button_press_count) {
+ del_timer (&button_timer);
+ }
+ button_press_count++;
+ init_timer (&button_timer);
+ button_timer.function = button_sequence_finished;
+ button_timer.expires = (jiffies + bdelay);
+ add_timer (&button_timer);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * This function is called when a user space program attempts to read
+ * /dev/nwbutton. It puts the device to sleep on the wait queue until
+ * button_sequence_finished writes some data to the buffer and flushes
+ * the queue, at which point it writes the data out to the device and
+ * returns the number of characters it has written. This function is
+ * reentrant, so that many processes can be attempting to read from the
+ * device at any one time.
+ */
+
+static int button_read (struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ interruptible_sleep_on (&button_wait_queue);
+ return (copy_to_user (buffer, &button_output_buffer, bcount))
+ ? -EFAULT : bcount;
+}
+
+/*
+ * This structure is the file operations structure, which specifies what
+ * callbacks functions the kernel should call when a user mode process
+ * attempts to perform these operations on the device.
+ */
+
+static struct file_operations button_fops = {
+ .owner = THIS_MODULE,
+ .read = button_read,
+};
+
+/*
+ * This structure is the misc device structure, which specifies the minor
+ * device number (158 in this case), the name of the device (for /proc/misc),
+ * and the address of the above file operations structure.
+ */
+
+static struct miscdevice button_misc_device = {
+ BUTTON_MINOR,
+ "nwbutton",
+ &button_fops,
+};
+
+/*
+ * This function is called to initialise the driver, either from misc.c at
+ * bootup if the driver is compiled into the kernel, or from init_module
+ * below at module insert time. It attempts to register the device node
+ * and the IRQ and fails with a warning message if either fails, though
+ * neither ever should because the device number and IRQ are unique to
+ * this driver.
+ */
+
+static int __init nwbutton_init(void)
+{
+ if (!machine_is_netwinder())
+ return -ENODEV;
+
+ printk (KERN_INFO "NetWinder Button Driver Version %s (C) Alex Holden "
+ "<alex@linuxhacker.org> 1998.\n", VERSION);
+
+ if (misc_register (&button_misc_device)) {
+ printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
+ "%d.\n", BUTTON_MINOR);
+ return -EBUSY;
+ }
+
+ if (request_irq (IRQ_NETWINDER_BUTTON, button_handler, SA_INTERRUPT,
+ "nwbutton", NULL)) {
+ printk (KERN_WARNING "nwbutton: IRQ %d is not free.\n",
+ IRQ_NETWINDER_BUTTON);
+ misc_deregister (&button_misc_device);
+ return -EIO;
+ }
+ return 0;
+}
+
+static void __exit nwbutton_exit (void)
+{
+ free_irq (IRQ_NETWINDER_BUTTON, NULL);
+ misc_deregister (&button_misc_device);
+}
+
+
+MODULE_AUTHOR("Alex Holden");
+MODULE_LICENSE("GPL");
+
+module_init(nwbutton_init);
+module_exit(nwbutton_exit);
diff --git a/drivers/char/nwbutton.h b/drivers/char/nwbutton.h
new file mode 100644
index 000000000000..ddb7b928dcbb
--- /dev/null
+++ b/drivers/char/nwbutton.h
@@ -0,0 +1,40 @@
+#ifndef __NWBUTTON_H
+#define __NWBUTTON_H
+
+/*
+ * NetWinder Button Driver-
+ * Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
+ */
+
+#ifdef __NWBUTTON_C /* Actually compiling the driver itself */
+
+/* Various defines: */
+
+#define NUM_PRESSES_REBOOT 2 /* How many presses to activate shutdown */
+#define BUTTON_DELAY 30 /* How many jiffies for sequence to end */
+#define VERSION "0.3" /* Driver version number */
+#define BUTTON_MINOR 158 /* Major 10, Minor 158, /dev/nwbutton */
+
+/* Structure definitions: */
+
+struct button_callback {
+ void (*callback) (void);
+ int count;
+};
+
+/* Function prototypes: */
+
+static void button_sequence_finished (unsigned long parameters);
+static irqreturn_t button_handler (int irq, void *dev_id, struct pt_regs *regs);
+int button_init (void);
+int button_add_callback (void (*callback) (void), int count);
+int button_del_callback (void (*callback) (void));
+static void button_consume_callbacks (int bpcount);
+
+#else /* Not compiling the driver itself */
+
+extern int button_add_callback (void (*callback) (void), int count);
+extern int button_del_callback (void (*callback) (void));
+
+#endif /* __NWBUTTON_C */
+#endif /* __NWBUTTON_H */
diff --git a/drivers/char/nwflash.c b/drivers/char/nwflash.c
new file mode 100644
index 000000000000..ca41d62b1d9d
--- /dev/null
+++ b/drivers/char/nwflash.c
@@ -0,0 +1,702 @@
+/*
+ * Flash memory interface rev.5 driver for the Intel
+ * Flash chips used on the NetWinder.
+ *
+ * 20/08/2000 RMK use __ioremap to map flash into virtual memory
+ * make a few more places use "volatile"
+ * 22/05/2001 RMK - Lock read against write
+ * - merge printk level changes (with mods) from Alan Cox.
+ * - use *ppos as the file position, not file->f_pos.
+ * - fix check for out of range pos and r/w size
+ *
+ * Please note that we are tampering with the only flash chip in the
+ * machine, which contains the bootup code. We therefore have the
+ * power to convert these machines into doorstops...
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+
+#include <asm/hardware/dec21285.h>
+#include <asm/io.h>
+#include <asm/leds.h>
+#include <asm/mach-types.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+/*****************************************************************************/
+#include <asm/nwflash.h>
+
+#define NWFLASH_VERSION "6.4"
+
+static void kick_open(void);
+static int get_flash_id(void);
+static int erase_block(int nBlock);
+static int write_block(unsigned long p, const char __user *buf, int count);
+
+#define KFLASH_SIZE 1024*1024 //1 Meg
+#define KFLASH_SIZE4 4*1024*1024 //4 Meg
+#define KFLASH_ID 0x89A6 //Intel flash
+#define KFLASH_ID4 0xB0D4 //Intel flash 4Meg
+
+static int flashdebug; //if set - we will display progress msgs
+
+static int gbWriteEnable;
+static int gbWriteBase64Enable;
+static volatile unsigned char *FLASH_BASE;
+static int gbFlashSize = KFLASH_SIZE;
+static DECLARE_MUTEX(nwflash_sem);
+
+extern spinlock_t gpio_lock;
+
+static int get_flash_id(void)
+{
+ volatile unsigned int c1, c2;
+
+ /*
+ * try to get flash chip ID
+ */
+ kick_open();
+ c2 = inb(0x80);
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x90;
+ udelay(15);
+ c1 = *(volatile unsigned char *) FLASH_BASE;
+ c2 = inb(0x80);
+
+ /*
+ * on 4 Meg flash the second byte is actually at offset 2...
+ */
+ if (c1 == 0xB0)
+ c2 = *(volatile unsigned char *) (FLASH_BASE + 2);
+ else
+ c2 = *(volatile unsigned char *) (FLASH_BASE + 1);
+
+ c2 += (c1 << 8);
+
+ /*
+ * set it back to read mode
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0xFF;
+
+ if (c2 == KFLASH_ID4)
+ gbFlashSize = KFLASH_SIZE4;
+
+ return c2;
+}
+
+static int flash_ioctl(struct inode *inodep, struct file *filep, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case CMD_WRITE_DISABLE:
+ gbWriteBase64Enable = 0;
+ gbWriteEnable = 0;
+ break;
+
+ case CMD_WRITE_ENABLE:
+ gbWriteEnable = 1;
+ break;
+
+ case CMD_WRITE_BASE64K_ENABLE:
+ gbWriteBase64Enable = 1;
+ break;
+
+ default:
+ gbWriteBase64Enable = 0;
+ gbWriteEnable = 0;
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static ssize_t flash_read(struct file *file, char __user *buf, size_t size,
+ loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ unsigned int count = size;
+ int ret = 0;
+
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_read: flash_read: offset=0x%lX, "
+ "buffer=%p, count=0x%X.\n", p, buf, count);
+
+ if (count)
+ ret = -ENXIO;
+
+ if (p < gbFlashSize) {
+ if (count > gbFlashSize - p)
+ count = gbFlashSize - p;
+
+ /*
+ * We now lock against reads and writes. --rmk
+ */
+ if (down_interruptible(&nwflash_sem))
+ return -ERESTARTSYS;
+
+ ret = copy_to_user(buf, (void *)(FLASH_BASE + p), count);
+ if (ret == 0) {
+ ret = count;
+ *ppos += count;
+ } else
+ ret = -EFAULT;
+ up(&nwflash_sem);
+ }
+ return ret;
+}
+
+static ssize_t flash_write(struct file *file, const char __user *buf,
+ size_t size, loff_t * ppos)
+{
+ unsigned long p = *ppos;
+ unsigned int count = size;
+ int written;
+ int nBlock, temp, rc;
+ int i, j;
+
+ if (flashdebug)
+ printk("flash_write: offset=0x%lX, buffer=0x%p, count=0x%X.\n",
+ p, buf, count);
+
+ if (!gbWriteEnable)
+ return -EINVAL;
+
+ if (p < 64 * 1024 && (!gbWriteBase64Enable))
+ return -EINVAL;
+
+ /*
+ * check for out of range pos or count
+ */
+ if (p >= gbFlashSize)
+ return count ? -ENXIO : 0;
+
+ if (count > gbFlashSize - p)
+ count = gbFlashSize - p;
+
+ if (!access_ok(VERIFY_READ, buf, count))
+ return -EFAULT;
+
+ /*
+ * We now lock against reads and writes. --rmk
+ */
+ if (down_interruptible(&nwflash_sem))
+ return -ERESTARTSYS;
+
+ written = 0;
+
+ leds_event(led_claim);
+ leds_event(led_green_on);
+
+ nBlock = (int) p >> 16; //block # of 64K bytes
+
+ /*
+ * # of 64K blocks to erase and write
+ */
+ temp = ((int) (p + count) >> 16) - nBlock + 1;
+
+ /*
+ * write ends at exactly 64k boundary?
+ */
+ if (((int) (p + count) & 0xFFFF) == 0)
+ temp -= 1;
+
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_write: writing %d block(s) "
+ "starting at %d.\n", temp, nBlock);
+
+ for (; temp; temp--, nBlock++) {
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_write: erasing block %d.\n", nBlock);
+
+ /*
+ * first we have to erase the block(s), where we will write...
+ */
+ i = 0;
+ j = 0;
+ RetryBlock:
+ do {
+ rc = erase_block(nBlock);
+ i++;
+ } while (rc && i < 10);
+
+ if (rc) {
+ printk(KERN_ERR "flash_write: erase error %x\n", rc);
+ break;
+ }
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_write: writing offset %lX, "
+ "from buf %p, bytes left %X.\n", p, buf,
+ count - written);
+
+ /*
+ * write_block will limit write to space left in this block
+ */
+ rc = write_block(p, buf, count - written);
+ j++;
+
+ /*
+ * if somehow write verify failed? Can't happen??
+ */
+ if (!rc) {
+ /*
+ * retry up to 10 times
+ */
+ if (j < 10)
+ goto RetryBlock;
+ else
+ /*
+ * else quit with error...
+ */
+ rc = -1;
+
+ }
+ if (rc < 0) {
+ printk(KERN_ERR "flash_write: write error %X\n", rc);
+ break;
+ }
+ p += rc;
+ buf += rc;
+ written += rc;
+ *ppos += rc;
+
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_write: written 0x%X bytes OK.\n", written);
+ }
+
+ /*
+ * restore reg on exit
+ */
+ leds_event(led_release);
+
+ up(&nwflash_sem);
+
+ return written;
+}
+
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t flash_llseek(struct file *file, loff_t offset, int orig)
+{
+ loff_t ret;
+
+ lock_kernel();
+ if (flashdebug)
+ printk(KERN_DEBUG "flash_llseek: offset=0x%X, orig=0x%X.\n",
+ (unsigned int) offset, orig);
+
+ switch (orig) {
+ case 0:
+ if (offset < 0) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if ((unsigned int) offset > gbFlashSize) {
+ ret = -EINVAL;
+ break;
+ }
+
+ file->f_pos = (unsigned int) offset;
+ ret = file->f_pos;
+ break;
+ case 1:
+ if ((file->f_pos + offset) > gbFlashSize) {
+ ret = -EINVAL;
+ break;
+ }
+ if ((file->f_pos + offset) < 0) {
+ ret = -EINVAL;
+ break;
+ }
+ file->f_pos += offset;
+ ret = file->f_pos;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ unlock_kernel();
+ return ret;
+}
+
+
+/*
+ * assume that main Write routine did the parameter checking...
+ * so just go ahead and erase, what requested!
+ */
+
+static int erase_block(int nBlock)
+{
+ volatile unsigned int c1;
+ volatile unsigned char *pWritePtr;
+ unsigned long timeout;
+ int temp, temp1;
+
+ /*
+ * orange LED == erase
+ */
+ leds_event(led_amber_on);
+
+ /*
+ * reset footbridge to the correct offset 0 (...0..3)
+ */
+ *CSR_ROMWRITEREG = 0;
+
+ /*
+ * dummy ROM read
+ */
+ c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+ kick_open();
+ /*
+ * reset status if old errors
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+ /*
+ * erase a block...
+ * aim at the middle of a current block...
+ */
+ pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + 0x8000 + (nBlock << 16)));
+ /*
+ * dummy read
+ */
+ c1 = *pWritePtr;
+
+ kick_open();
+ /*
+ * erase
+ */
+ *(volatile unsigned char *) pWritePtr = 0x20;
+
+ /*
+ * confirm
+ */
+ *(volatile unsigned char *) pWritePtr = 0xD0;
+
+ /*
+ * wait 10 ms
+ */
+ msleep(10);
+
+ /*
+ * wait while erasing in process (up to 10 sec)
+ */
+ timeout = jiffies + 10 * HZ;
+ c1 = 0;
+ while (!(c1 & 0x80) && time_before(jiffies, timeout)) {
+ msleep(10);
+ /*
+ * read any address
+ */
+ c1 = *(volatile unsigned char *) (pWritePtr);
+ // printk("Flash_erase: status=%X.\n",c1);
+ }
+
+ /*
+ * set flash for normal read access
+ */
+ kick_open();
+// *(volatile unsigned char*)(FLASH_BASE+0x8000) = 0xFF;
+ *(volatile unsigned char *) pWritePtr = 0xFF; //back to normal operation
+
+ /*
+ * check if erase errors were reported
+ */
+ if (c1 & 0x20) {
+ printk(KERN_ERR "flash_erase: err at %p\n", pWritePtr);
+
+ /*
+ * reset error
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+ return -2;
+ }
+
+ /*
+ * just to make sure - verify if erased OK...
+ */
+ msleep(10);
+
+ pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + (nBlock << 16)));
+
+ for (temp = 0; temp < 16 * 1024; temp++, pWritePtr += 4) {
+ if ((temp1 = *(volatile unsigned int *) pWritePtr) != 0xFFFFFFFF) {
+ printk(KERN_ERR "flash_erase: verify err at %p = %X\n",
+ pWritePtr, temp1);
+ return -1;
+ }
+ }
+
+ return 0;
+
+}
+
+/*
+ * write_block will limit number of bytes written to the space in this block
+ */
+static int write_block(unsigned long p, const char __user *buf, int count)
+{
+ volatile unsigned int c1;
+ volatile unsigned int c2;
+ unsigned char *pWritePtr;
+ unsigned int uAddress;
+ unsigned int offset;
+ unsigned long timeout;
+ unsigned long timeout1;
+
+ /*
+ * red LED == write
+ */
+ leds_event(led_amber_off);
+ leds_event(led_red_on);
+
+ pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p));
+
+ /*
+ * check if write will end in this block....
+ */
+ offset = p & 0xFFFF;
+
+ if (offset + count > 0x10000)
+ count = 0x10000 - offset;
+
+ /*
+ * wait up to 30 sec for this block
+ */
+ timeout = jiffies + 30 * HZ;
+
+ for (offset = 0; offset < count; offset++, pWritePtr++) {
+ uAddress = (unsigned int) pWritePtr;
+ uAddress &= 0xFFFFFFFC;
+ if (__get_user(c2, buf + offset))
+ return -EFAULT;
+
+ WriteRetry:
+ /*
+ * dummy read
+ */
+ c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+ /*
+ * kick open the write gate
+ */
+ kick_open();
+
+ /*
+ * program footbridge to the correct offset...0..3
+ */
+ *CSR_ROMWRITEREG = (unsigned int) pWritePtr & 3;
+
+ /*
+ * write cmd
+ */
+ *(volatile unsigned char *) (uAddress) = 0x40;
+
+ /*
+ * data to write
+ */
+ *(volatile unsigned char *) (uAddress) = c2;
+
+ /*
+ * get status
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x10000) = 0x70;
+
+ c1 = 0;
+
+ /*
+ * wait up to 1 sec for this byte
+ */
+ timeout1 = jiffies + 1 * HZ;
+
+ /*
+ * while not ready...
+ */
+ while (!(c1 & 0x80) && time_before(jiffies, timeout1))
+ c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+ /*
+ * if timeout getting status
+ */
+ if (time_after_eq(jiffies, timeout1)) {
+ kick_open();
+ /*
+ * reset err
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+ goto WriteRetry;
+ }
+ /*
+ * switch on read access, as a default flash operation mode
+ */
+ kick_open();
+ /*
+ * read access
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0xFF;
+
+ /*
+ * if hardware reports an error writing, and not timeout -
+ * reset the chip and retry
+ */
+ if (c1 & 0x10) {
+ kick_open();
+ /*
+ * reset err
+ */
+ *(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+ /*
+ * before timeout?
+ */
+ if (time_before(jiffies, timeout)) {
+ if (flashdebug)
+ printk(KERN_DEBUG "write_block: Retrying write at 0x%X)n",
+ pWritePtr - FLASH_BASE);
+
+ /*
+ * no LED == waiting
+ */
+ leds_event(led_amber_off);
+ /*
+ * wait couple ms
+ */
+ msleep(10);
+ /*
+ * red LED == write
+ */
+ leds_event(led_red_on);
+
+ goto WriteRetry;
+ } else {
+ printk(KERN_ERR "write_block: timeout at 0x%X\n",
+ pWritePtr - FLASH_BASE);
+ /*
+ * return error -2
+ */
+ return -2;
+
+ }
+ }
+ }
+
+ /*
+ * green LED == read/verify
+ */
+ leds_event(led_amber_off);
+ leds_event(led_green_on);
+
+ msleep(10);
+
+ pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p));
+
+ for (offset = 0; offset < count; offset++) {
+ char c, c1;
+ if (__get_user(c, buf))
+ return -EFAULT;
+ buf++;
+ if ((c1 = *pWritePtr++) != c) {
+ printk(KERN_ERR "write_block: verify error at 0x%X (%02X!=%02X)\n",
+ pWritePtr - FLASH_BASE, c1, c);
+ return 0;
+ }
+ }
+
+ return count;
+}
+
+
+static void kick_open(void)
+{
+ unsigned long flags;
+
+ /*
+ * we want to write a bit pattern XXX1 to Xilinx to enable
+ * the write gate, which will be open for about the next 2ms.
+ */
+ spin_lock_irqsave(&gpio_lock, flags);
+ cpld_modify(1, 1);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ /*
+ * let the ISA bus to catch on...
+ */
+ udelay(25);
+}
+
+static struct file_operations flash_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = flash_llseek,
+ .read = flash_read,
+ .write = flash_write,
+ .ioctl = flash_ioctl,
+};
+
+static struct miscdevice flash_miscdev =
+{
+ FLASH_MINOR,
+ "nwflash",
+ &flash_fops
+};
+
+static int __init nwflash_init(void)
+{
+ int ret = -ENODEV;
+
+ if (machine_is_netwinder()) {
+ int id;
+
+ FLASH_BASE = ioremap(DC21285_FLASH, KFLASH_SIZE4);
+ if (!FLASH_BASE)
+ goto out;
+
+ id = get_flash_id();
+ if ((id != KFLASH_ID) && (id != KFLASH_ID4)) {
+ ret = -ENXIO;
+ iounmap((void *)FLASH_BASE);
+ printk("Flash: incorrect ID 0x%04X.\n", id);
+ goto out;
+ }
+
+ printk("Flash ROM driver v.%s, flash device ID 0x%04X, size %d Mb.\n",
+ NWFLASH_VERSION, id, gbFlashSize / (1024 * 1024));
+
+ ret = misc_register(&flash_miscdev);
+ if (ret < 0) {
+ iounmap((void *)FLASH_BASE);
+ }
+ }
+out:
+ return ret;
+}
+
+static void __exit nwflash_exit(void)
+{
+ misc_deregister(&flash_miscdev);
+ iounmap((void *)FLASH_BASE);
+}
+
+MODULE_LICENSE("GPL");
+
+module_param(flashdebug, bool, 0644);
+
+module_init(nwflash_init);
+module_exit(nwflash_exit);
diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig
new file mode 100644
index 000000000000..d22bfdc13563
--- /dev/null
+++ b/drivers/char/pcmcia/Kconfig
@@ -0,0 +1,22 @@
+#
+# PCMCIA character device configuration
+#
+
+menu "PCMCIA character devices"
+ depends on HOTPLUG && PCMCIA!=n
+
+config SYNCLINK_CS
+ tristate "SyncLink PC Card support"
+ depends on PCMCIA
+ help
+ Enable support for the SyncLink PC Card serial adapter, running
+ asynchronous and HDLC communications up to 512Kbps. The port is
+ selectable for RS-232, V.35, RS-449, RS-530, and X.21
+
+ This driver may be built as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called synclinkmp. If you want to do that, say M
+ here.
+
+endmenu
+
diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile
new file mode 100644
index 000000000000..1fcd4c591958
--- /dev/null
+++ b/drivers/char/pcmcia/Makefile
@@ -0,0 +1,7 @@
+#
+# drivers/char/pcmcia/Makefile
+#
+# Makefile for the Linux PCMCIA char device drivers.
+#
+
+obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o
diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c
new file mode 100644
index 000000000000..1c8d866a49dc
--- /dev/null
+++ b/drivers/char/pcmcia/synclink_cs.c
@@ -0,0 +1,4611 @@
+/*
+ * linux/drivers/char/pcmcia/synclink_cs.c
+ *
+ * $Id: synclink_cs.c,v 4.26 2004/08/11 19:30:02 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink PC Card
+ * multiprotocol serial adapter.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+#if defined(__i386__)
+# define BREAKPOINT() asm(" int $3");
+#else
+# define BREAKPOINT() { }
+#endif
+
+#define MAX_DEVICE_COUNT 4
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+static MGSL_PARAMS default_params = {
+ MGSL_MODE_HDLC, /* unsigned long mode */
+ 0, /* unsigned char loopback; */
+ HDLC_FLAG_UNDERRUN_ABORT15, /* unsigned short flags; */
+ HDLC_ENCODING_NRZI_SPACE, /* unsigned char encoding; */
+ 0, /* unsigned long clock_speed; */
+ 0xff, /* unsigned char addr_filter; */
+ HDLC_CRC_16_CCITT, /* unsigned short crc_type; */
+ HDLC_PREAMBLE_LENGTH_8BITS, /* unsigned char preamble_length; */
+ HDLC_PREAMBLE_PATTERN_NONE, /* unsigned char preamble; */
+ 9600, /* unsigned long data_rate; */
+ 8, /* unsigned char data_bits; */
+ 1, /* unsigned char stop_bits; */
+ ASYNC_PARITY_NONE /* unsigned char parity; */
+};
+
+typedef struct
+{
+ int count;
+ unsigned char status;
+ char data[1];
+} RXBUF;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE 1
+#define BH_TRANSMIT 2
+#define BH_STATUS 4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct _input_signal_events {
+ int ri_up;
+ int ri_down;
+ int dsr_up;
+ int dsr_down;
+ int dcd_up;
+ int dcd_down;
+ int cts_up;
+ int cts_down;
+};
+
+
+/*
+ * Device instance data structure
+ */
+
+typedef struct _mgslpc_info {
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ int magic;
+ int flags;
+ int count; /* count of opens */
+ int line;
+ unsigned short close_delay;
+ unsigned short closing_wait; /* time to wait before closing */
+
+ struct mgsl_icount icount;
+
+ struct tty_struct *tty;
+ int timeout;
+ int x_char; /* xon/xoff character */
+ int blocked_open; /* # of blocked opens */
+ unsigned char read_status_mask;
+ unsigned char ignore_status_mask;
+
+ unsigned char *tx_buf;
+ int tx_put;
+ int tx_get;
+ int tx_count;
+
+ /* circular list of fixed length rx buffers */
+
+ unsigned char *rx_buf; /* memory allocated for all rx buffers */
+ int rx_buf_total_size; /* size of memory allocated for rx buffers */
+ int rx_put; /* index of next empty rx buffer */
+ int rx_get; /* index of next full rx buffer */
+ int rx_buf_size; /* size in bytes of single rx buffer */
+ int rx_buf_count; /* total number of rx buffers */
+ int rx_frame_count; /* number of full rx buffers */
+
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+
+ wait_queue_head_t status_event_wait_q;
+ wait_queue_head_t event_wait_q;
+ struct timer_list tx_timer; /* HDLC transmit timeout timer */
+ struct _mgslpc_info *next_device; /* device list link */
+
+ unsigned short imra_value;
+ unsigned short imrb_value;
+ unsigned char pim_value;
+
+ spinlock_t lock;
+ struct work_struct task; /* task structure for scheduling bh */
+
+ u32 max_frame_size;
+
+ u32 pending_bh;
+
+ int bh_running;
+ int bh_requested;
+
+ int dcd_chkcount; /* check counts to prevent */
+ int cts_chkcount; /* too many IRQs if a signal */
+ int dsr_chkcount; /* is floating */
+ int ri_chkcount;
+
+ int rx_enabled;
+ int rx_overflow;
+
+ int tx_enabled;
+ int tx_active;
+ int tx_aborting;
+ u32 idle_mode;
+
+ int if_mode; /* serial interface selection (RS-232, v.35 etc) */
+
+ char device_name[25]; /* device instance name */
+
+ unsigned int io_base; /* base I/O address of adapter */
+ unsigned int irq_level;
+
+ MGSL_PARAMS params; /* communications parameters */
+
+ unsigned char serial_signals; /* current serial signal states */
+
+ char irq_occurred; /* for diagnostics use */
+ char testing_irq;
+ unsigned int init_error; /* startup error (DIAGS) */
+
+ char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+ BOOLEAN drop_rts_on_tx_done;
+
+ struct _input_signal_events input_signal_events;
+
+ /* PCMCIA support */
+ dev_link_t link;
+ dev_node_t node;
+ int stop;
+
+ /* SPPP/Cisco HDLC device parts */
+ int netcount;
+ int dosyncppp;
+ spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+ struct net_device *netdev;
+#endif
+
+} MGSLPC_INFO;
+
+#define MGSLPC_MAGIC 0x5402
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+#define TXBUFSIZE 4096
+
+
+#define CHA 0x00 /* channel A offset */
+#define CHB 0x40 /* channel B offset */
+
+/*
+ * FIXME: PPC has PVR defined in asm/reg.h. For now we just undef it.
+ */
+#undef PVR
+
+#define RXFIFO 0
+#define TXFIFO 0
+#define STAR 0x20
+#define CMDR 0x20
+#define RSTA 0x21
+#define PRE 0x21
+#define MODE 0x22
+#define TIMR 0x23
+#define XAD1 0x24
+#define XAD2 0x25
+#define RAH1 0x26
+#define RAH2 0x27
+#define DAFO 0x27
+#define RAL1 0x28
+#define RFC 0x28
+#define RHCR 0x29
+#define RAL2 0x29
+#define RBCL 0x2a
+#define XBCL 0x2a
+#define RBCH 0x2b
+#define XBCH 0x2b
+#define CCR0 0x2c
+#define CCR1 0x2d
+#define CCR2 0x2e
+#define CCR3 0x2f
+#define VSTR 0x34
+#define BGR 0x34
+#define RLCR 0x35
+#define AML 0x36
+#define AMH 0x37
+#define GIS 0x38
+#define IVA 0x38
+#define IPC 0x39
+#define ISR 0x3a
+#define IMR 0x3a
+#define PVR 0x3c
+#define PIS 0x3d
+#define PIM 0x3d
+#define PCR 0x3e
+#define CCR4 0x3f
+
+// IMR/ISR
+
+#define IRQ_BREAK_ON BIT15 // rx break detected
+#define IRQ_DATAOVERRUN BIT14 // receive data overflow
+#define IRQ_ALLSENT BIT13 // all sent
+#define IRQ_UNDERRUN BIT12 // transmit data underrun
+#define IRQ_TIMER BIT11 // timer interrupt
+#define IRQ_CTS BIT10 // CTS status change
+#define IRQ_TXREPEAT BIT9 // tx message repeat
+#define IRQ_TXFIFO BIT8 // transmit pool ready
+#define IRQ_RXEOM BIT7 // receive message end
+#define IRQ_EXITHUNT BIT6 // receive frame start
+#define IRQ_RXTIME BIT6 // rx char timeout
+#define IRQ_DCD BIT2 // carrier detect status change
+#define IRQ_OVERRUN BIT1 // receive frame overflow
+#define IRQ_RXFIFO BIT0 // receive pool full
+
+// STAR
+
+#define XFW BIT6 // transmit FIFO write enable
+#define CEC BIT2 // command executing
+#define CTS BIT1 // CTS state
+
+#define PVR_DTR BIT0
+#define PVR_DSR BIT1
+#define PVR_RI BIT2
+#define PVR_AUTOCTS BIT3
+#define PVR_RS232 0x20 /* 0010b */
+#define PVR_V35 0xe0 /* 1110b */
+#define PVR_RS422 0x40 /* 0100b */
+
+/* Register access functions */
+
+#define write_reg(info, reg, val) outb((val),(info)->io_base + (reg))
+#define read_reg(info, reg) inb((info)->io_base + (reg))
+
+#define read_reg16(info, reg) inw((info)->io_base + (reg))
+#define write_reg16(info, reg, val) outw((val), (info)->io_base + (reg))
+
+#define set_reg_bits(info, reg, mask) \
+ write_reg(info, (reg), \
+ (unsigned char) (read_reg(info, (reg)) | (mask)))
+#define clear_reg_bits(info, reg, mask) \
+ write_reg(info, (reg), \
+ (unsigned char) (read_reg(info, (reg)) & ~(mask)))
+/*
+ * interrupt enable/disable routines
+ */
+static void irq_disable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask)
+{
+ if (channel == CHA) {
+ info->imra_value |= mask;
+ write_reg16(info, CHA + IMR, info->imra_value);
+ } else {
+ info->imrb_value |= mask;
+ write_reg16(info, CHB + IMR, info->imrb_value);
+ }
+}
+static void irq_enable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask)
+{
+ if (channel == CHA) {
+ info->imra_value &= ~mask;
+ write_reg16(info, CHA + IMR, info->imra_value);
+ } else {
+ info->imrb_value &= ~mask;
+ write_reg16(info, CHB + IMR, info->imrb_value);
+ }
+}
+
+#define port_irq_disable(info, mask) \
+ { info->pim_value |= (mask); write_reg(info, PIM, info->pim_value); }
+
+#define port_irq_enable(info, mask) \
+ { info->pim_value &= ~(mask); write_reg(info, PIM, info->pim_value); }
+
+static void rx_start(MGSLPC_INFO *info);
+static void rx_stop(MGSLPC_INFO *info);
+
+static void tx_start(MGSLPC_INFO *info);
+static void tx_stop(MGSLPC_INFO *info);
+static void tx_set_idle(MGSLPC_INFO *info);
+
+static void get_signals(MGSLPC_INFO *info);
+static void set_signals(MGSLPC_INFO *info);
+
+static void reset_device(MGSLPC_INFO *info);
+
+static void hdlc_mode(MGSLPC_INFO *info);
+static void async_mode(MGSLPC_INFO *info);
+
+static void tx_timeout(unsigned long context);
+
+static int ioctl_common(MGSLPC_INFO *info, unsigned int cmd, unsigned long arg);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(MGSLPC_INFO *info);
+static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size);
+static int hdlcdev_init(MGSLPC_INFO *info);
+static void hdlcdev_exit(MGSLPC_INFO *info);
+#endif
+
+static void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit);
+
+static BOOLEAN register_test(MGSLPC_INFO *info);
+static BOOLEAN irq_test(MGSLPC_INFO *info);
+static int adapter_test(MGSLPC_INFO *info);
+
+static int claim_resources(MGSLPC_INFO *info);
+static void release_resources(MGSLPC_INFO *info);
+static void mgslpc_add_device(MGSLPC_INFO *info);
+static void mgslpc_remove_device(MGSLPC_INFO *info);
+
+static int rx_get_frame(MGSLPC_INFO *info);
+static void rx_reset_buffers(MGSLPC_INFO *info);
+static int rx_alloc_buffers(MGSLPC_INFO *info);
+static void rx_free_buffers(MGSLPC_INFO *info);
+
+static irqreturn_t mgslpc_isr(int irq, void *dev_id, struct pt_regs * regs);
+
+/*
+ * Bottom half interrupt handlers
+ */
+static void bh_handler(void* Context);
+static void bh_transmit(MGSLPC_INFO *info);
+static void bh_status(MGSLPC_INFO *info);
+
+/*
+ * ioctl handlers
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file);
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static int get_stats(MGSLPC_INFO *info, struct mgsl_icount __user *user_icount);
+static int get_params(MGSLPC_INFO *info, MGSL_PARAMS __user *user_params);
+static int set_params(MGSLPC_INFO *info, MGSL_PARAMS __user *new_params);
+static int get_txidle(MGSLPC_INFO *info, int __user *idle_mode);
+static int set_txidle(MGSLPC_INFO *info, int idle_mode);
+static int set_txenable(MGSLPC_INFO *info, int enable);
+static int tx_abort(MGSLPC_INFO *info);
+static int set_rxenable(MGSLPC_INFO *info, int enable);
+static int wait_events(MGSLPC_INFO *info, int __user *mask);
+
+static MGSLPC_INFO *mgslpc_device_list = NULL;
+static int mgslpc_device_count = 0;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load=0;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor=0;
+
+static int debug_level = 0;
+static int maxframe[MAX_DEVICE_COUNT] = {0,};
+static int dosyncppp[MAX_DEVICE_COUNT] = {1,1,1,1};
+
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+
+MODULE_LICENSE("GPL");
+
+static char *driver_name = "SyncLink PC Card driver";
+static char *driver_version = "$Revision: 4.26 $";
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+static void mgslpc_change_params(MGSLPC_INFO *info);
+static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout);
+
+/* PCMCIA prototypes */
+
+static void mgslpc_config(dev_link_t *link);
+static void mgslpc_release(u_long arg);
+static int mgslpc_event(event_t event, int priority,
+ event_callback_args_t *args);
+static dev_link_t *mgslpc_attach(void);
+static void mgslpc_detach(dev_link_t *);
+
+static dev_info_t dev_info = "synclink_cs";
+static dev_link_t *dev_list = NULL;
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* mgslpc_get_text_ptr(void)
+{
+ return mgslpc_get_text_ptr;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_flush_buffer - flush line discipline receive buffers
+ * ldisc_receive_buf - pass receive data to line discipline
+ */
+
+static void ldisc_flush_buffer(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+}
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+ const __u8 *data, char *flags, int count)
+{
+ struct tty_ldisc *ld;
+ if (!tty)
+ return;
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->receive_buf)
+ ld->receive_buf(tty, data, flags, count);
+ tty_ldisc_deref(ld);
+ }
+}
+
+static dev_link_t *mgslpc_attach(void)
+{
+ MGSLPC_INFO *info;
+ dev_link_t *link;
+ client_reg_t client_reg;
+ int ret;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("mgslpc_attach\n");
+
+ info = (MGSLPC_INFO *)kmalloc(sizeof(MGSLPC_INFO), GFP_KERNEL);
+ if (!info) {
+ printk("Error can't allocate device instance data\n");
+ return NULL;
+ }
+
+ memset(info, 0, sizeof(MGSLPC_INFO));
+ info->magic = MGSLPC_MAGIC;
+ INIT_WORK(&info->task, bh_handler, info);
+ info->max_frame_size = 4096;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = 30*HZ;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->status_event_wait_q);
+ init_waitqueue_head(&info->event_wait_q);
+ spin_lock_init(&info->lock);
+ spin_lock_init(&info->netlock);
+ memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+ info->idle_mode = HDLC_TXIDLE_FLAGS;
+ info->imra_value = 0xffff;
+ info->imrb_value = 0xffff;
+ info->pim_value = 0xff;
+
+ link = &info->link;
+ link->priv = info;
+
+ /* Initialize the dev_link_t structure */
+
+ /* Interrupt setup */
+ link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
+ link->irq.IRQInfo1 = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
+ link->irq.Handler = NULL;
+
+ link->conf.Attributes = 0;
+ link->conf.Vcc = 50;
+ link->conf.IntType = INT_MEMORY_AND_IO;
+
+ /* Register with Card Services */
+ link->next = dev_list;
+ dev_list = link;
+
+ client_reg.dev_info = &dev_info;
+ client_reg.EventMask =
+ CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
+ CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
+ CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
+ client_reg.event_handler = &mgslpc_event;
+ client_reg.Version = 0x0210;
+ client_reg.event_callback_args.client_data = link;
+
+ ret = pcmcia_register_client(&link->handle, &client_reg);
+ if (ret != CS_SUCCESS) {
+ cs_error(link->handle, RegisterClient, ret);
+ mgslpc_detach(link);
+ return NULL;
+ }
+
+ mgslpc_add_device(info);
+
+ return link;
+}
+
+/* Card has been inserted.
+ */
+
+#define CS_CHECK(fn, ret) \
+do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0)
+
+static void mgslpc_config(dev_link_t *link)
+{
+ client_handle_t handle = link->handle;
+ MGSLPC_INFO *info = link->priv;
+ tuple_t tuple;
+ cisparse_t parse;
+ int last_fn, last_ret;
+ u_char buf[64];
+ config_info_t conf;
+ cistpl_cftable_entry_t dflt = { 0 };
+ cistpl_cftable_entry_t *cfg;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("mgslpc_config(0x%p)\n", link);
+
+ /* read CONFIG tuple to find its configuration registers */
+ tuple.DesiredTuple = CISTPL_CONFIG;
+ tuple.Attributes = 0;
+ tuple.TupleData = buf;
+ tuple.TupleDataMax = sizeof(buf);
+ tuple.TupleOffset = 0;
+ CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+ CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple));
+ CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse));
+ link->conf.ConfigBase = parse.config.base;
+ link->conf.Present = parse.config.rmask[0];
+
+ /* Configure card */
+ link->state |= DEV_CONFIG;
+
+ /* Look up the current Vcc */
+ CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf));
+ link->conf.Vcc = conf.Vcc;
+
+ /* get CIS configuration entry */
+
+ tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
+ CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+
+ cfg = &(parse.cftable_entry);
+ CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple));
+ CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse));
+
+ if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg;
+ if (cfg->index == 0)
+ goto cs_failed;
+
+ link->conf.ConfigIndex = cfg->index;
+ link->conf.Attributes |= CONF_ENABLE_IRQ;
+
+ /* IO window settings */
+ link->io.NumPorts1 = 0;
+ if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) {
+ cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io;
+ link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO;
+ if (!(io->flags & CISTPL_IO_8BIT))
+ link->io.Attributes1 = IO_DATA_PATH_WIDTH_16;
+ if (!(io->flags & CISTPL_IO_16BIT))
+ link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
+ link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK;
+ link->io.BasePort1 = io->win[0].base;
+ link->io.NumPorts1 = io->win[0].len;
+ CS_CHECK(RequestIO, pcmcia_request_io(link->handle, &link->io));
+ }
+
+ link->conf.Attributes = CONF_ENABLE_IRQ;
+ link->conf.Vcc = 50;
+ link->conf.IntType = INT_MEMORY_AND_IO;
+ link->conf.ConfigIndex = 8;
+ link->conf.Present = PRESENT_OPTION;
+
+ link->irq.Attributes |= IRQ_HANDLE_PRESENT;
+ link->irq.Handler = mgslpc_isr;
+ link->irq.Instance = info;
+ CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq));
+
+ CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf));
+
+ info->io_base = link->io.BasePort1;
+ info->irq_level = link->irq.AssignedIRQ;
+
+ /* add to linked list of devices */
+ sprintf(info->node.dev_name, "mgslpc0");
+ info->node.major = info->node.minor = 0;
+ link->dev = &info->node;
+
+ printk(KERN_INFO "%s: index 0x%02x:",
+ info->node.dev_name, link->conf.ConfigIndex);
+ if (link->conf.Attributes & CONF_ENABLE_IRQ)
+ printk(", irq %d", link->irq.AssignedIRQ);
+ if (link->io.NumPorts1)
+ printk(", io 0x%04x-0x%04x", link->io.BasePort1,
+ link->io.BasePort1+link->io.NumPorts1-1);
+ printk("\n");
+
+ link->state &= ~DEV_CONFIG_PENDING;
+ return;
+
+cs_failed:
+ cs_error(link->handle, last_fn, last_ret);
+ mgslpc_release((u_long)link);
+}
+
+/* Card has been removed.
+ * Unregister device and release PCMCIA configuration.
+ * If device is open, postpone until it is closed.
+ */
+static void mgslpc_release(u_long arg)
+{
+ dev_link_t *link = (dev_link_t *)arg;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("mgslpc_release(0x%p)\n", link);
+
+ /* Unlink the device chain */
+ link->dev = NULL;
+ link->state &= ~DEV_CONFIG;
+
+ pcmcia_release_configuration(link->handle);
+ if (link->io.NumPorts1)
+ pcmcia_release_io(link->handle, &link->io);
+ if (link->irq.AssignedIRQ)
+ pcmcia_release_irq(link->handle, &link->irq);
+ if (link->state & DEV_STALE_LINK)
+ mgslpc_detach(link);
+}
+
+static void mgslpc_detach(dev_link_t *link)
+{
+ dev_link_t **linkp;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("mgslpc_detach(0x%p)\n", link);
+
+ /* find device */
+ for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
+ if (*linkp == link) break;
+ if (*linkp == NULL)
+ return;
+
+ if (link->state & DEV_CONFIG) {
+ /* device is configured/active, mark it so when
+ * release() is called a proper detach() occurs.
+ */
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk(KERN_DEBUG "synclinkpc: detach postponed, '%s' "
+ "still locked\n", link->dev->dev_name);
+ link->state |= DEV_STALE_LINK;
+ return;
+ }
+
+ /* Break the link with Card Services */
+ if (link->handle)
+ pcmcia_deregister_client(link->handle);
+
+ /* Unlink device structure, and free it */
+ *linkp = link->next;
+ mgslpc_remove_device((MGSLPC_INFO *)link->priv);
+}
+
+static int mgslpc_event(event_t event, int priority,
+ event_callback_args_t *args)
+{
+ dev_link_t *link = args->client_data;
+ MGSLPC_INFO *info = link->priv;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("mgslpc_event(0x%06x)\n", event);
+
+ switch (event) {
+ case CS_EVENT_CARD_REMOVAL:
+ link->state &= ~DEV_PRESENT;
+ if (link->state & DEV_CONFIG) {
+ ((MGSLPC_INFO *)link->priv)->stop = 1;
+ mgslpc_release((u_long)link);
+ }
+ break;
+ case CS_EVENT_CARD_INSERTION:
+ link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
+ mgslpc_config(link);
+ break;
+ case CS_EVENT_PM_SUSPEND:
+ link->state |= DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_RESET_PHYSICAL:
+ /* Mark the device as stopped, to block IO until later */
+ info->stop = 1;
+ if (link->state & DEV_CONFIG)
+ pcmcia_release_configuration(link->handle);
+ break;
+ case CS_EVENT_PM_RESUME:
+ link->state &= ~DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_CARD_RESET:
+ if (link->state & DEV_CONFIG)
+ pcmcia_request_configuration(link->handle, &link->conf);
+ info->stop = 0;
+ break;
+ }
+ return 0;
+}
+
+static inline int mgslpc_paranoia_check(MGSLPC_INFO *info,
+ char *name, const char *routine)
+{
+#ifdef MGSLPC_PARANOIA_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for mgsl struct (%s) in %s\n";
+ static const char *badinfo =
+ "Warning: null mgslpc_info for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (info->magic != MGSLPC_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#else
+ if (!info)
+ return 1;
+#endif
+ return 0;
+}
+
+
+#define CMD_RXFIFO BIT7 // release current rx FIFO
+#define CMD_RXRESET BIT6 // receiver reset
+#define CMD_RXFIFO_READ BIT5
+#define CMD_START_TIMER BIT4
+#define CMD_TXFIFO BIT3 // release current tx FIFO
+#define CMD_TXEOM BIT1 // transmit end message
+#define CMD_TXRESET BIT0 // transmit reset
+
+static BOOLEAN wait_command_complete(MGSLPC_INFO *info, unsigned char channel)
+{
+ int i = 0;
+ /* wait for command completion */
+ while (read_reg(info, (unsigned char)(channel+STAR)) & BIT2) {
+ udelay(1);
+ if (i++ == 1000)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void issue_command(MGSLPC_INFO *info, unsigned char channel, unsigned char cmd)
+{
+ wait_command_complete(info, channel);
+ write_reg(info, (unsigned char) (channel + CMDR), cmd);
+}
+
+static void tx_pause(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (mgslpc_paranoia_check(info, tty->name, "tx_pause"))
+ return;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("tx_pause(%s)\n",info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (info->tx_enabled)
+ tx_stop(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+static void tx_release(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (mgslpc_paranoia_check(info, tty->name, "tx_release"))
+ return;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("tx_release(%s)\n",info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_enabled)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Return next bottom half action to perform.
+ * or 0 if nothing to do.
+ */
+static int bh_action(MGSLPC_INFO *info)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ if (info->pending_bh & BH_RECEIVE) {
+ info->pending_bh &= ~BH_RECEIVE;
+ rc = BH_RECEIVE;
+ } else if (info->pending_bh & BH_TRANSMIT) {
+ info->pending_bh &= ~BH_TRANSMIT;
+ rc = BH_TRANSMIT;
+ } else if (info->pending_bh & BH_STATUS) {
+ info->pending_bh &= ~BH_STATUS;
+ rc = BH_STATUS;
+ }
+
+ if (!rc) {
+ /* Mark BH routine as complete */
+ info->bh_running = 0;
+ info->bh_requested = 0;
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return rc;
+}
+
+void bh_handler(void* Context)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO*)Context;
+ int action;
+
+ if (!info)
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_BH)
+ printk( "%s(%d):bh_handler(%s) entry\n",
+ __FILE__,__LINE__,info->device_name);
+
+ info->bh_running = 1;
+
+ while((action = bh_action(info)) != 0) {
+
+ /* Process work item */
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):bh_handler() work item action=%d\n",
+ __FILE__,__LINE__,action);
+
+ switch (action) {
+
+ case BH_RECEIVE:
+ while(rx_get_frame(info));
+ break;
+ case BH_TRANSMIT:
+ bh_transmit(info);
+ break;
+ case BH_STATUS:
+ bh_status(info);
+ break;
+ default:
+ /* unknown work item ID */
+ printk("Unknown work item ID=%08X!\n", action);
+ break;
+ }
+ }
+
+ if (debug_level >= DEBUG_LEVEL_BH)
+ printk( "%s(%d):bh_handler(%s) exit\n",
+ __FILE__,__LINE__,info->device_name);
+}
+
+void bh_transmit(MGSLPC_INFO *info)
+{
+ struct tty_struct *tty = info->tty;
+ if (debug_level >= DEBUG_LEVEL_BH)
+ printk("bh_transmit() entry on %s\n", info->device_name);
+
+ if (tty) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+}
+
+void bh_status(MGSLPC_INFO *info)
+{
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+}
+
+/* eom: non-zero = end of frame */
+static void rx_ready_hdlc(MGSLPC_INFO *info, int eom)
+{
+ unsigned char data[2];
+ unsigned char fifo_count, read_count, i;
+ RXBUF *buf = (RXBUF*)(info->rx_buf + (info->rx_put * info->rx_buf_size));
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):rx_ready_hdlc(eom=%d)\n",__FILE__,__LINE__,eom);
+
+ if (!info->rx_enabled)
+ return;
+
+ if (info->rx_frame_count >= info->rx_buf_count) {
+ /* no more free buffers */
+ issue_command(info, CHA, CMD_RXRESET);
+ info->pending_bh |= BH_RECEIVE;
+ info->rx_overflow = 1;
+ info->icount.buf_overrun++;
+ return;
+ }
+
+ if (eom) {
+ /* end of frame, get FIFO count from RBCL register */
+ if (!(fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f)))
+ fifo_count = 32;
+ } else
+ fifo_count = 32;
+
+ do {
+ if (fifo_count == 1) {
+ read_count = 1;
+ data[0] = read_reg(info, CHA + RXFIFO);
+ } else {
+ read_count = 2;
+ *((unsigned short *) data) = read_reg16(info, CHA + RXFIFO);
+ }
+ fifo_count -= read_count;
+ if (!fifo_count && eom)
+ buf->status = data[--read_count];
+
+ for (i = 0; i < read_count; i++) {
+ if (buf->count >= info->max_frame_size) {
+ /* frame too large, reset receiver and reset current buffer */
+ issue_command(info, CHA, CMD_RXRESET);
+ buf->count = 0;
+ return;
+ }
+ *(buf->data + buf->count) = data[i];
+ buf->count++;
+ }
+ } while (fifo_count);
+
+ if (eom) {
+ info->pending_bh |= BH_RECEIVE;
+ info->rx_frame_count++;
+ info->rx_put++;
+ if (info->rx_put >= info->rx_buf_count)
+ info->rx_put = 0;
+ }
+ issue_command(info, CHA, CMD_RXFIFO);
+}
+
+static void rx_ready_async(MGSLPC_INFO *info, int tcd)
+{
+ unsigned char data, status;
+ int fifo_count;
+ struct tty_struct *tty = info->tty;
+ struct mgsl_icount *icount = &info->icount;
+
+ if (tcd) {
+ /* early termination, get FIFO count from RBCL register */
+ fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f);
+
+ /* Zero fifo count could mean 0 or 32 bytes available.
+ * If BIT5 of STAR is set then at least 1 byte is available.
+ */
+ if (!fifo_count && (read_reg(info,CHA+STAR) & BIT5))
+ fifo_count = 32;
+ } else
+ fifo_count = 32;
+
+ /* Flush received async data to receive data buffer. */
+ while (fifo_count) {
+ data = read_reg(info, CHA + RXFIFO);
+ status = read_reg(info, CHA + RXFIFO);
+ fifo_count -= 2;
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ break;
+
+ *tty->flip.char_buf_ptr = data;
+ icount->rx++;
+
+ *tty->flip.flag_buf_ptr = 0;
+
+ // if no frameing/crc error then save data
+ // BIT7:parity error
+ // BIT6:framing error
+
+ if (status & (BIT7 + BIT6)) {
+ if (status & BIT7)
+ icount->parity++;
+ else
+ icount->frame++;
+
+ /* discard char if tty control flags say so */
+ if (status & info->ignore_status_mask)
+ continue;
+
+ status &= info->read_status_mask;
+
+ if (status & BIT7)
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ else if (status & BIT6)
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ }
+
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+ issue_command(info, CHA, CMD_RXFIFO);
+
+ if (debug_level >= DEBUG_LEVEL_ISR) {
+ printk("%s(%d):rx_ready_async count=%d\n",
+ __FILE__,__LINE__,tty->flip.count);
+ printk("%s(%d):rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+ __FILE__,__LINE__,icount->rx,icount->brk,
+ icount->parity,icount->frame,icount->overrun);
+ }
+
+ if (tty->flip.count)
+ tty_flip_buffer_push(tty);
+}
+
+
+static void tx_done(MGSLPC_INFO *info)
+{
+ if (!info->tx_active)
+ return;
+
+ info->tx_active = 0;
+ info->tx_aborting = 0;
+
+ if (info->params.mode == MGSL_MODE_ASYNC)
+ return;
+
+ info->tx_count = info->tx_put = info->tx_get = 0;
+ del_timer(&info->tx_timer);
+
+ if (info->drop_rts_on_tx_done) {
+ get_signals(info);
+ if (info->serial_signals & SerialSignal_RTS) {
+ info->serial_signals &= ~SerialSignal_RTS;
+ set_signals(info);
+ }
+ info->drop_rts_on_tx_done = 0;
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ {
+ if (info->tty->stopped || info->tty->hw_stopped) {
+ tx_stop(info);
+ return;
+ }
+ info->pending_bh |= BH_TRANSMIT;
+ }
+}
+
+static void tx_ready(MGSLPC_INFO *info)
+{
+ unsigned char fifo_count = 32;
+ int c;
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):tx_ready(%s)\n", __FILE__,__LINE__,info->device_name);
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ if (!info->tx_active)
+ return;
+ } else {
+ if (info->tty->stopped || info->tty->hw_stopped) {
+ tx_stop(info);
+ return;
+ }
+ if (!info->tx_count)
+ info->tx_active = 0;
+ }
+
+ if (!info->tx_count)
+ return;
+
+ while (info->tx_count && fifo_count) {
+ c = min(2, min_t(int, fifo_count, min(info->tx_count, TXBUFSIZE - info->tx_get)));
+
+ if (c == 1) {
+ write_reg(info, CHA + TXFIFO, *(info->tx_buf + info->tx_get));
+ } else {
+ write_reg16(info, CHA + TXFIFO,
+ *((unsigned short*)(info->tx_buf + info->tx_get)));
+ }
+ info->tx_count -= c;
+ info->tx_get = (info->tx_get + c) & (TXBUFSIZE - 1);
+ fifo_count -= c;
+ }
+
+ if (info->params.mode == MGSL_MODE_ASYNC) {
+ if (info->tx_count < WAKEUP_CHARS)
+ info->pending_bh |= BH_TRANSMIT;
+ issue_command(info, CHA, CMD_TXFIFO);
+ } else {
+ if (info->tx_count)
+ issue_command(info, CHA, CMD_TXFIFO);
+ else
+ issue_command(info, CHA, CMD_TXFIFO + CMD_TXEOM);
+ }
+}
+
+static void cts_change(MGSLPC_INFO *info)
+{
+ get_signals(info);
+ if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ irq_disable(info, CHB, IRQ_CTS);
+ info->icount.cts++;
+ if (info->serial_signals & SerialSignal_CTS)
+ info->input_signal_events.cts_up++;
+ else
+ info->input_signal_events.cts_down++;
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ if (info->flags & ASYNC_CTS_FLOW) {
+ if (info->tty->hw_stopped) {
+ if (info->serial_signals & SerialSignal_CTS) {
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("CTS tx start...");
+ if (info->tty)
+ info->tty->hw_stopped = 0;
+ tx_start(info);
+ info->pending_bh |= BH_TRANSMIT;
+ return;
+ }
+ } else {
+ if (!(info->serial_signals & SerialSignal_CTS)) {
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("CTS tx stop...");
+ if (info->tty)
+ info->tty->hw_stopped = 1;
+ tx_stop(info);
+ }
+ }
+ }
+ info->pending_bh |= BH_STATUS;
+}
+
+static void dcd_change(MGSLPC_INFO *info)
+{
+ get_signals(info);
+ if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ irq_disable(info, CHB, IRQ_DCD);
+ info->icount.dcd++;
+ if (info->serial_signals & SerialSignal_DCD) {
+ info->input_signal_events.dcd_up++;
+ }
+ else
+ info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, info->netdev);
+#endif
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ if (info->flags & ASYNC_CHECK_CD) {
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s CD now %s...", info->device_name,
+ (info->serial_signals & SerialSignal_DCD) ? "on" : "off");
+ if (info->serial_signals & SerialSignal_DCD)
+ wake_up_interruptible(&info->open_wait);
+ else {
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("doing serial hangup...");
+ if (info->tty)
+ tty_hangup(info->tty);
+ }
+ }
+ info->pending_bh |= BH_STATUS;
+}
+
+static void dsr_change(MGSLPC_INFO *info)
+{
+ get_signals(info);
+ if ((info->dsr_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ port_irq_disable(info, PVR_DSR);
+ info->icount.dsr++;
+ if (info->serial_signals & SerialSignal_DSR)
+ info->input_signal_events.dsr_up++;
+ else
+ info->input_signal_events.dsr_down++;
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+ info->pending_bh |= BH_STATUS;
+}
+
+static void ri_change(MGSLPC_INFO *info)
+{
+ get_signals(info);
+ if ((info->ri_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ port_irq_disable(info, PVR_RI);
+ info->icount.rng++;
+ if (info->serial_signals & SerialSignal_RI)
+ info->input_signal_events.ri_up++;
+ else
+ info->input_signal_events.ri_down++;
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+ info->pending_bh |= BH_STATUS;
+}
+
+/* Interrupt service routine entry point.
+ *
+ * Arguments:
+ *
+ * irq interrupt number that caused interrupt
+ * dev_id device ID supplied during interrupt registration
+ * regs interrupted processor context
+ */
+static irqreturn_t mgslpc_isr(int irq, void *dev_id, struct pt_regs * regs)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)dev_id;
+ unsigned short isr;
+ unsigned char gis, pis;
+ int count=0;
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("mgslpc_isr(%d) entry.\n", irq);
+ if (!info)
+ return IRQ_NONE;
+
+ if (!(info->link.state & DEV_CONFIG))
+ return IRQ_HANDLED;
+
+ spin_lock(&info->lock);
+
+ while ((gis = read_reg(info, CHA + GIS))) {
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("mgslpc_isr %s gis=%04X\n", info->device_name,gis);
+
+ if ((gis & 0x70) || count > 1000) {
+ printk("synclink_cs:hardware failed or ejected\n");
+ break;
+ }
+ count++;
+
+ if (gis & (BIT1 + BIT0)) {
+ isr = read_reg16(info, CHB + ISR);
+ if (isr & IRQ_DCD)
+ dcd_change(info);
+ if (isr & IRQ_CTS)
+ cts_change(info);
+ }
+ if (gis & (BIT3 + BIT2))
+ {
+ isr = read_reg16(info, CHA + ISR);
+ if (isr & IRQ_TIMER) {
+ info->irq_occurred = 1;
+ irq_disable(info, CHA, IRQ_TIMER);
+ }
+
+ /* receive IRQs */
+ if (isr & IRQ_EXITHUNT) {
+ info->icount.exithunt++;
+ wake_up_interruptible(&info->event_wait_q);
+ }
+ if (isr & IRQ_BREAK_ON) {
+ info->icount.brk++;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(info->tty);
+ }
+ if (isr & IRQ_RXTIME) {
+ issue_command(info, CHA, CMD_RXFIFO_READ);
+ }
+ if (isr & (IRQ_RXEOM + IRQ_RXFIFO)) {
+ if (info->params.mode == MGSL_MODE_HDLC)
+ rx_ready_hdlc(info, isr & IRQ_RXEOM);
+ else
+ rx_ready_async(info, isr & IRQ_RXEOM);
+ }
+
+ /* transmit IRQs */
+ if (isr & IRQ_UNDERRUN) {
+ if (info->tx_aborting)
+ info->icount.txabort++;
+ else
+ info->icount.txunder++;
+ tx_done(info);
+ }
+ else if (isr & IRQ_ALLSENT) {
+ info->icount.txok++;
+ tx_done(info);
+ }
+ else if (isr & IRQ_TXFIFO)
+ tx_ready(info);
+ }
+ if (gis & BIT7) {
+ pis = read_reg(info, CHA + PIS);
+ if (pis & BIT1)
+ dsr_change(info);
+ if (pis & BIT2)
+ ri_change(info);
+ }
+ }
+
+ /* Request bottom half processing if there's something
+ * for it to do and the bh is not already running
+ */
+
+ if (info->pending_bh && !info->bh_running && !info->bh_requested) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s queueing bh task.\n",
+ __FILE__,__LINE__,info->device_name);
+ schedule_work(&info->task);
+ info->bh_requested = 1;
+ }
+
+ spin_unlock(&info->lock);
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):mgslpc_isr(%d)exit.\n",
+ __FILE__,__LINE__,irq);
+
+ return IRQ_HANDLED;
+}
+
+/* Initialize and start device.
+ */
+static int startup(MGSLPC_INFO * info)
+{
+ int retval = 0;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):startup(%s)\n",__FILE__,__LINE__,info->device_name);
+
+ if (info->flags & ASYNC_INITIALIZED)
+ return 0;
+
+ if (!info->tx_buf) {
+ /* allocate a page of memory for a transmit buffer */
+ info->tx_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
+ if (!info->tx_buf) {
+ printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+ __FILE__,__LINE__,info->device_name);
+ return -ENOMEM;
+ }
+ }
+
+ info->pending_bh = 0;
+
+ init_timer(&info->tx_timer);
+ info->tx_timer.data = (unsigned long)info;
+ info->tx_timer.function = tx_timeout;
+
+ /* Allocate and claim adapter resources */
+ retval = claim_resources(info);
+
+ /* perform existance check and diagnostics */
+ if ( !retval )
+ retval = adapter_test(info);
+
+ if ( retval ) {
+ if (capable(CAP_SYS_ADMIN) && info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ release_resources(info);
+ return retval;
+ }
+
+ /* program hardware for current parameters */
+ mgslpc_change_params(info);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags |= ASYNC_INITIALIZED;
+
+ return 0;
+}
+
+/* Called by mgslpc_close() and mgslpc_hangup() to shutdown hardware
+ */
+static void shutdown(MGSLPC_INFO * info)
+{
+ unsigned long flags;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_shutdown(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ /* clear status wait queue because status changes */
+ /* can't happen after shutting down the hardware */
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ del_timer(&info->tx_timer);
+
+ if (info->tx_buf) {
+ free_page((unsigned long) info->tx_buf);
+ info->tx_buf = NULL;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ rx_stop(info);
+ tx_stop(info);
+
+ /* TODO:disable interrupts instead of reset to preserve signal states */
+ reset_device(info);
+
+ if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+ set_signals(info);
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ release_resources(info);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+}
+
+static void mgslpc_program_hw(MGSLPC_INFO *info)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ rx_stop(info);
+ tx_stop(info);
+ info->tx_count = info->tx_put = info->tx_get = 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC || info->netcount)
+ hdlc_mode(info);
+ else
+ async_mode(info);
+
+ set_signals(info);
+
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+
+ irq_enable(info, CHB, IRQ_DCD | IRQ_CTS);
+ port_irq_enable(info, (unsigned char) PVR_DSR | PVR_RI);
+ get_signals(info);
+
+ if (info->netcount || info->tty->termios->c_cflag & CREAD)
+ rx_start(info);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void mgslpc_change_params(MGSLPC_INFO *info)
+{
+ unsigned cflag;
+ int bits_per_char;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_change_params(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ cflag = info->tty->termios->c_cflag;
+
+ /* if B0 rate (hangup) specified then negate DTR and RTS */
+ /* otherwise assert DTR and RTS */
+ if (cflag & CBAUD)
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ else
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+
+ /* byte size and parity */
+
+ switch (cflag & CSIZE) {
+ case CS5: info->params.data_bits = 5; break;
+ case CS6: info->params.data_bits = 6; break;
+ case CS7: info->params.data_bits = 7; break;
+ case CS8: info->params.data_bits = 8; break;
+ default: info->params.data_bits = 7; break;
+ }
+
+ if (cflag & CSTOPB)
+ info->params.stop_bits = 2;
+ else
+ info->params.stop_bits = 1;
+
+ info->params.parity = ASYNC_PARITY_NONE;
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ info->params.parity = ASYNC_PARITY_ODD;
+ else
+ info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+ if (cflag & CMSPAR)
+ info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+ }
+
+ /* calculate number of jiffies to transmit a full
+ * FIFO (32 bytes) at specified data rate
+ */
+ bits_per_char = info->params.data_bits +
+ info->params.stop_bits + 1;
+
+ /* if port data rate is set to 460800 or less then
+ * allow tty settings to override, otherwise keep the
+ * current data rate.
+ */
+ if (info->params.data_rate <= 460800) {
+ info->params.data_rate = tty_get_baud_rate(info->tty);
+ }
+
+ if ( info->params.data_rate ) {
+ info->timeout = (32*HZ*bits_per_char) /
+ info->params.data_rate;
+ }
+ info->timeout += HZ/50; /* Add .02 seconds of slop */
+
+ if (cflag & CRTSCTS)
+ info->flags |= ASYNC_CTS_FLOW;
+ else
+ info->flags &= ~ASYNC_CTS_FLOW;
+
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ /* process tty input control flags */
+
+ info->read_status_mask = 0;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= BIT7 | BIT6;
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= BIT7 | BIT6;
+
+ mgslpc_program_hw(info);
+}
+
+/* Add a character to the transmit buffer
+ */
+static void mgslpc_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO) {
+ printk( "%s(%d):mgslpc_put_char(%d) on %s\n",
+ __FILE__,__LINE__,ch,info->device_name);
+ }
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_put_char"))
+ return;
+
+ if (!tty || !info->tx_buf)
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ if (info->params.mode == MGSL_MODE_ASYNC || !info->tx_active) {
+ if (info->tx_count < TXBUFSIZE - 1) {
+ info->tx_buf[info->tx_put++] = ch;
+ info->tx_put &= TXBUFSIZE-1;
+ info->tx_count++;
+ }
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Enable transmitter so remaining characters in the
+ * transmit buffer are sent.
+ */
+static void mgslpc_flush_chars(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk( "%s(%d):mgslpc_flush_chars() entry on %s tx_count=%d\n",
+ __FILE__,__LINE__,info->device_name,info->tx_count);
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_chars"))
+ return;
+
+ if (info->tx_count <= 0 || tty->stopped ||
+ tty->hw_stopped || !info->tx_buf)
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk( "%s(%d):mgslpc_flush_chars() entry on %s starting transmitter\n",
+ __FILE__,__LINE__,info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_active)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Send a block of data
+ *
+ * Arguments:
+ *
+ * tty pointer to tty information structure
+ * buf pointer to buffer containing send data
+ * count size of send data in bytes
+ *
+ * Returns: number of characters written
+ */
+static int mgslpc_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ int c, ret = 0;
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk( "%s(%d):mgslpc_write(%s) count=%d\n",
+ __FILE__,__LINE__,info->device_name,count);
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_write") ||
+ !tty || !info->tx_buf)
+ goto cleanup;
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ if (count > TXBUFSIZE) {
+ ret = -EIO;
+ goto cleanup;
+ }
+ if (info->tx_active)
+ goto cleanup;
+ else if (info->tx_count)
+ goto start;
+ }
+
+ for (;;) {
+ c = min(count,
+ min(TXBUFSIZE - info->tx_count - 1,
+ TXBUFSIZE - info->tx_put));
+ if (c <= 0)
+ break;
+
+ memcpy(info->tx_buf + info->tx_put, buf, c);
+
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_put = (info->tx_put + c) & (TXBUFSIZE-1);
+ info->tx_count += c;
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+start:
+ if (info->tx_count && !tty->stopped && !tty->hw_stopped) {
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_active)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+cleanup:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk( "%s(%d):mgslpc_write(%s) returning=%d\n",
+ __FILE__,__LINE__,info->device_name,ret);
+ return ret;
+}
+
+/* Return the count of free bytes in transmit buffer
+ */
+static int mgslpc_write_room(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ int ret;
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_write_room"))
+ return 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ /* HDLC (frame oriented) mode */
+ if (info->tx_active)
+ return 0;
+ else
+ return HDLC_MAX_FRAME_SIZE;
+ } else {
+ ret = TXBUFSIZE - info->tx_count - 1;
+ if (ret < 0)
+ ret = 0;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_write_room(%s)=%d\n",
+ __FILE__,__LINE__, info->device_name, ret);
+ return ret;
+}
+
+/* Return the count of bytes in transmit buffer
+ */
+static int mgslpc_chars_in_buffer(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ int rc;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_chars_in_buffer(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_chars_in_buffer"))
+ return 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC)
+ rc = info->tx_active ? info->max_frame_size : 0;
+ else
+ rc = info->tx_count;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_chars_in_buffer(%s)=%d\n",
+ __FILE__,__LINE__, info->device_name, rc);
+
+ return rc;
+}
+
+/* Discard all data in the send buffer
+ */
+static void mgslpc_flush_buffer(struct tty_struct *tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_flush_buffer(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_buffer"))
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_count = info->tx_put = info->tx_get = 0;
+ del_timer(&info->tx_timer);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+/* Send a high-priority XON/XOFF character
+ */
+static void mgslpc_send_xchar(struct tty_struct *tty, char ch)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_send_xchar(%s,%d)\n",
+ __FILE__,__LINE__, info->device_name, ch );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_send_xchar"))
+ return;
+
+ info->x_char = ch;
+ if (ch) {
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_enabled)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* Signal remote device to throttle send data (our receive data)
+ */
+static void mgslpc_throttle(struct tty_struct * tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_throttle(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_throttle"))
+ return;
+
+ if (I_IXOFF(tty))
+ mgslpc_send_xchar(tty, STOP_CHAR(tty));
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals &= ~SerialSignal_RTS;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* Signal remote device to stop throttling send data (our receive data)
+ */
+static void mgslpc_unthrottle(struct tty_struct * tty)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_unthrottle(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_unthrottle"))
+ return;
+
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else
+ mgslpc_send_xchar(tty, START_CHAR(tty));
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals |= SerialSignal_RTS;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* get the current serial statistics
+ */
+static int get_stats(MGSLPC_INFO * info, struct mgsl_icount __user *user_icount)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("get_params(%s)\n", info->device_name);
+ COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+ if (err)
+ return -EFAULT;
+ return 0;
+}
+
+/* get the current serial parameters
+ */
+static int get_params(MGSLPC_INFO * info, MGSL_PARAMS __user *user_params)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("get_params(%s)\n", info->device_name);
+ COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+ if (err)
+ return -EFAULT;
+ return 0;
+}
+
+/* set the serial parameters
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * new_params user buffer containing new serial params
+ *
+ * Returns: 0 if success, otherwise error code
+ */
+static int set_params(MGSLPC_INFO * info, MGSL_PARAMS __user *new_params)
+{
+ unsigned long flags;
+ MGSL_PARAMS tmp_params;
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):set_params %s\n", __FILE__,__LINE__,
+ info->device_name );
+ COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):set_params(%s) user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ mgslpc_change_params(info);
+
+ return 0;
+}
+
+static int get_txidle(MGSLPC_INFO * info, int __user *idle_mode)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("get_txidle(%s)=%d\n", info->device_name, info->idle_mode);
+ COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+ if (err)
+ return -EFAULT;
+ return 0;
+}
+
+static int set_txidle(MGSLPC_INFO * info, int idle_mode)
+{
+ unsigned long flags;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("set_txidle(%s,%d)\n", info->device_name, idle_mode);
+ spin_lock_irqsave(&info->lock,flags);
+ info->idle_mode = idle_mode;
+ tx_set_idle(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int get_interface(MGSLPC_INFO * info, int __user *if_mode)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("get_interface(%s)=%d\n", info->device_name, info->if_mode);
+ COPY_TO_USER(err,if_mode, &info->if_mode, sizeof(int));
+ if (err)
+ return -EFAULT;
+ return 0;
+}
+
+static int set_interface(MGSLPC_INFO * info, int if_mode)
+{
+ unsigned long flags;
+ unsigned char val;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("set_interface(%s,%d)\n", info->device_name, if_mode);
+ spin_lock_irqsave(&info->lock,flags);
+ info->if_mode = if_mode;
+
+ val = read_reg(info, PVR) & 0x0f;
+ switch (info->if_mode)
+ {
+ case MGSL_INTERFACE_RS232: val |= PVR_RS232; break;
+ case MGSL_INTERFACE_V35: val |= PVR_V35; break;
+ case MGSL_INTERFACE_RS422: val |= PVR_RS422; break;
+ }
+ write_reg(info, PVR, val);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int set_txenable(MGSLPC_INFO * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("set_txenable(%s,%d)\n", info->device_name, enable);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (enable) {
+ if (!info->tx_enabled)
+ tx_start(info);
+ } else {
+ if (info->tx_enabled)
+ tx_stop(info);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int tx_abort(MGSLPC_INFO * info)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("tx_abort(%s)\n", info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (info->tx_active && info->tx_count &&
+ info->params.mode == MGSL_MODE_HDLC) {
+ /* clear data count so FIFO is not filled on next IRQ.
+ * This results in underrun and abort transmission.
+ */
+ info->tx_count = info->tx_put = info->tx_get = 0;
+ info->tx_aborting = TRUE;
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int set_rxenable(MGSLPC_INFO * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("set_rxenable(%s,%d)\n", info->device_name, enable);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (enable) {
+ if (!info->rx_enabled)
+ rx_start(info);
+ } else {
+ if (info->rx_enabled)
+ rx_stop(info);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+/* wait for specified event to occur
+ *
+ * Arguments: info pointer to device instance data
+ * mask pointer to bitmask of events to wait for
+ * Return Value: 0 if successful and bit mask updated with
+ * of events triggerred,
+ * otherwise error code
+ */
+static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr)
+{
+ unsigned long flags;
+ int s;
+ int rc=0;
+ struct mgsl_icount cprev, cnow;
+ int events;
+ int mask;
+ struct _input_signal_events oldsigs, newsigs;
+ DECLARE_WAITQUEUE(wait, current);
+
+ COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+ if (rc)
+ return -EFAULT;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("wait_events(%s,%d)\n", info->device_name, mask);
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ /* return immediately if state matches requested events */
+ get_signals(info);
+ s = info->serial_signals;
+ events = mask &
+ ( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+ ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+ ((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+ if (events) {
+ spin_unlock_irqrestore(&info->lock,flags);
+ goto exit;
+ }
+
+ /* save current irq counts */
+ cprev = info->icount;
+ oldsigs = info->input_signal_events;
+
+ if ((info->params.mode == MGSL_MODE_HDLC) &&
+ (mask & MgslEvent_ExitHuntMode))
+ irq_enable(info, CHA, IRQ_EXITHUNT);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&info->event_wait_q, &wait);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get current irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ newsigs = info->input_signal_events;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (newsigs.dsr_up == oldsigs.dsr_up &&
+ newsigs.dsr_down == oldsigs.dsr_down &&
+ newsigs.dcd_up == oldsigs.dcd_up &&
+ newsigs.dcd_down == oldsigs.dcd_down &&
+ newsigs.cts_up == oldsigs.cts_up &&
+ newsigs.cts_down == oldsigs.cts_down &&
+ newsigs.ri_up == oldsigs.ri_up &&
+ newsigs.ri_down == oldsigs.ri_down &&
+ cnow.exithunt == cprev.exithunt &&
+ cnow.rxidle == cprev.rxidle) {
+ rc = -EIO;
+ break;
+ }
+
+ events = mask &
+ ( (newsigs.dsr_up != oldsigs.dsr_up ? MgslEvent_DsrActive:0) +
+ (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+ (newsigs.dcd_up != oldsigs.dcd_up ? MgslEvent_DcdActive:0) +
+ (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+ (newsigs.cts_up != oldsigs.cts_up ? MgslEvent_CtsActive:0) +
+ (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+ (newsigs.ri_up != oldsigs.ri_up ? MgslEvent_RiActive:0) +
+ (newsigs.ri_down != oldsigs.ri_down ? MgslEvent_RiInactive:0) +
+ (cnow.exithunt != cprev.exithunt ? MgslEvent_ExitHuntMode:0) +
+ (cnow.rxidle != cprev.rxidle ? MgslEvent_IdleReceived:0) );
+ if (events)
+ break;
+
+ cprev = cnow;
+ oldsigs = newsigs;
+ }
+
+ remove_wait_queue(&info->event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+
+ if (mask & MgslEvent_ExitHuntMode) {
+ spin_lock_irqsave(&info->lock,flags);
+ if (!waitqueue_active(&info->event_wait_q))
+ irq_disable(info, CHA, IRQ_EXITHUNT);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+exit:
+ if (rc == 0)
+ PUT_USER(rc, events, mask_ptr);
+ return rc;
+}
+
+static int modem_input_wait(MGSLPC_INFO *info,int arg)
+{
+ unsigned long flags;
+ int rc;
+ struct mgsl_icount cprev, cnow;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /* save current irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cprev = info->icount;
+ add_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get new irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+ rc = -EIO;
+ break;
+ }
+
+ /* check for change in caller specified modem input */
+ if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+ (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+ (arg & TIOCM_CD && cnow.dcd != cprev.dcd) ||
+ (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+ rc = 0;
+ break;
+ }
+
+ cprev = cnow;
+ }
+ remove_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+ return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned int result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+ ((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+ ((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+ ((info->serial_signals & SerialSignal_RI) ? TIOCM_RNG:0) +
+ ((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+ ((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmget() value=%08X\n",
+ __FILE__,__LINE__, info->device_name, result );
+ return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmset(%x,%x)\n",
+ __FILE__,__LINE__,info->device_name, set, clear);
+
+ if (set & TIOCM_RTS)
+ info->serial_signals |= SerialSignal_RTS;
+ if (set & TIOCM_DTR)
+ info->serial_signals |= SerialSignal_DTR;
+ if (clear & TIOCM_RTS)
+ info->serial_signals &= ~SerialSignal_RTS;
+ if (clear & TIOCM_DTR)
+ info->serial_signals &= ~SerialSignal_DTR;
+
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return 0;
+}
+
+/* Set or clear transmit break condition
+ *
+ * Arguments: tty pointer to tty instance data
+ * break_state -1=set break condition, 0=clear
+ */
+static void mgslpc_break(struct tty_struct *tty, int break_state)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_break(%s,%d)\n",
+ __FILE__,__LINE__, info->device_name, break_state);
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_break"))
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (break_state == -1)
+ set_reg_bits(info, CHA+DAFO, BIT6);
+ else
+ clear_reg_bits(info, CHA+DAFO, BIT6);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Service an IOCTL request
+ *
+ * Arguments:
+ *
+ * tty pointer to tty instance data
+ * file pointer to associated file object for device
+ * cmd IOCTL command code
+ * arg command argument/context
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgslpc_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_ioctl %s cmd=%08X\n", __FILE__,__LINE__,
+ info->device_name, cmd );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_ioctl"))
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ return ioctl_common(info, cmd, arg);
+}
+
+int ioctl_common(MGSLPC_INFO *info, unsigned int cmd, unsigned long arg)
+{
+ int error;
+ struct mgsl_icount cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser; /* user space */
+ void __user *argp = (void __user *)arg;
+ unsigned long flags;
+
+ switch (cmd) {
+ case MGSL_IOCGPARAMS:
+ return get_params(info, argp);
+ case MGSL_IOCSPARAMS:
+ return set_params(info, argp);
+ case MGSL_IOCGTXIDLE:
+ return get_txidle(info, argp);
+ case MGSL_IOCSTXIDLE:
+ return set_txidle(info, (int)arg);
+ case MGSL_IOCGIF:
+ return get_interface(info, argp);
+ case MGSL_IOCSIF:
+ return set_interface(info,(int)arg);
+ case MGSL_IOCTXENABLE:
+ return set_txenable(info,(int)arg);
+ case MGSL_IOCRXENABLE:
+ return set_rxenable(info,(int)arg);
+ case MGSL_IOCTXABORT:
+ return tx_abort(info);
+ case MGSL_IOCGSTATS:
+ return get_stats(info, argp);
+ case MGSL_IOCWAITEVENT:
+ return wait_events(info, argp);
+ case TIOCMIWAIT:
+ return modem_input_wait(info,(int)arg);
+ case TIOCGICOUNT:
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->lock,flags);
+ p_cuser = argp;
+ PUT_USER(error,cnow.cts, &p_cuser->cts);
+ if (error) return error;
+ PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+ if (error) return error;
+ PUT_USER(error,cnow.rng, &p_cuser->rng);
+ if (error) return error;
+ PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+ if (error) return error;
+ PUT_USER(error,cnow.rx, &p_cuser->rx);
+ if (error) return error;
+ PUT_USER(error,cnow.tx, &p_cuser->tx);
+ if (error) return error;
+ PUT_USER(error,cnow.frame, &p_cuser->frame);
+ if (error) return error;
+ PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+ if (error) return error;
+ PUT_USER(error,cnow.parity, &p_cuser->parity);
+ if (error) return error;
+ PUT_USER(error,cnow.brk, &p_cuser->brk);
+ if (error) return error;
+ PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+ if (error) return error;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+/* Set new termios settings
+ *
+ * Arguments:
+ *
+ * tty pointer to tty structure
+ * termios pointer to buffer to hold returned old termios
+ */
+static void mgslpc_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_set_termios %s\n", __FILE__,__LINE__,
+ tty->driver->name );
+
+ /* just return if nothing has changed */
+ if ((tty->termios->c_cflag == old_termios->c_cflag)
+ && (RELEVANT_IFLAG(tty->termios->c_iflag)
+ == RELEVANT_IFLAG(old_termios->c_iflag)))
+ return;
+
+ mgslpc_change_params(info);
+
+ /* Handle transition to B0 status */
+ if (old_termios->c_cflag & CBAUD &&
+ !(tty->termios->c_cflag & CBAUD)) {
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ tty->termios->c_cflag & CBAUD) {
+ info->serial_signals |= SerialSignal_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ info->serial_signals |= SerialSignal_RTS;
+ }
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if (old_termios->c_cflag & CRTSCTS &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ tx_release(tty);
+ }
+}
+
+static void mgslpc_close(struct tty_struct *tty, struct file * filp)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_close"))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_close(%s) entry, count=%d\n",
+ __FILE__,__LINE__, info->device_name, info->count);
+
+ if (!info->count)
+ return;
+
+ if (tty_hung_up_p(filp))
+ goto cleanup;
+
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * tty->count is 1 and the tty structure will be freed.
+ * info->count should be one in this case.
+ * if it's not, correct it so that the port is shutdown.
+ */
+ printk("mgslpc_close: bad refcount; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+
+ info->count--;
+
+ /* if at least one open remaining, leave hardware active */
+ if (info->count)
+ goto cleanup;
+
+ info->flags |= ASYNC_CLOSING;
+
+ /* set tty->closing to notify line discipline to
+ * only process XON/XOFF characters. Only the N_TTY
+ * discipline appears to use this (ppp does not).
+ */
+ tty->closing = 1;
+
+ /* wait for transmit data to clear all layers */
+
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_close(%s) calling tty_wait_until_sent\n",
+ __FILE__,__LINE__, info->device_name );
+ tty_wait_until_sent(tty, info->closing_wait);
+ }
+
+ if (info->flags & ASYNC_INITIALIZED)
+ mgslpc_wait_until_sent(tty, info->timeout);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ ldisc_flush_buffer(tty);
+
+ shutdown(info);
+
+ tty->closing = 0;
+ info->tty = NULL;
+
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+
+ wake_up_interruptible(&info->close_wait);
+
+cleanup:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_close(%s) exit, count=%d\n", __FILE__,__LINE__,
+ tty->driver->name, info->count);
+}
+
+/* Wait until the transmitter is empty.
+ */
+static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+
+ if (!info )
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_wait_until_sent(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_wait_until_sent"))
+ return;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ goto exit;
+
+ orig_jiffies = jiffies;
+
+ /* Set check interval to 1/5 of estimated time to
+ * send a character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ * Note: use tight timings here to satisfy the NIST-PCTS.
+ */
+
+ if ( info->params.data_rate ) {
+ char_time = info->timeout/(32 * 5);
+ if (!char_time)
+ char_time++;
+ } else
+ char_time = 1;
+
+ if (timeout)
+ char_time = min_t(unsigned long, char_time, timeout);
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ while (info->tx_active) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ } else {
+ while ((info->tx_count || info->tx_active) &&
+ info->tx_enabled) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ }
+
+exit:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_wait_until_sent(%s) exit\n",
+ __FILE__,__LINE__, info->device_name );
+}
+
+/* Called by tty_hangup() when a hangup is signaled.
+ * This is the same as closing all open files for the port.
+ */
+static void mgslpc_hangup(struct tty_struct *tty)
+{
+ MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_hangup(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_hangup"))
+ return;
+
+ mgslpc_flush_buffer(tty);
+ shutdown(info);
+
+ info->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+
+ wake_up_interruptible(&info->open_wait);
+}
+
+/* Block the current process until the specified port
+ * is ready to be opened.
+ */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+ MGSLPC_INFO *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready on %s\n",
+ __FILE__,__LINE__, tty->driver->name );
+
+ if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+ /* nonblock mode is set or port is not enabled */
+ /* just verify that callout device is not active */
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /* Wait for carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * mgslpc_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready before block on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ info->count--;
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+ info->blocked_open++;
+
+ while (1) {
+ if ((tty->termios->c_cflag & CBAUD)) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+ retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS;
+ break;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ break;
+ }
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready blocking on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ schedule();
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&info->open_wait, &wait);
+
+ if (extra_count)
+ info->count++;
+ info->blocked_open--;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready after blocking on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ if (!retval)
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return retval;
+}
+
+static int mgslpc_open(struct tty_struct *tty, struct file * filp)
+{
+ MGSLPC_INFO *info;
+ int retval, line;
+ unsigned long flags;
+
+ /* verify range of specified line number */
+ line = tty->index;
+ if ((line < 0) || (line >= mgslpc_device_count)) {
+ printk("%s(%d):mgslpc_open with invalid line #%d.\n",
+ __FILE__,__LINE__,line);
+ return -ENODEV;
+ }
+
+ /* find the info structure for the specified line */
+ info = mgslpc_device_list;
+ while(info && info->line != line)
+ info = info->next_device;
+ if (mgslpc_paranoia_check(info, tty->name, "mgslpc_open"))
+ return -ENODEV;
+
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_open(%s), old ref count = %d\n",
+ __FILE__,__LINE__,tty->driver->name, info->count);
+
+ /* If port is closing, signal caller to try again */
+ if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+ retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ goto cleanup;
+ }
+
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->netcount) {
+ retval = -EBUSY;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ goto cleanup;
+ }
+ info->count++;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ if (info->count == 1) {
+ /* 1st open on this device, init hardware */
+ retval = startup(info);
+ if (retval < 0)
+ goto cleanup;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready(%s) returned %d\n",
+ __FILE__,__LINE__, info->device_name, retval);
+ goto cleanup;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgslpc_open(%s) success\n",
+ __FILE__,__LINE__, info->device_name);
+ retval = 0;
+
+cleanup:
+ if (retval) {
+ if (tty->count == 1)
+ info->tty = NULL; /* tty layer will release tty struct */
+ if(info->count)
+ info->count--;
+ }
+
+ return retval;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, MGSLPC_INFO *info)
+{
+ char stat_buf[30];
+ int ret;
+ unsigned long flags;
+
+ ret = sprintf(buf, "%s:io:%04X irq:%d",
+ info->device_name, info->io_base, info->irq_level);
+
+ /* output current serial signal states */
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ stat_buf[0] = 0;
+ stat_buf[1] = 0;
+ if (info->serial_signals & SerialSignal_RTS)
+ strcat(stat_buf, "|RTS");
+ if (info->serial_signals & SerialSignal_CTS)
+ strcat(stat_buf, "|CTS");
+ if (info->serial_signals & SerialSignal_DTR)
+ strcat(stat_buf, "|DTR");
+ if (info->serial_signals & SerialSignal_DSR)
+ strcat(stat_buf, "|DSR");
+ if (info->serial_signals & SerialSignal_DCD)
+ strcat(stat_buf, "|CD");
+ if (info->serial_signals & SerialSignal_RI)
+ strcat(stat_buf, "|RI");
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ ret += sprintf(buf+ret, " HDLC txok:%d rxok:%d",
+ info->icount.txok, info->icount.rxok);
+ if (info->icount.txunder)
+ ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+ if (info->icount.txabort)
+ ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+ if (info->icount.rxshort)
+ ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);
+ if (info->icount.rxlong)
+ ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+ if (info->icount.rxover)
+ ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+ if (info->icount.rxcrc)
+ ret += sprintf(buf+ret, " rxcrc:%d", info->icount.rxcrc);
+ } else {
+ ret += sprintf(buf+ret, " ASYNC tx:%d rx:%d",
+ info->icount.tx, info->icount.rx);
+ if (info->icount.frame)
+ ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+ if (info->icount.parity)
+ ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+ if (info->icount.brk)
+ ret += sprintf(buf+ret, " brk:%d", info->icount.brk);
+ if (info->icount.overrun)
+ ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+ }
+
+ /* Append serial signal status to end */
+ ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+
+ ret += sprintf(buf+ret, "txactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+ info->tx_active,info->bh_requested,info->bh_running,
+ info->pending_bh);
+
+ return ret;
+}
+
+/* Called to print information about devices
+ */
+static int mgslpc_read_proc(char *page, char **start, off_t off, int count,
+ int *eof, void *data)
+{
+ int len = 0, l;
+ off_t begin = 0;
+ MGSLPC_INFO *info;
+
+ len += sprintf(page, "synclink driver:%s\n", driver_version);
+
+ info = mgslpc_device_list;
+ while( info ) {
+ l = line_info(page + len, info);
+ len += l;
+ if (len+begin > off+count)
+ goto done;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ info = info->next_device;
+ }
+
+ *eof = 1;
+done:
+ if (off >= len+begin)
+ return 0;
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+int rx_alloc_buffers(MGSLPC_INFO *info)
+{
+ /* each buffer has header and data */
+ info->rx_buf_size = sizeof(RXBUF) + info->max_frame_size;
+
+ /* calculate total allocation size for 8 buffers */
+ info->rx_buf_total_size = info->rx_buf_size * 8;
+
+ /* limit total allocated memory */
+ if (info->rx_buf_total_size > 0x10000)
+ info->rx_buf_total_size = 0x10000;
+
+ /* calculate number of buffers */
+ info->rx_buf_count = info->rx_buf_total_size / info->rx_buf_size;
+
+ info->rx_buf = kmalloc(info->rx_buf_total_size, GFP_KERNEL);
+ if (info->rx_buf == NULL)
+ return -ENOMEM;
+
+ rx_reset_buffers(info);
+ return 0;
+}
+
+void rx_free_buffers(MGSLPC_INFO *info)
+{
+ if (info->rx_buf)
+ kfree(info->rx_buf);
+ info->rx_buf = NULL;
+}
+
+int claim_resources(MGSLPC_INFO *info)
+{
+ if (rx_alloc_buffers(info) < 0 ) {
+ printk( "Cant allocate rx buffer %s\n", info->device_name);
+ release_resources(info);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+void release_resources(MGSLPC_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("release_resources(%s)\n", info->device_name);
+ rx_free_buffers(info);
+}
+
+/* Add the specified device instance data structure to the
+ * global linked list of devices and increment the device count.
+ *
+ * Arguments: info pointer to device instance data
+ */
+void mgslpc_add_device(MGSLPC_INFO *info)
+{
+ info->next_device = NULL;
+ info->line = mgslpc_device_count;
+ sprintf(info->device_name,"ttySLP%d",info->line);
+
+ if (info->line < MAX_DEVICE_COUNT) {
+ if (maxframe[info->line])
+ info->max_frame_size = maxframe[info->line];
+ info->dosyncppp = dosyncppp[info->line];
+ }
+
+ mgslpc_device_count++;
+
+ if (!mgslpc_device_list)
+ mgslpc_device_list = info;
+ else {
+ MGSLPC_INFO *current_dev = mgslpc_device_list;
+ while( current_dev->next_device )
+ current_dev = current_dev->next_device;
+ current_dev->next_device = info;
+ }
+
+ if (info->max_frame_size < 4096)
+ info->max_frame_size = 4096;
+ else if (info->max_frame_size > 65535)
+ info->max_frame_size = 65535;
+
+ printk( "SyncLink PC Card %s:IO=%04X IRQ=%d\n",
+ info->device_name, info->io_base, info->irq_level);
+
+#ifdef CONFIG_HDLC
+ hdlcdev_init(info);
+#endif
+}
+
+void mgslpc_remove_device(MGSLPC_INFO *remove_info)
+{
+ MGSLPC_INFO *info = mgslpc_device_list;
+ MGSLPC_INFO *last = NULL;
+
+ while(info) {
+ if (info == remove_info) {
+ if (last)
+ last->next_device = info->next_device;
+ else
+ mgslpc_device_list = info->next_device;
+#ifdef CONFIG_HDLC
+ hdlcdev_exit(info);
+#endif
+ release_resources(info);
+ kfree(info);
+ mgslpc_device_count--;
+ return;
+ }
+ last = info;
+ info = info->next_device;
+ }
+}
+
+static struct pcmcia_driver mgslpc_driver = {
+ .owner = THIS_MODULE,
+ .drv = {
+ .name = "synclink_cs",
+ },
+ .attach = mgslpc_attach,
+ .detach = mgslpc_detach,
+};
+
+static struct tty_operations mgslpc_ops = {
+ .open = mgslpc_open,
+ .close = mgslpc_close,
+ .write = mgslpc_write,
+ .put_char = mgslpc_put_char,
+ .flush_chars = mgslpc_flush_chars,
+ .write_room = mgslpc_write_room,
+ .chars_in_buffer = mgslpc_chars_in_buffer,
+ .flush_buffer = mgslpc_flush_buffer,
+ .ioctl = mgslpc_ioctl,
+ .throttle = mgslpc_throttle,
+ .unthrottle = mgslpc_unthrottle,
+ .send_xchar = mgslpc_send_xchar,
+ .break_ctl = mgslpc_break,
+ .wait_until_sent = mgslpc_wait_until_sent,
+ .read_proc = mgslpc_read_proc,
+ .set_termios = mgslpc_set_termios,
+ .stop = tx_pause,
+ .start = tx_release,
+ .hangup = mgslpc_hangup,
+ .tiocmget = tiocmget,
+ .tiocmset = tiocmset,
+};
+
+static void synclink_cs_cleanup(void)
+{
+ int rc;
+
+ printk("Unloading %s: version %s\n", driver_name, driver_version);
+
+ while(mgslpc_device_list)
+ mgslpc_remove_device(mgslpc_device_list);
+
+ if (serial_driver) {
+ if ((rc = tty_unregister_driver(serial_driver)))
+ printk("%s(%d) failed to unregister tty driver err=%d\n",
+ __FILE__,__LINE__,rc);
+ put_tty_driver(serial_driver);
+ }
+
+ pcmcia_unregister_driver(&mgslpc_driver);
+ BUG_ON(dev_list != NULL);
+}
+
+static int __init synclink_cs_init(void)
+{
+ int rc;
+
+ if (break_on_load) {
+ mgslpc_get_text_ptr();
+ BREAKPOINT();
+ }
+
+ printk("%s %s\n", driver_name, driver_version);
+
+ if ((rc = pcmcia_register_driver(&mgslpc_driver)) < 0)
+ return rc;
+
+ serial_driver = alloc_tty_driver(MAX_DEVICE_COUNT);
+ if (!serial_driver) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* Initialize the tty_driver structure */
+
+ serial_driver->owner = THIS_MODULE;
+ serial_driver->driver_name = "synclink_cs";
+ serial_driver->name = "ttySLP";
+ serial_driver->major = ttymajor;
+ serial_driver->minor_start = 64;
+ serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ serial_driver->init_termios = tty_std_termios;
+ serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(serial_driver, &mgslpc_ops);
+
+ if ((rc = tty_register_driver(serial_driver)) < 0) {
+ printk("%s(%d):Couldn't register serial driver\n",
+ __FILE__,__LINE__);
+ put_tty_driver(serial_driver);
+ serial_driver = NULL;
+ goto error;
+ }
+
+ printk("%s %s, tty major#%d\n",
+ driver_name, driver_version,
+ serial_driver->major);
+
+ return 0;
+
+error:
+ synclink_cs_cleanup();
+ return rc;
+}
+
+static void __exit synclink_cs_exit(void)
+{
+ synclink_cs_cleanup();
+}
+
+module_init(synclink_cs_init);
+module_exit(synclink_cs_exit);
+
+static void mgslpc_set_rate(MGSLPC_INFO *info, unsigned char channel, unsigned int rate)
+{
+ unsigned int M, N;
+ unsigned char val;
+
+ /* note:standard BRG mode is broken in V3.2 chip
+ * so enhanced mode is always used
+ */
+
+ if (rate) {
+ N = 3686400 / rate;
+ if (!N)
+ N = 1;
+ N >>= 1;
+ for (M = 1; N > 64 && M < 16; M++)
+ N >>= 1;
+ N--;
+
+ /* BGR[5..0] = N
+ * BGR[9..6] = M
+ * BGR[7..0] contained in BGR register
+ * BGR[9..8] contained in CCR2[7..6]
+ * divisor = (N+1)*2^M
+ *
+ * Note: M *must* not be zero (causes asymetric duty cycle)
+ */
+ write_reg(info, (unsigned char) (channel + BGR),
+ (unsigned char) ((M << 6) + N));
+ val = read_reg(info, (unsigned char) (channel + CCR2)) & 0x3f;
+ val |= ((M << 4) & 0xc0);
+ write_reg(info, (unsigned char) (channel + CCR2), val);
+ }
+}
+
+/* Enabled the AUX clock output at the specified frequency.
+ */
+static void enable_auxclk(MGSLPC_INFO *info)
+{
+ unsigned char val;
+
+ /* MODE
+ *
+ * 07..06 MDS[1..0] 10 = transparent HDLC mode
+ * 05 ADM Address Mode, 0 = no addr recognition
+ * 04 TMD Timer Mode, 0 = external
+ * 03 RAC Receiver Active, 0 = inactive
+ * 02 RTS 0=RTS active during xmit, 1=RTS always active
+ * 01 TRS Timer Resolution, 1=512
+ * 00 TLP Test Loop, 0 = no loop
+ *
+ * 1000 0010
+ */
+ val = 0x82;
+
+ /* channel B RTS is used to enable AUXCLK driver on SP505 */
+ if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+ val |= BIT2;
+ write_reg(info, CHB + MODE, val);
+
+ /* CCR0
+ *
+ * 07 PU Power Up, 1=active, 0=power down
+ * 06 MCE Master Clock Enable, 1=enabled
+ * 05 Reserved, 0
+ * 04..02 SC[2..0] Encoding
+ * 01..00 SM[1..0] Serial Mode, 00=HDLC
+ *
+ * 11000000
+ */
+ write_reg(info, CHB + CCR0, 0xc0);
+
+ /* CCR1
+ *
+ * 07 SFLG Shared Flag, 0 = disable shared flags
+ * 06 GALP Go Active On Loop, 0 = not used
+ * 05 GLP Go On Loop, 0 = not used
+ * 04 ODS Output Driver Select, 1=TxD is push-pull output
+ * 03 ITF Interframe Time Fill, 0=mark, 1=flag
+ * 02..00 CM[2..0] Clock Mode
+ *
+ * 0001 0111
+ */
+ write_reg(info, CHB + CCR1, 0x17);
+
+ /* CCR2 (Channel B)
+ *
+ * 07..06 BGR[9..8] Baud rate bits 9..8
+ * 05 BDF Baud rate divisor factor, 0=1, 1=BGR value
+ * 04 SSEL Clock source select, 1=submode b
+ * 03 TOE 0=TxCLK is input, 1=TxCLK is output
+ * 02 RWX Read/Write Exchange 0=disabled
+ * 01 C32, CRC select, 0=CRC-16, 1=CRC-32
+ * 00 DIV, data inversion 0=disabled, 1=enabled
+ *
+ * 0011 1000
+ */
+ if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+ write_reg(info, CHB + CCR2, 0x38);
+ else
+ write_reg(info, CHB + CCR2, 0x30);
+
+ /* CCR4
+ *
+ * 07 MCK4 Master Clock Divide by 4, 1=enabled
+ * 06 EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+ * 05 TST1 Test Pin, 0=normal operation
+ * 04 ICD Ivert Carrier Detect, 1=enabled (active low)
+ * 03..02 Reserved, must be 0
+ * 01..00 RFT[1..0] RxFIFO Threshold 00=32 bytes
+ *
+ * 0101 0000
+ */
+ write_reg(info, CHB + CCR4, 0x50);
+
+ /* if auxclk not enabled, set internal BRG so
+ * CTS transitions can be detected (requires TxC)
+ */
+ if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+ mgslpc_set_rate(info, CHB, info->params.clock_speed);
+ else
+ mgslpc_set_rate(info, CHB, 921600);
+}
+
+static void loopback_enable(MGSLPC_INFO *info)
+{
+ unsigned char val;
+
+ /* CCR1:02..00 CM[2..0] Clock Mode = 111 (clock mode 7) */
+ val = read_reg(info, CHA + CCR1) | (BIT2 + BIT1 + BIT0);
+ write_reg(info, CHA + CCR1, val);
+
+ /* CCR2:04 SSEL Clock source select, 1=submode b */
+ val = read_reg(info, CHA + CCR2) | (BIT4 + BIT5);
+ write_reg(info, CHA + CCR2, val);
+
+ /* set LinkSpeed if available, otherwise default to 2Mbps */
+ if (info->params.clock_speed)
+ mgslpc_set_rate(info, CHA, info->params.clock_speed);
+ else
+ mgslpc_set_rate(info, CHA, 1843200);
+
+ /* MODE:00 TLP Test Loop, 1=loopback enabled */
+ val = read_reg(info, CHA + MODE) | BIT0;
+ write_reg(info, CHA + MODE, val);
+}
+
+void hdlc_mode(MGSLPC_INFO *info)
+{
+ unsigned char val;
+ unsigned char clkmode, clksubmode;
+
+ /* disable all interrupts */
+ irq_disable(info, CHA, 0xffff);
+ irq_disable(info, CHB, 0xffff);
+ port_irq_disable(info, 0xff);
+
+ /* assume clock mode 0a, rcv=RxC xmt=TxC */
+ clkmode = clksubmode = 0;
+ if (info->params.flags & HDLC_FLAG_RXC_DPLL
+ && info->params.flags & HDLC_FLAG_TXC_DPLL) {
+ /* clock mode 7a, rcv = DPLL, xmt = DPLL */
+ clkmode = 7;
+ } else if (info->params.flags & HDLC_FLAG_RXC_BRG
+ && info->params.flags & HDLC_FLAG_TXC_BRG) {
+ /* clock mode 7b, rcv = BRG, xmt = BRG */
+ clkmode = 7;
+ clksubmode = 1;
+ } else if (info->params.flags & HDLC_FLAG_RXC_DPLL) {
+ if (info->params.flags & HDLC_FLAG_TXC_BRG) {
+ /* clock mode 6b, rcv = DPLL, xmt = BRG/16 */
+ clkmode = 6;
+ clksubmode = 1;
+ } else {
+ /* clock mode 6a, rcv = DPLL, xmt = TxC */
+ clkmode = 6;
+ }
+ } else if (info->params.flags & HDLC_FLAG_TXC_BRG) {
+ /* clock mode 0b, rcv = RxC, xmt = BRG */
+ clksubmode = 1;
+ }
+
+ /* MODE
+ *
+ * 07..06 MDS[1..0] 10 = transparent HDLC mode
+ * 05 ADM Address Mode, 0 = no addr recognition
+ * 04 TMD Timer Mode, 0 = external
+ * 03 RAC Receiver Active, 0 = inactive
+ * 02 RTS 0=RTS active during xmit, 1=RTS always active
+ * 01 TRS Timer Resolution, 1=512
+ * 00 TLP Test Loop, 0 = no loop
+ *
+ * 1000 0010
+ */
+ val = 0x82;
+ if (info->params.loopback)
+ val |= BIT0;
+
+ /* preserve RTS state */
+ if (info->serial_signals & SerialSignal_RTS)
+ val |= BIT2;
+ write_reg(info, CHA + MODE, val);
+
+ /* CCR0
+ *
+ * 07 PU Power Up, 1=active, 0=power down
+ * 06 MCE Master Clock Enable, 1=enabled
+ * 05 Reserved, 0
+ * 04..02 SC[2..0] Encoding
+ * 01..00 SM[1..0] Serial Mode, 00=HDLC
+ *
+ * 11000000
+ */
+ val = 0xc0;
+ switch (info->params.encoding)
+ {
+ case HDLC_ENCODING_NRZI:
+ val |= BIT3;
+ break;
+ case HDLC_ENCODING_BIPHASE_SPACE:
+ val |= BIT4;
+ break; // FM0
+ case HDLC_ENCODING_BIPHASE_MARK:
+ val |= BIT4 + BIT2;
+ break; // FM1
+ case HDLC_ENCODING_BIPHASE_LEVEL:
+ val |= BIT4 + BIT3;
+ break; // Manchester
+ }
+ write_reg(info, CHA + CCR0, val);
+
+ /* CCR1
+ *
+ * 07 SFLG Shared Flag, 0 = disable shared flags
+ * 06 GALP Go Active On Loop, 0 = not used
+ * 05 GLP Go On Loop, 0 = not used
+ * 04 ODS Output Driver Select, 1=TxD is push-pull output
+ * 03 ITF Interframe Time Fill, 0=mark, 1=flag
+ * 02..00 CM[2..0] Clock Mode
+ *
+ * 0001 0000
+ */
+ val = 0x10 + clkmode;
+ write_reg(info, CHA + CCR1, val);
+
+ /* CCR2
+ *
+ * 07..06 BGR[9..8] Baud rate bits 9..8
+ * 05 BDF Baud rate divisor factor, 0=1, 1=BGR value
+ * 04 SSEL Clock source select, 1=submode b
+ * 03 TOE 0=TxCLK is input, 0=TxCLK is input
+ * 02 RWX Read/Write Exchange 0=disabled
+ * 01 C32, CRC select, 0=CRC-16, 1=CRC-32
+ * 00 DIV, data inversion 0=disabled, 1=enabled
+ *
+ * 0000 0000
+ */
+ val = 0x00;
+ if (clkmode == 2 || clkmode == 3 || clkmode == 6
+ || clkmode == 7 || (clkmode == 0 && clksubmode == 1))
+ val |= BIT5;
+ if (clksubmode)
+ val |= BIT4;
+ if (info->params.crc_type == HDLC_CRC_32_CCITT)
+ val |= BIT1;
+ if (info->params.encoding == HDLC_ENCODING_NRZB)
+ val |= BIT0;
+ write_reg(info, CHA + CCR2, val);
+
+ /* CCR3
+ *
+ * 07..06 PRE[1..0] Preamble count 00=1, 01=2, 10=4, 11=8
+ * 05 EPT Enable preamble transmission, 1=enabled
+ * 04 RADD Receive address pushed to FIFO, 0=disabled
+ * 03 CRL CRC Reset Level, 0=FFFF
+ * 02 RCRC Rx CRC 0=On 1=Off
+ * 01 TCRC Tx CRC 0=On 1=Off
+ * 00 PSD DPLL Phase Shift Disable
+ *
+ * 0000 0000
+ */
+ val = 0x00;
+ if (info->params.crc_type == HDLC_CRC_NONE)
+ val |= BIT2 + BIT1;
+ if (info->params.preamble != HDLC_PREAMBLE_PATTERN_NONE)
+ val |= BIT5;
+ switch (info->params.preamble_length)
+ {
+ case HDLC_PREAMBLE_LENGTH_16BITS:
+ val |= BIT6;
+ break;
+ case HDLC_PREAMBLE_LENGTH_32BITS:
+ val |= BIT6;
+ break;
+ case HDLC_PREAMBLE_LENGTH_64BITS:
+ val |= BIT7 + BIT6;
+ break;
+ }
+ write_reg(info, CHA + CCR3, val);
+
+ /* PRE - Preamble pattern */
+ val = 0;
+ switch (info->params.preamble)
+ {
+ case HDLC_PREAMBLE_PATTERN_FLAGS: val = 0x7e; break;
+ case HDLC_PREAMBLE_PATTERN_10: val = 0xaa; break;
+ case HDLC_PREAMBLE_PATTERN_01: val = 0x55; break;
+ case HDLC_PREAMBLE_PATTERN_ONES: val = 0xff; break;
+ }
+ write_reg(info, CHA + PRE, val);
+
+ /* CCR4
+ *
+ * 07 MCK4 Master Clock Divide by 4, 1=enabled
+ * 06 EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+ * 05 TST1 Test Pin, 0=normal operation
+ * 04 ICD Ivert Carrier Detect, 1=enabled (active low)
+ * 03..02 Reserved, must be 0
+ * 01..00 RFT[1..0] RxFIFO Threshold 00=32 bytes
+ *
+ * 0101 0000
+ */
+ val = 0x50;
+ write_reg(info, CHA + CCR4, val);
+ if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+ mgslpc_set_rate(info, CHA, info->params.clock_speed * 16);
+ else
+ mgslpc_set_rate(info, CHA, info->params.clock_speed);
+
+ /* RLCR Receive length check register
+ *
+ * 7 1=enable receive length check
+ * 6..0 Max frame length = (RL + 1) * 32
+ */
+ write_reg(info, CHA + RLCR, 0);
+
+ /* XBCH Transmit Byte Count High
+ *
+ * 07 DMA mode, 0 = interrupt driven
+ * 06 NRM, 0=ABM (ignored)
+ * 05 CAS Carrier Auto Start
+ * 04 XC Transmit Continuously (ignored)
+ * 03..00 XBC[10..8] Transmit byte count bits 10..8
+ *
+ * 0000 0000
+ */
+ val = 0x00;
+ if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+ val |= BIT5;
+ write_reg(info, CHA + XBCH, val);
+ enable_auxclk(info);
+ if (info->params.loopback || info->testing_irq)
+ loopback_enable(info);
+ if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+ {
+ irq_enable(info, CHB, IRQ_CTS);
+ /* PVR[3] 1=AUTO CTS active */
+ set_reg_bits(info, CHA + PVR, BIT3);
+ } else
+ clear_reg_bits(info, CHA + PVR, BIT3);
+
+ irq_enable(info, CHA,
+ IRQ_RXEOM + IRQ_RXFIFO + IRQ_ALLSENT +
+ IRQ_UNDERRUN + IRQ_TXFIFO);
+ issue_command(info, CHA, CMD_TXRESET + CMD_RXRESET);
+ wait_command_complete(info, CHA);
+ read_reg16(info, CHA + ISR); /* clear pending IRQs */
+
+ /* Master clock mode enabled above to allow reset commands
+ * to complete even if no data clocks are present.
+ *
+ * Disable master clock mode for normal communications because
+ * V3.2 of the ESCC2 has a bug that prevents the transmit all sent
+ * IRQ when in master clock mode.
+ *
+ * Leave master clock mode enabled for IRQ test because the
+ * timer IRQ used by the test can only happen in master clock mode.
+ */
+ if (!info->testing_irq)
+ clear_reg_bits(info, CHA + CCR0, BIT6);
+
+ tx_set_idle(info);
+
+ tx_stop(info);
+ rx_stop(info);
+}
+
+void rx_stop(MGSLPC_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):rx_stop(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ /* MODE:03 RAC Receiver Active, 0=inactive */
+ clear_reg_bits(info, CHA + MODE, BIT3);
+
+ info->rx_enabled = 0;
+ info->rx_overflow = 0;
+}
+
+void rx_start(MGSLPC_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):rx_start(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ rx_reset_buffers(info);
+ info->rx_enabled = 0;
+ info->rx_overflow = 0;
+
+ /* MODE:03 RAC Receiver Active, 1=active */
+ set_reg_bits(info, CHA + MODE, BIT3);
+
+ info->rx_enabled = 1;
+}
+
+void tx_start(MGSLPC_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):tx_start(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (info->tx_count) {
+ /* If auto RTS enabled and RTS is inactive, then assert */
+ /* RTS and set a flag indicating that the driver should */
+ /* negate RTS when the transmission completes. */
+ info->drop_rts_on_tx_done = 0;
+
+ if (info->params.flags & HDLC_FLAG_AUTO_RTS) {
+ get_signals(info);
+ if (!(info->serial_signals & SerialSignal_RTS)) {
+ info->serial_signals |= SerialSignal_RTS;
+ set_signals(info);
+ info->drop_rts_on_tx_done = 1;
+ }
+ }
+
+ if (info->params.mode == MGSL_MODE_ASYNC) {
+ if (!info->tx_active) {
+ info->tx_active = 1;
+ tx_ready(info);
+ }
+ } else {
+ info->tx_active = 1;
+ tx_ready(info);
+ info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+ add_timer(&info->tx_timer);
+ }
+ }
+
+ if (!info->tx_enabled)
+ info->tx_enabled = 1;
+}
+
+void tx_stop(MGSLPC_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):tx_stop(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ del_timer(&info->tx_timer);
+
+ info->tx_enabled = 0;
+ info->tx_active = 0;
+}
+
+/* Reset the adapter to a known state and prepare it for further use.
+ */
+void reset_device(MGSLPC_INFO *info)
+{
+ /* power up both channels (set BIT7) */
+ write_reg(info, CHA + CCR0, 0x80);
+ write_reg(info, CHB + CCR0, 0x80);
+ write_reg(info, CHA + MODE, 0);
+ write_reg(info, CHB + MODE, 0);
+
+ /* disable all interrupts */
+ irq_disable(info, CHA, 0xffff);
+ irq_disable(info, CHB, 0xffff);
+ port_irq_disable(info, 0xff);
+
+ /* PCR Port Configuration Register
+ *
+ * 07..04 DEC[3..0] Serial I/F select outputs
+ * 03 output, 1=AUTO CTS control enabled
+ * 02 RI Ring Indicator input 0=active
+ * 01 DSR input 0=active
+ * 00 DTR output 0=active
+ *
+ * 0000 0110
+ */
+ write_reg(info, PCR, 0x06);
+
+ /* PVR Port Value Register
+ *
+ * 07..04 DEC[3..0] Serial I/F select (0000=disabled)
+ * 03 AUTO CTS output 1=enabled
+ * 02 RI Ring Indicator input
+ * 01 DSR input
+ * 00 DTR output (1=inactive)
+ *
+ * 0000 0001
+ */
+// write_reg(info, PVR, PVR_DTR);
+
+ /* IPC Interrupt Port Configuration
+ *
+ * 07 VIS 1=Masked interrupts visible
+ * 06..05 Reserved, 0
+ * 04..03 SLA Slave address, 00 ignored
+ * 02 CASM Cascading Mode, 1=daisy chain
+ * 01..00 IC[1..0] Interrupt Config, 01=push-pull output, active low
+ *
+ * 0000 0101
+ */
+ write_reg(info, IPC, 0x05);
+}
+
+void async_mode(MGSLPC_INFO *info)
+{
+ unsigned char val;
+
+ /* disable all interrupts */
+ irq_disable(info, CHA, 0xffff);
+ irq_disable(info, CHB, 0xffff);
+ port_irq_disable(info, 0xff);
+
+ /* MODE
+ *
+ * 07 Reserved, 0
+ * 06 FRTS RTS State, 0=active
+ * 05 FCTS Flow Control on CTS
+ * 04 FLON Flow Control Enable
+ * 03 RAC Receiver Active, 0 = inactive
+ * 02 RTS 0=Auto RTS, 1=manual RTS
+ * 01 TRS Timer Resolution, 1=512
+ * 00 TLP Test Loop, 0 = no loop
+ *
+ * 0000 0110
+ */
+ val = 0x06;
+ if (info->params.loopback)
+ val |= BIT0;
+
+ /* preserve RTS state */
+ if (!(info->serial_signals & SerialSignal_RTS))
+ val |= BIT6;
+ write_reg(info, CHA + MODE, val);
+
+ /* CCR0
+ *
+ * 07 PU Power Up, 1=active, 0=power down
+ * 06 MCE Master Clock Enable, 1=enabled
+ * 05 Reserved, 0
+ * 04..02 SC[2..0] Encoding, 000=NRZ
+ * 01..00 SM[1..0] Serial Mode, 11=Async
+ *
+ * 1000 0011
+ */
+ write_reg(info, CHA + CCR0, 0x83);
+
+ /* CCR1
+ *
+ * 07..05 Reserved, 0
+ * 04 ODS Output Driver Select, 1=TxD is push-pull output
+ * 03 BCR Bit Clock Rate, 1=16x
+ * 02..00 CM[2..0] Clock Mode, 111=BRG
+ *
+ * 0001 1111
+ */
+ write_reg(info, CHA + CCR1, 0x1f);
+
+ /* CCR2 (channel A)
+ *
+ * 07..06 BGR[9..8] Baud rate bits 9..8
+ * 05 BDF Baud rate divisor factor, 0=1, 1=BGR value
+ * 04 SSEL Clock source select, 1=submode b
+ * 03 TOE 0=TxCLK is input, 0=TxCLK is input
+ * 02 RWX Read/Write Exchange 0=disabled
+ * 01 Reserved, 0
+ * 00 DIV, data inversion 0=disabled, 1=enabled
+ *
+ * 0001 0000
+ */
+ write_reg(info, CHA + CCR2, 0x10);
+
+ /* CCR3
+ *
+ * 07..01 Reserved, 0
+ * 00 PSD DPLL Phase Shift Disable
+ *
+ * 0000 0000
+ */
+ write_reg(info, CHA + CCR3, 0);
+
+ /* CCR4
+ *
+ * 07 MCK4 Master Clock Divide by 4, 1=enabled
+ * 06 EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+ * 05 TST1 Test Pin, 0=normal operation
+ * 04 ICD Ivert Carrier Detect, 1=enabled (active low)
+ * 03..00 Reserved, must be 0
+ *
+ * 0101 0000
+ */
+ write_reg(info, CHA + CCR4, 0x50);
+ mgslpc_set_rate(info, CHA, info->params.data_rate * 16);
+
+ /* DAFO Data Format
+ *
+ * 07 Reserved, 0
+ * 06 XBRK transmit break, 0=normal operation
+ * 05 Stop bits (0=1, 1=2)
+ * 04..03 PAR[1..0] Parity (01=odd, 10=even)
+ * 02 PAREN Parity Enable
+ * 01..00 CHL[1..0] Character Length (00=8, 01=7)
+ *
+ */
+ val = 0x00;
+ if (info->params.data_bits != 8)
+ val |= BIT0; /* 7 bits */
+ if (info->params.stop_bits != 1)
+ val |= BIT5;
+ if (info->params.parity != ASYNC_PARITY_NONE)
+ {
+ val |= BIT2; /* Parity enable */
+ if (info->params.parity == ASYNC_PARITY_ODD)
+ val |= BIT3;
+ else
+ val |= BIT4;
+ }
+ write_reg(info, CHA + DAFO, val);
+
+ /* RFC Rx FIFO Control
+ *
+ * 07 Reserved, 0
+ * 06 DPS, 1=parity bit not stored in data byte
+ * 05 DXS, 0=all data stored in FIFO (including XON/XOFF)
+ * 04 RFDF Rx FIFO Data Format, 1=status byte stored in FIFO
+ * 03..02 RFTH[1..0], rx threshold, 11=16 status + 16 data byte
+ * 01 Reserved, 0
+ * 00 TCDE Terminate Char Detect Enable, 0=disabled
+ *
+ * 0101 1100
+ */
+ write_reg(info, CHA + RFC, 0x5c);
+
+ /* RLCR Receive length check register
+ *
+ * Max frame length = (RL + 1) * 32
+ */
+ write_reg(info, CHA + RLCR, 0);
+
+ /* XBCH Transmit Byte Count High
+ *
+ * 07 DMA mode, 0 = interrupt driven
+ * 06 NRM, 0=ABM (ignored)
+ * 05 CAS Carrier Auto Start
+ * 04 XC Transmit Continuously (ignored)
+ * 03..00 XBC[10..8] Transmit byte count bits 10..8
+ *
+ * 0000 0000
+ */
+ val = 0x00;
+ if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+ val |= BIT5;
+ write_reg(info, CHA + XBCH, val);
+ if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+ irq_enable(info, CHA, IRQ_CTS);
+
+ /* MODE:03 RAC Receiver Active, 1=active */
+ set_reg_bits(info, CHA + MODE, BIT3);
+ enable_auxclk(info);
+ if (info->params.flags & HDLC_FLAG_AUTO_CTS) {
+ irq_enable(info, CHB, IRQ_CTS);
+ /* PVR[3] 1=AUTO CTS active */
+ set_reg_bits(info, CHA + PVR, BIT3);
+ } else
+ clear_reg_bits(info, CHA + PVR, BIT3);
+ irq_enable(info, CHA,
+ IRQ_RXEOM + IRQ_RXFIFO + IRQ_BREAK_ON + IRQ_RXTIME +
+ IRQ_ALLSENT + IRQ_TXFIFO);
+ issue_command(info, CHA, CMD_TXRESET + CMD_RXRESET);
+ wait_command_complete(info, CHA);
+ read_reg16(info, CHA + ISR); /* clear pending IRQs */
+}
+
+/* Set the HDLC idle mode for the transmitter.
+ */
+void tx_set_idle(MGSLPC_INFO *info)
+{
+ /* Note: ESCC2 only supports flags and one idle modes */
+ if (info->idle_mode == HDLC_TXIDLE_FLAGS)
+ set_reg_bits(info, CHA + CCR1, BIT3);
+ else
+ clear_reg_bits(info, CHA + CCR1, BIT3);
+}
+
+/* get state of the V24 status (input) signals.
+ */
+void get_signals(MGSLPC_INFO *info)
+{
+ unsigned char status = 0;
+
+ /* preserve DTR and RTS */
+ info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+ if (read_reg(info, CHB + VSTR) & BIT7)
+ info->serial_signals |= SerialSignal_DCD;
+ if (read_reg(info, CHB + STAR) & BIT1)
+ info->serial_signals |= SerialSignal_CTS;
+
+ status = read_reg(info, CHA + PVR);
+ if (!(status & PVR_RI))
+ info->serial_signals |= SerialSignal_RI;
+ if (!(status & PVR_DSR))
+ info->serial_signals |= SerialSignal_DSR;
+}
+
+/* Set the state of DTR and RTS based on contents of
+ * serial_signals member of device extension.
+ */
+void set_signals(MGSLPC_INFO *info)
+{
+ unsigned char val;
+
+ val = read_reg(info, CHA + MODE);
+ if (info->params.mode == MGSL_MODE_ASYNC) {
+ if (info->serial_signals & SerialSignal_RTS)
+ val &= ~BIT6;
+ else
+ val |= BIT6;
+ } else {
+ if (info->serial_signals & SerialSignal_RTS)
+ val |= BIT2;
+ else
+ val &= ~BIT2;
+ }
+ write_reg(info, CHA + MODE, val);
+
+ if (info->serial_signals & SerialSignal_DTR)
+ clear_reg_bits(info, CHA + PVR, PVR_DTR);
+ else
+ set_reg_bits(info, CHA + PVR, PVR_DTR);
+}
+
+void rx_reset_buffers(MGSLPC_INFO *info)
+{
+ RXBUF *buf;
+ int i;
+
+ info->rx_put = 0;
+ info->rx_get = 0;
+ info->rx_frame_count = 0;
+ for (i=0 ; i < info->rx_buf_count ; i++) {
+ buf = (RXBUF*)(info->rx_buf + (i * info->rx_buf_size));
+ buf->status = buf->count = 0;
+ }
+}
+
+/* Attempt to return a received HDLC frame
+ * Only frames received without errors are returned.
+ *
+ * Returns 1 if frame returned, otherwise 0
+ */
+int rx_get_frame(MGSLPC_INFO *info)
+{
+ unsigned short status;
+ RXBUF *buf;
+ unsigned int framesize = 0;
+ unsigned long flags;
+ struct tty_struct *tty = info->tty;
+ int return_frame = 0;
+
+ if (info->rx_frame_count == 0)
+ return 0;
+
+ buf = (RXBUF*)(info->rx_buf + (info->rx_get * info->rx_buf_size));
+
+ status = buf->status;
+
+ /* 07 VFR 1=valid frame
+ * 06 RDO 1=data overrun
+ * 05 CRC 1=OK, 0=error
+ * 04 RAB 1=frame aborted
+ */
+ if ((status & 0xf0) != 0xA0) {
+ if (!(status & BIT7) || (status & BIT4))
+ info->icount.rxabort++;
+ else if (status & BIT6)
+ info->icount.rxover++;
+ else if (!(status & BIT5)) {
+ info->icount.rxcrc++;
+ if (info->params.crc_type & HDLC_CRC_RETURN_EX)
+ return_frame = 1;
+ }
+ framesize = 0;
+#ifdef CONFIG_HDLC
+ {
+ struct net_device_stats *stats = hdlc_stats(info->netdev);
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ }
+#endif
+ } else
+ return_frame = 1;
+
+ if (return_frame)
+ framesize = buf->count;
+
+ if (debug_level >= DEBUG_LEVEL_BH)
+ printk("%s(%d):rx_get_frame(%s) status=%04X size=%d\n",
+ __FILE__,__LINE__,info->device_name,status,framesize);
+
+ if (debug_level >= DEBUG_LEVEL_DATA)
+ trace_block(info, buf->data, framesize, 0);
+
+ if (framesize) {
+ if ((info->params.crc_type & HDLC_CRC_RETURN_EX &&
+ framesize+1 > info->max_frame_size) ||
+ framesize > info->max_frame_size)
+ info->icount.rxlong++;
+ else {
+ if (status & BIT5)
+ info->icount.rxok++;
+
+ if (info->params.crc_type & HDLC_CRC_RETURN_EX) {
+ *(buf->data + framesize) = status & BIT5 ? RX_OK:RX_CRC_ERROR;
+ ++framesize;
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_rx(info, buf->data, framesize);
+ else
+#endif
+ ldisc_receive_buf(tty, buf->data, info->flag_buf, framesize);
+ }
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ buf->status = buf->count = 0;
+ info->rx_frame_count--;
+ info->rx_get++;
+ if (info->rx_get >= info->rx_buf_count)
+ info->rx_get = 0;
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return 1;
+}
+
+BOOLEAN register_test(MGSLPC_INFO *info)
+{
+ static unsigned char patterns[] =
+ { 0x00, 0xff, 0xaa, 0x55, 0x69, 0x96, 0x0f };
+ static unsigned int count = sizeof(patterns) / sizeof(patterns[0]);
+ unsigned int i;
+ BOOLEAN rc = TRUE;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_device(info);
+
+ for (i = 0; i < count; i++) {
+ write_reg(info, XAD1, patterns[i]);
+ write_reg(info, XAD2, patterns[(i + 1) % count]);
+ if ((read_reg(info, XAD1) != patterns[i]) ||
+ (read_reg(info, XAD2) != patterns[(i + 1) % count])) {
+ rc = FALSE;
+ break;
+ }
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+ return rc;
+}
+
+BOOLEAN irq_test(MGSLPC_INFO *info)
+{
+ unsigned long end_time;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_device(info);
+
+ info->testing_irq = TRUE;
+ hdlc_mode(info);
+
+ info->irq_occurred = FALSE;
+
+ /* init hdlc mode */
+
+ irq_enable(info, CHA, IRQ_TIMER);
+ write_reg(info, CHA + TIMR, 0); /* 512 cycles */
+ issue_command(info, CHA, CMD_START_TIMER);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ end_time=100;
+ while(end_time-- && !info->irq_occurred) {
+ msleep_interruptible(10);
+ }
+
+ info->testing_irq = FALSE;
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_device(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return info->irq_occurred ? TRUE : FALSE;
+}
+
+int adapter_test(MGSLPC_INFO *info)
+{
+ if (!register_test(info)) {
+ info->init_error = DiagStatus_AddressFailure;
+ printk( "%s(%d):Register test failure for device %s Addr=%04X\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->io_base) );
+ return -ENODEV;
+ }
+
+ if (!irq_test(info)) {
+ info->init_error = DiagStatus_IrqFailure;
+ printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+ return -ENODEV;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):device %s passed diagnostics\n",
+ __FILE__,__LINE__,info->device_name);
+ return 0;
+}
+
+void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit)
+{
+ int i;
+ int linecount;
+ if (xmit)
+ printk("%s tx data:\n",info->device_name);
+ else
+ printk("%s rx data:\n",info->device_name);
+
+ while(count) {
+ if (count > 16)
+ linecount = 16;
+ else
+ linecount = count;
+
+ for(i=0;i<linecount;i++)
+ printk("%02X ",(unsigned char)data[i]);
+ for(;i<17;i++)
+ printk(" ");
+ for(i=0;i<linecount;i++) {
+ if (data[i]>=040 && data[i]<=0176)
+ printk("%c",data[i]);
+ else
+ printk(".");
+ }
+ printk("\n");
+
+ data += linecount;
+ count -= linecount;
+ }
+}
+
+/* HDLC frame time out
+ * update stats and do tx completion processing
+ */
+void tx_timeout(unsigned long context)
+{
+ MGSLPC_INFO *info = (MGSLPC_INFO*)context;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):tx_timeout(%s)\n",
+ __FILE__,__LINE__,info->device_name);
+ if(info->tx_active &&
+ info->params.mode == MGSL_MODE_HDLC) {
+ info->icount.txtimeout++;
+ }
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_active = 0;
+ info->tx_count = info->tx_put = info->tx_get = 0;
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ bh_transmit(info);
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev pointer to network device structure
+ * encoding serial encoding setting
+ * parity FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ MGSLPC_INFO *info = dev_to_port(dev);
+ unsigned char new_encoding;
+ unsigned short new_crctype;
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ switch (encoding)
+ {
+ case ENCODING_NRZ: new_encoding = HDLC_ENCODING_NRZ; break;
+ case ENCODING_NRZI: new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+ case ENCODING_FM_MARK: new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+ case ENCODING_FM_SPACE: new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+ case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+ default: return -EINVAL;
+ }
+
+ switch (parity)
+ {
+ case PARITY_NONE: new_crctype = HDLC_CRC_NONE; break;
+ case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+ case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+ default: return -EINVAL;
+ }
+
+ info->params.encoding = new_encoding;
+ info->params.crc_type = new_crctype;;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ mgslpc_program_hw(info);
+
+ return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb socket buffer containing HDLC frame
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ MGSLPC_INFO *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+ /* stop sending until this frame completes */
+ netif_stop_queue(dev);
+
+ /* copy data to device buffers */
+ memcpy(info->tx_buf, skb->data, skb->len);
+ info->tx_get = 0;
+ info->tx_put = info->tx_count = skb->len;
+
+ /* update network statistics */
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+
+ /* done with socket buffer, so free it */
+ dev_kfree_skb(skb);
+
+ /* save start time for transmit timeout detection */
+ dev->trans_start = jiffies;
+
+ /* start hardware transmitter if necessary */
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_active)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+ MGSLPC_INFO *info = dev_to_port(dev);
+ int rc;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+ /* generic HDLC layer open processing */
+ if ((rc = hdlc_open(dev)))
+ return rc;
+
+ /* arbitrate between network and tty opens */
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->count != 0 || info->netcount != 0) {
+ printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return -EBUSY;
+ }
+ info->netcount=1;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ /* claim resources and init adapter */
+ if ((rc = startup(info)) != 0) {
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return rc;
+ }
+
+ /* assert DTR and RTS, apply hardware settings */
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ mgslpc_program_hw(info);
+
+ /* enable network layer transmit */
+ dev->trans_start = jiffies;
+ netif_start_queue(dev);
+
+ /* inform generic HDLC layer of current DCD status */
+ spin_lock_irqsave(&info->lock, flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock, flags);
+ hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+ MGSLPC_INFO *info = dev_to_port(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+ netif_stop_queue(dev);
+
+ /* shutdown adapter and release resources */
+ shutdown(info);
+
+ hdlc_close(dev);
+
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev pointer to network device structure
+ * ifr pointer to network interface request structure
+ * cmd IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ MGSLPC_INFO *info = dev_to_port(dev);
+ unsigned int flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE: /* return current sync_serial_settings */
+
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+
+ flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+
+ switch (flags){
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+ case (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_INT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_TXINT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+ default: new_line.clock_type = CLOCK_DEFAULT;
+ }
+
+ new_line.clock_rate = info->params.clock_speed;
+ new_line.loopback = info->params.loopback ? 1:0;
+
+ if (copy_to_user(line, &new_line, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ switch (new_line.clock_type)
+ {
+ case CLOCK_EXT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+ case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+ case CLOCK_INT: flags = HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_TXINT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_DEFAULT: flags = info->params.flags &
+ (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN); break;
+ default: return -EINVAL;
+ }
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+ info->params.flags |= flags;
+
+ info->params.loopback = new_line.loopback;
+
+ if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+ info->params.clock_speed = new_line.clock_rate;
+ else
+ info->params.clock_speed = 0;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ mgslpc_program_hw(info);
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+ MGSLPC_INFO *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+
+ spin_lock_irqsave(&info->lock,flags);
+ tx_stop(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_tx_done(MGSLPC_INFO *info)
+{
+ if (netif_queue_stopped(info->netdev))
+ netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info pointer to device instance information
+ * buf pointer to buffer contianing frame data
+ * size count of data bytes in buf
+ */
+static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size)
+{
+ struct sk_buff *skb = dev_alloc_skb(size);
+ struct net_device *dev = info->netdev;
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_rx(%s)\n",dev->name);
+
+ if (skb == NULL) {
+ printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+ stats->rx_dropped++;
+ return;
+ }
+
+ memcpy(skb_put(skb, size),buf,size);
+
+ skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+ stats->rx_packets++;
+ stats->rx_bytes += size;
+
+ netif_rx(skb);
+
+ info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(MGSLPC_INFO *info)
+{
+ int rc;
+ struct net_device *dev;
+ hdlc_device *hdlc;
+
+ /* allocate and initialize network and HDLC layer objects */
+
+ if (!(dev = alloc_hdlcdev(info))) {
+ printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+ return -ENOMEM;
+ }
+
+ /* for network layer reporting purposes only */
+ dev->base_addr = info->io_base;
+ dev->irq = info->irq_level;
+
+ /* network layer callbacks and settings */
+ dev->do_ioctl = hdlcdev_ioctl;
+ dev->open = hdlcdev_open;
+ dev->stop = hdlcdev_close;
+ dev->tx_timeout = hdlcdev_tx_timeout;
+ dev->watchdog_timeo = 10*HZ;
+ dev->tx_queue_len = 50;
+
+ /* generic HDLC layer callbacks and settings */
+ hdlc = dev_to_hdlc(dev);
+ hdlc->attach = hdlcdev_attach;
+ hdlc->xmit = hdlcdev_xmit;
+
+ /* register objects with HDLC layer */
+ if ((rc = register_hdlc_device(dev))) {
+ printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+ free_netdev(dev);
+ return rc;
+ }
+
+ info->netdev = dev;
+ return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_exit(MGSLPC_INFO *info)
+{
+ unregister_hdlc_device(info->netdev);
+ free_netdev(info->netdev);
+ info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c
new file mode 100644
index 000000000000..5eda075c62bd
--- /dev/null
+++ b/drivers/char/ppdev.c
@@ -0,0 +1,824 @@
+/*
+ * linux/drivers/char/ppdev.c
+ *
+ * This is the code behind /dev/parport* -- it allows a user-space
+ * application to use the parport subsystem.
+ *
+ * Copyright (C) 1998-2000, 2002 Tim Waugh <tim@cyberelk.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * A /dev/parportx device node represents an arbitrary device
+ * on port 'x'. The following operations are possible:
+ *
+ * open do nothing, set up default IEEE 1284 protocol to be COMPAT
+ * close release port and unregister device (if necessary)
+ * ioctl
+ * EXCL register device exclusively (may fail)
+ * CLAIM (register device first time) parport_claim_or_block
+ * RELEASE parport_release
+ * SETMODE set the IEEE 1284 protocol to use for read/write
+ * SETPHASE set the IEEE 1284 phase of a particular mode. Not to be
+ * confused with ioctl(fd, SETPHASER, &stun). ;-)
+ * DATADIR data_forward / data_reverse
+ * WDATA write_data
+ * RDATA read_data
+ * WCONTROL write_control
+ * RCONTROL read_control
+ * FCONTROL frob_control
+ * RSTATUS read_status
+ * NEGOT parport_negotiate
+ * YIELD parport_yield_blocking
+ * WCTLONIRQ on interrupt, set control lines
+ * CLRIRQ clear (and return) interrupt count
+ * SETTIME sets device timeout (struct timeval)
+ * GETTIME gets device timeout (struct timeval)
+ * GETMODES gets hardware supported modes (unsigned int)
+ * GETMODE gets the current IEEE1284 mode
+ * GETPHASE gets the current IEEE1284 phase
+ * GETFLAGS gets current (user-visible) flags
+ * SETFLAGS sets current (user-visible) flags
+ * read/write read or write in current IEEE 1284 protocol
+ * select wait for interrupt (in readfds)
+ *
+ * Changes:
+ * Added SETTIME/GETTIME ioctl, Fred Barnes, 1999.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> 2000/08/25
+ * - On error, copy_from_user and copy_to_user do not return -EFAULT,
+ * They return the positive number of bytes *not* copied due to address
+ * space errors.
+ *
+ * Added GETMODES/GETMODE/GETPHASE ioctls, Fred Barnes <frmb2@ukc.ac.uk>, 03/01/2001.
+ * Added GETFLAGS/SETFLAGS ioctls, Fred Barnes, 04/2001
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ioctl.h>
+#include <linux/parport.h>
+#include <linux/ctype.h>
+#include <linux/poll.h>
+#include <asm/uaccess.h>
+#include <linux/ppdev.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#define PP_VERSION "ppdev: user-space parallel port driver"
+#define CHRDEV "ppdev"
+
+struct pp_struct {
+ struct pardevice * pdev;
+ wait_queue_head_t irq_wait;
+ atomic_t irqc;
+ unsigned int flags;
+ int irqresponse;
+ unsigned char irqctl;
+ struct ieee1284_info state;
+ struct ieee1284_info saved_state;
+ long default_inactivity;
+};
+
+/* pp_struct.flags bitfields */
+#define PP_CLAIMED (1<<0)
+#define PP_EXCL (1<<1)
+
+/* Other constants */
+#define PP_INTERRUPT_TIMEOUT (10 * HZ) /* 10s */
+#define PP_BUFFER_SIZE 1024
+#define PARDEVICE_MAX 8
+
+/* ROUND_UP macro from fs/select.c */
+#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
+
+static inline void pp_enable_irq (struct pp_struct *pp)
+{
+ struct parport *port = pp->pdev->port;
+ port->ops->enable_irq (port);
+}
+
+static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
+ loff_t * ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode);
+ struct pp_struct *pp = file->private_data;
+ char * kbuffer;
+ ssize_t bytes_read = 0;
+ struct parport *pport;
+ int mode;
+
+ if (!(pp->flags & PP_CLAIMED)) {
+ /* Don't have the port claimed */
+ printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+ minor);
+ return -EINVAL;
+ }
+
+ /* Trivial case. */
+ if (count == 0)
+ return 0;
+
+ kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
+ if (!kbuffer) {
+ return -ENOMEM;
+ }
+ pport = pp->pdev->port;
+ mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+ parport_set_timeout (pp->pdev,
+ (file->f_flags & O_NONBLOCK) ?
+ PARPORT_INACTIVITY_O_NONBLOCK :
+ pp->default_inactivity);
+
+ while (bytes_read == 0) {
+ ssize_t need = min_t(unsigned long, count, PP_BUFFER_SIZE);
+
+ if (mode == IEEE1284_MODE_EPP) {
+ /* various specials for EPP mode */
+ int flags = 0;
+ size_t (*fn)(struct parport *, void *, size_t, int);
+
+ if (pp->flags & PP_W91284PIC) {
+ flags |= PARPORT_W91284PIC;
+ }
+ if (pp->flags & PP_FASTREAD) {
+ flags |= PARPORT_EPP_FAST;
+ }
+ if (pport->ieee1284.mode & IEEE1284_ADDR) {
+ fn = pport->ops->epp_read_addr;
+ } else {
+ fn = pport->ops->epp_read_data;
+ }
+ bytes_read = (*fn)(pport, kbuffer, need, flags);
+ } else {
+ bytes_read = parport_read (pport, kbuffer, need);
+ }
+
+ if (bytes_read != 0)
+ break;
+
+ if (file->f_flags & O_NONBLOCK) {
+ bytes_read = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending (current)) {
+ bytes_read = -ERESTARTSYS;
+ break;
+ }
+
+ cond_resched();
+ }
+
+ parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+ if (bytes_read > 0 && copy_to_user (buf, kbuffer, bytes_read))
+ bytes_read = -EFAULT;
+
+ kfree (kbuffer);
+ pp_enable_irq (pp);
+ return bytes_read;
+}
+
+static ssize_t pp_write (struct file * file, const char __user * buf,
+ size_t count, loff_t * ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode);
+ struct pp_struct *pp = file->private_data;
+ char * kbuffer;
+ ssize_t bytes_written = 0;
+ ssize_t wrote;
+ int mode;
+ struct parport *pport;
+
+ if (!(pp->flags & PP_CLAIMED)) {
+ /* Don't have the port claimed */
+ printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+ minor);
+ return -EINVAL;
+ }
+
+ kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
+ if (!kbuffer) {
+ return -ENOMEM;
+ }
+ pport = pp->pdev->port;
+ mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+ parport_set_timeout (pp->pdev,
+ (file->f_flags & O_NONBLOCK) ?
+ PARPORT_INACTIVITY_O_NONBLOCK :
+ pp->default_inactivity);
+
+ while (bytes_written < count) {
+ ssize_t n = min_t(unsigned long, count - bytes_written, PP_BUFFER_SIZE);
+
+ if (copy_from_user (kbuffer, buf + bytes_written, n)) {
+ bytes_written = -EFAULT;
+ break;
+ }
+
+ if ((pp->flags & PP_FASTWRITE) && (mode == IEEE1284_MODE_EPP)) {
+ /* do a fast EPP write */
+ if (pport->ieee1284.mode & IEEE1284_ADDR) {
+ wrote = pport->ops->epp_write_addr (pport,
+ kbuffer, n, PARPORT_EPP_FAST);
+ } else {
+ wrote = pport->ops->epp_write_data (pport,
+ kbuffer, n, PARPORT_EPP_FAST);
+ }
+ } else {
+ wrote = parport_write (pp->pdev->port, kbuffer, n);
+ }
+
+ if (wrote <= 0) {
+ if (!bytes_written) {
+ bytes_written = wrote;
+ }
+ break;
+ }
+
+ bytes_written += wrote;
+
+ if (file->f_flags & O_NONBLOCK) {
+ if (!bytes_written)
+ bytes_written = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending (current)) {
+ if (!bytes_written) {
+ bytes_written = -EINTR;
+ }
+ break;
+ }
+
+ cond_resched();
+ }
+
+ parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+ kfree (kbuffer);
+ pp_enable_irq (pp);
+ return bytes_written;
+}
+
+static void pp_irq (int irq, void * private, struct pt_regs * unused)
+{
+ struct pp_struct * pp = (struct pp_struct *) private;
+
+ if (pp->irqresponse) {
+ parport_write_control (pp->pdev->port, pp->irqctl);
+ pp->irqresponse = 0;
+ }
+
+ atomic_inc (&pp->irqc);
+ wake_up_interruptible (&pp->irq_wait);
+}
+
+static int register_device (int minor, struct pp_struct *pp)
+{
+ struct parport *port;
+ struct pardevice * pdev = NULL;
+ char *name;
+ int fl;
+
+ name = kmalloc (strlen (CHRDEV) + 3, GFP_KERNEL);
+ if (name == NULL)
+ return -ENOMEM;
+
+ sprintf (name, CHRDEV "%x", minor);
+
+ port = parport_find_number (minor);
+ if (!port) {
+ printk (KERN_WARNING "%s: no associated port!\n", name);
+ kfree (name);
+ return -ENXIO;
+ }
+
+ fl = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0;
+ pdev = parport_register_device (port, name, NULL,
+ NULL, pp_irq, fl, pp);
+ parport_put_port (port);
+
+ if (!pdev) {
+ printk (KERN_WARNING "%s: failed to register device!\n", name);
+ kfree (name);
+ return -ENXIO;
+ }
+
+ pp->pdev = pdev;
+ printk (KERN_DEBUG "%s: registered pardevice\n", name);
+ return 0;
+}
+
+static enum ieee1284_phase init_phase (int mode)
+{
+ switch (mode & ~(IEEE1284_DEVICEID
+ | IEEE1284_ADDR)) {
+ case IEEE1284_MODE_NIBBLE:
+ case IEEE1284_MODE_BYTE:
+ return IEEE1284_PH_REV_IDLE;
+ }
+ return IEEE1284_PH_FWD_IDLE;
+}
+
+static int pp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int minor = iminor(inode);
+ struct pp_struct *pp = file->private_data;
+ struct parport * port;
+ void __user *argp = (void __user *)arg;
+
+ /* First handle the cases that don't take arguments. */
+ switch (cmd) {
+ case PPCLAIM:
+ {
+ struct ieee1284_info *info;
+ int ret;
+
+ if (pp->flags & PP_CLAIMED) {
+ printk (KERN_DEBUG CHRDEV
+ "%x: you've already got it!\n", minor);
+ return -EINVAL;
+ }
+
+ /* Deferred device registration. */
+ if (!pp->pdev) {
+ int err = register_device (minor, pp);
+ if (err) {
+ return err;
+ }
+ }
+
+ ret = parport_claim_or_block (pp->pdev);
+ if (ret < 0)
+ return ret;
+
+ pp->flags |= PP_CLAIMED;
+
+ /* For interrupt-reporting to work, we need to be
+ * informed of each interrupt. */
+ pp_enable_irq (pp);
+
+ /* We may need to fix up the state machine. */
+ info = &pp->pdev->port->ieee1284;
+ pp->saved_state.mode = info->mode;
+ pp->saved_state.phase = info->phase;
+ info->mode = pp->state.mode;
+ info->phase = pp->state.phase;
+ pp->default_inactivity = parport_set_timeout (pp->pdev, 0);
+ parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+ return 0;
+ }
+ case PPEXCL:
+ if (pp->pdev) {
+ printk (KERN_DEBUG CHRDEV "%x: too late for PPEXCL; "
+ "already registered\n", minor);
+ if (pp->flags & PP_EXCL)
+ /* But it's not really an error. */
+ return 0;
+ /* There's no chance of making the driver happy. */
+ return -EINVAL;
+ }
+
+ /* Just remember to register the device exclusively
+ * when we finally do the registration. */
+ pp->flags |= PP_EXCL;
+ return 0;
+ case PPSETMODE:
+ {
+ int mode;
+ if (copy_from_user (&mode, argp, sizeof (mode)))
+ return -EFAULT;
+ /* FIXME: validate mode */
+ pp->state.mode = mode;
+ pp->state.phase = init_phase (mode);
+
+ if (pp->flags & PP_CLAIMED) {
+ pp->pdev->port->ieee1284.mode = mode;
+ pp->pdev->port->ieee1284.phase = pp->state.phase;
+ }
+
+ return 0;
+ }
+ case PPGETMODE:
+ {
+ int mode;
+
+ if (pp->flags & PP_CLAIMED) {
+ mode = pp->pdev->port->ieee1284.mode;
+ } else {
+ mode = pp->state.mode;
+ }
+ if (copy_to_user (argp, &mode, sizeof (mode))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+ case PPSETPHASE:
+ {
+ int phase;
+ if (copy_from_user (&phase, argp, sizeof (phase))) {
+ return -EFAULT;
+ }
+ /* FIXME: validate phase */
+ pp->state.phase = phase;
+
+ if (pp->flags & PP_CLAIMED) {
+ pp->pdev->port->ieee1284.phase = phase;
+ }
+
+ return 0;
+ }
+ case PPGETPHASE:
+ {
+ int phase;
+
+ if (pp->flags & PP_CLAIMED) {
+ phase = pp->pdev->port->ieee1284.phase;
+ } else {
+ phase = pp->state.phase;
+ }
+ if (copy_to_user (argp, &phase, sizeof (phase))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+ case PPGETMODES:
+ {
+ unsigned int modes;
+
+ port = parport_find_number (minor);
+ if (!port)
+ return -ENODEV;
+
+ modes = port->modes;
+ if (copy_to_user (argp, &modes, sizeof (modes))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+ case PPSETFLAGS:
+ {
+ int uflags;
+
+ if (copy_from_user (&uflags, argp, sizeof (uflags))) {
+ return -EFAULT;
+ }
+ pp->flags &= ~PP_FLAGMASK;
+ pp->flags |= (uflags & PP_FLAGMASK);
+ return 0;
+ }
+ case PPGETFLAGS:
+ {
+ int uflags;
+
+ uflags = pp->flags & PP_FLAGMASK;
+ if (copy_to_user (argp, &uflags, sizeof (uflags))) {
+ return -EFAULT;
+ }
+ return 0;
+ }
+ } /* end switch() */
+
+ /* Everything else requires the port to be claimed, so check
+ * that now. */
+ if ((pp->flags & PP_CLAIMED) == 0) {
+ printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+ minor);
+ return -EINVAL;
+ }
+
+ port = pp->pdev->port;
+ switch (cmd) {
+ struct ieee1284_info *info;
+ unsigned char reg;
+ unsigned char mask;
+ int mode;
+ int ret;
+ struct timeval par_timeout;
+ long to_jiffies;
+
+ case PPRSTATUS:
+ reg = parport_read_status (port);
+ if (copy_to_user (argp, &reg, sizeof (reg)))
+ return -EFAULT;
+ return 0;
+ case PPRDATA:
+ reg = parport_read_data (port);
+ if (copy_to_user (argp, &reg, sizeof (reg)))
+ return -EFAULT;
+ return 0;
+ case PPRCONTROL:
+ reg = parport_read_control (port);
+ if (copy_to_user (argp, &reg, sizeof (reg)))
+ return -EFAULT;
+ return 0;
+ case PPYIELD:
+ parport_yield_blocking (pp->pdev);
+ return 0;
+
+ case PPRELEASE:
+ /* Save the state machine's state. */
+ info = &pp->pdev->port->ieee1284;
+ pp->state.mode = info->mode;
+ pp->state.phase = info->phase;
+ info->mode = pp->saved_state.mode;
+ info->phase = pp->saved_state.phase;
+ parport_release (pp->pdev);
+ pp->flags &= ~PP_CLAIMED;
+ return 0;
+
+ case PPWCONTROL:
+ if (copy_from_user (&reg, argp, sizeof (reg)))
+ return -EFAULT;
+ parport_write_control (port, reg);
+ return 0;
+
+ case PPWDATA:
+ if (copy_from_user (&reg, argp, sizeof (reg)))
+ return -EFAULT;
+ parport_write_data (port, reg);
+ return 0;
+
+ case PPFCONTROL:
+ if (copy_from_user (&mask, argp,
+ sizeof (mask)))
+ return -EFAULT;
+ if (copy_from_user (&reg, 1 + (unsigned char __user *) arg,
+ sizeof (reg)))
+ return -EFAULT;
+ parport_frob_control (port, mask, reg);
+ return 0;
+
+ case PPDATADIR:
+ if (copy_from_user (&mode, argp, sizeof (mode)))
+ return -EFAULT;
+ if (mode)
+ port->ops->data_reverse (port);
+ else
+ port->ops->data_forward (port);
+ return 0;
+
+ case PPNEGOT:
+ if (copy_from_user (&mode, argp, sizeof (mode)))
+ return -EFAULT;
+ switch ((ret = parport_negotiate (port, mode))) {
+ case 0: break;
+ case -1: /* handshake failed, peripheral not IEEE 1284 */
+ ret = -EIO;
+ break;
+ case 1: /* handshake succeeded, peripheral rejected mode */
+ ret = -ENXIO;
+ break;
+ }
+ pp_enable_irq (pp);
+ return ret;
+
+ case PPWCTLONIRQ:
+ if (copy_from_user (&reg, argp, sizeof (reg)))
+ return -EFAULT;
+
+ /* Remember what to set the control lines to, for next
+ * time we get an interrupt. */
+ pp->irqctl = reg;
+ pp->irqresponse = 1;
+ return 0;
+
+ case PPCLRIRQ:
+ ret = atomic_read (&pp->irqc);
+ if (copy_to_user (argp, &ret, sizeof (ret)))
+ return -EFAULT;
+ atomic_sub (ret, &pp->irqc);
+ return 0;
+
+ case PPSETTIME:
+ if (copy_from_user (&par_timeout, argp, sizeof(struct timeval))) {
+ return -EFAULT;
+ }
+ /* Convert to jiffies, place in pp->pdev->timeout */
+ if ((par_timeout.tv_sec < 0) || (par_timeout.tv_usec < 0)) {
+ return -EINVAL;
+ }
+ to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
+ to_jiffies += par_timeout.tv_sec * (long)HZ;
+ if (to_jiffies <= 0) {
+ return -EINVAL;
+ }
+ pp->pdev->timeout = to_jiffies;
+ return 0;
+
+ case PPGETTIME:
+ to_jiffies = pp->pdev->timeout;
+ par_timeout.tv_sec = to_jiffies / HZ;
+ par_timeout.tv_usec = (to_jiffies % (long)HZ) * (1000000/HZ);
+ if (copy_to_user (argp, &par_timeout, sizeof(struct timeval)))
+ return -EFAULT;
+ return 0;
+
+ default:
+ printk (KERN_DEBUG CHRDEV "%x: What? (cmd=0x%x)\n", minor,
+ cmd);
+ return -EINVAL;
+ }
+
+ /* Keep the compiler happy */
+ return 0;
+}
+
+static int pp_open (struct inode * inode, struct file * file)
+{
+ unsigned int minor = iminor(inode);
+ struct pp_struct *pp;
+
+ if (minor >= PARPORT_MAX)
+ return -ENXIO;
+
+ pp = kmalloc (sizeof (struct pp_struct), GFP_KERNEL);
+ if (!pp)
+ return -ENOMEM;
+
+ pp->state.mode = IEEE1284_MODE_COMPAT;
+ pp->state.phase = init_phase (pp->state.mode);
+ pp->flags = 0;
+ pp->irqresponse = 0;
+ atomic_set (&pp->irqc, 0);
+ init_waitqueue_head (&pp->irq_wait);
+
+ /* Defer the actual device registration until the first claim.
+ * That way, we know whether or not the driver wants to have
+ * exclusive access to the port (PPEXCL).
+ */
+ pp->pdev = NULL;
+ file->private_data = pp;
+
+ return 0;
+}
+
+static int pp_release (struct inode * inode, struct file * file)
+{
+ unsigned int minor = iminor(inode);
+ struct pp_struct *pp = file->private_data;
+ int compat_negot;
+
+ compat_negot = 0;
+ if (!(pp->flags & PP_CLAIMED) && pp->pdev &&
+ (pp->state.mode != IEEE1284_MODE_COMPAT)) {
+ struct ieee1284_info *info;
+
+ /* parport released, but not in compatibility mode */
+ parport_claim_or_block (pp->pdev);
+ pp->flags |= PP_CLAIMED;
+ info = &pp->pdev->port->ieee1284;
+ pp->saved_state.mode = info->mode;
+ pp->saved_state.phase = info->phase;
+ info->mode = pp->state.mode;
+ info->phase = pp->state.phase;
+ compat_negot = 1;
+ } else if ((pp->flags & PP_CLAIMED) && pp->pdev &&
+ (pp->pdev->port->ieee1284.mode != IEEE1284_MODE_COMPAT)) {
+ compat_negot = 2;
+ }
+ if (compat_negot) {
+ parport_negotiate (pp->pdev->port, IEEE1284_MODE_COMPAT);
+ printk (KERN_DEBUG CHRDEV
+ "%x: negotiated back to compatibility mode because "
+ "user-space forgot\n", minor);
+ }
+
+ if (pp->flags & PP_CLAIMED) {
+ struct ieee1284_info *info;
+
+ info = &pp->pdev->port->ieee1284;
+ pp->state.mode = info->mode;
+ pp->state.phase = info->phase;
+ info->mode = pp->saved_state.mode;
+ info->phase = pp->saved_state.phase;
+ parport_release (pp->pdev);
+ if (compat_negot != 1) {
+ printk (KERN_DEBUG CHRDEV "%x: released pardevice "
+ "because user-space forgot\n", minor);
+ }
+ }
+
+ if (pp->pdev) {
+ const char *name = pp->pdev->name;
+ parport_unregister_device (pp->pdev);
+ kfree (name);
+ pp->pdev = NULL;
+ printk (KERN_DEBUG CHRDEV "%x: unregistered pardevice\n",
+ minor);
+ }
+
+ kfree (pp);
+
+ return 0;
+}
+
+/* No kernel lock held - fine */
+static unsigned int pp_poll (struct file * file, poll_table * wait)
+{
+ struct pp_struct *pp = file->private_data;
+ unsigned int mask = 0;
+
+ poll_wait (file, &pp->irq_wait, wait);
+ if (atomic_read (&pp->irqc))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static struct class_simple *ppdev_class;
+
+static struct file_operations pp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pp_read,
+ .write = pp_write,
+ .poll = pp_poll,
+ .ioctl = pp_ioctl,
+ .open = pp_open,
+ .release = pp_release,
+};
+
+static void pp_attach(struct parport *port)
+{
+ class_simple_device_add(ppdev_class, MKDEV(PP_MAJOR, port->number),
+ NULL, "parport%d", port->number);
+}
+
+static void pp_detach(struct parport *port)
+{
+ class_simple_device_remove(MKDEV(PP_MAJOR, port->number));
+}
+
+static struct parport_driver pp_driver = {
+ .name = CHRDEV,
+ .attach = pp_attach,
+ .detach = pp_detach,
+};
+
+static int __init ppdev_init (void)
+{
+ int i, err = 0;
+
+ if (register_chrdev (PP_MAJOR, CHRDEV, &pp_fops)) {
+ printk (KERN_WARNING CHRDEV ": unable to get major %d\n",
+ PP_MAJOR);
+ return -EIO;
+ }
+ ppdev_class = class_simple_create(THIS_MODULE, CHRDEV);
+ if (IS_ERR(ppdev_class)) {
+ err = PTR_ERR(ppdev_class);
+ goto out_chrdev;
+ }
+ devfs_mk_dir("parports");
+ for (i = 0; i < PARPORT_MAX; i++) {
+ devfs_mk_cdev(MKDEV(PP_MAJOR, i),
+ S_IFCHR | S_IRUGO | S_IWUGO, "parports/%d", i);
+ }
+ if (parport_register_driver(&pp_driver)) {
+ printk (KERN_WARNING CHRDEV ": unable to register with parport\n");
+ goto out_class;
+ }
+
+ printk (KERN_INFO PP_VERSION "\n");
+ goto out;
+
+out_class:
+ for (i = 0; i < PARPORT_MAX; i++)
+ devfs_remove("parports/%d", i);
+ devfs_remove("parports");
+ class_simple_destroy(ppdev_class);
+out_chrdev:
+ unregister_chrdev(PP_MAJOR, CHRDEV);
+out:
+ return err;
+}
+
+static void __exit ppdev_cleanup (void)
+{
+ int i;
+ /* Clean up all parport stuff */
+ for (i = 0; i < PARPORT_MAX; i++)
+ devfs_remove("parports/%d", i);
+ parport_unregister_driver(&pp_driver);
+ devfs_remove("parports");
+ class_simple_destroy(ppdev_class);
+ unregister_chrdev (PP_MAJOR, CHRDEV);
+}
+
+module_init(ppdev_init);
+module_exit(ppdev_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(PP_MAJOR);
diff --git a/drivers/char/pty.c b/drivers/char/pty.c
new file mode 100644
index 000000000000..da32889d22c0
--- /dev/null
+++ b/drivers/char/pty.c
@@ -0,0 +1,412 @@
+/*
+ * linux/drivers/char/pty.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ *
+ * Added support for a Unix98-style ptmx device.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
+ * Added TTY_DO_WRITE_WAKEUP to enable n_tty to send POLL_OUT to
+ * waiting writers -- Sapan Bhatia <sapan@corewars.org>
+ *
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h> /* For EXPORT_SYMBOL */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/sysctl.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/bitops.h>
+#include <linux/devpts_fs.h>
+
+/* These are global because they are accessed in tty_io.c */
+#ifdef CONFIG_UNIX98_PTYS
+struct tty_driver *ptm_driver;
+static struct tty_driver *pts_driver;
+#endif
+
+static void pty_close(struct tty_struct * tty, struct file * filp)
+{
+ if (!tty)
+ return;
+ if (tty->driver->subtype == PTY_TYPE_MASTER) {
+ if (tty->count > 1)
+ printk("master pty_close: count = %d!!\n", tty->count);
+ } else {
+ if (tty->count > 2)
+ return;
+ }
+ wake_up_interruptible(&tty->read_wait);
+ wake_up_interruptible(&tty->write_wait);
+ tty->packet = 0;
+ if (!tty->link)
+ return;
+ tty->link->packet = 0;
+ set_bit(TTY_OTHER_CLOSED, &tty->link->flags);
+ wake_up_interruptible(&tty->link->read_wait);
+ wake_up_interruptible(&tty->link->write_wait);
+ if (tty->driver->subtype == PTY_TYPE_MASTER) {
+ set_bit(TTY_OTHER_CLOSED, &tty->flags);
+#ifdef CONFIG_UNIX98_PTYS
+ if (tty->driver == ptm_driver)
+ devpts_pty_kill(tty->index);
+#endif
+ tty_vhangup(tty->link);
+ }
+}
+
+/*
+ * The unthrottle routine is called by the line discipline to signal
+ * that it can receive more characters. For PTY's, the TTY_THROTTLED
+ * flag is always set, to force the line discipline to always call the
+ * unthrottle routine when there are fewer than TTY_THRESHOLD_UNTHROTTLE
+ * characters in the queue. This is necessary since each time this
+ * happens, we need to wake up any sleeping processes that could be
+ * (1) trying to send data to the pty, or (2) waiting in wait_until_sent()
+ * for the pty buffer to be drained.
+ */
+static void pty_unthrottle(struct tty_struct * tty)
+{
+ struct tty_struct *o_tty = tty->link;
+
+ if (!o_tty)
+ return;
+
+ tty_wakeup(o_tty);
+ set_bit(TTY_THROTTLED, &tty->flags);
+}
+
+/*
+ * WSH 05/24/97: modified to
+ * (1) use space in tty->flip instead of a shared temp buffer
+ * The flip buffers aren't being used for a pty, so there's lots
+ * of space available. The buffer is protected by a per-pty
+ * semaphore that should almost never come under contention.
+ * (2) avoid redundant copying for cases where count >> receive_room
+ * N.B. Calls from user space may now return an error code instead of
+ * a count.
+ *
+ * FIXME: Our pty_write method is called with our ldisc lock held but
+ * not our partners. We can't just take the other one blindly without
+ * risking deadlocks. There is also the small matter of TTY_DONT_FLIP
+ */
+static int pty_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+ struct tty_struct *to = tty->link;
+ int c;
+
+ if (!to || tty->stopped)
+ return 0;
+
+ c = to->ldisc.receive_room(to);
+ if (c > count)
+ c = count;
+ to->ldisc.receive_buf(to, buf, NULL, c);
+
+ return c;
+}
+
+static int pty_write_room(struct tty_struct *tty)
+{
+ struct tty_struct *to = tty->link;
+
+ if (!to || tty->stopped)
+ return 0;
+
+ return to->ldisc.receive_room(to);
+}
+
+/*
+ * WSH 05/24/97: Modified for asymmetric MASTER/SLAVE behavior
+ * The chars_in_buffer() value is used by the ldisc select() function
+ * to hold off writing when chars_in_buffer > WAKEUP_CHARS (== 256).
+ * The pty driver chars_in_buffer() Master/Slave must behave differently:
+ *
+ * The Master side needs to allow typed-ahead commands to accumulate
+ * while being canonicalized, so we report "our buffer" as empty until
+ * some threshold is reached, and then report the count. (Any count >
+ * WAKEUP_CHARS is regarded by select() as "full".) To avoid deadlock
+ * the count returned must be 0 if no canonical data is available to be
+ * read. (The N_TTY ldisc.chars_in_buffer now knows this.)
+ *
+ * The Slave side passes all characters in raw mode to the Master side's
+ * buffer where they can be read immediately, so in this case we can
+ * return the true count in the buffer.
+ */
+static int pty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct tty_struct *to = tty->link;
+ ssize_t (*chars_in_buffer)(struct tty_struct *);
+ int count;
+
+ /* We should get the line discipline lock for "tty->link" */
+ if (!to || !(chars_in_buffer = to->ldisc.chars_in_buffer))
+ return 0;
+
+ /* The ldisc must report 0 if no characters available to be read */
+ count = chars_in_buffer(to);
+
+ if (tty->driver->subtype == PTY_TYPE_SLAVE) return count;
+
+ /* Master side driver ... if the other side's read buffer is less than
+ * half full, return 0 to allow writers to proceed; otherwise return
+ * the count. This leaves a comfortable margin to avoid overflow,
+ * and still allows half a buffer's worth of typed-ahead commands.
+ */
+ return ((count < N_TTY_BUF_SIZE/2) ? 0 : count);
+}
+
+/* Set the lock flag on a pty */
+static int pty_set_lock(struct tty_struct *tty, int __user * arg)
+{
+ int val;
+ if (get_user(val,arg))
+ return -EFAULT;
+ if (val)
+ set_bit(TTY_PTY_LOCK, &tty->flags);
+ else
+ clear_bit(TTY_PTY_LOCK, &tty->flags);
+ return 0;
+}
+
+static void pty_flush_buffer(struct tty_struct *tty)
+{
+ struct tty_struct *to = tty->link;
+
+ if (!to)
+ return;
+
+ if (to->ldisc.flush_buffer)
+ to->ldisc.flush_buffer(to);
+
+ if (to->packet) {
+ tty->ctrl_status |= TIOCPKT_FLUSHWRITE;
+ wake_up_interruptible(&to->read_wait);
+ }
+}
+
+static int pty_open(struct tty_struct *tty, struct file * filp)
+{
+ int retval = -ENODEV;
+
+ if (!tty || !tty->link)
+ goto out;
+
+ retval = -EIO;
+ if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+ goto out;
+ if (test_bit(TTY_PTY_LOCK, &tty->link->flags))
+ goto out;
+ if (tty->link->count != 1)
+ goto out;
+
+ clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
+ set_bit(TTY_THROTTLED, &tty->flags);
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ retval = 0;
+out:
+ return retval;
+}
+
+static void pty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ tty->termios->c_cflag &= ~(CSIZE | PARENB);
+ tty->termios->c_cflag |= (CS8 | CREAD);
+}
+
+static struct tty_operations pty_ops = {
+ .open = pty_open,
+ .close = pty_close,
+ .write = pty_write,
+ .write_room = pty_write_room,
+ .flush_buffer = pty_flush_buffer,
+ .chars_in_buffer = pty_chars_in_buffer,
+ .unthrottle = pty_unthrottle,
+ .set_termios = pty_set_termios,
+};
+
+/* Traditional BSD devices */
+#ifdef CONFIG_LEGACY_PTYS
+static struct tty_driver *pty_driver, *pty_slave_driver;
+
+static int pty_bsd_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
+ return pty_set_lock(tty, (int __user *) arg);
+ }
+ return -ENOIOCTLCMD;
+}
+
+static void __init legacy_pty_init(void)
+{
+
+ pty_driver = alloc_tty_driver(NR_PTYS);
+ if (!pty_driver)
+ panic("Couldn't allocate pty driver");
+
+ pty_slave_driver = alloc_tty_driver(NR_PTYS);
+ if (!pty_slave_driver)
+ panic("Couldn't allocate pty slave driver");
+
+ pty_driver->owner = THIS_MODULE;
+ pty_driver->driver_name = "pty_master";
+ pty_driver->name = "pty";
+ pty_driver->devfs_name = "pty/m";
+ pty_driver->major = PTY_MASTER_MAJOR;
+ pty_driver->minor_start = 0;
+ pty_driver->type = TTY_DRIVER_TYPE_PTY;
+ pty_driver->subtype = PTY_TYPE_MASTER;
+ pty_driver->init_termios = tty_std_termios;
+ pty_driver->init_termios.c_iflag = 0;
+ pty_driver->init_termios.c_oflag = 0;
+ pty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+ pty_driver->init_termios.c_lflag = 0;
+ pty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
+ pty_driver->other = pty_slave_driver;
+ tty_set_operations(pty_driver, &pty_ops);
+ pty_driver->ioctl = pty_bsd_ioctl;
+
+ pty_slave_driver->owner = THIS_MODULE;
+ pty_slave_driver->driver_name = "pty_slave";
+ pty_slave_driver->name = "ttyp";
+ pty_slave_driver->devfs_name = "pty/s";
+ pty_slave_driver->major = PTY_SLAVE_MAJOR;
+ pty_slave_driver->minor_start = 0;
+ pty_slave_driver->type = TTY_DRIVER_TYPE_PTY;
+ pty_slave_driver->subtype = PTY_TYPE_SLAVE;
+ pty_slave_driver->init_termios = tty_std_termios;
+ pty_slave_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+ pty_slave_driver->flags = TTY_DRIVER_RESET_TERMIOS |
+ TTY_DRIVER_REAL_RAW;
+ pty_slave_driver->other = pty_driver;
+ tty_set_operations(pty_slave_driver, &pty_ops);
+
+ if (tty_register_driver(pty_driver))
+ panic("Couldn't register pty driver");
+ if (tty_register_driver(pty_slave_driver))
+ panic("Couldn't register pty slave driver");
+}
+#else
+static inline void legacy_pty_init(void) { }
+#endif
+
+/* Unix98 devices */
+#ifdef CONFIG_UNIX98_PTYS
+/*
+ * sysctl support for setting limits on the number of Unix98 ptys allocated.
+ * Otherwise one can eat up all kernel memory by opening /dev/ptmx repeatedly.
+ */
+int pty_limit = NR_UNIX98_PTY_DEFAULT;
+static int pty_limit_min = 0;
+static int pty_limit_max = NR_UNIX98_PTY_MAX;
+
+ctl_table pty_table[] = {
+ {
+ .ctl_name = PTY_MAX,
+ .procname = "max",
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .data = &pty_limit,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &pty_limit_min,
+ .extra2 = &pty_limit_max,
+ }, {
+ .ctl_name = PTY_NR,
+ .procname = "nr",
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = &proc_dointvec,
+ }, {
+ .ctl_name = 0
+ }
+};
+
+static int pty_unix98_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
+ return pty_set_lock(tty, (int __user *)arg);
+ case TIOCGPTN: /* Get PT Number */
+ return put_user(tty->index, (unsigned int __user *)arg);
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+static void __init unix98_pty_init(void)
+{
+ devfs_mk_dir("pts");
+ ptm_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
+ if (!ptm_driver)
+ panic("Couldn't allocate Unix98 ptm driver");
+ pts_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
+ if (!pts_driver)
+ panic("Couldn't allocate Unix98 pts driver");
+
+ ptm_driver->owner = THIS_MODULE;
+ ptm_driver->driver_name = "pty_master";
+ ptm_driver->name = "ptm";
+ ptm_driver->major = UNIX98_PTY_MASTER_MAJOR;
+ ptm_driver->minor_start = 0;
+ ptm_driver->type = TTY_DRIVER_TYPE_PTY;
+ ptm_driver->subtype = PTY_TYPE_MASTER;
+ ptm_driver->init_termios = tty_std_termios;
+ ptm_driver->init_termios.c_iflag = 0;
+ ptm_driver->init_termios.c_oflag = 0;
+ ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+ ptm_driver->init_termios.c_lflag = 0;
+ ptm_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
+ ptm_driver->other = pts_driver;
+ tty_set_operations(ptm_driver, &pty_ops);
+ ptm_driver->ioctl = pty_unix98_ioctl;
+
+ pts_driver->owner = THIS_MODULE;
+ pts_driver->driver_name = "pty_slave";
+ pts_driver->name = "pts";
+ pts_driver->major = UNIX98_PTY_SLAVE_MAJOR;
+ pts_driver->minor_start = 0;
+ pts_driver->type = TTY_DRIVER_TYPE_PTY;
+ pts_driver->subtype = PTY_TYPE_SLAVE;
+ pts_driver->init_termios = tty_std_termios;
+ pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+ pts_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
+ TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
+ pts_driver->other = ptm_driver;
+ tty_set_operations(pts_driver, &pty_ops);
+
+ if (tty_register_driver(ptm_driver))
+ panic("Couldn't register Unix98 ptm driver");
+ if (tty_register_driver(pts_driver))
+ panic("Couldn't register Unix98 pts driver");
+
+ pty_table[1].data = &ptm_driver->refcount;
+}
+#else
+static inline void unix98_pty_init(void) { }
+#endif
+
+static int __init pty_init(void)
+{
+ legacy_pty_init();
+ unix98_pty_init();
+ return 0;
+}
+module_init(pty_init);
diff --git a/drivers/char/qtronix.c b/drivers/char/qtronix.c
new file mode 100644
index 000000000000..40a3cf62e1a8
--- /dev/null
+++ b/drivers/char/qtronix.c
@@ -0,0 +1,601 @@
+/*
+ *
+ * BRIEF MODULE DESCRIPTION
+ * Qtronix 990P infrared keyboard driver.
+ *
+ *
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: MontaVista Software, Inc.
+ * ppopov@mvista.com or source@mvista.com
+ *
+ *
+ * The bottom portion of this driver was take from
+ * pc_keyb.c Please see that file for copyrights.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/config.h>
+
+/*
+ * NOTE:
+ *
+ * This driver has only been tested with the Consumer IR
+ * port of the ITE 8172 system controller.
+ *
+ * You do not need this driver if you are using the ps/2 or
+ * USB adapter that the keyboard ships with. You only need
+ * this driver if your board has a IR port and the keyboard
+ * data is being sent directly to the IR. In that case,
+ * you also need some low-level IR support. See it8172_cir.c.
+ *
+ */
+
+#ifdef CONFIG_QTRONIX_KEYBOARD
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+
+#include <asm/it8172/it8172.h>
+#include <asm/it8172/it8172_int.h>
+#include <asm/it8172/it8172_cir.h>
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <linux/pc_keyb.h>
+
+#include <asm/keyboard.h>
+#include <linux/bitops.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#define leading1 0
+#define leading2 0xF
+
+#define KBD_CIR_PORT 0
+#define AUX_RECONNECT 170 /* scancode when ps2 device is plugged (back) in */
+
+static int data_index;
+struct cir_port *cir;
+static unsigned char kbdbytes[5];
+static unsigned char cir_data[32]; /* we only need 16 chars */
+
+static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs);
+static int handle_data(unsigned char *p_data);
+static inline void handle_mouse_event(unsigned char scancode);
+static inline void handle_keyboard_event(unsigned char scancode, int down);
+static int __init psaux_init(void);
+
+static struct aux_queue *queue; /* Mouse data buffer. */
+static int aux_count = 0;
+
+/*
+ * Keys accessed through the 'Fn' key
+ * The Fn key does not produce a key-up sequence. So, the first
+ * time the user presses it, it will be key-down event. The key
+ * stays down until the user presses it again.
+ */
+#define NUM_FN_KEYS 56
+static unsigned char fn_keys[NUM_FN_KEYS] = {
+ 0,0,0,0,0,0,0,0, /* 0 7 */
+ 8,9,10,93,0,0,0,0, /* 8 15 */
+ 0,0,0,0,0,0,0,5, /* 16 23 */
+ 6,7,91,0,0,0,0,0, /* 24 31 */
+ 0,0,0,0,0,2,3,4, /* 32 39 */
+ 92,0,0,0,0,0,0,0, /* 40 47 */
+ 0,0,0,0,11,0,94,95 /* 48 55 */
+
+};
+
+void __init init_qtronix_990P_kbd(void)
+{
+ int retval;
+
+ cir = (struct cir_port *)kmalloc(sizeof(struct cir_port), GFP_KERNEL);
+ if (!cir) {
+ printk("Unable to initialize Qtronix keyboard\n");
+ return;
+ }
+
+ /*
+ * revisit
+ * this should be programmable, somehow by the, by the user.
+ */
+ cir->port = KBD_CIR_PORT;
+ cir->baud_rate = 0x1d;
+ cir->rdwos = 0;
+ cir->rxdcr = 0x3;
+ cir->hcfs = 0;
+ cir->fifo_tl = 0;
+ cir->cfq = 0x1d;
+ cir_port_init(cir);
+
+ retval = request_irq(IT8172_CIR0_IRQ, kbd_int_handler,
+ (unsigned long )(SA_INTERRUPT|SA_SHIRQ),
+ (const char *)"Qtronix IR Keyboard", (void *)cir);
+
+ if (retval) {
+ printk("unable to allocate cir %d irq %d\n",
+ cir->port, IT8172_CIR0_IRQ);
+ }
+#ifdef CONFIG_PSMOUSE
+ psaux_init();
+#endif
+}
+
+static inline unsigned char BitReverse(unsigned short key)
+{
+ unsigned char rkey = 0;
+ rkey |= (key & 0x1) << 7;
+ rkey |= (key & 0x2) << 5;
+ rkey |= (key & 0x4) << 3;
+ rkey |= (key & 0x8) << 1;
+ rkey |= (key & 0x10) >> 1;
+ rkey |= (key & 0x20) >> 3;
+ rkey |= (key & 0x40) >> 5;
+ rkey |= (key & 0x80) >> 7;
+ return rkey;
+
+}
+
+
+static inline u_int8_t UpperByte(u_int8_t data)
+{
+ return (data >> 4);
+}
+
+
+static inline u_int8_t LowerByte(u_int8_t data)
+{
+ return (data & 0xF);
+}
+
+
+int CheckSumOk(u_int8_t byte1, u_int8_t byte2,
+ u_int8_t byte3, u_int8_t byte4, u_int8_t byte5)
+{
+ u_int8_t CheckSum;
+
+ CheckSum = (byte1 & 0x0F) + byte2 + byte3 + byte4 + byte5;
+ if ( LowerByte(UpperByte(CheckSum) + LowerByte(CheckSum)) != UpperByte(byte1) )
+ return 0;
+ else
+ return 1;
+}
+
+
+static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct cir_port *cir;
+ int j;
+ unsigned char int_status;
+
+ cir = (struct cir_port *)dev_id;
+ int_status = get_int_status(cir);
+ if (int_status & 0x4) {
+ clear_fifo(cir);
+ return;
+ }
+
+ while (cir_get_rx_count(cir)) {
+
+ cir_data[data_index] = cir_read_data(cir);
+
+ if (data_index == 0) {/* expecting first byte */
+ if (cir_data[data_index] != leading1) {
+ //printk("!leading byte %x\n", cir_data[data_index]);
+ set_rx_active(cir);
+ clear_fifo(cir);
+ continue;
+ }
+ }
+ if (data_index == 1) {
+ if ((cir_data[data_index] & 0xf) != leading2) {
+ set_rx_active(cir);
+ data_index = 0; /* start over */
+ clear_fifo(cir);
+ continue;
+ }
+ }
+
+ if ( (cir_data[data_index] == 0xff)) { /* last byte */
+ //printk("data_index %d\n", data_index);
+ set_rx_active(cir);
+#if 0
+ for (j=0; j<=data_index; j++) {
+ printk("rx_data %d: %x\n", j, cir_data[j]);
+ }
+#endif
+ data_index = 0;
+ handle_data(cir_data);
+ return;
+ }
+ else if (data_index>16) {
+ set_rx_active(cir);
+#if 0
+ printk("warning: data_index %d\n", data_index);
+ for (j=0; j<=data_index; j++) {
+ printk("rx_data %d: %x\n", j, cir_data[j]);
+ }
+#endif
+ data_index = 0;
+ clear_fifo(cir);
+ return;
+ }
+ data_index++;
+ }
+}
+
+
+#define NUM_KBD_BYTES 5
+static int handle_data(unsigned char *p_data)
+{
+ u_int32_t bit_bucket;
+ u_int32_t i, j;
+ u_int32_t got_bits, next_byte;
+ int down = 0;
+
+ /* Reorganize the bit stream */
+ for (i=0; i<16; i++)
+ p_data[i] = BitReverse(~p_data[i]);
+
+ /*
+ * We've already previously checked that p_data[0]
+ * is equal to leading1 and that (p_data[1] & 0xf)
+ * is equal to leading2. These twelve bits are the
+ * leader code. We can now throw them away (the 12
+ * bits) and continue parsing the stream.
+ */
+ bit_bucket = p_data[1] << 12;
+ got_bits = 4;
+ next_byte = 2;
+
+ /*
+ * Process four bits at a time
+ */
+ for (i=0; i<NUM_KBD_BYTES; i++) {
+
+ kbdbytes[i]=0;
+
+ for (j=0; j<8; j++) /* 8 bits per byte */
+ {
+ if (got_bits < 4) {
+ bit_bucket |= (p_data[next_byte++] << (8 - got_bits));
+ got_bits += 8;
+ }
+
+ if ((bit_bucket & 0xF000) == 0x8000) {
+ /* Convert 1000b to 1 */
+ kbdbytes[i] = 0x80 | (kbdbytes[i] >> 1);
+ got_bits -= 4;
+ bit_bucket = bit_bucket << 4;
+ }
+ else if ((bit_bucket & 0xC000) == 0x8000) {
+ /* Convert 10b to 0 */
+ kbdbytes[i] = kbdbytes[i] >> 1;
+ got_bits -= 2;
+ bit_bucket = bit_bucket << 2;
+ }
+ else {
+ /* bad serial stream */
+ return 1;
+ }
+
+ if (next_byte > 16) {
+ //printk("error: too many bytes\n");
+ return 1;
+ }
+ }
+ }
+
+
+ if (!CheckSumOk(kbdbytes[0], kbdbytes[1],
+ kbdbytes[2], kbdbytes[3], kbdbytes[4])) {
+ //printk("checksum failed\n");
+ return 1;
+ }
+
+ if (kbdbytes[1] & 0x08) {
+ //printk("m: %x %x %x\n", kbdbytes[1], kbdbytes[2], kbdbytes[3]);
+ handle_mouse_event(kbdbytes[1]);
+ handle_mouse_event(kbdbytes[2]);
+ handle_mouse_event(kbdbytes[3]);
+ }
+ else {
+ if (kbdbytes[2] == 0) down = 1;
+#if 0
+ if (down)
+ printk("down %d\n", kbdbytes[3]);
+ else
+ printk("up %d\n", kbdbytes[3]);
+#endif
+ handle_keyboard_event(kbdbytes[3], down);
+ }
+ return 0;
+}
+
+
+DEFINE_SPINLOCK(kbd_controller_lock);
+static unsigned char handle_kbd_event(void);
+
+
+int kbd_setkeycode(unsigned int scancode, unsigned int keycode)
+{
+ printk("kbd_setkeycode scancode %x keycode %x\n", scancode, keycode);
+ return 0;
+}
+
+int kbd_getkeycode(unsigned int scancode)
+{
+ return scancode;
+}
+
+
+int kbd_translate(unsigned char scancode, unsigned char *keycode,
+ char raw_mode)
+{
+ static int prev_scancode = 0;
+
+ if (scancode == 0x00 || scancode == 0xff) {
+ prev_scancode = 0;
+ return 0;
+ }
+
+ /* todo */
+ if (!prev_scancode && scancode == 160) { /* Fn key down */
+ //printk("Fn key down\n");
+ prev_scancode = 160;
+ return 0;
+ }
+ else if (prev_scancode && scancode == 160) { /* Fn key up */
+ //printk("Fn key up\n");
+ prev_scancode = 0;
+ return 0;
+ }
+
+ /* todo */
+ if (prev_scancode == 160) {
+ if (scancode <= NUM_FN_KEYS) {
+ *keycode = fn_keys[scancode];
+ //printk("fn keycode %d\n", *keycode);
+ }
+ else
+ return 0;
+ }
+ else if (scancode <= 127) {
+ *keycode = scancode;
+ }
+ else
+ return 0;
+
+
+ return 1;
+}
+
+char kbd_unexpected_up(unsigned char keycode)
+{
+ //printk("kbd_unexpected_up\n");
+ return 0;
+}
+
+static unsigned char kbd_exists = 1;
+
+static inline void handle_keyboard_event(unsigned char scancode, int down)
+{
+ kbd_exists = 1;
+ handle_scancode(scancode, down);
+ tasklet_schedule(&keyboard_tasklet);
+}
+
+
+void kbd_leds(unsigned char leds)
+{
+}
+
+/* dummy */
+void kbd_init_hw(void)
+{
+}
+
+
+
+static inline void handle_mouse_event(unsigned char scancode)
+{
+ if(scancode == AUX_RECONNECT){
+ queue->head = queue->tail = 0; /* Flush input queue */
+ // __aux_write_ack(AUX_ENABLE_DEV); /* ping the mouse :) */
+ return;
+ }
+
+ if (aux_count) {
+ int head = queue->head;
+
+ queue->buf[head] = scancode;
+ head = (head + 1) & (AUX_BUF_SIZE-1);
+ if (head != queue->tail) {
+ queue->head = head;
+ kill_fasync(&queue->fasync, SIGIO, POLL_IN);
+ wake_up_interruptible(&queue->proc_list);
+ }
+ }
+}
+
+static unsigned char get_from_queue(void)
+{
+ unsigned char result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kbd_controller_lock, flags);
+ result = queue->buf[queue->tail];
+ queue->tail = (queue->tail + 1) & (AUX_BUF_SIZE-1);
+ spin_unlock_irqrestore(&kbd_controller_lock, flags);
+ return result;
+}
+
+
+static inline int queue_empty(void)
+{
+ return queue->head == queue->tail;
+}
+
+static int fasync_aux(int fd, struct file *filp, int on)
+{
+ int retval;
+
+ //printk("fasync_aux\n");
+ retval = fasync_helper(fd, filp, on, &queue->fasync);
+ if (retval < 0)
+ return retval;
+ return 0;
+}
+
+
+/*
+ * Random magic cookie for the aux device
+ */
+#define AUX_DEV ((void *)queue)
+
+static int release_aux(struct inode * inode, struct file * file)
+{
+ fasync_aux(-1, file, 0);
+ aux_count--;
+ return 0;
+}
+
+static int open_aux(struct inode * inode, struct file * file)
+{
+ if (aux_count++) {
+ return 0;
+ }
+ queue->head = queue->tail = 0; /* Flush input queue */
+ return 0;
+}
+
+/*
+ * Put bytes from input queue to buffer.
+ */
+
+static ssize_t read_aux(struct file * file, char * buffer,
+ size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ ssize_t i = count;
+ unsigned char c;
+
+ if (queue_empty()) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ add_wait_queue(&queue->proc_list, &wait);
+repeat:
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (queue_empty() && !signal_pending(current)) {
+ schedule();
+ goto repeat;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&queue->proc_list, &wait);
+ }
+ while (i > 0 && !queue_empty()) {
+ c = get_from_queue();
+ put_user(c, buffer++);
+ i--;
+ }
+ if (count-i) {
+ struct inode *inode = file->f_dentry->d_inode;
+ inode->i_atime = current_fs_time(inode->i_sb);
+ return count-i;
+ }
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ return 0;
+}
+
+/*
+ * Write to the aux device.
+ */
+
+static ssize_t write_aux(struct file * file, const char * buffer,
+ size_t count, loff_t *ppos)
+{
+ /*
+ * The ITE boards this was tested on did not have the
+ * transmit wires connected.
+ */
+ return count;
+}
+
+static unsigned int aux_poll(struct file *file, poll_table * wait)
+{
+ poll_wait(file, &queue->proc_list, wait);
+ if (!queue_empty())
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+struct file_operations psaux_fops = {
+ .read = read_aux,
+ .write = write_aux,
+ .poll = aux_poll,
+ .open = open_aux,
+ .release = release_aux,
+ .fasync = fasync_aux,
+};
+
+/*
+ * Initialize driver.
+ */
+static struct miscdevice psaux_mouse = {
+ PSMOUSE_MINOR, "psaux", &psaux_fops
+};
+
+static int __init psaux_init(void)
+{
+ int retval;
+
+ retval = misc_register(&psaux_mouse);
+ if(retval < 0)
+ return retval;
+
+ queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL);
+ memset(queue, 0, sizeof(*queue));
+ queue->head = queue->tail = 0;
+ init_waitqueue_head(&queue->proc_list);
+
+ return 0;
+}
+module_init(init_qtronix_990P_kbd);
+#endif
diff --git a/drivers/char/qtronixmap.c_shipped b/drivers/char/qtronixmap.c_shipped
new file mode 100644
index 000000000000..1e2b92b7d57a
--- /dev/null
+++ b/drivers/char/qtronixmap.c_shipped
@@ -0,0 +1,265 @@
+
+/* Do not edit this file! It was automatically generated by */
+/* loadkeys --mktable defkeymap.map > defkeymap.c */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+u_short plain_map[NR_KEYS] = {
+ 0xf200, 0xf060, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036,
+ 0xf037, 0xf038, 0xf039, 0xf030, 0xf02d, 0xf03d, 0xf200, 0xf07f,
+ 0xf009, 0xfb71, 0xfb77, 0xfb65, 0xfb72, 0xfb74, 0xfb79, 0xfb75,
+ 0xfb69, 0xfb6f, 0xfb70, 0xf05b, 0xf05d, 0xf05c, 0xf207, 0xfb61,
+ 0xfb73, 0xfb64, 0xfb66, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c,
+ 0xf03b, 0xf027, 0xf060, 0xf201, 0xf700, 0xf200, 0xfb7a, 0xfb78,
+ 0xfb63, 0xfb76, 0xfb62, 0xfb6e, 0xfb6d, 0xf02c, 0xf02e, 0xf02f,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf020, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf602, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf01b, 0xf200,
+ 0xf100, 0xf101, 0xf102, 0xf103, 0xf104, 0xf105, 0xf106, 0xf107,
+ 0xf108, 0xf109, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short shift_map[NR_KEYS] = {
+ 0xf200, 0xf07e, 0xf021, 0xf040, 0xf023, 0xf024, 0xf025, 0xf05e,
+ 0xf026, 0xf02a, 0xf028, 0xf029, 0xf05f, 0xf02b, 0xf200, 0xf07f,
+ 0xf009, 0xfb51, 0xfb57, 0xfb45, 0xfb52, 0xfb54, 0xfb59, 0xfb55,
+ 0xfb49, 0xfb4f, 0xfb50, 0xf07b, 0xf07d, 0xf07c, 0xf207, 0xfb41,
+ 0xfb53, 0xfb44, 0xfb46, 0xfb47, 0xfb48, 0xfb4a, 0xfb4b, 0xfb4c,
+ 0xf03a, 0xf022, 0xf07e, 0xf201, 0xf700, 0xf200, 0xfb5a, 0xfb58,
+ 0xfb43, 0xfb56, 0xfb42, 0xfb4e, 0xfb4d, 0xf03c, 0xf03e, 0xf03f,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf020, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf20b, 0xf20a, 0xf200,
+ 0xf200, 0xf602, 0xf213, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf01b, 0xf200,
+ 0xf10a, 0xf10b, 0xf10c, 0xf10d, 0xf10e, 0xf10f, 0xf110, 0xf111,
+ 0xf112, 0xf113, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short altgr_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf040, 0xf200, 0xf024, 0xf200, 0xf200,
+ 0xf07b, 0xf05b, 0xf05d, 0xf07d, 0xf05c, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xfb71, 0xfb77, 0xfb65, 0xfb72, 0xfb74, 0xfb79, 0xfb75,
+ 0xfb69, 0xfb6f, 0xfb70, 0xf200, 0xf200, 0xf200, 0xf207, 0xfb61,
+ 0xfb73, 0xfb64, 0xfb66, 0xfb67, 0xfb68, 0xfb6a, 0xfb6b, 0xfb6c,
+ 0xf200, 0xf200, 0xf200, 0xf201, 0xf700, 0xf200, 0xfb7a, 0xfb78,
+ 0xfb63, 0xfb76, 0xfb62, 0xfb6e, 0xfb6d, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf200, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf602, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf200, 0xf200,
+ 0xf50c, 0xf50d, 0xf50e, 0xf50f, 0xf510, 0xf511, 0xf512, 0xf513,
+ 0xf514, 0xf515, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf000, 0xf01b, 0xf01c, 0xf01d, 0xf01e,
+ 0xf01f, 0xf07f, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf200, 0xf008,
+ 0xf200, 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015,
+ 0xf009, 0xf00f, 0xf010, 0xf01b, 0xf01d, 0xf01c, 0xf207, 0xf001,
+ 0xf013, 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c,
+ 0xf007, 0xf000, 0xf200, 0xf201, 0xf700, 0xf200, 0xf01a, 0xf018,
+ 0xf003, 0xf016, 0xf002, 0xf00e, 0xf20e, 0xf07f, 0xf200, 0xf200,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf000, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf602, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf200, 0xf200,
+ 0xf100, 0xf101, 0xf102, 0xf103, 0xf104, 0xf105, 0xf106, 0xf107,
+ 0xf108, 0xf109, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short shift_ctrl_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf000, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf01f, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf011, 0xf017, 0xf005, 0xf012, 0xf014, 0xf019, 0xf015,
+ 0xf009, 0xf00f, 0xf010, 0xf200, 0xf200, 0xf200, 0xf207, 0xf001,
+ 0xf013, 0xf004, 0xf006, 0xf007, 0xf008, 0xf00a, 0xf00b, 0xf00c,
+ 0xf200, 0xf200, 0xf200, 0xf201, 0xf700, 0xf200, 0xf01a, 0xf018,
+ 0xf003, 0xf016, 0xf002, 0xf00e, 0xf00d, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf200, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf602, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short alt_map[NR_KEYS] = {
+ 0xf200, 0xf81b, 0xf831, 0xf832, 0xf833, 0xf834, 0xf835, 0xf836,
+ 0xf837, 0xf838, 0xf839, 0xf830, 0xf82d, 0xf83d, 0xf200, 0xf87f,
+ 0xf809, 0xf871, 0xf877, 0xf865, 0xf872, 0xf874, 0xf879, 0xf875,
+ 0xf869, 0xf86f, 0xf870, 0xf85b, 0xf85d, 0xf85c, 0xf207, 0xf861,
+ 0xf873, 0xf864, 0xf866, 0xf867, 0xf868, 0xf86a, 0xf86b, 0xf83b,
+ 0xf827, 0xf860, 0xf200, 0xf80d, 0xf700, 0xf200, 0xf87a, 0xf878,
+ 0xf863, 0xf876, 0xf862, 0xf82c, 0xf82e, 0xf82f, 0xf200, 0xf200,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf820, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf210,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf211, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf200, 0xf200,
+ 0xf500, 0xf501, 0xf502, 0xf503, 0xf504, 0xf505, 0xf506, 0xf507,
+ 0xf508, 0xf509, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+u_short ctrl_alt_map[NR_KEYS] = {
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf811, 0xf817, 0xf805, 0xf812, 0xf814, 0xf819, 0xf815,
+ 0xf809, 0xf80f, 0xf810, 0xf200, 0xf200, 0xf200, 0xf207, 0xf801,
+ 0xf813, 0xf804, 0xf806, 0xf807, 0xf808, 0xf80a, 0xf80b, 0xf80c,
+ 0xf200, 0xf200, 0xf200, 0xf201, 0xf700, 0xf200, 0xf81a, 0xf818,
+ 0xf803, 0xf816, 0xf802, 0xf80e, 0xf80d, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf700, 0xf702, 0xf200, 0xf703, 0xf200, 0xf703, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf115, 0xf07f, 0xf200, 0xf200, 0xf601,
+ 0xf200, 0xf200, 0xf200, 0xf603, 0xf600, 0xf118, 0xf119, 0xf200,
+ 0xf200, 0xf602, 0xf208, 0xf02d, 0xf02b, 0xf30c, 0xf02e, 0xf30d,
+ 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
+ 0xf200, 0xf200, 0xf200, 0xf117, 0xf600, 0xf200, 0xf200, 0xf200,
+ 0xf500, 0xf501, 0xf502, 0xf503, 0xf504, 0xf505, 0xf506, 0xf507,
+ 0xf508, 0xf509, 0xf200, 0xf200, 0xf200, 0xf200, 0xf11d, 0xf200,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+ plain_map, shift_map, altgr_map, 0,
+ ctrl_map, shift_ctrl_map, 0, 0,
+ alt_map, 0, 0, 0,
+ ctrl_alt_map, 0
+};
+
+unsigned int keymap_count = 7;
+
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+ '\033', '[', '[', 'A', 0,
+ '\033', '[', '[', 'B', 0,
+ '\033', '[', '[', 'C', 0,
+ '\033', '[', '[', 'D', 0,
+ '\033', '[', '[', 'E', 0,
+ '\033', '[', '1', '7', '~', 0,
+ '\033', '[', '1', '8', '~', 0,
+ '\033', '[', '1', '9', '~', 0,
+ '\033', '[', '2', '0', '~', 0,
+ '\033', '[', '2', '1', '~', 0,
+ '\033', '[', '2', '3', '~', 0,
+ '\033', '[', '2', '4', '~', 0,
+ '\033', '[', '2', '5', '~', 0,
+ '\033', '[', '2', '6', '~', 0,
+ '\033', '[', '2', '8', '~', 0,
+ '\033', '[', '2', '9', '~', 0,
+ '\033', '[', '3', '1', '~', 0,
+ '\033', '[', '3', '2', '~', 0,
+ '\033', '[', '3', '3', '~', 0,
+ '\033', '[', '3', '4', '~', 0,
+ '\033', '[', '1', '~', 0,
+ '\033', '[', '2', '~', 0,
+ '\033', '[', '3', '~', 0,
+ '\033', '[', '4', '~', 0,
+ '\033', '[', '5', '~', 0,
+ '\033', '[', '6', '~', 0,
+ '\033', '[', 'M', 0,
+ '\033', '[', 'P', 0,
+};
+
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0; /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+ func_buf + 0,
+ func_buf + 5,
+ func_buf + 10,
+ func_buf + 15,
+ func_buf + 20,
+ func_buf + 25,
+ func_buf + 31,
+ func_buf + 37,
+ func_buf + 43,
+ func_buf + 49,
+ func_buf + 55,
+ func_buf + 61,
+ func_buf + 67,
+ func_buf + 73,
+ func_buf + 79,
+ func_buf + 85,
+ func_buf + 91,
+ func_buf + 97,
+ func_buf + 103,
+ func_buf + 109,
+ func_buf + 115,
+ func_buf + 120,
+ func_buf + 125,
+ func_buf + 130,
+ func_buf + 135,
+ func_buf + 140,
+ func_buf + 145,
+ 0,
+ 0,
+ func_buf + 149,
+ 0,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+ {'`', 'A', 'À'}, {'`', 'a', 'à'},
+ {'\'', 'A', 'Á'}, {'\'', 'a', 'á'},
+ {'^', 'A', 'Â'}, {'^', 'a', 'â'},
+ {'~', 'A', 'Ã'}, {'~', 'a', 'ã'},
+ {'"', 'A', 'Ä'}, {'"', 'a', 'ä'},
+ {'O', 'A', 'Å'}, {'o', 'a', 'å'},
+ {'0', 'A', 'Å'}, {'0', 'a', 'å'},
+ {'A', 'A', 'Å'}, {'a', 'a', 'å'},
+ {'A', 'E', 'Æ'}, {'a', 'e', 'æ'},
+ {',', 'C', 'Ç'}, {',', 'c', 'ç'},
+ {'`', 'E', 'È'}, {'`', 'e', 'è'},
+ {'\'', 'E', 'É'}, {'\'', 'e', 'é'},
+ {'^', 'E', 'Ê'}, {'^', 'e', 'ê'},
+ {'"', 'E', 'Ë'}, {'"', 'e', 'ë'},
+ {'`', 'I', 'Ì'}, {'`', 'i', 'ì'},
+ {'\'', 'I', 'Í'}, {'\'', 'i', 'í'},
+ {'^', 'I', 'Î'}, {'^', 'i', 'î'},
+ {'"', 'I', 'Ï'}, {'"', 'i', 'ï'},
+ {'-', 'D', 'Ð'}, {'-', 'd', 'ð'},
+ {'~', 'N', 'Ñ'}, {'~', 'n', 'ñ'},
+ {'`', 'O', 'Ò'}, {'`', 'o', 'ò'},
+ {'\'', 'O', 'Ó'}, {'\'', 'o', 'ó'},
+ {'^', 'O', 'Ô'}, {'^', 'o', 'ô'},
+ {'~', 'O', 'Õ'}, {'~', 'o', 'õ'},
+ {'"', 'O', 'Ö'}, {'"', 'o', 'ö'},
+ {'/', 'O', 'Ø'}, {'/', 'o', 'ø'},
+ {'`', 'U', 'Ù'}, {'`', 'u', 'ù'},
+ {'\'', 'U', 'Ú'}, {'\'', 'u', 'ú'},
+ {'^', 'U', 'Û'}, {'^', 'u', 'û'},
+ {'"', 'U', 'Ü'}, {'"', 'u', 'ü'},
+ {'\'', 'Y', 'Ý'}, {'\'', 'y', 'ý'},
+ {'T', 'H', 'Þ'}, {'t', 'h', 'þ'},
+ {'s', 's', 'ß'}, {'"', 'y', 'ÿ'},
+ {'s', 'z', 'ß'}, {'i', 'j', 'ÿ'},
+};
+
+unsigned int accent_table_size = 68;
diff --git a/drivers/char/qtronixmap.map b/drivers/char/qtronixmap.map
new file mode 100644
index 000000000000..8d1ff5c1e281
--- /dev/null
+++ b/drivers/char/qtronixmap.map
@@ -0,0 +1,287 @@
+# Default kernel keymap. This uses 7 modifier combinations.
+keymaps 0-2,4-5,8,12
+# Change the above line into
+# keymaps 0-2,4-6,8,12
+# in case you want the entries
+# altgr control keycode 83 = Boot
+# altgr control keycode 111 = Boot
+# below.
+#
+# In fact AltGr is used very little, and one more keymap can
+# be saved by mapping AltGr to Alt (and adapting a few entries):
+# keycode 100 = Alt
+#
+keycode 1 = grave asciitilde
+ alt keycode 1 = Meta_Escape
+keycode 2 = one exclam
+ alt keycode 2 = Meta_one
+keycode 3 = two at at
+ control keycode 3 = nul
+ shift control keycode 3 = nul
+ alt keycode 3 = Meta_two
+keycode 4 = three numbersign
+ control keycode 4 = Escape
+ alt keycode 4 = Meta_three
+keycode 5 = four dollar dollar
+ control keycode 5 = Control_backslash
+ alt keycode 5 = Meta_four
+keycode 6 = five percent
+ control keycode 6 = Control_bracketright
+ alt keycode 6 = Meta_five
+keycode 7 = six asciicircum
+ control keycode 7 = Control_asciicircum
+ alt keycode 7 = Meta_six
+keycode 8 = seven ampersand braceleft
+ control keycode 8 = Control_underscore
+ alt keycode 8 = Meta_seven
+keycode 9 = eight asterisk bracketleft
+ control keycode 9 = Delete
+ alt keycode 9 = Meta_eight
+keycode 10 = nine parenleft bracketright
+ alt keycode 10 = Meta_nine
+keycode 11 = zero parenright braceright
+ alt keycode 11 = Meta_zero
+keycode 12 = minus underscore backslash
+ control keycode 12 = Control_underscore
+ shift control keycode 12 = Control_underscore
+ alt keycode 12 = Meta_minus
+keycode 13 = equal plus
+ alt keycode 13 = Meta_equal
+keycode 15 = Delete Delete
+ control keycode 15 = BackSpace
+ alt keycode 15 = Meta_Delete
+keycode 16 = Tab Tab
+ alt keycode 16 = Meta_Tab
+keycode 17 = q
+keycode 18 = w
+keycode 19 = e
+keycode 20 = r
+keycode 21 = t
+keycode 22 = y
+keycode 23 = u
+keycode 24 = i
+keycode 25 = o
+keycode 26 = p
+keycode 27 = bracketleft braceleft
+ control keycode 27 = Escape
+ alt keycode 27 = Meta_bracketleft
+keycode 28 = bracketright braceright
+ control keycode 28 = Control_bracketright
+ alt keycode 28 = Meta_bracketright
+keycode 29 = backslash bar
+ control keycode 29 = Control_backslash
+ alt keycode 29 = Meta_backslash
+keycode 30 = Caps_Lock
+keycode 31 = a
+keycode 32 = s
+keycode 33 = d
+keycode 34 = f
+keycode 35 = g
+keycode 36 = h
+keycode 37 = j
+keycode 38 = k
+keycode 39 = l
+keycode 40 = semicolon colon
+ alt keycode 39 = Meta_semicolon
+keycode 41 = apostrophe quotedbl
+ control keycode 40 = Control_g
+ alt keycode 40 = Meta_apostrophe
+keycode 42 = grave asciitilde
+ control keycode 41 = nul
+ alt keycode 41 = Meta_grave
+keycode 43 = Return
+ alt keycode 43 = Meta_Control_m
+keycode 44 = Shift
+keycode 46 = z
+keycode 47 = x
+keycode 48 = c
+keycode 49 = v
+keycode 50 = b
+keycode 51 = n
+keycode 52 = m
+keycode 53 = comma less
+ alt keycode 51 = Meta_comma
+keycode 54 = period greater
+ control keycode 52 = Compose
+ alt keycode 52 = Meta_period
+keycode 55 = slash question
+ control keycode 53 = Delete
+ alt keycode 53 = Meta_slash
+keycode 57 = Shift
+keycode 58 = Control
+keycode 60 = Alt
+keycode 61 = space space
+ control keycode 61 = nul
+ alt keycode 61 = Meta_space
+keycode 62 = Alt
+
+keycode 75 = Insert
+keycode 76 = Delete
+
+keycode 83 = Up
+keycode 84 = Down
+
+keycode 85 = Prior
+ shift keycode 85 = Scroll_Backward
+keycode 86 = Next
+ shift keycode 86 = Scroll_Forward
+keycode 89 = Right
+ alt keycode 89 = Incr_Console
+keycode 79 = Left
+ alt keycode 79 = Decr_Console
+
+keycode 90 = Num_Lock
+ shift keycode 90 = Bare_Num_Lock
+
+keycode 91 = minus
+keycode 92 = plus
+keycode 93 = KP_Multiply
+keycode 94 = period
+keycode 95 = KP_Divide
+
+keycode 107 = Select
+keycode 108 = Down
+
+keycode 110 = Escape Escape
+ alt keycode 1 = Meta_Escape
+
+keycode 112 = F1 F11 Console_13
+ control keycode 112 = F1
+ alt keycode 112 = Console_1
+ control alt keycode 112 = Console_1
+keycode 113 = F2 F12 Console_14
+ control keycode 113 = F2
+ alt keycode 113 = Console_2
+ control alt keycode 113 = Console_2
+keycode 114 = F3 F13 Console_15
+ control keycode 114 = F3
+ alt keycode 114 = Console_3
+ control alt keycode 114 = Console_3
+keycode 115 = F4 F14 Console_16
+ control keycode 115 = F4
+ alt keycode 115 = Console_4
+ control alt keycode 115 = Console_4
+keycode 116 = F5 F15 Console_17
+ control keycode 116 = F5
+ alt keycode 116 = Console_5
+ control alt keycode 116 = Console_5
+keycode 117 = F6 F16 Console_18
+ control keycode 117 = F6
+ alt keycode 117 = Console_6
+ control alt keycode 117 = Console_6
+keycode 118 = F7 F17 Console_19
+ control keycode 118 = F7
+ alt keycode 118 = Console_7
+ control alt keycode 118 = Console_7
+keycode 119 = F8 F18 Console_20
+ control keycode 119 = F8
+ alt keycode 119 = Console_8
+ control alt keycode 119 = Console_8
+keycode 120 = F9 F19 Console_21
+ control keycode 120 = F9
+ alt keycode 120 = Console_9
+ control alt keycode 120 = Console_9
+keycode 121 = F10 F20 Console_22
+ control keycode 121 = F10
+ alt keycode 121 = Console_10
+ control alt keycode 121 = Console_10
+
+keycode 126 = Pause
+
+
+string F1 = "\033[[A"
+string F2 = "\033[[B"
+string F3 = "\033[[C"
+string F4 = "\033[[D"
+string F5 = "\033[[E"
+string F6 = "\033[17~"
+string F7 = "\033[18~"
+string F8 = "\033[19~"
+string F9 = "\033[20~"
+string F10 = "\033[21~"
+string F11 = "\033[23~"
+string F12 = "\033[24~"
+string F13 = "\033[25~"
+string F14 = "\033[26~"
+string F15 = "\033[28~"
+string F16 = "\033[29~"
+string F17 = "\033[31~"
+string F18 = "\033[32~"
+string F19 = "\033[33~"
+string F20 = "\033[34~"
+string Find = "\033[1~"
+string Insert = "\033[2~"
+string Remove = "\033[3~"
+string Select = "\033[4~"
+string Prior = "\033[5~"
+string Next = "\033[6~"
+string Macro = "\033[M"
+string Pause = "\033[P"
+compose '`' 'A' to 'À'
+compose '`' 'a' to 'à'
+compose '\'' 'A' to 'Á'
+compose '\'' 'a' to 'á'
+compose '^' 'A' to 'Â'
+compose '^' 'a' to 'â'
+compose '~' 'A' to 'Ã'
+compose '~' 'a' to 'ã'
+compose '"' 'A' to 'Ä'
+compose '"' 'a' to 'ä'
+compose 'O' 'A' to 'Å'
+compose 'o' 'a' to 'å'
+compose '0' 'A' to 'Å'
+compose '0' 'a' to 'å'
+compose 'A' 'A' to 'Å'
+compose 'a' 'a' to 'å'
+compose 'A' 'E' to 'Æ'
+compose 'a' 'e' to 'æ'
+compose ',' 'C' to 'Ç'
+compose ',' 'c' to 'ç'
+compose '`' 'E' to 'È'
+compose '`' 'e' to 'è'
+compose '\'' 'E' to 'É'
+compose '\'' 'e' to 'é'
+compose '^' 'E' to 'Ê'
+compose '^' 'e' to 'ê'
+compose '"' 'E' to 'Ë'
+compose '"' 'e' to 'ë'
+compose '`' 'I' to 'Ì'
+compose '`' 'i' to 'ì'
+compose '\'' 'I' to 'Í'
+compose '\'' 'i' to 'í'
+compose '^' 'I' to 'Î'
+compose '^' 'i' to 'î'
+compose '"' 'I' to 'Ï'
+compose '"' 'i' to 'ï'
+compose '-' 'D' to 'Ð'
+compose '-' 'd' to 'ð'
+compose '~' 'N' to 'Ñ'
+compose '~' 'n' to 'ñ'
+compose '`' 'O' to 'Ò'
+compose '`' 'o' to 'ò'
+compose '\'' 'O' to 'Ó'
+compose '\'' 'o' to 'ó'
+compose '^' 'O' to 'Ô'
+compose '^' 'o' to 'ô'
+compose '~' 'O' to 'Õ'
+compose '~' 'o' to 'õ'
+compose '"' 'O' to 'Ö'
+compose '"' 'o' to 'ö'
+compose '/' 'O' to 'Ø'
+compose '/' 'o' to 'ø'
+compose '`' 'U' to 'Ù'
+compose '`' 'u' to 'ù'
+compose '\'' 'U' to 'Ú'
+compose '\'' 'u' to 'ú'
+compose '^' 'U' to 'Û'
+compose '^' 'u' to 'û'
+compose '"' 'U' to 'Ü'
+compose '"' 'u' to 'ü'
+compose '\'' 'Y' to 'Ý'
+compose '\'' 'y' to 'ý'
+compose 'T' 'H' to 'Þ'
+compose 't' 'h' to 'þ'
+compose 's' 's' to 'ß'
+compose '"' 'y' to 'ÿ'
+compose 's' 'z' to 'ß'
+compose 'i' 'j' to 'ÿ'
diff --git a/drivers/char/random.c b/drivers/char/random.c
new file mode 100644
index 000000000000..ad9b52c2ae3c
--- /dev/null
+++ b/drivers/char/random.c
@@ -0,0 +1,1629 @@
+/*
+ * random.c -- A strong random number generator
+ *
+ * Version 1.89, last modified 19-Sep-99
+ *
+ * Copyright Theodore Ts'o, 1994, 1995, 1996, 1997, 1998, 1999. All
+ * rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU General Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/*
+ * (now, with legal B.S. out of the way.....)
+ *
+ * This routine gathers environmental noise from device drivers, etc.,
+ * and returns good random numbers, suitable for cryptographic use.
+ * Besides the obvious cryptographic uses, these numbers are also good
+ * for seeding TCP sequence numbers, and other places where it is
+ * desirable to have numbers which are not only random, but hard to
+ * predict by an attacker.
+ *
+ * Theory of operation
+ * ===================
+ *
+ * Computers are very predictable devices. Hence it is extremely hard
+ * to produce truly random numbers on a computer --- as opposed to
+ * pseudo-random numbers, which can easily generated by using a
+ * algorithm. Unfortunately, it is very easy for attackers to guess
+ * the sequence of pseudo-random number generators, and for some
+ * applications this is not acceptable. So instead, we must try to
+ * gather "environmental noise" from the computer's environment, which
+ * must be hard for outside attackers to observe, and use that to
+ * generate random numbers. In a Unix environment, this is best done
+ * from inside the kernel.
+ *
+ * Sources of randomness from the environment include inter-keyboard
+ * timings, inter-interrupt timings from some interrupts, and other
+ * events which are both (a) non-deterministic and (b) hard for an
+ * outside observer to measure. Randomness from these sources are
+ * added to an "entropy pool", which is mixed using a CRC-like function.
+ * This is not cryptographically strong, but it is adequate assuming
+ * the randomness is not chosen maliciously, and it is fast enough that
+ * the overhead of doing it on every interrupt is very reasonable.
+ * As random bytes are mixed into the entropy pool, the routines keep
+ * an *estimate* of how many bits of randomness have been stored into
+ * the random number generator's internal state.
+ *
+ * When random bytes are desired, they are obtained by taking the SHA
+ * hash of the contents of the "entropy pool". The SHA hash avoids
+ * exposing the internal state of the entropy pool. It is believed to
+ * be computationally infeasible to derive any useful information
+ * about the input of SHA from its output. Even if it is possible to
+ * analyze SHA in some clever way, as long as the amount of data
+ * returned from the generator is less than the inherent entropy in
+ * the pool, the output data is totally unpredictable. For this
+ * reason, the routine decreases its internal estimate of how many
+ * bits of "true randomness" are contained in the entropy pool as it
+ * outputs random numbers.
+ *
+ * If this estimate goes to zero, the routine can still generate
+ * random numbers; however, an attacker may (at least in theory) be
+ * able to infer the future output of the generator from prior
+ * outputs. This requires successful cryptanalysis of SHA, which is
+ * not believed to be feasible, but there is a remote possibility.
+ * Nonetheless, these numbers should be useful for the vast majority
+ * of purposes.
+ *
+ * Exported interfaces ---- output
+ * ===============================
+ *
+ * There are three exported interfaces; the first is one designed to
+ * be used from within the kernel:
+ *
+ * void get_random_bytes(void *buf, int nbytes);
+ *
+ * This interface will return the requested number of random bytes,
+ * and place it in the requested buffer.
+ *
+ * The two other interfaces are two character devices /dev/random and
+ * /dev/urandom. /dev/random is suitable for use when very high
+ * quality randomness is desired (for example, for key generation or
+ * one-time pads), as it will only return a maximum of the number of
+ * bits of randomness (as estimated by the random number generator)
+ * contained in the entropy pool.
+ *
+ * The /dev/urandom device does not have this limit, and will return
+ * as many bytes as are requested. As more and more random bytes are
+ * requested without giving time for the entropy pool to recharge,
+ * this will result in random numbers that are merely cryptographically
+ * strong. For many applications, however, this is acceptable.
+ *
+ * Exported interfaces ---- input
+ * ==============================
+ *
+ * The current exported interfaces for gathering environmental noise
+ * from the devices are:
+ *
+ * void add_input_randomness(unsigned int type, unsigned int code,
+ * unsigned int value);
+ * void add_interrupt_randomness(int irq);
+ *
+ * add_input_randomness() uses the input layer interrupt timing, as well as
+ * the event type information from the hardware.
+ *
+ * add_interrupt_randomness() uses the inter-interrupt timing as random
+ * inputs to the entropy pool. Note that not all interrupts are good
+ * sources of randomness! For example, the timer interrupts is not a
+ * good choice, because the periodicity of the interrupts is too
+ * regular, and hence predictable to an attacker. Disk interrupts are
+ * a better measure, since the timing of the disk interrupts are more
+ * unpredictable.
+ *
+ * All of these routines try to estimate how many bits of randomness a
+ * particular randomness source. They do this by keeping track of the
+ * first and second order deltas of the event timings.
+ *
+ * Ensuring unpredictability at system startup
+ * ============================================
+ *
+ * When any operating system starts up, it will go through a sequence
+ * of actions that are fairly predictable by an adversary, especially
+ * if the start-up does not involve interaction with a human operator.
+ * This reduces the actual number of bits of unpredictability in the
+ * entropy pool below the value in entropy_count. In order to
+ * counteract this effect, it helps to carry information in the
+ * entropy pool across shut-downs and start-ups. To do this, put the
+ * following lines an appropriate script which is run during the boot
+ * sequence:
+ *
+ * echo "Initializing random number generator..."
+ * random_seed=/var/run/random-seed
+ * # Carry a random seed from start-up to start-up
+ * # Load and then save the whole entropy pool
+ * if [ -f $random_seed ]; then
+ * cat $random_seed >/dev/urandom
+ * else
+ * touch $random_seed
+ * fi
+ * chmod 600 $random_seed
+ * dd if=/dev/urandom of=$random_seed count=1 bs=512
+ *
+ * and the following lines in an appropriate script which is run as
+ * the system is shutdown:
+ *
+ * # Carry a random seed from shut-down to start-up
+ * # Save the whole entropy pool
+ * echo "Saving random seed..."
+ * random_seed=/var/run/random-seed
+ * touch $random_seed
+ * chmod 600 $random_seed
+ * dd if=/dev/urandom of=$random_seed count=1 bs=512
+ *
+ * For example, on most modern systems using the System V init
+ * scripts, such code fragments would be found in
+ * /etc/rc.d/init.d/random. On older Linux systems, the correct script
+ * location might be in /etc/rcb.d/rc.local or /etc/rc.d/rc.0.
+ *
+ * Effectively, these commands cause the contents of the entropy pool
+ * to be saved at shut-down time and reloaded into the entropy pool at
+ * start-up. (The 'dd' in the addition to the bootup script is to
+ * make sure that /etc/random-seed is different for every start-up,
+ * even if the system crashes without executing rc.0.) Even with
+ * complete knowledge of the start-up activities, predicting the state
+ * of the entropy pool requires knowledge of the previous history of
+ * the system.
+ *
+ * Configuring the /dev/random driver under Linux
+ * ==============================================
+ *
+ * The /dev/random driver under Linux uses minor numbers 8 and 9 of
+ * the /dev/mem major number (#1). So if your system does not have
+ * /dev/random and /dev/urandom created already, they can be created
+ * by using the commands:
+ *
+ * mknod /dev/random c 1 8
+ * mknod /dev/urandom c 1 9
+ *
+ * Acknowledgements:
+ * =================
+ *
+ * Ideas for constructing this random number generator were derived
+ * from Pretty Good Privacy's random number generator, and from private
+ * discussions with Phil Karn. Colin Plumb provided a faster random
+ * number generator, which speed up the mixing function of the entropy
+ * pool, taken from PGPfone. Dale Worley has also contributed many
+ * useful ideas and suggestions to improve this driver.
+ *
+ * Any flaws in the design are solely my responsibility, and should
+ * not be attributed to the Phil, Colin, or any of authors of PGP.
+ *
+ * Further background information on this topic may be obtained from
+ * RFC 1750, "Randomness Recommendations for Security", by Donald
+ * Eastlake, Steve Crocker, and Jeff Schiller.
+ */
+
+#include <linux/utsname.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/genhd.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/percpu.h>
+#include <linux/cryptohash.h>
+
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/*
+ * Configuration information
+ */
+#define INPUT_POOL_WORDS 128
+#define OUTPUT_POOL_WORDS 32
+#define SEC_XFER_SIZE 512
+
+/*
+ * The minimum number of bits of entropy before we wake up a read on
+ * /dev/random. Should be enough to do a significant reseed.
+ */
+static int random_read_wakeup_thresh = 64;
+
+/*
+ * If the entropy count falls under this number of bits, then we
+ * should wake up processes which are selecting or polling on write
+ * access to /dev/random.
+ */
+static int random_write_wakeup_thresh = 128;
+
+/*
+ * When the input pool goes over trickle_thresh, start dropping most
+ * samples to avoid wasting CPU time and reduce lock contention.
+ */
+
+static int trickle_thresh = INPUT_POOL_WORDS * 28;
+
+static DEFINE_PER_CPU(int, trickle_count) = 0;
+
+/*
+ * A pool of size .poolwords is stirred with a primitive polynomial
+ * of degree .poolwords over GF(2). The taps for various sizes are
+ * defined below. They are chosen to be evenly spaced (minimum RMS
+ * distance from evenly spaced; the numbers in the comments are a
+ * scaled squared error sum) except for the last tap, which is 1 to
+ * get the twisting happening as fast as possible.
+ */
+static struct poolinfo {
+ int poolwords;
+ int tap1, tap2, tap3, tap4, tap5;
+} poolinfo_table[] = {
+ /* x^128 + x^103 + x^76 + x^51 +x^25 + x + 1 -- 105 */
+ { 128, 103, 76, 51, 25, 1 },
+ /* x^32 + x^26 + x^20 + x^14 + x^7 + x + 1 -- 15 */
+ { 32, 26, 20, 14, 7, 1 },
+#if 0
+ /* x^2048 + x^1638 + x^1231 + x^819 + x^411 + x + 1 -- 115 */
+ { 2048, 1638, 1231, 819, 411, 1 },
+
+ /* x^1024 + x^817 + x^615 + x^412 + x^204 + x + 1 -- 290 */
+ { 1024, 817, 615, 412, 204, 1 },
+
+ /* x^1024 + x^819 + x^616 + x^410 + x^207 + x^2 + 1 -- 115 */
+ { 1024, 819, 616, 410, 207, 2 },
+
+ /* x^512 + x^411 + x^308 + x^208 + x^104 + x + 1 -- 225 */
+ { 512, 411, 308, 208, 104, 1 },
+
+ /* x^512 + x^409 + x^307 + x^206 + x^102 + x^2 + 1 -- 95 */
+ { 512, 409, 307, 206, 102, 2 },
+ /* x^512 + x^409 + x^309 + x^205 + x^103 + x^2 + 1 -- 95 */
+ { 512, 409, 309, 205, 103, 2 },
+
+ /* x^256 + x^205 + x^155 + x^101 + x^52 + x + 1 -- 125 */
+ { 256, 205, 155, 101, 52, 1 },
+
+ /* x^128 + x^103 + x^78 + x^51 + x^27 + x^2 + 1 -- 70 */
+ { 128, 103, 78, 51, 27, 2 },
+
+ /* x^64 + x^52 + x^39 + x^26 + x^14 + x + 1 -- 15 */
+ { 64, 52, 39, 26, 14, 1 },
+#endif
+};
+
+#define POOLBITS poolwords*32
+#define POOLBYTES poolwords*4
+
+/*
+ * For the purposes of better mixing, we use the CRC-32 polynomial as
+ * well to make a twisted Generalized Feedback Shift Reigster
+ *
+ * (See M. Matsumoto & Y. Kurita, 1992. Twisted GFSR generators. ACM
+ * Transactions on Modeling and Computer Simulation 2(3):179-194.
+ * Also see M. Matsumoto & Y. Kurita, 1994. Twisted GFSR generators
+ * II. ACM Transactions on Mdeling and Computer Simulation 4:254-266)
+ *
+ * Thanks to Colin Plumb for suggesting this.
+ *
+ * We have not analyzed the resultant polynomial to prove it primitive;
+ * in fact it almost certainly isn't. Nonetheless, the irreducible factors
+ * of a random large-degree polynomial over GF(2) are more than large enough
+ * that periodicity is not a concern.
+ *
+ * The input hash is much less sensitive than the output hash. All
+ * that we want of it is that it be a good non-cryptographic hash;
+ * i.e. it not produce collisions when fed "random" data of the sort
+ * we expect to see. As long as the pool state differs for different
+ * inputs, we have preserved the input entropy and done a good job.
+ * The fact that an intelligent attacker can construct inputs that
+ * will produce controlled alterations to the pool's state is not
+ * important because we don't consider such inputs to contribute any
+ * randomness. The only property we need with respect to them is that
+ * the attacker can't increase his/her knowledge of the pool's state.
+ * Since all additions are reversible (knowing the final state and the
+ * input, you can reconstruct the initial state), if an attacker has
+ * any uncertainty about the initial state, he/she can only shuffle
+ * that uncertainty about, but never cause any collisions (which would
+ * decrease the uncertainty).
+ *
+ * The chosen system lets the state of the pool be (essentially) the input
+ * modulo the generator polymnomial. Now, for random primitive polynomials,
+ * this is a universal class of hash functions, meaning that the chance
+ * of a collision is limited by the attacker's knowledge of the generator
+ * polynomail, so if it is chosen at random, an attacker can never force
+ * a collision. Here, we use a fixed polynomial, but we *can* assume that
+ * ###--> it is unknown to the processes generating the input entropy. <-###
+ * Because of this important property, this is a good, collision-resistant
+ * hash; hash collisions will occur no more often than chance.
+ */
+
+/*
+ * Static global variables
+ */
+static DECLARE_WAIT_QUEUE_HEAD(random_read_wait);
+static DECLARE_WAIT_QUEUE_HEAD(random_write_wait);
+
+#if 0
+static int debug = 0;
+module_param(debug, bool, 0644);
+#define DEBUG_ENT(fmt, arg...) do { if (debug) \
+ printk(KERN_DEBUG "random %04d %04d %04d: " \
+ fmt,\
+ input_pool.entropy_count,\
+ blocking_pool.entropy_count,\
+ nonblocking_pool.entropy_count,\
+ ## arg); } while (0)
+#else
+#define DEBUG_ENT(fmt, arg...) do {} while (0)
+#endif
+
+/**********************************************************************
+ *
+ * OS independent entropy store. Here are the functions which handle
+ * storing entropy in an entropy pool.
+ *
+ **********************************************************************/
+
+struct entropy_store;
+struct entropy_store {
+ /* mostly-read data: */
+ struct poolinfo *poolinfo;
+ __u32 *pool;
+ const char *name;
+ int limit;
+ struct entropy_store *pull;
+
+ /* read-write data: */
+ spinlock_t lock ____cacheline_aligned_in_smp;
+ unsigned add_ptr;
+ int entropy_count;
+ int input_rotate;
+};
+
+static __u32 input_pool_data[INPUT_POOL_WORDS];
+static __u32 blocking_pool_data[OUTPUT_POOL_WORDS];
+static __u32 nonblocking_pool_data[OUTPUT_POOL_WORDS];
+
+static struct entropy_store input_pool = {
+ .poolinfo = &poolinfo_table[0],
+ .name = "input",
+ .limit = 1,
+ .lock = SPIN_LOCK_UNLOCKED,
+ .pool = input_pool_data
+};
+
+static struct entropy_store blocking_pool = {
+ .poolinfo = &poolinfo_table[1],
+ .name = "blocking",
+ .limit = 1,
+ .pull = &input_pool,
+ .lock = SPIN_LOCK_UNLOCKED,
+ .pool = blocking_pool_data
+};
+
+static struct entropy_store nonblocking_pool = {
+ .poolinfo = &poolinfo_table[1],
+ .name = "nonblocking",
+ .pull = &input_pool,
+ .lock = SPIN_LOCK_UNLOCKED,
+ .pool = nonblocking_pool_data
+};
+
+/*
+ * This function adds a byte into the entropy "pool". It does not
+ * update the entropy estimate. The caller should call
+ * credit_entropy_store if this is appropriate.
+ *
+ * The pool is stirred with a primitive polynomial of the appropriate
+ * degree, and then twisted. We twist by three bits at a time because
+ * it's cheap to do so and helps slightly in the expected case where
+ * the entropy is concentrated in the low-order bits.
+ */
+static void __add_entropy_words(struct entropy_store *r, const __u32 *in,
+ int nwords, __u32 out[16])
+{
+ static __u32 const twist_table[8] = {
+ 0x00000000, 0x3b6e20c8, 0x76dc4190, 0x4db26158,
+ 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278 };
+ unsigned long i, add_ptr, tap1, tap2, tap3, tap4, tap5;
+ int new_rotate, input_rotate;
+ int wordmask = r->poolinfo->poolwords - 1;
+ __u32 w, next_w;
+ unsigned long flags;
+
+ /* Taps are constant, so we can load them without holding r->lock. */
+ tap1 = r->poolinfo->tap1;
+ tap2 = r->poolinfo->tap2;
+ tap3 = r->poolinfo->tap3;
+ tap4 = r->poolinfo->tap4;
+ tap5 = r->poolinfo->tap5;
+ next_w = *in++;
+
+ spin_lock_irqsave(&r->lock, flags);
+ prefetch_range(r->pool, wordmask);
+ input_rotate = r->input_rotate;
+ add_ptr = r->add_ptr;
+
+ while (nwords--) {
+ w = rol32(next_w, input_rotate);
+ if (nwords > 0)
+ next_w = *in++;
+ i = add_ptr = (add_ptr - 1) & wordmask;
+ /*
+ * Normally, we add 7 bits of rotation to the pool.
+ * At the beginning of the pool, add an extra 7 bits
+ * rotation, so that successive passes spread the
+ * input bits across the pool evenly.
+ */
+ new_rotate = input_rotate + 14;
+ if (i)
+ new_rotate = input_rotate + 7;
+ input_rotate = new_rotate & 31;
+
+ /* XOR in the various taps */
+ w ^= r->pool[(i + tap1) & wordmask];
+ w ^= r->pool[(i + tap2) & wordmask];
+ w ^= r->pool[(i + tap3) & wordmask];
+ w ^= r->pool[(i + tap4) & wordmask];
+ w ^= r->pool[(i + tap5) & wordmask];
+ w ^= r->pool[i];
+ r->pool[i] = (w >> 3) ^ twist_table[w & 7];
+ }
+
+ r->input_rotate = input_rotate;
+ r->add_ptr = add_ptr;
+
+ if (out) {
+ for (i = 0; i < 16; i++) {
+ out[i] = r->pool[add_ptr];
+ add_ptr = (add_ptr - 1) & wordmask;
+ }
+ }
+
+ spin_unlock_irqrestore(&r->lock, flags);
+}
+
+static inline void add_entropy_words(struct entropy_store *r, const __u32 *in,
+ int nwords)
+{
+ __add_entropy_words(r, in, nwords, NULL);
+}
+
+/*
+ * Credit (or debit) the entropy store with n bits of entropy
+ */
+static void credit_entropy_store(struct entropy_store *r, int nbits)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&r->lock, flags);
+
+ if (r->entropy_count + nbits < 0) {
+ DEBUG_ENT("negative entropy/overflow (%d+%d)\n",
+ r->entropy_count, nbits);
+ r->entropy_count = 0;
+ } else if (r->entropy_count + nbits > r->poolinfo->POOLBITS) {
+ r->entropy_count = r->poolinfo->POOLBITS;
+ } else {
+ r->entropy_count += nbits;
+ if (nbits)
+ DEBUG_ENT("added %d entropy credits to %s\n",
+ nbits, r->name);
+ }
+
+ spin_unlock_irqrestore(&r->lock, flags);
+}
+
+/*********************************************************************
+ *
+ * Entropy input management
+ *
+ *********************************************************************/
+
+/* There is one of these per entropy source */
+struct timer_rand_state {
+ cycles_t last_time;
+ long last_delta,last_delta2;
+ unsigned dont_count_entropy:1;
+};
+
+static struct timer_rand_state input_timer_state;
+static struct timer_rand_state *irq_timer_state[NR_IRQS];
+
+/*
+ * This function adds entropy to the entropy "pool" by using timing
+ * delays. It uses the timer_rand_state structure to make an estimate
+ * of how many bits of entropy this call has added to the pool.
+ *
+ * The number "num" is also added to the pool - it should somehow describe
+ * the type of event which just happened. This is currently 0-255 for
+ * keyboard scan codes, and 256 upwards for interrupts.
+ *
+ */
+static void add_timer_randomness(struct timer_rand_state *state, unsigned num)
+{
+ struct {
+ cycles_t cycles;
+ long jiffies;
+ unsigned num;
+ } sample;
+ long delta, delta2, delta3;
+
+ preempt_disable();
+ /* if over the trickle threshold, use only 1 in 4096 samples */
+ if (input_pool.entropy_count > trickle_thresh &&
+ (__get_cpu_var(trickle_count)++ & 0xfff))
+ goto out;
+
+ sample.jiffies = jiffies;
+ sample.cycles = get_cycles();
+ sample.num = num;
+ add_entropy_words(&input_pool, (u32 *)&sample, sizeof(sample)/4);
+
+ /*
+ * Calculate number of bits of randomness we probably added.
+ * We take into account the first, second and third-order deltas
+ * in order to make our estimate.
+ */
+
+ if (!state->dont_count_entropy) {
+ delta = sample.jiffies - state->last_time;
+ state->last_time = sample.jiffies;
+
+ delta2 = delta - state->last_delta;
+ state->last_delta = delta;
+
+ delta3 = delta2 - state->last_delta2;
+ state->last_delta2 = delta2;
+
+ if (delta < 0)
+ delta = -delta;
+ if (delta2 < 0)
+ delta2 = -delta2;
+ if (delta3 < 0)
+ delta3 = -delta3;
+ if (delta > delta2)
+ delta = delta2;
+ if (delta > delta3)
+ delta = delta3;
+
+ /*
+ * delta is now minimum absolute delta.
+ * Round down by 1 bit on general principles,
+ * and limit entropy entimate to 12 bits.
+ */
+ credit_entropy_store(&input_pool,
+ min_t(int, fls(delta>>1), 11));
+ }
+
+ if(input_pool.entropy_count >= random_read_wakeup_thresh)
+ wake_up_interruptible(&random_read_wait);
+
+out:
+ preempt_enable();
+}
+
+extern void add_input_randomness(unsigned int type, unsigned int code,
+ unsigned int value)
+{
+ static unsigned char last_value;
+
+ /* ignore autorepeat and the like */
+ if (value == last_value)
+ return;
+
+ DEBUG_ENT("input event\n");
+ last_value = value;
+ add_timer_randomness(&input_timer_state,
+ (type << 4) ^ code ^ (code >> 4) ^ value);
+}
+
+void add_interrupt_randomness(int irq)
+{
+ if (irq >= NR_IRQS || irq_timer_state[irq] == 0)
+ return;
+
+ DEBUG_ENT("irq event %d\n", irq);
+ add_timer_randomness(irq_timer_state[irq], 0x100 + irq);
+}
+
+void add_disk_randomness(struct gendisk *disk)
+{
+ if (!disk || !disk->random)
+ return;
+ /* first major is 1, so we get >= 0x200 here */
+ DEBUG_ENT("disk event %d:%d\n", disk->major, disk->first_minor);
+
+ add_timer_randomness(disk->random,
+ 0x100 + MKDEV(disk->major, disk->first_minor));
+}
+
+EXPORT_SYMBOL(add_disk_randomness);
+
+#define EXTRACT_SIZE 10
+
+/*********************************************************************
+ *
+ * Entropy extraction routines
+ *
+ *********************************************************************/
+
+static ssize_t extract_entropy(struct entropy_store *r, void * buf,
+ size_t nbytes, int min, int rsvd);
+
+/*
+ * This utility inline function is responsible for transfering entropy
+ * from the primary pool to the secondary extraction pool. We make
+ * sure we pull enough for a 'catastrophic reseed'.
+ */
+static void xfer_secondary_pool(struct entropy_store *r, size_t nbytes)
+{
+ __u32 tmp[OUTPUT_POOL_WORDS];
+
+ if (r->pull && r->entropy_count < nbytes * 8 &&
+ r->entropy_count < r->poolinfo->POOLBITS) {
+ int bytes = max_t(int, random_read_wakeup_thresh / 8,
+ min_t(int, nbytes, sizeof(tmp)));
+ int rsvd = r->limit ? 0 : random_read_wakeup_thresh/4;
+
+ DEBUG_ENT("going to reseed %s with %d bits "
+ "(%d of %d requested)\n",
+ r->name, bytes * 8, nbytes * 8, r->entropy_count);
+
+ bytes=extract_entropy(r->pull, tmp, bytes,
+ random_read_wakeup_thresh / 8, rsvd);
+ add_entropy_words(r, tmp, (bytes + 3) / 4);
+ credit_entropy_store(r, bytes*8);
+ }
+}
+
+/*
+ * These functions extracts randomness from the "entropy pool", and
+ * returns it in a buffer.
+ *
+ * The min parameter specifies the minimum amount we can pull before
+ * failing to avoid races that defeat catastrophic reseeding while the
+ * reserved parameter indicates how much entropy we must leave in the
+ * pool after each pull to avoid starving other readers.
+ *
+ * Note: extract_entropy() assumes that .poolwords is a multiple of 16 words.
+ */
+
+static size_t account(struct entropy_store *r, size_t nbytes, int min,
+ int reserved)
+{
+ unsigned long flags;
+
+ BUG_ON(r->entropy_count > r->poolinfo->POOLBITS);
+
+ /* Hold lock while accounting */
+ spin_lock_irqsave(&r->lock, flags);
+
+ DEBUG_ENT("trying to extract %d bits from %s\n",
+ nbytes * 8, r->name);
+
+ /* Can we pull enough? */
+ if (r->entropy_count / 8 < min + reserved) {
+ nbytes = 0;
+ } else {
+ /* If limited, never pull more than available */
+ if (r->limit && nbytes + reserved >= r->entropy_count / 8)
+ nbytes = r->entropy_count/8 - reserved;
+
+ if(r->entropy_count / 8 >= nbytes + reserved)
+ r->entropy_count -= nbytes*8;
+ else
+ r->entropy_count = reserved;
+
+ if (r->entropy_count < random_write_wakeup_thresh)
+ wake_up_interruptible(&random_write_wait);
+ }
+
+ DEBUG_ENT("debiting %d entropy credits from %s%s\n",
+ nbytes * 8, r->name, r->limit ? "" : " (unlimited)");
+
+ spin_unlock_irqrestore(&r->lock, flags);
+
+ return nbytes;
+}
+
+static void extract_buf(struct entropy_store *r, __u8 *out)
+{
+ int i, x;
+ __u32 data[16], buf[5 + SHA_WORKSPACE_WORDS];
+
+ sha_init(buf);
+ /*
+ * As we hash the pool, we mix intermediate values of
+ * the hash back into the pool. This eliminates
+ * backtracking attacks (where the attacker knows
+ * the state of the pool plus the current outputs, and
+ * attempts to find previous ouputs), unless the hash
+ * function can be inverted.
+ */
+ for (i = 0, x = 0; i < r->poolinfo->poolwords; i += 16, x+=2) {
+ sha_transform(buf, (__u8 *)r->pool+i, buf + 5);
+ add_entropy_words(r, &buf[x % 5], 1);
+ }
+
+ /*
+ * To avoid duplicates, we atomically extract a
+ * portion of the pool while mixing, and hash one
+ * final time.
+ */
+ __add_entropy_words(r, &buf[x % 5], 1, data);
+ sha_transform(buf, (__u8 *)data, buf + 5);
+
+ /*
+ * In case the hash function has some recognizable
+ * output pattern, we fold it in half.
+ */
+
+ buf[0] ^= buf[3];
+ buf[1] ^= buf[4];
+ buf[0] ^= rol32(buf[3], 16);
+ memcpy(out, buf, EXTRACT_SIZE);
+ memset(buf, 0, sizeof(buf));
+}
+
+static ssize_t extract_entropy(struct entropy_store *r, void * buf,
+ size_t nbytes, int min, int reserved)
+{
+ ssize_t ret = 0, i;
+ __u8 tmp[EXTRACT_SIZE];
+
+ xfer_secondary_pool(r, nbytes);
+ nbytes = account(r, nbytes, min, reserved);
+
+ while (nbytes) {
+ extract_buf(r, tmp);
+ i = min_t(int, nbytes, EXTRACT_SIZE);
+ memcpy(buf, tmp, i);
+ nbytes -= i;
+ buf += i;
+ ret += i;
+ }
+
+ /* Wipe data just returned from memory */
+ memset(tmp, 0, sizeof(tmp));
+
+ return ret;
+}
+
+static ssize_t extract_entropy_user(struct entropy_store *r, void __user *buf,
+ size_t nbytes)
+{
+ ssize_t ret = 0, i;
+ __u8 tmp[EXTRACT_SIZE];
+
+ xfer_secondary_pool(r, nbytes);
+ nbytes = account(r, nbytes, 0, 0);
+
+ while (nbytes) {
+ if (need_resched()) {
+ if (signal_pending(current)) {
+ if (ret == 0)
+ ret = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+
+ extract_buf(r, tmp);
+ i = min_t(int, nbytes, EXTRACT_SIZE);
+ if (copy_to_user(buf, tmp, i)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ nbytes -= i;
+ buf += i;
+ ret += i;
+ }
+
+ /* Wipe data just returned from memory */
+ memset(tmp, 0, sizeof(tmp));
+
+ return ret;
+}
+
+/*
+ * This function is the exported kernel interface. It returns some
+ * number of good random numbers, suitable for seeding TCP sequence
+ * numbers, etc.
+ */
+void get_random_bytes(void *buf, int nbytes)
+{
+ extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0);
+}
+
+EXPORT_SYMBOL(get_random_bytes);
+
+/*
+ * init_std_data - initialize pool with system data
+ *
+ * @r: pool to initialize
+ *
+ * This function clears the pool's entropy count and mixes some system
+ * data into the pool to prepare it for use. The pool is not cleared
+ * as that can only decrease the entropy in the pool.
+ */
+static void init_std_data(struct entropy_store *r)
+{
+ struct timeval tv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&r->lock, flags);
+ r->entropy_count = 0;
+ spin_unlock_irqrestore(&r->lock, flags);
+
+ do_gettimeofday(&tv);
+ add_entropy_words(r, (__u32 *)&tv, sizeof(tv)/4);
+ add_entropy_words(r, (__u32 *)&system_utsname,
+ sizeof(system_utsname)/4);
+}
+
+static int __init rand_initialize(void)
+{
+ init_std_data(&input_pool);
+ init_std_data(&blocking_pool);
+ init_std_data(&nonblocking_pool);
+ return 0;
+}
+module_init(rand_initialize);
+
+void rand_initialize_irq(int irq)
+{
+ struct timer_rand_state *state;
+
+ if (irq >= NR_IRQS || irq_timer_state[irq])
+ return;
+
+ /*
+ * If kmalloc returns null, we just won't use that entropy
+ * source.
+ */
+ state = kmalloc(sizeof(struct timer_rand_state), GFP_KERNEL);
+ if (state) {
+ memset(state, 0, sizeof(struct timer_rand_state));
+ irq_timer_state[irq] = state;
+ }
+}
+
+void rand_initialize_disk(struct gendisk *disk)
+{
+ struct timer_rand_state *state;
+
+ /*
+ * If kmalloc returns null, we just won't use that entropy
+ * source.
+ */
+ state = kmalloc(sizeof(struct timer_rand_state), GFP_KERNEL);
+ if (state) {
+ memset(state, 0, sizeof(struct timer_rand_state));
+ disk->random = state;
+ }
+}
+
+static ssize_t
+random_read(struct file * file, char __user * buf, size_t nbytes, loff_t *ppos)
+{
+ ssize_t n, retval = 0, count = 0;
+
+ if (nbytes == 0)
+ return 0;
+
+ while (nbytes > 0) {
+ n = nbytes;
+ if (n > SEC_XFER_SIZE)
+ n = SEC_XFER_SIZE;
+
+ DEBUG_ENT("reading %d bits\n", n*8);
+
+ n = extract_entropy_user(&blocking_pool, buf, n);
+
+ DEBUG_ENT("read got %d bits (%d still needed)\n",
+ n*8, (nbytes-n)*8);
+
+ if (n == 0) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ DEBUG_ENT("sleeping?\n");
+
+ wait_event_interruptible(random_read_wait,
+ input_pool.entropy_count >=
+ random_read_wakeup_thresh);
+
+ DEBUG_ENT("awake\n");
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ continue;
+ }
+
+ if (n < 0) {
+ retval = n;
+ break;
+ }
+ count += n;
+ buf += n;
+ nbytes -= n;
+ break; /* This break makes the device work */
+ /* like a named pipe */
+ }
+
+ /*
+ * If we gave the user some bytes, update the access time.
+ */
+ if (count)
+ file_accessed(file);
+
+ return (count ? count : retval);
+}
+
+static ssize_t
+urandom_read(struct file * file, char __user * buf,
+ size_t nbytes, loff_t *ppos)
+{
+ return extract_entropy_user(&nonblocking_pool, buf, nbytes);
+}
+
+static unsigned int
+random_poll(struct file *file, poll_table * wait)
+{
+ unsigned int mask;
+
+ poll_wait(file, &random_read_wait, wait);
+ poll_wait(file, &random_write_wait, wait);
+ mask = 0;
+ if (input_pool.entropy_count >= random_read_wakeup_thresh)
+ mask |= POLLIN | POLLRDNORM;
+ if (input_pool.entropy_count < random_write_wakeup_thresh)
+ mask |= POLLOUT | POLLWRNORM;
+ return mask;
+}
+
+static ssize_t
+random_write(struct file * file, const char __user * buffer,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0;
+ size_t bytes;
+ __u32 buf[16];
+ const char __user *p = buffer;
+ size_t c = count;
+
+ while (c > 0) {
+ bytes = min(c, sizeof(buf));
+
+ bytes -= copy_from_user(&buf, p, bytes);
+ if (!bytes) {
+ ret = -EFAULT;
+ break;
+ }
+ c -= bytes;
+ p += bytes;
+
+ add_entropy_words(&input_pool, buf, (bytes + 3) / 4);
+ }
+ if (p == buffer) {
+ return (ssize_t)ret;
+ } else {
+ struct inode *inode = file->f_dentry->d_inode;
+ inode->i_mtime = current_fs_time(inode->i_sb);
+ mark_inode_dirty(inode);
+ return (ssize_t)(p - buffer);
+ }
+}
+
+static int
+random_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ int size, ent_count;
+ int __user *p = (int __user *)arg;
+ int retval;
+
+ switch (cmd) {
+ case RNDGETENTCNT:
+ ent_count = input_pool.entropy_count;
+ if (put_user(ent_count, p))
+ return -EFAULT;
+ return 0;
+ case RNDADDTOENTCNT:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(ent_count, p))
+ return -EFAULT;
+ credit_entropy_store(&input_pool, ent_count);
+ /*
+ * Wake up waiting processes if we have enough
+ * entropy.
+ */
+ if (input_pool.entropy_count >= random_read_wakeup_thresh)
+ wake_up_interruptible(&random_read_wait);
+ return 0;
+ case RNDADDENTROPY:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(ent_count, p++))
+ return -EFAULT;
+ if (ent_count < 0)
+ return -EINVAL;
+ if (get_user(size, p++))
+ return -EFAULT;
+ retval = random_write(file, (const char __user *) p,
+ size, &file->f_pos);
+ if (retval < 0)
+ return retval;
+ credit_entropy_store(&input_pool, ent_count);
+ /*
+ * Wake up waiting processes if we have enough
+ * entropy.
+ */
+ if (input_pool.entropy_count >= random_read_wakeup_thresh)
+ wake_up_interruptible(&random_read_wait);
+ return 0;
+ case RNDZAPENTCNT:
+ case RNDCLEARPOOL:
+ /* Clear the entropy pool counters. */
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ init_std_data(&input_pool);
+ init_std_data(&blocking_pool);
+ init_std_data(&nonblocking_pool);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+struct file_operations random_fops = {
+ .read = random_read,
+ .write = random_write,
+ .poll = random_poll,
+ .ioctl = random_ioctl,
+};
+
+struct file_operations urandom_fops = {
+ .read = urandom_read,
+ .write = random_write,
+ .ioctl = random_ioctl,
+};
+
+/***************************************************************
+ * Random UUID interface
+ *
+ * Used here for a Boot ID, but can be useful for other kernel
+ * drivers.
+ ***************************************************************/
+
+/*
+ * Generate random UUID
+ */
+void generate_random_uuid(unsigned char uuid_out[16])
+{
+ get_random_bytes(uuid_out, 16);
+ /* Set UUID version to 4 --- truely random generation */
+ uuid_out[6] = (uuid_out[6] & 0x0F) | 0x40;
+ /* Set the UUID variant to DCE */
+ uuid_out[8] = (uuid_out[8] & 0x3F) | 0x80;
+}
+
+EXPORT_SYMBOL(generate_random_uuid);
+
+/********************************************************************
+ *
+ * Sysctl interface
+ *
+ ********************************************************************/
+
+#ifdef CONFIG_SYSCTL
+
+#include <linux/sysctl.h>
+
+static int min_read_thresh = 8, min_write_thresh;
+static int max_read_thresh = INPUT_POOL_WORDS * 32;
+static int max_write_thresh = INPUT_POOL_WORDS * 32;
+static char sysctl_bootid[16];
+
+/*
+ * These functions is used to return both the bootid UUID, and random
+ * UUID. The difference is in whether table->data is NULL; if it is,
+ * then a new UUID is generated and returned to the user.
+ *
+ * If the user accesses this via the proc interface, it will be returned
+ * as an ASCII string in the standard UUID format. If accesses via the
+ * sysctl system call, it is returned as 16 bytes of binary data.
+ */
+static int proc_do_uuid(ctl_table *table, int write, struct file *filp,
+ void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+ ctl_table fake_table;
+ unsigned char buf[64], tmp_uuid[16], *uuid;
+
+ uuid = table->data;
+ if (!uuid) {
+ uuid = tmp_uuid;
+ uuid[8] = 0;
+ }
+ if (uuid[8] == 0)
+ generate_random_uuid(uuid);
+
+ sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+ "%02x%02x%02x%02x%02x%02x",
+ uuid[0], uuid[1], uuid[2], uuid[3],
+ uuid[4], uuid[5], uuid[6], uuid[7],
+ uuid[8], uuid[9], uuid[10], uuid[11],
+ uuid[12], uuid[13], uuid[14], uuid[15]);
+ fake_table.data = buf;
+ fake_table.maxlen = sizeof(buf);
+
+ return proc_dostring(&fake_table, write, filp, buffer, lenp, ppos);
+}
+
+static int uuid_strategy(ctl_table *table, int __user *name, int nlen,
+ void __user *oldval, size_t __user *oldlenp,
+ void __user *newval, size_t newlen, void **context)
+{
+ unsigned char tmp_uuid[16], *uuid;
+ unsigned int len;
+
+ if (!oldval || !oldlenp)
+ return 1;
+
+ uuid = table->data;
+ if (!uuid) {
+ uuid = tmp_uuid;
+ uuid[8] = 0;
+ }
+ if (uuid[8] == 0)
+ generate_random_uuid(uuid);
+
+ if (get_user(len, oldlenp))
+ return -EFAULT;
+ if (len) {
+ if (len > 16)
+ len = 16;
+ if (copy_to_user(oldval, uuid, len) ||
+ put_user(len, oldlenp))
+ return -EFAULT;
+ }
+ return 1;
+}
+
+static int sysctl_poolsize = INPUT_POOL_WORDS * 32;
+ctl_table random_table[] = {
+ {
+ .ctl_name = RANDOM_POOLSIZE,
+ .procname = "poolsize",
+ .data = &sysctl_poolsize,
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = RANDOM_ENTROPY_COUNT,
+ .procname = "entropy_avail",
+ .maxlen = sizeof(int),
+ .mode = 0444,
+ .proc_handler = &proc_dointvec,
+ .data = &input_pool.entropy_count,
+ },
+ {
+ .ctl_name = RANDOM_READ_THRESH,
+ .procname = "read_wakeup_threshold",
+ .data = &random_read_wakeup_thresh,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &min_read_thresh,
+ .extra2 = &max_read_thresh,
+ },
+ {
+ .ctl_name = RANDOM_WRITE_THRESH,
+ .procname = "write_wakeup_threshold",
+ .data = &random_write_wakeup_thresh,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec_minmax,
+ .strategy = &sysctl_intvec,
+ .extra1 = &min_write_thresh,
+ .extra2 = &max_write_thresh,
+ },
+ {
+ .ctl_name = RANDOM_BOOT_ID,
+ .procname = "boot_id",
+ .data = &sysctl_bootid,
+ .maxlen = 16,
+ .mode = 0444,
+ .proc_handler = &proc_do_uuid,
+ .strategy = &uuid_strategy,
+ },
+ {
+ .ctl_name = RANDOM_UUID,
+ .procname = "uuid",
+ .maxlen = 16,
+ .mode = 0444,
+ .proc_handler = &proc_do_uuid,
+ .strategy = &uuid_strategy,
+ },
+ { .ctl_name = 0 }
+};
+#endif /* CONFIG_SYSCTL */
+
+/********************************************************************
+ *
+ * Random funtions for networking
+ *
+ ********************************************************************/
+
+/*
+ * TCP initial sequence number picking. This uses the random number
+ * generator to pick an initial secret value. This value is hashed
+ * along with the TCP endpoint information to provide a unique
+ * starting point for each pair of TCP endpoints. This defeats
+ * attacks which rely on guessing the initial TCP sequence number.
+ * This algorithm was suggested by Steve Bellovin.
+ *
+ * Using a very strong hash was taking an appreciable amount of the total
+ * TCP connection establishment time, so this is a weaker hash,
+ * compensated for by changing the secret periodically.
+ */
+
+/* F, G and H are basic MD4 functions: selection, majority, parity */
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+
+/*
+ * The generic round function. The application is so specific that
+ * we don't bother protecting all the arguments with parens, as is generally
+ * good macro practice, in favor of extra legibility.
+ * Rotation is separate from addition to prevent recomputation
+ */
+#define ROUND(f, a, b, c, d, x, s) \
+ (a += f(b, c, d) + x, a = (a << s) | (a >> (32 - s)))
+#define K1 0
+#define K2 013240474631UL
+#define K3 015666365641UL
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+
+static __u32 twothirdsMD4Transform (__u32 const buf[4], __u32 const in[12])
+{
+ __u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+ /* Round 1 */
+ ROUND(F, a, b, c, d, in[ 0] + K1, 3);
+ ROUND(F, d, a, b, c, in[ 1] + K1, 7);
+ ROUND(F, c, d, a, b, in[ 2] + K1, 11);
+ ROUND(F, b, c, d, a, in[ 3] + K1, 19);
+ ROUND(F, a, b, c, d, in[ 4] + K1, 3);
+ ROUND(F, d, a, b, c, in[ 5] + K1, 7);
+ ROUND(F, c, d, a, b, in[ 6] + K1, 11);
+ ROUND(F, b, c, d, a, in[ 7] + K1, 19);
+ ROUND(F, a, b, c, d, in[ 8] + K1, 3);
+ ROUND(F, d, a, b, c, in[ 9] + K1, 7);
+ ROUND(F, c, d, a, b, in[10] + K1, 11);
+ ROUND(F, b, c, d, a, in[11] + K1, 19);
+
+ /* Round 2 */
+ ROUND(G, a, b, c, d, in[ 1] + K2, 3);
+ ROUND(G, d, a, b, c, in[ 3] + K2, 5);
+ ROUND(G, c, d, a, b, in[ 5] + K2, 9);
+ ROUND(G, b, c, d, a, in[ 7] + K2, 13);
+ ROUND(G, a, b, c, d, in[ 9] + K2, 3);
+ ROUND(G, d, a, b, c, in[11] + K2, 5);
+ ROUND(G, c, d, a, b, in[ 0] + K2, 9);
+ ROUND(G, b, c, d, a, in[ 2] + K2, 13);
+ ROUND(G, a, b, c, d, in[ 4] + K2, 3);
+ ROUND(G, d, a, b, c, in[ 6] + K2, 5);
+ ROUND(G, c, d, a, b, in[ 8] + K2, 9);
+ ROUND(G, b, c, d, a, in[10] + K2, 13);
+
+ /* Round 3 */
+ ROUND(H, a, b, c, d, in[ 3] + K3, 3);
+ ROUND(H, d, a, b, c, in[ 7] + K3, 9);
+ ROUND(H, c, d, a, b, in[11] + K3, 11);
+ ROUND(H, b, c, d, a, in[ 2] + K3, 15);
+ ROUND(H, a, b, c, d, in[ 6] + K3, 3);
+ ROUND(H, d, a, b, c, in[10] + K3, 9);
+ ROUND(H, c, d, a, b, in[ 1] + K3, 11);
+ ROUND(H, b, c, d, a, in[ 5] + K3, 15);
+ ROUND(H, a, b, c, d, in[ 9] + K3, 3);
+ ROUND(H, d, a, b, c, in[ 0] + K3, 9);
+ ROUND(H, c, d, a, b, in[ 4] + K3, 11);
+ ROUND(H, b, c, d, a, in[ 8] + K3, 15);
+
+ return buf[1] + b; /* "most hashed" word */
+ /* Alternative: return sum of all words? */
+}
+#endif
+
+#undef ROUND
+#undef F
+#undef G
+#undef H
+#undef K1
+#undef K2
+#undef K3
+
+/* This should not be decreased so low that ISNs wrap too fast. */
+#define REKEY_INTERVAL (300 * HZ)
+/*
+ * Bit layout of the tcp sequence numbers (before adding current time):
+ * bit 24-31: increased after every key exchange
+ * bit 0-23: hash(source,dest)
+ *
+ * The implementation is similar to the algorithm described
+ * in the Appendix of RFC 1185, except that
+ * - it uses a 1 MHz clock instead of a 250 kHz clock
+ * - it performs a rekey every 5 minutes, which is equivalent
+ * to a (source,dest) tulple dependent forward jump of the
+ * clock by 0..2^(HASH_BITS+1)
+ *
+ * Thus the average ISN wraparound time is 68 minutes instead of
+ * 4.55 hours.
+ *
+ * SMP cleanup and lock avoidance with poor man's RCU.
+ * Manfred Spraul <manfred@colorfullife.com>
+ *
+ */
+#define COUNT_BITS 8
+#define COUNT_MASK ((1 << COUNT_BITS) - 1)
+#define HASH_BITS 24
+#define HASH_MASK ((1 << HASH_BITS) - 1)
+
+static struct keydata {
+ __u32 count; /* already shifted to the final position */
+ __u32 secret[12];
+} ____cacheline_aligned ip_keydata[2];
+
+static unsigned int ip_cnt;
+
+static void rekey_seq_generator(void *private_);
+
+static DECLARE_WORK(rekey_work, rekey_seq_generator, NULL);
+
+/*
+ * Lock avoidance:
+ * The ISN generation runs lockless - it's just a hash over random data.
+ * State changes happen every 5 minutes when the random key is replaced.
+ * Synchronization is performed by having two copies of the hash function
+ * state and rekey_seq_generator always updates the inactive copy.
+ * The copy is then activated by updating ip_cnt.
+ * The implementation breaks down if someone blocks the thread
+ * that processes SYN requests for more than 5 minutes. Should never
+ * happen, and even if that happens only a not perfectly compliant
+ * ISN is generated, nothing fatal.
+ */
+static void rekey_seq_generator(void *private_)
+{
+ struct keydata *keyptr = &ip_keydata[1 ^ (ip_cnt & 1)];
+
+ get_random_bytes(keyptr->secret, sizeof(keyptr->secret));
+ keyptr->count = (ip_cnt & COUNT_MASK) << HASH_BITS;
+ smp_wmb();
+ ip_cnt++;
+ schedule_delayed_work(&rekey_work, REKEY_INTERVAL);
+}
+
+static inline struct keydata *get_keyptr(void)
+{
+ struct keydata *keyptr = &ip_keydata[ip_cnt & 1];
+
+ smp_rmb();
+
+ return keyptr;
+}
+
+static __init int seqgen_init(void)
+{
+ rekey_seq_generator(NULL);
+ return 0;
+}
+late_initcall(seqgen_init);
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+__u32 secure_tcpv6_sequence_number(__u32 *saddr, __u32 *daddr,
+ __u16 sport, __u16 dport)
+{
+ struct timeval tv;
+ __u32 seq;
+ __u32 hash[12];
+ struct keydata *keyptr = get_keyptr();
+
+ /* The procedure is the same as for IPv4, but addresses are longer.
+ * Thus we must use twothirdsMD4Transform.
+ */
+
+ memcpy(hash, saddr, 16);
+ hash[4]=(sport << 16) + dport;
+ memcpy(&hash[5],keyptr->secret,sizeof(__u32) * 7);
+
+ seq = twothirdsMD4Transform(daddr, hash) & HASH_MASK;
+ seq += keyptr->count;
+
+ do_gettimeofday(&tv);
+ seq += tv.tv_usec + tv.tv_sec * 1000000;
+
+ return seq;
+}
+EXPORT_SYMBOL(secure_tcpv6_sequence_number);
+#endif
+
+/* The code below is shamelessly stolen from secure_tcp_sequence_number().
+ * All blames to Andrey V. Savochkin <saw@msu.ru>.
+ */
+__u32 secure_ip_id(__u32 daddr)
+{
+ struct keydata *keyptr;
+ __u32 hash[4];
+
+ keyptr = get_keyptr();
+
+ /*
+ * Pick a unique starting offset for each IP destination.
+ * The dest ip address is placed in the starting vector,
+ * which is then hashed with random data.
+ */
+ hash[0] = daddr;
+ hash[1] = keyptr->secret[9];
+ hash[2] = keyptr->secret[10];
+ hash[3] = keyptr->secret[11];
+
+ return half_md4_transform(hash, keyptr->secret);
+}
+
+#ifdef CONFIG_INET
+
+__u32 secure_tcp_sequence_number(__u32 saddr, __u32 daddr,
+ __u16 sport, __u16 dport)
+{
+ struct timeval tv;
+ __u32 seq;
+ __u32 hash[4];
+ struct keydata *keyptr = get_keyptr();
+
+ /*
+ * Pick a unique starting offset for each TCP connection endpoints
+ * (saddr, daddr, sport, dport).
+ * Note that the words are placed into the starting vector, which is
+ * then mixed with a partial MD4 over random data.
+ */
+ hash[0]=saddr;
+ hash[1]=daddr;
+ hash[2]=(sport << 16) + dport;
+ hash[3]=keyptr->secret[11];
+
+ seq = half_md4_transform(hash, keyptr->secret) & HASH_MASK;
+ seq += keyptr->count;
+ /*
+ * As close as possible to RFC 793, which
+ * suggests using a 250 kHz clock.
+ * Further reading shows this assumes 2 Mb/s networks.
+ * For 10 Mb/s Ethernet, a 1 MHz clock is appropriate.
+ * That's funny, Linux has one built in! Use it!
+ * (Networks are faster now - should this be increased?)
+ */
+ do_gettimeofday(&tv);
+ seq += tv.tv_usec + tv.tv_sec * 1000000;
+#if 0
+ printk("init_seq(%lx, %lx, %d, %d) = %d\n",
+ saddr, daddr, sport, dport, seq);
+#endif
+ return seq;
+}
+
+EXPORT_SYMBOL(secure_tcp_sequence_number);
+
+
+
+/* Generate secure starting point for ephemeral TCP port search */
+u32 secure_tcp_port_ephemeral(__u32 saddr, __u32 daddr, __u16 dport)
+{
+ struct keydata *keyptr = get_keyptr();
+ u32 hash[4];
+
+ /*
+ * Pick a unique starting offset for each ephemeral port search
+ * (saddr, daddr, dport) and 48bits of random data.
+ */
+ hash[0] = saddr;
+ hash[1] = daddr;
+ hash[2] = dport ^ keyptr->secret[10];
+ hash[3] = keyptr->secret[11];
+
+ return half_md4_transform(hash, keyptr->secret);
+}
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+u32 secure_tcpv6_port_ephemeral(const __u32 *saddr, const __u32 *daddr, __u16 dport)
+{
+ struct keydata *keyptr = get_keyptr();
+ u32 hash[12];
+
+ memcpy(hash, saddr, 16);
+ hash[4] = dport;
+ memcpy(&hash[5],keyptr->secret,sizeof(__u32) * 7);
+
+ return twothirdsMD4Transform(daddr, hash);
+}
+EXPORT_SYMBOL(secure_tcpv6_port_ephemeral);
+#endif
+
+#endif /* CONFIG_INET */
+
+
+/*
+ * Get a random word for internal kernel use only. Similar to urandom but
+ * with the goal of minimal entropy pool depletion. As a result, the random
+ * value is not cryptographically secure but for several uses the cost of
+ * depleting entropy is too high
+ */
+unsigned int get_random_int(void)
+{
+ /*
+ * Use IP's RNG. It suits our purpose perfectly: it re-keys itself
+ * every second, from the entropy pool (and thus creates a limited
+ * drain on it), and uses halfMD4Transform within the second. We
+ * also mix it with jiffies and the PID:
+ */
+ return secure_ip_id(current->pid + jiffies);
+}
+
+/*
+ * randomize_range() returns a start address such that
+ *
+ * [...... <range> .....]
+ * start end
+ *
+ * a <range> with size "len" starting at the return value is inside in the
+ * area defined by [start, end], but is otherwise randomized.
+ */
+unsigned long
+randomize_range(unsigned long start, unsigned long end, unsigned long len)
+{
+ unsigned long range = end - len - start;
+
+ if (end <= start + len)
+ return 0;
+ return PAGE_ALIGN(get_random_int() % range + start);
+}
diff --git a/drivers/char/raw.c b/drivers/char/raw.c
new file mode 100644
index 000000000000..a2e33ec79615
--- /dev/null
+++ b/drivers/char/raw.c
@@ -0,0 +1,342 @@
+/*
+ * linux/drivers/char/raw.c
+ *
+ * Front-end raw character devices. These can be bound to any block
+ * devices to provide genuine Unix raw character device semantics.
+ *
+ * We reserve minor number 0 for a control interface. ioctl()s on this
+ * device are used to bind the other minor numbers to block devices.
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/major.h>
+#include <linux/blkdev.h>
+#include <linux/module.h>
+#include <linux/raw.h>
+#include <linux/capability.h>
+#include <linux/uio.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+
+#include <asm/uaccess.h>
+
+struct raw_device_data {
+ struct block_device *binding;
+ int inuse;
+};
+
+static struct class_simple *raw_class;
+static struct raw_device_data raw_devices[MAX_RAW_MINORS];
+static DECLARE_MUTEX(raw_mutex);
+static struct file_operations raw_ctl_fops; /* forward declaration */
+
+/*
+ * Open/close code for raw IO.
+ *
+ * We just rewrite the i_mapping for the /dev/raw/rawN file descriptor to
+ * point at the blockdev's address_space and set the file handle to use
+ * O_DIRECT.
+ *
+ * Set the device's soft blocksize to the minimum possible. This gives the
+ * finest possible alignment and has no adverse impact on performance.
+ */
+static int raw_open(struct inode *inode, struct file *filp)
+{
+ const int minor = iminor(inode);
+ struct block_device *bdev;
+ int err;
+
+ if (minor == 0) { /* It is the control device */
+ filp->f_op = &raw_ctl_fops;
+ return 0;
+ }
+
+ down(&raw_mutex);
+
+ /*
+ * All we need to do on open is check that the device is bound.
+ */
+ bdev = raw_devices[minor].binding;
+ err = -ENODEV;
+ if (!bdev)
+ goto out;
+ igrab(bdev->bd_inode);
+ err = blkdev_get(bdev, filp->f_mode, 0);
+ if (err)
+ goto out;
+ err = bd_claim(bdev, raw_open);
+ if (err)
+ goto out1;
+ err = set_blocksize(bdev, bdev_hardsect_size(bdev));
+ if (err)
+ goto out2;
+ filp->f_flags |= O_DIRECT;
+ filp->f_mapping = bdev->bd_inode->i_mapping;
+ if (++raw_devices[minor].inuse == 1)
+ filp->f_dentry->d_inode->i_mapping =
+ bdev->bd_inode->i_mapping;
+ filp->private_data = bdev;
+ up(&raw_mutex);
+ return 0;
+
+out2:
+ bd_release(bdev);
+out1:
+ blkdev_put(bdev);
+out:
+ up(&raw_mutex);
+ return err;
+}
+
+/*
+ * When the final fd which refers to this character-special node is closed, we
+ * make its ->mapping point back at its own i_data.
+ */
+static int raw_release(struct inode *inode, struct file *filp)
+{
+ const int minor= iminor(inode);
+ struct block_device *bdev;
+
+ down(&raw_mutex);
+ bdev = raw_devices[minor].binding;
+ if (--raw_devices[minor].inuse == 0) {
+ /* Here inode->i_mapping == bdev->bd_inode->i_mapping */
+ inode->i_mapping = &inode->i_data;
+ inode->i_mapping->backing_dev_info = &default_backing_dev_info;
+ }
+ up(&raw_mutex);
+
+ bd_release(bdev);
+ blkdev_put(bdev);
+ return 0;
+}
+
+/*
+ * Forward ioctls to the underlying block device.
+ */
+static int
+raw_ioctl(struct inode *inode, struct file *filp,
+ unsigned int command, unsigned long arg)
+{
+ struct block_device *bdev = filp->private_data;
+
+ return ioctl_by_bdev(bdev, command, arg);
+}
+
+static void bind_device(struct raw_config_request *rq)
+{
+ class_simple_device_remove(MKDEV(RAW_MAJOR, rq->raw_minor));
+ class_simple_device_add(raw_class, MKDEV(RAW_MAJOR, rq->raw_minor),
+ NULL, "raw%d", rq->raw_minor);
+}
+
+/*
+ * Deal with ioctls against the raw-device control interface, to bind
+ * and unbind other raw devices.
+ */
+static int raw_ctl_ioctl(struct inode *inode, struct file *filp,
+ unsigned int command, unsigned long arg)
+{
+ struct raw_config_request rq;
+ struct raw_device_data *rawdev;
+ int err = 0;
+
+ switch (command) {
+ case RAW_SETBIND:
+ case RAW_GETBIND:
+
+ /* First, find out which raw minor we want */
+
+ if (copy_from_user(&rq, (void __user *) arg, sizeof(rq))) {
+ err = -EFAULT;
+ goto out;
+ }
+
+ if (rq.raw_minor < 0 || rq.raw_minor >= MAX_RAW_MINORS) {
+ err = -EINVAL;
+ goto out;
+ }
+ rawdev = &raw_devices[rq.raw_minor];
+
+ if (command == RAW_SETBIND) {
+ dev_t dev;
+
+ /*
+ * This is like making block devices, so demand the
+ * same capability
+ */
+ if (!capable(CAP_SYS_ADMIN)) {
+ err = -EPERM;
+ goto out;
+ }
+
+ /*
+ * For now, we don't need to check that the underlying
+ * block device is present or not: we can do that when
+ * the raw device is opened. Just check that the
+ * major/minor numbers make sense.
+ */
+
+ dev = MKDEV(rq.block_major, rq.block_minor);
+ if ((rq.block_major == 0 && rq.block_minor != 0) ||
+ MAJOR(dev) != rq.block_major ||
+ MINOR(dev) != rq.block_minor) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ down(&raw_mutex);
+ if (rawdev->inuse) {
+ up(&raw_mutex);
+ err = -EBUSY;
+ goto out;
+ }
+ if (rawdev->binding) {
+ bdput(rawdev->binding);
+ module_put(THIS_MODULE);
+ }
+ if (rq.block_major == 0 && rq.block_minor == 0) {
+ /* unbind */
+ rawdev->binding = NULL;
+ class_simple_device_remove(MKDEV(RAW_MAJOR,
+ rq.raw_minor));
+ } else {
+ rawdev->binding = bdget(dev);
+ if (rawdev->binding == NULL)
+ err = -ENOMEM;
+ else {
+ __module_get(THIS_MODULE);
+ bind_device(&rq);
+ }
+ }
+ up(&raw_mutex);
+ } else {
+ struct block_device *bdev;
+
+ down(&raw_mutex);
+ bdev = rawdev->binding;
+ if (bdev) {
+ rq.block_major = MAJOR(bdev->bd_dev);
+ rq.block_minor = MINOR(bdev->bd_dev);
+ } else {
+ rq.block_major = rq.block_minor = 0;
+ }
+ up(&raw_mutex);
+ if (copy_to_user((void __user *)arg, &rq, sizeof(rq))) {
+ err = -EFAULT;
+ goto out;
+ }
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+out:
+ return err;
+}
+
+static ssize_t raw_file_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct iovec local_iov = {
+ .iov_base = (char __user *)buf,
+ .iov_len = count
+ };
+
+ return generic_file_write_nolock(file, &local_iov, 1, ppos);
+}
+
+static ssize_t raw_file_aio_write(struct kiocb *iocb, const char __user *buf,
+ size_t count, loff_t pos)
+{
+ struct iovec local_iov = {
+ .iov_base = (char __user *)buf,
+ .iov_len = count
+ };
+
+ return generic_file_aio_write_nolock(iocb, &local_iov, 1, &iocb->ki_pos);
+}
+
+
+static struct file_operations raw_fops = {
+ .read = generic_file_read,
+ .aio_read = generic_file_aio_read,
+ .write = raw_file_write,
+ .aio_write = raw_file_aio_write,
+ .open = raw_open,
+ .release= raw_release,
+ .ioctl = raw_ioctl,
+ .readv = generic_file_readv,
+ .writev = generic_file_writev,
+ .owner = THIS_MODULE,
+};
+
+static struct file_operations raw_ctl_fops = {
+ .ioctl = raw_ctl_ioctl,
+ .open = raw_open,
+ .owner = THIS_MODULE,
+};
+
+static struct cdev raw_cdev = {
+ .kobj = {.name = "raw", },
+ .owner = THIS_MODULE,
+};
+
+static int __init raw_init(void)
+{
+ int i;
+ dev_t dev = MKDEV(RAW_MAJOR, 0);
+
+ if (register_chrdev_region(dev, MAX_RAW_MINORS, "raw"))
+ goto error;
+
+ cdev_init(&raw_cdev, &raw_fops);
+ if (cdev_add(&raw_cdev, dev, MAX_RAW_MINORS)) {
+ kobject_put(&raw_cdev.kobj);
+ unregister_chrdev_region(dev, MAX_RAW_MINORS);
+ goto error;
+ }
+
+ raw_class = class_simple_create(THIS_MODULE, "raw");
+ if (IS_ERR(raw_class)) {
+ printk(KERN_ERR "Error creating raw class.\n");
+ cdev_del(&raw_cdev);
+ unregister_chrdev_region(dev, MAX_RAW_MINORS);
+ goto error;
+ }
+ class_simple_device_add(raw_class, MKDEV(RAW_MAJOR, 0), NULL, "rawctl");
+
+ devfs_mk_cdev(MKDEV(RAW_MAJOR, 0),
+ S_IFCHR | S_IRUGO | S_IWUGO,
+ "raw/rawctl");
+ for (i = 1; i < MAX_RAW_MINORS; i++)
+ devfs_mk_cdev(MKDEV(RAW_MAJOR, i),
+ S_IFCHR | S_IRUGO | S_IWUGO,
+ "raw/raw%d", i);
+ return 0;
+
+error:
+ printk(KERN_ERR "error register raw device\n");
+ return 1;
+}
+
+static void __exit raw_exit(void)
+{
+ int i;
+
+ for (i = 1; i < MAX_RAW_MINORS; i++)
+ devfs_remove("raw/raw%d", i);
+ devfs_remove("raw/rawctl");
+ devfs_remove("raw");
+ class_simple_device_remove(MKDEV(RAW_MAJOR, 0));
+ class_simple_destroy(raw_class);
+ cdev_del(&raw_cdev);
+ unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), MAX_RAW_MINORS);
+}
+
+module_init(raw_init);
+module_exit(raw_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/rio/Makefile b/drivers/char/rio/Makefile
new file mode 100644
index 000000000000..bce2bd1204ed
--- /dev/null
+++ b/drivers/char/rio/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the linux rio-subsystem.
+#
+# (C) R.E.Wolff@BitWizard.nl
+#
+# This file is GPL. See other files for the full Blurb. I'm lazy today.
+#
+
+obj-$(CONFIG_RIO) += rio.o
+
+rio-objs := rio_linux.o rioinit.o rioboot.o riocmd.o rioctrl.o riointr.o \
+ rioparam.o riopcicopy.o rioroute.o riotable.o riotty.o
diff --git a/drivers/char/rio/board.h b/drivers/char/rio/board.h
new file mode 100644
index 000000000000..0b397e1c8f1c
--- /dev/null
+++ b/drivers/char/rio/board.h
@@ -0,0 +1,143 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : board.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:07
+** Retrieved : 11/6/98 11:34:20
+**
+** ident @(#)board.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_board_h__
+#define __rio_board_h__
+
+#ifdef SCCS_LABELS
+static char *_board_h_sccs_ = "@(#)board.h 1.2";
+#endif
+
+/*
+** board.h contains the definitions for the *hardware* of the host cards.
+** It describes the memory overlay for the dual port RAM area.
+*/
+
+#define DP_SRAM1_SIZE 0x7C00
+#define DP_SRAM2_SIZE 0x0200
+#define DP_SRAM3_SIZE 0x7000
+#define DP_SCRATCH_SIZE 0x1000
+#define DP_PARMMAP_ADDR 0x01FE /* offset into SRAM2 */
+#define DP_STARTUP_ADDR 0x01F8 /* offset into SRAM2 */
+
+/*
+** The shape of the Host Control area, at offset 0x7C00, Write Only
+*/
+struct s_Ctrl
+{
+ BYTE DpCtl; /* 7C00 */
+ BYTE Dp_Unused2_[127];
+ BYTE DpIntSet; /* 7C80 */
+ BYTE Dp_Unused3_[127];
+ BYTE DpTpuReset; /* 7D00 */
+ BYTE Dp_Unused4_[127];
+ BYTE DpIntReset; /* 7D80 */
+ BYTE Dp_Unused5_[127];
+};
+
+/*
+** The PROM data area on the host (0x7C00), Read Only
+*/
+struct s_Prom
+{
+ WORD DpSlxCode[2];
+ WORD DpRev;
+ WORD Dp_Unused6_;
+ WORD DpUniq[4];
+ WORD DpJahre;
+ WORD DpWoche;
+ WORD DpHwFeature[5];
+ WORD DpOemId;
+ WORD DpSiggy[16];
+};
+
+/*
+** Union of the Ctrl and Prom areas
+*/
+union u_CtrlProm /* This is the control/PROM area (0x7C00) */
+{
+ struct s_Ctrl DpCtrl;
+ struct s_Prom DpProm;
+};
+
+/*
+** The top end of memory!
+*/
+struct s_ParmMapS /* Area containing Parm Map Pointer */
+{
+ BYTE Dp_Unused8_[DP_PARMMAP_ADDR];
+ WORD DpParmMapAd;
+};
+
+struct s_StartUpS
+{
+ BYTE Dp_Unused9_[DP_STARTUP_ADDR];
+ BYTE Dp_LongJump[0x4];
+ BYTE Dp_Unused10_[2];
+ BYTE Dp_ShortJump[0x2];
+};
+
+union u_Sram2ParmMap /* This is the top of memory (0x7E00-0x7FFF) */
+{
+ BYTE DpSramMem[DP_SRAM2_SIZE];
+ struct s_ParmMapS DpParmMapS;
+ struct s_StartUpS DpStartUpS;
+};
+
+/*
+** This is the DP RAM overlay.
+*/
+struct DpRam
+{
+ BYTE DpSram1[DP_SRAM1_SIZE]; /* 0000 - 7BFF */
+ union u_CtrlProm DpCtrlProm; /* 7C00 - 7DFF */
+ union u_Sram2ParmMap DpSram2ParmMap; /* 7E00 - 7FFF */
+ BYTE DpScratch[DP_SCRATCH_SIZE]; /* 8000 - 8FFF */
+ BYTE DpSram3[DP_SRAM3_SIZE]; /* 9000 - FFFF */
+};
+
+#define DpControl DpCtrlProm.DpCtrl.DpCtl
+#define DpSetInt DpCtrlProm.DpCtrl.DpIntSet
+#define DpResetTpu DpCtrlProm.DpCtrl.DpTpuReset
+#define DpResetInt DpCtrlProm.DpCtrl.DpIntReset
+
+#define DpSlx DpCtrlProm.DpProm.DpSlxCode
+#define DpRevision DpCtrlProm.DpProm.DpRev
+#define DpUnique DpCtrlProm.DpProm.DpUniq
+#define DpYear DpCtrlProm.DpProm.DpJahre
+#define DpWeek DpCtrlProm.DpProm.DpWoche
+#define DpSignature DpCtrlProm.DpProm.DpSiggy
+
+#define DpParmMapR DpSram2ParmMap.DpParmMapS.DpParmMapAd
+#define DpSram2 DpSram2ParmMap.DpSramMem
+
+#endif
diff --git a/drivers/char/rio/bootpkt.h b/drivers/char/rio/bootpkt.h
new file mode 100644
index 000000000000..c329aeb7c871
--- /dev/null
+++ b/drivers/char/rio/bootpkt.h
@@ -0,0 +1,62 @@
+
+
+/****************************************************************************
+ ******* *******
+ ******* B O O T P A C K E T H E A D E R F I L E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _pkt_h
+#define _pkt_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_bootpkt_h_sccs = "@(#)bootpkt.h 1.1" ;
+#endif
+#endif
+
+ /*************************************************
+ * Overlayed onto the Data fields of a regular
+ * Packet
+ ************************************************/
+typedef struct BOOT_PKT BOOT_PKT ;
+struct BOOT_PKT {
+ short seq_num ;
+ char data[10] ;
+ } ;
+
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/brates.h b/drivers/char/rio/brates.h
new file mode 100644
index 000000000000..bd4fc84ec6cf
--- /dev/null
+++ b/drivers/char/rio/brates.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+ ******* *******
+ ******* BRATES.H *******
+ ******* *******
+ ****************************************************************************
+
+ Author : Jeremy Rolls
+ Date : 1 Nov 1990
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _brates_h
+#ifndef lint
+/* static char * _brates_h_sccs = "@(#)brates.h 1.4"; */
+#endif
+#define _brates_h 1
+/* List of baud rate defines. Most are borrowed from /usr/include/sys/termio.h
+*/
+#ifndef INKERNEL
+
+#define B0 0x00
+#define B50 0x01
+#define B75 0x02
+#define B110 0x03
+#define B134 0x04
+#define B150 0x05
+#define B200 0x06
+#define B300 0x07
+#define B600 0x08
+#define B1200 0x09
+#define B1800 0x0a
+#define B2400 0x0b
+#define B4800 0x0c
+#define B9600 0x0d
+#define B19200 0x0e
+#define B38400 0x0f
+
+#endif
+
+/*
+** The following baudrates may or may not be defined
+** on various UNIX systems.
+** If they are not then we define them.
+** If they are then we do not define them ;-)
+**
+** This is appalling that we use same definitions as UNIX
+** for our own download code as there is no garuntee that
+** B57600 will be defined as 0x11 by a UNIX system....
+** Arghhhhh!!!!!!!!!!!!!!
+*/
+#if !defined(B56000)
+#define B56000 0x10
+#endif
+
+#if !defined(B57600)
+#define B57600 0x11
+#endif
+
+#if !defined(B64000)
+#define B64000 0x12
+#endif
+
+#if !defined(B115200)
+#define B115200 0x13
+#endif
+
+
+#if !defined(B2000)
+#define B2000 0x14
+#endif
+
+
+#define MAX_RATE B2000
+
+struct baud_rate /* Tag for baud rates */
+{
+ /* short host_rate,*/ /* As passed by the driver */
+ short divisor, /* The divisor */
+ prescaler; /* The pre-scaler */
+};
+
+#endif
diff --git a/drivers/char/rio/chan.h b/drivers/char/rio/chan.h
new file mode 100644
index 000000000000..5b306543328f
--- /dev/null
+++ b/drivers/char/rio/chan.h
@@ -0,0 +1,33 @@
+/*
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+#ifndef _chan_h
+#define _chan_h
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_chan_h_sccs = "@(#)chan.h 1.1" ;
+#endif
+#endif
+
+#define Link0 0
+#define Link1 1
+#define Link2 2
+#define Link3 3
+
+#endif
diff --git a/drivers/char/rio/cirrus.h b/drivers/char/rio/cirrus.h
new file mode 100644
index 000000000000..cf056a990f18
--- /dev/null
+++ b/drivers/char/rio/cirrus.h
@@ -0,0 +1,463 @@
+/****************************************************************************
+ ******* *******
+ ******* CIRRUS.H *******
+ ******* *******
+ ****************************************************************************
+
+ Author : Jeremy Rolls
+ Date : 3 Aug 1990
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _cirrus_h
+#ifndef lint
+/* static char* _cirrus_h_sccs = "@(#)cirrus.h 1.16"; */
+#endif
+#define _cirrus_h 1
+
+#ifdef RTA
+#define TO_UART RX
+#define TO_DRIVER TX
+#endif
+
+#ifdef HOST
+#define TO_UART TX
+#define TO_DRIVER RX
+#endif
+#ifdef RTA
+/* Miscellaneous defines for CIRRUS addresses and related logic for
+ interrupts etc.
+*/
+#define MAP(a) ((short *)(cirrus_base + (a)))
+#define outp(a,b) (*MAP (a) =(b))
+#define inp(a) ((*MAP (a)) & 0xff)
+#define CIRRUS_FIRST (short*)0x7300
+#define CIRRUS_SECOND (short*)0x7200
+#define CIRRUS_THIRD (short*)0x7100
+#define CIRRUS_FOURTH (short*)0x7000
+#define PORTS_ON_CIRRUS 4
+#define CIRRUS_FIFO_SIZE 12
+#define SPACE 0x20
+#define TAB 0x09
+#define LINE_FEED 0x0a
+#define CARRIAGE_RETURN 0x0d
+#define BACKSPACE 0x08
+#define SPACES_IN_TABS 8
+#define SEND_ESCAPE 0x00
+#define START_BREAK 0x81
+#define TIMER_TICK 0x82
+#define STOP_BREAK 0x83
+#define BASE(a) ((a) < 4 ? (short*)CIRRUS_FIRST : ((a) < 8 ? (short *)CIRRUS_SECOND : ((a) < 12 ? (short*)CIRRUS_THIRD : (short *)CIRRUS_FOURTH)))
+#define txack1 ((short *)0x7104)
+#define rxack1 ((short *)0x7102)
+#define mdack1 ((short *)0x7106)
+#define txack2 ((short *)0x7006)
+#define rxack2 ((short *)0x7004)
+#define mdack2 ((short *)0x7100)
+#define int_latch ((short *) 0x7800)
+#define int_status ((short *) 0x7c00)
+#define tx1_pending 0x20
+#define rx1_pending 0x10
+#define md1_pending 0x40
+#define tx2_pending 0x02
+#define rx2_pending 0x01
+#define md2_pending 0x40
+#define module1_bits 0x07
+#define module1_modern 0x08
+#define module2_bits 0x70
+#define module2_modern 0x80
+#define module_blank 0xf
+#define rs232_d25 0x0
+#define rs232_rj45 0x1
+#define rs422_d25 0x3
+#define parallel 0x5
+
+#define CLK0 0x00
+#define CLK1 0x01
+#define CLK2 0x02
+#define CLK3 0x03
+#define CLK4 0x04
+
+#define CIRRUS_REVC 0x42
+#define CIRRUS_REVE 0x44
+
+#define TURNON 1
+#define TURNOFF 0
+
+/* The list of CIRRUS registers.
+ NB. These registers are relative values on 8 bit boundaries whereas
+ on the RTA's the CIRRUS registers are on word boundaries. Use pointer
+ arithmetic (short *) to obtain the real addresses required */
+#define ccr 0x05 /* Channel Command Register */
+#define ier 0x06 /* Interrupt Enable Register */
+#define cor1 0x08 /* Channel Option Register 1 */
+#define cor2 0x09 /* Channel Option Register 2 */
+#define cor3 0x0a /* Channel Option Register 3 */
+#define cor4 0x1e /* Channel Option Register 4 */
+#define cor5 0x1f /* Channel Option Register 5 */
+
+#define ccsr 0x0b /* Channel Control Status Register */
+#define rdcr 0x0e /* Receive Data Count Register */
+#define tdcr 0x12 /* Transmit Data Count Register */
+#define mcor1 0x15 /* Modem Change Option Register 1 */
+#define mcor2 0x16 /* Modem Change Option Regsiter 2 */
+
+#define livr 0x18 /* Local Interrupt Vector Register */
+#define schr1 0x1a /* Special Character Register 1 */
+#define schr2 0x1b /* Special Character Register 2 */
+#define schr3 0x1c /* Special Character Register 3 */
+#define schr4 0x1d /* Special Character Register 4 */
+
+#define rtr 0x20 /* Receive Timer Register */
+#define rtpr 0x21 /* Receive Timeout Period Register */
+#define lnc 0x24 /* Lnext character */
+
+#define rivr 0x43 /* Receive Interrupt Vector Register */
+#define tivr 0x42 /* Transmit Interrupt Vector Register */
+#define mivr 0x41 /* Modem Interrupt Vector Register */
+#define gfrcr 0x40 /* Global Firmware Revision code Reg */
+#define ricr 0x44 /* Receive Interrupting Channel Reg */
+#define ticr 0x45 /* Transmit Interrupting Channel Reg */
+#define micr 0x46 /* Modem Interrupting Channel Register */
+
+#define gcr 0x4b /* Global configuration register*/
+#define misr 0x4c /* Modem interrupt status register */
+
+#define rbusr 0x59
+#define tbusr 0x5a
+#define mbusr 0x5b
+
+#define eoir 0x60 /* End Of Interrupt Register */
+#define rdsr 0x62 /* Receive Data / Status Register */
+#define tdr 0x63 /* Transmit Data Register */
+#define svrr 0x67 /* Service Request Register */
+
+#define car 0x68 /* Channel Access Register */
+#define mir 0x69 /* Modem Interrupt Register */
+#define tir 0x6a /* Transmit Interrupt Register */
+#define rir 0x6b /* Receive Interrupt Register */
+#define msvr1 0x6c /* Modem Signal Value Register 1 */
+#define msvr2 0x6d /* Modem Signal Value Register 2*/
+#define psvr 0x6f /* Printer Signal Value Register*/
+
+#define tbpr 0x72 /* Transmit Baud Rate Period Register */
+#define tcor 0x76 /* Transmit Clock Option Register */
+
+#define rbpr 0x78 /* Receive Baud Rate Period Register */
+#define rber 0x7a /* Receive Baud Rate Extension Register */
+#define rcor 0x7c /* Receive Clock Option Register*/
+#define ppr 0x7e /* Prescalar Period Register */
+
+/* Misc registers used for forcing the 1400 out of its reset woes */
+#define airl 0x6d
+#define airm 0x6e
+#define airh 0x6f
+#define btcr 0x66
+#define mtcr 0x6c
+#define tber 0x74
+
+#endif /* #ifdef RTA */
+
+
+/* Bit fields for particular registers */
+
+/* GCR */
+#define GCR_SERIAL 0x00 /* Configure as serial channel */
+#define GCR_PARALLEL 0x80 /* Configure as parallel channel */
+
+/* RDSR - when status read from FIFO */
+#define RDSR_BREAK 0x08 /* Break received */
+#define RDSR_TIMEOUT 0x80 /* No new data timeout */
+#define RDSR_SC1 0x10 /* Special char 1 (tx XON) matched */
+#define RDSR_SC2 0x20 /* Special char 2 (tx XOFF) matched */
+#define RDSR_SC12_MASK 0x30 /* Mask for special chars 1 and 2 */
+
+/* PPR */
+#define PPR_DEFAULT 0x31 /* Default value - for a 25Mhz clock gives
+ a timeout period of 1ms */
+
+/* LIVR */
+#define LIVR_EXCEPTION 0x07 /* Receive exception interrupt */
+
+/* CCR */
+#define CCR_RESET 0x80 /* Reset channel */
+#define CCR_CHANGE 0x4e /* COR's have changed - NB always change all
+ COR's */
+#define CCR_WFLUSH 0x82 /* Flush transmit FIFO and TSR / THR */
+
+#define CCR_SENDSC1 0x21 /* Send special character one */
+#define CCR_SENDSC2 0x22 /* Send special character two */
+#define CCR_SENDSC3 0x23 /* Send special character three */
+#define CCR_SENDSC4 0x24 /* Send special character four */
+
+#define CCR_TENABLE 0x18 /* Enable transmitter */
+#define CCR_TDISABLE 0x14 /* Disable transmitter */
+#define CCR_RENABLE 0x12 /* Enable receiver */
+#define CCR_RDISABLE 0x11 /* Disable receiver */
+
+#define CCR_READY 0x00 /* CCR is ready for another command */
+
+/* CCSR */
+#define CCSR_TXENABLE 0x08 /* Transmitter enable */
+#define CCSR_RXENABLE 0x80 /* Receiver enable */
+#define CCSR_TXFLOWOFF 0x04 /* Transmit flow off */
+#define CCSR_TXFLOWON 0x02 /* Transmit flow on */
+
+/* SVRR */
+#define SVRR_RECEIVE 0x01 /* Receive interrupt pending */
+#define SVRR_TRANSMIT 0x02 /* Transmit interrupt pending */
+#define SVRR_MODEM 0x04 /* Modem interrupt pending */
+
+/* CAR */
+#define CAR_PORTS 0x03 /* Bit fields for ports */
+
+/* IER */
+#define IER_MODEM 0x80 /* Change in modem status */
+#define IER_RECEIVE 0x10 /* Good data / data exception */
+#define IER_TRANSMITR 0x04 /* Transmit ready (FIFO empty) */
+#define IER_TRANSMITE 0x02 /* Transmit empty */
+#define IER_TIMEOUT 0x01 /* Timeout on no data */
+
+#define IER_DEFAULT 0x94 /* Default values */
+#define IER_PARALLEL 0x84 /* Default for Parallel */
+#define IER_EMPTY 0x92 /* Transmitter empty rather than ready */
+
+/* COR1 - Driver only */
+#define COR1_INPCK 0x10 /* Check parity of received characters */
+
+/* COR1 - driver and RTA */
+#define COR1_ODD 0x80 /* Odd parity */
+#define COR1_EVEN 0x00 /* Even parity */
+#define COR1_NOP 0x00 /* No parity */
+#define COR1_FORCE 0x20 /* Force parity */
+#define COR1_NORMAL 0x40 /* With parity */
+#define COR1_1STOP 0x00 /* 1 stop bit */
+#define COR1_15STOP 0x04 /* 1.5 stop bits */
+#define COR1_2STOP 0x08 /* 2 stop bits */
+#define COR1_5BITS 0x00 /* 5 data bits */
+#define COR1_6BITS 0x01 /* 6 data bits */
+#define COR1_7BITS 0x02 /* 7 data bits */
+#define COR1_8BITS 0x03 /* 8 data bits */
+
+#define COR1_HOST 0xef /* Safe host bits */
+
+/* RTA only */
+#define COR1_CINPCK 0x00 /* Check parity of received characters */
+#define COR1_CNINPCK 0x10 /* Don't check parity */
+
+/* COR2 bits for both RTA and driver use */
+#define COR2_IXANY 0x80 /* IXANY - any character is XON */
+#define COR2_IXON 0x40 /* IXON - enable tx soft flowcontrol */
+#define COR2_RTSFLOW 0x02 /* Enable tx hardware flow control */
+
+/* Additional driver bits */
+#define COR2_HUPCL 0x20 /* Hang up on close */
+#define COR2_CTSFLOW 0x04 /* Enable rx hardware flow control */
+#define COR2_IXOFF 0x01 /* Enable rx software flow control */
+#define COR2_DTRFLOW 0x08 /* Enable tx hardware flow control */
+
+/* RTA use only */
+#define COR2_ETC 0x20 /* Embedded transmit options */
+#define COR2_LOCAL 0x10 /* Local loopback mode */
+#define COR2_REMOTE 0x08 /* Remote loopback mode */
+#define COR2_HOST 0xc2 /* Safe host bits */
+
+/* COR3 - RTA use only */
+#define COR3_SCDRNG 0x80 /* Enable special char detect for range */
+#define COR3_SCD34 0x40 /* Special character detect for SCHR's 3 + 4 */
+#define COR3_FCT 0x20 /* Flow control transparency */
+#define COR3_SCD12 0x10 /* Special character detect for SCHR's 1 + 2 */
+#define COR3_FIFO12 0x0c /* 12 chars for receive FIFO threshold */
+#define COR3_FIFO10 0x0a /* 10 chars for receive FIFO threshold */
+#define COR3_FIFO8 0x08 /* 8 chars for receive FIFO threshold */
+#define COR3_FIFO6 0x06 /* 6 chars for receive FIFO threshold */
+
+#define COR3_THRESHOLD COR3_FIFO8 /* MUST BE LESS THAN MCOR_THRESHOLD */
+
+#define COR3_DEFAULT (COR3_FCT | COR3_THRESHOLD)
+ /* Default bits for COR3 */
+
+/* COR4 driver and RTA use */
+#define COR4_IGNCR 0x80 /* Throw away CR's on input */
+#define COR4_ICRNL 0x40 /* Map CR -> NL on input */
+#define COR4_INLCR 0x20 /* Map NL -> CR on input */
+#define COR4_IGNBRK 0x10 /* Ignore Break */
+#define COR4_NBRKINT 0x08 /* No interrupt on break (-BRKINT) */
+#define COR4_RAISEMOD 0x01 /* Raise modem output lines on non-zero baud */
+
+
+/* COR4 driver only */
+#define COR4_IGNPAR 0x04 /* IGNPAR (ignore characters with errors) */
+#define COR4_PARMRK 0x02 /* PARMRK */
+
+#define COR4_HOST 0xf8 /* Safe host bits */
+
+/* COR4 RTA only */
+#define COR4_CIGNPAR 0x02 /* Thrown away bad characters */
+#define COR4_CPARMRK 0x04 /* PARMRK characters */
+#define COR4_CNPARMRK 0x03 /* Don't PARMRK */
+
+/* COR5 driver and RTA use */
+#define COR5_ISTRIP 0x80 /* Strip input chars to 7 bits */
+#define COR5_LNE 0x40 /* Enable LNEXT processing */
+#define COR5_CMOE 0x20 /* Match good and errored characters */
+#define COR5_ONLCR 0x02 /* NL -> CR NL on output */
+#define COR5_OCRNL 0x01 /* CR -> NL on output */
+
+/*
+** Spare bits - these are not used in the CIRRUS registers, so we use
+** them to set various other features.
+*/
+/*
+** tstop and tbusy indication
+*/
+#define COR5_TSTATE_ON 0x08 /* Turn on monitoring of tbusy and tstop */
+#define COR5_TSTATE_OFF 0x04 /* Turn off monitoring of tbusy and tstop */
+/*
+** TAB3
+*/
+#define COR5_TAB3 0x10 /* TAB3 mode */
+
+#define COR5_HOST 0xc3 /* Safe host bits */
+
+/* CCSR */
+#define CCSR_TXFLOFF 0x04 /* Tx is xoffed */
+
+/* MSVR1 */
+/* NB. DTR / CD swapped from Cirrus spec as the pins are also reversed on the
+ RTA. This is because otherwise DCD would get lost on the 1 parallel / 3
+ serial option.
+*/
+#define MSVR1_CD 0x80 /* CD (DSR on Cirrus) */
+#define MSVR1_RTS 0x40 /* RTS (CTS on Cirrus) */
+#define MSVR1_RI 0x20 /* RI */
+#define MSVR1_DTR 0x10 /* DTR (CD on Cirrus) */
+#define MSVR1_CTS 0x01 /* CTS output pin (RTS on Cirrus) */
+/* Next two used to indicate state of tbusy and tstop to driver */
+#define MSVR1_TSTOP 0x08 /* Set if port flow controlled */
+#define MSVR1_TEMPTY 0x04 /* Set if port tx buffer empty */
+
+#define MSVR1_HOST 0xf3 /* The bits the host wants */
+
+/* MSVR2 */
+#define MSVR2_DSR 0x02 /* DSR output pin (DTR on Cirrus) */
+
+/* MCOR */
+#define MCOR_CD 0x80 /* CD (DSR on Cirrus) */
+#define MCOR_RTS 0x40 /* RTS (CTS on Cirrus) */
+#define MCOR_RI 0x20 /* RI */
+#define MCOR_DTR 0x10 /* DTR (CD on Cirrus) */
+
+#define MCOR_DEFAULT (MCOR_CD | MCOR_RTS | MCOR_RI | MCOR_DTR)
+#define MCOR_FULLMODEM MCOR_DEFAULT
+#define MCOR_RJ45 (MCOR_CD | MCOR_RTS | MCOR_DTR)
+#define MCOR_RESTRICTED (MCOR_CD | MCOR_RTS)
+
+/* More MCOR - H/W Handshake (flowcontrol) stuff */
+#define MCOR_THRESH8 0x08 /* eight characters then we stop */
+#define MCOR_THRESH9 0x09 /* nine characters then we stop */
+#define MCOR_THRESH10 0x0A /* ten characters then we stop */
+#define MCOR_THRESH11 0x0B /* eleven characters then we stop */
+
+#define MCOR_THRESHBITS 0x0F /* mask for ANDing out the above */
+
+#define MCOR_THRESHOLD MCOR_THRESH9 /* MUST BE GREATER THAN COR3_THRESHOLD */
+
+
+/* RTPR */
+#define RTPR_DEFAULT 0x02 /* Default */
+
+
+/* Defines for the subscripts of a CONFIG packet */
+#define CONFIG_COR1 1 /* Option register 1 */
+#define CONFIG_COR2 2 /* Option register 2 */
+#define CONFIG_COR4 3 /* Option register 4 */
+#define CONFIG_COR5 4 /* Option register 5 */
+#define CONFIG_TXXON 5 /* Tx XON character */
+#define CONFIG_TXXOFF 6 /* Tx XOFF character */
+#define CONFIG_RXXON 7 /* Rx XON character */
+#define CONFIG_RXXOFF 8 /* Rx XOFF character */
+#define CONFIG_LNEXT 9 /* LNEXT character */
+#define CONFIG_TXBAUD 10 /* Tx baud rate */
+#define CONFIG_RXBAUD 11 /* Rx baud rate */
+
+/* Port status stuff */
+#define IDLE_CLOSED 0 /* Closed */
+#define IDLE_OPEN 1 /* Idle open */
+#define IDLE_BREAK 2 /* Idle on break */
+
+/* Subscript of MODEM STATUS packet */
+#define MODEM_VALUE 3 /* Current values of handshake pins */
+/* Subscript of SBREAK packet */
+#define BREAK_LENGTH 1 /* Length of a break in slices of 0.01 seconds
+ 0 = stay on break until an EBREAK command
+ is sent */
+
+
+#define PRE_EMPTIVE 0x80 /* Pre-emptive bit in command field */
+
+/* Packet types going from Host to remote - with the exception of OPEN, MOPEN,
+ CONFIG, SBREAK and MEMDUMP the remaining bytes of the data array will not
+ be used
+*/
+#define OPEN 0x00 /* Open a port */
+#define CONFIG 0x01 /* Configure a port */
+#define MOPEN 0x02 /* Modem open (block for DCD) */
+#define CLOSE 0x03 /* Close a port */
+#define WFLUSH (0x04 | PRE_EMPTIVE) /* Write flush */
+#define RFLUSH (0x05 | PRE_EMPTIVE) /* Read flush */
+#define RESUME (0x06 | PRE_EMPTIVE) /* Resume if xoffed */
+#define SBREAK 0x07 /* Start break */
+#define EBREAK 0x08 /* End break */
+#define SUSPEND (0x09 | PRE_EMPTIVE) /* Susp op (behave as tho xoffed) */
+#define FCLOSE (0x0a | PRE_EMPTIVE) /* Force close */
+#define XPRINT 0x0b /* Xprint packet */
+#define MBIS (0x0c | PRE_EMPTIVE) /* Set modem lines */
+#define MBIC (0x0d | PRE_EMPTIVE) /* Clear modem lines */
+#define MSET (0x0e | PRE_EMPTIVE) /* Set modem lines */
+#define PCLOSE 0x0f /* Pseudo close - Leaves rx/tx enabled */
+#define MGET (0x10 | PRE_EMPTIVE) /* Force update of modem status */
+#define MEMDUMP (0x11 | PRE_EMPTIVE) /* Send back mem from addr supplied */
+#define READ_REGISTER (0x12 | PRE_EMPTIVE) /* Read CD1400 register (debug) */
+
+/* "Command" packets going from remote to host COMPLETE and MODEM_STATUS
+ use data[4] / data[3] to indicate current state and modem status respectively
+*/
+
+#define COMPLETE (0x20 | PRE_EMPTIVE)
+ /* Command complete */
+#define BREAK_RECEIVED (0x21 | PRE_EMPTIVE)
+ /* Break received */
+#define MODEM_STATUS (0x22 | PRE_EMPTIVE)
+ /* Change in modem status */
+
+/* "Command" packet that could go either way - handshake wake-up */
+#define HANDSHAKE (0x23 | PRE_EMPTIVE)
+ /* Wake-up to HOST / RTA */
+
+#endif
diff --git a/drivers/char/rio/cmd.h b/drivers/char/rio/cmd.h
new file mode 100644
index 000000000000..c369edaea2b3
--- /dev/null
+++ b/drivers/char/rio/cmd.h
@@ -0,0 +1,84 @@
+
+
+/****************************************************************************
+ ******* *******
+ ******* C O M M A N D P A C K E T H E A D E R S
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef _cmd_h
+#define _cmd_h
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_cmd_h_sccs = "@(#)cmd.h 1.1" ;
+#endif
+#endif
+
+
+#define PRE_EMPTIVE_CMD 0x80
+#define INLINE_CMD ~PRE_EMPTIVE_CMD
+
+#define CMD_IGNORE_PKT ( (ushort) 0)
+#define CMD_STATUS_REQ ( (ushort) 1)
+#define CMD_UNIT_STATUS_REQ ( (ushort) 2) /* Is this needed ??? */
+#define CMD_CONF_PORT ( (ushort) 3)
+#define CMD_CONF_UNIT ( (ushort) 4)
+#define CMD_ROUTE_MAP_REQ ( (ushort) 5)
+#define CMD_FLUSH_TX ( (ushort) 6)
+#define CMD_FLUSH_RX ( (ushort) 7)
+#define CMD_PARTION_PORT ( (ushort) 8)
+#define CMD_RESET_PORT ( (ushort) 0x0a)
+#define CMD_BOOT_UNIT ( (ushort) 0x0b)
+#define CMD_FOUND_UNIT ( (ushort) 0x0c)
+#define CMD_ATTACHED_RTA_2 ( (ushort) 0x0d)
+#define CMD_PROVIDE_BOOT ( (ushort) 0x0e)
+#define CMD_CIRRUS ( (ushort) 0x0f)
+
+#define FORM_STATUS_PKT ( (ushort) 1 )
+#define FORM_POLL_PKT ( (ushort) 2 )
+#define FORM_LINK_STATUS_PKT ( (ushort) 3 )
+
+
+#define CMD_DATA_PORT ( (ushort) 1 )
+#define CMD_DATA ( (ushort) 2 )
+
+#define CMD_TX_PART ( (ushort) 2 )
+#define CMD_RX_PART ( (ushort) 3 )
+#define CMD_RX_LIMIT ( (ushort) 4 )
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/cmdblk.h b/drivers/char/rio/cmdblk.h
new file mode 100644
index 000000000000..2b8efbdbee1c
--- /dev/null
+++ b/drivers/char/rio/cmdblk.h
@@ -0,0 +1,60 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : cmdblk.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:09
+** Retrieved : 11/6/98 11:34:20
+**
+** ident @(#)cmdblk.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_cmdblk_h__
+#define __rio_cmdblk_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_cmdblk_h_sccs_ = "@(#)cmdblk.h 1.2";
+#endif
+#endif
+
+/*
+** the structure of a command block, used to queue commands destined for
+** a rup.
+*/
+
+struct CmdBlk
+{
+ struct CmdBlk *NextP; /* Pointer to next command block */
+ struct PKT Packet; /* A packet, to copy to the rup */
+ /* The func to call to check if OK */
+ int (*PreFuncP)(int, struct CmdBlk *);
+ int PreArg; /* The arg for the func */
+ /* The func to call when completed */
+ int (*PostFuncP)(int, struct CmdBlk *);
+ int PostArg; /* The arg for the func */
+};
+
+#define NUM_RIO_CMD_BLKS (3 * (MAX_RUP * 4 + LINKS_PER_UNIT * 4))
+#endif
diff --git a/drivers/char/rio/cmdpkt.h b/drivers/char/rio/cmdpkt.h
new file mode 100644
index 000000000000..46befd354f20
--- /dev/null
+++ b/drivers/char/rio/cmdpkt.h
@@ -0,0 +1,206 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : cmdpkt.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:09
+** Retrieved : 11/6/98 11:34:20
+**
+** ident @(#)cmdpkt.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifndef __rio_cmdpkt_h__
+#define __rio_cmdpkt_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_cmdpkt_h_sccs_ = "@(#)cmdpkt.h 1.2";
+#endif
+#endif
+
+/*
+** overlays for the data area of a packet. Used in both directions
+** (to build a packet to send, and to interpret a packet that arrives)
+** and is very inconvenient for MIPS, so they appear as two separate
+** structures - those used for modifying/reading packets on the card
+** and those for modifying/reading packets in real memory, which have an _M
+** suffix.
+*/
+
+#define RTA_BOOT_DATA_SIZE (PKT_MAX_DATA_LEN-2)
+
+/*
+** The boot information packet looks like this:
+** This structure overlays a PktCmd->CmdData structure, and so starts
+** at Data[2] in the actual pkt!
+*/
+struct BootSequence
+{
+ WORD NumPackets;
+ WORD LoadBase;
+ WORD CodeSize;
+};
+
+#define BOOT_SEQUENCE_LEN 8
+
+struct SamTop
+{
+ BYTE Unit;
+ BYTE Link;
+};
+
+struct CmdHdr
+{
+ BYTE PcCommand;
+ union
+ {
+ BYTE PcPhbNum;
+ BYTE PcLinkNum;
+ BYTE PcIDNum;
+ } U0;
+};
+
+
+struct PktCmd
+{
+ union
+ {
+ struct
+ {
+ struct CmdHdr CmdHdr;
+ struct BootSequence PcBootSequence;
+ } S1;
+ struct
+ {
+ WORD PcSequence;
+ BYTE PcBootData[RTA_BOOT_DATA_SIZE];
+ } S2;
+ struct
+ {
+ WORD __crud__;
+ BYTE PcUniqNum[4]; /* this is really a uint. */
+ BYTE PcModuleTypes; /* what modules are fitted */
+ } S3;
+ struct
+ {
+ struct CmdHdr CmdHdr;
+ BYTE __undefined__;
+ BYTE PcModemStatus;
+ BYTE PcPortStatus;
+ BYTE PcSubCommand; /* commands like mem or register dump */
+ WORD PcSubAddr; /* Address for command */
+ BYTE PcSubData[64]; /* Date area for command */
+ } S4;
+ struct
+ {
+ struct CmdHdr CmdHdr;
+ BYTE PcCommandText[1];
+ BYTE __crud__[20];
+ BYTE PcIDNum2; /* It had to go somewhere! */
+ } S5;
+ struct
+ {
+ struct CmdHdr CmdHdr;
+ struct SamTop Topology[LINKS_PER_UNIT];
+ } S6;
+ } U1;
+};
+
+struct PktCmd_M
+{
+ union
+ {
+ struct
+ {
+ struct
+ {
+ uchar PcCommand;
+ union
+ {
+ uchar PcPhbNum;
+ uchar PcLinkNum;
+ uchar PcIDNum;
+ } U0;
+ } CmdHdr;
+ struct
+ {
+ ushort NumPackets;
+ ushort LoadBase;
+ ushort CodeSize;
+ } PcBootSequence;
+ } S1;
+ struct
+ {
+ ushort PcSequence;
+ uchar PcBootData[RTA_BOOT_DATA_SIZE];
+ } S2;
+ struct
+ {
+ ushort __crud__;
+ uchar PcUniqNum[4]; /* this is really a uint. */
+ uchar PcModuleTypes; /* what modules are fitted */
+ } S3;
+ struct
+ {
+ ushort __cmd_hdr__;
+ uchar __undefined__;
+ uchar PcModemStatus;
+ uchar PcPortStatus;
+ uchar PcSubCommand;
+ ushort PcSubAddr;
+ uchar PcSubData[64];
+ } S4;
+ struct
+ {
+ ushort __cmd_hdr__;
+ uchar PcCommandText[1];
+ uchar __crud__[20];
+ uchar PcIDNum2; /* Tacked on end */
+ } S5;
+ struct
+ {
+ ushort __cmd_hdr__;
+ struct Top Topology[LINKS_PER_UNIT];
+ } S6;
+ } U1;
+};
+
+#define Command U1.S1.CmdHdr.PcCommand
+#define PhbNum U1.S1.CmdHdr.U0.PcPhbNum
+#define IDNum U1.S1.CmdHdr.U0.PcIDNum
+#define IDNum2 U1.S5.PcIDNum2
+#define LinkNum U1.S1.CmdHdr.U0.PcLinkNum
+#define Sequence U1.S2.PcSequence
+#define BootData U1.S2.PcBootData
+#define BootSequence U1.S1.PcBootSequence
+#define UniqNum U1.S3.PcUniqNum
+#define ModemStatus U1.S4.PcModemStatus
+#define PortStatus U1.S4.PcPortStatus
+#define SubCommand U1.S4.PcSubCommand
+#define SubAddr U1.S4.PcSubAddr
+#define SubData U1.S4.PcSubData
+#define CommandText U1.S5.PcCommandText
+#define RouteTopology U1.S6.Topology
+#define ModuleTypes U1.S3.PcModuleTypes
+
+#endif
diff --git a/drivers/char/rio/control.h b/drivers/char/rio/control.h
new file mode 100644
index 000000000000..1712f6261dd1
--- /dev/null
+++ b/drivers/char/rio/control.h
@@ -0,0 +1,62 @@
+
+
+/****************************************************************************
+ ******* *******
+ ******* C O N T R O L P A C K E T H E A D E R S
+ ******* *******
+ ****************************************************************************
+
+ Author : Jon Brawn
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef _control_h
+#define _control_h
+
+#ifndef lint
+/* static char *_rio_control_h_sccs = "@(#)control.h 1.4"; */
+#endif
+
+#define CONTROL '^'
+#define IFOAD ( CONTROL + 1 )
+#define IDENTIFY ( CONTROL + 2 )
+#define ZOMBIE ( CONTROL + 3 )
+#define UFOAD ( CONTROL + 4 )
+#define IWAIT ( CONTROL + 5 )
+
+#define IFOAD_MAGIC 0xF0AD /* of course */
+#define ZOMBIE_MAGIC (~0xDEAD) /* not dead -> zombie */
+#define UFOAD_MAGIC 0xD1E /* kill-your-neighbour */
+#define IWAIT_MAGIC 0xB1DE /* Bide your time */
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/daemon.h b/drivers/char/rio/daemon.h
new file mode 100644
index 000000000000..62dba0e68b3e
--- /dev/null
+++ b/drivers/char/rio/daemon.h
@@ -0,0 +1,334 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : daemon.h
+** SID : 1.3
+** Last Modified : 11/6/98 11:34:09
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)daemon.h 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_daemon_h__
+#define __rio_daemon_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_daemon_h_sccs_ = "@(#)daemon.h 1.3";
+#endif
+#endif
+
+
+/*
+** structures used on /dev/rio
+*/
+
+struct Error
+{
+ uint Error;
+ uint Entry;
+ uint Other;
+};
+
+struct DownLoad
+{
+ char *DataP;
+ uint Count;
+ uint ProductCode;
+};
+
+/*
+** A few constants....
+*/
+#ifndef MAX_VERSION_LEN
+#define MAX_VERSION_LEN 256
+#endif
+
+#ifndef MAX_XP_CTRL_LEN
+#define MAX_XP_CTRL_LEN 16 /* ALSO IN PORT.H */
+#endif
+
+struct PortSetup
+{
+ uint From; /* Set/Clear XP & IXANY Control from this port.... */
+ uint To; /* .... to this port */
+ uint XpCps; /* at this speed */
+ char XpOn[MAX_XP_CTRL_LEN]; /* this is the start string */
+ char XpOff[MAX_XP_CTRL_LEN]; /* this is the stop string */
+ uchar IxAny; /* enable/disable IXANY */
+ uchar IxOn; /* enable/disable IXON */
+ uchar Lock; /* lock port params */
+ uchar Store; /* store params across closes */
+ uchar Drain; /* close only when drained */
+};
+
+struct LpbReq
+{
+ uint Host;
+ uint Link;
+ struct LPB *LpbP;
+};
+
+struct RupReq
+{
+ uint HostNum;
+ uint RupNum;
+ struct RUP *RupP;
+};
+
+struct PortReq
+{
+ uint SysPort;
+ struct Port *PortP;
+};
+
+struct StreamInfo
+{
+ uint SysPort;
+#if 0
+ queue_t RQueue;
+ queue_t WQueue;
+#else
+ int RQueue;
+ int WQueue;
+#endif
+};
+
+struct HostReq
+{
+ uint HostNum;
+ struct Host *HostP;
+};
+
+struct HostDpRam
+{
+ uint HostNum;
+ struct DpRam *DpRamP;
+};
+
+struct DebugCtrl
+{
+ uint SysPort;
+ uint Debug;
+ uint Wait;
+};
+
+struct MapInfo
+{
+ uint FirstPort; /* 8 ports, starting from this (tty) number */
+ uint RtaUnique; /* reside on this RTA (unique number) */
+};
+
+struct MapIn
+{
+ uint NumEntries; /* How many port sets are we mapping? */
+ struct MapInfo *MapInfoP; /* Pointer to (user space) info */
+};
+
+struct SendPack
+{
+ unsigned int PortNum;
+ unsigned char Len;
+ unsigned char Data[PKT_MAX_DATA_LEN];
+};
+
+struct SpecialRupCmd
+{
+ struct PKT Packet;
+ unsigned short Host;
+ unsigned short RupNum;
+};
+
+struct IdentifyRta
+{
+ ulong RtaUnique;
+ uchar ID;
+};
+
+struct KillNeighbour
+{
+ ulong UniqueNum;
+ uchar Link;
+};
+
+struct rioVersion {
+ char version[MAX_VERSION_LEN];
+ char relid[MAX_VERSION_LEN];
+ int buildLevel;
+ char buildDate[MAX_VERSION_LEN];
+};
+
+
+/*
+** RIOC commands are for the daemon type operations
+**
+** 09.12.1998 ARG - ESIL 0776 part fix
+** Definition for 'RIOC' also appears in rioioctl.h, so we'd better do a
+** #ifndef here first.
+** rioioctl.h also now has #define 'RIO_QUICK_CHECK' as this ioctl is now
+** allowed to be used by customers.
+*/
+#ifndef RIOC
+#define RIOC ('R'<<8)|('i'<<16)|('o'<<24)
+#endif
+
+/*
+** Boot stuff
+*/
+#define RIO_GET_TABLE (RIOC | 100)
+#define RIO_PUT_TABLE (RIOC | 101)
+#define RIO_ASSIGN_RTA (RIOC | 102)
+#define RIO_DELETE_RTA (RIOC | 103)
+#define RIO_HOST_FOAD (RIOC | 104)
+#define RIO_QUICK_CHECK (RIOC | 105)
+#define RIO_SIGNALS_ON (RIOC | 106)
+#define RIO_SIGNALS_OFF (RIOC | 107)
+#define RIO_CHANGE_NAME (RIOC | 108)
+#define RIO_DOWNLOAD (RIOC | 109)
+#define RIO_GET_LOG (RIOC | 110)
+#define RIO_SETUP_PORTS (RIOC | 111)
+#define RIO_ALL_MODEM (RIOC | 112)
+
+/*
+** card state, debug stuff
+*/
+#define RIO_NUM_HOSTS (RIOC | 120)
+#define RIO_HOST_LPB (RIOC | 121)
+#define RIO_HOST_RUP (RIOC | 122)
+#define RIO_HOST_PORT (RIOC | 123)
+#define RIO_PARMS (RIOC | 124)
+#define RIO_HOST_REQ (RIOC | 125)
+#define RIO_READ_CONFIG (RIOC | 126)
+#define RIO_SET_CONFIG (RIOC | 127)
+#define RIO_VERSID (RIOC | 128)
+#define RIO_FLAGS (RIOC | 129)
+#define RIO_SETDEBUG (RIOC | 130)
+#define RIO_GETDEBUG (RIOC | 131)
+#define RIO_READ_LEVELS (RIOC | 132)
+#define RIO_SET_FAST_BUS (RIOC | 133)
+#define RIO_SET_SLOW_BUS (RIOC | 134)
+#define RIO_SET_BYTE_MODE (RIOC | 135)
+#define RIO_SET_WORD_MODE (RIOC | 136)
+#define RIO_STREAM_INFO (RIOC | 137)
+#define RIO_START_POLLER (RIOC | 138)
+#define RIO_STOP_POLLER (RIOC | 139)
+#define RIO_LAST_ERROR (RIOC | 140)
+#define RIO_TICK (RIOC | 141)
+#define RIO_TOCK (RIOC | 241) /* I did this on purpose, you know. */
+#define RIO_SEND_PACKET (RIOC | 142)
+#define RIO_SET_BUSY (RIOC | 143)
+#define SPECIAL_RUP_CMD (RIOC | 144)
+#define RIO_FOAD_RTA (RIOC | 145)
+#define RIO_ZOMBIE_RTA (RIOC | 146)
+#define RIO_IDENTIFY_RTA (RIOC | 147)
+#define RIO_KILL_NEIGHBOUR (RIOC | 148)
+#define RIO_DEBUG_MEM (RIOC | 149)
+/*
+** 150 - 167 used..... See below
+*/
+#define RIO_GET_PORT_SETUP (RIOC | 168)
+#define RIO_RESUME (RIOC | 169)
+#define RIO_MESG (RIOC | 170)
+#define RIO_NO_MESG (RIOC | 171)
+#define RIO_WHAT_MESG (RIOC | 172)
+#define RIO_HOST_DPRAM (RIOC | 173)
+#define RIO_MAP_B50_TO_50 (RIOC | 174)
+#define RIO_MAP_B50_TO_57600 (RIOC | 175)
+#define RIO_MAP_B110_TO_110 (RIOC | 176)
+#define RIO_MAP_B110_TO_115200 (RIOC | 177)
+#define RIO_GET_PORT_PARAMS (RIOC | 178)
+#define RIO_SET_PORT_PARAMS (RIOC | 179)
+#define RIO_GET_PORT_TTY (RIOC | 180)
+#define RIO_SET_PORT_TTY (RIOC | 181)
+#define RIO_SYSLOG_ONLY (RIOC | 182)
+#define RIO_SYSLOG_CONS (RIOC | 183)
+#define RIO_CONS_ONLY (RIOC | 184)
+#define RIO_BLOCK_OPENS (RIOC | 185)
+
+/*
+** 02.03.1999 ARG - ESIL 0820 fix :
+** RIOBootMode is no longer use by the driver, so these ioctls
+** are now obsolete :
+**
+#define RIO_GET_BOOT_MODE (RIOC | 186)
+#define RIO_SET_BOOT_MODE (RIOC | 187)
+**
+*/
+
+#define RIO_MEM_DUMP (RIOC | 189)
+#define RIO_READ_REGISTER (RIOC | 190)
+#define RIO_GET_MODTYPE (RIOC | 191)
+#define RIO_SET_TIMER (RIOC | 192)
+#define RIO_READ_CHECK (RIOC | 196)
+#define RIO_WAITING_FOR_RESTART (RIOC | 197)
+#define RIO_BIND_RTA (RIOC | 198)
+#define RIO_GET_BINDINGS (RIOC | 199)
+#define RIO_PUT_BINDINGS (RIOC | 200)
+
+#define RIO_MAKE_DEV (RIOC | 201)
+#define RIO_MINOR (RIOC | 202)
+
+#define RIO_IDENTIFY_DRIVER (RIOC | 203)
+#define RIO_DISPLAY_HOST_CFG (RIOC | 204)
+
+
+/*
+** MAKE_DEV / MINOR stuff
+*/
+#define RIO_DEV_DIRECT 0x0000
+#define RIO_DEV_MODEM 0x0200
+#define RIO_DEV_XPRINT 0x0400
+#define RIO_DEV_MASK 0x0600
+
+/*
+** port management, xprint stuff
+*/
+#define rIOCN(N) (RIOC|(N))
+#define rIOCR(N,T) (RIOC|(N))
+#define rIOCW(N,T) (RIOC|(N))
+
+#define RIO_GET_XP_ON rIOCR(150,char[16]) /* start xprint string */
+#define RIO_SET_XP_ON rIOCW(151,char[16])
+#define RIO_GET_XP_OFF rIOCR(152,char[16]) /* finish xprint string */
+#define RIO_SET_XP_OFF rIOCW(153,char[16])
+#define RIO_GET_XP_CPS rIOCR(154,int) /* xprint CPS */
+#define RIO_SET_XP_CPS rIOCW(155,int)
+#define RIO_GET_IXANY rIOCR(156,int) /* ixany allowed? */
+#define RIO_SET_IXANY rIOCW(157,int)
+#define RIO_SET_IXANY_ON rIOCN(158) /* allow ixany */
+#define RIO_SET_IXANY_OFF rIOCN(159) /* disallow ixany */
+#define RIO_GET_MODEM rIOCR(160,int) /* port is modem/direct line? */
+#define RIO_SET_MODEM rIOCW(161,int)
+#define RIO_SET_MODEM_ON rIOCN(162) /* port is a modem */
+#define RIO_SET_MODEM_OFF rIOCN(163) /* port is direct */
+#define RIO_GET_IXON rIOCR(164,int) /* ixon allowed? */
+#define RIO_SET_IXON rIOCW(165,int)
+#define RIO_SET_IXON_ON rIOCN(166) /* allow ixon */
+#define RIO_SET_IXON_OFF rIOCN(167) /* disallow ixon */
+
+#define RIO_GET_SIVIEW ((('s')<<8) | 106) /* backwards compatible with SI */
+
+#define RIO_IOCTL_UNKNOWN -2
+
+#endif
diff --git a/drivers/char/rio/data.h b/drivers/char/rio/data.h
new file mode 100644
index 000000000000..dabc2d1fa40f
--- /dev/null
+++ b/drivers/char/rio/data.h
@@ -0,0 +1,40 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : data.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:09
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)data.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_datadex__
+#define __rio_datadex__
+
+#ifndef lint
+static char *_data_h_sccs_ = "@(#)data.h 1.2";
+#endif
+
+#endif
diff --git a/drivers/char/rio/debug.h b/drivers/char/rio/debug.h
new file mode 100644
index 000000000000..b6e0d0935552
--- /dev/null
+++ b/drivers/char/rio/debug.h
@@ -0,0 +1,39 @@
+/*
+** File: debug.h
+**
+** Author: David Dix
+**
+** Created: 12th March 1993
+**
+** Last modified: 93/04/27
+**
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _debug_h_
+#define _debug_h_
+
+
+#if defined(DCIRRUS)
+#define DBPACKET(pkt, opt, str, chn) debug_packet((pkt), (opt), (str), (chn))
+#else
+#define DBPACKET(pkt, opt, str, c)
+#endif /* DCIRRUS */
+
+
+#endif /* _debug_h_ */
diff --git a/drivers/char/rio/defaults.h b/drivers/char/rio/defaults.h
new file mode 100644
index 000000000000..2e7309e27622
--- /dev/null
+++ b/drivers/char/rio/defaults.h
@@ -0,0 +1,59 @@
+
+/****************************************************************************
+ ******* *******
+ ******* D E F A U L T S
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_defaults_h_sccs = "@(#)defaults.h 1.1" ;
+#endif
+#endif
+
+
+#define MILLISECOND (int) (1000/64) /* 15.625 low ticks */
+#define SECOND (int) 15625 /* Low priority ticks */
+
+#ifdef RTA
+#define RX_LIMIT (ushort) 3
+#endif
+#ifdef HOST
+#define RX_LIMIT (ushort) 1
+#endif
+
+#define LINK_TIMEOUT (int) (POLL_PERIOD / 2)
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/eisa.h b/drivers/char/rio/eisa.h
new file mode 100644
index 000000000000..59371b0528b0
--- /dev/null
+++ b/drivers/char/rio/eisa.h
@@ -0,0 +1,104 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : eisa.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:10
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)eisa.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_eisa_h__
+#define __rio_eisa_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_eisa_h_sccs_ = "@(#)eisa.h 1.2";
+#endif
+#endif
+
+/*
+** things to do with the EISA bus
+*/
+
+#define RIO_EISA_STRING_ADDRESS 0xfffd9 /* where EISA is stored */
+
+#define RIO_MAX_EISA_SLOTS 16 /* how many EISA slots? */
+
+#define RIO_EISA_IDENT 0x984D /* Specialix */
+#define RIO_EISA_PRODUCT_CODE 0x14 /* Code 14 */
+#define RIO_EISA_ENABLE_BIT 0x01 /* To enable card */
+
+#define EISA_MEMORY_BASE_LO 0xC00 /* A16-A23 */
+#define EISA_MEMORY_BASE_HI 0xC01 /* A24-A31 */
+#define EISA_INTERRUPT_VEC 0xC02 /* see below */
+#define EISA_CONTROL_PORT 0xC02 /* see below */
+#define EISA_INTERRUPT_RESET 0xC03 /* read to clear IRQ */
+
+#define EISA_PRODUCT_IDENT_LO 0xC80 /* where RIO_EISA_IDENT is */
+#define EISA_PRODUCT_IDENT_HI 0xC81
+#define EISA_PRODUCT_NUMBER 0xC82 /* where PROD_CODE is */
+#define EISA_REVISION_NUMBER 0xC83 /* revision (1dp) */
+#define EISA_ENABLE 0xC84 /* set LSB to enable card */
+#define EISA_UNIQUE_NUM_0 0xC88 /* vomit */
+#define EISA_UNIQUE_NUM_1 0xC8A
+#define EISA_UNIQUE_NUM_2 0xC90 /* bit strangely arranged */
+#define EISA_UNIQUE_NUM_3 0xC92
+#define EISA_MANUF_YEAR 0xC98 /* when */
+#define EISA_MANUF_WEEK 0xC9A /* more when */
+
+#define EISA_TP_BOOT_FROM_RAM 0x01
+#define EISA_TP_BOOT_FROM_LINK 0x00
+#define EISA_TP_FAST_LINKS 0x02
+#define EISA_TP_SLOW_LINKS 0x00
+#define EISA_TP_BUS_ENABLE 0x04
+#define EISA_TP_BUS_DISABLE 0x00
+#define EISA_TP_RUN 0x08
+#define EISA_TP_RESET 0x00
+#define EISA_POLLED 0x00
+#define EISA_IRQ_3 0x30
+#define EISA_IRQ_4 0x40
+#define EISA_IRQ_5 0x50
+#define EISA_IRQ_6 0x60
+#define EISA_IRQ_7 0x70
+#define EISA_IRQ_9 0x90
+#define EISA_IRQ_10 0xA0
+#define EISA_IRQ_11 0xB0
+#define EISA_IRQ_12 0xC0
+#define EISA_IRQ_14 0xE0
+#define EISA_IRQ_15 0xF0
+
+#define EISA_INTERRUPT_MASK 0xF0
+#define EISA_CONTROL_MASK 0x0F
+
+#define RIO_EISA_DEFAULT_MODE EISA_TP_SLOW_LINKS
+
+#define RIOEisaToIvec(X) (uchar )((uchar)((X) & EISA_INTERRUPT_MASK)>>4)
+
+#define INBZ(z,x) inb(((z)<<12) | (x))
+#define OUTBZ(z,x,y) outb((((z)<<12) | (x)), y)
+
+#endif /* __rio_eisa_h__ */
diff --git a/drivers/char/rio/enable.h b/drivers/char/rio/enable.h
new file mode 100644
index 000000000000..8e9a419e15b0
--- /dev/null
+++ b/drivers/char/rio/enable.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ ******* *******
+ ******* E N A B L E H E A D E R S
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_enable_h_sccs = "@(#)enable.h 1.1" ;
+#endif
+#endif
+
+
+#define ENABLE_LTT TRUE
+#define ENABLE_LRT TRUE
+
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/error.h b/drivers/char/rio/error.h
new file mode 100644
index 000000000000..229438e355f2
--- /dev/null
+++ b/drivers/char/rio/error.h
@@ -0,0 +1,85 @@
+
+/****************************************************************************
+ ******* *******
+ ******* E R R O R H E A D E R F I L E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+/* static char *_rio_error_h_sccs = "@(#)error.h 1.3"; */
+#endif
+
+#define E_NO_ERROR ((ushort) 0)
+#define E_PROCESS_NOT_INIT ((ushort) 1)
+#define E_LINK_TIMEOUT ((ushort) 2)
+#define E_NO_ROUTE ((ushort) 3)
+#define E_CONFUSED ((ushort) 4)
+#define E_HOME ((ushort) 5)
+#define E_CSUM_FAIL ((ushort) 6)
+#define E_DISCONNECTED ((ushort) 7)
+#define E_BAD_RUP ((ushort) 8)
+#define E_NO_VIRGIN ((ushort) 9)
+#define E_BOOT_RUP_BUSY ((ushort) 10)
+
+
+
+ /*************************************************
+ * Parsed to mem_halt()
+ ************************************************/
+#define E_CHANALLOC ((ushort) 0x80)
+#define E_POLL_ALLOC ((ushort) 0x81)
+#define E_LTTWAKE ((ushort) 0x82)
+#define E_LTT_ALLOC ((ushort) 0x83)
+#define E_LRT_ALLOC ((ushort) 0x84)
+#define E_CIRRUS ((ushort) 0x85)
+#define E_MONITOR ((ushort) 0x86)
+#define E_PHB_ALLOC ((ushort) 0x87)
+#define E_ARRAY_ALLOC ((ushort) 0x88)
+#define E_QBUF_ALLOC ((ushort) 0x89)
+#define E_PKT_ALLOC ((ushort) 0x8a)
+#define E_GET_TX_Q_BUF ((ushort) 0x8b)
+#define E_GET_RX_Q_BUF ((ushort) 0x8c)
+#define E_MEM_OUT ((ushort) 0x8d)
+#define E_MMU_INIT ((ushort) 0x8e)
+#define E_LTT_INIT ((ushort) 0x8f)
+#define E_LRT_INIT ((ushort) 0x90)
+#define E_LINK_RUN ((ushort) 0x91)
+#define E_MONITOR_ALLOC ((ushort) 0x92)
+#define E_MONITOR_INIT ((ushort) 0x93)
+#define E_POLL_INIT ((ushort) 0x94)
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/errors.h b/drivers/char/rio/errors.h
new file mode 100644
index 000000000000..f920b9f3e2bd
--- /dev/null
+++ b/drivers/char/rio/errors.h
@@ -0,0 +1,104 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : errors.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:10
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)errors.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_errors_h__
+#define __rio_errors_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_errors_h_sccs_ = "@(#)errors.h 1.2";
+#endif
+#endif
+
+/*
+** error codes
+*/
+
+#define NOTHING_WRONG_AT_ALL 0
+#define BAD_CHARACTER_IN_NAME 1
+#define TABLE_ENTRY_ISNT_PROPERLY_NULL 2
+#define UNKNOWN_HOST_NUMBER 3
+#define ZERO_RTA_ID 4
+#define BAD_RTA_ID 5
+#define DUPLICATED_RTA_ID 6
+#define DUPLICATE_UNIQUE_NUMBER 7
+#define BAD_TTY_NUMBER 8
+#define TTY_NUMBER_IN_USE 9
+#define NAME_USED_TWICE 10
+#define HOST_ID_NOT_ZERO 11
+#define BOOT_IN_PROGRESS 12
+#define COPYIN_FAILED 13
+#define HOST_FILE_TOO_LARGE 14
+#define COPYOUT_FAILED 15
+#define NOT_SUPER_USER 16
+#define RIO_ALREADY_POLLING 17
+
+#define ID_NUMBER_OUT_OF_RANGE 18
+#define PORT_NUMBER_OUT_OF_RANGE 19
+#define HOST_NUMBER_OUT_OF_RANGE 20
+#define RUP_NUMBER_OUT_OF_RANGE 21
+#define TTY_NUMBER_OUT_OF_RANGE 22
+#define LINK_NUMBER_OUT_OF_RANGE 23
+
+#define HOST_NOT_RUNNING 24
+#define IOCTL_COMMAND_UNKNOWN 25
+#define RIO_SYSTEM_HALTED 26
+#define WAIT_FOR_DRAIN_BROKEN 27
+#define PORT_NOT_MAPPED_INTO_SYSTEM 28
+#define EXCLUSIVE_USE_SET 29
+#define WAIT_FOR_NOT_CLOSING_BROKEN 30
+#define WAIT_FOR_PORT_TO_OPEN_BROKEN 31
+#define WAIT_FOR_CARRIER_BROKEN 32
+#define WAIT_FOR_NOT_IN_USE_BROKEN 33
+#define WAIT_FOR_CAN_ADD_COMMAND_BROKEN 34
+#define WAIT_FOR_ADD_COMMAND_BROKEN 35
+#define WAIT_FOR_NOT_PARAM_BROKEN 36
+#define WAIT_FOR_RETRY_BROKEN 37
+#define HOST_HAS_ALREADY_BEEN_BOOTED 38
+#define UNIT_IS_IN_USE 39
+#define COULDNT_FIND_ENTRY 40
+#define RTA_UNIQUE_NUMBER_ZERO 41
+#define CLOSE_COMMAND_FAILED 42
+#define WAIT_FOR_CLOSE_BROKEN 43
+#define CPS_VALUE_OUT_OF_RANGE 44
+#define ID_ALREADY_IN_USE 45
+#define SIGNALS_ALREADY_SET 46
+#define NOT_RECEIVING_PROCESS 47
+#define RTA_NUMBER_WRONG 48
+#define NO_SUCH_PRODUCT 49
+#define HOST_SYSPORT_BAD 50
+#define ID_NOT_TENTATIVE 51
+#define XPRINT_CPS_OUT_OF_RANGE 52
+#define NOT_ENOUGH_CORE_FOR_PCI_COPY 53
+
+
+#endif /* __rio_errors_h__ */
diff --git a/drivers/char/rio/formpkt.h b/drivers/char/rio/formpkt.h
new file mode 100644
index 000000000000..a8b65ae0de90
--- /dev/null
+++ b/drivers/char/rio/formpkt.h
@@ -0,0 +1,154 @@
+
+
+/****************************************************************************
+ ******* *******
+ ******* F O R M P A C K E T H E A D E R F I L E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _formpkt_h
+#define _formpkt_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_formpkt_h_sccs = "@(#)formpkt.h 1.1" ;
+#endif
+#endif
+
+typedef struct FORM_BOOT_PKT_1 FORM_BOOT_PKT_1 ;
+struct FORM_BOOT_PKT_1 {
+ ushort pkt_number ;
+ ushort pkt_total ;
+ ushort boot_top ;
+ } ;
+
+typedef struct FORM_BOOT_PKT_2 FORM_BOOT_PKT_2 ;
+struct FORM_BOOT_PKT_2 {
+ ushort pkt_number ;
+ char boot_data[10] ;
+ } ;
+
+
+typedef struct FORM_ATTACH_RTA FORM_ATTACH_RTA ;
+struct FORM_ATTACH_RTA {
+ char cmd_code ;
+ char booter_serial[4] ;
+ char booter_link ;
+ char bootee_serial[4] ;
+ char bootee_link ;
+ } ;
+
+
+typedef struct FORM_BOOT_ID FORM_BOOT_ID ;
+struct FORM_BOOT_ID {
+ char cmd_code ;
+ char bootee_serial[4] ;
+ char bootee_prod_id ;
+ char bootee_link ;
+ } ;
+
+
+
+typedef struct FORM_ROUTE_1 FORM_ROUTE_1 ;
+struct FORM_ROUTE_1 {
+ char cmd_code ;
+ char pkt_number ;
+ char total_in_sequence ;
+ char unit_id ;
+ char host_unit_id ;
+ } ;
+
+typedef struct FORM_ROUTE_2 FORM_ROUTE_2 ;
+struct FORM_ROUTE_2 {
+ char cmd_code ;
+ char pkt_number ;
+ char total_in_sequence ;
+ char route_data[9] ;
+ } ;
+
+typedef struct FORM_ROUTE_REQ FORM_ROUTE_REQ ;
+struct FORM_ROUTE_REQ {
+ char cmd_code ;
+ char pkt_number ;
+ char total_in_sequence ;
+ char route_data[10] ;
+ } ;
+
+
+typedef struct FORM_ERROR FORM_ERROR ;
+struct FORM_ERROR {
+ char cmd_code ;
+ char error_code ;
+
+ } ;
+
+typedef struct FORM_STATUS FORM_STATUS ;
+struct FORM_STATUS {
+ char cmd_code ;
+ char status_code ;
+ char last_packet_valid ;
+ char tx_buffer ;
+ char rx_buffer ;
+ char port_status ;
+ char phb_status ;
+ } ;
+
+
+typedef struct FORM_LINK_STATUS FORM_LINK_STATUS ;
+struct FORM_LINK_STATUS {
+ char cmd_code ;
+ char status_code ;
+ char link_number ;
+ ushort rx_errors ;
+ ushort tx_errors ;
+ ushort csum_errors ;
+ ushort disconnects ;
+ } ;
+
+
+
+typedef struct FORM_PARTITION FORM_PARTITION ;
+struct FORM_PARTITION {
+ char cmd_code ;
+ char status_code ;
+ char port_number ;
+ char tx_max ;
+ char rx_max ;
+ char rx_limit ;
+ } ;
+
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/func.h b/drivers/char/rio/func.h
new file mode 100644
index 000000000000..e8f3860f4726
--- /dev/null
+++ b/drivers/char/rio/func.h
@@ -0,0 +1,154 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : func.h
+** SID : 1.3
+** Last Modified : 11/6/98 11:34:10
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)func.h 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __func_h_def
+#define __func_h_def
+
+#include <linux/kdev_t.h>
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_func_h_sccs_ = "@(#)func.h 1.3";
+#endif
+#endif
+
+/* rioboot.c */
+int RIOBootCodeRTA(struct rio_info *, struct DownLoad *);
+int RIOBootCodeHOST(struct rio_info *, register struct DownLoad *);
+int RIOBootCodeUNKNOWN(struct rio_info *, struct DownLoad *);
+void msec_timeout(struct Host *);
+int RIOBootRup(struct rio_info *, uint, struct Host *, struct PKT *);
+int RIOBootOk(struct rio_info *,struct Host *, ulong);
+int RIORtaBound(struct rio_info *, uint);
+void FillSlot(int, int, uint, struct Host *);
+
+/* riocmd.c */
+int RIOFoadRta(struct Host *, struct Map *);
+int RIOZombieRta(struct Host *, struct Map *);
+int RIOCommandRta(struct rio_info *, uint, int (* func)( struct Host *,
+ struct Map *));
+int RIOIdentifyRta(struct rio_info *, caddr_t);
+int RIOKillNeighbour(struct rio_info *, caddr_t);
+int RIOSuspendBootRta(struct Host *, int, int);
+int RIOFoadWakeup(struct rio_info *);
+struct CmdBlk * RIOGetCmdBlk(void);
+void RIOFreeCmdBlk(struct CmdBlk *);
+int RIOQueueCmdBlk(struct Host *, uint, struct CmdBlk *);
+void RIOPollHostCommands(struct rio_info *, struct Host *);
+int RIOWFlushMark(int, struct CmdBlk *);
+int RIORFlushEnable(int, struct CmdBlk *);
+int RIOUnUse(int, struct CmdBlk *);
+void ShowPacket(uint, struct PKT *);
+
+/* rioctrl.c */
+int copyin(int, caddr_t, int);
+int riocontrol(struct rio_info *, dev_t,int,caddr_t,int);
+int RIOPreemptiveCmd(struct rio_info *,struct Port *,uchar);
+
+/* rioinit.c */
+void rioinit(struct rio_info *, struct RioHostInfo *);
+void RIOInitHosts(struct rio_info *, struct RioHostInfo *);
+void RIOISAinit(struct rio_info *,int);
+int RIODoAT(struct rio_info *, int, int);
+caddr_t RIOCheckForATCard(int);
+int RIOAssignAT(struct rio_info *, int, caddr_t, int);
+int RIOBoardTest(paddr_t, caddr_t, uchar, int);
+void RIOAllocDataStructs(struct rio_info *);
+void RIOSetupDataStructs(struct rio_info *);
+int RIODefaultName(struct rio_info *, struct Host *, uint);
+struct rioVersion * RIOVersid(void);
+int RIOMapin(paddr_t, int, caddr_t *);
+void RIOMapout(paddr_t, long, caddr_t);
+void RIOHostReset(uint, volatile struct DpRam *, uint);
+
+/* riointr.c */
+void RIOTxEnable(char *);
+void RIOServiceHost(struct rio_info *, struct Host *, int);
+int riotproc(struct rio_info *, register struct ttystatics *, int, int);
+
+/* rioparam.c */
+int RIOParam(struct Port *, int, int, int);
+int RIODelay(struct Port *PortP, int);
+int RIODelay_ni(struct Port *PortP, int);
+void ms_timeout(struct Port *);
+int can_add_transmit(struct PKT **, struct Port *);
+void add_transmit(struct Port *);
+void put_free_end(struct Host *, struct PKT *);
+int can_remove_receive(struct PKT **, struct Port *);
+void remove_receive(struct Port *);
+
+/* rioroute.c */
+int RIORouteRup(struct rio_info *, uint, struct Host *, struct PKT *);
+void RIOFixPhbs(struct rio_info *, struct Host *, uint);
+uint GetUnitType(uint);
+int RIOSetChange(struct rio_info *);
+int RIOFindFreeID(struct rio_info *, struct Host *, uint *, uint *);
+
+
+/* riotty.c */
+
+int riotopen(struct tty_struct * tty, struct file * filp);
+int riotclose(void *ptr);
+int riotioctl(struct rio_info *, struct tty_struct *, register int, register caddr_t);
+void ttyseth(struct Port *, struct ttystatics *, struct old_sgttyb *sg);
+
+/* riotable.c */
+int RIONewTable(struct rio_info *);
+int RIOApel(struct rio_info *);
+int RIODeleteRta(struct rio_info *, struct Map *);
+int RIOAssignRta(struct rio_info *, struct Map *);
+int RIOReMapPorts(struct rio_info *, struct Host *, struct Map *);
+int RIOChangeName(struct rio_info *, struct Map*);
+
+#if 0
+/* riodrvr.c */
+struct rio_info * rio_install(struct RioHostInfo *);
+int rio_uninstall(register struct rio_info *);
+int rio_open(struct rio_info *, int, struct file *);
+int rio_close(struct rio_info *, struct file *);
+int rio_read(struct rio_info *, struct file *, char *, int);
+int rio_write(struct rio_info *, struct file * f, char *, int);
+int rio_ioctl(struct rio_info *, struct file *, int, char *);
+int rio_select(struct rio_info *, struct file * f, int, struct sel *);
+int rio_intr(char *);
+int rio_isr_thread(char *);
+struct rio_info * rio_info_store( int cmd, struct rio_info * p);
+#endif
+
+extern int rio_pcicopy(char *src, char *dst, int n);
+extern int rio_minor (struct tty_struct *tty);
+extern int rio_ismodem (struct tty_struct *tty);
+extern void rio_udelay (int usecs);
+
+extern void rio_start_card_running (struct Host * HostP);
+
+#endif /* __func_h_def */
diff --git a/drivers/char/rio/host.h b/drivers/char/rio/host.h
new file mode 100644
index 000000000000..4c65963870a4
--- /dev/null
+++ b/drivers/char/rio/host.h
@@ -0,0 +1,134 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : host.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:10
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)host.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_host_h__
+#define __rio_host_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_host_h_sccs_ = "@(#)host.h 1.2";
+#endif
+#endif
+
+/*
+** the host structure - one per host card in the system.
+*/
+
+#define MAX_EXTRA_UNITS 64
+
+/*
+** Host data structure. This is used for the software equiv. of
+** the host.
+*/
+struct Host
+{
+ uchar Type; /* RIO_EISA, RIO_MCA, ... */
+ uchar Ivec; /* POLLED or ivec number */
+ uchar Mode; /* Control stuff */
+ uchar Slot; /* Slot */
+ volatile caddr_t Caddr; /* KV address of DPRAM */
+ volatile struct DpRam *CardP; /* KV address of DPRAM, with overlay */
+ paddr_t PaddrP; /* Phys. address of DPRAM */
+ char Name[MAX_NAME_LEN]; /* The name of the host */
+ uint UniqueNum; /* host unique number */
+ spinlock_t HostLock; /* Lock structure for MPX */
+ /*struct pci_devinfo PciDevInfo; *//* PCI Bus/Device/Function stuff */
+ /*struct lockb HostLock; *//* Lock structure for MPX */
+ uint WorkToBeDone; /* set to true each interrupt */
+ uint InIntr; /* Being serviced? */
+ uint IntSrvDone;/* host's interrupt has been serviced */
+ int (*Copy)( caddr_t, caddr_t, int ); /* copy func */
+ struct timer_list timer;
+ /*
+ ** I M P O R T A N T !
+ **
+ ** The rest of this data structure is cleared to zero after
+ ** a RIO_HOST_FOAD command.
+ */
+
+ ulong Flags; /* Whats going down */
+#define RC_WAITING 0
+#define RC_STARTUP 1
+#define RC_RUNNING 2
+#define RC_STUFFED 3
+#define RC_SOMETHING 4
+#define RC_SOMETHING_NEW 5
+#define RC_SOMETHING_ELSE 6
+#define RC_READY 7
+#define RUN_STATE 7
+/*
+** Boot mode applies to the way in which hosts in this system will
+** boot RTAs
+*/
+#define RC_BOOT_ALL 0x8 /* Boot all RTAs attached */
+#define RC_BOOT_OWN 0x10 /* Only boot RTAs bound to this system */
+#define RC_BOOT_NONE 0x20 /* Don't boot any RTAs (slave mode) */
+
+ struct Top Topology[LINKS_PER_UNIT]; /* one per link */
+ struct Map Mapping[MAX_RUP]; /* Mappings for host */
+ struct PHB *PhbP; /* Pointer to the PHB array */
+ ushort *PhbNumP; /* Ptr to Number of PHB's */
+ struct LPB *LinkStrP ; /* Link Structure Array */
+ struct RUP *RupP; /* Sixteen real rups here */
+ struct PARM_MAP *ParmMapP; /* points to the parmmap */
+ uint ExtraUnits[MAX_EXTRA_UNITS]; /* unknown things */
+ uint NumExtraBooted; /* how many of the above */
+ /*
+ ** Twenty logical rups.
+ ** The first sixteen are the real Rup entries (above), the last four
+ ** are the link RUPs.
+ */
+ struct UnixRup UnixRups[MAX_RUP+LINKS_PER_UNIT];
+ int timeout_id; /* For calling 100 ms delays */
+ int timeout_sem;/* For calling 100 ms delays */
+ long locks; /* long req'd for set_bit --RR */
+ char ____end_marker____;
+};
+#define Control CardP->DpControl
+#define SetInt CardP->DpSetInt
+#define ResetTpu CardP->DpResetTpu
+#define ResetInt CardP->DpResetInt
+#define Signature CardP->DpSignature
+#define Sram1 CardP->DpSram1
+#define Sram2 CardP->DpSram2
+#define Sram3 CardP->DpSram3
+#define Scratch CardP->DpScratch
+#define __ParmMapR CardP->DpParmMapR
+#define SLX CardP->DpSlx
+#define Revision CardP->DpRevision
+#define Unique CardP->DpUnique
+#define Year CardP->DpYear
+#define Week CardP->DpWeek
+
+#define RIO_DUMBPARM 0x0860 /* what not to expect */
+
+#endif
diff --git a/drivers/char/rio/hosthw.h b/drivers/char/rio/hosthw.h
new file mode 100644
index 000000000000..f6f31ece6e32
--- /dev/null
+++ b/drivers/char/rio/hosthw.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+ ******* *******
+ ******* H O S T H A R D W A R E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_hosthw_h_sccs = "@(#)hosthw.h 1.2" ;
+#endif
+#endif
+
+#define SET_OTHER_INTERRUPT ( (volatile u_short *) 0x7c80 )
+#define SET_EISA_INTERRUPT ( (volatile u_short *) 0x7ef0 )
+
+#define EISA_HOST 0x30
+#define AT_HOST 0xa0
+#define MCA_HOST 0xb0
+#define PCI_HOST 0xd0
+
+#define PRODUCT_MASK 0xf0
+
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/link.h b/drivers/char/rio/link.h
new file mode 100644
index 000000000000..972250348f4a
--- /dev/null
+++ b/drivers/char/rio/link.h
@@ -0,0 +1,188 @@
+/****************************************************************************
+ ******* *******
+ ******* L I N K
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _link_h
+#define _link_h 1
+
+#ifndef lint
+#ifdef SCCS_LABELS
+/* static char *_rio_link_h_sccs = "@(#)link.h 1.15"; */
+#endif
+#endif
+
+
+
+/*************************************************
+ * Define the Link Status stuff
+ ************************************************/
+#define LRT_ACTIVE ((ushort) 0x01)
+#define LRT_SPARE1 ((ushort) 0x02)
+#define INTRO_RCVD ((ushort) 0x04)
+#define FORCED_DISCONNECT ((ushort) 0x08)
+#define LRT_SPARE2 ((ushort) 0x80)
+
+#define TOP_OF_RTA_RAM ((ushort) 0x7000)
+#define HOST_SERIAL_POINTER (unsigned char **) (TOP_OF_RTA_RAM - 2 * sizeof (ushort))
+
+/* Flags for ltt_status */
+#define WAITING_ACK (ushort) 0x0001
+#define DATA_SENT (ushort) 0x0002
+#define WAITING_RUP (ushort) 0x0004
+#define WAITING_RETRY (ushort) 0x0008
+#define WAITING_TOPOLOGY (ushort) 0x0010
+#define SEND_SYNC (ushort) 0x0020
+#define FOAD_THIS_LINK (ushort) 0x0040
+#define REQUEST_SYNC (ushort) 0x0080
+#define REMOTE_DYING (ushort) 0x0100
+#define DIE_NOW (ushort) 0x0200
+
+/* Boot request stuff */
+#define BOOT_REQUEST ((ushort) 0) /* Request for a boot */
+#define BOOT_ABORT ((ushort) 1) /* Abort a boot */
+#define BOOT_SEQUENCE ((ushort) 2) /* Packet with the number of packets
+ and load address */
+#define BOOT_COMPLETED ((ushort) 3) /* Boot completed */
+
+/* States that a link can be in */
+#define LINK_DISCONNECTED ((ushort) 0) /* Disconnected */
+#define LINK_BOOT1 ((ushort) 1) /* Trying to send 1st stage boot */
+#define LINK_BOOT2 ((ushort) 2) /* Trying to send 2nd stage boot */
+#define LINK_BOOT2WAIT ((ushort) 3) /* Waiting for selftest results */
+#define LINK_BOOT3 ((ushort) 4) /* Trying to send 3rd stage boots */
+#define LINK_SYNC ((ushort) 5) /* Syncing */
+
+#define LINK_INTRO ((ushort) 10) /* Introductory packet */
+#define LINK_SUPPLYID ((ushort) 11) /* Trying to supply an ID */
+#define LINK_TOPOLOGY ((ushort) 12) /* Send a topology update */
+#define LINK_REQUESTID ((ushort) 13) /* Waiting for an ID */
+#define LINK_CONNECTED ((ushort) 14) /* Connected */
+
+#define LINK_INTERCONNECT ((ushort) 20) /* Subnets interconnected */
+
+#define LINK_SPARE ((ushort) 40)
+
+/*
+** Set the default timeout for link communications.
+*/
+#define LINKTIMEOUT (400 * MILLISECOND)
+
+/*
+** LED stuff
+*/
+#if defined(RTA)
+#define LED_OFF ((ushort) 0) /* LED off */
+#define LED_RED ((ushort) 1) /* LED Red */
+#define LED_GREEN ((ushort) 2) /* LED Green */
+#define LED_ORANGE ((ushort) 4) /* LED Orange */
+#define LED_1TO8_OPEN ((ushort) 1) /* Port 1->8 LED on */
+#define LED_9TO16_OPEN ((ushort) 2) /* Port 9->16 LED on */
+#define LED_SET_COLOUR(colour) (link->led = (colour))
+#define LED_OR_COLOUR(colour) (link->led |= (colour))
+#define LED_TIMEOUT(time) (link->led_timeout = RioTimePlus(RioTime(),(time)))
+#else
+#define LED_SET_COLOUR(colour)
+#define LED_OR_COLOUR(colour)
+#define LED_TIMEOUT(time)
+#endif /* RTA */
+
+struct LPB {
+ WORD link_number ; /* Link Number */
+ Channel_ptr in_ch ; /* Link In Channel */
+ Channel_ptr out_ch ; /* Link Out Channel */
+#ifdef RTA
+ uchar stat_led ; /* Port open leds */
+ uchar led ; /* True, light led! */
+#endif
+ BYTE attached_serial[4]; /* Attached serial number */
+ BYTE attached_host_serial[4];
+ /* Serial number of Host who
+ booted the other end */
+ WORD descheduled ; /* Currently Descheduled */
+ WORD state; /* Current state */
+ WORD send_poll ; /* Send a Poll Packet */
+ Process_ptr ltt_p ; /* Process Descriptor */
+ Process_ptr lrt_p ; /* Process Descriptor */
+ WORD lrt_status ; /* Current lrt status */
+ WORD ltt_status ; /* Current ltt status */
+ WORD timeout ; /* Timeout value */
+ WORD topology; /* Topology bits */
+ WORD mon_ltt ;
+ WORD mon_lrt ;
+ WORD WaitNoBoot ; /* Secs to hold off booting */
+ PKT_ptr add_packet_list; /* Add packets to here */
+ PKT_ptr remove_packet_list; /* Send packets from here */
+#ifdef RTA
+#ifdef DCIRRUS
+#define QBUFS_PER_REDIRECT (4 / PKTS_PER_BUFFER + 1)
+#else
+#define QBUFS_PER_REDIRECT (8 / PKTS_PER_BUFFER + 1)
+#endif
+ PKT_ptr_ptr rd_add ; /* Add a new Packet here */
+ Q_BUF_ptr rd_add_qb; /* Pointer to the add Q buf */
+ PKT_ptr_ptr rd_add_st_qbb ; /* Pointer to start of the Q's buf */
+ PKT_ptr_ptr rd_add_end_qbb ; /* Pointer to the end of the Q's buf */
+ PKT_ptr_ptr rd_remove ; /* Remove a Packet here */
+ Q_BUF_ptr rd_remove_qb ; /* Pointer to the remove Q buf */
+ PKT_ptr_ptr rd_remove_st_qbb ; /* Pointer to the start of the Q buf */
+ PKT_ptr_ptr rd_remove_end_qbb ; /* Pointer to the end of the Q buf */
+ ushort pkts_in_q ; /* Packets in queue */
+#endif
+
+ Channel_ptr lrt_fail_chan ; /* Lrt's failure channel */
+ Channel_ptr ltt_fail_chan ; /* Ltt's failure channel */
+
+#if defined (HOST) || defined (INKERNEL)
+ /* RUP structure for HOST to driver communications */
+ struct RUP rup ;
+#endif
+ struct RUP link_rup; /* RUP for the link (POLL,
+ topology etc.) */
+ WORD attached_link ; /* Number of attached link */
+ WORD csum_errors ; /* csum errors */
+ WORD num_disconnects ; /* number of disconnects */
+ WORD num_sync_rcvd ; /* # sync's received */
+ WORD num_sync_rqst ; /* # sync requests */
+ WORD num_tx ; /* Num pkts sent */
+ WORD num_rx ; /* Num pkts received */
+ WORD module_attached; /* Module tpyes of attached */
+ WORD led_timeout; /* LED timeout */
+ WORD first_port; /* First port to service */
+ WORD last_port; /* Last port to service */
+ } ;
+
+#endif
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/linux_compat.h b/drivers/char/rio/linux_compat.h
new file mode 100644
index 000000000000..d53843abe02d
--- /dev/null
+++ b/drivers/char/rio/linux_compat.h
@@ -0,0 +1,122 @@
+/*
+ * (C) 2000 R.E.Wolff@BitWizard.nl
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/interrupt.h>
+
+
+#define disable(oldspl) save_flags (oldspl)
+#define restore(oldspl) restore_flags (oldspl)
+
+#define sysbrk(x) kmalloc ((x),in_interrupt()? GFP_ATOMIC : GFP_KERNEL)
+#define sysfree(p,size) kfree ((p))
+
+#define WBYTE(p,v) writeb(v, &p)
+#define RBYTE(p) readb (&p)
+#define WWORD(p,v) writew(v, &p)
+#define RWORD(p) readw(&p)
+#define WINDW(p,v) writew(v, p)
+#define RINDW(p) readw(p)
+
+#define DEBUG_ALL
+
+#define cprintf printk
+
+#ifdef __KERNEL__
+#define INKERNEL
+#endif
+
+struct ttystatics {
+ struct termios tm;
+};
+
+#define bzero(d, n) memset((d), 0, (n))
+#define bcopy(src, dest, n) memcpy ((dest), (src), (n))
+
+#define SEM_SIGIGNORE 0x1234
+
+#ifdef DEBUG_SEM
+#define swait(a,b) printk ("waiting: " __FILE__ " line %d\n", __LINE__)
+#define ssignal(sem) printk ("signalling: " __FILE__ " line %d\n", __LINE__)
+
+#define sreset(sem) printk ("sreset: " __FILE__ "\n")
+#define sem_init(sem,v) printk ("sreset: " __FILE__ "\n")
+#endif
+
+
+#define getpid() (current->pid)
+
+#define QSIZE SERIAL_XMIT_SIZE
+
+#define pseterr(errno) return (- errno)
+
+#define V_CBAUD CBAUD
+
+/* For one reason or another rioboot.c uses delay instead of RIODelay. */
+#define delay(x,y) RIODelay(NULL, y)
+
+extern int rio_debug;
+
+#define RIO_DEBUG_INIT 0x000001
+#define RIO_DEBUG_BOOT 0x000002
+#define RIO_DEBUG_CMD 0x000004
+#define RIO_DEBUG_CTRL 0x000008
+#define RIO_DEBUG_INTR 0x000010
+#define RIO_DEBUG_PARAM 0x000020
+#define RIO_DEBUG_ROUTE 0x000040
+#define RIO_DEBUG_TABLE 0x000080
+#define RIO_DEBUG_TTY 0x000100
+#define RIO_DEBUG_FLOW 0x000200
+#define RIO_DEBUG_MODEMSIGNALS 0x000400
+#define RIO_DEBUG_PROBE 0x000800
+#define RIO_DEBUG_CLEANUP 0x001000
+#define RIO_DEBUG_IFLOW 0x002000
+#define RIO_DEBUG_PFE 0x004000
+#define RIO_DEBUG_REC 0x008000
+#define RIO_DEBUG_SPINLOCK 0x010000
+#define RIO_DEBUG_DELAY 0x020000
+#define RIO_DEBUG_MOD_COUNT 0x040000
+
+/* Copied over from riowinif.h . This is ugly. The winif file declares
+also much other stuff which is incompatible with the headers from
+the older driver. The older driver includes "brates.h" which shadows
+the definitions from Linux, and is incompatible... */
+
+/* RxBaud and TxBaud definitions... */
+#define RIO_B0 0x00 /* RTS / DTR signals dropped */
+#define RIO_B50 0x01 /* 50 baud */
+#define RIO_B75 0x02 /* 75 baud */
+#define RIO_B110 0x03 /* 110 baud */
+#define RIO_B134 0x04 /* 134.5 baud */
+#define RIO_B150 0x05 /* 150 baud */
+#define RIO_B200 0x06 /* 200 baud */
+#define RIO_B300 0x07 /* 300 baud */
+#define RIO_B600 0x08 /* 600 baud */
+#define RIO_B1200 0x09 /* 1200 baud */
+#define RIO_B1800 0x0A /* 1800 baud */
+#define RIO_B2400 0x0B /* 2400 baud */
+#define RIO_B4800 0x0C /* 4800 baud */
+#define RIO_B9600 0x0D /* 9600 baud */
+#define RIO_B19200 0x0E /* 19200 baud */
+#define RIO_B38400 0x0F /* 38400 baud */
+#define RIO_B56000 0x10 /* 56000 baud */
+#define RIO_B57600 0x11 /* 57600 baud */
+#define RIO_B64000 0x12 /* 64000 baud */
+#define RIO_B115200 0x13 /* 115200 baud */
+#define RIO_B2000 0x14 /* 2000 baud */
+
+
diff --git a/drivers/char/rio/list.h b/drivers/char/rio/list.h
new file mode 100644
index 000000000000..a4f7f1f56255
--- /dev/null
+++ b/drivers/char/rio/list.h
@@ -0,0 +1,196 @@
+/****************************************************************************
+ ******* *******
+ ******* L I S T *******
+ ******* *******
+ ****************************************************************************
+
+ Author : Jeremy Rolls.
+ Date : 04-Nov-1990
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+ ***************************************************************************/
+
+#ifndef _list_h
+#define _list_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_rio_list_h_sccs = "@(#)list.h 1.9" ;
+#endif
+#endif
+
+#define PKT_IN_USE 0x1
+
+#ifdef INKERNEL
+
+#define ZERO_PTR (ushort) 0x8000
+#define CaD PortP->Caddr
+
+/*
+** We can add another packet to a transmit queue if the packet pointer pointed
+** to by the TxAdd pointer has PKT_IN_USE clear in its address.
+*/
+
+#ifndef linux
+#if defined( MIPS ) && !defined( MIPSEISA )
+/* May the shoes of the Devil dance on your grave for creating this */
+#define can_add_transmit(PacketP,PortP) \
+ (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,RINDW(PortP->TxAdd))) \
+ & (PKT_IN_USE<<2)))
+
+#elif defined(MIPSEISA) || defined(nx6000) || \
+ defined(drs6000) || defined(UWsparc)
+
+#define can_add_transmit(PacketP,PortP) \
+ (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,RINDW(PortP->TxAdd))) \
+ & PKT_IN_USE))
+
+#else
+#define can_add_transmit(PacketP,PortP) \
+ (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,*PortP->TxAdd)) \
+ & PKT_IN_USE))
+#endif
+
+/*
+** To add a packet to the queue, you set the PKT_IN_USE bit in the address,
+** and then move the TxAdd pointer along one position to point to the next
+** packet pointer. You must wrap the pointer from the end back to the start.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+# define add_transmit(PortP) \
+ WINDW(PortP->TxAdd,RINDW(PortP->TxAdd) | PKT_IN_USE);\
+ if (PortP->TxAdd == PortP->TxEnd)\
+ PortP->TxAdd = PortP->TxStart;\
+ else\
+ PortP->TxAdd++;\
+ WWORD(PortP->PhbP->tx_add , RIO_OFF(CaD,PortP->TxAdd));
+#elif defined(AIX)
+# define add_transmit(PortP) \
+ {\
+ register ushort *TxAddP = (ushort *)RIO_PTR(Cad,PortP->TxAddO);\
+ WINDW( TxAddP, RINDW( TxAddP ) | PKT_IN_USE );\
+ if (PortP->TxAddO == PortP->TxEndO )\
+ PortP->TxAddO = PortP->TxStartO;\
+ else\
+ PortP->TxAddO += sizeof(ushort);\
+ WWORD(((PHB *)RIO_PTR(Cad,PortP->PhbO))->tx_add , PortP->TxAddO );\
+ }
+#else
+# define add_transmit(PortP) \
+ *PortP->TxAdd |= PKT_IN_USE;\
+ if (PortP->TxAdd == PortP->TxEnd)\
+ PortP->TxAdd = PortP->TxStart;\
+ else\
+ PortP->TxAdd++;\
+ PortP->PhbP->tx_add = RIO_OFF(CaD,PortP->TxAdd);
+#endif
+
+/*
+** can_remove_receive( PacketP, PortP ) returns non-zero if PKT_IN_USE is set
+** for the next packet on the queue. It will also set PacketP to point to the
+** relevant packet, [having cleared the PKT_IN_USE bit]. If PKT_IN_USE is clear,
+** then can_remove_receive() returns 0.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+# define can_remove_receive(PacketP,PortP) \
+ ((RINDW(PortP->RxRemove) & PKT_IN_USE) ? \
+ (PacketP=(struct PKT *)RIO_PTR(CaD,(RINDW(PortP->RxRemove) & ~PKT_IN_USE))):0)
+#elif defined(AIX)
+# define can_remove_receive(PacketP,PortP) \
+ ((RINDW((ushort *)RIO_PTR(Cad,PortP->RxRemoveO)) & PKT_IN_USE) ? \
+ (PacketP=(struct PKT *)RIO_PTR(Cad,RINDW((ushort *)RIO_PTR(Cad,PortP->RxRemoveO)) & ~PKT_IN_USE)):0)
+#else
+# define can_remove_receive(PacketP,PortP) \
+ ((*PortP->RxRemove & PKT_IN_USE) ? \
+ (PacketP=(struct PKT *)RIO_PTR(CaD,(*PortP->RxRemove & ~PKT_IN_USE))):0)
+#endif
+
+
+/*
+** Will God see it within his heart to forgive us for this thing that
+** we have created? To remove a packet from the receive queue you clear
+** its PKT_IN_USE bit, and then bump the pointers. Once the pointers
+** get to the end, they must be wrapped back to the start.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+# define remove_receive(PortP) \
+ WINDW(PortP->RxRemove, (RINDW(PortP->RxRemove) & ~PKT_IN_USE));\
+ if (PortP->RxRemove == PortP->RxEnd)\
+ PortP->RxRemove = PortP->RxStart;\
+ else\
+ PortP->RxRemove++;\
+ WWORD(PortP->PhbP->rx_remove , RIO_OFF(CaD,PortP->RxRemove));
+#elif defined(AIX)
+# define remove_receive(PortP) \
+ {\
+ register ushort *RxRemoveP = (ushort *)RIO_PTR(Cad,PortP->RxRemoveO);\
+ WINDW( RxRemoveP, RINDW( RxRemoveP ) & ~PKT_IN_USE );\
+ if (PortP->RxRemoveO == PortP->RxEndO)\
+ PortP->RxRemoveO = PortP->RxStartO;\
+ else\
+ PortP->RxRemoveO += sizeof(ushort);\
+ WWORD(((PHB *)RIO_PTR(Cad,PortP->PhbO))->rx_remove, PortP->RxRemoveO );\
+ }
+#else
+# define remove_receive(PortP) \
+ *PortP->RxRemove &= ~PKT_IN_USE;\
+ if (PortP->RxRemove == PortP->RxEnd)\
+ PortP->RxRemove = PortP->RxStart;\
+ else\
+ PortP->RxRemove++;\
+ PortP->PhbP->rx_remove = RIO_OFF(CaD,PortP->RxRemove);
+#endif
+#endif
+
+
+#else /* !IN_KERNEL */
+
+#define ZERO_PTR NULL
+
+
+#ifdef HOST
+/* #define can_remove_transmit(pkt,phb) ((((char*)pkt = (*(char**)(phb->tx_remove))-1) || 1)) && (*phb->u3.s2.tx_remove_ptr & PKT_IN_USE)) */
+#define remove_transmit(phb) *phb->u3.s2.tx_remove_ptr &= ~(ushort)PKT_IN_USE;\
+ if (phb->tx_remove == phb->tx_end)\
+ phb->tx_remove = phb->tx_start;\
+ else\
+ phb->tx_remove++;
+#define can_add_receive(phb) !(*phb->u4.s2.rx_add_ptr & PKT_IN_USE)
+#define add_receive(pkt,phb) *phb->rx_add = pkt;\
+ *phb->u4.s2.rx_add_ptr |= PKT_IN_USE;\
+ if (phb->rx_add == phb->rx_end)\
+ phb->rx_add = phb->rx_start;\
+ else\
+ phb->rx_add++;
+#endif
+#endif
+
+#ifdef RTA
+#define splx(oldspl) if ((oldspl) == 0) spl0()
+#endif
+
+#endif /* ifndef _list.h */
+/*********** end of file ***********/
diff --git a/drivers/char/rio/lrt.h b/drivers/char/rio/lrt.h
new file mode 100644
index 000000000000..bbac8fa18fee
--- /dev/null
+++ b/drivers/char/rio/lrt.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+ ******* *******
+ ******* L R T
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_lrt_h_sccs = "@(#)lrt.h 1.1" ;
+#endif
+#endif
+
+
+#ifdef DCIRRUS
+#define LRT_STACK (unsigned short) 600
+#else
+#define LRT_STACK (ushort) 200
+#endif
+
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/ltt.h b/drivers/char/rio/ltt.h
new file mode 100644
index 000000000000..f27dcecf03ca
--- /dev/null
+++ b/drivers/char/rio/ltt.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+ ******* *******
+ ******* L T T
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_ltt_h_sccs = "@(#)ltt.h 1.1" ;
+#endif
+#endif
+
+#ifdef DCIRRUS
+#define LTT_STACK (unsigned short) 600
+#else
+#define LTT_STACK (ushort) 200
+#endif
+
+
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/lttwake.h b/drivers/char/rio/lttwake.h
new file mode 100644
index 000000000000..fe17d0ee4933
--- /dev/null
+++ b/drivers/char/rio/lttwake.h
@@ -0,0 +1,53 @@
+
+
+
+/****************************************************************************
+ ******* *******
+ ******* L T T W A K E U P H E A D E R
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_lttwake_h_sccs = "@(#)lttwake.h 1.1" ;
+#endif
+#endif
+
+#define LTT_WAKEUP_STACK 500
+#define LTT_WAKEUP_INTERVAL (int) (500 * MILLISECOND)
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/map.h b/drivers/char/rio/map.h
new file mode 100644
index 000000000000..400645a1ff28
--- /dev/null
+++ b/drivers/char/rio/map.h
@@ -0,0 +1,103 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : map.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:11
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)map.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_map_h__
+#define __rio_map_h__
+
+#ifdef SCCS_LABELS
+static char *_map_h_sccs_ = "@(#)map.h 1.2";
+#endif
+
+/*
+** mapping structure passed to and from the config.rio program to
+** determine the current topology of the world
+*/
+
+#define MAX_MAP_ENTRY 17
+#define TOTAL_MAP_ENTRIES (MAX_MAP_ENTRY*RIO_SLOTS)
+#define MAX_NAME_LEN 32
+
+struct Map
+{
+ uint HostUniqueNum; /* Supporting hosts unique number */
+ uint RtaUniqueNum; /* Unique number */
+ /*
+ ** The next two IDs must be swapped on big-endian architectures
+ ** when using a v2.04 /etc/rio/config with a v3.00 driver (when
+ ** upgrading for example).
+ */
+ ushort ID; /* ID used in the subnet */
+ ushort ID2; /* ID of 2nd block of 8 for 16 port */
+ ulong Flags; /* Booted, ID Given, Disconnected */
+ ulong SysPort; /* First tty mapped to this port */
+ struct Top Topology[LINKS_PER_UNIT]; /* ID connected to each link */
+ char Name[MAX_NAME_LEN]; /* Cute name by which RTA is known */
+};
+
+/*
+** Flag values:
+*/
+#define RTA_BOOTED 0x00000001
+#define RTA_NEWBOOT 0x00000010
+#define MSG_DONE 0x00000020
+#define RTA_INTERCONNECT 0x00000040
+#define RTA16_SECOND_SLOT 0x00000080
+#define BEEN_HERE 0x00000100
+#define SLOT_TENTATIVE 0x40000000
+#define SLOT_IN_USE 0x80000000
+
+/*
+** HostUniqueNum is the unique number from the host card that this RTA
+** is to be connected to.
+** RtaUniqueNum is the unique number of the RTA concerned. It will be ZERO
+** if the slot in the table is unused. If it is the same as the HostUniqueNum
+** then this slot represents a host card.
+** Flags contains current boot/route state info
+** SysPort is a value in the range 0-504, being the number of the first tty
+** on this RTA. Each RTA supports 8 ports. The SysPort value must be modulo 8.
+** SysPort 0-127 correspond to /dev/ttyr001 to /dev/ttyr128, with minor
+** numbers 0-127. SysPort 128-255 correspond to /dev/ttyr129 to /dev/ttyr256,
+** again with minor numbers 0-127, and so on for SysPorts 256-383 and 384-511
+** ID will be in the range 0-16 for a `known' RTA. ID will be 0xFFFF for an
+** unused slot/unknown ID etc.
+** The Topology array contains the ID of the unit connected to each of the
+** four links on this unit. The entry will be 0xFFFF if NOTHING is connected
+** to the link, or will be 0xFF00 if an UNKNOWN unit is connected to the link.
+** The Name field is a null-terminated string, upto 31 characters, containing
+** the 'cute' name that the sysadmin/users know the RTA by. It is permissible
+** for this string to contain any character in the range \040 to \176 inclusive.
+** In particular, ctrl sequences and DEL (0x7F, \177) are not allowed. The
+** special character '%' IS allowable, and needs no special action.
+**
+*/
+
+#endif
diff --git a/drivers/char/rio/mca.h b/drivers/char/rio/mca.h
new file mode 100644
index 000000000000..08a327e473af
--- /dev/null
+++ b/drivers/char/rio/mca.h
@@ -0,0 +1,73 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : mca.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:11
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)mca.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_mca_h__
+#define __rio_mca_h__
+
+#ifdef SCCS_LABELS
+static char *_mca_h_sccs_ = "@(#)mca.h 1.2";
+#endif
+
+/*
+** Micro Channel stuff
+*/
+
+#define McaMaxSlots 8
+#define McaSlotSelect 0x96
+#define McaSlotEnable 0x08
+#define McaIdLow 0x100
+#define McaIdHigh 0x101
+#define McaIrqEnable 0x102
+#define McaMemory 0x103
+#define McaRIOId 0x6a5c
+#define McaIrq9 0x00
+#define McaIrq3 0x02
+#define McaIrq4 0x04
+#define McaIrq7 0x06
+#define McaIrq10 0x08
+#define McaIrq11 0x0A
+#define McaIrq12 0x0C
+#define McaIrq15 0x0E
+#define McaIrqMask 0x0E
+#define McaCardEnable 0x01
+#define McaAddress(X) (((X)&0xFF)<<16)
+
+#define McaTpFastLinks 0x40
+#define McaTpSlowLinks 0x00
+#define McaTpBootFromRam 0x01
+#define McaTpBootFromLink 0x00
+#define McaTpBusEnable 0x02
+#define McaTpBusDisable 0x00
+
+#define RIO_MCA_DEFAULT_MODE SLOW_LINKS
+
+#endif /* __rio_mca_h__ */
diff --git a/drivers/char/rio/mesg.h b/drivers/char/rio/mesg.h
new file mode 100644
index 000000000000..9cf6c0bacea4
--- /dev/null
+++ b/drivers/char/rio/mesg.h
@@ -0,0 +1,41 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : mesg.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:12
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)mesg.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_mesg_h__
+#define __rio_mesg_h__
+
+#ifdef SCCS_LABELS
+static char *_mesg_h_sccs_ = "@(#)mesg.h 1.2";
+#endif
+
+
+#endif /* __rio_mesg_h__ */
diff --git a/drivers/char/rio/param.h b/drivers/char/rio/param.h
new file mode 100644
index 000000000000..2dc30b9aab37
--- /dev/null
+++ b/drivers/char/rio/param.h
@@ -0,0 +1,61 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : param.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:12
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)param.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_param_h__
+#define __rio_param_h__
+
+#ifdef SCCS_LABELS
+static char *_param_h_sccs_ = "@(#)param.h 1.2";
+#endif
+
+
+/*
+** the param command block, as used in OPEN and PARAM calls.
+*/
+
+struct phb_param
+{
+ BYTE Cmd; /* It is very important that these line up */
+ BYTE Cor1; /* with what is expected at the other end. */
+ BYTE Cor2; /* to confirm that you've got it right, */
+ BYTE Cor4; /* check with cirrus/cirrus.h */
+ BYTE Cor5;
+ BYTE TxXon; /* Transmit X-On character */
+ BYTE TxXoff; /* Transmit X-Off character */
+ BYTE RxXon; /* Receive X-On character */
+ BYTE RxXoff; /* Receive X-Off character */
+ BYTE LNext; /* Literal-next character */
+ BYTE TxBaud; /* Transmit baudrate */
+ BYTE RxBaud; /* Receive baudrate */
+};
+
+#endif
diff --git a/drivers/char/rio/parmmap.h b/drivers/char/rio/parmmap.h
new file mode 100644
index 000000000000..46f99dfdac8d
--- /dev/null
+++ b/drivers/char/rio/parmmap.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+ ******* *******
+ ******* H O S T M E M O R Y M A P
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+6/4/1991 jonb Made changes to accommodate Mips R3230 bus
+ ***************************************************************************/
+
+#ifndef _parmap_h
+#define _parmap_h
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_parmmap_h_sccs = "@(#)parmmap.h 1.4"; */
+#endif
+#endif
+
+typedef struct PARM_MAP PARM_MAP ;
+
+struct PARM_MAP
+{
+PHB_ptr phb_ptr ; /* Pointer to the PHB array */
+WORD_ptr phb_num_ptr ; /* Ptr to Number of PHB's */
+FREE_LIST_ptr free_list; /* Free List pointer */
+FREE_LIST_ptr free_list_end; /* Free List End pointer */
+Q_BUF_ptr_ptr q_free_list_ptr ; /* Ptr to Q_BUF variable */
+BYTE_ptr unit_id_ptr ; /* Unit Id */
+LPB_ptr link_str_ptr ; /* Link Structure Array */
+BYTE_ptr bootloader_1 ; /* 1st Stage Boot Loader */
+BYTE_ptr bootloader_2 ; /* 2nd Stage Boot Loader */
+WORD_ptr port_route_map_ptr ; /* Port Route Map */
+ROUTE_STR_ptr route_ptr ; /* Unit Route Map */
+NUMBER_ptr map_present ; /* Route Map present */
+NUMBER pkt_num ; /* Total number of packets */
+NUMBER q_num ; /* Total number of Q packets */
+WORD buffers_per_port ; /* Number of buffers per port */
+WORD heap_size ; /* Initial size of heap */
+WORD heap_left ; /* Current Heap left */
+WORD error ; /* Error code */
+WORD tx_max; /* Max number of tx pkts per phb */
+WORD rx_max; /* Max number of rx pkts per phb */
+WORD rx_limit; /* For high / low watermarks */
+NUMBER links ; /* Links to use */
+NUMBER timer ; /* Interrupts per second */
+RUP_ptr rups ; /* Pointer to the RUPs */
+WORD max_phb ; /* Mostly for debugging */
+WORD living ; /* Just increments!! */
+WORD init_done ; /* Initialisation over */
+WORD booting_link ;
+WORD idle_count ; /* Idle time counter */
+WORD busy_count ; /* Busy counter */
+WORD idle_control ; /* Control Idle Process */
+#if defined(HOST) || defined(INKERNEL)
+WORD tx_intr; /* TX interrupt pending */
+WORD rx_intr; /* RX interrupt pending */
+WORD rup_intr; /* RUP interrupt pending */
+#endif
+#if defined(RTA)
+WORD dying_count; /* Count of processes dead */
+#endif
+} ;
+
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/pci.h b/drivers/char/rio/pci.h
new file mode 100644
index 000000000000..dc635bd25194
--- /dev/null
+++ b/drivers/char/rio/pci.h
@@ -0,0 +1,76 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : pci.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:12
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)pci.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_pci_h__
+#define __rio_pci_h__
+
+#ifdef SCCS_LABELS
+static char *_pci_h_sccs_ = "@(#)pci.h 1.2";
+#endif
+
+/*
+** PCI stuff
+*/
+
+#define PCITpFastClock 0x80
+#define PCITpSlowClock 0x00
+#define PCITpFastLinks 0x40
+#define PCITpSlowLinks 0x00
+#define PCITpIntEnable 0x04
+#define PCITpIntDisable 0x00
+#define PCITpBusEnable 0x02
+#define PCITpBusDisable 0x00
+#define PCITpBootFromRam 0x01
+#define PCITpBootFromLink 0x00
+
+#define RIO_PCI_VENDOR 0x11CB
+#define RIO_PCI_DEVICE 0x8000
+#define RIO_PCI_BASE_CLASS 0x02
+#define RIO_PCI_SUB_CLASS 0x80
+#define RIO_PCI_PROG_IFACE 0x00
+
+#define RIO_PCI_RID 0x0008
+#define RIO_PCI_BADR0 0x0010
+#define RIO_PCI_INTLN 0x003C
+#define RIO_PCI_INTPIN 0x003D
+
+#define RIO_PCI_MEM_SIZE 65536
+
+#define RIO_PCI_TURBO_TP 0x80
+#define RIO_PCI_FAST_LINKS 0x40
+#define RIO_PCI_INT_ENABLE 0x04
+#define RIO_PCI_TP_BUS_ENABLE 0x02
+#define RIO_PCI_BOOT_FROM_RAM 0x01
+
+#define RIO_PCI_DEFAULT_MODE 0x05
+
+#endif /* __rio_pci_h__ */
diff --git a/drivers/char/rio/phb.h b/drivers/char/rio/phb.h
new file mode 100644
index 000000000000..e1483a0e30bd
--- /dev/null
+++ b/drivers/char/rio/phb.h
@@ -0,0 +1,293 @@
+/****************************************************************************
+ ******* *******
+ ******* P H B H E A D E R *******
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra, Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _phb_h
+#define _phb_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_phb_h_sccs = "@(#)phb.h 1.12"; */
+#endif
+#endif
+
+
+ /*************************************************
+ * Set the LIMIT values.
+ ************************************************/
+#ifdef RTA
+#define RX_LIMIT (ushort) 3
+#endif
+#ifdef HOST
+#define RX_LIMIT (ushort) 1
+#endif
+
+
+/*************************************************
+ * Handshake asserted. Deasserted by the LTT(s)
+ ************************************************/
+#define PHB_HANDSHAKE_SET ((ushort) 0x001) /* Set by LRT */
+
+#define PHB_HANDSHAKE_RESET ((ushort) 0x002) /* Set by ISR / driver */
+
+#define PHB_HANDSHAKE_FLAGS (PHB_HANDSHAKE_RESET | PHB_HANDSHAKE_SET)
+ /* Reset by ltt */
+
+
+/*************************************************
+ * Maximum number of PHB's
+ ************************************************/
+#if defined (HOST) || defined (INKERNEL)
+#define MAX_PHB ((ushort) 128) /* range 0-127 */
+#else
+#define MAX_PHB ((ushort) 8) /* range 0-7 */
+#endif
+
+/*************************************************
+ * Defines for the mode fields
+ ************************************************/
+#define TXPKT_INCOMPLETE 0x0001 /* Previous tx packet not completed */
+#define TXINTR_ENABLED 0x0002 /* Tx interrupt is enabled */
+#define TX_TAB3 0x0004 /* TAB3 mode */
+#define TX_OCRNL 0x0008 /* OCRNL mode */
+#define TX_ONLCR 0x0010 /* ONLCR mode */
+#define TX_SENDSPACES 0x0020 /* Send n spaces command needs
+ completing */
+#define TX_SENDNULL 0x0040 /* Escaping NULL needs completing */
+#define TX_SENDLF 0x0080 /* LF -> CR LF needs completing */
+#define TX_PARALLELBUG 0x0100 /* CD1400 LF -> CR LF bug on parallel
+ port */
+#define TX_HANGOVER (TX_SENDSPACES | TX_SENDLF | TX_SENDNULL)
+#define TX_DTRFLOW 0x0200 /* DTR tx flow control */
+#define TX_DTRFLOWED 0x0400 /* DTR is low - don't allow more data
+ into the FIFO */
+#define TX_DATAINFIFO 0x0800 /* There is data in the FIFO */
+#define TX_BUSY 0x1000 /* Data in FIFO, shift or holding regs */
+
+#define RX_SPARE 0x0001 /* SPARE */
+#define RXINTR_ENABLED 0x0002 /* Rx interrupt enabled */
+#define RX_ICRNL 0x0008 /* ICRNL mode */
+#define RX_INLCR 0x0010 /* INLCR mode */
+#define RX_IGNCR 0x0020 /* IGNCR mode */
+#define RX_CTSFLOW 0x0040 /* CTSFLOW enabled */
+#define RX_IXOFF 0x0080 /* IXOFF enabled */
+#define RX_CTSFLOWED 0x0100 /* CTSFLOW and CTS dropped */
+#define RX_IXOFFED 0x0200 /* IXOFF and xoff sent */
+#define RX_BUFFERED 0x0400 /* Try and pass on complete packets */
+
+#define PORT_ISOPEN 0x0001 /* Port open? */
+#define PORT_HUPCL 0x0002 /* Hangup on close? */
+#define PORT_MOPENPEND 0x0004 /* Modem open pending */
+#define PORT_ISPARALLEL 0x0008 /* Parallel port */
+#define PORT_BREAK 0x0010 /* Port on break */
+#define PORT_STATUSPEND 0x0020 /* Status packet pending */
+#define PORT_BREAKPEND 0x0040 /* Break packet pending */
+#define PORT_MODEMPEND 0x0080 /* Modem status packet pending */
+#define PORT_PARALLELBUG 0x0100 /* CD1400 LF -> CR LF bug on parallel
+ port */
+#define PORT_FULLMODEM 0x0200 /* Full modem signals */
+#define PORT_RJ45 0x0400 /* RJ45 connector - no RI signal */
+#define PORT_RESTRICTED 0x0600 /* Restricted connector - no RI / DTR */
+
+#define PORT_MODEMBITS 0x0600 /* Mask for modem fields */
+
+#define PORT_WCLOSE 0x0800 /* Waiting for close */
+#define PORT_HANDSHAKEFIX 0x1000 /* Port has H/W flow control fix */
+#define PORT_WASPCLOSED 0x2000 /* Port closed with PCLOSE */
+#define DUMPMODE 0x4000 /* Dump RTA mem */
+#define READ_REG 0x8000 /* Read CD1400 register */
+
+
+
+/**************************************************************************
+ * PHB Structure
+ * A few words.
+ *
+ * Normally Packets are added to the end of the list and removed from
+ * the start. The pointer tx_add points to a SPACE to put a Packet.
+ * The pointer tx_remove points to the next Packet to remove
+ *************************************************************************/
+#ifndef INKERNEL
+#define src_unit u2.s2.unit
+#define src_port u2.s2.port
+#define dest_unit u1.s1.unit
+#define dest_port u1.s1.port
+#endif
+#ifdef HOST
+#define tx_start u3.s1.tx_start_ptr_ptr
+#define tx_add u3.s1.tx_add_ptr_ptr
+#define tx_end u3.s1.tx_end_ptr_ptr
+#define tx_remove u3.s1.tx_remove_ptr_ptr
+#define rx_start u4.s1.rx_start_ptr_ptr
+#define rx_add u4.s1.rx_add_ptr_ptr
+#define rx_end u4.s1.rx_end_ptr_ptr
+#define rx_remove u4.s1.rx_remove_ptr_ptr
+#endif
+typedef struct PHB PHB ;
+struct PHB {
+#ifdef RTA
+ ushort port;
+#endif
+#ifdef INKERNEL
+ WORD source;
+#else
+ union
+ {
+ ushort source; /* Complete source */
+ struct
+ {
+ unsigned char unit; /* Source unit */
+ unsigned char port; /* Source port */
+ } s2;
+ } u2;
+#endif
+ WORD handshake ;
+ WORD status ;
+ NUMBER timeout ; /* Maximum of 1.9 seconds */
+ WORD link ; /* Send down this link */
+#ifdef INKERNEL
+ WORD destination;
+#else
+ union
+ {
+ ushort destination; /* Complete destination */
+ struct
+ {
+ unsigned char unit; /* Destination unit */
+ unsigned char port; /* Destination port */
+ } s1;
+ } u1;
+#endif
+#ifdef RTA
+ ushort tx_pkts_added;
+ ushort tx_pkts_removed;
+ Q_BUF_ptr tx_q_start ; /* Start of the Q list chain */
+ short num_tx_q_bufs ; /* Number of Q buffers in the chain */
+ PKT_ptr_ptr tx_add ; /* Add a new Packet here */
+ Q_BUF_ptr tx_add_qb; /* Pointer to the add Q buf */
+ PKT_ptr_ptr tx_add_st_qbb ; /* Pointer to start of the Q's buf */
+ PKT_ptr_ptr tx_add_end_qbb ; /* Pointer to the end of the Q's buf */
+ PKT_ptr_ptr tx_remove ; /* Remove a Packet here */
+ Q_BUF_ptr tx_remove_qb ; /* Pointer to the remove Q buf */
+ PKT_ptr_ptr tx_remove_st_qbb ; /* Pointer to the start of the Q buf */
+ PKT_ptr_ptr tx_remove_end_qbb ; /* Pointer to the end of the Q buf */
+#endif
+#ifdef INKERNEL
+ PKT_ptr_ptr tx_start ;
+ PKT_ptr_ptr tx_end ;
+ PKT_ptr_ptr tx_add ;
+ PKT_ptr_ptr tx_remove ;
+#endif
+#ifdef HOST
+ union
+ {
+ struct
+ {
+ PKT_ptr_ptr tx_start_ptr_ptr;
+ PKT_ptr_ptr tx_end_ptr_ptr;
+ PKT_ptr_ptr tx_add_ptr_ptr;
+ PKT_ptr_ptr tx_remove_ptr_ptr;
+ } s1;
+ struct
+ {
+ ushort * tx_start_ptr;
+ ushort * tx_end_ptr;
+ ushort * tx_add_ptr;
+ ushort * tx_remove_ptr;
+ } s2;
+ } u3;
+#endif
+
+#ifdef RTA
+ ushort rx_pkts_added;
+ ushort rx_pkts_removed;
+ Q_BUF_ptr rx_q_start ; /* Start of the Q list chain */
+ short num_rx_q_bufs ; /* Number of Q buffers in the chain */
+ PKT_ptr_ptr rx_add ; /* Add a new Packet here */
+ Q_BUF_ptr rx_add_qb ; /* Pointer to the add Q buf */
+ PKT_ptr_ptr rx_add_st_qbb ; /* Pointer to start of the Q's buf */
+ PKT_ptr_ptr rx_add_end_qbb ; /* Pointer to the end of the Q's buf */
+ PKT_ptr_ptr rx_remove ; /* Remove a Packet here */
+ Q_BUF_ptr rx_remove_qb ; /* Pointer to the remove Q buf */
+ PKT_ptr_ptr rx_remove_st_qbb ; /* Pointer to the start of the Q buf */
+ PKT_ptr_ptr rx_remove_end_qbb ; /* Pointer to the end of the Q buf */
+#endif
+#ifdef INKERNEL
+ PKT_ptr_ptr rx_start ;
+ PKT_ptr_ptr rx_end ;
+ PKT_ptr_ptr rx_add ;
+ PKT_ptr_ptr rx_remove ;
+#endif
+#ifdef HOST
+ union
+ {
+ struct
+ {
+ PKT_ptr_ptr rx_start_ptr_ptr;
+ PKT_ptr_ptr rx_end_ptr_ptr;
+ PKT_ptr_ptr rx_add_ptr_ptr;
+ PKT_ptr_ptr rx_remove_ptr_ptr;
+ } s1;
+ struct
+ {
+ ushort * rx_start_ptr;
+ ushort * rx_end_ptr;
+ ushort * rx_add_ptr;
+ ushort * rx_remove_ptr;
+ } s2;
+ } u4;
+#endif
+
+#ifdef RTA /* some fields for the remotes */
+ ushort flush_count; /* Count of write flushes */
+ ushort txmode; /* Modes for tx */
+ ushort rxmode; /* Modes for rx */
+ ushort portmode; /* Generic modes */
+ ushort column; /* TAB3 column count */
+ ushort tx_subscript; /* (TX) Subscript into data field */
+ ushort rx_subscript; /* (RX) Subscript into data field */
+ PKT_ptr rx_incomplete; /* Hold an incomplete packet here */
+ ushort modem_bits; /* Modem bits to mask */
+ ushort lastModem; /* Modem control lines. */
+ ushort addr; /* Address for sub commands */
+ ushort MonitorTstate; /* TRUE if monitoring tstop */
+#endif
+
+ } ;
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/pkt.h b/drivers/char/rio/pkt.h
new file mode 100644
index 000000000000..66bb2ff0f694
--- /dev/null
+++ b/drivers/char/rio/pkt.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+ ******* *******
+ ******* P A C K E T H E A D E R F I L E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _pkt_h
+#define _pkt_h 1
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_pkt_h_sccs = "@(#)pkt.h 1.8"; */
+#endif
+#endif
+
+#define MAX_TTL 0xf
+#define PKT_CMD_BIT ((ushort) 0x080)
+#define PKT_CMD_DATA ((ushort) 0x080)
+
+#define PKT_ACK ((ushort) 0x040)
+
+#define PKT_TGL ((ushort) 0x020)
+
+#define PKT_LEN_MASK ((ushort) 0x07f)
+
+#define DATA_WNDW ((ushort) 0x10)
+#define PKT_TTL_MASK ((ushort) 0x0f)
+
+#define PKT_MAX_DATA_LEN 72
+
+#define PKT_LENGTH sizeof(struct PKT)
+#define SYNC_PKT_LENGTH (PKT_LENGTH + 4)
+
+#define CONTROL_PKT_LEN_MASK PKT_LEN_MASK
+#define CONTROL_PKT_CMD_BIT PKT_CMD_BIT
+#define CONTROL_PKT_ACK (PKT_ACK << 8)
+#define CONTROL_PKT_TGL (PKT_TGL << 8)
+#define CONTROL_PKT_TTL_MASK (PKT_TTL_MASK << 8)
+#define CONTROL_DATA_WNDW (DATA_WNDW << 8)
+
+struct PKT {
+#ifdef INKERNEL
+ BYTE dest_unit ; /* Destination Unit Id */
+ BYTE dest_port ; /* Destination POrt */
+ BYTE src_unit ; /* Source Unit Id */
+ BYTE src_port ; /* Source POrt */
+#else
+ union
+ {
+ ushort destination; /* Complete destination */
+ struct
+ {
+ unsigned char unit; /* Destination unit */
+ unsigned char port; /* Destination port */
+ } s1;
+ } u1;
+ union
+ {
+ ushort source; /* Complete source */
+ struct
+ {
+ unsigned char unit; /* Source unit */
+ unsigned char port; /* Source port */
+ } s2;
+ } u2;
+#endif
+#ifdef INKERNEL
+ BYTE len ;
+ BYTE control;
+#else
+ union
+ {
+ ushort control;
+ struct
+ {
+ unsigned char len;
+ unsigned char control;
+ } s3;
+ } u3;
+#endif
+ BYTE data[PKT_MAX_DATA_LEN] ;
+ /* Actual data :-) */
+ WORD csum ; /* C-SUM */
+ } ;
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/poll.h b/drivers/char/rio/poll.h
new file mode 100644
index 000000000000..d9b8e983e175
--- /dev/null
+++ b/drivers/char/rio/poll.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+ ******* *******
+ ******* P O L L
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _poll_h
+#define _poll_h
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_poll_h_sccs = "@(#)poll.h 1.2" ;
+#endif
+#endif
+
+
+#ifdef HOST
+#define POLL_STACK 100
+#endif
+#ifdef RTA
+#define POLL_STACK 200
+#endif
+
+#define POLL_PERIOD (int) SECOND
+
+/* The various poll commands */
+#define POLL_POLL 0 /* We are connected and happy.. */
+#define POLL_INTRO 1 /* Introduction packet */
+#define POLL_TOPOLOGY 2 /* Topology update */
+#define POLL_ASSIGN 3 /* ID assign */
+#define POLL_FOAD 4 /* F*** Off And Die */
+#define POLL_LMD 5 /* Let Me Die */
+#define POLL_DYB 6 /* Die You Ba***** */
+
+/* The way data fields are split up for POLL packets */
+#define POLL_HOST_SERIAL 2 /* Host who booted me */
+#define POLL_MY_SERIAL 6 /* My serial number */
+#define POLL_YOUR_ID 1 /* Your ID number */
+#define POLL_TOPOLOGY_FIELDS 2 /* Topology maps */
+
+#endif
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/port.h b/drivers/char/rio/port.h
new file mode 100644
index 000000000000..8506af06aa9f
--- /dev/null
+++ b/drivers/char/rio/port.h
@@ -0,0 +1,245 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : port.h
+** SID : 1.3
+** Last Modified : 11/6/98 11:34:12
+** Retrieved : 11/6/98 11:34:21
+**
+** ident @(#)port.h 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_port_h__
+#define __rio_port_h__
+
+#ifdef SCCS_LABELS
+static char *_port_h_sccs_ = "@(#)port.h 1.3";
+#endif
+
+
+#undef VPIX
+
+
+/*
+** the port data structure - one per port in the system
+*/
+
+#ifdef STATS
+struct RIOStats
+{
+ /*
+ ** interrupt statistics
+ */
+ uint BreakIntCnt;
+ uint ModemOffCnt;
+ uint ModemOnCnt;
+ uint RxIntCnt;
+ uint TxIntCnt;
+ /*
+ ** throughput statistics
+ */
+ uint RxCharCnt;
+ uint RxPktCnt;
+ uint RxSaveCnt;
+ uint TxCharCnt;
+ uint TxPktCnt;
+ /*
+ ** driver entry statistics
+ */
+ uint CloseCnt;
+ uint IoctlCnt;
+ uint OpenCnt;
+ uint ReadCnt;
+ uint WriteCnt;
+ /*
+ ** proc statistics
+ */
+ uint BlockCnt;
+ uint OutputCnt;
+ uint ResumeCnt;
+ uint RflushCnt;
+ uint SuspendCnt;
+ uint TbreakCnt;
+ uint TimeoutCnt;
+ uint UnblockCnt;
+ uint WflushCnt;
+ uint WFBodgeCnt;
+};
+#endif
+
+/*
+** Port data structure
+*/
+struct Port
+{
+ struct gs_port gs;
+ int PortNum; /* RIO port no., 0-511 */
+ struct Host *HostP;
+ volatile caddr_t Caddr;
+ ushort HostPort; /* Port number on host card */
+ uchar RupNum; /* Number of RUP for port */
+ uchar ID2; /* Second ID of RTA for port */
+ ulong State; /* FLAGS for open & xopen */
+#define RIO_LOPEN 0x00001 /* Local open */
+#define RIO_MOPEN 0x00002 /* Modem open */
+#define RIO_WOPEN 0x00004 /* Waiting for open */
+#define RIO_CLOSING 0x00008 /* The port is being close */
+#define RIO_XPBUSY 0x00010 /* Transparent printer busy */
+#define RIO_BREAKING 0x00020 /* Break in progress */
+#define RIO_DIRECT 0x00040 /* Doing Direct output */
+#define RIO_EXCLUSIVE 0x00080 /* Stream open for exclusive use */
+#define RIO_NDELAY 0x00100 /* Stream is open FNDELAY */
+#define RIO_CARR_ON 0x00200 /* Stream has carrier present */
+#define RIO_XPWANTR 0x00400 /* Stream wanted by Xprint */
+#define RIO_RBLK 0x00800 /* Stream is read-blocked */
+#define RIO_BUSY 0x01000 /* Stream is BUSY for write */
+#define RIO_TIMEOUT 0x02000 /* Stream timeout in progress */
+#define RIO_TXSTOP 0x04000 /* Stream output is stopped */
+#define RIO_WAITFLUSH 0x08000 /* Stream waiting for flush */
+#define RIO_DYNOROD 0x10000 /* Drain failed */
+#define RIO_DELETED 0x20000 /* RTA has been deleted */
+#define RIO_ISSCANCODE 0x40000 /* This line is in scancode mode */
+#define RIO_USING_EUC 0x100000 /* Using extended Unix chars */
+#define RIO_CAN_COOK 0x200000 /* This line can do cooking */
+#define RIO_TRIAD_MODE 0x400000 /* Enable TRIAD special ops. */
+#define RIO_TRIAD_BLOCK 0x800000 /* Next read will block */
+#define RIO_TRIAD_FUNC 0x1000000 /* Seen a function key coming in */
+#define RIO_THROTTLE_RX 0x2000000 /* RX needs to be throttled. */
+
+ ulong Config; /* FLAGS for NOREAD.... */
+#define RIO_NOREAD 0x0001 /* Are not allowed to read port */
+#define RIO_NOWRITE 0x0002 /* Are not allowed to write port */
+#define RIO_NOXPRINT 0x0004 /* Are not allowed to xprint port */
+#define RIO_NOMASK 0x0007 /* All not allowed things */
+#define RIO_IXANY 0x0008 /* Port is allowed ixany */
+#define RIO_MODEM 0x0010 /* Stream is a modem device */
+#define RIO_IXON 0x0020 /* Port is allowed ixon */
+#define RIO_WAITDRAIN 0x0040 /* Wait for port to completely drain */
+#define RIO_MAP_50_TO_50 0x0080 /* Map 50 baud to 50 baud */
+#define RIO_MAP_110_TO_110 0x0100 /* Map 110 baud to 110 baud */
+
+/*
+** 15.10.1998 ARG - ESIL 0761 prt fix
+** As LynxOS does not appear to support Hardware Flow Control .....
+** Define our own flow control flags in 'Config'.
+*/
+#define RIO_CTSFLOW 0x0200 /* RIO's own CTSFLOW flag */
+#define RIO_RTSFLOW 0x0400 /* RIO's own RTSFLOW flag */
+
+
+ struct PHB *PhbP; /* pointer to PHB for port */
+ WORD *TxAdd; /* Add packets here */
+ WORD *TxStart; /* Start of add array */
+ WORD *TxEnd; /* End of add array */
+ WORD *RxRemove; /* Remove packets here */
+ WORD *RxStart; /* Start of remove array */
+ WORD *RxEnd; /* End of remove array */
+ uint RtaUniqueNum; /* Unique number of RTA */
+ ushort PortState; /* status of port */
+ ushort ModemState; /* status of modem lines */
+ ulong ModemLines; /* Modem bits sent to RTA */
+ uchar CookMode; /* who expands CR/LF? */
+ uchar ParamSem; /* Prevent write during param */
+ uchar Mapped; /* if port mapped onto host */
+ uchar SecondBlock; /* if port belongs to 2nd block
+ of 16 port RTA */
+ uchar InUse; /* how many pre-emptive cmds */
+ uchar Lock; /* if params locked */
+ uchar Store; /* if params stored across closes */
+ uchar FirstOpen; /* TRUE if first time port opened */
+ uchar FlushCmdBodge; /* if doing a (non)flush */
+ uchar MagicFlags; /* require intr processing */
+#define MAGIC_FLUSH 0x01 /* mirror of WflushFlag */
+#define MAGIC_REBOOT 0x02 /* RTA re-booted, re-open ports */
+#define MORE_OUTPUT_EYGOR 0x04 /* riotproc failed to empty clists */
+ uchar WflushFlag; /* 1 How many WFLUSHs active */
+/*
+** Transparent print stuff
+*/
+ struct Xprint
+ {
+#ifndef MAX_XP_CTRL_LEN
+#define MAX_XP_CTRL_LEN 16 /* ALSO IN DAEMON.H */
+#endif
+ uint XpCps;
+ char XpOn[MAX_XP_CTRL_LEN];
+ char XpOff[MAX_XP_CTRL_LEN];
+ ushort XpLen; /* strlen(XpOn)+strlen(XpOff) */
+ uchar XpActive;
+ uchar XpLastTickOk; /* TRUE if we can process */
+#define XP_OPEN 00001
+#define XP_RUNABLE 00002
+ struct ttystatics *XttyP;
+ } Xprint;
+#ifdef VPIX
+ v86_t *StashP;
+ uint IntMask;
+ struct termss VpixSs;
+ uchar ModemStatusReg; /* Modem status register */
+#endif
+ uchar RxDataStart;
+ uchar Cor2Copy; /* copy of COR2 */
+ char *Name; /* points to the Rta's name */
+#ifdef STATS
+ struct RIOStats Stat; /* ports statistics */
+#endif
+ char *TxRingBuffer;
+ ushort TxBufferIn; /* New data arrives here */
+ ushort TxBufferOut; /* Intr removes data here */
+ ushort OldTxBufferOut; /* Indicates if draining */
+ int TimeoutId; /* Timeout ID */
+ uint Debug;
+ uchar WaitUntilBooted; /* True if open should block */
+ uint statsGather; /* True if gathering stats */
+ ulong txchars; /* Chars transmitted */
+ ulong rxchars; /* Chars received */
+ ulong opens; /* port open count */
+ ulong closes; /* port close count */
+ ulong ioctls; /* ioctl count */
+ uchar LastRxTgl; /* Last state of rx toggle bit */
+ spinlock_t portSem; /* Lock using this sem */
+ int MonitorTstate; /* Monitoring ? */
+ int timeout_id; /* For calling 100 ms delays */
+ int timeout_sem;/* For calling 100 ms delays */
+ int firstOpen; /* First time open ? */
+ char * p; /* save the global struc here .. */
+};
+
+struct ModuleInfo
+{
+ char *Name;
+ uint Flags[4]; /* one per port on a module */
+};
+#endif
+
+/*
+** This struct is required because trying to grab an entire Port structure
+** runs into problems with differing struct sizes between driver and config.
+*/
+struct PortParams {
+ uint Port;
+ ulong Config;
+ ulong State;
+ struct ttystatics *TtyP;
+};
diff --git a/drivers/char/rio/proto.h b/drivers/char/rio/proto.h
new file mode 100644
index 000000000000..ddff0ef84e3a
--- /dev/null
+++ b/drivers/char/rio/proto.h
@@ -0,0 +1,244 @@
+/*
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef _prototypes_h
+#define _prototypes_h
+
+
+/*
+** boot.c
+*/
+void init_boot( char *p, short stage);
+
+/*
+** disconct.c
+*/
+void kill_boot ( LPB *link );
+void disconnected( LPB *link );
+short boot_3( LPB *link, PKT *pkt );
+short send_3_pkt( LPB *link, PKT *pkt);
+
+/*
+** error.c
+*/
+void du_error(void);
+
+/*
+** formpkt.c
+*/
+ushort sum_it( PKT *pkt ) ;
+void form_rup_pkt( RUP *form_rup, PKT *pkt );
+void form_poll_pkt ( int type, LPB *link, int node );
+void form_route_pkt ( int type, PKT *pkt, LPB *link );
+
+/*
+** idle.c
+*/
+void idle( Process *idle_p );
+
+/*
+** init.c
+*/
+void general_init(void);
+void mem_halt( int error);
+
+/*
+** linkinit.c
+*/
+void initlink( u_short number, LPB *link);
+void runlink( LPB *link);
+
+/*
+** list.c
+*/
+PKT *get_free_start(void);
+void put_free_start( PKT *pkt);
+
+#ifdef HOST
+int can_remove_transmit ( PKT **pkt, PKT *pointer );
+#endif
+
+#ifdef RTA
+int spl7 ( void );
+int spl0 ( void );
+Q_BUF *get_free_q( void );
+PKT *get_free_end(void);
+int add_end( PKT *pkt, PHB *phb, int type);
+unsigned short free_packets( PHB *phb, int type);
+int can_remove_start( PKT **pkt, PHB *phb, int type);
+int can_add_start( PHB *phb, int type);
+int can_add_end( PHB *phb, int type);
+void put_free_end( PKT *pkt);
+int remove_start( PKT **pkt, PHB *phb, int type);
+#endif
+
+/*
+** Lrt.c
+*/
+void lrt( Process *lrt_p, LPB *link );
+
+#ifdef RTA
+void set_led_red ( LPB *link );
+#endif
+
+/*
+** ltt.c
+*/
+void ltt( Process *ltt_p, LPB *link, PHB *phb_ptr[] );
+void send_poll ( LPB *link );
+void request_id ( LPB *link );
+void send_topology_update ( LPB *link );
+void send_topology ( LPB *link );
+void supply_id ( LPB *link );
+
+#ifdef RTA
+void redirect_queue ( LPB *link, ushort flush );
+int obtain_rup ( int rup_number, PKT **pkt_address, LPB *link );
+#endif
+
+#ifdef TESTING_PERF
+int consume_cpu( void );
+#endif
+
+/*
+** lttwake.c
+*/
+#ifdef HOST
+void ltt_wakeup( Process *ltt_wakeup_p );
+#endif
+
+/*
+** mapgen.c
+*/
+void generate_id_map( short mapping, ROUTE_STR route[] );
+void gen_map( int mapping, int looking_at, int come_from, ROUTE_STR route[], int link, int *ttl );
+void adjust_ttl( int mapping, int looking_at, int come_from, ROUTE_STR route[], int link, int *ttl);
+void init_sys_map(void);
+
+/*
+** mmu.c
+*/
+char *rio_malloc( unsigned int amount);
+char *rio_calloc( unsigned int num, unsigned int size);
+ERROR rio_mmu_init( uint total_mem );
+
+/*
+** partn.c
+*/
+void partition_tx( struct PHB *phb, u_short tx_size, u_short rx_size, u_short rx_limit);
+
+/*
+** poll.c
+*/
+void tx_poll( Process *tx_poll_p);
+
+/*
+** process.c
+*/
+int get_proc_space( Process **pd, int **pws, int wssize);
+
+/*
+** readrom.c
+*/
+void read_serial_number(char *buf);
+
+/*
+** rio.c
+*/
+int main( void );
+
+/*
+** route.c
+*/
+void route_update ( PKT *pkt, LPB *link);
+
+/*
+** rtainit.c
+*/
+#if defined(RTA)
+void rta_init(ushort RtaType);
+#endif /* defined(RTA) */
+
+/*
+** rupboot.c
+*/
+void rup_boot( PKT *pkt, RUP *this_rup, LPB *link);
+
+#ifdef RTA
+void kill_your_neighbour( int link_to_kill );
+#endif
+
+/*
+** rupcmd.c
+*/
+void rup_command( PKT *pkt, struct RUP *this_rup, LPB *link);
+
+/*
+** ruperr.c
+*/
+void rup_error( PKT *pkt, RUP *this_rup, LPB *link );
+void illegal_cmd( PKT *src_pkt );
+
+/*
+** ruppoll.c
+*/
+void rup_poll( PKT *pkt, RUP *this_rup, LPB *link );
+
+/*
+** ruppower.c
+*/
+void rup_power( PKT *pkt, RUP *this_rup, LPB *link );
+
+/*
+** ruprm.c
+*/
+void rup_route_map( PKT *pkt, RUP *this_rup, LPB *link);
+
+/*
+** rupstat.c
+*/
+void rup_status( PKT *pkt, RUP *this_rup, LPB *link);
+
+/*
+** rupsync.c
+*/
+void rup_sync( PKT *pkt);
+
+/*
+** rxpkt.c
+*/
+ERROR rx_pkt( PKT_ptr_ptr pkt_address, LPB *link);
+
+/*
+** sendsts.c
+*/
+void send_status( PKT *requesting_pkt, RUP *this_rup);
+
+/*
+** serial.c
+*/
+void assign_serial ( char *ser_in, char *ser_out);
+int cmp_serial ( char *ser_1, char *ser_2);
+
+/*
+** txpkt.c
+*/
+ERROR tx_pkt( PKT *pkt, LPB *link);
+short send_sync( LPB *link);
+
+#endif /* _prototypes_h */
diff --git a/drivers/char/rio/protsts.h b/drivers/char/rio/protsts.h
new file mode 100644
index 000000000000..848111ac9380
--- /dev/null
+++ b/drivers/char/rio/protsts.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+ ******* *******
+ ******* P R O T O C O L S T A T U S S T R U C T U R E *******
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _protsts_h
+#define _protsts_h 1
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_protsts_h_sccs = "@(#)protsts.h 1.4"; */
+#endif
+#endif
+
+/*************************************************
+ * ACK bit. Last Packet received OK. Set by
+ * rxpkt to indicate that the Packet has been
+ * received OK and that the LTT must set the ACK
+ * bit in the next outward bound Packet
+ * and re-set by LTT's after xmit.
+ *
+ * Gets shoved into rx_status
+ ************************************************/
+#define PHB_RX_LAST_PKT_ACKED ((ushort) 0x080)
+
+/*******************************************************
+ * The Rx TOGGLE bit.
+ * Stuffed into rx_status by RXPKT
+ ******************************************************/
+#define PHB_RX_DATA_WNDW ((ushort) 0x040)
+
+/*******************************************************
+ * The Rx TOGGLE bit. Matches the setting in PKT.H
+ * Stuffed into rx_status
+ ******************************************************/
+#define PHB_RX_TGL ((ushort) 0x2000)
+
+
+/*************************************************
+ * This bit is set by the LRT to indicate that
+ * an ACK (packet) must be returned.
+ *
+ * Gets shoved into tx_status
+ ************************************************/
+#define PHB_TX_SEND_PKT_ACK ((ushort) 0x08)
+
+/*************************************************
+ * Set by LTT to indicate that an ACK is required
+ *************************************************/
+#define PHB_TX_ACK_RQRD ((ushort) 0x01)
+
+
+/*******************************************************
+ * The Tx TOGGLE bit.
+ * Stuffed into tx_status by RXPKT from the PKT WndW
+ * field. Looked by the LTT when the NEXT Packet
+ * is going to be sent.
+ ******************************************************/
+#define PHB_TX_DATA_WNDW ((ushort) 0x04)
+
+
+/*******************************************************
+ * The Tx TOGGLE bit. Matches the setting in PKT.H
+ * Stuffed into tx_status
+ ******************************************************/
+#define PHB_TX_TGL ((ushort) 0x02)
+
+/*******************************************************
+ * Request intr bit. Set when the queue has gone quiet
+ * and the PHB has requested an interrupt.
+ ******************************************************/
+#define PHB_TX_INTR ((ushort) 0x100)
+
+/*******************************************************
+ * SET if the PHB cannot send any more data down the
+ * Link
+ ******************************************************/
+#define PHB_TX_HANDSHAKE ((ushort) 0x010)
+
+
+#define RUP_SEND_WNDW ((ushort) 0x08) ;
+
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/qbuf.h b/drivers/char/rio/qbuf.h
new file mode 100644
index 000000000000..1fce02f8fcfc
--- /dev/null
+++ b/drivers/char/rio/qbuf.h
@@ -0,0 +1,67 @@
+
+/****************************************************************************
+ ******* *******
+ ******* Q U E U E B U F F E R S T R U C T U R E S
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _qbuf_h
+#define _qbuf_h 1
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_qbuf_h_sccs = "@(#)qbuf.h 1.1" ;
+#endif
+#endif
+
+
+
+#ifdef HOST
+#define PKTS_PER_BUFFER 1
+#else
+#define PKTS_PER_BUFFER (220 / PKT_LENGTH)
+#endif
+
+typedef struct Q_BUF Q_BUF ;
+struct Q_BUF {
+ Q_BUF_ptr next ;
+ Q_BUF_ptr prev ;
+ PKT_ptr buf[PKTS_PER_BUFFER] ;
+ } ;
+
+
+#endif
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rio.h b/drivers/char/rio/rio.h
new file mode 100644
index 000000000000..13a9931958b1
--- /dev/null
+++ b/drivers/char/rio/rio.h
@@ -0,0 +1,294 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 1998 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rio.h
+** SID : 1.3
+** Last Modified : 11/6/98 11:34:13
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)rio.h 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_rio_h__
+#define __rio_rio_h__
+
+#ifdef SCCS_LABELS
+static char *_rio_h_sccs_ = "@(#)rio.h 1.3";
+#endif
+
+/*
+** 30.09.1998 ARG -
+** Introduced driver version and host card type strings
+*/
+#define RIO_DRV_STR "Specialix RIO Driver"
+#define RIO_AT_HOST_STR "ISA"
+#define RIO_PCI_HOST_STR "PCI"
+
+
+/*
+** rio_info_store() commands (arbitary values) :
+*/
+#define RIO_INFO_PUT 0xA4B3C2D1
+#define RIO_INFO_GET 0xF1E2D3C4
+
+
+/*
+** anything that I couldn't cram in somewhere else
+*/
+/*
+#ifndef RIODEBUG
+#define debug
+#else
+#define debug rioprint
+#endif
+*/
+
+
+/*
+** Maximum numbers of things
+*/
+#define RIO_SLOTS 4 /* number of configuration slots */
+#define RIO_HOSTS 4 /* number of hosts that can be found */
+#define PORTS_PER_HOST 128 /* number of ports per host */
+#define LINKS_PER_UNIT 4 /* number of links from a host */
+#define RIO_PORTS (PORTS_PER_HOST * RIO_HOSTS) /* max. no. of ports */
+#define RTAS_PER_HOST (MAX_RUP) /* number of RTAs per host */
+#define PORTS_PER_RTA (PORTS_PER_HOST/RTAS_PER_HOST) /* ports on a rta */
+#define PORTS_PER_MODULE 4 /* number of ports on a plug-in module */
+ /* number of modules on an RTA */
+#define MODULES_PER_RTA (PORTS_PER_RTA/PORTS_PER_MODULE)
+#define MAX_PRODUCT 16 /* numbr of different product codes */
+#define MAX_MODULE_TYPES 16 /* number of different types of module */
+
+#define RIO_CONTROL_DEV 128 /* minor number of host/control device */
+#define RIO_INVALID_MAJOR 0 /* test first host card's major no for validity */
+
+/*
+** number of RTAs that can be bound to a master
+*/
+#define MAX_RTA_BINDINGS (MAX_RUP * RIO_HOSTS)
+
+/*
+** Unit types
+*/
+#define PC_RTA16 0x90000000
+#define PC_RTA8 0xe0000000
+#define TYPE_HOST 0
+#define TYPE_RTA8 1
+#define TYPE_RTA16 2
+
+/*
+** Flag values returned by functions
+*/
+#define RIO_FAIL -1
+#define RIO_SUCCESS 0
+#define COPYFAIL -1 /* copy[in|out] failed */
+
+/*
+** SysPort value for something that hasn't any ports
+*/
+#define NO_PORT 0xFFFFFFFF
+
+/*
+** Unit ID Of all hosts
+*/
+#define HOST_ID 0
+
+/*
+** Break bytes into nybles
+*/
+#define LONYBLE(X) ((X) & 0xF)
+#define HINYBLE(X) (((X)>>4) & 0xF)
+
+/*
+** Flag values passed into some functions
+*/
+#define DONT_SLEEP 0
+#define OK_TO_SLEEP 1
+
+#define DONT_PRINT 1
+#define DO_PRINT 0
+
+#define PRINT_TO_LOG_CONS 0
+#define PRINT_TO_CONS 1
+#define PRINT_TO_LOG 2
+
+/*
+** Timeout has trouble with times of less than 3 ticks...
+*/
+#define MIN_TIMEOUT 3
+
+/*
+** Generally useful constants
+*/
+#define HALF_A_SECOND ((HZ)>>1)
+#define A_SECOND (HZ)
+#define HUNDRED_HZ ((HZ/100)?(HZ/100):1)
+#define FIFTY_HZ ((HZ/50)?(HZ/50):1)
+#define TWENTY_HZ ((HZ/20)?(HZ/20):1)
+#define TEN_HZ ((HZ/10)?(HZ/10):1)
+#define FIVE_HZ ((HZ/5)?(HZ/5):1)
+#define HUNDRED_MS TEN_HZ
+#define FIFTY_MS TWENTY_HZ
+#define TWENTY_MS FIFTY_HZ
+#define TEN_MS HUNDRED_HZ
+#define TWO_SECONDS ((A_SECOND)*2)
+#define FIVE_SECONDS ((A_SECOND)*5)
+#define TEN_SECONDS ((A_SECOND)*10)
+#define FIFTEEN_SECONDS ((A_SECOND)*15)
+#define TWENTY_SECONDS ((A_SECOND)*20)
+#define HALF_A_MINUTE (A_MINUTE>>1)
+#define A_MINUTE (A_SECOND*60)
+#define FIVE_MINUTES (A_MINUTE*5)
+#define QUARTER_HOUR (A_MINUTE*15)
+#define HALF_HOUR (A_MINUTE*30)
+#define HOUR (A_MINUTE*60)
+
+#define SIXTEEN_MEG 0x1000000
+#define ONE_MEG 0x100000
+#define SIXTY_FOUR_K 0x10000
+
+#define RIO_AT_MEM_SIZE SIXTY_FOUR_K
+#define RIO_EISA_MEM_SIZE SIXTY_FOUR_K
+#define RIO_MCA_MEM_SIZE SIXTY_FOUR_K
+
+#define POLL_VECTOR 0x100
+
+#define COOK_WELL 0
+#define COOK_MEDIUM 1
+#define COOK_RAW 2
+
+/*
+** Pointer manipulation stuff
+** RIO_PTR takes hostp->Caddr and the offset into the DP RAM area
+** and produces a UNIX caddr_t (pointer) to the object
+** RIO_OBJ takes hostp->Caddr and a UNIX pointer to an object and
+** returns the offset into the DP RAM area.
+*/
+#define RIO_PTR(C,O) (((caddr_t)(C))+(0xFFFF&(O)))
+#define RIO_OFF(C,O) ((int)(O)-(int)(C))
+
+/*
+** How to convert from various different device number formats:
+** DEV is a dev number, as passed to open, close etc - NOT a minor
+** number!
+**
+** Note: LynxOS only gives us 8 bits for the device minor number,
+** so all this crap here to deal with 'modem' bits etc. is
+** just a load of irrelevant old bunkum!
+** This however does not stop us needing to define a value
+** for RIO_MODEMOFFSET which is required by the 'riomkdev'
+** utility in the New Config Utilities suite.
+*/
+/* 0-511: direct 512-1023: modem */
+#define RIO_MODEMOFFSET 0x200 /* doesn't mean anything */
+#define RIO_MODEM_MASK 0x1FF
+#define RIO_MODEM_BIT 0x200
+#define RIO_UNMODEM(DEV) (MINOR(DEV) & RIO_MODEM_MASK)
+#define RIO_ISMODEM(DEV) (MINOR(DEV) & RIO_MODEM_BIT)
+#define RIO_PORT(DEV,FIRST_MAJ) ( (MAJOR(DEV) - FIRST_MAJ) * PORTS_PER_HOST) \
+ + MINOR(DEV)
+
+#define splrio spltty
+
+#define RIO_IPL 5
+#define RIO_PRI (PZERO+10)
+#define RIO_CLOSE_PRI PZERO-1 /* uninterruptible sleeps for close */
+
+typedef struct DbInf
+{
+ uint Flag;
+ char Name[8];
+} DbInf;
+
+#ifndef TRUE
+#define TRUE (1==1)
+#endif
+#ifndef FALSE
+#define FALSE (!TRUE)
+#endif
+
+#define CSUM(pkt_ptr) (((ushort *)(pkt_ptr))[0] + ((ushort *)(pkt_ptr))[1] + \
+ ((ushort *)(pkt_ptr))[2] + ((ushort *)(pkt_ptr))[3] + \
+ ((ushort *)(pkt_ptr))[4] + ((ushort *)(pkt_ptr))[5] + \
+ ((ushort *)(pkt_ptr))[6] + ((ushort *)(pkt_ptr))[7] + \
+ ((ushort *)(pkt_ptr))[8] + ((ushort *)(pkt_ptr))[9] )
+
+/*
+** This happy little macro copies SIZE bytes of data from FROM to TO
+** quite well. SIZE must be a constant.
+*/
+#define CCOPY( FROM, TO, SIZE ) { *(struct s { char data[SIZE]; } *)(TO) = *(struct s *)(FROM); }
+
+/*
+** increment a buffer pointer modulo the size of the buffer...
+*/
+#define BUMP( P, I ) ((P) = (((P)+(I)) & RIOBufferMask))
+
+#define INIT_PACKET( PK, PP ) \
+{ \
+ *((uint *)PK) = PP->PacketInfo; \
+}
+
+#define RIO_LINK_ENABLE 0x80FF /* FF is a hack, mainly for Mips, to */
+ /* prevent a really stupid race condition. */
+
+#define NOT_INITIALISED 0
+#define INITIALISED 1
+
+#define NOT_POLLING 0
+#define POLLING 1
+
+#define NOT_CHANGED 0
+#define CHANGED 1
+
+#define NOT_INUSE 0
+
+#define DISCONNECT 0
+#define CONNECT 1
+
+
+/*
+** Machine types - these must NOT overlap with product codes 0-15
+*/
+#define RIO_MIPS_R3230 31
+#define RIO_MIPS_R4030 32
+
+#define RIO_IO_UNKNOWN -2
+
+#undef MODERN
+#define ERROR( E ) do { u.u_error = E; return OPENFAIL } while ( 0 )
+
+/* Defines for MPX line discipline routines */
+
+#define DIST_LINESW_OPEN 0x01
+#define DIST_LINESW_CLOSE 0x02
+#define DIST_LINESW_READ 0x04
+#define DIST_LINESW_WRITE 0x08
+#define DIST_LINESW_IOCTL 0x10
+#define DIST_LINESW_INPUT 0x20
+#define DIST_LINESW_OUTPUT 0x40
+#define DIST_LINESW_MDMINT 0x80
+
+#endif /* __rio_h__ */
diff --git a/drivers/char/rio/rio_linux.c b/drivers/char/rio/rio_linux.c
new file mode 100644
index 000000000000..a91ae271cf0a
--- /dev/null
+++ b/drivers/char/rio/rio_linux.c
@@ -0,0 +1,1380 @@
+
+/* rio_linux.c -- Linux driver for the Specialix RIO series cards.
+ *
+ *
+ * (C) 1999 R.E.Wolff@BitWizard.nl
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact support@specialix.co.uk if you require
+ * support. But please read the documentation (rio.txt) first.
+ *
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Revision history:
+ * $Log: rio.c,v $
+ * Revision 1.1 1999/07/11 10:13:54 wolff
+ * Initial revision
+ *
+ * */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kdev_t.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+
+#include <linux/generic_serial.h>
+#include <asm/uaccess.h>
+
+#if BITS_PER_LONG != 32
+# error FIXME: this driver only works on 32-bit platforms
+#endif
+
+#include "linux_compat.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+#include "protsts.h"
+#include "rioboard.h"
+
+
+#include "rio_linux.h"
+
+/* I don't think that this driver can handle more than 512 ports on
+one machine. Specialix specifies max 4 boards in one machine. I don't
+know why. If you want to try anyway you'll have to increase the number
+of boards in rio.h. You'll have to allocate more majors if you need
+more than 512 ports.... */
+
+#ifndef RIO_NORMAL_MAJOR0
+/* This allows overriding on the compiler commandline, or in a "major.h"
+ include or something like that */
+#define RIO_NORMAL_MAJOR0 154
+#define RIO_NORMAL_MAJOR1 156
+#endif
+
+#ifndef PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8
+#define PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8 0x2000
+#endif
+
+#ifndef RIO_WINDOW_LEN
+#define RIO_WINDOW_LEN 0x10000
+#endif
+
+
+/* Configurable options:
+ (Don't be too sure that it'll work if you toggle them) */
+
+/* Am I paranoid or not ? ;-) */
+#undef RIO_PARANOIA_CHECK
+
+
+/* 20 -> 2000 per second. The card should rate-limit interrupts at 1000
+ Hz, but it is user configurable. I don't recommend going above 1000
+ Hz. The interrupt ratelimit might trigger if the interrupt is
+ shared with a very active other device.
+ undef this if you want to disable the check....
+*/
+#define IRQ_RATE_LIMIT 200
+
+#if 0
+/* Not implemented */
+/*
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#define RIO_REPORT_FIFO
+#define RIO_REPORT_OVERRUN
+#endif
+
+
+/* These constants are derived from SCO Source */
+static struct Conf
+RIOConf =
+{
+ /* locator */ "RIO Config here",
+ /* startuptime */ HZ*2, /* how long to wait for card to run */
+ /* slowcook */ 0, /* TRUE -> always use line disc. */
+ /* intrpolltime */ 1, /* The frequency of OUR polls */
+ /* breakinterval */ 25, /* x10 mS XXX: units seem to be 1ms not 10! -- REW*/
+ /* timer */ 10, /* mS */
+ /* RtaLoadBase */ 0x7000,
+ /* HostLoadBase */ 0x7C00,
+ /* XpHz */ 5, /* number of Xprint hits per second */
+ /* XpCps */ 120, /* Xprint characters per second */
+ /* XpOn */ "\033d#", /* start Xprint for a wyse 60 */
+ /* XpOff */ "\024", /* end Xprint for a wyse 60 */
+ /* MaxXpCps */ 2000, /* highest Xprint speed */
+ /* MinXpCps */ 10, /* slowest Xprint speed */
+ /* SpinCmds */ 1, /* non-zero for mega fast boots */
+ /* First Addr */ 0x0A0000, /* First address to look at */
+ /* Last Addr */ 0xFF0000, /* Last address looked at */
+ /* BufferSize */ 1024, /* Bytes per port of buffering */
+ /* LowWater */ 256, /* how much data left before wakeup */
+ /* LineLength */ 80, /* how wide is the console? */
+ /* CmdTimeout */ HZ, /* how long a close command may take */
+};
+
+
+
+
+/* Function prototypes */
+
+static void rio_disable_tx_interrupts (void * ptr);
+static void rio_enable_tx_interrupts (void * ptr);
+static void rio_disable_rx_interrupts (void * ptr);
+static void rio_enable_rx_interrupts (void * ptr);
+static int rio_get_CD (void * ptr);
+static void rio_shutdown_port (void * ptr);
+static int rio_set_real_termios (void *ptr);
+static void rio_hungup (void *ptr);
+static void rio_close (void *ptr);
+static int rio_chars_in_buffer (void * ptr);
+static int rio_fw_ioctl (struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+static int rio_init_drivers(void);
+
+static void my_hd (void *addr, int len);
+
+static struct tty_driver *rio_driver, *rio_driver2;
+
+/* The name "p" is a bit non-descript. But that's what the rio-lynxos
+sources use all over the place. */
+struct rio_info *p;
+
+int rio_debug;
+
+
+/* You can have the driver poll your card.
+ - Set rio_poll to 1 to poll every timer tick (10ms on Intel).
+ This is used when the card cannot use an interrupt for some reason.
+*/
+static int rio_poll = 1;
+
+
+/* These are the only open spaces in my computer. Yours may have more
+ or less.... */
+static int rio_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000};
+
+#define NR_RIO_ADDRS (sizeof(rio_probe_addrs)/sizeof (int))
+
+
+/* Set the mask to all-ones. This alas, only supports 32 interrupts.
+ Some architectures may need more. -- Changed to LONG to
+ support up to 64 bits on 64bit architectures. -- REW 20/06/99 */
+long rio_irqmask = -1;
+
+MODULE_AUTHOR("Rogier Wolff <R.E.Wolff@bitwizard.nl>, Patrick van de Lageweg <patrick@bitwizard.nl>");
+MODULE_DESCRIPTION("RIO driver");
+MODULE_LICENSE("GPL");
+module_param(rio_poll, int, 0);
+module_param(rio_debug, int, 0644);
+module_param(rio_irqmask, long, 0);
+
+static struct real_driver rio_real_driver = {
+ rio_disable_tx_interrupts,
+ rio_enable_tx_interrupts,
+ rio_disable_rx_interrupts,
+ rio_enable_rx_interrupts,
+ rio_get_CD,
+ rio_shutdown_port,
+ rio_set_real_termios,
+ rio_chars_in_buffer,
+ rio_close,
+ rio_hungup,
+ NULL
+};
+
+/*
+ * Firmware loader driver specific routines
+ *
+ */
+
+static struct file_operations rio_fw_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = rio_fw_ioctl,
+};
+
+static struct miscdevice rio_fw_device = {
+ RIOCTL_MISC_MINOR, "rioctl", &rio_fw_fops
+};
+
+
+
+
+
+#ifdef RIO_PARANOIA_CHECK
+
+/* This doesn't work. Who's paranoid around here? Not me! */
+
+static inline int rio_paranoia_check(struct rio_port const * port,
+ char *name, const char *routine)
+{
+
+ static const char *badmagic =
+ KERN_ERR "rio: Warning: bad rio port magic number for device %s in %s\n";
+ static const char *badinfo =
+ KERN_ERR "rio: Warning: null rio port for device %s in %s\n";
+
+ if (!port) {
+ printk (badinfo, name, routine);
+ return 1;
+ }
+ if (port->magic != RIO_MAGIC) {
+ printk (badmagic, name, routine);
+ return 1;
+ }
+
+ return 0;
+}
+#else
+#define rio_paranoia_check(a,b,c) 0
+#endif
+
+
+#ifdef DEBUG
+static void my_hd (void *ad, int len)
+{
+ int i, j, ch;
+ unsigned char *addr = ad;
+
+ for (i=0;i<len;i+=16) {
+ rio_dprintk (RIO_DEBUG_PARAM, "%08x ", (int) addr+i);
+ for (j=0;j<16;j++) {
+ rio_dprintk (RIO_DEBUG_PARAM, "%02x %s", addr[j+i], (j==7)?" ":"");
+ }
+ for (j=0;j<16;j++) {
+ ch = addr[j+i];
+ rio_dprintk (RIO_DEBUG_PARAM, "%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+ }
+ rio_dprintk (RIO_DEBUG_PARAM, "\n");
+ }
+}
+#else
+#define my_hd(ad,len) do{/* nothing*/ } while (0)
+#endif
+
+
+/* Delay a number of jiffies, allowing a signal to interrupt */
+int RIODelay (struct Port *PortP, int njiffies)
+{
+ func_enter ();
+
+ rio_dprintk (RIO_DEBUG_DELAY, "delaying %d jiffies\n", njiffies);
+ msleep_interruptible(jiffies_to_msecs(njiffies));
+ func_exit();
+
+ if (signal_pending(current))
+ return RIO_FAIL;
+ else
+ return !RIO_FAIL;
+}
+
+
+/* Delay a number of jiffies, disallowing a signal to interrupt */
+int RIODelay_ni (struct Port *PortP, int njiffies)
+{
+ func_enter ();
+
+ rio_dprintk (RIO_DEBUG_DELAY, "delaying %d jiffies (ni)\n", njiffies);
+ msleep(jiffies_to_msecs(njiffies));
+ func_exit();
+ return !RIO_FAIL;
+}
+
+
+int rio_minor(struct tty_struct *tty)
+{
+ return tty->index + (tty->driver == rio_driver) ? 0 : 256;
+}
+
+
+int rio_ismodem(struct tty_struct *tty)
+{
+ return 1;
+}
+
+
+void rio_udelay (int usecs)
+{
+ udelay (usecs);
+}
+
+static int rio_set_real_termios (void *ptr)
+{
+ int rv, modem;
+ struct tty_struct *tty;
+ func_enter();
+
+ tty = ((struct Port *)ptr)->gs.tty;
+
+ modem = rio_ismodem(tty);
+
+ rv = RIOParam( (struct Port *) ptr, CONFIG, modem, 1);
+
+ func_exit ();
+
+ return rv;
+}
+
+
+static void rio_reset_interrupt (struct Host *HostP)
+{
+ func_enter();
+
+ switch( HostP->Type ) {
+ case RIO_AT:
+ case RIO_MCA:
+ case RIO_PCI:
+ WBYTE(HostP->ResetInt , 0xff);
+ }
+
+ func_exit();
+}
+
+
+static irqreturn_t rio_interrupt (int irq, void *ptr, struct pt_regs *regs)
+{
+ struct Host *HostP;
+ func_enter ();
+
+ HostP = (struct Host*)ptr; /* &p->RIOHosts[(long)ptr]; */
+ rio_dprintk (RIO_DEBUG_IFLOW, "rio: enter rio_interrupt (%d/%d)\n",
+ irq, HostP->Ivec);
+
+ /* AAargh! The order in which to do these things is essential and
+ not trivial.
+
+ - Rate limit goes before "recursive". Otherwise a series of
+ recursive calls will hang the machine in the interrupt routine.
+
+ - hardware twiddling goes before "recursive". Otherwise when we
+ poll the card, and a recursive interrupt happens, we won't
+ ack the card, so it might keep on interrupting us. (especially
+ level sensitive interrupt systems like PCI).
+
+ - Rate limit goes before hardware twiddling. Otherwise we won't
+ catch a card that has gone bonkers.
+
+ - The "initialized" test goes after the hardware twiddling. Otherwise
+ the card will stick us in the interrupt routine again.
+
+ - The initialized test goes before recursive.
+ */
+
+
+
+#ifdef IRQ_RATE_LIMIT
+ /* Aaargh! I'm ashamed. This costs more lines-of-code than the
+ actual interrupt routine!. (Well, used to when I wrote that comment) */
+ {
+ static int lastjif;
+ static int nintr=0;
+
+ if (lastjif == jiffies) {
+ if (++nintr > IRQ_RATE_LIMIT) {
+ free_irq (HostP->Ivec, ptr);
+ printk (KERN_ERR "rio: Too many interrupts. Turning off interrupt %d.\n",
+ HostP->Ivec);
+ }
+ } else {
+ lastjif = jiffies;
+ nintr = 0;
+ }
+ }
+#endif
+ rio_dprintk (RIO_DEBUG_IFLOW, "rio: We've have noticed the interrupt\n");
+ if (HostP->Ivec == irq) {
+ /* Tell the card we've noticed the interrupt. */
+ rio_reset_interrupt (HostP);
+ }
+
+ if ((HostP->Flags & RUN_STATE) != RC_RUNNING)
+ return IRQ_HANDLED;
+
+ if (test_and_set_bit (RIO_BOARD_INTR_LOCK, &HostP->locks)) {
+ printk (KERN_ERR "Recursive interrupt! (host %d/irq%d)\n",
+ (int) ptr, HostP->Ivec);
+ return IRQ_HANDLED;
+ }
+
+ RIOServiceHost(p, HostP, irq);
+
+ rio_dprintk ( RIO_DEBUG_IFLOW, "riointr() doing host %d type %d\n",
+ (int) ptr, HostP->Type);
+
+ clear_bit (RIO_BOARD_INTR_LOCK, &HostP->locks);
+ rio_dprintk (RIO_DEBUG_IFLOW, "rio: exit rio_interrupt (%d/%d)\n",
+ irq, HostP->Ivec);
+ func_exit ();
+ return IRQ_HANDLED;
+}
+
+
+static void rio_pollfunc (unsigned long data)
+{
+ func_enter ();
+
+ rio_interrupt (0, &p->RIOHosts[data], NULL);
+ p->RIOHosts[data].timer.expires = jiffies + rio_poll;
+ add_timer (&p->RIOHosts[data].timer);
+
+ func_exit ();
+}
+
+
+/* ********************************************************************** *
+ * Here are the routines that actually *
+ * interface with the generic_serial driver *
+ * ********************************************************************** */
+
+/* Ehhm. I don't know how to fiddle with interrupts on the Specialix
+ cards. .... Hmm. Ok I figured it out. You don't. -- REW */
+
+static void rio_disable_tx_interrupts (void * ptr)
+{
+ func_enter();
+
+ /* port->gs.flags &= ~GS_TX_INTEN; */
+
+ func_exit();
+}
+
+
+static void rio_enable_tx_interrupts (void * ptr)
+{
+ struct Port *PortP = ptr;
+ /* int hn; */
+
+ func_enter();
+
+ /* hn = PortP->HostP - p->RIOHosts;
+
+ rio_dprintk (RIO_DEBUG_TTY, "Pushing host %d\n", hn);
+ rio_interrupt (-1,(void *) hn, NULL); */
+
+ RIOTxEnable((char *) PortP);
+
+ /*
+ * In general we cannot count on "tx empty" interrupts, although
+ * the interrupt routine seems to be able to tell the difference.
+ */
+ PortP->gs.flags &= ~GS_TX_INTEN;
+
+ func_exit();
+}
+
+
+static void rio_disable_rx_interrupts (void * ptr)
+{
+ func_enter();
+ func_exit();
+}
+
+static void rio_enable_rx_interrupts (void * ptr)
+{
+ /* struct rio_port *port = ptr; */
+ func_enter();
+ func_exit();
+}
+
+
+/* Jeez. Isn't this simple? */
+static int rio_get_CD (void * ptr)
+{
+ struct Port *PortP = ptr;
+ int rv;
+
+ func_enter();
+ rv = (PortP->ModemState & MSVR1_CD) != 0;
+
+ rio_dprintk (RIO_DEBUG_INIT, "Getting CD status: %d\n", rv);
+
+ func_exit();
+ return rv;
+}
+
+
+/* Jeez. Isn't this simple? Actually, we can sync with the actual port
+ by just pushing stuff into the queue going to the port... */
+static int rio_chars_in_buffer (void * ptr)
+{
+ func_enter();
+
+ func_exit();
+ return 0;
+}
+
+
+/* Nothing special here... */
+static void rio_shutdown_port (void * ptr)
+{
+ struct Port *PortP;
+
+ func_enter();
+
+ PortP = (struct Port *)ptr;
+ PortP->gs.tty = NULL;
+#if 0
+ port->gs.flags &= ~ GS_ACTIVE;
+ if (!port->gs.tty) {
+ rio_dprintk (RIO_DBUG_TTY, "No tty.\n");
+ return;
+ }
+ if (!port->gs.tty->termios) {
+ rio_dprintk (RIO_DEBUG_TTY, "No termios.\n");
+ return;
+ }
+ if (port->gs.tty->termios->c_cflag & HUPCL) {
+ rio_setsignals (port, 0, 0);
+ }
+#endif
+
+ func_exit();
+}
+
+
+/* I haven't the foggiest why the decrement use count has to happen
+ here. The whole linux serial drivers stuff needs to be redesigned.
+ My guess is that this is a hack to minimize the impact of a bug
+ elsewhere. Thinking about it some more. (try it sometime) Try
+ running minicom on a serial port that is driven by a modularized
+ driver. Have the modem hangup. Then remove the driver module. Then
+ exit minicom. I expect an "oops". -- REW */
+static void rio_hungup (void *ptr)
+{
+ struct Port *PortP;
+
+ func_enter();
+
+ PortP = (struct Port *)ptr;
+ PortP->gs.tty = NULL;
+
+ func_exit ();
+}
+
+
+/* The standard serial_close would become shorter if you'd wrap it like
+ this.
+ rs_close (...){save_flags;cli;real_close();dec_use_count;restore_flags;}
+ */
+static void rio_close (void *ptr)
+{
+ struct Port *PortP;
+
+ func_enter ();
+
+ PortP = (struct Port *)ptr;
+
+ riotclose (ptr);
+
+ if(PortP->gs.count) {
+ printk (KERN_ERR "WARNING port count:%d\n", PortP->gs.count);
+ PortP->gs.count = 0;
+ }
+
+ PortP->gs.tty = NULL;
+ func_exit ();
+}
+
+
+
+static int rio_fw_ioctl (struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+ func_enter();
+
+ /* The "dev" argument isn't used. */
+ rc = riocontrol (p, 0, cmd, (void *)arg, capable(CAP_SYS_ADMIN));
+
+ func_exit ();
+ return rc;
+}
+
+extern int RIOShortCommand(struct rio_info *p, struct Port *PortP,
+ int command, int len, int arg);
+
+static int rio_ioctl (struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc;
+ struct Port *PortP;
+ int ival;
+
+ func_enter();
+
+ PortP = (struct Port *)tty->driver_data;
+
+ rc = 0;
+ switch (cmd) {
+#if 0
+ case TIOCGSOFTCAR:
+ rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+ (unsigned int *) arg);
+ break;
+#endif
+ case TIOCSSOFTCAR:
+ if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+ tty->termios->c_cflag =
+ (tty->termios->c_cflag & ~CLOCAL) |
+ (ival ? CLOCAL : 0);
+ }
+ break;
+ case TIOCGSERIAL:
+ rc = -EFAULT;
+ if (access_ok(VERIFY_WRITE, (void *) arg,
+ sizeof(struct serial_struct)))
+ rc = gs_getserial(&PortP->gs, (struct serial_struct *) arg);
+ break;
+ case TCSBRK:
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_TTY, "BREAK on deleted RTA\n");
+ rc = -EIO;
+ } else {
+ if (RIOShortCommand(p, PortP, SBREAK, 2, 250) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+ rc = -EIO;
+ }
+ }
+ break;
+ case TCSBRKP:
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_TTY, "BREAK on deleted RTA\n");
+ rc = -EIO;
+ } else {
+ int l;
+ l = arg?arg*100:250;
+ if (l > 255) l = 255;
+ if (RIOShortCommand(p, PortP, SBREAK, 2, arg?arg*100:250) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+ rc = -EIO;
+ }
+ }
+ break;
+ case TIOCSSERIAL:
+ rc = -EFAULT;
+ if (access_ok(VERIFY_READ, (void *) arg,
+ sizeof(struct serial_struct)))
+ rc = gs_setserial(&PortP->gs, (struct serial_struct *) arg);
+ break;
+#if 0
+ /*
+ * note: these IOCTLs no longer reach here. Use
+ * tiocmset/tiocmget driver methods instead. The
+ * #if 0 disablement predates this comment.
+ */
+ case TIOCMGET:
+ rc = -EFAULT;
+ if (access_ok(VERIFY_WRITE, (void *) arg,
+ sizeof(unsigned int))) {
+ rc = 0;
+ ival = rio_getsignals(port);
+ put_user(ival, (unsigned int *) arg);
+ }
+ break;
+ case TIOCMBIS:
+ if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+ rio_setsignals(port, ((ival & TIOCM_DTR) ? 1 : -1),
+ ((ival & TIOCM_RTS) ? 1 : -1));
+ }
+ break;
+ case TIOCMBIC:
+ if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+ rio_setsignals(port, ((ival & TIOCM_DTR) ? 0 : -1),
+ ((ival & TIOCM_RTS) ? 0 : -1));
+ }
+ break;
+ case TIOCMSET:
+ if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+ rio_setsignals(port, ((ival & TIOCM_DTR) ? 1 : 0),
+ ((ival & TIOCM_RTS) ? 1 : 0));
+ }
+ break;
+#endif
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+ func_exit();
+ return rc;
+}
+
+
+/* The throttle/unthrottle scheme for the Specialix card is different
+ * from other drivers and deserves some explanation.
+ * The Specialix hardware takes care of XON/XOFF
+ * and CTS/RTS flow control itself. This means that all we have to
+ * do when signalled by the upper tty layer to throttle/unthrottle is
+ * to make a note of it here. When we come to read characters from the
+ * rx buffers on the card (rio_receive_chars()) we look to see if the
+ * upper layer can accept more (as noted here in rio_rx_throt[]).
+ * If it can't we simply don't remove chars from the cards buffer.
+ * When the tty layer can accept chars, we again note that here and when
+ * rio_receive_chars() is called it will remove them from the cards buffer.
+ * The card will notice that a ports buffer has drained below some low
+ * water mark and will unflow control the line itself, using whatever
+ * flow control scheme is in use for that port. -- Simon Allen
+ */
+
+static void rio_throttle (struct tty_struct * tty)
+{
+ struct Port *port = (struct Port *)tty->driver_data;
+
+ func_enter();
+ /* If the port is using any type of input flow
+ * control then throttle the port.
+ */
+
+ if((tty->termios->c_cflag & CRTSCTS) || (I_IXOFF(tty)) ) {
+ port->State |= RIO_THROTTLE_RX;
+ }
+
+ func_exit();
+}
+
+
+static void rio_unthrottle (struct tty_struct * tty)
+{
+ struct Port *port = (struct Port *)tty->driver_data;
+
+ func_enter();
+ /* Always unthrottle even if flow control is not enabled on
+ * this port in case we disabled flow control while the port
+ * was throttled
+ */
+
+ port->State &= ~RIO_THROTTLE_RX;
+
+ func_exit();
+ return;
+}
+
+
+
+
+
+/* ********************************************************************** *
+ * Here are the initialization routines. *
+ * ********************************************************************** */
+
+
+static struct vpd_prom *get_VPD_PROM (struct Host *hp)
+{
+ static struct vpd_prom vpdp;
+ char *p;
+ int i;
+
+ func_enter();
+ rio_dprintk (RIO_DEBUG_PROBE, "Going to verify vpd prom at %p.\n",
+ hp->Caddr + RIO_VPD_ROM);
+
+ p = (char *) &vpdp;
+ for (i=0;i< sizeof (struct vpd_prom);i++)
+ *p++ = readb (hp->Caddr+RIO_VPD_ROM + i*2);
+ /* read_rio_byte (hp, RIO_VPD_ROM + i*2); */
+
+ /* Terminate the identifier string.
+ *** requires one extra byte in struct vpd_prom *** */
+ *p++=0;
+
+ if (rio_debug & RIO_DEBUG_PROBE)
+ my_hd ((char *)&vpdp, 0x20);
+
+ func_exit();
+
+ return &vpdp;
+}
+
+static struct tty_operations rio_ops = {
+ .open = riotopen,
+ .close = gs_close,
+ .write = gs_write,
+ .put_char = gs_put_char,
+ .flush_chars = gs_flush_chars,
+ .write_room = gs_write_room,
+ .chars_in_buffer = gs_chars_in_buffer,
+ .flush_buffer = gs_flush_buffer,
+ .ioctl = rio_ioctl,
+ .throttle = rio_throttle,
+ .unthrottle = rio_unthrottle,
+ .set_termios = gs_set_termios,
+ .stop = gs_stop,
+ .start = gs_start,
+ .hangup = gs_hangup,
+};
+
+static int rio_init_drivers(void)
+{
+ int error = -ENOMEM;
+
+ rio_driver = alloc_tty_driver(256);
+ if (!rio_driver)
+ goto out;
+ rio_driver2 = alloc_tty_driver(256);
+ if (!rio_driver2)
+ goto out1;
+
+ func_enter();
+
+ rio_driver->owner = THIS_MODULE;
+ rio_driver->driver_name = "specialix_rio";
+ rio_driver->name = "ttySR";
+ rio_driver->major = RIO_NORMAL_MAJOR0;
+ rio_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ rio_driver->subtype = SERIAL_TYPE_NORMAL;
+ rio_driver->init_termios = tty_std_termios;
+ rio_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ rio_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(rio_driver, &rio_ops);
+
+ rio_driver2->owner = THIS_MODULE;
+ rio_driver2->driver_name = "specialix_rio";
+ rio_driver2->name = "ttySR";
+ rio_driver2->major = RIO_NORMAL_MAJOR1;
+ rio_driver2->type = TTY_DRIVER_TYPE_SERIAL;
+ rio_driver2->subtype = SERIAL_TYPE_NORMAL;
+ rio_driver2->init_termios = tty_std_termios;
+ rio_driver2->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ rio_driver2->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(rio_driver2, &rio_ops);
+
+ rio_dprintk (RIO_DEBUG_INIT, "set_termios = %p\n", gs_set_termios);
+
+ if ((error = tty_register_driver(rio_driver)))
+ goto out2;
+ if ((error = tty_register_driver(rio_driver2)))
+ goto out3;
+ func_exit();
+ return 0;
+out3:
+ tty_unregister_driver(rio_driver);
+out2:
+ put_tty_driver(rio_driver2);
+out1:
+ put_tty_driver(rio_driver);
+out:
+ printk(KERN_ERR "rio: Couldn't register a rio driver, error = %d\n",
+ error);
+ return 1;
+}
+
+
+static void * ckmalloc (int size)
+{
+ void *p;
+
+ p = kmalloc(size, GFP_KERNEL);
+ if (p)
+ memset(p, 0, size);
+ return p;
+}
+
+
+
+static int rio_init_datastructures (void)
+{
+ int i;
+ struct Port *port;
+ func_enter();
+
+ /* Many drivers statically allocate the maximum number of ports
+ There is no reason not to allocate them dynamically. Is there? -- REW */
+ /* However, the RIO driver allows users to configure their first
+ RTA as the ports numbered 504-511. We therefore need to allocate
+ the whole range. :-( -- REW */
+
+#define RI_SZ sizeof(struct rio_info)
+#define HOST_SZ sizeof(struct Host)
+#define PORT_SZ sizeof(struct Port *)
+#define TMIO_SZ sizeof(struct termios *)
+ rio_dprintk (RIO_DEBUG_INIT, "getting : %d %d %d %d %d bytes\n",
+ RI_SZ,
+ RIO_HOSTS * HOST_SZ,
+ RIO_PORTS * PORT_SZ,
+ RIO_PORTS * TMIO_SZ,
+ RIO_PORTS * TMIO_SZ);
+
+ if (!(p = ckmalloc ( RI_SZ))) goto free0;
+ if (!(p->RIOHosts = ckmalloc (RIO_HOSTS * HOST_SZ))) goto free1;
+ if (!(p->RIOPortp = ckmalloc (RIO_PORTS * PORT_SZ))) goto free2;
+ p->RIOConf = RIOConf;
+ rio_dprintk (RIO_DEBUG_INIT, "Got : %p %p %p\n",
+ p, p->RIOHosts, p->RIOPortp);
+
+#if 1
+ for (i = 0; i < RIO_PORTS; i++) {
+ port = p->RIOPortp[i] = ckmalloc (sizeof (struct Port));
+ if (!port) {
+ goto free6;
+ }
+ rio_dprintk (RIO_DEBUG_INIT, "initing port %d (%d)\n", i, port->Mapped);
+ port->PortNum = i;
+ port->gs.magic = RIO_MAGIC;
+ port->gs.close_delay = HZ/2;
+ port->gs.closing_wait = 30 * HZ;
+ port->gs.rd = &rio_real_driver;
+ spin_lock_init(&port->portSem);
+ /*
+ * Initializing wait queue
+ */
+ init_waitqueue_head(&port->gs.open_wait);
+ init_waitqueue_head(&port->gs.close_wait);
+ }
+#else
+ /* We could postpone initializing them to when they are configured. */
+#endif
+
+
+
+ if (rio_debug & RIO_DEBUG_INIT) {
+ my_hd (&rio_real_driver, sizeof (rio_real_driver));
+ }
+
+
+ func_exit();
+ return 0;
+
+ free6:for (i--;i>=0;i--)
+ kfree (p->RIOPortp[i]);
+/*free5:
+ free4:
+ free3:*/kfree (p->RIOPortp);
+ free2:kfree (p->RIOHosts);
+ free1:
+ rio_dprintk (RIO_DEBUG_INIT, "Not enough memory! %p %p %p\n",
+ p, p->RIOHosts, p->RIOPortp);
+ kfree(p);
+ free0:
+ return -ENOMEM;
+}
+
+static void __exit rio_release_drivers(void)
+{
+ func_enter();
+ tty_unregister_driver(rio_driver2);
+ tty_unregister_driver(rio_driver);
+ put_tty_driver(rio_driver2);
+ put_tty_driver(rio_driver);
+ func_exit();
+}
+
+
+#ifdef CONFIG_PCI
+ /* This was written for SX, but applies to RIO too...
+ (including bugs....)
+
+ There is another bit besides Bit 17. Turning that bit off
+ (on boards shipped with the fix in the eeprom) results in a
+ hang on the next access to the card.
+ */
+
+ /********************************************************
+ * Setting bit 17 in the CNTRL register of the PLX 9050 *
+ * chip forces a retry on writes while a read is pending.*
+ * This is to prevent the card locking up on Intel Xeon *
+ * multiprocessor systems with the NX chipset. -- NV *
+ ********************************************************/
+
+/* Newer cards are produced with this bit set from the configuration
+ EEprom. As the bit is read/write for the CPU, we can fix it here,
+ if we detect that it isn't set correctly. -- REW */
+
+static void fix_rio_pci (struct pci_dev *pdev)
+{
+ unsigned int hwbase;
+ unsigned long rebase;
+ unsigned int t;
+
+#define CNTRL_REG_OFFSET 0x50
+#define CNTRL_REG_GOODVALUE 0x18260000
+
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &hwbase);
+ hwbase &= PCI_BASE_ADDRESS_MEM_MASK;
+ rebase = (ulong) ioremap(hwbase, 0x80);
+ t = readl (rebase + CNTRL_REG_OFFSET);
+ if (t != CNTRL_REG_GOODVALUE) {
+ printk (KERN_DEBUG "rio: performing cntrl reg fix: %08x -> %08x\n",
+ t, CNTRL_REG_GOODVALUE);
+ writel (CNTRL_REG_GOODVALUE, rebase + CNTRL_REG_OFFSET);
+ }
+ iounmap((char*) rebase);
+}
+#endif
+
+
+static int __init rio_init(void)
+{
+ int found = 0;
+ int i;
+ struct Host *hp;
+ int retval;
+ struct vpd_prom *vpdp;
+ int okboard;
+
+#ifdef CONFIG_PCI
+ struct pci_dev *pdev = NULL;
+ unsigned int tint;
+ unsigned short tshort;
+#endif
+
+ func_enter();
+ rio_dprintk (RIO_DEBUG_INIT, "Initing rio module... (rio_debug=%d)\n",
+ rio_debug);
+
+ if (abs ((long) (&rio_debug) - rio_debug) < 0x10000) {
+ printk (KERN_WARNING "rio: rio_debug is an address, instead of a value. "
+ "Assuming -1. Was %x/%p.\n", rio_debug, &rio_debug);
+ rio_debug=-1;
+ }
+
+ if (misc_register(&rio_fw_device) < 0) {
+ printk(KERN_ERR "RIO: Unable to register firmware loader driver.\n");
+ return -EIO;
+ }
+
+ retval = rio_init_datastructures ();
+ if (retval < 0) {
+ misc_deregister(&rio_fw_device);
+ return retval;
+ }
+
+#ifdef CONFIG_PCI
+ /* First look for the JET devices: */
+ while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX,
+ PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8,
+ pdev))) {
+ if (pci_enable_device(pdev)) continue;
+
+ /* Specialix has a whole bunch of cards with
+ 0x2000 as the device ID. They say its because
+ the standard requires it. Stupid standard. */
+ /* It seems that reading a word doesn't work reliably on 2.0.
+ Also, reading a non-aligned dword doesn't work. So we read the
+ whole dword at 0x2c and extract the word at 0x2e (SUBSYSTEM_ID)
+ ourselves */
+ /* I don't know why the define doesn't work, constant 0x2c does --REW */
+ pci_read_config_dword (pdev, 0x2c, &tint);
+ tshort = (tint >> 16) & 0xffff;
+ rio_dprintk (RIO_DEBUG_PROBE, "Got a specialix card: %x.\n", tint);
+ if (tshort != 0x0100) {
+ rio_dprintk (RIO_DEBUG_PROBE, "But it's not a RIO card (%d)...\n",
+ tshort);
+ continue;
+ }
+ rio_dprintk (RIO_DEBUG_PROBE, "cp1\n");
+
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_2, &tint);
+
+ hp = &p->RIOHosts[p->RIONumHosts];
+ hp->PaddrP = tint & PCI_BASE_ADDRESS_MEM_MASK;
+ hp->Ivec = pdev->irq;
+ if (((1 << hp->Ivec) & rio_irqmask) == 0)
+ hp->Ivec = 0;
+ hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+ hp->CardP = (struct DpRam *) hp->Caddr;
+ hp->Type = RIO_PCI;
+ hp->Copy = rio_pcicopy;
+ hp->Mode = RIO_PCI_BOOT_FROM_RAM;
+ spin_lock_init(&hp->HostLock);
+ rio_reset_interrupt (hp);
+ rio_start_card_running (hp);
+
+ rio_dprintk (RIO_DEBUG_PROBE, "Going to test it (%p/%p).\n",
+ (void *)p->RIOHosts[p->RIONumHosts].PaddrP,
+ p->RIOHosts[p->RIONumHosts].Caddr);
+ if (RIOBoardTest( p->RIOHosts[p->RIONumHosts].PaddrP,
+ p->RIOHosts[p->RIONumHosts].Caddr,
+ RIO_PCI, 0 ) == RIO_SUCCESS) {
+ rio_dprintk (RIO_DEBUG_INIT, "Done RIOBoardTest\n");
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+ p->RIOHosts[p->RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0]) &0xFF)<< 0)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1]) &0xFF)<< 8)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2]) &0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3]) &0xFF)<<24);
+ rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+ p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+ fix_rio_pci (pdev);
+ p->RIOLastPCISearch = RIO_SUCCESS;
+ p->RIONumHosts++;
+ found++;
+ } else {
+ iounmap((char*) (p->RIOHosts[p->RIONumHosts].Caddr));
+ }
+ }
+
+ /* Then look for the older PCI card.... : */
+
+ /* These older PCI cards have problems (only byte-mode access is
+ supported), which makes them a bit awkward to support.
+ They also have problems sharing interrupts. Be careful.
+ (The driver now refuses to share interrupts for these
+ cards. This should be sufficient).
+ */
+
+ /* Then look for the older RIO/PCI devices: */
+ while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX,
+ PCI_DEVICE_ID_SPECIALIX_RIO,
+ pdev))) {
+ if (pci_enable_device(pdev)) continue;
+
+#ifdef CONFIG_RIO_OLDPCI
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &tint);
+
+ hp = &p->RIOHosts[p->RIONumHosts];
+ hp->PaddrP = tint & PCI_BASE_ADDRESS_MEM_MASK;
+ hp->Ivec = pdev->irq;
+ if (((1 << hp->Ivec) & rio_irqmask) == 0)
+ hp->Ivec = 0;
+ hp->Ivec |= 0x8000; /* Mark as non-sharable */
+ hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+ hp->CardP = (struct DpRam *) hp->Caddr;
+ hp->Type = RIO_PCI;
+ hp->Copy = rio_pcicopy;
+ hp->Mode = RIO_PCI_BOOT_FROM_RAM;
+ spin_lock_init(&hp->HostLock);
+
+ rio_dprintk (RIO_DEBUG_PROBE, "Ivec: %x\n", hp->Ivec);
+ rio_dprintk (RIO_DEBUG_PROBE, "Mode: %x\n", hp->Mode);
+
+ rio_reset_interrupt (hp);
+ rio_start_card_running (hp);
+ rio_dprintk (RIO_DEBUG_PROBE, "Going to test it (%p/%p).\n",
+ (void *)p->RIOHosts[p->RIONumHosts].PaddrP,
+ p->RIOHosts[p->RIONumHosts].Caddr);
+ if (RIOBoardTest( p->RIOHosts[p->RIONumHosts].PaddrP,
+ p->RIOHosts[p->RIONumHosts].Caddr,
+ RIO_PCI, 0 ) == RIO_SUCCESS) {
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+ p->RIOHosts[p->RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0]) &0xFF)<< 0)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1]) &0xFF)<< 8)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2]) &0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3]) &0xFF)<<24);
+ rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+ p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+ p->RIOLastPCISearch = RIO_SUCCESS;
+ p->RIONumHosts++;
+ found++;
+ } else {
+ iounmap((char*) (p->RIOHosts[p->RIONumHosts].Caddr));
+ }
+#else
+ printk (KERN_ERR "Found an older RIO PCI card, but the driver is not "
+ "compiled to support it.\n");
+#endif
+ }
+#endif /* PCI */
+
+ /* Now probe for ISA cards... */
+ for (i=0;i<NR_RIO_ADDRS;i++) {
+ hp = &p->RIOHosts[p->RIONumHosts];
+ hp->PaddrP = rio_probe_addrs[i];
+ /* There was something about the IRQs of these cards. 'Forget what.--REW */
+ hp->Ivec = 0;
+ hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+ hp->CardP = (struct DpRam *) hp->Caddr;
+ hp->Type = RIO_AT;
+ hp->Copy = rio_pcicopy; /* AT card PCI???? - PVDL
+ * -- YES! this is now a normal copy. Only the
+ * old PCI card uses the special PCI copy.
+ * Moreover, the ISA card will work with the
+ * special PCI copy anyway. -- REW */
+ hp->Mode = 0;
+ spin_lock_init(&hp->HostLock);
+
+ vpdp = get_VPD_PROM (hp);
+ rio_dprintk (RIO_DEBUG_PROBE, "Got VPD ROM\n");
+ okboard = 0;
+ if ((strncmp (vpdp->identifier, RIO_ISA_IDENT, 16) == 0) ||
+ (strncmp (vpdp->identifier, RIO_ISA2_IDENT, 16) == 0) ||
+ (strncmp (vpdp->identifier, RIO_ISA3_IDENT, 16) == 0)) {
+ /* Board is present... */
+ if (RIOBoardTest (hp->PaddrP,
+ hp->Caddr, RIO_AT, 0) == RIO_SUCCESS) {
+ /* ... and feeling fine!!!! */
+ rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+ p->RIOHosts[p->RIONumHosts].UniqueNum);
+ if (RIOAssignAT(p, hp->PaddrP, hp->Caddr, 0)) {
+ rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, host%d uniqid = %x.\n",
+ p->RIONumHosts,
+ p->RIOHosts[p->RIONumHosts-1].UniqueNum);
+ okboard++;
+ found++;
+ }
+ }
+
+ if (!okboard)
+ iounmap ((char*) (hp->Caddr));
+ }
+ }
+
+
+ for (i=0;i<p->RIONumHosts;i++) {
+ hp = &p->RIOHosts[i];
+ if (hp->Ivec) {
+ int mode = SA_SHIRQ;
+ if (hp->Ivec & 0x8000) {mode = 0; hp->Ivec &= 0x7fff;}
+ rio_dprintk (RIO_DEBUG_INIT, "Requesting interrupt hp: %p rio_interrupt: %d Mode: %x\n", hp,hp->Ivec, hp->Mode);
+ retval = request_irq (hp->Ivec, rio_interrupt, mode, "rio", hp);
+ rio_dprintk (RIO_DEBUG_INIT, "Return value from request_irq: %d\n", retval);
+ if (retval) {
+ printk(KERN_ERR "rio: Cannot allocate irq %d.\n", hp->Ivec);
+ hp->Ivec = 0;
+ }
+ rio_dprintk (RIO_DEBUG_INIT, "Got irq %d.\n", hp->Ivec);
+ if (hp->Ivec != 0){
+ rio_dprintk (RIO_DEBUG_INIT, "Enabling interrupts on rio card.\n");
+ hp->Mode |= RIO_PCI_INT_ENABLE;
+ } else
+ hp->Mode &= !RIO_PCI_INT_ENABLE;
+ rio_dprintk (RIO_DEBUG_INIT, "New Mode: %x\n", hp->Mode);
+ rio_start_card_running (hp);
+ }
+ /* Init the timer "always" to make sure that it can safely be
+ deleted when we unload... */
+
+ init_timer (&hp->timer);
+ if (!hp->Ivec) {
+ rio_dprintk (RIO_DEBUG_INIT, "Starting polling at %dj intervals.\n",
+ rio_poll);
+ hp->timer.data = i;
+ hp->timer.function = rio_pollfunc;
+ hp->timer.expires = jiffies + rio_poll;
+ add_timer (&hp->timer);
+ }
+ }
+
+ if (found) {
+ rio_dprintk (RIO_DEBUG_INIT, "rio: total of %d boards detected.\n", found);
+ rio_init_drivers ();
+ } else {
+ /* deregister the misc device we created earlier */
+ misc_deregister(&rio_fw_device);
+ }
+
+ func_exit();
+ return found?0:-EIO;
+}
+
+
+static void __exit rio_exit (void)
+{
+ int i;
+ struct Host *hp;
+
+ func_enter();
+
+ for (i=0,hp=p->RIOHosts;i<p->RIONumHosts;i++, hp++) {
+ RIOHostReset (hp->Type, hp->CardP, hp->Slot);
+ if (hp->Ivec) {
+ free_irq (hp->Ivec, hp);
+ rio_dprintk (RIO_DEBUG_INIT, "freed irq %d.\n", hp->Ivec);
+ }
+ /* It is safe/allowed to del_timer a non-active timer */
+ del_timer (&hp->timer);
+ }
+
+ if (misc_deregister(&rio_fw_device) < 0) {
+ printk (KERN_INFO "rio: couldn't deregister control-device\n");
+ }
+
+
+ rio_dprintk (RIO_DEBUG_CLEANUP, "Cleaning up drivers\n");
+
+ rio_release_drivers ();
+
+ /* Release dynamically allocated memory */
+ kfree (p->RIOPortp);
+ kfree (p->RIOHosts);
+ kfree (p);
+
+ func_exit();
+}
+
+module_init(rio_init);
+module_exit(rio_exit);
+
+/*
+ * Anybody who knows why this doesn't work for me, please tell me -- REW.
+ * Snatched from scsi.c (fixed one spelling error):
+ * Overrides for Emacs so that we follow Linus' tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only. This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local Variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
+
diff --git a/drivers/char/rio/rio_linux.h b/drivers/char/rio/rio_linux.h
new file mode 100644
index 000000000000..1fba19d5b66a
--- /dev/null
+++ b/drivers/char/rio/rio_linux.h
@@ -0,0 +1,187 @@
+
+/*
+ * rio_linux.h
+ *
+ * Copyright (C) 1998,1999,2000 R.E.Wolff@BitWizard.nl
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * RIO serial driver.
+ *
+ * Version 1.0 -- July, 1999.
+ *
+ */
+#include <linux/config.h>
+
+#define RIO_NBOARDS 4
+#define RIO_PORTSPERBOARD 128
+#define RIO_NPORTS (RIO_NBOARDS * RIO_PORTSPERBOARD)
+
+#define MODEM_SUPPORT
+
+#ifdef __KERNEL__
+
+#define RIO_MAGIC 0x12345678
+
+
+struct vpd_prom {
+ unsigned short id;
+ char hwrev;
+ char hwass;
+ int uniqid;
+ char myear;
+ char mweek;
+ char hw_feature[5];
+ char oem_id;
+ char identifier[16];
+};
+
+
+#define RIO_DEBUG_ALL 0xffffffff
+
+#define O_OTHER(tty) \
+ ((O_OLCUC(tty)) ||\
+ (O_ONLCR(tty)) ||\
+ (O_OCRNL(tty)) ||\
+ (O_ONOCR(tty)) ||\
+ (O_ONLRET(tty)) ||\
+ (O_OFILL(tty)) ||\
+ (O_OFDEL(tty)) ||\
+ (O_NLDLY(tty)) ||\
+ (O_CRDLY(tty)) ||\
+ (O_TABDLY(tty)) ||\
+ (O_BSDLY(tty)) ||\
+ (O_VTDLY(tty)) ||\
+ (O_FFDLY(tty)))
+
+/* Same for input. */
+#define I_OTHER(tty) \
+ ((I_INLCR(tty)) ||\
+ (I_IGNCR(tty)) ||\
+ (I_ICRNL(tty)) ||\
+ (I_IUCLC(tty)) ||\
+ (L_ISIG(tty)))
+
+
+#endif /* __KERNEL__ */
+
+
+#define RIO_BOARD_INTR_LOCK 1
+
+
+#ifndef RIOCTL_MISC_MINOR
+/* Allow others to gather this into "major.h" or something like that */
+#define RIOCTL_MISC_MINOR 169
+#endif
+
+
+/* Allow us to debug "in the field" without requiring clients to
+ recompile.... */
+#if 1
+#define rio_spin_lock_irqsave(sem, flags) do { \
+ rio_dprintk (RIO_DEBUG_SPINLOCK, "spinlockirqsave: %p %s:%d\n", \
+ sem, __FILE__, __LINE__);\
+ spin_lock_irqsave(sem, flags);\
+ } while (0)
+
+#define rio_spin_unlock_irqrestore(sem, flags) do { \
+ rio_dprintk (RIO_DEBUG_SPINLOCK, "spinunlockirqrestore: %p %s:%d\n",\
+ sem, __FILE__, __LINE__);\
+ spin_unlock_irqrestore(sem, flags);\
+ } while (0)
+
+#define rio_spin_lock(sem) do { \
+ rio_dprintk (RIO_DEBUG_SPINLOCK, "spinlock: %p %s:%d\n",\
+ sem, __FILE__, __LINE__);\
+ spin_lock(sem);\
+ } while (0)
+
+#define rio_spin_unlock(sem) do { \
+ rio_dprintk (RIO_DEBUG_SPINLOCK, "spinunlock: %p %s:%d\n",\
+ sem, __FILE__, __LINE__);\
+ spin_unlock(sem);\
+ } while (0)
+#else
+#define rio_spin_lock_irqsave(sem, flags) \
+ spin_lock_irqsave(sem, flags)
+
+#define rio_spin_unlock_irqrestore(sem, flags) \
+ spin_unlock_irqrestore(sem, flags)
+
+#define rio_spin_lock(sem) \
+ spin_lock(sem)
+
+#define rio_spin_unlock(sem) \
+ spin_unlock(sem)
+
+#endif
+
+
+
+#ifdef CONFIG_RIO_OLDPCI
+static inline void *rio_memcpy_toio (void *dummy, void *dest, void *source, int n)
+{
+ char *dst = dest;
+ char *src = source;
+
+ while (n--) {
+ writeb (*src++, dst++);
+ (void) readb (dummy);
+ }
+
+ return dest;
+}
+
+
+static inline void *rio_memcpy_fromio (void *dest, void *source, int n)
+{
+ char *dst = dest;
+ char *src = source;
+
+ while (n--)
+ *dst++ = readb (src++);
+
+ return dest;
+}
+
+#else
+#define rio_memcpy_toio(dummy,dest,source,n) memcpy_toio(dest, source, n)
+#define rio_memcpy_fromio memcpy_fromio
+#endif
+
+#define DEBUG 1
+
+
+/*
+ This driver can spew a whole lot of debugging output at you. If you
+ need maximum performance, you should disable the DEBUG define. To
+ aid in debugging in the field, I'm leaving the compile-time debug
+ features enabled, and disable them "runtime". That allows me to
+ instruct people with problems to enable debugging without requiring
+ them to recompile...
+*/
+
+#ifdef DEBUG
+#define rio_dprintk(f, str...) do { if (rio_debug & f) printk (str);} while (0)
+#define func_enter() rio_dprintk (RIO_DEBUG_FLOW, "rio: enter %s\n", __FUNCTION__)
+#define func_exit() rio_dprintk (RIO_DEBUG_FLOW, "rio: exit %s\n", __FUNCTION__)
+#define func_enter2() rio_dprintk (RIO_DEBUG_FLOW, "rio: enter %s (port %d)\n",__FUNCTION__, port->line)
+#else
+#define rio_dprintk(f, str...) /* nothing */
+#define func_enter()
+#define func_exit()
+#define func_enter2()
+#endif
+
diff --git a/drivers/char/rio/rioboard.h b/drivers/char/rio/rioboard.h
new file mode 100644
index 000000000000..cc6ac6a98f65
--- /dev/null
+++ b/drivers/char/rio/rioboard.h
@@ -0,0 +1,281 @@
+/************************************************************************/
+/* */
+/* Title : RIO Host Card Hardware Definitions */
+/* */
+/* Author : N.P.Vassallo */
+/* */
+/* Creation : 26th April 1999 */
+/* */
+/* Version : 1.0.0 */
+/* */
+/* Copyright : (c) Specialix International Ltd. 1999 *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * */
+/* Description : Prototypes, structures and definitions */
+/* describing the RIO board hardware */
+/* */
+/************************************************************************/
+
+/* History...
+
+1.0.0 26/04/99 NPV Creation.
+
+*/
+
+#ifndef _rioboard_h /* If RIOBOARD.H not already defined */
+#define _rioboard_h 1
+
+/*****************************************************************************
+*********************** ***********************
+*********************** Hardware Control Registers ***********************
+*********************** ***********************
+*****************************************************************************/
+
+/* Hardware Registers... */
+
+#define RIO_REG_BASE 0x7C00 /* Base of control registers */
+
+#define RIO_CONFIG RIO_REG_BASE + 0x0000 /* WRITE: Configuration Register */
+#define RIO_INTSET RIO_REG_BASE + 0x0080 /* WRITE: Interrupt Set */
+#define RIO_RESET RIO_REG_BASE + 0x0100 /* WRITE: Host Reset */
+#define RIO_INTRESET RIO_REG_BASE + 0x0180 /* WRITE: Interrupt Reset */
+
+#define RIO_VPD_ROM RIO_REG_BASE + 0x0000 /* READ: Vital Product Data ROM */
+#define RIO_INTSTAT RIO_REG_BASE + 0x0080 /* READ: Interrupt Status (Jet boards only) */
+#define RIO_RESETSTAT RIO_REG_BASE + 0x0100 /* READ: Reset Status (Jet boards only) */
+
+/* RIO_VPD_ROM definitions... */
+#define VPD_SLX_ID1 0x00 /* READ: Specialix Identifier #1 */
+#define VPD_SLX_ID2 0x01 /* READ: Specialix Identifier #2 */
+#define VPD_HW_REV 0x02 /* READ: Hardware Revision */
+#define VPD_HW_ASSEM 0x03 /* READ: Hardware Assembly Level */
+#define VPD_UNIQUEID4 0x04 /* READ: Unique Identifier #4 */
+#define VPD_UNIQUEID3 0x05 /* READ: Unique Identifier #3 */
+#define VPD_UNIQUEID2 0x06 /* READ: Unique Identifier #2 */
+#define VPD_UNIQUEID1 0x07 /* READ: Unique Identifier #1 */
+#define VPD_MANU_YEAR 0x08 /* READ: Year Of Manufacture (0 = 1970) */
+#define VPD_MANU_WEEK 0x09 /* READ: Week Of Manufacture (0 = week 1 Jan) */
+#define VPD_HWFEATURE1 0x0A /* READ: Hardware Feature Byte 1 */
+#define VPD_HWFEATURE2 0x0B /* READ: Hardware Feature Byte 2 */
+#define VPD_HWFEATURE3 0x0C /* READ: Hardware Feature Byte 3 */
+#define VPD_HWFEATURE4 0x0D /* READ: Hardware Feature Byte 4 */
+#define VPD_HWFEATURE5 0x0E /* READ: Hardware Feature Byte 5 */
+#define VPD_OEMID 0x0F /* READ: OEM Identifier */
+#define VPD_IDENT 0x10 /* READ: Identifier string (16 bytes) */
+#define VPD_IDENT_LEN 0x10
+
+/* VPD ROM Definitions... */
+#define SLX_ID1 0x4D
+#define SLX_ID2 0x98
+
+#define PRODUCT_ID(a) ((a>>4)&0xF) /* Use to obtain Product ID from VPD_UNIQUEID1 */
+
+#define ID_SX_ISA 0x2
+#define ID_RIO_EISA 0x3
+#define ID_SX_PCI 0x5
+#define ID_SX_EISA 0x7
+#define ID_RIO_RTA16 0x9
+#define ID_RIO_ISA 0xA
+#define ID_RIO_MCA 0xB
+#define ID_RIO_SBUS 0xC
+#define ID_RIO_PCI 0xD
+#define ID_RIO_RTA8 0xE
+
+/* Transputer bootstrap definitions... */
+
+#define BOOTLOADADDR (0x8000 - 6)
+#define BOOTINDICATE (0x8000 - 2)
+
+/* Firmware load position... */
+
+#define FIRMWARELOADADDR 0x7C00 /* Firmware is loaded _before_ this address */
+
+/*****************************************************************************
+***************************** *****************************
+***************************** RIO (Rev1) ISA *****************************
+***************************** *****************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_ISA_IDENT "JBJGPGGHINSMJPJR"
+
+#define RIO_ISA_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_ISA_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_ISA_CFG_IRQMASK 0x30 /* Interrupt mask */
+#define RIO_ISA_CFG_IRQ12 0x10 /* Interrupt Level 12 */
+#define RIO_ISA_CFG_IRQ11 0x20 /* Interrupt Level 11 */
+#define RIO_ISA_CFG_IRQ9 0x30 /* Interrupt Level 9 */
+#define RIO_ISA_CFG_LINK20 0x40 /* 20Mbps link, else 10Mbps */
+#define RIO_ISA_CFG_WAITSTATE0 0x80 /* 0 waitstates, else 1 */
+
+/*****************************************************************************
+***************************** *****************************
+***************************** RIO (Rev2) ISA *****************************
+***************************** *****************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_ISA2_IDENT "JBJGPGGHINSMJPJR"
+
+#define RIO_ISA2_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_ISA2_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_ISA2_CFG_INTENABLE 0x04 /* Interrupt enable, else disable */
+#define RIO_ISA2_CFG_16BIT 0x08 /* 16bit mode, else 8bit */
+#define RIO_ISA2_CFG_IRQMASK 0x30 /* Interrupt mask */
+#define RIO_ISA2_CFG_IRQ15 0x00 /* Interrupt Level 15 */
+#define RIO_ISA2_CFG_IRQ12 0x10 /* Interrupt Level 12 */
+#define RIO_ISA2_CFG_IRQ11 0x20 /* Interrupt Level 11 */
+#define RIO_ISA2_CFG_IRQ9 0x30 /* Interrupt Level 9 */
+#define RIO_ISA2_CFG_LINK20 0x40 /* 20Mbps link, else 10Mbps */
+#define RIO_ISA2_CFG_WAITSTATE0 0x80 /* 0 waitstates, else 1 */
+
+/*****************************************************************************
+***************************** ******************************
+***************************** RIO (Jet) ISA ******************************
+***************************** ******************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_ISA3_IDENT "JET HOST BY KEV#"
+
+#define RIO_ISA3_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_ISA3_CFG_INTENABLE 0x04 /* Interrupt enable, else disable */
+#define RIO_ISA32_CFG_IRQMASK 0xF30 /* Interrupt mask */
+#define RIO_ISA3_CFG_IRQ15 0xF0 /* Interrupt Level 15 */
+#define RIO_ISA3_CFG_IRQ12 0xC0 /* Interrupt Level 12 */
+#define RIO_ISA3_CFG_IRQ11 0xB0 /* Interrupt Level 11 */
+#define RIO_ISA3_CFG_IRQ10 0xA0 /* Interrupt Level 10 */
+#define RIO_ISA3_CFG_IRQ9 0x90 /* Interrupt Level 9 */
+
+/*****************************************************************************
+********************************* ********************************
+********************************* RIO MCA ********************************
+********************************* ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_MCA_IDENT "JBJGPGGHINSMJPJR"
+
+#define RIO_MCA_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_MCA_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_MCA_CFG_LINK20 0x40 /* 20Mbps link, else 10Mbps */
+
+/*****************************************************************************
+******************************** ********************************
+******************************** RIO EISA ********************************
+******************************** ********************************
+*****************************************************************************/
+
+/* EISA Configuration Space Definitions... */
+#define EISA_PRODUCT_ID1 0xC80
+#define EISA_PRODUCT_ID2 0xC81
+#define EISA_PRODUCT_NUMBER 0xC82
+#define EISA_REVISION_NUMBER 0xC83
+#define EISA_CARD_ENABLE 0xC84
+#define EISA_VPD_UNIQUEID4 0xC88 /* READ: Unique Identifier #4 */
+#define EISA_VPD_UNIQUEID3 0xC8A /* READ: Unique Identifier #3 */
+#define EISA_VPD_UNIQUEID2 0xC90 /* READ: Unique Identifier #2 */
+#define EISA_VPD_UNIQUEID1 0xC92 /* READ: Unique Identifier #1 */
+#define EISA_VPD_MANU_YEAR 0xC98 /* READ: Year Of Manufacture (0 = 1970) */
+#define EISA_VPD_MANU_WEEK 0xC9A /* READ: Week Of Manufacture (0 = week 1 Jan) */
+#define EISA_MEM_ADDR_23_16 0xC00
+#define EISA_MEM_ADDR_31_24 0xC01
+#define EISA_RIO_CONFIG 0xC02 /* WRITE: Configuration Register */
+#define EISA_RIO_INTSET 0xC03 /* WRITE: Interrupt Set */
+#define EISA_RIO_INTRESET 0xC03 /* READ: Interrupt Reset */
+
+/* Control Register Definitions... */
+#define RIO_EISA_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_EISA_CFG_LINK20 0x02 /* 20Mbps link, else 10Mbps */
+#define RIO_EISA_CFG_BUSENABLE 0x04 /* Enable processor bus */
+#define RIO_EISA_CFG_PROCRUN 0x08 /* Processor running, else reset */
+#define RIO_EISA_CFG_IRQMASK 0xF0 /* Interrupt mask */
+#define RIO_EISA_CFG_IRQ15 0xF0 /* Interrupt Level 15 */
+#define RIO_EISA_CFG_IRQ14 0xE0 /* Interrupt Level 14 */
+#define RIO_EISA_CFG_IRQ12 0xC0 /* Interrupt Level 12 */
+#define RIO_EISA_CFG_IRQ11 0xB0 /* Interrupt Level 11 */
+#define RIO_EISA_CFG_IRQ10 0xA0 /* Interrupt Level 10 */
+#define RIO_EISA_CFG_IRQ9 0x90 /* Interrupt Level 9 */
+#define RIO_EISA_CFG_IRQ7 0x70 /* Interrupt Level 7 */
+#define RIO_EISA_CFG_IRQ6 0x60 /* Interrupt Level 6 */
+#define RIO_EISA_CFG_IRQ5 0x50 /* Interrupt Level 5 */
+#define RIO_EISA_CFG_IRQ4 0x40 /* Interrupt Level 4 */
+#define RIO_EISA_CFG_IRQ3 0x30 /* Interrupt Level 3 */
+
+/*****************************************************************************
+******************************** ********************************
+******************************** RIO SBus ********************************
+******************************** ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_SBUS_IDENT "JBPGK#\0\0\0\0\0\0\0\0\0\0"
+
+#define RIO_SBUS_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_SBUS_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_SBUS_CFG_INTENABLE 0x04 /* Interrupt enable, else disable */
+#define RIO_SBUS_CFG_IRQMASK 0x38 /* Interrupt mask */
+#define RIO_SBUS_CFG_IRQNONE 0x00 /* No Interrupt */
+#define RIO_SBUS_CFG_IRQ7 0x38 /* Interrupt Level 7 */
+#define RIO_SBUS_CFG_IRQ6 0x30 /* Interrupt Level 6 */
+#define RIO_SBUS_CFG_IRQ5 0x28 /* Interrupt Level 5 */
+#define RIO_SBUS_CFG_IRQ4 0x20 /* Interrupt Level 4 */
+#define RIO_SBUS_CFG_IRQ3 0x18 /* Interrupt Level 3 */
+#define RIO_SBUS_CFG_IRQ2 0x10 /* Interrupt Level 2 */
+#define RIO_SBUS_CFG_IRQ1 0x08 /* Interrupt Level 1 */
+#define RIO_SBUS_CFG_LINK20 0x40 /* 20Mbps link, else 10Mbps */
+#define RIO_SBUS_CFG_PROC25 0x80 /* 25Mhz processor clock, else 20Mhz */
+
+/*****************************************************************************
+********************************* ********************************
+********************************* RIO PCI ********************************
+********************************* ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_PCI_IDENT "ECDDPGJGJHJRGSK#"
+
+#define RIO_PCI_CFG_BOOTRAM 0x01 /* Boot from RAM, else Link */
+#define RIO_PCI_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_PCI_CFG_INTENABLE 0x04 /* Interrupt enable, else disable */
+#define RIO_PCI_CFG_LINK20 0x40 /* 20Mbps link, else 10Mbps */
+#define RIO_PCI_CFG_PROC25 0x80 /* 25Mhz processor clock, else 20Mhz */
+
+/* PCI Definitions... */
+#define SPX_VENDOR_ID 0x11CB /* Assigned by the PCI SIG */
+#define SPX_DEVICE_ID 0x8000 /* RIO bridge boards */
+#define SPX_PLXDEVICE_ID 0x2000 /* PLX bridge boards */
+#define SPX_SUB_VENDOR_ID SPX_VENDOR_ID /* Same as vendor id */
+#define RIO_SUB_SYS_ID 0x0800 /* RIO PCI board */
+
+/*****************************************************************************
+***************************** ******************************
+***************************** RIO (Jet) PCI ******************************
+***************************** ******************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define RIO_PCI2_IDENT "JET HOST BY KEV#"
+
+#define RIO_PCI2_CFG_BUSENABLE 0x02 /* Enable processor bus */
+#define RIO_PCI2_CFG_INTENABLE 0x04 /* Interrupt enable, else disable */
+
+/* PCI Definitions... */
+#define RIO2_SUB_SYS_ID 0x0100 /* RIO (Jet) PCI board */
+
+#endif /*_rioboard_h */
+
+/* End of RIOBOARD.H */
diff --git a/drivers/char/rio/rioboot.c b/drivers/char/rio/rioboot.c
new file mode 100644
index 000000000000..a8be11dfcba3
--- /dev/null
+++ b/drivers/char/rio/rioboot.c
@@ -0,0 +1,1360 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioboot.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:36
+** Retrieved : 11/6/98 10:33:48
+**
+** ident @(#)rioboot.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifdef SCCS_LABELS
+static char *_rioboot_c_sccs_ = "@(#)rioboot.c 1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+
+static int RIOBootComplete( struct rio_info *p, struct Host *HostP, uint Rup, struct PktCmd *PktCmdP );
+
+static uchar
+RIOAtVec2Ctrl[] =
+{
+ /* 0 */ INTERRUPT_DISABLE,
+ /* 1 */ INTERRUPT_DISABLE,
+ /* 2 */ INTERRUPT_DISABLE,
+ /* 3 */ INTERRUPT_DISABLE,
+ /* 4 */ INTERRUPT_DISABLE,
+ /* 5 */ INTERRUPT_DISABLE,
+ /* 6 */ INTERRUPT_DISABLE,
+ /* 7 */ INTERRUPT_DISABLE,
+ /* 8 */ INTERRUPT_DISABLE,
+ /* 9 */ IRQ_9|INTERRUPT_ENABLE,
+ /* 10 */ INTERRUPT_DISABLE,
+ /* 11 */ IRQ_11|INTERRUPT_ENABLE,
+ /* 12 */ IRQ_12|INTERRUPT_ENABLE,
+ /* 13 */ INTERRUPT_DISABLE,
+ /* 14 */ INTERRUPT_DISABLE,
+ /* 15 */ IRQ_15|INTERRUPT_ENABLE
+};
+
+/*
+** Load in the RTA boot code.
+*/
+int
+RIOBootCodeRTA(p, rbp)
+struct rio_info * p;
+struct DownLoad * rbp;
+{
+ int offset;
+
+ func_enter ();
+
+ /* Linux doesn't allow you to disable interrupts during a
+ "copyin". (Crash when a pagefault occurs). */
+ /* disable(oldspl); */
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Data at user address 0x%x\n",(int)rbp->DataP);
+
+ /*
+ ** Check that we have set asside enough memory for this
+ */
+ if ( rbp->Count > SIXTY_FOUR_K ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot Code Too Large!\n");
+ p->RIOError.Error = HOST_FILE_TOO_LARGE;
+ /* restore(oldspl); */
+ func_exit ();
+ return -ENOMEM;
+ }
+
+ if ( p->RIOBooting ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot Code : BUSY BUSY BUSY!\n");
+ p->RIOError.Error = BOOT_IN_PROGRESS;
+ /* restore(oldspl); */
+ func_exit ();
+ return -EBUSY;
+ }
+
+ /*
+ ** The data we load in must end on a (RTA_BOOT_DATA_SIZE) byte boundary,
+ ** so calculate how far we have to move the data up the buffer
+ ** to achieve this.
+ */
+ offset = (RTA_BOOT_DATA_SIZE - (rbp->Count % RTA_BOOT_DATA_SIZE)) %
+ RTA_BOOT_DATA_SIZE;
+
+ /*
+ ** Be clean, and clear the 'unused' portion of the boot buffer,
+ ** because it will (eventually) be part of the Rta run time environment
+ ** and so should be zeroed.
+ */
+ bzero( (caddr_t)p->RIOBootPackets, offset );
+
+ /*
+ ** Copy the data from user space.
+ */
+
+ if ( copyin((int)rbp->DataP,((caddr_t)(p->RIOBootPackets))+offset,
+ rbp->Count) ==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Bad data copy from user space\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ /* restore(oldspl); */
+ func_exit ();
+ return -EFAULT;
+ }
+
+ /*
+ ** Make sure that our copy of the size includes that offset we discussed
+ ** earlier.
+ */
+ p->RIONumBootPkts = (rbp->Count+offset)/RTA_BOOT_DATA_SIZE;
+ p->RIOBootCount = rbp->Count;
+
+ /* restore(oldspl); */
+ func_exit();
+ return 0;
+}
+
+void rio_start_card_running (struct Host * HostP)
+{
+ func_enter ();
+
+ switch ( HostP->Type ) {
+ case RIO_AT:
+ rio_dprintk (RIO_DEBUG_BOOT, "Start ISA card running\n");
+ WBYTE(HostP->Control,
+ BOOT_FROM_RAM | EXTERNAL_BUS_ON
+ | HostP->Mode
+ | RIOAtVec2Ctrl[HostP->Ivec & 0xF] );
+ break;
+
+#ifdef FUTURE_RELEASE
+ case RIO_MCA:
+ /*
+ ** MCA handles IRQ vectors differently, so we don't write
+ ** them to this register.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Start MCA card running\n");
+ WBYTE(HostP->Control, McaTpBootFromRam | McaTpBusEnable | HostP->Mode);
+ break;
+
+ case RIO_EISA:
+ /*
+ ** EISA is totally different and expects OUTBZs to turn it on.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Start EISA card running\n");
+ OUTBZ( HostP->Slot, EISA_CONTROL_PORT, HostP->Mode | RIOEisaVec2Ctrl[HostP->Ivec] | EISA_TP_RUN | EISA_TP_BUS_ENABLE | EISA_TP_BOOT_FROM_RAM );
+ break;
+#endif
+
+ case RIO_PCI:
+ /*
+ ** PCI is much the same as MCA. Everything is once again memory
+ ** mapped, so we are writing to memory registers instead of io
+ ** ports.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Start PCI card running\n");
+ WBYTE(HostP->Control, PCITpBootFromRam | PCITpBusEnable | HostP->Mode);
+ break;
+ default:
+ rio_dprintk (RIO_DEBUG_BOOT, "Unknown host type %d\n", HostP->Type);
+ break;
+ }
+/*
+ printk (KERN_INFO "Done with starting the card\n");
+ func_exit ();
+*/
+ return;
+}
+
+/*
+** Load in the host boot code - load it directly onto all halted hosts
+** of the correct type.
+**
+** Put your rubber pants on before messing with this code - even the magic
+** numbers have trouble understanding what they are doing here.
+*/
+int
+RIOBootCodeHOST(p, rbp)
+struct rio_info * p;
+register struct DownLoad *rbp;
+{
+ register struct Host *HostP;
+ register caddr_t Cad;
+ register PARM_MAP *ParmMapP;
+ register int RupN;
+ int PortN;
+ uint host;
+ caddr_t StartP;
+ BYTE *DestP;
+ int wait_count;
+ ushort OldParmMap;
+ ushort offset; /* It is very important that this is a ushort */
+ /* uint byte; */
+ caddr_t DownCode = NULL;
+ unsigned long flags;
+
+ HostP = NULL; /* Assure the compiler we've initialized it */
+ for ( host=0; host<p->RIONumHosts; host++ ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Attempt to boot host %d\n",host);
+ HostP = &p->RIOHosts[host];
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Host Type = 0x%x, Mode = 0x%x, IVec = 0x%x\n",
+ HostP->Type, HostP->Mode, HostP->Ivec);
+
+
+ if ( (HostP->Flags & RUN_STATE) != RC_WAITING ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "%s %d already running\n","Host",host);
+ continue;
+ }
+
+ /*
+ ** Grab a 32 bit pointer to the card.
+ */
+ Cad = HostP->Caddr;
+
+ /*
+ ** We are going to (try) and load in rbp->Count bytes.
+ ** The last byte will reside at p->RIOConf.HostLoadBase-1;
+ ** Therefore, we need to start copying at address
+ ** (caddr+p->RIOConf.HostLoadBase-rbp->Count)
+ */
+ StartP = (caddr_t)&Cad[p->RIOConf.HostLoadBase-rbp->Count];
+
+ rio_dprintk (RIO_DEBUG_BOOT, "kernel virtual address for host is 0x%x\n", (int)Cad );
+ rio_dprintk (RIO_DEBUG_BOOT, "kernel virtual address for download is 0x%x\n", (int)StartP);
+ rio_dprintk (RIO_DEBUG_BOOT, "host loadbase is 0x%x\n",p->RIOConf.HostLoadBase);
+ rio_dprintk (RIO_DEBUG_BOOT, "size of download is 0x%x\n", rbp->Count);
+
+ if ( p->RIOConf.HostLoadBase < rbp->Count ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Bin too large\n");
+ p->RIOError.Error = HOST_FILE_TOO_LARGE;
+ func_exit ();
+ return -EFBIG;
+ }
+ /*
+ ** Ensure that the host really is stopped.
+ ** Disable it's external bus & twang its reset line.
+ */
+ RIOHostReset( HostP->Type, (struct DpRam *)HostP->CardP, HostP->Slot );
+
+ /*
+ ** Copy the data directly from user space to the SRAM.
+ ** This ain't going to be none too clever if the download
+ ** code is bigger than this segment.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Copy in code\n");
+
+ /*
+ ** PCI hostcard can't cope with 32 bit accesses and so need to copy
+ ** data to a local buffer, and then dripfeed the card.
+ */
+ if ( HostP->Type == RIO_PCI ) {
+ /* int offset; */
+
+ DownCode = sysbrk(rbp->Count);
+ if ( !DownCode ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "No system memory available\n");
+ p->RIOError.Error = NOT_ENOUGH_CORE_FOR_PCI_COPY;
+ func_exit ();
+ return -ENOMEM;
+ }
+ bzero(DownCode, rbp->Count);
+
+ if ( copyin((int)rbp->DataP,DownCode,rbp->Count)==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Bad copyin of host data\n");
+ sysfree( DownCode, rbp->Count );
+ p->RIOError.Error = COPYIN_FAILED;
+ func_exit ();
+ return -EFAULT;
+ }
+
+ HostP->Copy( DownCode, StartP, rbp->Count );
+
+ sysfree( DownCode, rbp->Count );
+ }
+ else if ( copyin((int)rbp->DataP,StartP,rbp->Count)==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Bad copyin of host data\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ func_exit ();
+ return -EFAULT;
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Copy completed\n");
+
+ /*
+ ** S T O P !
+ **
+ ** Upto this point the code has been fairly rational, and possibly
+ ** even straight forward. What follows is a pile of crud that will
+ ** magically turn into six bytes of transputer assembler. Normally
+ ** you would expect an array or something, but, being me, I have
+ ** chosen [been told] to use a technique whereby the startup code
+ ** will be correct if we change the loadbase for the code. Which
+ ** brings us onto another issue - the loadbase is the *end* of the
+ ** code, not the start.
+ **
+ ** If I were you I wouldn't start from here.
+ */
+
+ /*
+ ** We now need to insert a short boot section into
+ ** the memory at the end of Sram2. This is normally (de)composed
+ ** of the last eight bytes of the download code. The
+ ** download has been assembled/compiled to expect to be
+ ** loaded from 0x7FFF downwards. We have loaded it
+ ** at some other address. The startup code goes into the small
+ ** ram window at Sram2, in the last 8 bytes, which are really
+ ** at addresses 0x7FF8-0x7FFF.
+ **
+ ** If the loadbase is, say, 0x7C00, then we need to branch to
+ ** address 0x7BFE to run the host.bin startup code. We assemble
+ ** this jump manually.
+ **
+ ** The two byte sequence 60 08 is loaded into memory at address
+ ** 0x7FFE,F. This is a local branch to location 0x7FF8 (60 is nfix 0,
+ ** which adds '0' to the .O register, complements .O, and then shifts
+ ** it left by 4 bit positions, 08 is a jump .O+8 instruction. This will
+ ** add 8 to .O (which was 0xFFF0), and will branch RELATIVE to the new
+ ** location. Now, the branch starts from the value of .PC (or .IP or
+ ** whatever the bloody register is called on this chip), and the .PC
+ ** will be pointing to the location AFTER the branch, in this case
+ ** .PC == 0x8000, so the branch will be to 0x8000+0xFFF8 = 0x7FF8.
+ **
+ ** A long branch is coded at 0x7FF8. This consists of loading a four
+ ** byte offset into .O using nfix (as above) and pfix operators. The
+ ** pfix operates in exactly the same way as the nfix operator, but
+ ** without the complement operation. The offset, of course, must be
+ ** relative to the address of the byte AFTER the branch instruction,
+ ** which will be (urm) 0x7FFC, so, our final destination of the branch
+ ** (loadbase-2), has to be reached from here. Imagine that the loadbase
+ ** is 0x7C00 (which it is), then we will need to branch to 0x7BFE (which
+ ** is the first byte of the initial two byte short local branch of the
+ ** download code).
+ **
+ ** To code a jump from 0x7FFC (which is where the branch will start
+ ** from) to 0x7BFE, we will need to branch 0xFC02 bytes (0x7FFC+0xFC02)=
+ ** 0x7BFE.
+ ** This will be coded as four bytes:
+ ** 60 2C 20 02
+ ** being nfix .O+0
+ ** pfix .O+C
+ ** pfix .O+0
+ ** jump .O+2
+ **
+ ** The nfix operator is used, so that the startup code will be
+ ** compatible with the whole Tp family. (lies, damn lies, it'll never
+ ** work in a month of Sundays).
+ **
+ ** The nfix nyble is the 1s complement of the nyble value you
+ ** want to load - in this case we wanted 'F' so we nfix loaded '0'.
+ */
+
+
+ /*
+ ** Dest points to the top 8 bytes of Sram2. The Tp jumps
+ ** to 0x7FFE at reset time, and starts executing. This is
+ ** a short branch to 0x7FF8, where a long branch is coded.
+ */
+
+ DestP = (BYTE *)&Cad[0x7FF8]; /* <<<---- READ THE ABOVE COMMENTS */
+
+#define NFIX(N) (0x60 | (N)) /* .O = (~(.O + N))<<4 */
+#define PFIX(N) (0x20 | (N)) /* .O = (.O + N)<<4 */
+#define JUMP(N) (0x00 | (N)) /* .PC = .PC + .O */
+
+ /*
+ ** 0x7FFC is the address of the location following the last byte of
+ ** the four byte jump instruction.
+ ** READ THE ABOVE COMMENTS
+ **
+ ** offset is (TO-FROM) % MEMSIZE, but with compound buggering about.
+ ** Memsize is 64K for this range of Tp, so offset is a short (unsigned,
+ ** cos I don't understand 2's complement).
+ */
+ offset = (p->RIOConf.HostLoadBase-2)-0x7FFC;
+ WBYTE( DestP[0] , NFIX(((ushort)(~offset) >> (ushort)12) & 0xF) );
+ WBYTE( DestP[1] , PFIX(( offset >> 8) & 0xF) );
+ WBYTE( DestP[2] , PFIX(( offset >> 4) & 0xF) );
+ WBYTE( DestP[3] , JUMP( offset & 0xF) );
+
+ WBYTE( DestP[6] , NFIX(0) );
+ WBYTE( DestP[7] , JUMP(8) );
+
+ rio_dprintk (RIO_DEBUG_BOOT, "host loadbase is 0x%x\n",p->RIOConf.HostLoadBase);
+ rio_dprintk (RIO_DEBUG_BOOT, "startup offset is 0x%x\n",offset);
+
+ /*
+ ** Flag what is going on
+ */
+ HostP->Flags &= ~RUN_STATE;
+ HostP->Flags |= RC_STARTUP;
+
+ /*
+ ** Grab a copy of the current ParmMap pointer, so we
+ ** can tell when it has changed.
+ */
+ OldParmMap = RWORD(HostP->__ParmMapR);
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Original parmmap is 0x%x\n",OldParmMap);
+
+ /*
+ ** And start it running (I hope).
+ ** As there is nothing dodgy or obscure about the
+ ** above code, this is guaranteed to work every time.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Host Type = 0x%x, Mode = 0x%x, IVec = 0x%x\n",
+ HostP->Type, HostP->Mode, HostP->Ivec);
+
+ rio_start_card_running(HostP);
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Set control port\n");
+
+ /*
+ ** Now, wait for upto five seconds for the Tp to setup the parmmap
+ ** pointer:
+ */
+ for ( wait_count=0; (wait_count<p->RIOConf.StartupTime)&&
+ (RWORD(HostP->__ParmMapR)==OldParmMap); wait_count++ ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Checkout %d, 0x%x\n",wait_count,RWORD(HostP->__ParmMapR));
+ delay(HostP, HUNDRED_MS);
+
+ }
+
+ /*
+ ** If the parmmap pointer is unchanged, then the host code
+ ** has crashed & burned in a really spectacular way
+ */
+ if ( RWORD(HostP->__ParmMapR) == OldParmMap ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "parmmap 0x%x\n", RWORD(HostP->__ParmMapR));
+ rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail\n");
+
+#define HOST_DISABLE \
+ HostP->Flags &= ~RUN_STATE; \
+ HostP->Flags |= RC_STUFFED; \
+ RIOHostReset( HostP->Type, (struct DpRam *)HostP->CardP, HostP->Slot );\
+ continue
+
+ HOST_DISABLE;
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Running 0x%x\n", RWORD(HostP->__ParmMapR));
+
+ /*
+ ** Well, the board thought it was OK, and setup its parmmap
+ ** pointer. For the time being, we will pretend that this
+ ** board is running, and check out what the error flag says.
+ */
+
+ /*
+ ** Grab a 32 bit pointer to the parmmap structure
+ */
+ ParmMapP = (PARM_MAP *)RIO_PTR(Cad,RWORD(HostP->__ParmMapR));
+ rio_dprintk (RIO_DEBUG_BOOT, "ParmMapP : %x\n", (int)ParmMapP);
+ ParmMapP = (PARM_MAP *)((unsigned long)Cad +
+ (unsigned long)((RWORD((HostP->__ParmMapR))) & 0xFFFF));
+ rio_dprintk (RIO_DEBUG_BOOT, "ParmMapP : %x\n", (int)ParmMapP);
+
+ /*
+ ** The links entry should be 0xFFFF; we set it up
+ ** with a mask to say how many PHBs to use, and
+ ** which links to use.
+ */
+ if ( (RWORD(ParmMapP->links) & 0xFFFF) != 0xFFFF ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail %s\n", HostP->Name);
+ rio_dprintk (RIO_DEBUG_BOOT, "Links = 0x%x\n",RWORD(ParmMapP->links));
+ HOST_DISABLE;
+ }
+
+ WWORD(ParmMapP->links , RIO_LINK_ENABLE);
+
+ /*
+ ** now wait for the card to set all the parmmap->XXX stuff
+ ** this is a wait of upto two seconds....
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Looking for init_done - %d ticks\n",p->RIOConf.StartupTime);
+ HostP->timeout_id = 0;
+ for ( wait_count=0; (wait_count<p->RIOConf.StartupTime) &&
+ !RWORD(ParmMapP->init_done); wait_count++ ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Waiting for init_done\n");
+ delay(HostP, HUNDRED_MS);
+ }
+ rio_dprintk (RIO_DEBUG_BOOT, "OK! init_done!\n");
+
+ if (RWORD(ParmMapP->error) != E_NO_ERROR ||
+ !RWORD(ParmMapP->init_done) ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail %s\n", HostP->Name);
+ rio_dprintk (RIO_DEBUG_BOOT, "Timedout waiting for init_done\n");
+ HOST_DISABLE;
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Got init_done\n");
+
+ /*
+ ** It runs! It runs!
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Host ID %x Running\n",HostP->UniqueNum);
+
+ /*
+ ** set the time period between interrupts.
+ */
+ WWORD(ParmMapP->timer, (short)p->RIOConf.Timer );
+
+ /*
+ ** Translate all the 16 bit pointers in the __ParmMapR into
+ ** 32 bit pointers for the driver.
+ */
+ HostP->ParmMapP = ParmMapP;
+ HostP->PhbP = (PHB*)RIO_PTR(Cad,RWORD(ParmMapP->phb_ptr));
+ HostP->RupP = (RUP*)RIO_PTR(Cad,RWORD(ParmMapP->rups));
+ HostP->PhbNumP = (ushort*)RIO_PTR(Cad,RWORD(ParmMapP->phb_num_ptr));
+ HostP->LinkStrP = (LPB*)RIO_PTR(Cad,RWORD(ParmMapP->link_str_ptr));
+
+ /*
+ ** point the UnixRups at the real Rups
+ */
+ for ( RupN = 0; RupN<MAX_RUP; RupN++ ) {
+ HostP->UnixRups[RupN].RupP = &HostP->RupP[RupN];
+ HostP->UnixRups[RupN].Id = RupN+1;
+ HostP->UnixRups[RupN].BaseSysPort = NO_PORT;
+ spin_lock_init(&HostP->UnixRups[RupN].RupLock);
+ }
+
+ for ( RupN = 0; RupN<LINKS_PER_UNIT; RupN++ ) {
+ HostP->UnixRups[RupN+MAX_RUP].RupP = &HostP->LinkStrP[RupN].rup;
+ HostP->UnixRups[RupN+MAX_RUP].Id = 0;
+ HostP->UnixRups[RupN+MAX_RUP].BaseSysPort = NO_PORT;
+ spin_lock_init(&HostP->UnixRups[RupN+MAX_RUP].RupLock);
+ }
+
+ /*
+ ** point the PortP->Phbs at the real Phbs
+ */
+ for ( PortN=p->RIOFirstPortsMapped;
+ PortN<p->RIOLastPortsMapped+PORTS_PER_RTA; PortN++ ) {
+ if ( p->RIOPortp[PortN]->HostP == HostP ) {
+ struct Port *PortP = p->RIOPortp[PortN];
+ struct PHB *PhbP;
+ /* int oldspl; */
+
+ if ( !PortP->Mapped )
+ continue;
+
+ PhbP = &HostP->PhbP[PortP->HostPort];
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ PortP->PhbP = PhbP;
+
+ PortP->TxAdd = (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_add));
+ PortP->TxStart = (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_start));
+ PortP->TxEnd = (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_end));
+ PortP->RxRemove = (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_remove));
+ PortP->RxStart = (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_start));
+ PortP->RxEnd = (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_end));
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ /*
+ ** point the UnixRup at the base SysPort
+ */
+ if ( !(PortN % PORTS_PER_RTA) )
+ HostP->UnixRups[PortP->RupNum].BaseSysPort = PortN;
+ }
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Set the card running... \n");
+ /*
+ ** last thing - show the world that everything is in place
+ */
+ HostP->Flags &= ~RUN_STATE;
+ HostP->Flags |= RC_RUNNING;
+ }
+ /*
+ ** MPX always uses a poller. This is actually patched into the system
+ ** configuration and called directly from each clock tick.
+ **
+ */
+ p->RIOPolling = 1;
+
+ p->RIOSystemUp++;
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Done everything %x\n", HostP->Ivec);
+ func_exit ();
+ return 0;
+}
+
+
+
+/*
+** Boot an RTA. If we have successfully processed this boot, then
+** return 1. If we havent, then return 0.
+*/
+int
+RIOBootRup( p, Rup, HostP, PacketP)
+struct rio_info * p;
+uint Rup;
+struct Host *HostP;
+struct PKT *PacketP;
+{
+ struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+ struct PktCmd_M *PktReplyP;
+ struct CmdBlk *CmdBlkP;
+ uint sequence;
+
+#ifdef CHECK
+ CheckHost(Host);
+ CheckRup(Rup);
+ CheckHostP(HostP);
+ CheckPacketP(PacketP);
+#endif
+
+ /*
+ ** If we haven't been told what to boot, we can't boot it.
+ */
+ if ( p->RIONumBootPkts == 0 ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "No RTA code to download yet\n");
+ return 0;
+ }
+
+ /* rio_dprint(RIO_DEBUG_BOOT, NULL,DBG_BOOT,"Incoming command packet\n"); */
+ /* ShowPacket( DBG_BOOT, PacketP ); */
+
+ /*
+ ** Special case of boot completed - if we get one of these then we
+ ** don't need a command block. For all other cases we do, so handle
+ ** this first and then get a command block, then handle every other
+ ** case, relinquishing the command block if disaster strikes!
+ */
+ if ( (RBYTE(PacketP->len) & PKT_CMD_BIT) &&
+ (RBYTE(PktCmdP->Command)==BOOT_COMPLETED) )
+ return RIOBootComplete(p, HostP, Rup, PktCmdP );
+
+ /*
+ ** try to unhook a command block from the command free list.
+ */
+ if ( !(CmdBlkP = RIOGetCmdBlk()) ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "No command blocks to boot RTA! come back later.\n");
+ return 0;
+ }
+
+ /*
+ ** Fill in the default info on the command block
+ */
+ CmdBlkP->Packet.dest_unit = Rup < (ushort)MAX_RUP ? Rup : 0;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+
+ CmdBlkP->PreFuncP = CmdBlkP->PostFuncP = NULL;
+ PktReplyP = (struct PktCmd_M *)CmdBlkP->Packet.data;
+
+ /*
+ ** process COMMANDS on the boot rup!
+ */
+ if ( RBYTE(PacketP->len) & PKT_CMD_BIT ) {
+ /*
+ ** We only expect one type of command - a BOOT_REQUEST!
+ */
+ if ( RBYTE(PktCmdP->Command) != BOOT_REQUEST ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Unexpected command %d on BOOT RUP %d of host %d\n",
+ PktCmdP->Command,Rup,HostP-p->RIOHosts);
+ ShowPacket( DBG_BOOT, PacketP );
+ RIOFreeCmdBlk( CmdBlkP );
+ return 1;
+ }
+
+ /*
+ ** Build a Boot Sequence command block
+ **
+ ** 02.03.1999 ARG - ESIL 0820 fix
+ ** We no longer need to use "Boot Mode", we'll always allow
+ ** boot requests - the boot will not complete if the device
+ ** appears in the bindings table.
+ ** So, this conditional is not required ...
+ **
+ if (p->RIOBootMode == RC_BOOT_NONE)
+ **
+ ** If the system is in slave mode, and a boot request is
+ ** received, set command to BOOT_ABORT so that the boot
+ ** will not complete.
+ **
+ PktReplyP->Command = BOOT_ABORT;
+ else
+ **
+ ** We'll just (always) set the command field in packet reply
+ ** to allow an attempted boot sequence :
+ */
+ PktReplyP->Command = BOOT_SEQUENCE;
+
+ PktReplyP->BootSequence.NumPackets = p->RIONumBootPkts;
+ PktReplyP->BootSequence.LoadBase = p->RIOConf.RtaLoadBase;
+ PktReplyP->BootSequence.CodeSize = p->RIOBootCount;
+
+ CmdBlkP->Packet.len = BOOT_SEQUENCE_LEN | PKT_CMD_BIT;
+
+ bcopy("BOOT",(void *)&CmdBlkP->Packet.data[BOOT_SEQUENCE_LEN],4);
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Boot RTA on Host %d Rup %d - %d (0x%x) packets to 0x%x\n",
+ HostP-p->RIOHosts, Rup, p->RIONumBootPkts, p->RIONumBootPkts,
+ p->RIOConf.RtaLoadBase);
+
+ /*
+ ** If this host is in slave mode, send the RTA an invalid boot
+ ** sequence command block to force it to kill the boot. We wait
+ ** for half a second before sending this packet to prevent the RTA
+ ** attempting to boot too often. The master host should then grab
+ ** the RTA and make it its own.
+ */
+ p->RIOBooting++;
+ RIOQueueCmdBlk( HostP, Rup, CmdBlkP );
+ return 1;
+ }
+
+ /*
+ ** It is a request for boot data.
+ */
+ sequence = RWORD(PktCmdP->Sequence);
+
+ rio_dprintk (RIO_DEBUG_BOOT, "Boot block %d on Host %d Rup%d\n",sequence,HostP-p->RIOHosts,Rup);
+
+ if ( sequence >= p->RIONumBootPkts ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "Got a request for packet %d, max is %d\n", sequence,
+ p->RIONumBootPkts);
+ ShowPacket( DBG_BOOT, PacketP );
+ }
+
+ PktReplyP->Sequence = sequence;
+
+ bcopy( p->RIOBootPackets[ p->RIONumBootPkts - sequence - 1 ],
+ PktReplyP->BootData, RTA_BOOT_DATA_SIZE );
+
+ CmdBlkP->Packet.len = PKT_MAX_DATA_LEN;
+ ShowPacket( DBG_BOOT, &CmdBlkP->Packet );
+ RIOQueueCmdBlk( HostP, Rup, CmdBlkP );
+ return 1;
+}
+
+/*
+** This function is called when an RTA been booted.
+** If booted by a host, HostP->HostUniqueNum is the booting host.
+** If booted by an RTA, HostP->Mapping[Rup].RtaUniqueNum is the booting RTA.
+** RtaUniq is the booted RTA.
+*/
+static int RIOBootComplete( struct rio_info *p, struct Host *HostP, uint Rup, struct PktCmd *PktCmdP )
+{
+ struct Map *MapP = NULL;
+ struct Map *MapP2 = NULL;
+ int Flag;
+ int found;
+ int host, rta;
+ int EmptySlot = -1;
+ int entry, entry2;
+ char *MyType, *MyName;
+ uint MyLink;
+ ushort RtaType;
+ uint RtaUniq = (RBYTE(PktCmdP->UniqNum[0])) +
+ (RBYTE(PktCmdP->UniqNum[1]) << 8) +
+ (RBYTE(PktCmdP->UniqNum[2]) << 16) +
+ (RBYTE(PktCmdP->UniqNum[3]) << 24);
+
+ /* Was RIOBooting-- . That's bad. If an RTA sends two of them, the
+ driver will never think that the RTA has booted... -- REW */
+ p->RIOBooting = 0;
+
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot completed - BootInProgress now %d\n", p->RIOBooting);
+
+ /*
+ ** Determine type of unit (16/8 port RTA).
+ */
+ RtaType = GetUnitType(RtaUniq);
+ if ( Rup >= (ushort)MAX_RUP ) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RIO: Host %s has booted an RTA(%d) on link %c\n",
+ HostP->Name, 8 * RtaType, RBYTE(PktCmdP->LinkNum)+'A');
+ } else {
+ rio_dprintk (RIO_DEBUG_BOOT, "RIO: RTA %s has booted an RTA(%d) on link %c\n",
+ HostP->Mapping[Rup].Name, 8 * RtaType,
+ RBYTE(PktCmdP->LinkNum)+'A');
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "UniqNum is 0x%x\n",RtaUniq);
+
+ if ( ( RtaUniq == 0x00000000 ) || ( RtaUniq == 0xffffffff ) )
+ {
+ rio_dprintk (RIO_DEBUG_BOOT, "Illegal RTA Uniq Number\n");
+ return TRUE;
+ }
+
+ /*
+ ** If this RTA has just booted an RTA which doesn't belong to this
+ ** system, or the system is in slave mode, do not attempt to create
+ ** a new table entry for it.
+ */
+ if (!RIOBootOk(p, HostP, RtaUniq))
+ {
+ MyLink = RBYTE(PktCmdP->LinkNum);
+ if (Rup < (ushort) MAX_RUP)
+ {
+ /*
+ ** RtaUniq was clone booted (by this RTA). Instruct this RTA
+ ** to hold off further attempts to boot on this link for 30
+ ** seconds.
+ */
+ if (RIOSuspendBootRta(HostP, HostP->Mapping[Rup].ID, MyLink))
+ {
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA failed to suspend booting on link %c\n",
+ 'A' + MyLink);
+ }
+ }
+ else
+ {
+ /*
+ ** RtaUniq was booted by this host. Set the booting link
+ ** to hold off for 30 seconds to give another unit a
+ ** chance to boot it.
+ */
+ WWORD(HostP->LinkStrP[MyLink].WaitNoBoot, 30);
+ }
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA %x not owned - suspend booting down link %c on unit %x\n",
+ RtaUniq, 'A' + MyLink, HostP->Mapping[Rup].RtaUniqueNum);
+ return TRUE;
+ }
+
+ /*
+ ** Check for a SLOT_IN_USE entry for this RTA attached to the
+ ** current host card in the driver table.
+ **
+ ** If it exists, make a note that we have booted it. Other parts of
+ ** the driver are interested in this information at a later date,
+ ** in particular when the booting RTA asks for an ID for this unit,
+ ** we must have set the BOOTED flag, and the NEWBOOT flag is used
+ ** to force an open on any ports that where previously open on this
+ ** unit.
+ */
+ for ( entry=0; entry<MAX_RUP; entry++ )
+ {
+ uint sysport;
+
+ if ((HostP->Mapping[entry].Flags & SLOT_IN_USE) &&
+ (HostP->Mapping[entry].RtaUniqueNum==RtaUniq))
+ {
+ HostP->Mapping[entry].Flags |= RTA_BOOTED|RTA_NEWBOOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+ if ( (sysport=HostP->Mapping[entry].SysPort) != NO_PORT )
+ {
+ if ( sysport < p->RIOFirstPortsBooted )
+ p->RIOFirstPortsBooted = sysport;
+ if ( sysport > p->RIOLastPortsBooted )
+ p->RIOLastPortsBooted = sysport;
+ /*
+ ** For a 16 port RTA, check the second bank of 8 ports
+ */
+ if (RtaType == TYPE_RTA16)
+ {
+ entry2 = HostP->Mapping[entry].ID2 - 1;
+ HostP->Mapping[entry2].Flags |= RTA_BOOTED|RTA_NEWBOOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+ sysport = HostP->Mapping[entry2].SysPort;
+ if ( sysport < p->RIOFirstPortsBooted )
+ p->RIOFirstPortsBooted = sysport;
+ if ( sysport > p->RIOLastPortsBooted )
+ p->RIOLastPortsBooted = sysport;
+ }
+ }
+ if (RtaType == TYPE_RTA16) {
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA will be given IDs %d+%d\n",
+ entry+1, entry2+1);
+ } else {
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA will be given ID %d\n",entry+1);
+ }
+ return TRUE;
+ }
+ }
+
+ rio_dprintk (RIO_DEBUG_BOOT, "RTA not configured for this host\n");
+
+ if ( Rup >= (ushort)MAX_RUP )
+ {
+ /*
+ ** It was a host that did the booting
+ */
+ MyType = "Host";
+ MyName = HostP->Name;
+ }
+ else
+ {
+ /*
+ ** It was an RTA that did the booting
+ */
+ MyType = "RTA";
+ MyName = HostP->Mapping[Rup].Name;
+ }
+#ifdef CHECK
+ CheckString(MyType);
+ CheckString(MyName);
+#endif
+
+ MyLink = RBYTE(PktCmdP->LinkNum);
+
+ /*
+ ** There is no SLOT_IN_USE entry for this RTA attached to the current
+ ** host card in the driver table.
+ **
+ ** Check for a SLOT_TENTATIVE entry for this RTA attached to the
+ ** current host card in the driver table.
+ **
+ ** If we find one, then we re-use that slot.
+ */
+ for ( entry=0; entry<MAX_RUP; entry++ )
+ {
+ if ( (HostP->Mapping[entry].Flags & SLOT_TENTATIVE) &&
+ (HostP->Mapping[entry].RtaUniqueNum == RtaUniq) )
+ {
+ if (RtaType == TYPE_RTA16)
+ {
+ entry2 = HostP->Mapping[entry].ID2 - 1;
+ if ( (HostP->Mapping[entry2].Flags & SLOT_TENTATIVE) &&
+ (HostP->Mapping[entry2].RtaUniqueNum == RtaUniq) )
+ rio_dprintk (RIO_DEBUG_BOOT, "Found previous tentative slots (%d+%d)\n",
+ entry, entry2);
+ else
+ continue;
+ }
+ else
+ rio_dprintk (RIO_DEBUG_BOOT, "Found previous tentative slot (%d)\n",entry);
+ if (! p->RIONoMessage)
+ cprintf("RTA connected to %s '%s' (%c) not configured.\n",MyType,MyName,MyLink+'A');
+ return TRUE;
+ }
+ }
+
+ /*
+ ** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+ ** attached to the current host card in the driver table.
+ **
+ ** Check if there is a SLOT_IN_USE or SLOT_TENTATIVE entry on another
+ ** host for this RTA in the driver table.
+ **
+ ** For a SLOT_IN_USE entry on another host, we need to delete the RTA
+ ** entry from the other host and add it to this host (using some of
+ ** the functions from table.c which do this).
+ ** For a SLOT_TENTATIVE entry on another host, we must cope with the
+ ** following scenario:
+ **
+ ** + Plug 8 port RTA into host A. (This creates SLOT_TENTATIVE entry
+ ** in table)
+ ** + Unplug RTA and plug into host B. (We now have 2 SLOT_TENTATIVE
+ ** entries)
+ ** + Configure RTA on host B. (This slot now becomes SLOT_IN_USE)
+ ** + Unplug RTA and plug back into host A.
+ ** + Configure RTA on host A. We now have the same RTA configured
+ ** with different ports on two different hosts.
+ */
+ rio_dprintk (RIO_DEBUG_BOOT, "Have we seen RTA %x before?\n", RtaUniq );
+ found = 0;
+ Flag = 0; /* Convince the compiler this variable is initialized */
+ for ( host = 0; !found && (host < p->RIONumHosts); host++ )
+ {
+ for ( rta=0; rta<MAX_RUP; rta++ )
+ {
+ if ((p->RIOHosts[host].Mapping[rta].Flags &
+ (SLOT_IN_USE | SLOT_TENTATIVE)) &&
+ (p->RIOHosts[host].Mapping[rta].RtaUniqueNum==RtaUniq))
+ {
+ Flag = p->RIOHosts[host].Mapping[rta].Flags;
+ MapP = &p->RIOHosts[host].Mapping[rta];
+ if (RtaType == TYPE_RTA16)
+ {
+ MapP2 = &p->RIOHosts[host].Mapping[MapP->ID2 - 1];
+ rio_dprintk (RIO_DEBUG_BOOT, "This RTA is units %d+%d from host %s\n",
+ rta+1, MapP->ID2, p->RIOHosts[host].Name);
+ }
+ else
+ rio_dprintk (RIO_DEBUG_BOOT, "This RTA is unit %d from host %s\n",
+ rta+1, p->RIOHosts[host].Name);
+ found = 1;
+ break;
+ }
+ }
+ }
+
+ /*
+ ** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+ ** attached to the current host card in the driver table.
+ **
+ ** If we have not found a SLOT_IN_USE or SLOT_TENTATIVE entry on
+ ** another host for this RTA in the driver table...
+ **
+ ** Check for a SLOT_IN_USE entry for this RTA in the config table.
+ */
+ if ( !MapP )
+ {
+ rio_dprintk (RIO_DEBUG_BOOT, "Look for RTA %x in RIOSavedTable\n",RtaUniq);
+ for ( rta=0; rta < TOTAL_MAP_ENTRIES; rta++ )
+ {
+ rio_dprintk (RIO_DEBUG_BOOT, "Check table entry %d (%x)",
+ rta,
+ p->RIOSavedTable[rta].RtaUniqueNum);
+
+ if ( (p->RIOSavedTable[rta].Flags & SLOT_IN_USE) &&
+ (p->RIOSavedTable[rta].RtaUniqueNum == RtaUniq) )
+ {
+ MapP = &p->RIOSavedTable[rta];
+ Flag = p->RIOSavedTable[rta].Flags;
+ if (RtaType == TYPE_RTA16)
+ {
+ for (entry2 = rta + 1; entry2 < TOTAL_MAP_ENTRIES;
+ entry2++)
+ {
+ if (p->RIOSavedTable[entry2].RtaUniqueNum == RtaUniq)
+ break;
+ }
+ MapP2 = &p->RIOSavedTable[entry2];
+ rio_dprintk (RIO_DEBUG_BOOT, "This RTA is from table entries %d+%d\n",
+ rta, entry2);
+ }
+ else
+ rio_dprintk (RIO_DEBUG_BOOT, "This RTA is from table entry %d\n", rta);
+ break;
+ }
+ }
+ }
+
+ /*
+ ** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+ ** attached to the current host card in the driver table.
+ **
+ ** We may have found a SLOT_IN_USE entry on another host for this
+ ** RTA in the config table, or a SLOT_IN_USE or SLOT_TENTATIVE entry
+ ** on another host for this RTA in the driver table.
+ **
+ ** Check the driver table for room to fit this newly discovered RTA.
+ ** RIOFindFreeID() first looks for free slots and if it does not
+ ** find any free slots it will then attempt to oust any
+ ** tentative entry in the table.
+ */
+ EmptySlot = 1;
+ if (RtaType == TYPE_RTA16)
+ {
+ if (RIOFindFreeID(p, HostP, &entry, &entry2) == 0)
+ {
+ RIODefaultName(p, HostP, entry);
+ FillSlot(entry, entry2, RtaUniq, HostP);
+ EmptySlot = 0;
+ }
+ }
+ else
+ {
+ if (RIOFindFreeID(p, HostP, &entry, NULL) == 0)
+ {
+ RIODefaultName(p, HostP, entry);
+ FillSlot(entry, 0, RtaUniq, HostP);
+ EmptySlot = 0;
+ }
+ }
+
+ /*
+ ** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+ ** attached to the current host card in the driver table.
+ **
+ ** If we found a SLOT_IN_USE entry on another host for this
+ ** RTA in the config or driver table, and there are enough free
+ ** slots in the driver table, then we need to move it over and
+ ** delete it from the other host.
+ ** If we found a SLOT_TENTATIVE entry on another host for this
+ ** RTA in the driver table, just delete the other host entry.
+ */
+ if (EmptySlot == 0)
+ {
+ if ( MapP )
+ {
+ if (Flag & SLOT_IN_USE)
+ {
+ rio_dprintk (RIO_DEBUG_BOOT,
+ "This RTA configured on another host - move entry to current host (1)\n");
+ HostP->Mapping[entry].SysPort = MapP->SysPort;
+ CCOPY( MapP->Name, HostP->Mapping[entry].Name, MAX_NAME_LEN );
+ HostP->Mapping[entry].Flags =
+ SLOT_IN_USE | RTA_BOOTED | RTA_NEWBOOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+ RIOReMapPorts( p, HostP, &HostP->Mapping[entry] );
+ if ( HostP->Mapping[entry].SysPort < p->RIOFirstPortsBooted )
+ p->RIOFirstPortsBooted = HostP->Mapping[entry].SysPort;
+ if ( HostP->Mapping[entry].SysPort > p->RIOLastPortsBooted )
+ p->RIOLastPortsBooted = HostP->Mapping[entry].SysPort;
+ rio_dprintk (RIO_DEBUG_BOOT, "SysPort %d, Name %s\n",(int)MapP->SysPort,MapP->Name);
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_BOOT,
+ "This RTA has a tentative entry on another host - delete that entry (1)\n");
+ HostP->Mapping[entry].Flags =
+ SLOT_TENTATIVE | RTA_BOOTED | RTA_NEWBOOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+ }
+ if (RtaType == TYPE_RTA16)
+ {
+ if (Flag & SLOT_IN_USE)
+ {
+ HostP->Mapping[entry2].Flags = SLOT_IN_USE |
+ RTA_BOOTED | RTA_NEWBOOT | RTA16_SECOND_SLOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+ HostP->Mapping[entry2].SysPort = MapP2->SysPort;
+ /*
+ ** Map second block of ttys for 16 port RTA
+ */
+ RIOReMapPorts( p, HostP, &HostP->Mapping[entry2] );
+ if (HostP->Mapping[entry2].SysPort < p->RIOFirstPortsBooted)
+ p->RIOFirstPortsBooted = HostP->Mapping[entry2].SysPort;
+ if (HostP->Mapping[entry2].SysPort > p->RIOLastPortsBooted)
+ p->RIOLastPortsBooted = HostP->Mapping[entry2].SysPort;
+ rio_dprintk (RIO_DEBUG_BOOT, "SysPort %d, Name %s\n",
+ (int)HostP->Mapping[entry2].SysPort,
+ HostP->Mapping[entry].Name);
+ }
+ else
+ HostP->Mapping[entry2].Flags = SLOT_TENTATIVE |
+ RTA_BOOTED | RTA_NEWBOOT | RTA16_SECOND_SLOT;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+ bzero( (caddr_t)MapP2, sizeof(struct Map) );
+ }
+ bzero( (caddr_t)MapP, sizeof(struct Map) );
+ if (! p->RIONoMessage)
+ cprintf("An orphaned RTA has been adopted by %s '%s' (%c).\n",MyType,MyName,MyLink+'A');
+ }
+ else if (! p->RIONoMessage)
+ cprintf("RTA connected to %s '%s' (%c) not configured.\n",MyType,MyName,MyLink+'A');
+ RIOSetChange(p);
+ return TRUE;
+ }
+
+ /*
+ ** There is no room in the driver table to make an entry for the
+ ** booted RTA. Keep a note of its Uniq Num in the overflow table,
+ ** so we can ignore it's ID requests.
+ */
+ if (! p->RIONoMessage)
+ cprintf("The RTA connected to %s '%s' (%c) cannot be configured. You cannot configure more than 128 ports to one host card.\n",MyType,MyName,MyLink+'A');
+ for ( entry=0; entry<HostP->NumExtraBooted; entry++ )
+ {
+ if ( HostP->ExtraUnits[entry] == RtaUniq )
+ {
+ /*
+ ** already got it!
+ */
+ return TRUE;
+ }
+ }
+ /*
+ ** If there is room, add the unit to the list of extras
+ */
+ if ( HostP->NumExtraBooted < MAX_EXTRA_UNITS )
+ HostP->ExtraUnits[HostP->NumExtraBooted++] = RtaUniq;
+ return TRUE;
+}
+
+
+/*
+** If the RTA or its host appears in the RIOBindTab[] structure then
+** we mustn't boot the RTA and should return FALSE.
+** This operation is slightly different from the other drivers for RIO
+** in that this is designed to work with the new utilities
+** not config.rio and is FAR SIMPLER.
+** We no longer support the RIOBootMode variable. It is all done from the
+** "boot/noboot" field in the rio.cf file.
+*/
+int
+RIOBootOk(p, HostP, RtaUniq)
+struct rio_info * p;
+struct Host * HostP;
+ulong RtaUniq;
+{
+ int Entry;
+ uint HostUniq = HostP->UniqueNum;
+
+ /*
+ ** Search bindings table for RTA or its parent.
+ ** If it exists, return 0, else 1.
+ */
+ for (Entry = 0;
+ ( Entry < MAX_RTA_BINDINGS ) && ( p->RIOBindTab[Entry] != 0 );
+ Entry++)
+ {
+ if ( (p->RIOBindTab[Entry] == HostUniq) ||
+ (p->RIOBindTab[Entry] == RtaUniq) )
+ return 0;
+ }
+ return 1;
+}
+
+/*
+** Make an empty slot tentative. If this is a 16 port RTA, make both
+** slots tentative, and the second one RTA_SECOND_SLOT as well.
+*/
+
+void
+FillSlot(entry, entry2, RtaUniq, HostP)
+int entry;
+int entry2;
+uint RtaUniq;
+struct Host *HostP;
+{
+ int link;
+
+ rio_dprintk (RIO_DEBUG_BOOT, "FillSlot(%d, %d, 0x%x...)\n", entry, entry2, RtaUniq);
+
+ HostP->Mapping[entry].Flags = (RTA_BOOTED | RTA_NEWBOOT | SLOT_TENTATIVE);
+ HostP->Mapping[entry].SysPort = NO_PORT;
+ HostP->Mapping[entry].RtaUniqueNum = RtaUniq;
+ HostP->Mapping[entry].HostUniqueNum = HostP->UniqueNum;
+ HostP->Mapping[entry].ID = entry + 1;
+ HostP->Mapping[entry].ID2 = 0;
+ if (entry2) {
+ HostP->Mapping[entry2].Flags = (RTA_BOOTED | RTA_NEWBOOT |
+ SLOT_TENTATIVE | RTA16_SECOND_SLOT);
+ HostP->Mapping[entry2].SysPort = NO_PORT;
+ HostP->Mapping[entry2].RtaUniqueNum = RtaUniq;
+ HostP->Mapping[entry2].HostUniqueNum = HostP->UniqueNum;
+ HostP->Mapping[entry2].Name[0] = '\0';
+ HostP->Mapping[entry2].ID = entry2 + 1;
+ HostP->Mapping[entry2].ID2 = entry + 1;
+ HostP->Mapping[entry].ID2 = entry2 + 1;
+ }
+ /*
+ ** Must set these up, so that utilities show
+ ** topology of 16 port RTAs correctly
+ */
+ for ( link=0; link<LINKS_PER_UNIT; link++ ) {
+ HostP->Mapping[entry].Topology[link].Unit = ROUTE_DISCONNECT;
+ HostP->Mapping[entry].Topology[link].Link = NO_LINK;
+ if (entry2) {
+ HostP->Mapping[entry2].Topology[link].Unit = ROUTE_DISCONNECT;
+ HostP->Mapping[entry2].Topology[link].Link = NO_LINK;
+ }
+ }
+}
+
+#if 0
+/*
+ Function: This function is to disable the disk interrupt
+ Returns : Nothing
+*/
+void
+disable_interrupt(vector)
+int vector;
+{
+ int ps;
+ int val;
+
+ disable(ps);
+ if (vector > 40) {
+ val = 1 << (vector - 40);
+ __outb(S8259+1, __inb(S8259+1) | val);
+ }
+ else {
+ val = 1 << (vector - 32);
+ __outb(M8259+1, __inb(M8259+1) | val);
+ }
+ restore(ps);
+}
+
+/*
+ Function: This function is to enable the disk interrupt
+ Returns : Nothing
+*/
+void
+enable_interrupt(vector)
+int vector;
+{
+ int ps;
+ int val;
+
+ disable(ps);
+ if (vector > 40) {
+ val = 1 << (vector - 40);
+ val = ~val;
+ __outb(S8259+1, __inb(S8259+1) & val);
+ }
+ else {
+ val = 1 << (vector - 32);
+ val = ~val;
+ __outb(M8259+1, __inb(M8259+1) & val);
+ }
+ restore(ps);
+}
+#endif
diff --git a/drivers/char/rio/riocmd.c b/drivers/char/rio/riocmd.c
new file mode 100644
index 000000000000..533085ec6f1b
--- /dev/null
+++ b/drivers/char/rio/riocmd.c
@@ -0,0 +1,1041 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** ported from the existing SCO driver source
+**
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riocmd.c
+** SID : 1.2
+** Last Modified : 11/6/98 10:33:41
+** Retrieved : 11/6/98 10:33:49
+**
+** ident @(#)riocmd.c 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riocmd_c_sccs_ = "@(#)riocmd.c 1.2";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+
+
+static struct IdentifyRta IdRta;
+static struct KillNeighbour KillUnit;
+
+int
+RIOFoadRta(struct Host *HostP, struct Map *MapP)
+{
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA\n");
+
+ CmdBlkP = RIOGetCmdBlk();
+
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA: GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+
+ CmdBlkP->Packet.dest_unit = MapP->ID;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+ CmdBlkP->Packet.len = 0x84;
+ CmdBlkP->Packet.data[0] = IFOAD;
+ CmdBlkP->Packet.data[1] = 0;
+ CmdBlkP->Packet.data[2] = IFOAD_MAGIC & 0xFF;
+ CmdBlkP->Packet.data[3] = (IFOAD_MAGIC >> 8) & 0xFF;
+
+ if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA: Failed to queue foad command\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+int
+RIOZombieRta(struct Host *HostP, struct Map *MapP)
+{
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA\n");
+
+ CmdBlkP = RIOGetCmdBlk();
+
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA: GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+
+ CmdBlkP->Packet.dest_unit = MapP->ID;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+ CmdBlkP->Packet.len = 0x84;
+ CmdBlkP->Packet.data[0] = ZOMBIE;
+ CmdBlkP->Packet.data[1] = 0;
+ CmdBlkP->Packet.data[2] = ZOMBIE_MAGIC & 0xFF;
+ CmdBlkP->Packet.data[3] = (ZOMBIE_MAGIC >> 8) & 0xFF;
+
+ if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA: Failed to queue zombie command\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+int
+RIOCommandRta(struct rio_info *p, uint RtaUnique,
+ int (* func)(struct Host *HostP, struct Map *MapP))
+{
+ uint Host;
+
+ rio_dprintk (RIO_DEBUG_CMD, "Command RTA 0x%x func 0x%x\n", RtaUnique, (int)func);
+
+ if ( !RtaUnique )
+ return(0);
+
+ for ( Host = 0; Host < p->RIONumHosts; Host++ ) {
+ uint Rta;
+ struct Host *HostP = &p->RIOHosts[Host];
+
+ for ( Rta = 0; Rta < RTAS_PER_HOST; Rta++ ) {
+ struct Map *MapP = &HostP->Mapping[Rta];
+
+ if ( MapP->RtaUniqueNum == RtaUnique ) {
+ uint Link;
+
+ /*
+ ** now, lets just check we have a route to it...
+ ** IF the routing stuff is working, then one of the
+ ** topology entries for this unit will have a legit
+ ** route *somewhere*. We care not where - if its got
+ ** any connections, we can get to it.
+ */
+ for ( Link = 0; Link < LINKS_PER_UNIT; Link++ ) {
+ if ( MapP->Topology[Link].Unit <= (uchar)MAX_RUP ) {
+ /*
+ ** Its worth trying the operation...
+ */
+ return (*func)( HostP, MapP );
+ }
+ }
+ }
+ }
+ }
+ return -ENXIO;
+}
+
+
+int
+RIOIdentifyRta(struct rio_info *p, caddr_t arg)
+{
+ uint Host;
+
+ if ( copyin( (int)arg, (caddr_t)&IdRta, sizeof(IdRta) ) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "RIO_IDENTIFY_RTA copy failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+
+ for ( Host = 0 ; Host < p->RIONumHosts; Host++ ) {
+ uint Rta;
+ struct Host *HostP = &p->RIOHosts[Host];
+
+ for ( Rta = 0; Rta < RTAS_PER_HOST; Rta++ ) {
+ struct Map *MapP = &HostP->Mapping[Rta];
+
+ if ( MapP->RtaUniqueNum == IdRta.RtaUnique ) {
+ uint Link;
+ /*
+ ** now, lets just check we have a route to it...
+ ** IF the routing stuff is working, then one of the
+ ** topology entries for this unit will have a legit
+ ** route *somewhere*. We care not where - if its got
+ ** any connections, we can get to it.
+ */
+ for ( Link = 0; Link < LINKS_PER_UNIT; Link++ ) {
+ if ( MapP->Topology[Link].Unit <= (uchar)MAX_RUP ) {
+ /*
+ ** Its worth trying the operation...
+ */
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA\n");
+
+ CmdBlkP = RIOGetCmdBlk();
+
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA: GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+
+ CmdBlkP->Packet.dest_unit = MapP->ID;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+ CmdBlkP->Packet.len = 0x84;
+ CmdBlkP->Packet.data[0] = IDENTIFY;
+ CmdBlkP->Packet.data[1] = 0;
+ CmdBlkP->Packet.data[2] = IdRta.ID;
+
+ if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA: Failed to queue command\n");
+ return -EIO;
+ }
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ return -ENOENT;
+}
+
+
+int
+RIOKillNeighbour(struct rio_info *p, caddr_t arg)
+{
+ uint Host;
+ uint ID;
+ struct Host *HostP;
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "KILL HOST NEIGHBOUR\n");
+
+ if ( copyin( (int)arg, (caddr_t)&KillUnit, sizeof(KillUnit) ) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "RIO_KILL_NEIGHBOUR copy failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+
+ if ( KillUnit.Link > 3 )
+ return -ENXIO;
+
+ CmdBlkP = RIOGetCmdBlk();
+
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "UFOAD: GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+
+ CmdBlkP->Packet.dest_unit = 0;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+ CmdBlkP->Packet.len = 0x84;
+ CmdBlkP->Packet.data[0] = UFOAD;
+ CmdBlkP->Packet.data[1] = KillUnit.Link;
+ CmdBlkP->Packet.data[2] = UFOAD_MAGIC & 0xFF;
+ CmdBlkP->Packet.data[3] = (UFOAD_MAGIC >> 8) & 0xFF;
+
+ for ( Host = 0; Host < p->RIONumHosts; Host++ ) {
+ ID = 0;
+ HostP = &p->RIOHosts[Host];
+
+ if ( HostP->UniqueNum == KillUnit.UniqueNum ) {
+ if ( RIOQueueCmdBlk( HostP, RTAS_PER_HOST+KillUnit.Link,
+ CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "UFOAD: Failed queue command\n");
+ return -EIO;
+ }
+ return 0;
+ }
+
+ for ( ID=0; ID < RTAS_PER_HOST; ID++ ) {
+ if ( HostP->Mapping[ID].RtaUniqueNum == KillUnit.UniqueNum ) {
+ CmdBlkP->Packet.dest_unit = ID+1;
+ if ( RIOQueueCmdBlk( HostP, ID, CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "UFOAD: Failed queue command\n");
+ return -EIO;
+ }
+ return 0;
+ }
+ }
+ }
+ RIOFreeCmdBlk( CmdBlkP );
+ return -ENXIO;
+}
+
+int
+RIOSuspendBootRta(struct Host *HostP, int ID, int Link)
+{
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA ID %d, link %c\n", ID, 'A' + Link);
+
+ CmdBlkP = RIOGetCmdBlk();
+
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA: GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+
+ CmdBlkP->Packet.dest_unit = ID;
+ CmdBlkP->Packet.dest_port = BOOT_RUP;
+ CmdBlkP->Packet.src_unit = 0;
+ CmdBlkP->Packet.src_port = BOOT_RUP;
+ CmdBlkP->Packet.len = 0x84;
+ CmdBlkP->Packet.data[0] = IWAIT;
+ CmdBlkP->Packet.data[1] = Link;
+ CmdBlkP->Packet.data[2] = IWAIT_MAGIC & 0xFF;
+ CmdBlkP->Packet.data[3] = (IWAIT_MAGIC >> 8) & 0xFF;
+
+ if ( RIOQueueCmdBlk( HostP, ID - 1, CmdBlkP) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA: Failed to queue iwait command\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+int
+RIOFoadWakeup(struct rio_info *p)
+{
+ int port;
+ register struct Port *PortP;
+ unsigned long flags;
+
+ for ( port=0; port<RIO_PORTS; port++) {
+ PortP = p->RIOPortp[port];
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->Config = 0;
+ PortP->State = 0;
+ PortP->InUse = NOT_INUSE;
+ PortP->PortState = 0;
+ PortP->FlushCmdBodge = 0;
+ PortP->ModemLines = 0;
+ PortP->ModemState = 0;
+ PortP->CookMode = 0;
+ PortP->ParamSem = 0;
+ PortP->Mapped = 0;
+ PortP->WflushFlag = 0;
+ PortP->MagicFlags = 0;
+ PortP->RxDataStart = 0;
+ PortP->TxBufferIn = 0;
+ PortP->TxBufferOut = 0;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ return(0);
+}
+
+/*
+** Incoming command on the COMMAND_RUP to be processed.
+*/
+static int
+RIOCommandRup(struct rio_info *p, uint Rup, struct Host *HostP, PKT *PacketP)
+{
+ struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+ struct Port *PortP;
+ struct UnixRup *UnixRupP;
+ ushort SysPort;
+ ushort ReportedModemStatus;
+ ushort rup;
+ ushort subCommand;
+ unsigned long flags;
+
+ func_enter ();
+
+#ifdef CHECK
+ CheckHost( Host );
+ CheckHostP( HostP );
+ CheckPacketP( PacketP );
+#endif
+
+ /*
+ ** 16 port RTA note:
+ ** Command rup packets coming from the RTA will have pkt->data[1] (which
+ ** translates to PktCmdP->PhbNum) set to the host port number for the
+ ** particular unit. To access the correct BaseSysPort for a 16 port RTA,
+ ** we can use PhbNum to get the rup number for the appropriate 8 port
+ ** block (for the first block, this should be equal to 'Rup').
+ */
+ rup = RBYTE(PktCmdP->PhbNum) / (ushort)PORTS_PER_RTA;
+ UnixRupP = &HostP->UnixRups[rup];
+ SysPort = UnixRupP->BaseSysPort +
+ (RBYTE(PktCmdP->PhbNum) % (ushort)PORTS_PER_RTA);
+ rio_dprintk (RIO_DEBUG_CMD, "Command on rup %d, port %d\n", rup, SysPort);
+
+#ifdef CHECK
+ CheckRup( rup );
+ CheckUnixRupP( UnixRupP );
+#endif
+ if ( UnixRupP->BaseSysPort == NO_PORT ) {
+ rio_dprintk (RIO_DEBUG_CMD, "OBSCURE ERROR!\n");
+ rio_dprintk (RIO_DEBUG_CMD, "Diagnostics follow. Please WRITE THESE DOWN and report them to Specialix Technical Support\n");
+ rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: Host number %d, name ``%s''\n",
+ HostP-p->RIOHosts, HostP->Name );
+ rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: Rup number 0x%x\n", rup);
+
+ if ( Rup >= (ushort)MAX_RUP ) {
+ rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: This is the RUP for RTA ``%s''\n",
+ HostP->Mapping[Rup].Name);
+ } else
+ rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: This is the RUP for link ``%c'' of host ``%s''\n",
+ ('A' + Rup - MAX_RUP), HostP->Name);
+
+ rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Destination 0x%x:0x%x\n",
+ PacketP->dest_unit, PacketP->dest_port );
+ rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Source 0x%x:0x%x\n",
+ PacketP->src_unit, PacketP->src_port );
+ rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Length 0x%x (%d)\n", PacketP->len,PacketP->len );
+ rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Control 0x%x (%d)\n", PacketP->control, PacketP->control);
+ rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Check 0x%x (%d)\n", PacketP->csum, PacketP->csum );
+ rio_dprintk (RIO_DEBUG_CMD, "COMMAND information: Host Port Number 0x%x, "
+ "Command Code 0x%x\n", PktCmdP->PhbNum, PktCmdP->Command );
+ return TRUE;
+ }
+
+#ifdef CHECK
+ CheckSysPort( SysPort );
+#endif
+ PortP = p->RIOPortp[ SysPort ];
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ switch( RBYTE(PktCmdP->Command) ) {
+ case BREAK_RECEIVED:
+ rio_dprintk (RIO_DEBUG_CMD, "Received a break!\n");
+ /* If the current line disc. is not multi-threading and
+ the current processor is not the default, reset rup_intr
+ and return FALSE to ensure that the command packet is
+ not freed. */
+ /* Call tmgr HANGUP HERE */
+ /* Fix this later when every thing works !!!! RAMRAJ */
+ gs_got_break (&PortP->gs);
+ break;
+
+ case COMPLETE:
+ rio_dprintk (RIO_DEBUG_CMD, "Command complete on phb %d host %d\n",
+ RBYTE(PktCmdP->PhbNum), HostP-p->RIOHosts);
+ subCommand = 1;
+ switch (RBYTE(PktCmdP->SubCommand)) {
+ case MEMDUMP :
+ rio_dprintk (RIO_DEBUG_CMD, "Memory dump cmd (0x%x) from addr 0x%x\n",
+ RBYTE(PktCmdP->SubCommand), RWORD(PktCmdP->SubAddr));
+ break;
+ case READ_REGISTER :
+ rio_dprintk (RIO_DEBUG_CMD, "Read register (0x%x)\n", RWORD(PktCmdP->SubAddr));
+ p->CdRegister = (RBYTE(PktCmdP->ModemStatus) & MSVR1_HOST);
+ break;
+ default :
+ subCommand = 0;
+ break;
+ }
+ if (subCommand)
+ break;
+ rio_dprintk (RIO_DEBUG_CMD, "New status is 0x%x was 0x%x\n",
+ RBYTE(PktCmdP->PortStatus),PortP->PortState);
+ if (PortP->PortState != RBYTE(PktCmdP->PortStatus)) {
+ rio_dprintk (RIO_DEBUG_CMD, "Mark status & wakeup\n");
+ PortP->PortState = RBYTE(PktCmdP->PortStatus);
+ /* What should we do here ...
+ wakeup( &PortP->PortState );
+ */
+ } else
+ rio_dprintk (RIO_DEBUG_CMD, "No change\n");
+
+ /* FALLTHROUGH */
+ case MODEM_STATUS:
+ /*
+ ** Knock out the tbusy and tstop bits, as these are not relevant
+ ** to the check for modem status change (they're just there because
+ ** it's a convenient place to put them!).
+ */
+ ReportedModemStatus = RBYTE(PktCmdP->ModemStatus);
+ if ((PortP->ModemState & MSVR1_HOST) ==
+ (ReportedModemStatus & MSVR1_HOST)) {
+ rio_dprintk (RIO_DEBUG_CMD, "Modem status unchanged 0x%x\n", PortP->ModemState);
+ /*
+ ** Update ModemState just in case tbusy or tstop states have
+ ** changed.
+ */
+ PortP->ModemState = ReportedModemStatus;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CMD, "Modem status change from 0x%x to 0x%x\n",
+ PortP->ModemState, ReportedModemStatus);
+ PortP->ModemState = ReportedModemStatus;
+#ifdef MODEM_SUPPORT
+ if ( PortP->Mapped ) {
+ /***********************************************************\
+ *************************************************************
+ *** ***
+ *** M O D E M S T A T E C H A N G E ***
+ *** ***
+ *************************************************************
+ \***********************************************************/
+ /*
+ ** If the device is a modem, then check the modem
+ ** carrier.
+ */
+ if (PortP->gs.tty == NULL)
+ break;
+ if (PortP->gs.tty->termios == NULL)
+ break;
+
+ if (!(PortP->gs.tty->termios->c_cflag & CLOCAL) &&
+ ((PortP->State & (RIO_MOPEN|RIO_WOPEN)))) {
+
+ rio_dprintk (RIO_DEBUG_CMD, "Is there a Carrier?\n");
+ /*
+ ** Is there a carrier?
+ */
+ if ( PortP->ModemState & MSVR1_CD ) {
+ /*
+ ** Has carrier just appeared?
+ */
+ if (!(PortP->State & RIO_CARR_ON)) {
+ rio_dprintk (RIO_DEBUG_CMD, "Carrier just came up.\n");
+ PortP->State |= RIO_CARR_ON;
+ /*
+ ** wakeup anyone in WOPEN
+ */
+ if (PortP->State & (PORT_ISOPEN | RIO_WOPEN) )
+ wake_up_interruptible (&PortP->gs.open_wait);
+#ifdef STATS
+ PortP->Stat.ModemOnCnt++;
+#endif
+ }
+ } else {
+ /*
+ ** Has carrier just dropped?
+ */
+ if (PortP->State & RIO_CARR_ON) {
+ if (PortP->State & (PORT_ISOPEN|RIO_WOPEN|RIO_MOPEN))
+ tty_hangup (PortP->gs.tty);
+ PortP->State &= ~RIO_CARR_ON;
+ rio_dprintk (RIO_DEBUG_CMD, "Carrirer just went down\n");
+#ifdef STATS
+ PortP->Stat.ModemOffCnt++;
+#endif
+ }
+ }
+ }
+ }
+#endif
+ }
+ break;
+
+ default:
+ rio_dprintk (RIO_DEBUG_CMD, "Unknown command %d on CMD_RUP of host %d\n",
+ RBYTE(PktCmdP->Command),HostP-p->RIOHosts);
+ break;
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+ func_exit ();
+
+ return TRUE;
+}
+/*
+** The command mechanism:
+** Each rup has a chain of commands associated with it.
+** This chain is maintained by routines in this file.
+** Periodically we are called and we run a quick check of all the
+** active chains to determine if there is a command to be executed,
+** and if the rup is ready to accept it.
+**
+*/
+
+/*
+** Allocate an empty command block.
+*/
+struct CmdBlk *
+RIOGetCmdBlk(void)
+{
+ struct CmdBlk *CmdBlkP;
+
+ CmdBlkP = (struct CmdBlk *)sysbrk(sizeof(struct CmdBlk));
+ if (CmdBlkP)
+ bzero(CmdBlkP, sizeof(struct CmdBlk));
+
+ return CmdBlkP;
+}
+
+/*
+** Return a block to the head of the free list.
+*/
+void
+RIOFreeCmdBlk(struct CmdBlk *CmdBlkP)
+{
+ sysfree((void *)CmdBlkP, sizeof(struct CmdBlk));
+}
+
+/*
+** attach a command block to the list of commands to be performed for
+** a given rup.
+*/
+int
+RIOQueueCmdBlk(struct Host *HostP, uint Rup, struct CmdBlk *CmdBlkP)
+{
+ struct CmdBlk **Base;
+ struct UnixRup *UnixRupP;
+ unsigned long flags;
+
+#ifdef CHECK
+ CheckHostP( HostP );
+ CheckRup( Rup );
+ CheckCmdBlkP( CmdBlkP );
+#endif
+ if ( Rup >= (ushort)(MAX_RUP+LINKS_PER_UNIT) ) {
+ rio_dprintk (RIO_DEBUG_CMD, "Illegal rup number %d in RIOQueueCmdBlk\n",Rup);
+ RIOFreeCmdBlk( CmdBlkP );
+ return RIO_FAIL;
+ }
+
+ UnixRupP = &HostP->UnixRups[Rup];
+
+ rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+
+ /*
+ ** If the RUP is currently inactive, then put the request
+ ** straight on the RUP....
+ */
+ if ( (UnixRupP->CmdsWaitingP == NULL) && (UnixRupP->CmdPendingP == NULL) &&
+ (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE ) &&
+ (CmdBlkP->PreFuncP ? (*CmdBlkP->PreFuncP)(CmdBlkP->PreArg,CmdBlkP)
+ :TRUE)) {
+ rio_dprintk (RIO_DEBUG_CMD, "RUP inactive-placing command straight on. Cmd byte is 0x%x\n",
+ CmdBlkP->Packet.data[0]);
+
+ /*
+ ** Whammy! blat that pack!
+ */
+ HostP->Copy( (caddr_t)&CmdBlkP->Packet,
+ RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt ), sizeof(PKT) );
+
+ /*
+ ** place command packet on the pending position.
+ */
+ UnixRupP->CmdPendingP = CmdBlkP;
+
+ /*
+ ** set the command register
+ */
+ WWORD(UnixRupP->RupP->txcontrol , TX_PACKET_READY);
+
+ rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+
+ return RIO_SUCCESS;
+ }
+ rio_dprintk (RIO_DEBUG_CMD, "RUP active - en-queing\n");
+
+ if ( UnixRupP->CmdsWaitingP != NULL)
+ rio_dprintk (RIO_DEBUG_CMD, "Rup active - command waiting\n");
+ if ( UnixRupP->CmdPendingP != NULL )
+ rio_dprintk (RIO_DEBUG_CMD, "Rup active - command pending\n");
+ if ( RWORD(UnixRupP->RupP->txcontrol) != TX_RUP_INACTIVE )
+ rio_dprintk (RIO_DEBUG_CMD, "Rup active - command rup not ready\n");
+
+ Base = &UnixRupP->CmdsWaitingP;
+
+ rio_dprintk (RIO_DEBUG_CMD, "First try to queue cmdblk 0x%x at 0x%x\n", (int)CmdBlkP,(int)Base);
+
+ while ( *Base ) {
+ rio_dprintk (RIO_DEBUG_CMD, "Command cmdblk 0x%x here\n", (int)(*Base));
+ Base = &((*Base)->NextP);
+ rio_dprintk (RIO_DEBUG_CMD, "Now try to queue cmd cmdblk 0x%x at 0x%x\n",
+ (int)CmdBlkP,(int)Base);
+ }
+
+ rio_dprintk (RIO_DEBUG_CMD, "Will queue cmdblk 0x%x at 0x%x\n",(int)CmdBlkP,(int)Base);
+
+ *Base = CmdBlkP;
+
+ CmdBlkP->NextP = NULL;
+
+ rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+
+ return RIO_SUCCESS;
+}
+
+/*
+** Here we go - if there is an empty rup, fill it!
+** must be called at splrio() or higher.
+*/
+void
+RIOPollHostCommands(struct rio_info *p, struct Host *HostP)
+{
+ register struct CmdBlk *CmdBlkP;
+ register struct UnixRup *UnixRupP;
+ struct PKT *PacketP;
+ ushort Rup;
+ unsigned long flags;
+
+
+ Rup = MAX_RUP+LINKS_PER_UNIT;
+
+ do { /* do this loop for each RUP */
+ /*
+ ** locate the rup we are processing & lock it
+ */
+ UnixRupP = &HostP->UnixRups[--Rup];
+
+ spin_lock_irqsave(&UnixRupP->RupLock, flags);
+
+ /*
+ ** First check for incoming commands:
+ */
+ if ( RWORD(UnixRupP->RupP->rxcontrol) != RX_RUP_INACTIVE ) {
+ int FreeMe;
+
+ PacketP =(PKT *)RIO_PTR(HostP->Caddr,RWORD(UnixRupP->RupP->rxpkt));
+
+ ShowPacket( DBG_CMD, PacketP );
+
+ switch ( RBYTE(PacketP->dest_port) ) {
+ case BOOT_RUP:
+ rio_dprintk (RIO_DEBUG_CMD, "Incoming Boot %s packet '%x'\n",
+ RBYTE(PacketP->len) & 0x80 ? "Command":"Data",
+ RBYTE(PacketP->data[0]));
+ rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+ FreeMe= RIOBootRup(p, Rup,HostP,PacketP);
+ rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+ break;
+
+ case COMMAND_RUP:
+ /*
+ ** Free the RUP lock as loss of carrier causes a
+ ** ttyflush which will (eventually) call another
+ ** routine that uses the RUP lock.
+ */
+ rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+ FreeMe= RIOCommandRup(p, Rup,HostP,PacketP);
+ if (PacketP->data[5] == MEMDUMP) {
+ rio_dprintk (RIO_DEBUG_CMD, "Memdump from 0x%x complete\n",
+ *(ushort *) &(PacketP->data[6]));
+ HostP->Copy( (caddr_t)&(PacketP->data[8]),
+ (caddr_t)p->RIOMemDump, 32 );
+ }
+ rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+ break;
+
+ case ROUTE_RUP:
+ rio_spin_unlock_irqrestore( &UnixRupP->RupLock, flags);
+ FreeMe = RIORouteRup(p, Rup, HostP, PacketP );
+ rio_spin_lock_irqsave( &UnixRupP->RupLock, flags );
+ break;
+
+ default:
+ rio_dprintk (RIO_DEBUG_CMD, "Unknown RUP %d\n", RBYTE(PacketP->dest_port));
+ FreeMe = 1;
+ break;
+ }
+
+ if ( FreeMe ) {
+ rio_dprintk (RIO_DEBUG_CMD, "Free processed incoming command packet\n");
+ put_free_end(HostP,PacketP);
+
+ WWORD(UnixRupP->RupP->rxcontrol , RX_RUP_INACTIVE);
+
+ if ( RWORD(UnixRupP->RupP->handshake)==PHB_HANDSHAKE_SET ) {
+ rio_dprintk (RIO_DEBUG_CMD, "Handshake rup %d\n",Rup);
+ WWORD(UnixRupP->RupP->handshake,
+ PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET);
+ }
+ }
+ }
+
+ /*
+ ** IF a command was running on the port,
+ ** and it has completed, then tidy it up.
+ */
+ if ( (CmdBlkP = UnixRupP->CmdPendingP) && /* ASSIGN! */
+ (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE)) {
+ /*
+ ** we are idle.
+ ** there is a command in pending.
+ ** Therefore, this command has finished.
+ ** So, wakeup whoever is waiting for it (and tell them
+ ** what happened).
+ */
+ if ( CmdBlkP->Packet.dest_port == BOOT_RUP )
+ rio_dprintk (RIO_DEBUG_CMD, "Free Boot %s Command Block '%x'\n",
+ CmdBlkP->Packet.len & 0x80 ? "Command":"Data",
+ CmdBlkP->Packet.data[0]);
+
+ rio_dprintk (RIO_DEBUG_CMD, "Command 0x%x completed\n",(int)CmdBlkP);
+
+ /*
+ ** Clear the Rup lock to prevent mutual exclusion.
+ */
+ if ( CmdBlkP->PostFuncP ) {
+ rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+ (*CmdBlkP->PostFuncP) (CmdBlkP->PostArg,CmdBlkP);
+ rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+ }
+
+ /*
+ ** ....clear the pending flag....
+ */
+ UnixRupP->CmdPendingP = NULL;
+
+ /*
+ ** ....and return the command block to the freelist.
+ */
+ RIOFreeCmdBlk( CmdBlkP );
+ }
+
+ /*
+ ** If there is a command for this rup, and the rup
+ ** is idle, then process the command
+ */
+ if ( (CmdBlkP = UnixRupP->CmdsWaitingP) && /* ASSIGN! */
+ (UnixRupP->CmdPendingP == NULL) &&
+ (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE)) {
+ /*
+ ** if the pre-function is non-zero, call it.
+ ** If it returns RIO_FAIL then don't
+ ** send this command yet!
+ */
+#ifdef CHECK
+ CheckCmdBlkP (CmdBlkP);
+#endif
+ if ( !(CmdBlkP->PreFuncP ?
+ (*CmdBlkP->PreFuncP)(CmdBlkP->PreArg, CmdBlkP) : TRUE)) {
+ rio_dprintk (RIO_DEBUG_CMD, "Not ready to start command 0x%x\n",(int)CmdBlkP);
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CMD, "Start new command 0x%x Cmd byte is 0x%x\n",
+ (int)CmdBlkP, CmdBlkP->Packet.data[0]);
+ /*
+ ** Whammy! blat that pack!
+ */
+#ifdef CHECK
+ CheckPacketP ((PKT *)RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt));
+#endif
+ HostP->Copy( (caddr_t)&CmdBlkP->Packet,
+ RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt), sizeof(PKT));
+
+ /*
+ ** remove the command from the rup command queue...
+ */
+ UnixRupP->CmdsWaitingP = CmdBlkP->NextP;
+
+ /*
+ ** ...and place it on the pending position.
+ */
+ UnixRupP->CmdPendingP = CmdBlkP;
+
+ /*
+ ** set the command register
+ */
+ WWORD(UnixRupP->RupP->txcontrol,TX_PACKET_READY);
+
+ /*
+ ** the command block will be freed
+ ** when the command has been processed.
+ */
+ }
+ }
+ spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+ } while ( Rup );
+}
+
+int
+RIOWFlushMark(int iPortP, struct CmdBlk *CmdBlkP)
+{
+ struct Port * PortP = (struct Port *)iPortP;
+ unsigned long flags;
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+#ifdef CHECK
+ CheckPortP( PortP );
+#endif
+ PortP->WflushFlag++;
+ PortP->MagicFlags |= MAGIC_FLUSH;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return RIOUnUse( iPortP, CmdBlkP );
+}
+
+int
+RIORFlushEnable(int iPortP, struct CmdBlk *CmdBlkP)
+{
+ struct Port * PortP = (struct Port *)iPortP;
+ PKT *PacketP;
+ unsigned long flags;
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ while ( can_remove_receive(&PacketP, PortP) ) {
+ remove_receive(PortP);
+ ShowPacket(DBG_PROC, PacketP );
+ put_free_end( PortP->HostP, PacketP );
+ }
+
+ if ( RWORD(PortP->PhbP->handshake)==PHB_HANDSHAKE_SET ) {
+ /*
+ ** MAGIC! (Basically, handshake the RX buffer, so that
+ ** the RTAs upstream can be re-enabled.)
+ */
+ rio_dprintk (RIO_DEBUG_CMD, "Util: Set RX handshake bit\n");
+ WWORD(PortP->PhbP->handshake, PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET);
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return RIOUnUse( iPortP, CmdBlkP );
+}
+
+int
+RIOUnUse(int iPortP, struct CmdBlk *CmdBlkP)
+{
+ struct Port * PortP = (struct Port *)iPortP;
+ unsigned long flags;
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+#ifdef CHECK
+ CheckPortP( PortP );
+#endif
+ rio_dprintk (RIO_DEBUG_CMD, "Decrement in use count for port\n");
+
+ if (PortP->InUse) {
+ if ( --PortP->InUse != NOT_INUSE ) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+ }
+ }
+ /*
+ ** While PortP->InUse is set (i.e. a preemptive command has been sent to
+ ** the RTA and is awaiting completion), any transmit data is prevented from
+ ** being transferred from the write queue into the transmit packets
+ ** (add_transmit) and no furthur transmit interrupt will be sent for that
+ ** data. The next interrupt will occur up to 500ms later (RIOIntr is called
+ ** twice a second as a saftey measure). This was the case when kermit was
+ ** used to send data into a RIO port. After each packet was sent, TCFLSH
+ ** was called to flush the read queue preemptively. PortP->InUse was
+ ** incremented, thereby blocking the 6 byte acknowledgement packet
+ ** transmitted back. This acknowledgment hung around for 500ms before
+ ** being sent, thus reducing input performance substantially!.
+ ** When PortP->InUse becomes NOT_INUSE, we must ensure that any data
+ ** hanging around in the transmit buffer is sent immediately.
+ */
+ WWORD(PortP->HostP->ParmMapP->tx_intr, 1);
+ /* What to do here ..
+ wakeup( (caddr_t)&(PortP->InUse) );
+ */
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+}
+
+void
+ShowPacket(uint Flags, struct PKT *PacketP)
+{
+}
+
+/*
+**
+** How to use this file:
+**
+** To send a command down a rup, you need to allocate a command block, fill
+** in the packet information, fill in the command number, fill in the pre-
+** and post- functions and arguments, and then add the command block to the
+** queue of command blocks for the port in question. When the port is idle,
+** then the pre-function will be called. If this returns RIO_FAIL then the
+** command will be re-queued and tried again at a later date (probably in one
+** clock tick). If the pre-function returns NOT RIO_FAIL, then the command
+** packet will be queued on the RUP, and the txcontrol field set to the
+** command number. When the txcontrol field has changed from being the
+** command number, then the post-function will be called, with the argument
+** specified earlier, a pointer to the command block, and the value of
+** txcontrol.
+**
+** To allocate a command block, call RIOGetCmdBlk(). This returns a pointer
+** to the command block structure allocated, or NULL if there aren't any.
+** The block will have been zeroed for you.
+**
+** The structure has the following fields:
+**
+** struct CmdBlk
+** {
+** struct CmdBlk *NextP; ** Pointer to next command block **
+** struct PKT Packet; ** A packet, to copy to the rup **
+** int (*PreFuncP)(); ** The func to call to check if OK **
+** int PreArg; ** The arg for the func **
+** int (*PostFuncP)(); ** The func to call when completed **
+** int PostArg; ** The arg for the func **
+** };
+**
+** You need to fill in ALL fields EXCEPT NextP, which is used to link the
+** blocks together either on the free list or on the Rup list.
+**
+** Packet is an actual packet structure to be filled in with the packet
+** information associated with the command. You need to fill in everything,
+** as the command processore doesn't process the command packet in any way.
+**
+** The PreFuncP is called before the packet is enqueued on the host rup.
+** PreFuncP is called as (*PreFuncP)(PreArg, CmdBlkP);. PreFuncP must
+** return !RIO_FAIL to have the packet queued on the rup, and RIO_FAIL
+** if the packet is NOT to be queued.
+**
+** The PostFuncP is called when the command has completed. It is called
+** as (*PostFuncP)(PostArg, CmdBlkP, txcontrol);. PostFuncP is not expected
+** to return a value. PostFuncP does NOT need to free the command block,
+** as this happens automatically after PostFuncP returns.
+**
+** Once the command block has been filled in, it is attached to the correct
+** queue by calling RIOQueueCmdBlk( HostP, Rup, CmdBlkP ) where HostP is
+** a pointer to the struct Host, Rup is the NUMBER of the rup (NOT a pointer
+** to it!), and CmdBlkP is the pointer to the command block allocated using
+** RIOGetCmdBlk().
+**
+*/
diff --git a/drivers/char/rio/rioctrl.c b/drivers/char/rio/rioctrl.c
new file mode 100644
index 000000000000..b4d1a23e27e4
--- /dev/null
+++ b/drivers/char/rio/rioctrl.c
@@ -0,0 +1,1869 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioctrl.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:42
+** Retrieved : 11/6/98 10:33:49
+**
+** ident @(#)rioctrl.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioctrl_c_sccs_ = "@(#)rioctrl.c 1.3";
+#endif
+
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+
+
+static struct LpbReq LpbReq;
+static struct RupReq RupReq;
+static struct PortReq PortReq;
+static struct HostReq HostReq;
+static struct HostDpRam HostDpRam;
+static struct DebugCtrl DebugCtrl;
+static struct Map MapEnt;
+static struct PortSetup PortSetup;
+static struct DownLoad DownLoad;
+static struct SendPack SendPack;
+/* static struct StreamInfo StreamInfo; */
+/* static char modemtable[RIO_PORTS]; */
+static struct SpecialRupCmd SpecialRupCmd;
+static struct PortParams PortParams;
+static struct portStats portStats;
+
+static struct SubCmdStruct {
+ ushort Host;
+ ushort Rup;
+ ushort Port;
+ ushort Addr;
+} SubCmd;
+
+struct PortTty {
+ uint port;
+ struct ttystatics Tty;
+};
+
+static struct PortTty PortTty;
+typedef struct ttystatics TERMIO;
+
+/*
+** This table is used when the config.rio downloads bin code to the
+** driver. We index the table using the product code, 0-F, and call
+** the function pointed to by the entry, passing the information
+** about the boot.
+** The RIOBootCodeUNKNOWN entry is there to politely tell the calling
+** process to bog off.
+*/
+static int
+(*RIOBootTable[MAX_PRODUCT])(struct rio_info *, struct DownLoad *) =
+{
+/* 0 */ RIOBootCodeHOST, /* Host Card */
+/* 1 */ RIOBootCodeRTA, /* RTA */
+};
+
+#define drv_makedev(maj, min) ((((uint) maj & 0xff) << 8) | ((uint) min & 0xff))
+
+int copyin (int arg, caddr_t dp, int siz)
+{
+ int rv;
+
+ rio_dprintk (RIO_DEBUG_CTRL, "Copying %d bytes from user %p to %p.\n", siz, (void *)arg, dp);
+ rv = copy_from_user (dp, (void *)arg, siz);
+ if (rv) return COPYFAIL;
+ else return rv;
+}
+
+static int copyout (caddr_t dp, int arg, int siz)
+{
+ int rv;
+
+ rio_dprintk (RIO_DEBUG_CTRL, "Copying %d bytes to user %p from %p.\n", siz, (void *)arg, dp);
+ rv = copy_to_user ((void *)arg, dp, siz);
+ if (rv) return COPYFAIL;
+ else return rv;
+}
+
+int
+riocontrol(p, dev, cmd, arg, su)
+struct rio_info * p;
+dev_t dev;
+int cmd;
+caddr_t arg;
+int su;
+{
+ uint Host; /* leave me unsigned! */
+ uint port; /* and me! */
+ struct Host *HostP;
+ ushort loop;
+ int Entry;
+ struct Port *PortP;
+ PKT *PacketP;
+ int retval = 0;
+ unsigned long flags;
+
+ func_enter ();
+
+ /* Confuse the compiler to think that we've initialized these */
+ Host=0;
+ PortP = NULL;
+
+ rio_dprintk (RIO_DEBUG_CTRL, "control ioctl cmd: 0x%x arg: 0x%x\n", cmd, (int)arg);
+
+ switch (cmd) {
+ /*
+ ** RIO_SET_TIMER
+ **
+ ** Change the value of the host card interrupt timer.
+ ** If the host card number is -1 then all host cards are changed
+ ** otherwise just the specified host card will be changed.
+ */
+ case RIO_SET_TIMER:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_TIMER to %dms\n", (uint)arg);
+ {
+ int host, value;
+ host = (uint)arg >> 16;
+ value = (uint)arg & 0x0000ffff;
+ if (host == -1) {
+ for (host = 0; host < p->RIONumHosts; host++) {
+ if (p->RIOHosts[host].Flags == RC_RUNNING) {
+ WWORD(p->RIOHosts[host].ParmMapP->timer , value);
+ }
+ }
+ } else if (host >= p->RIONumHosts) {
+ return -EINVAL;
+ } else {
+ if ( p->RIOHosts[host].Flags == RC_RUNNING ) {
+ WWORD(p->RIOHosts[host].ParmMapP->timer , value);
+ }
+ }
+ }
+ return 0;
+
+ case RIO_IDENTIFY_DRIVER:
+ /*
+ ** 15.10.1998 ARG - ESIL 0760 part fix
+ ** Added driver ident string output.
+ **
+#ifndef __THIS_RELEASE__
+#warning Driver Version string not defined !
+#endif
+ cprintf("%s %s %s %s\n",
+ RIO_DRV_STR,
+ __THIS_RELEASE__,
+ __DATE__, __TIME__ );
+
+ return 0;
+
+ case RIO_DISPLAY_HOST_CFG:
+ **
+ ** 15.10.1998 ARG - ESIL 0760 part fix
+ ** Added driver host card ident string output.
+ **
+ ** Note that the only types currently supported
+ ** are ISA and PCI. Also this driver does not
+ ** (yet) distinguish between the Old PCI card
+ ** and the Jet PCI card. In fact I think this
+ ** driver only supports JET PCI !
+ **
+
+ for (Host = 0; Host < p->RIONumHosts; Host++)
+ {
+ HostP = &(p->RIOHosts[Host]);
+
+ switch ( HostP->Type )
+ {
+ case RIO_AT :
+ strcpy( host_type, RIO_AT_HOST_STR );
+ break;
+
+ case RIO_PCI :
+ strcpy( host_type, RIO_PCI_HOST_STR );
+ break;
+
+ default :
+ strcpy( host_type, "Unknown" );
+ break;
+ }
+
+ cprintf(
+ "RIO Host %d - Type:%s Addr:%X IRQ:%d\n",
+ Host, host_type,
+ (uint)HostP->PaddrP,
+ (int)HostP->Ivec - 32 );
+ }
+ return 0;
+ **
+ */
+
+ case RIO_FOAD_RTA:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_FOAD_RTA\n");
+ return RIOCommandRta(p, (uint)arg, RIOFoadRta);
+
+ case RIO_ZOMBIE_RTA:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_ZOMBIE_RTA\n");
+ return RIOCommandRta(p, (uint)arg, RIOZombieRta);
+
+ case RIO_IDENTIFY_RTA:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_IDENTIFY_RTA\n");
+ return RIOIdentifyRta(p, arg);
+
+ case RIO_KILL_NEIGHBOUR:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_KILL_NEIGHBOUR\n");
+ return RIOKillNeighbour(p, arg);
+
+ case SPECIAL_RUP_CMD:
+ {
+ struct CmdBlk *CmdBlkP;
+
+ rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD\n");
+ if (copyin((int)arg, (caddr_t)&SpecialRupCmd,
+ sizeof(SpecialRupCmd)) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD copy failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ CmdBlkP = RIOGetCmdBlk();
+ if ( !CmdBlkP ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD GetCmdBlk failed\n");
+ return -ENXIO;
+ }
+ CmdBlkP->Packet = SpecialRupCmd.Packet;
+ if ( SpecialRupCmd.Host >= p->RIONumHosts )
+ SpecialRupCmd.Host = 0;
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue special rup command for host %d rup %d\n",
+ SpecialRupCmd.Host, SpecialRupCmd.RupNum);
+ if (RIOQueueCmdBlk(&p->RIOHosts[SpecialRupCmd.Host],
+ SpecialRupCmd.RupNum, CmdBlkP) == RIO_FAIL) {
+ cprintf("FAILED TO QUEUE SPECIAL RUP COMMAND\n");
+ }
+ return 0;
+ }
+
+ case RIO_DEBUG_MEM:
+#ifdef DEBUG_MEM_SUPPORT
+RIO_DEBUG_CTRL, if (su)
+ return rio_RIODebugMemory(RIO_DEBUG_CTRL, arg);
+ else
+#endif
+ return -EPERM;
+
+ case RIO_ALL_MODEM:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_ALL_MODEM\n");
+ p->RIOError.Error = IOCTL_COMMAND_UNKNOWN;
+ return -EINVAL;
+
+ case RIO_GET_TABLE:
+ /*
+ ** Read the routing table from the device driver to user space
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_TABLE\n");
+
+ if ((retval = RIOApel(p)) != 0)
+ return retval;
+
+ if (copyout((caddr_t)p->RIOConnectTable, (int)arg,
+ TOTAL_MAP_ENTRIES*sizeof(struct Map)) == COPYFAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_TABLE copy failed\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+
+ {
+ int entry;
+ rio_dprintk (RIO_DEBUG_CTRL, "*****\nMAP ENTRIES\n");
+ for ( entry=0; entry<TOTAL_MAP_ENTRIES; entry++ )
+ {
+ if ((p->RIOConnectTable[entry].ID == 0) &&
+ (p->RIOConnectTable[entry].HostUniqueNum == 0) &&
+ (p->RIOConnectTable[entry].RtaUniqueNum == 0)) continue;
+
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.HostUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].HostUniqueNum );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.RtaUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].RtaUniqueNum );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.ID = 0x%x\n", entry, p->RIOConnectTable[entry].ID );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.ID2 = 0x%x\n", entry, p->RIOConnectTable[entry].ID2 );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Flags = 0x%x\n", entry, (int)p->RIOConnectTable[entry].Flags );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.SysPort = 0x%x\n", entry, (int)p->RIOConnectTable[entry].SysPort );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[0].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[0].Unit );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[0].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[0].Link );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[1].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[1].Unit );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[1].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[1].Link );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[2].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[2].Unit );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[2].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[2].Link );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[3].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[3].Unit );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[4].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[3].Link );
+ rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Name = %s\n", entry, p->RIOConnectTable[entry].Name );
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "*****\nEND MAP ENTRIES\n");
+ }
+ p->RIOQuickCheck = NOT_CHANGED; /* a table has been gotten */
+ return 0;
+
+ case RIO_PUT_TABLE:
+ /*
+ ** Write the routing table to the device driver from user space
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE\n");
+
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if ( copyin((int)arg, (caddr_t)&p->RIOConnectTable[0],
+ TOTAL_MAP_ENTRIES*sizeof(struct Map) ) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE copy failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+/*
+***********************************
+ {
+ int entry;
+ rio_dprint(RIO_DEBUG_CTRL, ("*****\nMAP ENTRIES\n") );
+ for ( entry=0; entry<TOTAL_MAP_ENTRIES; entry++ )
+ {
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.HostUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].HostUniqueNum ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.RtaUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].RtaUniqueNum ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.ID = 0x%x\n", entry, p->RIOConnectTable[entry].ID ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.ID2 = 0x%x\n", entry, p->RIOConnectTable[entry].ID2 ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Flags = 0x%x\n", entry, p->RIOConnectTable[entry].Flags ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.SysPort = 0x%x\n", entry, p->RIOConnectTable[entry].SysPort ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[0].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[0].Unit ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[0].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[0].Link ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[1].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[1].Unit ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[1].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[1].Link ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[2].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[2].Unit ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[2].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[2].Link ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[3].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[3].Unit ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Top[4].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[3].Link ) );
+ rio_dprint(RIO_DEBUG_CTRL, ("Map entry %d.Name = %s\n", entry, p->RIOConnectTable[entry].Name ) );
+ }
+ rio_dprint(RIO_DEBUG_CTRL, ("*****\nEND MAP ENTRIES\n") );
+ }
+***********************************
+*/
+ return RIONewTable(p);
+
+ case RIO_GET_BINDINGS :
+ /*
+ ** Send bindings table, containing unique numbers of RTAs owned
+ ** by this system to user space
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS\n");
+
+ if ( !su )
+ {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if (copyout((caddr_t) p->RIOBindTab, (int)arg,
+ (sizeof(ulong) * MAX_RTA_BINDINGS)) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS copy failed\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_PUT_BINDINGS :
+ /*
+ ** Receive a bindings table, containing unique numbers of RTAs owned
+ ** by this system
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS\n");
+
+ if ( !su )
+ {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if (copyin((int)arg, (caddr_t)&p->RIOBindTab[0],
+ (sizeof(ulong) * MAX_RTA_BINDINGS))==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS copy failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_BIND_RTA :
+ {
+ int EmptySlot = -1;
+ /*
+ ** Bind this RTA to host, so that it will be booted by
+ ** host in 'boot owned RTAs' mode.
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_BIND_RTA\n");
+
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_BIND_RTA !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ for (Entry = 0; Entry < MAX_RTA_BINDINGS; Entry++) {
+ if ((EmptySlot == -1) && (p->RIOBindTab[Entry] == 0L))
+ EmptySlot = Entry;
+ else if (p->RIOBindTab[Entry] == (int) arg) {
+ /*
+ ** Already exists - delete
+ */
+ p->RIOBindTab[Entry] = 0L;
+ rio_dprintk (RIO_DEBUG_CTRL, "Removing Rta %x from p->RIOBindTab\n",
+ (int) arg);
+ return 0;
+ }
+ }
+ /*
+ ** Dosen't exist - add
+ */
+ if (EmptySlot != -1) {
+ p->RIOBindTab[EmptySlot] = (int) arg;
+ rio_dprintk (RIO_DEBUG_CTRL, "Adding Rta %x to p->RIOBindTab\n",
+ (int) arg);
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CTRL, "p->RIOBindTab full! - Rta %x not added\n",
+ (int) arg);
+ return -ENOMEM;
+ }
+ return 0;
+ }
+
+ case RIO_RESUME :
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME\n");
+ port = (uint) arg;
+ if ((port < 0) || (port > 511)) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Bad port number %d\n", port);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ PortP = p->RIOPortp[port];
+ if (!PortP->Mapped) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d not mapped\n", port);
+ p->RIOError.Error = PORT_NOT_MAPPED_INTO_SYSTEM;
+ return -EINVAL;
+ }
+ if (!(PortP->State & (RIO_LOPEN | RIO_MOPEN))) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d not open\n", port);
+ return -EINVAL;
+ }
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ if (RIOPreemptiveCmd(p, (p->RIOPortp[port]), RESUME) ==
+ RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME failed\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return -EBUSY;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d resumed\n", port);
+ PortP->State |= RIO_BUSY;
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return retval;
+
+ case RIO_ASSIGN_RTA:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_ASSIGN_RTA\n");
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_ASSIGN_RTA !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+ == COPYFAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Copy from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ return RIOAssignRta(p, &MapEnt);
+
+ case RIO_CHANGE_NAME:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_CHANGE_NAME\n");
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_CHANGE_NAME !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+ == COPYFAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Copy from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ return RIOChangeName(p, &MapEnt);
+
+ case RIO_DELETE_RTA:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DELETE_RTA\n");
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DELETE_RTA !Root\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+ == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Copy from data space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ return RIODeleteRta(p, &MapEnt);
+
+ case RIO_QUICK_CHECK:
+ /*
+ ** 09.12.1998 ARG - ESIL 0776 part fix
+ ** A customer was using this to get the RTAs
+ ** connect/disconnect status.
+ ** RIOConCon() had been botched use RIOHalted
+ ** to keep track of RTA connections and
+ ** disconnections. That has been changed and
+ ** RIORtaDisCons in the rio_info struct now
+ ** does the job. So we need to return the value
+ ** of RIORtaCons instead of RIOHalted.
+ **
+ if (copyout((caddr_t)&p->RIOHalted,(int)arg,
+ sizeof(uint))==COPYFAIL) {
+ **
+ */
+
+ if (copyout((caddr_t)&p->RIORtaDisCons,(int)arg,
+ sizeof(uint))==COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_LAST_ERROR:
+ if (copyout((caddr_t)&p->RIOError, (int)arg,
+ sizeof(struct Error)) ==COPYFAIL )
+ return -EFAULT;
+ return 0;
+
+ case RIO_GET_LOG:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_LOG\n");
+#ifdef LOGGING
+ RIOGetLog(arg);
+ return 0;
+#else
+ return -EINVAL;
+#endif
+
+ case RIO_GET_MODTYPE:
+ if ( copyin( (int)arg, (caddr_t)&port,
+ sizeof(uint)) == COPYFAIL )
+ {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Get module type for port %d\n", port);
+ if ( port < 0 || port > 511 )
+ {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_MODTYPE: Bad port number %d\n", port);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ PortP = (p->RIOPortp[port]);
+ if (!PortP->Mapped)
+ {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_MODTYPE: Port %d not mapped\n", port);
+ p->RIOError.Error = PORT_NOT_MAPPED_INTO_SYSTEM;
+ return -EINVAL;
+ }
+ /*
+ ** Return module type of port
+ */
+ port = PortP->HostP->UnixRups[PortP->RupNum].ModTypes;
+ if (copyout((caddr_t)&port, (int)arg,
+ sizeof(uint)) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return(0);
+ /*
+ ** 02.03.1999 ARG - ESIL 0820 fix
+ ** We are no longer using "Boot Mode", so these ioctls
+ ** are not required :
+ **
+ case RIO_GET_BOOT_MODE :
+ rio_dprint(RIO_DEBUG_CTRL, ("Get boot mode - %x\n", p->RIOBootMode));
+ **
+ ** Return boot state of system - BOOT_ALL, BOOT_OWN or BOOT_NONE
+ **
+ if (copyout((caddr_t)&p->RIOBootMode, (int)arg,
+ sizeof(p->RIOBootMode)) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return(0);
+
+ case RIO_SET_BOOT_MODE :
+ p->RIOBootMode = (uint) arg;
+ rio_dprint(RIO_DEBUG_CTRL, ("Set boot mode to 0x%x\n", p->RIOBootMode));
+ return(0);
+ **
+ ** End ESIL 0820 fix
+ */
+
+ case RIO_BLOCK_OPENS:
+ rio_dprintk (RIO_DEBUG_CTRL, "Opens block until booted\n");
+ for ( Entry=0; Entry < RIO_PORTS; Entry++ ) {
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ p->RIOPortp[Entry]->WaitUntilBooted = 1;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ return 0;
+
+ case RIO_SETUP_PORTS:
+ rio_dprintk (RIO_DEBUG_CTRL, "Setup ports\n");
+ if (copyin((int)arg, (caddr_t)&PortSetup, sizeof(PortSetup))
+ == COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "EFAULT");
+ return -EFAULT;
+ }
+ if ( PortSetup.From > PortSetup.To ||
+ PortSetup.To >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ rio_dprintk (RIO_DEBUG_CTRL, "ENXIO");
+ return -ENXIO;
+ }
+ if ( PortSetup.XpCps > p->RIOConf.MaxXpCps ||
+ PortSetup.XpCps < p->RIOConf.MinXpCps ) {
+ p->RIOError.Error = XPRINT_CPS_OUT_OF_RANGE;
+ rio_dprintk (RIO_DEBUG_CTRL, "EINVAL");
+ return -EINVAL;
+ }
+ if ( !p->RIOPortp ) {
+ cprintf("No p->RIOPortp array!\n");
+ rio_dprintk (RIO_DEBUG_CTRL, "No p->RIOPortp array!\n");
+ return -EIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "entering loop (%d %d)!\n", PortSetup.From, PortSetup.To);
+ for (loop=PortSetup.From; loop<=PortSetup.To; loop++) {
+ rio_dprintk (RIO_DEBUG_CTRL, "in loop (%d)!\n", loop);
+#if 0
+ PortP = p->RIOPortp[loop];
+ if ( !PortP->TtyP )
+ PortP->TtyP = &p->channel[loop];
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ if ( PortSetup.IxAny )
+ PortP->Config |= RIO_IXANY;
+ else
+ PortP->Config &= ~RIO_IXANY;
+ if ( PortSetup.IxOn )
+ PortP->Config |= RIO_IXON;
+ else
+ PortP->Config &= ~RIO_IXON;
+
+ /*
+ ** If the port needs to wait for all a processes output
+ ** to drain before closing then this flag will be set.
+ */
+ if (PortSetup.Drain) {
+ PortP->Config |= RIO_WAITDRAIN;
+ } else {
+ PortP->Config &= ~RIO_WAITDRAIN;
+ }
+ /*
+ ** Store settings if locking or unlocking port or if the
+ ** port is not locked, when setting the store option.
+ */
+ if (PortP->Mapped &&
+ ((PortSetup.Lock && !PortP->Lock) ||
+ (!PortP->Lock &&
+ (PortSetup.Store && !PortP->Store)))) {
+ PortP->StoredTty.iflag = PortP->TtyP->tm.c_iflag;
+ PortP->StoredTty.oflag = PortP->TtyP->tm.c_oflag;
+ PortP->StoredTty.cflag = PortP->TtyP->tm.c_cflag;
+ PortP->StoredTty.lflag = PortP->TtyP->tm.c_lflag;
+ PortP->StoredTty.line = PortP->TtyP->tm.c_line;
+ bcopy(PortP->TtyP->tm.c_cc, PortP->StoredTty.cc,
+ NCC + 5);
+ }
+ PortP->Lock = PortSetup.Lock;
+ PortP->Store = PortSetup.Store;
+ PortP->Xprint.XpCps = PortSetup.XpCps;
+ bcopy(PortSetup.XpOn,PortP->Xprint.XpOn,MAX_XP_CTRL_LEN);
+ bcopy(PortSetup.XpOff,PortP->Xprint.XpOff,MAX_XP_CTRL_LEN);
+ PortP->Xprint.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+ PortP->Xprint.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+ PortP->Xprint.XpLen = RIOStrlen(PortP->Xprint.XpOn)+
+ RIOStrlen(PortP->Xprint.XpOff);
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+#endif
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "after loop (%d)!\n", loop);
+ rio_dprintk (RIO_DEBUG_CTRL, "Retval:%x\n", retval);
+ return retval;
+
+ case RIO_GET_PORT_SETUP :
+ rio_dprintk (RIO_DEBUG_CTRL, "Get port setup\n");
+ if (copyin((int)arg, (caddr_t)&PortSetup, sizeof(PortSetup))
+ == COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( PortSetup.From >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+
+ port = PortSetup.To = PortSetup.From;
+ PortSetup.IxAny = (p->RIOPortp[port]->Config & RIO_IXANY) ?
+ 1 : 0;
+ PortSetup.IxOn = (p->RIOPortp[port]->Config & RIO_IXON) ?
+ 1 : 0;
+ PortSetup.Drain = (p->RIOPortp[port]->Config & RIO_WAITDRAIN) ?
+ 1 : 0;
+ PortSetup.Store = p->RIOPortp[port]->Store;
+ PortSetup.Lock = p->RIOPortp[port]->Lock;
+ PortSetup.XpCps = p->RIOPortp[port]->Xprint.XpCps;
+ bcopy(p->RIOPortp[port]->Xprint.XpOn, PortSetup.XpOn,
+ MAX_XP_CTRL_LEN);
+ bcopy(p->RIOPortp[port]->Xprint.XpOff, PortSetup.XpOff,
+ MAX_XP_CTRL_LEN);
+ PortSetup.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+ PortSetup.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+
+ if ( copyout((caddr_t)&PortSetup,(int)arg,sizeof(PortSetup))
+ ==COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_GET_PORT_PARAMS :
+ rio_dprintk (RIO_DEBUG_CTRL, "Get port params\n");
+ if (copyin( (int)arg, (caddr_t)&PortParams,
+ sizeof(struct PortParams)) == COPYFAIL) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if (PortParams.Port >= RIO_PORTS) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[PortParams.Port]);
+ PortParams.Config = PortP->Config;
+ PortParams.State = PortP->State;
+ rio_dprintk (RIO_DEBUG_CTRL, "Port %d\n", PortParams.Port);
+
+ if (copyout((caddr_t)&PortParams, (int)arg,
+ sizeof(struct PortParams)) == COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_GET_PORT_TTY :
+ rio_dprintk (RIO_DEBUG_CTRL, "Get port tty\n");
+ if (copyin((int)arg, (caddr_t)&PortTty, sizeof(struct PortTty))
+ == COPYFAIL) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( PortTty.port >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+
+ rio_dprintk (RIO_DEBUG_CTRL, "Port %d\n", PortTty.port);
+ PortP = (p->RIOPortp[PortTty.port]);
+#if 0
+ PortTty.Tty.tm.c_iflag = PortP->TtyP->tm.c_iflag;
+ PortTty.Tty.tm.c_oflag = PortP->TtyP->tm.c_oflag;
+ PortTty.Tty.tm.c_cflag = PortP->TtyP->tm.c_cflag;
+ PortTty.Tty.tm.c_lflag = PortP->TtyP->tm.c_lflag;
+#endif
+ if (copyout((caddr_t)&PortTty, (int)arg,
+ sizeof(struct PortTty)) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_SET_PORT_TTY :
+ if (copyin((int)arg, (caddr_t)&PortTty,
+ sizeof(struct PortTty)) == COPYFAIL) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Set port %d tty\n", PortTty.port);
+ if (PortTty.port >= (ushort) RIO_PORTS) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[PortTty.port]);
+#if 0
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->TtyP->tm.c_iflag = PortTty.Tty.tm.c_iflag;
+ PortP->TtyP->tm.c_oflag = PortTty.Tty.tm.c_oflag;
+ PortP->TtyP->tm.c_cflag = PortTty.Tty.tm.c_cflag;
+ PortP->TtyP->tm.c_lflag = PortTty.Tty.tm.c_lflag;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+#endif
+
+ RIOParam(PortP, CONFIG, PortP->State & RIO_MODEM, OK_TO_SLEEP);
+ return retval;
+
+ case RIO_SET_PORT_PARAMS :
+ rio_dprintk (RIO_DEBUG_CTRL, "Set port params\n");
+ if ( copyin((int)arg, (caddr_t)&PortParams, sizeof(PortParams))
+ == COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if (PortParams.Port >= (ushort) RIO_PORTS) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[PortParams.Port]);
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->Config = PortParams.Config;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return retval;
+
+ case RIO_GET_PORT_STATS :
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_PORT_STATS\n");
+ if ( copyin((int)arg, (caddr_t)&portStats,
+ sizeof(struct portStats)) == COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( portStats.port >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[portStats.port]);
+ portStats.gather = PortP->statsGather;
+ portStats.txchars = PortP->txchars;
+ portStats.rxchars = PortP->rxchars;
+ portStats.opens = PortP->opens;
+ portStats.closes = PortP->closes;
+ portStats.ioctls = PortP->ioctls;
+ if ( copyout((caddr_t)&portStats, (int)arg,
+ sizeof(struct portStats)) == COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_RESET_PORT_STATS :
+ port = (uint) arg;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESET_PORT_STATS\n");
+ if ( port >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[port]);
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->txchars = 0;
+ PortP->rxchars = 0;
+ PortP->opens = 0;
+ PortP->closes = 0;
+ PortP->ioctls = 0;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return retval;
+
+ case RIO_GATHER_PORT_STATS :
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GATHER_PORT_STATS\n");
+ if ( copyin( (int)arg, (caddr_t)&portStats,
+ sizeof(struct portStats)) == COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( portStats.port >= RIO_PORTS ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ PortP = (p->RIOPortp[portStats.port]);
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->statsGather = portStats.gather;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return retval;
+
+#ifdef DEBUG_SUPPORTED
+ case RIO_READ_LEVELS:
+ {
+ int num;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_LEVELS\n");
+ for ( num=0; RIODbInf[num].Flag; num++ ) ;
+ rio_dprintk (RIO_DEBUG_CTRL, "%d levels to copy\n",num);
+ if (copyout((caddr_t)RIODbInf,(int)arg,
+ sizeof(struct DbInf)*(num+1))==COPYFAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "ReadLevels Copy failed\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "%d levels to copied\n",num);
+ return retval;
+ }
+#endif
+
+ case RIO_READ_CONFIG:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_CONFIG\n");
+ if (copyout((caddr_t)&p->RIOConf, (int)arg,
+ sizeof(struct Conf)) ==COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_SET_CONFIG:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_CONFIG\n");
+ if ( !su ) {
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if ( copyin((int)arg, (caddr_t)&p->RIOConf, sizeof(struct Conf) )
+ ==COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ /*
+ ** move a few value around
+ */
+ for (Host=0; Host < p->RIONumHosts; Host++)
+ if ( (p->RIOHosts[Host].Flags & RUN_STATE) == RC_RUNNING )
+ WWORD(p->RIOHosts[Host].ParmMapP->timer ,
+ p->RIOConf.Timer);
+ return retval;
+
+ case RIO_START_POLLER:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_START_POLLER\n");
+ return -EINVAL;
+
+ case RIO_STOP_POLLER:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_STOP_POLLER\n");
+ if ( !su ) {
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ p->RIOPolling = NOT_POLLING;
+ return retval;
+
+ case RIO_SETDEBUG:
+ case RIO_GETDEBUG:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SETDEBUG/RIO_GETDEBUG\n");
+ if ( copyin( (int)arg, (caddr_t)&DebugCtrl, sizeof(DebugCtrl) )
+ ==COPYFAIL ) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( DebugCtrl.SysPort == NO_PORT ) {
+ if ( cmd == RIO_SETDEBUG ) {
+ if ( !su ) {
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ p->rio_debug = DebugCtrl.Debug;
+ p->RIODebugWait = DebugCtrl.Wait;
+ rio_dprintk (RIO_DEBUG_CTRL, "Set global debug to 0x%x set wait to 0x%x\n",
+ p->rio_debug,p->RIODebugWait);
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CTRL, "Get global debug 0x%x wait 0x%x\n",
+ p->rio_debug,p->RIODebugWait);
+ DebugCtrl.Debug = p->rio_debug;
+ DebugCtrl.Wait = p->RIODebugWait;
+ if ( copyout((caddr_t)&DebugCtrl,(int)arg,
+ sizeof(DebugCtrl)) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET/GET DEBUG: bad port number %d\n",
+ DebugCtrl.SysPort);
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ }
+ }
+ else if ( DebugCtrl.SysPort >= RIO_PORTS &&
+ DebugCtrl.SysPort != NO_PORT ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET/GET DEBUG: bad port number %d\n",
+ DebugCtrl.SysPort);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ else if ( cmd == RIO_SETDEBUG ) {
+ if ( !su ) {
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ p->RIOPortp[DebugCtrl.SysPort]->Debug = DebugCtrl.Debug;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SETDEBUG 0x%x\n",
+ p->RIOPortp[DebugCtrl.SysPort]->Debug);
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GETDEBUG 0x%x\n",
+ p->RIOPortp[DebugCtrl.SysPort]->Debug);
+ DebugCtrl.Debug = p->RIOPortp[DebugCtrl.SysPort]->Debug;
+ if ( copyout((caddr_t)&DebugCtrl,(int)arg,
+ sizeof(DebugCtrl))==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_GETDEBUG: Bad copy to user space\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ }
+ return retval;
+
+ case RIO_VERSID:
+ /*
+ ** Enquire about the release and version.
+ ** We return MAX_VERSION_LEN bytes, being a
+ ** textual null terminated string.
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_VERSID\n");
+ if ( copyout( (caddr_t)RIOVersid(),
+ (int)arg,
+ sizeof(struct rioVersion) ) == COPYFAIL )
+ {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_VERSID: Bad copy to user space (host=%d)\n", Host);
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ /*
+ ** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ ** !! commented out previous 'RIO_VERSID' functionality !!
+ ** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ **
+ case RIO_VERSID:
+ **
+ ** Enquire about the release and version.
+ ** We return MAX_VERSION_LEN bytes, being a textual null
+ ** terminated string.
+ **
+ rio_dprint(RIO_DEBUG_CTRL, ("RIO_VERSID\n"));
+ if (copyout((caddr_t)RIOVersid(),
+ (int)arg, MAX_VERSION_LEN ) == COPYFAIL ) {
+ rio_dprint(RIO_DEBUG_CTRL, ("RIO_VERSID: Bad copy to user space\n",Host));
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+ **
+ ** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ */
+
+ case RIO_NUM_HOSTS:
+ /*
+ ** Enquire as to the number of hosts located
+ ** at init time.
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_NUM_HOSTS\n");
+ if (copyout((caddr_t)&p->RIONumHosts, (int)arg,
+ sizeof(p->RIONumHosts) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_NUM_HOSTS: Bad copy to user space\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_HOST_FOAD:
+ /*
+ ** Kill host. This may not be in the final version...
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_FOAD %d\n", (int)arg);
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_FOAD: Not super user\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ p->RIOHalted = 1;
+ p->RIOSystemUp = 0;
+
+ for ( Host=0; Host<p->RIONumHosts; Host++ ) {
+ (void)RIOBoardTest( p->RIOHosts[Host].PaddrP,
+ p->RIOHosts[Host].Caddr, p->RIOHosts[Host].Type,
+ p->RIOHosts[Host].Slot );
+ bzero( (caddr_t)&p->RIOHosts[Host].Flags,
+ ((int)&p->RIOHosts[Host].____end_marker____) -
+ ((int)&p->RIOHosts[Host].Flags) );
+ p->RIOHosts[Host].Flags = RC_WAITING;
+#if 0
+ RIOSetupDataStructs(p);
+#endif
+ }
+ RIOFoadWakeup(p);
+ p->RIONumBootPkts = 0;
+ p->RIOBooting = 0;
+
+#ifdef RINGBUFFER_SUPPORT
+ for( loop=0; loop<RIO_PORTS; loop++ )
+ if ( p->RIOPortp[loop]->TxRingBuffer )
+ sysfree((void *)p->RIOPortp[loop]->TxRingBuffer,
+ RIOBufferSize );
+#endif
+#if 0
+ bzero((caddr_t)&p->RIOPortp[0],RIO_PORTS*sizeof(struct Port));
+#else
+ printk ("HEEEEELP!\n");
+#endif
+
+ for( loop=0; loop<RIO_PORTS; loop++ ) {
+#if 0
+ p->RIOPortp[loop]->TtyP = &p->channel[loop];
+#endif
+
+ spin_lock_init(&p->RIOPortp[loop]->portSem);
+ p->RIOPortp[loop]->InUse = NOT_INUSE;
+ }
+
+ p->RIOSystemUp = 0;
+ return retval;
+
+ case RIO_DOWNLOAD:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD\n");
+ if ( !su ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Not super user\n");
+ p->RIOError.Error = NOT_SUPER_USER;
+ return -EPERM;
+ }
+ if ( copyin((int)arg, (caddr_t)&DownLoad,
+ sizeof(DownLoad) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Copied in download code for product code 0x%x\n",
+ DownLoad.ProductCode);
+
+ /*
+ ** It is important that the product code is an unsigned object!
+ */
+ if ( DownLoad.ProductCode > MAX_PRODUCT ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Bad product code %d passed\n",
+ DownLoad.ProductCode);
+ p->RIOError.Error = NO_SUCH_PRODUCT;
+ return -ENXIO;
+ }
+ /*
+ ** do something!
+ */
+ retval = (*(RIOBootTable[DownLoad.ProductCode]))(p, &DownLoad);
+ /* <-- Panic */
+ p->RIOHalted = 0;
+ /*
+ ** and go back, content with a job well completed.
+ */
+ return retval;
+
+ case RIO_PARMS:
+ {
+ uint host;
+
+ if (copyin((int)arg, (caddr_t)&host,
+ sizeof(host) ) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL,
+ "RIO_HOST_REQ: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ /*
+ ** Fetch the parmmap
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PARMS\n");
+ if ( copyout( (caddr_t)p->RIOHosts[host].ParmMapP,
+ (int)arg, sizeof(PARM_MAP) )==COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_PARMS: Copy out to user space failed\n");
+ return -EFAULT;
+ }
+ }
+ return retval;
+
+ case RIO_HOST_REQ:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ\n");
+ if (copyin((int)arg, (caddr_t)&HostReq,
+ sizeof(HostReq) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( HostReq.HostNum >= p->RIONumHosts ) {
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Illegal host number %d\n",
+ HostReq.HostNum);
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for host %d\n", HostReq.HostNum);
+
+ if (copyout((caddr_t)&p->RIOHosts[HostReq.HostNum],
+ (int)HostReq.HostP,sizeof(struct Host) ) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Bad copy to user space\n");
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_HOST_DPRAM:
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for DPRAM\n");
+ if ( copyin( (int)arg, (caddr_t)&HostDpRam,
+ sizeof(HostDpRam) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( HostDpRam.HostNum >= p->RIONumHosts ) {
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Illegal host number %d\n",
+ HostDpRam.HostNum);
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for host %d\n", HostDpRam.HostNum);
+
+ if (p->RIOHosts[HostDpRam.HostNum].Type == RIO_PCI) {
+ int off;
+ /* It's hardware like this that really gets on my tits. */
+ static unsigned char copy[sizeof(struct DpRam)];
+ for ( off=0; off<sizeof(struct DpRam); off++ )
+ copy[off] = p->RIOHosts[HostDpRam.HostNum].Caddr[off];
+ if ( copyout( (caddr_t)copy, (int)HostDpRam.DpRamP,
+ sizeof(struct DpRam) ) == COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Bad copy to user space\n");
+ return -EFAULT;
+ }
+ }
+ else if (copyout((caddr_t)p->RIOHosts[HostDpRam.HostNum].Caddr,
+ (int)HostDpRam.DpRamP,
+ sizeof(struct DpRam) ) == COPYFAIL ) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Bad copy to user space\n");
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_SET_BUSY:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_BUSY\n");
+ if ( (int)arg < 0 || (int)arg > 511 ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_BUSY: Bad port number %d\n",(int)arg);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ p->RIOPortp[(int)arg]->State |= RIO_BUSY;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return retval;
+
+ case RIO_HOST_PORT:
+ /*
+ ** The daemon want port information
+ ** (probably for debug reasons)
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT\n");
+ if ( copyin((int)arg, (caddr_t)&PortReq,
+ sizeof(PortReq) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+
+ if (PortReq.SysPort >= RIO_PORTS) { /* SysPort is unsigned */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Illegal port number %d\n",
+ PortReq.SysPort);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for port %d\n", PortReq.SysPort);
+ if (copyout((caddr_t)p->RIOPortp[PortReq.SysPort],
+ (int)PortReq.PortP,
+ sizeof(struct Port) ) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Bad copy to user space\n");
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_HOST_RUP:
+ /*
+ ** The daemon want rup information
+ ** (probably for debug reasons)
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP\n");
+ if (copyin((int)arg, (caddr_t)&RupReq,
+ sizeof(RupReq) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Copy in from user space failed\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if (RupReq.HostNum >= p->RIONumHosts) { /* host is unsigned */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Illegal host number %d\n",
+ RupReq.HostNum);
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ if ( RupReq.RupNum >= MAX_RUP+LINKS_PER_UNIT ) { /* eek! */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Illegal rup number %d\n",
+ RupReq.RupNum);
+ p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ HostP = &p->RIOHosts[RupReq.HostNum];
+
+ if ((HostP->Flags & RUN_STATE) != RC_RUNNING) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Host %d not running\n",
+ RupReq.HostNum);
+ p->RIOError.Error = HOST_NOT_RUNNING;
+ return -EIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for rup %d from host %d\n",
+ RupReq.RupNum,RupReq.HostNum);
+
+ if (copyout((caddr_t)HostP->UnixRups[RupReq.RupNum].RupP,
+ (int)RupReq.RupP,sizeof(struct RUP) ) == COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Bad copy to user space\n");
+ return -EFAULT;
+ }
+ return retval;
+
+ case RIO_HOST_LPB:
+ /*
+ ** The daemon want lpb information
+ ** (probably for debug reasons)
+ */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB\n");
+ if (copyin((int)arg, (caddr_t)&LpbReq,
+ sizeof(LpbReq) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Bad copy from user space\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if (LpbReq.Host >= p->RIONumHosts) { /* host is unsigned */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Illegal host number %d\n",
+ LpbReq.Host);
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+ if ( LpbReq.Link >= LINKS_PER_UNIT ) { /* eek! */
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Illegal link number %d\n",
+ LpbReq.Link);
+ p->RIOError.Error = LINK_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ HostP = &p->RIOHosts[LpbReq.Host];
+
+ if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Host %d not running\n",
+ LpbReq.Host );
+ p->RIOError.Error = HOST_NOT_RUNNING;
+ return -EIO;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Request for lpb %d from host %d\n",
+ LpbReq.Link, LpbReq.Host);
+
+ if (copyout((caddr_t)&HostP->LinkStrP[LpbReq.Link],
+ (int)LpbReq.LpbP,sizeof(struct LPB) ) == COPYFAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Bad copy to user space\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return retval;
+
+ /*
+ ** Here 3 IOCTL's that allow us to change the way in which
+ ** rio logs errors. send them just to syslog or send them
+ ** to both syslog and console or send them to just the console.
+ **
+ ** See RioStrBuf() in util.c for the other half.
+ */
+ case RIO_SYSLOG_ONLY:
+ p->RIOPrintLogState = PRINT_TO_LOG; /* Just syslog */
+ return 0;
+
+ case RIO_SYSLOG_CONS:
+ p->RIOPrintLogState = PRINT_TO_LOG_CONS;/* syslog and console */
+ return 0;
+
+ case RIO_CONS_ONLY:
+ p->RIOPrintLogState = PRINT_TO_CONS; /* Just console */
+ return 0;
+
+ case RIO_SIGNALS_ON:
+ if ( p->RIOSignalProcess ) {
+ p->RIOError.Error = SIGNALS_ALREADY_SET;
+ return -EBUSY;
+ }
+ p->RIOSignalProcess = getpid();
+ p->RIOPrintDisabled = DONT_PRINT;
+ return retval;
+
+ case RIO_SIGNALS_OFF:
+ if ( p->RIOSignalProcess != getpid() ) {
+ p->RIOError.Error = NOT_RECEIVING_PROCESS;
+ return -EPERM;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "Clear signal process to zero\n");
+ p->RIOSignalProcess = 0;
+ return retval;
+
+ case RIO_SET_BYTE_MODE:
+ for ( Host=0; Host<p->RIONumHosts; Host++ )
+ if ( p->RIOHosts[Host].Type == RIO_AT )
+ p->RIOHosts[Host].Mode &= ~WORD_OPERATION;
+ return retval;
+
+ case RIO_SET_WORD_MODE:
+ for ( Host=0; Host<p->RIONumHosts; Host++ )
+ if ( p->RIOHosts[Host].Type == RIO_AT )
+ p->RIOHosts[Host].Mode |= WORD_OPERATION;
+ return retval;
+
+ case RIO_SET_FAST_BUS:
+ for ( Host=0; Host<p->RIONumHosts; Host++ )
+ if ( p->RIOHosts[Host].Type == RIO_AT )
+ p->RIOHosts[Host].Mode |= FAST_AT_BUS;
+ return retval;
+
+ case RIO_SET_SLOW_BUS:
+ for ( Host=0; Host<p->RIONumHosts; Host++ )
+ if ( p->RIOHosts[Host].Type == RIO_AT )
+ p->RIOHosts[Host].Mode &= ~FAST_AT_BUS;
+ return retval;
+
+ case RIO_MAP_B50_TO_50:
+ case RIO_MAP_B50_TO_57600:
+ case RIO_MAP_B110_TO_110:
+ case RIO_MAP_B110_TO_115200:
+ rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping\n");
+ port = (uint) arg;
+ if ( port < 0 || port > 511 ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping: Bad port number %d\n", port);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ switch( cmd )
+ {
+ case RIO_MAP_B50_TO_50 :
+ p->RIOPortp[port]->Config |= RIO_MAP_50_TO_50;
+ break;
+ case RIO_MAP_B50_TO_57600 :
+ p->RIOPortp[port]->Config &= ~RIO_MAP_50_TO_50;
+ break;
+ case RIO_MAP_B110_TO_110 :
+ p->RIOPortp[port]->Config |= RIO_MAP_110_TO_110;
+ break;
+ case RIO_MAP_B110_TO_115200 :
+ p->RIOPortp[port]->Config &= ~RIO_MAP_110_TO_110;
+ break;
+ }
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return retval;
+
+ case RIO_STREAM_INFO:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_STREAM_INFO\n");
+ return -EINVAL;
+
+ case RIO_SEND_PACKET:
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SEND_PACKET\n");
+ if ( copyin( (int)arg, (caddr_t)&SendPack,
+ sizeof(SendPack) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_SEND_PACKET: Bad copy from user space\n");
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ if ( SendPack.PortNum >= 128 ) {
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -ENXIO;
+ }
+
+ PortP = p->RIOPortp[SendPack.PortNum];
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ if ( !can_add_transmit(&PacketP,PortP) ) {
+ p->RIOError.Error = UNIT_IS_IN_USE;
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return -ENOSPC;
+ }
+
+ for ( loop=0; loop<(ushort)(SendPack.Len & 127); loop++ )
+ WBYTE(PacketP->data[loop], SendPack.Data[loop] );
+
+ WBYTE(PacketP->len, SendPack.Len);
+
+ add_transmit( PortP );
+ /*
+ ** Count characters transmitted for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->txchars += (SendPack.Len & 127);
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return retval;
+
+ case RIO_NO_MESG:
+ if ( su )
+ p->RIONoMessage = 1;
+ return su ? 0 : -EPERM;
+
+ case RIO_MESG:
+ if ( su )
+ p->RIONoMessage = 0;
+ return su ? 0 : -EPERM;
+
+ case RIO_WHAT_MESG:
+ if ( copyout( (caddr_t)&p->RIONoMessage, (int)arg,
+ sizeof(p->RIONoMessage) )==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_WHAT_MESG: Bad copy to user space\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_MEM_DUMP :
+ if (copyin((int)arg, (caddr_t)&SubCmd,
+ sizeof(struct SubCmdStruct)) == COPYFAIL) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP host %d rup %d addr %x\n",
+ SubCmd.Host, SubCmd.Rup, SubCmd.Addr);
+
+ if (SubCmd.Rup >= MAX_RUP+LINKS_PER_UNIT ) {
+ p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ if (SubCmd.Host >= p->RIONumHosts ) {
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ port = p->RIOHosts[SubCmd.Host].
+ UnixRups[SubCmd.Rup].BaseSysPort;
+
+ PortP = p->RIOPortp[port];
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ if ( RIOPreemptiveCmd(p, PortP, MEMDUMP ) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP failed\n");
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return -EBUSY;
+ }
+ else
+ PortP->State |= RIO_BUSY;
+
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ if ( copyout( (caddr_t)p->RIOMemDump, (int)arg,
+ MEMDUMP_SIZE) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP copy failed\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_TICK:
+ if ((int)arg < 0 || (int)arg >= p->RIONumHosts)
+ return -EINVAL;
+ rio_dprintk (RIO_DEBUG_CTRL, "Set interrupt for host %d\n", (int)arg);
+ WBYTE(p->RIOHosts[(int)arg].SetInt , 0xff);
+ return 0;
+
+ case RIO_TOCK:
+ if ((int)arg < 0 || (int)arg >= p->RIONumHosts)
+ return -EINVAL;
+ rio_dprintk (RIO_DEBUG_CTRL, "Clear interrupt for host %d\n", (int)arg);
+ WBYTE((p->RIOHosts[(int)arg].ResetInt) , 0xff);
+ return 0;
+
+ case RIO_READ_CHECK:
+ /* Check reads for pkts with data[0] the same */
+ p->RIOReadCheck = !p->RIOReadCheck;
+ if (copyout((caddr_t)&p->RIOReadCheck,(int)arg,
+ sizeof(uint))== COPYFAIL) {
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+
+ case RIO_READ_REGISTER :
+ if (copyin((int)arg, (caddr_t)&SubCmd,
+ sizeof(struct SubCmdStruct)) == COPYFAIL) {
+ p->RIOError.Error = COPYIN_FAILED;
+ return -EFAULT;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER host %d rup %d port %d reg %x\n",
+ SubCmd.Host, SubCmd.Rup, SubCmd.Port, SubCmd.Addr);
+
+ if (SubCmd.Port > 511) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping: Bad port number %d\n",
+ SubCmd.Port);
+ p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ if (SubCmd.Rup >= MAX_RUP+LINKS_PER_UNIT ) {
+ p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ if (SubCmd.Host >= p->RIONumHosts ) {
+ p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ port = p->RIOHosts[SubCmd.Host].
+ UnixRups[SubCmd.Rup].BaseSysPort + SubCmd.Port;
+ PortP = p->RIOPortp[port];
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ if (RIOPreemptiveCmd(p, PortP, READ_REGISTER) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER failed\n");
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ return -EBUSY;
+ }
+ else
+ PortP->State |= RIO_BUSY;
+
+ rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+ if (copyout((caddr_t)&p->CdRegister, (int)arg,
+ sizeof(uint)) == COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER copy failed\n");
+ p->RIOError.Error = COPYOUT_FAILED;
+ return -EFAULT;
+ }
+ return 0;
+ /*
+ ** rio_make_dev: given port number (0-511) ORed with port type
+ ** (RIO_DEV_DIRECT, RIO_DEV_MODEM, RIO_DEV_XPRINT) return dev_t
+ ** value to pass to mknod to create the correct device node.
+ */
+ case RIO_MAKE_DEV:
+ {
+ uint port = (uint)arg & RIO_MODEM_MASK;
+
+ switch ( (uint)arg & RIO_DEV_MASK ) {
+ case RIO_DEV_DIRECT:
+ arg = (caddr_t)drv_makedev(MAJOR(dev), port);
+ rio_dprintk (RIO_DEBUG_CTRL, "Makedev direct 0x%x is 0x%x\n",port, (int)arg);
+ return (int)arg;
+ case RIO_DEV_MODEM:
+ arg = (caddr_t)drv_makedev(MAJOR(dev), (port|RIO_MODEM_BIT) );
+ rio_dprintk (RIO_DEBUG_CTRL, "Makedev modem 0x%x is 0x%x\n",port, (int)arg);
+ return (int)arg;
+ case RIO_DEV_XPRINT:
+ arg = (caddr_t)drv_makedev(MAJOR(dev), port);
+ rio_dprintk (RIO_DEBUG_CTRL, "Makedev printer 0x%x is 0x%x\n",port, (int)arg);
+ return (int)arg;
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "MAKE Device is called\n");
+ return -EINVAL;
+ }
+ /*
+ ** rio_minor: given a dev_t from a stat() call, return
+ ** the port number (0-511) ORed with the port type
+ ** ( RIO_DEV_DIRECT, RIO_DEV_MODEM, RIO_DEV_XPRINT )
+ */
+ case RIO_MINOR:
+ {
+ dev_t dv;
+ int mino;
+
+ dv = (dev_t)((int)arg);
+ mino = RIO_UNMODEM(dv);
+
+ if ( RIO_ISMODEM(dv) ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Minor for device 0x%x: modem %d\n", dv, mino);
+ arg = (caddr_t)(mino | RIO_DEV_MODEM);
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_CTRL, "Minor for device 0x%x: direct %d\n", dv, mino);
+ arg = (caddr_t)(mino | RIO_DEV_DIRECT);
+ }
+ return (int)arg;
+ }
+ }
+ rio_dprintk (RIO_DEBUG_CTRL, "INVALID DAEMON IOCTL 0x%x\n",cmd);
+ p->RIOError.Error = IOCTL_COMMAND_UNKNOWN;
+
+ func_exit ();
+ return -EINVAL;
+}
+
+/*
+** Pre-emptive commands go on RUPs and are only one byte long.
+*/
+int
+RIOPreemptiveCmd(p, PortP, Cmd)
+struct rio_info * p;
+struct Port *PortP;
+uchar Cmd;
+{
+ struct CmdBlk *CmdBlkP;
+ struct PktCmd_M *PktCmdP;
+ int Ret;
+ ushort rup;
+ int port;
+
+#ifdef CHECK
+ CheckPortP( PortP );
+#endif
+
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Preemptive command to deleted RTA ignored\n");
+ return RIO_FAIL;
+ }
+
+ if (((int)((char)PortP->InUse) == -1) || ! (CmdBlkP = RIOGetCmdBlk()) ) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Cannot allocate command block for command %d on port %d\n",
+ Cmd, PortP->PortNum);
+ return RIO_FAIL;
+ }
+
+ rio_dprintk (RIO_DEBUG_CTRL, "Command blk 0x%x - InUse now %d\n",
+ (int)CmdBlkP,PortP->InUse);
+
+ PktCmdP = (struct PktCmd_M *)&CmdBlkP->Packet.data[0];
+
+ CmdBlkP->Packet.src_unit = 0;
+ if (PortP->SecondBlock)
+ rup = PortP->ID2;
+ else
+ rup = PortP->RupNum;
+ CmdBlkP->Packet.dest_unit = rup;
+ CmdBlkP->Packet.src_port = COMMAND_RUP;
+ CmdBlkP->Packet.dest_port = COMMAND_RUP;
+ CmdBlkP->Packet.len = PKT_CMD_BIT | 2;
+ CmdBlkP->PostFuncP = RIOUnUse;
+ CmdBlkP->PostArg = (int)PortP;
+ PktCmdP->Command = Cmd;
+ port = PortP->HostPort % (ushort)PORTS_PER_RTA;
+ /*
+ ** Index ports 8-15 for 2nd block of 16 port RTA.
+ */
+ if (PortP->SecondBlock)
+ port += (ushort) PORTS_PER_RTA;
+ PktCmdP->PhbNum = port;
+
+ switch ( Cmd ) {
+ case MEMDUMP:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue MEMDUMP command blk 0x%x (addr 0x%x)\n",
+ (int)CmdBlkP, (int)SubCmd.Addr);
+ PktCmdP->SubCommand = MEMDUMP;
+ PktCmdP->SubAddr = SubCmd.Addr;
+ break;
+ case FCLOSE:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue FCLOSE command blk 0x%x\n",(int)CmdBlkP);
+ break;
+ case READ_REGISTER:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue READ_REGISTER (0x%x) command blk 0x%x\n",
+ (int)SubCmd.Addr, (int)CmdBlkP);
+ PktCmdP->SubCommand = READ_REGISTER;
+ PktCmdP->SubAddr = SubCmd.Addr;
+ break;
+ case RESUME:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue RESUME command blk 0x%x\n",(int)CmdBlkP);
+ break;
+ case RFLUSH:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue RFLUSH command blk 0x%x\n",(int)CmdBlkP);
+ CmdBlkP->PostFuncP = RIORFlushEnable;
+ break;
+ case SUSPEND:
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue SUSPEND command blk 0x%x\n",(int)CmdBlkP);
+ break;
+
+ case MGET :
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue MGET command blk 0x%x\n", (int)CmdBlkP);
+ break;
+
+ case MSET :
+ case MBIC :
+ case MBIS :
+ CmdBlkP->Packet.data[4] = (char) PortP->ModemLines;
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue MSET/MBIC/MBIS command blk 0x%x\n", (int)CmdBlkP);
+ break;
+
+ case WFLUSH:
+ /*
+ ** If we have queued up the maximum number of Write flushes
+ ** allowed then we should not bother sending any more to the
+ ** RTA.
+ */
+ if ((int)((char)PortP->WflushFlag) == (int)-1) {
+ rio_dprintk (RIO_DEBUG_CTRL, "Trashed WFLUSH, WflushFlag about to wrap!");
+ RIOFreeCmdBlk(CmdBlkP);
+ return(RIO_FAIL);
+ } else {
+ rio_dprintk (RIO_DEBUG_CTRL, "Queue WFLUSH command blk 0x%x\n",
+ (int)CmdBlkP);
+ CmdBlkP->PostFuncP = RIOWFlushMark;
+ }
+ break;
+ }
+
+ PortP->InUse++;
+
+ Ret = RIOQueueCmdBlk( PortP->HostP, rup, CmdBlkP );
+
+ return Ret;
+}
diff --git a/drivers/char/rio/riodrvr.h b/drivers/char/rio/riodrvr.h
new file mode 100644
index 000000000000..bc38ac5dfbde
--- /dev/null
+++ b/drivers/char/rio/riodrvr.h
@@ -0,0 +1,144 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riodrvr.h
+** SID : 1.3
+** Last Modified : 11/6/98 09:22:46
+** Retrieved : 11/6/98 09:22:46
+**
+** ident @(#)riodrvr.h 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __riodrvr_h
+#define __riodrvr_h
+
+#include <asm/param.h> /* for HZ */
+
+#ifdef SCCS_LABELS
+static char *_riodrvr_h_sccs_ = "@(#)riodrvr.h 1.3";
+#endif
+
+#define MEMDUMP_SIZE 32
+#define MOD_DISABLE (RIO_NOREAD|RIO_NOWRITE|RIO_NOXPRINT)
+
+
+struct rio_info {
+ int mode; /* Intr or polled, word/byte */
+ spinlock_t RIOIntrSem; /* Interrupt thread sem */
+ int current_chan; /* current channel */
+ int RIOFailed; /* Not initialised ? */
+ int RIOInstallAttempts; /* no. of rio-install() calls */
+ int RIOLastPCISearch; /* status of last search */
+ int RIONumHosts; /* Number of RIO Hosts */
+ struct Host * RIOHosts; /* RIO Host values */
+ struct Port **RIOPortp; /* RIO port values */
+/*
+** 02.03.1999 ARG - ESIL 0820 fix
+** We no longer use RIOBootMode
+**
+ int RIOBootMode; * RIO boot mode *
+**
+*/
+ int RIOPrintDisabled; /* RIO printing disabled ? */
+ int RIOPrintLogState; /* RIO printing state ? */
+ int RIOPolling; /* Polling ? */
+/*
+** 09.12.1998 ARG - ESIL 0776 part fix
+** The 'RIO_QUICK_CHECK' ioctl was using RIOHalted.
+** The fix for this ESIL introduces another member (RIORtaDisCons) here to be
+** updated in RIOConCon() - to keep track of RTA connections/disconnections.
+** 'RIO_QUICK_CHECK' now returns the value of RIORtaDisCons.
+*/
+ int RIOHalted; /* halted ? */
+ int RIORtaDisCons; /* RTA connections/disconnections */
+ uint RIOReadCheck; /* Rio read check */
+ uint RIONoMessage; /* To display message or not */
+ uint RIONumBootPkts; /* how many packets for an RTA */
+ uint RIOBootCount; /* size of RTA code */
+ uint RIOBooting; /* count of outstanding boots */
+ uint RIOSystemUp; /* Booted ?? */
+ uint RIOCounting; /* for counting interrupts */
+ uint RIOIntCount; /* # of intr since last check */
+ uint RIOTxCount; /* number of xmit intrs */
+ uint RIORxCount; /* number of rx intrs */
+ uint RIORupCount; /* number of rup intrs */
+ int RIXTimer;
+ int RIOBufferSize; /* Buffersize */
+ int RIOBufferMask; /* Buffersize */
+
+ int RIOFirstMajor; /* First host card's major no */
+
+ uint RIOLastPortsMapped; /* highest port number known */
+ uint RIOFirstPortsMapped; /* lowest port number known */
+
+ uint RIOLastPortsBooted; /* highest port number running */
+ uint RIOFirstPortsBooted; /* lowest port number running */
+
+ uint RIOLastPortsOpened; /* highest port number running */
+ uint RIOFirstPortsOpened; /* lowest port number running */
+
+ /* Flag to say that the topology information has been changed. */
+ uint RIOQuickCheck;
+ uint CdRegister; /* ??? */
+ int RIOSignalProcess; /* Signalling process */
+ int rio_debug; /* To debug ... */
+ int RIODebugWait; /* For what ??? */
+ int tpri; /* Thread prio */
+ int tid; /* Thread id */
+ uint _RIO_Polled; /* Counter for polling */
+ uint _RIO_Interrupted; /* Counter for interrupt */
+ int intr_tid; /* iointset return value */
+ int TxEnSem; /* TxEnable Semaphore */
+
+
+ struct Error RIOError; /* to Identify what went wrong */
+ struct Conf RIOConf; /* Configuration ??? */
+ struct ttystatics channel[RIO_PORTS]; /* channel information */
+ char RIOBootPackets[1+(SIXTY_FOUR_K/RTA_BOOT_DATA_SIZE)]
+ [RTA_BOOT_DATA_SIZE];
+ struct Map RIOConnectTable[TOTAL_MAP_ENTRIES];
+ struct Map RIOSavedTable[TOTAL_MAP_ENTRIES];
+
+ /* RTA to host binding table for master/slave operation */
+ ulong RIOBindTab[MAX_RTA_BINDINGS];
+ /* RTA memory dump variable */
+ uchar RIOMemDump[MEMDUMP_SIZE];
+ struct ModuleInfo RIOModuleTypes[MAX_MODULE_TYPES];
+
+};
+
+
+#ifdef linux
+#define debug(x) printk x
+#else
+#define debug(x) kkprintf x
+#endif
+
+
+
+#define RIO_RESET_INT 0x7d80
+#define WRBYTE(x,y) *(volatile unsigned char *)((x)) = \
+ (unsigned char)(y)
+
+#endif /* __riodrvr.h */
diff --git a/drivers/char/rio/rioinfo.h b/drivers/char/rio/rioinfo.h
new file mode 100644
index 000000000000..e08421c9558e
--- /dev/null
+++ b/drivers/char/rio/rioinfo.h
@@ -0,0 +1,96 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioinfo.h
+** SID : 1.2
+** Last Modified : 11/6/98 14:07:49
+** Retrieved : 11/6/98 14:07:50
+**
+** ident @(#)rioinfo.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rioinfo_h
+#define __rioinfo_h
+
+#ifdef SCCS_LABELS
+static char *_rioinfo_h_sccs_ = "@(#)rioinfo.h 1.2";
+#endif
+
+/*
+** Host card data structure
+*/
+struct RioHostInfo {
+ long location; /* RIO Card Base I/O address */
+ long vector; /* RIO Card IRQ vector */
+ int bus; /* ISA/EISA/MCA/PCI */
+ int mode; /* pointer to host mode - INTERRUPT / POLLED */
+ struct old_sgttyb
+ * Sg; /* pointer to default term characteristics */
+};
+
+
+/* Mode in rio device info */
+#define INTERRUPTED_MODE 0x01 /* Interrupt is generated */
+#define POLLED_MODE 0x02 /* No interrupt */
+#define AUTO_MODE 0x03 /* Auto mode */
+
+#define WORD_ACCESS_MODE 0x10 /* Word Access Mode */
+#define BYTE_ACCESS_MODE 0x20 /* Byte Access Mode */
+
+
+/* Bus type that RIO supports */
+#define ISA_BUS 0x01 /* The card is ISA */
+#define EISA_BUS 0x02 /* The card is EISA */
+#define MCA_BUS 0x04 /* The card is MCA */
+#define PCI_BUS 0x08 /* The card is PCI */
+
+/*
+** 11.11.1998 ARG - ESIL ???? part fix
+** Moved definition for 'CHAN' here from rioinfo.c (it is now
+** called 'DEF_TERM_CHARACTERISTICS').
+*/
+
+#define DEF_TERM_CHARACTERISTICS \
+{ \
+ B19200, B19200, /* input and output speed */ \
+ 'H' - '@', /* erase char */ \
+ -1, /* 2nd erase char */ \
+ 'U' - '@', /* kill char */ \
+ ECHO | CRMOD, /* mode */ \
+ 'C' - '@', /* interrupt character */ \
+ '\\' - '@', /* quit char */ \
+ 'Q' - '@', /* start char */ \
+ 'S' - '@', /* stop char */ \
+ 'D' - '@', /* EOF */ \
+ -1, /* brk */ \
+ (LCRTBS | LCRTERA | LCRTKIL | LCTLECH), /* local mode word */ \
+ 'Z' - '@', /* process stop */ \
+ 'Y' - '@', /* delayed stop */ \
+ 'R' - '@', /* reprint line */ \
+ 'O' - '@', /* flush output */ \
+ 'W' - '@', /* word erase */ \
+ 'V' - '@' /* literal next char */ \
+}
+
+#endif /* __rioinfo_h */
diff --git a/drivers/char/rio/rioinit.c b/drivers/char/rio/rioinit.c
new file mode 100644
index 000000000000..dca941ed10cf
--- /dev/null
+++ b/drivers/char/rio/rioinit.c
@@ -0,0 +1,1617 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioinit.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:43
+** Retrieved : 11/6/98 10:33:49
+**
+** ident @(#)rioinit.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioinit_c_sccs_ = "@(#)rioinit.c 1.3";
+#endif
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "rio_linux.h"
+
+#undef bcopy
+#define bcopy rio_pcicopy
+
+int RIOPCIinit(struct rio_info *p, int Mode);
+
+#if 0
+static void RIOAllocateInterrupts(struct rio_info *);
+static int RIOReport(struct rio_info *);
+static void RIOStopInterrupts(struct rio_info *, int, int);
+#endif
+
+static int RIOScrub(int, BYTE *, int);
+
+#if 0
+extern int rio_intr();
+
+/*
+** Init time code.
+*/
+void
+rioinit( p, info )
+struct rio_info * p;
+struct RioHostInfo * info;
+{
+ /*
+ ** Multi-Host card support - taking the easy way out - sorry !
+ ** We allocate and set up the Host and Port structs when the
+ ** driver is called to 'install' the first host.
+ ** We check for this first 'call' by testing the RIOPortp pointer.
+ */
+ if ( !p->RIOPortp )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Allocating and setting up driver data structures\n");
+
+ RIOAllocDataStructs(p); /* allocate host/port structs */
+ RIOSetupDataStructs(p); /* setup topology structs */
+ }
+
+ RIOInitHosts( p, info ); /* hunt down the hardware */
+
+ RIOAllocateInterrupts(p); /* allocate interrupts */
+ RIOReport(p); /* show what we found */
+}
+
+/*
+** Initialise the Cards
+*/
+void
+RIOInitHosts(p, info)
+struct rio_info * p;
+struct RioHostInfo * info;
+{
+/*
+** 15.10.1998 ARG - ESIL 0762 part fix
+** If there is no ISA card definition - we always look for PCI cards.
+** As we currently only support one host card this lets an ISA card
+** definition take precedence over PLUG and PLAY.
+** No ISA card - we are PLUG and PLAY with PCI.
+*/
+
+ /*
+ ** Note - for PCI both these will be zero, that's okay because
+ ** RIOPCIInit() fills them in if a card is found.
+ */
+ p->RIOHosts[p->RIONumHosts].Ivec = info->vector;
+ p->RIOHosts[p->RIONumHosts].PaddrP = info->location;
+
+ /*
+ ** Check that we are able to accommodate another host
+ */
+ if ( p->RIONumHosts >= RIO_HOSTS )
+ {
+ p->RIOFailed++;
+ return;
+ }
+
+ if ( info->bus & ISA_BUS )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "initialising card %d (ISA)\n", p->RIONumHosts);
+ RIOISAinit(p, p->mode);
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "initialising card %d (PCI)\n", p->RIONumHosts);
+ RIOPCIinit(p, RIO_PCI_DEFAULT_MODE);
+ }
+
+ rio_dprintk (RIO_DEBUG_INIT, "Total hosts initialised so far : %d\n", p->RIONumHosts);
+
+
+#ifdef FUTURE_RELEASE
+ if (p->bus & EISA_BUS)
+ /* EISA card */
+ RIOEISAinit(p, RIO_EISA_DEFAULT_MODE);
+
+ if (p->bus & MCA_BUS)
+ /* MCA card */
+ RIOMCAinit(p, RIO_MCA_DEFAULT_MODE);
+#endif
+}
+
+/*
+** go through memory for an AT host that we pass in the device info
+** structure and initialise
+*/
+void
+RIOISAinit(p, mode)
+struct rio_info * p;
+int mode;
+{
+
+ /* XXX Need to implement this. */
+#if 0
+ p->intr_tid = iointset(p->RIOHosts[p->RIONumHosts].Ivec,
+ (int (*)())rio_intr, (char*)p->RIONumHosts);
+
+ rio_dprintk (RIO_DEBUG_INIT, "Set interrupt handler, intr_tid = 0x%x\n", p->intr_tid );
+
+ if (RIODoAT(p, p->RIOHosts[p->RIONumHosts].PaddrP, mode)) {
+ return;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_INIT, "RIODoAT failed\n");
+ p->RIOFailed++;
+ }
+#endif
+
+}
+
+/*
+** RIODoAT :
+**
+** Map in a boards physical address, check that the board is there,
+** test the board and if everything is okay assign the board an entry
+** in the Rio Hosts structure.
+*/
+int
+RIODoAT(p, Base, mode)
+struct rio_info * p;
+int Base;
+int mode;
+{
+#define FOUND 1
+#define NOT_FOUND 0
+
+ caddr_t cardAddr;
+
+ /*
+ ** Check to see if we actually have a board at this physical address.
+ */
+ if ((cardAddr = RIOCheckForATCard(Base)) != 0) {
+ /*
+ ** Now test the board to see if it is working.
+ */
+ if (RIOBoardTest(Base, cardAddr, RIO_AT, 0) == RIO_SUCCESS) {
+ /*
+ ** Fill out a slot in the Rio host structure.
+ */
+ if (RIOAssignAT(p, Base, cardAddr, mode)) {
+ return(FOUND);
+ }
+ }
+ RIOMapout(Base, RIO_AT_MEM_SIZE, cardAddr);
+ }
+ return(NOT_FOUND);
+}
+
+caddr_t
+RIOCheckForATCard(Base)
+int Base;
+{
+ int off;
+ struct DpRam *cardp; /* (Points at the host) */
+ caddr_t virtAddr;
+ unsigned char RIOSigTab[24];
+/*
+** Table of values to search for as prom signature of a host card
+*/
+ strcpy(RIOSigTab, "JBJGPGGHINSMJPJR");
+
+ /*
+ ** Hey! Yes, You reading this code! Yo, grab a load a this:
+ **
+ ** IF the card is using WORD MODE rather than BYTE MODE
+ ** then it will occupy 128K of PHYSICAL memory area. So,
+ ** you might think that the following Mapin is wrong. Well,
+ ** it isn't, because the SECOND 64K of occupied space is an
+ ** EXACT COPY of the FIRST 64K. (good?), so, we need only
+ ** map it in in one 64K block.
+ */
+ if (RIOMapin(Base, RIO_AT_MEM_SIZE, &virtAddr) == -1) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Couldn't map the board in!\n");
+ return((caddr_t)0);
+ }
+
+ /*
+ ** virtAddr points to the DP ram of the system.
+ ** We now cast this to a pointer to a RIO Host,
+ ** and have a rummage about in the PROM.
+ */
+ cardp = (struct DpRam *)virtAddr;
+
+ for (off=0; RIOSigTab[off]; off++) {
+ if ((RBYTE(cardp->DpSignature[off]) & 0xFF) != RIOSigTab[off]) {
+ /*
+ ** Signature mismatch - card not at this address
+ */
+ RIOMapout(Base, RIO_AT_MEM_SIZE, virtAddr);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Couldn't match the signature 0x%x 0x%x!\n",
+ (int)cardp, off);
+ return((caddr_t)0);
+ }
+ }
+
+ /*
+ ** If we get here then we must have found a valid board so return
+ ** its virtual address.
+ */
+ return(virtAddr);
+}
+#endif
+
+/**
+** RIOAssignAT :
+**
+** Fill out the fields in the p->RIOHosts structure now we know we know
+** we have a board present.
+**
+** bits < 0 indicates 8 bit operation requested,
+** bits > 0 indicates 16 bit operation.
+*/
+int
+RIOAssignAT(p, Base, virtAddr, mode)
+struct rio_info * p;
+int Base;
+caddr_t virtAddr;
+int mode;
+{
+ int bits;
+ struct DpRam *cardp = (struct DpRam *)virtAddr;
+
+ if ((Base < ONE_MEG) || (mode & BYTE_ACCESS_MODE))
+ bits = BYTE_OPERATION;
+ else
+ bits = WORD_OPERATION;
+
+ /*
+ ** Board has passed its scrub test. Fill in all the
+ ** transient stuff.
+ */
+ p->RIOHosts[p->RIONumHosts].Caddr = virtAddr;
+ p->RIOHosts[p->RIONumHosts].CardP = (struct DpRam *)virtAddr;
+
+ /*
+ ** Revision 01 AT host cards don't support WORD operations,
+ */
+ if ( RBYTE(cardp->DpRevision) == 01 )
+ bits = BYTE_OPERATION;
+
+ p->RIOHosts[p->RIONumHosts].Type = RIO_AT;
+ p->RIOHosts[p->RIONumHosts].Copy = bcopy;
+ /* set this later */
+ p->RIOHosts[p->RIONumHosts].Slot = -1;
+ p->RIOHosts[p->RIONumHosts].Mode = SLOW_LINKS | SLOW_AT_BUS | bits;
+ WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+ BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ p->RIOHosts[p->RIONumHosts].Mode |
+ INTERRUPT_DISABLE );
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+ WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+ BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ p->RIOHosts[p->RIONumHosts].Mode |
+ INTERRUPT_DISABLE );
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+ p->RIOHosts[p->RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0])&0xFF)<<0)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1])&0xFF)<<8)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2])&0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3])&0xFF)<<24);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Uniquenum 0x%x\n",p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+ p->RIONumHosts++;
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Tests Passed at 0x%x\n", Base);
+ return(1);
+}
+#if 0
+#ifdef FUTURE_RELEASE
+int RIOMCAinit(int Mode)
+{
+ uchar SlotNumber;
+ caddr_t Caddr;
+ uint Paddr;
+ uint Ivec;
+ int Handle;
+ int ret = 0;
+
+ /*
+ ** Valid mode information for MCA cards
+ ** is only FAST LINKS
+ */
+ Mode = (Mode & FAST_LINKS) ? McaTpFastLinks : McaTpSlowLinks;
+ rio_dprintk (RIO_DEBUG_INIT, "RIOMCAinit(%d)\n",Mode);
+
+
+ /*
+ ** Check out each of the slots
+ */
+ for (SlotNumber = 0; SlotNumber < McaMaxSlots; SlotNumber++) {
+ /*
+ ** Enable the slot we want to talk to
+ */
+ outb( McaSlotSelect, SlotNumber | McaSlotEnable );
+
+ /*
+ ** Read the ID word from the slot
+ */
+ if (((inb(McaIdHigh)<< 8)|inb(McaIdLow)) == McaRIOId)
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Potential MCA card in slot %d\n", SlotNumber);
+
+ /*
+ ** Card appears to be a RIO MCA card!
+ */
+ RIOMachineType |= (1<<RIO_MCA);
+
+ /*
+ ** Just check we haven't found too many wonderful objects
+ */
+ if ( RIONumHosts >= RIO_HOSTS )
+ {
+ Rprintf(RIOMesgTooManyCards);
+ return(ret);
+ }
+
+ /*
+ ** McaIrqEnable contains the interrupt vector, and a card
+ ** enable bit.
+ */
+ Ivec = inb(McaIrqEnable);
+
+ rio_dprintk (RIO_DEBUG_INIT, "Ivec is %x\n", Ivec);
+
+ switch ( Ivec & McaIrqMask )
+ {
+ case McaIrq9:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ9\n");
+ break;
+ case McaIrq3:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ3\n");
+ break;
+ case McaIrq4:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ4\n");
+ break;
+ case McaIrq7:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ7\n");
+ break;
+ case McaIrq10:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ10\n");
+ break;
+ case McaIrq11:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ11\n");
+ break;
+ case McaIrq12:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ12\n");
+ break;
+ case McaIrq15:
+ rio_dprintk (RIO_DEBUG_INIT, "IRQ15\n");
+ break;
+ }
+
+ /*
+ ** If the card enable bit isn't set, then set it!
+ */
+ if ((Ivec & McaCardEnable) != McaCardEnable) {
+ rio_dprintk (RIO_DEBUG_INIT, "McaCardEnable not set - setting!\n");
+ outb(McaIrqEnable,Ivec|McaCardEnable);
+ } else
+ rio_dprintk (RIO_DEBUG_INIT, "McaCardEnable already set\n");
+
+ /*
+ ** Convert the IRQ enable mask into something useful
+ */
+ Ivec = RIOMcaToIvec[Ivec & McaIrqMask];
+
+ /*
+ ** Find the physical address
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "inb(McaMemory) is %x\n", inb(McaMemory));
+ Paddr = McaAddress(inb(McaMemory));
+
+ rio_dprintk (RIO_DEBUG_INIT, "MCA card has Ivec %d Addr %x\n", Ivec, Paddr);
+
+ if ( Paddr != 0 )
+ {
+
+ /*
+ ** Tell the memory mapper that we want to talk to it
+ */
+ Handle = RIOMapin( Paddr, RIO_MCA_MEM_SIZE, &Caddr );
+
+ if ( Handle == -1 ) {
+ rio_dprintk (RIO_DEBUG_INIT, "Couldn't map %d bytes at %x\n", RIO_MCA_MEM_SIZE, Paddr;
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_INIT, "Board mapped to vaddr 0x%x\n", Caddr);
+
+ /*
+ ** And check that it is actually there!
+ */
+ if ( RIOBoardTest( Paddr,Caddr,RIO_MCA,SlotNumber ) == RIO_SUCCESS )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Board has passed test\n");
+ rio_dprintk (RIO_DEBUG_INIT, "Slot %d. Type %d. Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n",
+ SlotNumber, RIO_MCA, Paddr, Caddr, Mode);
+
+ /*
+ ** Board has passed its scrub test. Fill in all the
+ ** transient stuff.
+ */
+ p->RIOHosts[RIONumHosts].Slot = SlotNumber;
+ p->RIOHosts[RIONumHosts].Ivec = Ivec;
+ p->RIOHosts[RIONumHosts].Type = RIO_MCA;
+ p->RIOHosts[RIONumHosts].Copy = bcopy;
+ p->RIOHosts[RIONumHosts].PaddrP = Paddr;
+ p->RIOHosts[RIONumHosts].Caddr = Caddr;
+ p->RIOHosts[RIONumHosts].CardP = (struct DpRam *)Caddr;
+ p->RIOHosts[RIONumHosts].Mode = Mode;
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt , 0xff);
+ p->RIOHosts[RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[0])&0xFF)<<0)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[1])&0xFF)<<8)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[2])&0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[3])&0xFF)<<24);
+ RIONumHosts++;
+ ret++;
+ }
+ else
+ {
+ /*
+ ** It failed the test, so ignore it.
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "TEST FAILED\n");
+ RIOMapout(Paddr, RIO_MCA_MEM_SIZE, Caddr );
+ }
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Slot %d - Paddr zero!\n", SlotNumber);
+ }
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Slot %d NOT RIO\n", SlotNumber);
+ }
+ }
+ /*
+ ** Now we have checked all the slots, turn off the MCA slot selector
+ */
+ outb(McaSlotSelect,0);
+ rio_dprintk (RIO_DEBUG_INIT, "Slot %d NOT RIO\n", SlotNumber);
+ return ret;
+}
+
+int RIOEISAinit( int Mode )
+{
+ static int EISADone = 0;
+ uint Paddr;
+ int PollIntMixMsgDone = 0;
+ caddr_t Caddr;
+ ushort Ident;
+ uchar EisaSlot;
+ uchar Ivec;
+ int ret = 0;
+
+ /*
+ ** The only valid mode information for EISA hosts is fast or slow
+ ** links.
+ */
+ Mode = (Mode & FAST_LINKS) ? EISA_TP_FAST_LINKS : EISA_TP_SLOW_LINKS;
+
+ if ( EISADone )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "RIOEISAinit() - already done, return.\n");
+ return(0);
+ }
+
+ EISADone++;
+
+ rio_dprintk (RIO_DEBUG_INIT, "RIOEISAinit()\n");
+
+
+ /*
+ ** First check all cards to see if ANY are set for polled mode operation.
+ ** If so, set ALL to polled.
+ */
+
+ for ( EisaSlot=1; EisaSlot<=RIO_MAX_EISA_SLOTS; EisaSlot++ )
+ {
+ Ident = (INBZ(EisaSlot,EISA_PRODUCT_IDENT_HI)<<8) |
+ INBZ(EisaSlot,EISA_PRODUCT_IDENT_LO);
+
+ if ( Ident == RIO_EISA_IDENT )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Found Specialix product\n");
+
+ if ( INBZ(EisaSlot,EISA_PRODUCT_NUMBER) != RIO_EISA_PRODUCT_CODE )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Not Specialix RIO - Product number %x\n",
+ INBZ(EisaSlot, EISA_PRODUCT_NUMBER));
+ continue; /* next slot */
+ }
+ /*
+ ** Its a Specialix RIO!
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "RIO Revision %d\n",
+ INBZ(EisaSlot, EISA_REVISION_NUMBER));
+
+ RIOMachineType |= (1<<RIO_EISA);
+
+ /*
+ ** Just check we haven't found too many wonderful objects
+ */
+ if ( RIONumHosts >= RIO_HOSTS )
+ {
+ Rprintf(RIOMesgTooManyCards);
+ return 0;
+ }
+
+ /*
+ ** Ensure that the enable bit is set!
+ */
+ OUTBZ( EisaSlot, EISA_ENABLE, RIO_EISA_ENABLE_BIT );
+
+ /*
+ ** EISA_INTERRUPT_VEC contains the interrupt vector.
+ */
+ Ivec = INBZ(EisaSlot,EISA_INTERRUPT_VEC);
+
+#ifdef RIODEBUG
+ switch ( Ivec & EISA_INTERRUPT_MASK )
+ {
+ case EISA_IRQ_3:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 3\n");
+ break;
+ case EISA_IRQ_4:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 4\n");
+ break;
+ case EISA_IRQ_5:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 5\n");
+ break;
+ case EISA_IRQ_6:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 6\n");
+ break;
+ case EISA_IRQ_7:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 7\n");
+ break;
+ case EISA_IRQ_9:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 9\n");
+ break;
+ case EISA_IRQ_10:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 10\n");
+ break;
+ case EISA_IRQ_11:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 11\n");
+ break;
+ case EISA_IRQ_12:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 12\n");
+ break;
+ case EISA_IRQ_14:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 14\n");
+ break;
+ case EISA_IRQ_15:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 15\n");
+ break;
+ case EISA_POLLED:
+ rio_dprintk (RIO_DEBUG_INIT, "EISA POLLED\n");
+ break;
+ default:
+ rio_dprintk (RIO_DEBUG_INIT, NULL,DBG_INIT|DBG_FAIL,"Shagged interrupt number!\n");
+ Ivec &= EISA_CONTROL_MASK;
+ }
+#endif
+
+ if ( (Ivec & EISA_INTERRUPT_MASK) ==
+ EISA_POLLED )
+ {
+ RIOWillPoll = 1;
+ break; /* From EisaSlot loop */
+ }
+ }
+ }
+
+ /*
+ ** Do it all again now we know whether to change all cards to polled
+ ** mode or not
+ */
+
+ for ( EisaSlot=1; EisaSlot<=RIO_MAX_EISA_SLOTS; EisaSlot++ )
+ {
+ Ident = (INBZ(EisaSlot,EISA_PRODUCT_IDENT_HI)<<8) |
+ INBZ(EisaSlot,EISA_PRODUCT_IDENT_LO);
+
+ if ( Ident == RIO_EISA_IDENT )
+ {
+ if ( INBZ(EisaSlot,EISA_PRODUCT_NUMBER) != RIO_EISA_PRODUCT_CODE )
+ continue; /* next slot */
+
+ /*
+ ** Its a Specialix RIO!
+ */
+
+ /*
+ ** Ensure that the enable bit is set!
+ */
+ OUTBZ( EisaSlot, EISA_ENABLE, RIO_EISA_ENABLE_BIT );
+
+ /*
+ ** EISA_INTERRUPT_VEC contains the interrupt vector.
+ */
+ Ivec = INBZ(EisaSlot,EISA_INTERRUPT_VEC);
+
+ if ( RIOWillPoll )
+ {
+ /*
+ ** If we are going to operate in polled mode, but this
+ ** board is configured to be interrupt driven, display
+ ** the message explaining the situation to the punter,
+ ** assuming we haven't already done so.
+ */
+
+ if ( !PollIntMixMsgDone &&
+ (Ivec & EISA_INTERRUPT_MASK) != EISA_POLLED )
+ {
+ Rprintf(RIOMesgAllPolled);
+ PollIntMixMsgDone = 1;
+ }
+
+ /*
+ ** Ungraciously ignore whatever the board reports as its
+ ** interrupt vector...
+ */
+
+ Ivec &= ~EISA_INTERRUPT_MASK;
+
+ /*
+ ** ...and force it to dance to the poll tune.
+ */
+
+ Ivec |= EISA_POLLED;
+ }
+
+ /*
+ ** Convert the IRQ enable mask into something useful (0-15)
+ */
+ Ivec = RIOEisaToIvec(Ivec);
+
+ rio_dprintk (RIO_DEBUG_INIT, "EISA host in slot %d has Ivec 0x%x\n",
+ EisaSlot, Ivec);
+
+ /*
+ ** Find the physical address
+ */
+ Paddr = (INBZ(EisaSlot,EISA_MEMORY_BASE_HI)<<24) |
+ (INBZ(EisaSlot,EISA_MEMORY_BASE_LO)<<16);
+
+ rio_dprintk (RIO_DEBUG_INIT, "EISA card has Ivec %d Addr %x\n", Ivec, Paddr);
+
+ if ( Paddr == 0 )
+ {
+ rio_dprintk (RIO_DEBUG_INIT,
+ "Board in slot %d configured for address zero!\n", EisaSlot);
+ continue;
+ }
+
+ /*
+ ** Tell the memory mapper that we want to talk to it
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "About to map EISA card \n");
+
+ if (RIOMapin( Paddr, RIO_EISA_MEM_SIZE, &Caddr) == -1) {
+ rio_dprintk (RIO_DEBUG_INIT, "Couldn't map %d bytes at %x\n",
+ RIO_EISA_MEM_SIZE,Paddr);
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_INIT, "Board mapped to vaddr 0x%x\n", Caddr);
+
+ /*
+ ** And check that it is actually there!
+ */
+ if ( RIOBoardTest( Paddr,Caddr,RIO_EISA,EisaSlot) == RIO_SUCCESS )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Board has passed test\n");
+ rio_dprintk (RIO_DEBUG_INIT,
+ "Slot %d. Ivec %d. Type %d. Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n",
+ EisaSlot,Ivec,RIO_EISA,Paddr,Caddr,Mode);
+
+ /*
+ ** Board has passed its scrub test. Fill in all the
+ ** transient stuff.
+ */
+ p->RIOHosts[RIONumHosts].Slot = EisaSlot;
+ p->RIOHosts[RIONumHosts].Ivec = Ivec;
+ p->RIOHosts[RIONumHosts].Type = RIO_EISA;
+ p->RIOHosts[RIONumHosts].Copy = bcopy;
+ p->RIOHosts[RIONumHosts].PaddrP = Paddr;
+ p->RIOHosts[RIONumHosts].Caddr = Caddr;
+ p->RIOHosts[RIONumHosts].CardP = (struct DpRam *)Caddr;
+ p->RIOHosts[RIONumHosts].Mode = Mode;
+ /*
+ ** because the EISA prom is mapped into IO space, we
+ ** need to copy the unqiue number into the memory area
+ ** that it would have occupied, so that the download
+ ** code can determine its ID and card type.
+ */
+ WBYTE(p->RIOHosts[RIONumHosts].Unique[0],INBZ(EisaSlot,EISA_UNIQUE_NUM_0));
+ WBYTE(p->RIOHosts[RIONumHosts].Unique[1],INBZ(EisaSlot,EISA_UNIQUE_NUM_1));
+ WBYTE(p->RIOHosts[RIONumHosts].Unique[2],INBZ(EisaSlot,EISA_UNIQUE_NUM_2));
+ WBYTE(p->RIOHosts[RIONumHosts].Unique[3],INBZ(EisaSlot,EISA_UNIQUE_NUM_3));
+ p->RIOHosts[RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[0])&0xFF)<<0)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[1])&0xFF)<<8)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[2])&0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[RIONumHosts].Unique[3])&0xFF)<<24);
+ INBZ(EisaSlot,EISA_INTERRUPT_RESET);
+ RIONumHosts++;
+ ret++;
+ }
+ else
+ {
+ /*
+ ** It failed the test, so ignore it.
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "TEST FAILED\n");
+
+ RIOMapout(Paddr, RIO_EISA_MEM_SIZE, Caddr );
+ }
+ }
+ }
+ if (RIOMachineType & RIO_EISA)
+ return ret+1;
+ return ret;
+}
+#endif
+
+
+#ifndef linux
+
+#define CONFIG_ADDRESS 0xcf8
+#define CONFIG_DATA 0xcfc
+#define FORWARD_REG 0xcfa
+
+
+static int
+read_config(int bus_number, int device_num, int r_number)
+{
+ unsigned int cav;
+ unsigned int val;
+
+/*
+ Build config_address_value:
+
+ 31 24 23 16 15 11 10 8 7 0
+ ------------------------------------------------------
+ |1| 0000000 | bus_number | device # | 000 | register |
+ ------------------------------------------------------
+*/
+
+ cav = r_number & 0xff;
+ cav |= ((device_num & 0x1f) << 11);
+ cav |= ((bus_number & 0xff) << 16);
+ cav |= 0x80000000; /* Enable bit */
+ outpd(CONFIG_ADDRESS,cav);
+ val = inpd(CONFIG_DATA);
+ outpd(CONFIG_ADDRESS,0);
+ return val;
+}
+
+static
+write_config(bus_number,device_num,r_number,val)
+{
+ unsigned int cav;
+
+/*
+ Build config_address_value:
+
+ 31 24 23 16 15 11 10 8 7 0
+ ------------------------------------------------------
+ |1| 0000000 | bus_number | device # | 000 | register |
+ ------------------------------------------------------
+*/
+
+ cav = r_number & 0xff;
+ cav |= ((device_num & 0x1f) << 11);
+ cav |= ((bus_number & 0xff) << 16);
+ cav |= 0x80000000; /* Enable bit */
+ outpd(CONFIG_ADDRESS, cav);
+ outpd(CONFIG_DATA, val);
+ outpd(CONFIG_ADDRESS, 0);
+ return val;
+}
+#else
+/* XXX Implement these... */
+static int
+read_config(int bus_number, int device_num, int r_number)
+{
+ return 0;
+}
+
+static int
+write_config(int bus_number, int device_num, int r_number)
+{
+ return 0;
+}
+
+#endif
+
+int
+RIOPCIinit(p, Mode)
+struct rio_info *p;
+int Mode;
+{
+ #define MAX_PCI_SLOT 32
+ #define RIO_PCI_JET_CARD 0x200011CB
+
+ static int slot; /* count of machine's PCI slots searched so far */
+ caddr_t Caddr; /* Virtual address of the current PCI host card. */
+ unsigned char Ivec; /* interrupt vector for the current PCI host */
+ unsigned long Paddr; /* Physical address for the current PCI host */
+ int Handle; /* Handle to Virtual memory allocated for current PCI host */
+
+
+ rio_dprintk (RIO_DEBUG_INIT, "Search for a RIO PCI card - start at slot %d\n", slot);
+
+ /*
+ ** Initialise the search status
+ */
+ p->RIOLastPCISearch = RIO_FAIL;
+
+ while ( (slot < MAX_PCI_SLOT) & (p->RIOLastPCISearch != RIO_SUCCESS) )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Currently testing slot %d\n", slot);
+
+ if (read_config(0,slot,0) == RIO_PCI_JET_CARD) {
+ p->RIOHosts[p->RIONumHosts].Ivec = 0;
+ Paddr = read_config(0,slot,0x18);
+ Paddr = Paddr - (Paddr & 0x1); /* Mask off the io bit */
+
+ if ( (Paddr == 0) || ((Paddr & 0xffff0000) == 0xffff0000) ) {
+ rio_dprintk (RIO_DEBUG_INIT, "Goofed up slot\n"); /* what! */
+ slot++;
+ continue;
+ }
+
+ p->RIOHosts[p->RIONumHosts].PaddrP = Paddr;
+ Ivec = (read_config(0,slot,0x3c) & 0xff);
+
+ rio_dprintk (RIO_DEBUG_INIT, "PCI Host at 0x%x, Intr %d\n", (int)Paddr, Ivec);
+
+ Handle = RIOMapin( Paddr, RIO_PCI_MEM_SIZE, &Caddr );
+ if (Handle == -1) {
+ rio_dprintk (RIO_DEBUG_INIT, "Couldn't map %d bytes at 0x%x\n", RIO_PCI_MEM_SIZE, (int)Paddr);
+ slot++;
+ continue;
+ }
+ p->RIOHosts[p->RIONumHosts].Ivec = Ivec + 32;
+ p->intr_tid = iointset(p->RIOHosts[p->RIONumHosts].Ivec,
+ (int (*)())rio_intr, (char *)p->RIONumHosts);
+ if (RIOBoardTest( Paddr, Caddr, RIO_PCI, 0 ) == RIO_SUCCESS) {
+ rio_dprintk (RIO_DEBUG_INIT, ("Board has passed test\n");
+ rio_dprintk (RIO_DEBUG_INIT, ("Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n", Paddr, Caddr, Mode);
+
+ /*
+ ** Board has passed its scrub test. Fill in all the
+ ** transient stuff.
+ */
+ p->RIOHosts[p->RIONumHosts].Slot = 0;
+ p->RIOHosts[p->RIONumHosts].Ivec = Ivec + 32;
+ p->RIOHosts[p->RIONumHosts].Type = RIO_PCI;
+ p->RIOHosts[p->RIONumHosts].Copy = rio_pcicopy;
+ p->RIOHosts[p->RIONumHosts].PaddrP = Paddr;
+ p->RIOHosts[p->RIONumHosts].Caddr = Caddr;
+ p->RIOHosts[p->RIONumHosts].CardP = (struct DpRam *)Caddr;
+ p->RIOHosts[p->RIONumHosts].Mode = Mode;
+
+#if 0
+ WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+ BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ p->RIOHosts[p->RIONumHosts].Mode |
+ INTERRUPT_DISABLE );
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+ WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+ BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ p->RIOHosts[p->RIONumHosts].Mode |
+ INTERRUPT_DISABLE );
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+#else
+ WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+#endif
+ p->RIOHosts[p->RIONumHosts].UniqueNum =
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0])&0xFF)<<0)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1])&0xFF)<<8)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2])&0xFF)<<16)|
+ ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3])&0xFF)<<24);
+
+ rio_dprintk (RIO_DEBUG_INIT, "Unique no 0x%x.\n",
+ p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+ p->RIOLastPCISearch = RIO_SUCCESS;
+ p->RIONumHosts++;
+ }
+ }
+ slot++;
+ }
+
+ if ( slot >= MAX_PCI_SLOT ) {
+ rio_dprintk (RIO_DEBUG_INIT, "All %d PCI slots have tested for RIO cards !!!\n",
+ MAX_PCI_SLOT);
+ }
+
+
+ /*
+ ** I don't think we want to do this anymore
+ **
+
+ if (!p->RIOLastPCISearch == RIO_FAIL ) {
+ p->RIOFailed++;
+ }
+
+ **
+ */
+}
+
+#ifdef FUTURE_RELEASE
+void riohalt( void )
+{
+ int host;
+ for ( host=0; host<p->RIONumHosts; host++ )
+ {
+ rio_dprintk (RIO_DEBUG_INIT, "Stop host %d\n", host);
+ (void)RIOBoardTest( p->RIOHosts[host].PaddrP, p->RIOHosts[host].Caddr, p->RIOHosts[host].Type,p->RIOHosts[host].Slot );
+ }
+}
+#endif
+#endif
+
+static uchar val[] = {
+#ifdef VERY_LONG_TEST
+ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+ 0xa5, 0xff, 0x5a, 0x00, 0xff, 0xc9, 0x36,
+#endif
+ 0xff, 0x00, 0x00 };
+
+#define TEST_END sizeof(val)
+
+/*
+** RAM test a board.
+** Nothing too complicated, just enough to check it out.
+*/
+int
+RIOBoardTest(paddr, caddr, type, slot)
+paddr_t paddr;
+caddr_t caddr;
+uchar type;
+int slot;
+{
+ struct DpRam *DpRam = (struct DpRam *)caddr;
+ char *ram[4];
+ int size[4];
+ int op, bank;
+ int nbanks;
+
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Reset host type=%d, DpRam=0x%x, slot=%d\n",
+ type,(int)DpRam, slot);
+
+ RIOHostReset(type, DpRam, slot);
+
+ /*
+ ** Scrub the memory. This comes in several banks:
+ ** DPsram1 - 7000h bytes
+ ** DPsram2 - 200h bytes
+ ** DPsram3 - 7000h bytes
+ ** scratch - 1000h bytes
+ */
+
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Setup ram/size arrays\n");
+
+ size[0] = DP_SRAM1_SIZE;
+ size[1] = DP_SRAM2_SIZE;
+ size[2] = DP_SRAM3_SIZE;
+ size[3] = DP_SCRATCH_SIZE;
+
+ ram[0] = (char *)&DpRam->DpSram1[0];
+ ram[1] = (char *)&DpRam->DpSram2[0];
+ ram[2] = (char *)&DpRam->DpSram3[0];
+ nbanks = (type == RIO_PCI) ? 3 : 4;
+ if (nbanks == 4)
+ ram[3] = (char *)&DpRam->DpScratch[0];
+
+
+ if (nbanks == 3) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Memory: 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x)\n",
+ (int)ram[0], size[0], (int)ram[1], size[1], (int)ram[2], size[2]);
+ } else {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x)\n",
+ (int)ram[0], size[0], (int)ram[1], size[1], (int)ram[2], size[2], (int)ram[3],
+ size[3]);
+ }
+
+ /*
+ ** This scrub operation will test for crosstalk between
+ ** banks. TEST_END is a magic number, and relates to the offset
+ ** within the 'val' array used by Scrub.
+ */
+ for (op=0; op<TEST_END; op++) {
+ for (bank=0; bank<nbanks; bank++) {
+ if (RIOScrub(op, (BYTE *)ram[bank], size[bank]) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: RIOScrub band %d, op %d failed\n",
+ bank, op);
+ return RIO_FAIL;
+ }
+ }
+ }
+
+ rio_dprintk (RIO_DEBUG_INIT, "Test completed\n");
+ return RIO_SUCCESS;
+}
+
+
+/*
+** Scrub an area of RAM.
+** Define PRETEST and POSTTEST for a more thorough checking of the
+** state of the memory.
+** Call with op set to an index into the above 'val' array to determine
+** which value will be written into memory.
+** Call with op set to zero means that the RAM will not be read and checked
+** before it is written.
+** Call with op not zero, and the RAM will be read and compated with val[op-1]
+** to check that the data from the previous phase was retained.
+*/
+static int
+RIOScrub(op, ram, size)
+int op;
+BYTE * ram;
+int size;
+{
+ int off;
+ unsigned char oldbyte;
+ unsigned char newbyte;
+ unsigned char invbyte;
+ unsigned short oldword;
+ unsigned short newword;
+ unsigned short invword;
+ unsigned short swapword;
+
+ if (op) {
+ oldbyte = val[op-1];
+ oldword = oldbyte | (oldbyte<<8);
+ } else
+ oldbyte = oldword = 0; /* Tell the compiler we've initilalized them. */
+ newbyte = val[op];
+ newword = newbyte | (newbyte<<8);
+ invbyte = ~newbyte;
+ invword = invbyte | (invbyte<<8);
+
+ /*
+ ** Check that the RAM contains the value that should have been left there
+ ** by the previous test (not applicable for pass zero)
+ */
+ if (op) {
+ for (off=0; off<size; off++) {
+ if (RBYTE(ram[off]) != oldbyte) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Pre Check 1: BYTE at offset 0x%x should have been=%x, was=%x\n", off, oldbyte, RBYTE(ram[off]));
+ return RIO_FAIL;
+ }
+ }
+ for (off=0; off<size; off+=2) {
+ if (*(ushort *)&ram[off] != oldword) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Pre Check: WORD at offset 0x%x should have been=%x, was=%x\n",off,oldword,*(ushort *)&ram[off]);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Pre Check: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+ }
+ }
+
+ /*
+ ** Now write the INVERSE of the test data into every location, using
+ ** BYTE write operations, first checking before each byte is written
+ ** that the location contains the old value still, and checking after
+ ** the write that the location contains the data specified - this is
+ ** the BYTE read/write test.
+ */
+ for (off=0; off<size; off++) {
+ if (op && (RBYTE(ram[off]) != oldbyte)) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Pre Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off, oldbyte, RBYTE(ram[off]));
+ return RIO_FAIL;
+ }
+ WBYTE(ram[off],invbyte);
+ if (RBYTE(ram[off]) != invbyte) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Inv Check: BYTE at offset 0x%x should have been=%x, was=%x\n", off, invbyte, RBYTE(ram[off]));
+ return RIO_FAIL;
+ }
+ }
+
+ /*
+ ** now, use WORD operations to write the test value into every location,
+ ** check as before that the location contains the previous test value
+ ** before overwriting, and that it contains the data value written
+ ** afterwards.
+ ** This is the WORD operation test.
+ */
+ for (off=0; off<size; off+=2) {
+ if (*(ushort *)&ram[off] != invword) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Inv Check: WORD at offset 0x%x should have been=%x, was=%x\n", off, invword, *(ushort *)&ram[off]);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Inv Check: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+
+ *(ushort *)&ram[off] = newword;
+ if ( *(ushort *)&ram[off] != newword ) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 1: WORD at offset 0x%x should have been=%x, was=%x\n", off, newword, *(ushort *)&ram[off]);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 1: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+ }
+
+ /*
+ ** now run through the block of memory again, first in byte mode
+ ** then in word mode, and check that all the locations contain the
+ ** required test data.
+ */
+ for (off=0; off<size; off++) {
+ if (RBYTE(ram[off]) != newbyte) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Byte Check: BYTE at offset 0x%x should have been=%x, was=%x\n", off, newbyte, RBYTE(ram[off]));
+ return RIO_FAIL;
+ }
+ }
+
+ for (off=0; off<size; off+=2) {
+ if ( *(ushort *)&ram[off] != newword ) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 2: WORD at offset 0x%x should have been=%x, was=%x\n", off, newword, *(ushort *)&ram[off]);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 2: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+ }
+
+ /*
+ ** time to check out byte swapping errors
+ */
+ swapword = invbyte | (newbyte << 8);
+
+ for (off=0; off<size; off+=2) {
+ WBYTE(ram[off],invbyte);
+ WBYTE(ram[off+1],newbyte);
+ }
+
+ for ( off=0; off<size; off+=2 ) {
+ if (*(ushort *)&ram[off] != swapword) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 1: WORD at offset 0x%x should have been=%x, was=%x\n", off, swapword, *((ushort *)&ram[off]));
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 1: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+ *((ushort *)&ram[off]) = ~swapword;
+ }
+
+ for (off=0; off<size; off+=2) {
+ if (RBYTE(ram[off]) != newbyte) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off, newbyte, RBYTE(ram[off]));
+ return RIO_FAIL;
+ }
+ if (RBYTE(ram[off+1]) != invbyte) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off+1, invbyte, RBYTE(ram[off+1]));
+ return RIO_FAIL;
+ }
+ *((ushort *)&ram[off]) = newword;
+ }
+ return RIO_SUCCESS;
+}
+
+/*
+** try to ensure that every host is either in polled mode
+** or is in interrupt mode. Only allow interrupt mode if
+** all hosts can interrupt (why?)
+** and force into polled mode if told to. Patch up the
+** interrupt vector & salute The Queen when you've done.
+*/
+#if 0
+static void
+RIOAllocateInterrupts(p)
+struct rio_info * p;
+{
+ int Host;
+
+ /*
+ ** Easy case - if we have been told to poll, then we poll.
+ */
+ if (p->mode & POLLED_MODE) {
+ RIOStopInterrupts(p, 0, 0);
+ return;
+ }
+
+ /*
+ ** check - if any host has been set to polled mode, then all must be.
+ */
+ for (Host=0; Host<p->RIONumHosts; Host++) {
+ if ( (p->RIOHosts[Host].Type != RIO_AT) &&
+ (p->RIOHosts[Host].Ivec == POLLED) ) {
+ RIOStopInterrupts(p, 1, Host );
+ return;
+ }
+ }
+ for (Host=0; Host<p->RIONumHosts; Host++) {
+ if (p->RIOHosts[Host].Type == RIO_AT) {
+ if ( (p->RIOHosts[Host].Ivec - 32) == 0) {
+ RIOStopInterrupts(p, 2, Host );
+ return;
+ }
+ }
+ }
+}
+
+/*
+** something has decided that we can't be doing with these
+** new-fangled interrupt thingies. Set everything up to just
+** poll.
+*/
+static void
+RIOStopInterrupts(p, Reason, Host)
+struct rio_info * p;
+int Reason;
+int Host;
+{
+#ifdef FUTURE_RELEASE
+ switch (Reason) {
+ case 0: /* forced into polling by rio_polled */
+ break;
+ case 1: /* SCU has set 'Host' into polled mode */
+ break;
+ case 2: /* there aren't enough interrupt vectors for 'Host' */
+ break;
+ }
+#endif
+
+ for (Host=0; Host<p->RIONumHosts; Host++ ) {
+ struct Host *HostP = &p->RIOHosts[Host];
+
+ switch (HostP->Type) {
+ case RIO_AT:
+ /*
+ ** The AT host has it's interrupts disabled by clearing the
+ ** int_enable bit.
+ */
+ HostP->Mode &= ~INTERRUPT_ENABLE;
+ HostP->Ivec = POLLED;
+ break;
+#ifdef FUTURE_RELEASE
+ case RIO_EISA:
+ /*
+ ** The EISA host has it's interrupts disabled by setting the
+ ** Ivec to zero
+ */
+ HostP->Ivec = POLLED;
+ break;
+#endif
+ case RIO_PCI:
+ /*
+ ** The PCI host has it's interrupts disabled by clearing the
+ ** int_enable bit, like a regular host card.
+ */
+ HostP->Mode &= ~RIO_PCI_INT_ENABLE;
+ HostP->Ivec = POLLED;
+ break;
+#ifdef FUTURE_RELEASE
+ case RIO_MCA:
+ /*
+ ** There's always one, isn't there?
+ ** The MCA host card cannot have it's interrupts disabled.
+ */
+ RIOPatchVec(HostP);
+ break;
+#endif
+ }
+ }
+}
+
+/*
+** This function is called at init time to setup the data structures.
+*/
+void
+RIOAllocDataStructs(p)
+struct rio_info * p;
+{
+ int port,
+ host,
+ tm;
+
+ p->RIOPortp = (struct Port *)sysbrk(RIO_PORTS * sizeof(struct Port));
+ if (!p->RIOPortp) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: No memory for port structures\n");
+ p->RIOFailed++;
+ return;
+ }
+ bzero( p->RIOPortp, sizeof(struct Port) * RIO_PORTS );
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: allocated and cleared memory for port structs\n");
+ rio_dprintk (RIO_DEBUG_INIT, "First RIO port struct @0x%x, size=0x%x bytes\n",
+ (int)p->RIOPortp, sizeof(struct Port));
+
+ for( port=0; port<RIO_PORTS; port++ ) {
+ p->RIOPortp[port].PortNum = port;
+ p->RIOPortp[port].TtyP = &p->channel[port];
+ sreset (p->RIOPortp[port].InUse); /* Let the first guy uses it */
+ p->RIOPortp[port].portSem = -1; /* Let the first guy takes it */
+ p->RIOPortp[port].ParamSem = -1; /* Let the first guy takes it */
+ p->RIOPortp[port].timeout_id = 0; /* Let the first guy takes it */
+ }
+
+ p->RIOHosts = (struct Host *)sysbrk(RIO_HOSTS * sizeof(struct Host));
+ if (!p->RIOHosts) {
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: No memory for host structures\n");
+ p->RIOFailed++;
+ return;
+ }
+ bzero(p->RIOHosts, sizeof(struct Host)*RIO_HOSTS);
+ rio_dprintk (RIO_DEBUG_INIT, "RIO-init: allocated and cleared memory for host structs\n");
+ rio_dprintk (RIO_DEBUG_INIT, "First RIO host struct @0x%x, size=0x%x bytes\n",
+ (int)p->RIOHosts, sizeof(struct Host));
+
+ for( host=0; host<RIO_HOSTS; host++ ) {
+ spin_lock_init (&p->RIOHosts[host].HostLock);
+ p->RIOHosts[host].timeout_id = 0; /* Let the first guy takes it */
+ }
+ /*
+ ** check that the buffer size is valid, round down to the next power of
+ ** two if necessary; if the result is zero, then, hey, no double buffers.
+ */
+ for ( tm = 1; tm && tm <= p->RIOConf.BufferSize; tm <<= 1 )
+ ;
+ tm >>= 1;
+ p->RIOBufferSize = tm;
+ p->RIOBufferMask = tm ? tm - 1 : 0;
+}
+
+/*
+** this function gets called whenever the data structures need to be
+** re-setup, for example, after a riohalt (why did I ever invent it?)
+*/
+void
+RIOSetupDataStructs(p)
+struct rio_info * p;
+{
+ int host, entry, rup;
+
+ for ( host=0; host<RIO_HOSTS; host++ ) {
+ struct Host *HostP = &p->RIOHosts[host];
+ for ( entry=0; entry<LINKS_PER_UNIT; entry++ ) {
+ HostP->Topology[entry].Unit = ROUTE_DISCONNECT;
+ HostP->Topology[entry].Link = NO_LINK;
+ }
+ bcopy("HOST X", HostP->Name, 7);
+ HostP->Name[5] = '1'+host;
+ for (rup=0; rup<(MAX_RUP + LINKS_PER_UNIT); rup++) {
+ if (rup < MAX_RUP) {
+ for (entry=0; entry<LINKS_PER_UNIT; entry++ ) {
+ HostP->Mapping[rup].Topology[entry].Unit = ROUTE_DISCONNECT;
+ HostP->Mapping[rup].Topology[entry].Link = NO_LINK;
+ }
+ RIODefaultName(p, HostP, rup);
+ }
+ spin_lock_init(&HostP->UnixRups[rup].RupLock);
+ }
+ }
+}
+#endif
+
+int
+RIODefaultName(p, HostP, UnitId)
+struct rio_info * p;
+struct Host * HostP;
+uint UnitId;
+{
+#ifdef CHECK
+ CheckHost( Host );
+ CheckUnitId( UnitId );
+#endif
+ bcopy("UNKNOWN RTA X-XX",HostP->Mapping[UnitId].Name,17);
+ HostP->Mapping[UnitId].Name[12]='1'+(HostP-p->RIOHosts);
+ if ((UnitId+1) > 9) {
+ HostP->Mapping[UnitId].Name[14]='0'+((UnitId+1)/10);
+ HostP->Mapping[UnitId].Name[15]='0'+((UnitId+1)%10);
+ }
+ else {
+ HostP->Mapping[UnitId].Name[14]='1'+UnitId;
+ HostP->Mapping[UnitId].Name[15]=0;
+ }
+ return 0;
+}
+
+#define RIO_RELEASE "Linux"
+#define RELEASE_ID "1.0"
+
+#if 0
+static int
+RIOReport(p)
+struct rio_info * p;
+{
+ char * RIORelease = RIO_RELEASE;
+ char * RIORelID = RELEASE_ID;
+ int host;
+
+ rio_dprintk (RIO_DEBUG_INIT, "RIO : Release: %s ID: %s\n", RIORelease, RIORelID);
+
+ if ( p->RIONumHosts==0 ) {
+ rio_dprintk (RIO_DEBUG_INIT, "\nNo Hosts configured\n");
+ return(0);
+ }
+
+ for ( host=0; host < p->RIONumHosts; host++ ) {
+ struct Host *HostP = &p->RIOHosts[host];
+ switch ( HostP->Type ) {
+ case RIO_AT:
+ rio_dprintk (RIO_DEBUG_INIT, "AT BUS : found the card at 0x%x\n", HostP->PaddrP);
+ }
+ }
+ return 0;
+}
+#endif
+
+static struct rioVersion stVersion;
+
+struct rioVersion *
+RIOVersid(void)
+{
+ strlcpy(stVersion.version, "RIO driver for linux V1.0",
+ sizeof(stVersion.version));
+ strlcpy(stVersion.buildDate, __DATE__,
+ sizeof(stVersion.buildDate));
+
+ return &stVersion;
+}
+
+#if 0
+int
+RIOMapin(paddr, size, vaddr)
+paddr_t paddr;
+int size;
+caddr_t * vaddr;
+{
+ *vaddr = (caddr_t)permap( (long)paddr, size);
+ return ((int)*vaddr);
+}
+
+void
+RIOMapout(paddr, size, vaddr)
+paddr_t paddr;
+long size;
+caddr_t vaddr;
+{
+}
+#endif
+
+
+void
+RIOHostReset(Type, DpRamP, Slot)
+uint Type;
+volatile struct DpRam *DpRamP;
+uint Slot;
+{
+ /*
+ ** Reset the Tpu
+ */
+ rio_dprintk (RIO_DEBUG_INIT, "RIOHostReset: type 0x%x", Type);
+ switch ( Type ) {
+ case RIO_AT:
+ rio_dprintk (RIO_DEBUG_INIT, " (RIO_AT)\n");
+ WBYTE(DpRamP->DpControl, BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ INTERRUPT_DISABLE | BYTE_OPERATION |
+ SLOW_LINKS | SLOW_AT_BUS);
+ WBYTE(DpRamP->DpResetTpu, 0xFF);
+ rio_udelay (3);
+
+ rio_dprintk (RIO_DEBUG_INIT, "RIOHostReset: Don't know if it worked. Try reset again\n");
+ WBYTE(DpRamP->DpControl, BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+ INTERRUPT_DISABLE | BYTE_OPERATION |
+ SLOW_LINKS | SLOW_AT_BUS);
+ WBYTE(DpRamP->DpResetTpu, 0xFF);
+ rio_udelay (3);
+ break;
+#ifdef FUTURE_RELEASE
+ case RIO_EISA:
+ /*
+ ** Bet this doesn't work!
+ */
+ OUTBZ( Slot, EISA_CONTROL_PORT,
+ EISA_TP_RUN | EISA_TP_BUS_DISABLE |
+ EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+ OUTBZ( Slot, EISA_CONTROL_PORT,
+ EISA_TP_RESET | EISA_TP_BUS_DISABLE |
+ EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+ suspend( 3 );
+ OUTBZ( Slot, EISA_CONTROL_PORT,
+ EISA_TP_RUN | EISA_TP_BUS_DISABLE |
+ EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+ break;
+ case RIO_MCA:
+ WBYTE(DpRamP->DpControl , McaTpBootFromRam | McaTpBusDisable );
+ WBYTE(DpRamP->DpResetTpu , 0xFF );
+ suspend( 3 );
+ WBYTE(DpRamP->DpControl , McaTpBootFromRam | McaTpBusDisable );
+ WBYTE(DpRamP->DpResetTpu , 0xFF );
+ suspend( 3 );
+ break;
+#endif
+ case RIO_PCI:
+ rio_dprintk (RIO_DEBUG_INIT, " (RIO_PCI)\n");
+ DpRamP->DpControl = RIO_PCI_BOOT_FROM_RAM;
+ DpRamP->DpResetInt = 0xFF;
+ DpRamP->DpResetTpu = 0xFF;
+ rio_udelay (100);
+ /* for (i=0; i<6000; i++); */
+ /* suspend( 3 ); */
+ break;
+#ifdef FUTURE_RELEASE
+ default:
+ Rprintf(RIOMesgNoSupport,Type,DpRamP,Slot);
+ return;
+#endif
+
+ default:
+ rio_dprintk (RIO_DEBUG_INIT, " (UNKNOWN)\n");
+ break;
+ }
+ return;
+}
diff --git a/drivers/char/rio/riointr.c b/drivers/char/rio/riointr.c
new file mode 100644
index 000000000000..e42e7b50bf6b
--- /dev/null
+++ b/drivers/char/rio/riointr.c
@@ -0,0 +1,951 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riointr.c
+** SID : 1.2
+** Last Modified : 11/6/98 10:33:44
+** Retrieved : 11/6/98 10:33:49
+**
+** ident @(#)riointr.c 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riointr_c_sccs_ = "@(#)riointr.c 1.2";
+#endif
+
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+#include <linux/delay.h>
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+
+
+static void RIOReceive(struct rio_info *, struct Port *);
+
+
+static char *firstchars (char *p, int nch)
+{
+ static char buf[2][128];
+ static int t=0;
+ t = ! t;
+ memcpy (buf[t], p, nch);
+ buf[t][nch] = 0;
+ return buf[t];
+}
+
+
+#define INCR( P, I ) ((P) = (((P)+(I)) & p->RIOBufferMask))
+/* Enable and start the transmission of packets */
+void
+RIOTxEnable(en)
+char * en;
+{
+ struct Port * PortP;
+ struct rio_info *p;
+ struct tty_struct* tty;
+ int c;
+ struct PKT * PacketP;
+ unsigned long flags;
+
+ PortP = (struct Port *)en;
+ p = (struct rio_info *)PortP->p;
+ tty = PortP->gs.tty;
+
+
+ rio_dprintk (RIO_DEBUG_INTR, "tx port %d: %d chars queued.\n",
+ PortP->PortNum, PortP->gs.xmit_cnt);
+
+ if (!PortP->gs.xmit_cnt) return;
+
+
+ /* This routine is an order of magnitude simpler than the specialix
+ version. One of the disadvantages is that this version will send
+ an incomplete packet (usually 64 bytes instead of 72) once for
+ every 4k worth of data. Let's just say that this won't influence
+ performance significantly..... */
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ while (can_add_transmit( &PacketP, PortP )) {
+ c = PortP->gs.xmit_cnt;
+ if (c > PKT_MAX_DATA_LEN) c = PKT_MAX_DATA_LEN;
+
+ /* Don't copy past the end of the source buffer */
+ if (c > SERIAL_XMIT_SIZE - PortP->gs.xmit_tail)
+ c = SERIAL_XMIT_SIZE - PortP->gs.xmit_tail;
+
+ { int t;
+ t = (c > 10)?10:c;
+
+ rio_dprintk (RIO_DEBUG_INTR, "rio: tx port %d: copying %d chars: %s - %s\n",
+ PortP->PortNum, c,
+ firstchars (PortP->gs.xmit_buf + PortP->gs.xmit_tail , t),
+ firstchars (PortP->gs.xmit_buf + PortP->gs.xmit_tail + c-t, t));
+ }
+ /* If for one reason or another, we can't copy more data,
+ we're done! */
+ if (c == 0) break;
+
+ rio_memcpy_toio (PortP->HostP->Caddr, (caddr_t)PacketP->data,
+ PortP->gs.xmit_buf + PortP->gs.xmit_tail, c);
+ /* udelay (1); */
+
+ writeb (c, &(PacketP->len));
+ if (!( PortP->State & RIO_DELETED ) ) {
+ add_transmit ( PortP );
+ /*
+ ** Count chars tx'd for port statistics reporting
+ */
+ if ( PortP->statsGather )
+ PortP->txchars += c;
+ }
+ PortP->gs.xmit_tail = (PortP->gs.xmit_tail + c) & (SERIAL_XMIT_SIZE-1);
+ PortP->gs.xmit_cnt -= c;
+ }
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+ if (PortP->gs.xmit_cnt <= (PortP->gs.wakeup_chars + 2*PKT_MAX_DATA_LEN)) {
+ rio_dprintk (RIO_DEBUG_INTR, "Waking up.... ldisc:%d (%d/%d)....",
+ (int)(PortP->gs.tty->flags & (1 << TTY_DO_WRITE_WAKEUP)),
+ PortP->gs.wakeup_chars, PortP->gs.xmit_cnt);
+ if ((PortP->gs.tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+ PortP->gs.tty->ldisc.write_wakeup)
+ (PortP->gs.tty->ldisc.write_wakeup)(PortP->gs.tty);
+ rio_dprintk (RIO_DEBUG_INTR, "(%d/%d)\n",
+ PortP->gs.wakeup_chars, PortP->gs.xmit_cnt);
+ wake_up_interruptible(&PortP->gs.tty->write_wait);
+ }
+
+}
+
+
+/*
+** RIO Host Service routine. Does all the work traditionally associated with an
+** interrupt.
+*/
+static int RupIntr;
+static int RxIntr;
+static int TxIntr;
+void
+RIOServiceHost(p, HostP, From)
+struct rio_info * p;
+struct Host *HostP;
+int From;
+{
+ rio_spin_lock (&HostP->HostLock);
+ if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+ static int t =0;
+ rio_spin_unlock (&HostP->HostLock);
+ if ((t++ % 200) == 0)
+ rio_dprintk (RIO_DEBUG_INTR, "Interrupt but host not running. flags=%x.\n", (int)HostP->Flags);
+ return;
+ }
+ rio_spin_unlock (&HostP->HostLock);
+
+ if ( RWORD( HostP->ParmMapP->rup_intr ) ) {
+ WWORD( HostP->ParmMapP->rup_intr , 0 );
+ p->RIORupCount++;
+ RupIntr++;
+ rio_dprintk (RIO_DEBUG_INTR, "rio: RUP interrupt on host %d\n", HostP-p->RIOHosts);
+ RIOPollHostCommands(p, HostP );
+ }
+
+ if ( RWORD( HostP->ParmMapP->rx_intr ) ) {
+ int port;
+
+ WWORD( HostP->ParmMapP->rx_intr , 0 );
+ p->RIORxCount++;
+ RxIntr++;
+
+ rio_dprintk (RIO_DEBUG_INTR, "rio: RX interrupt on host %d\n", HostP-p->RIOHosts);
+ /*
+ ** Loop through every port. If the port is mapped into
+ ** the system ( i.e. has /dev/ttyXXXX associated ) then it is
+ ** worth checking. If the port isn't open, grab any packets
+ ** hanging on its receive queue and stuff them on the free
+ ** list; check for commands on the way.
+ */
+ for ( port=p->RIOFirstPortsBooted;
+ port<p->RIOLastPortsBooted+PORTS_PER_RTA; port++ ) {
+ struct Port *PortP = p->RIOPortp[port];
+ struct tty_struct *ttyP;
+ struct PKT *PacketP;
+
+ /*
+ ** not mapped in - most of the RIOPortp[] information
+ ** has not been set up!
+ ** Optimise: ports come in bundles of eight.
+ */
+ if ( !PortP->Mapped ) {
+ port += 7;
+ continue; /* with the next port */
+ }
+
+ /*
+ ** If the host board isn't THIS host board, check the next one.
+ ** optimise: ports come in bundles of eight.
+ */
+ if ( PortP->HostP != HostP ) {
+ port += 7;
+ continue;
+ }
+
+ /*
+ ** Let us see - is the port open? If not, then don't service it.
+ */
+ if ( !( PortP->PortState & PORT_ISOPEN ) ) {
+ continue;
+ }
+
+ /*
+ ** find corresponding tty structure. The process of mapping
+ ** the ports puts these here.
+ */
+ ttyP = PortP->gs.tty;
+
+ /*
+ ** Lock the port before we begin working on it.
+ */
+ rio_spin_lock(&PortP->portSem);
+
+ /*
+ ** Process received data if there is any.
+ */
+ if ( can_remove_receive( &PacketP, PortP ) )
+ RIOReceive(p, PortP);
+
+ /*
+ ** If there is no data left to be read from the port, and
+ ** it's handshake bit is set, then we must clear the handshake,
+ ** so that that downstream RTA is re-enabled.
+ */
+ if ( !can_remove_receive( &PacketP, PortP ) &&
+ ( RWORD( PortP->PhbP->handshake )==PHB_HANDSHAKE_SET ) ) {
+ /*
+ ** MAGIC! ( Basically, handshake the RX buffer, so that
+ ** the RTAs upstream can be re-enabled. )
+ */
+ rio_dprintk (RIO_DEBUG_INTR, "Set RX handshake bit\n");
+ WWORD( PortP->PhbP->handshake,
+ PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET );
+ }
+ rio_spin_unlock(&PortP->portSem);
+ }
+ }
+
+ if ( RWORD( HostP->ParmMapP->tx_intr ) ) {
+ int port;
+
+ WWORD( HostP->ParmMapP->tx_intr , 0);
+
+ p->RIOTxCount++;
+ TxIntr++;
+ rio_dprintk (RIO_DEBUG_INTR, "rio: TX interrupt on host %d\n", HostP-p->RIOHosts);
+
+ /*
+ ** Loop through every port.
+ ** If the port is mapped into the system ( i.e. has /dev/ttyXXXX
+ ** associated ) then it is worth checking.
+ */
+ for ( port=p->RIOFirstPortsBooted;
+ port<p->RIOLastPortsBooted+PORTS_PER_RTA; port++ ) {
+ struct Port *PortP = p->RIOPortp[port];
+ struct tty_struct *ttyP;
+ struct PKT *PacketP;
+
+ /*
+ ** not mapped in - most of the RIOPortp[] information
+ ** has not been set up!
+ */
+ if ( !PortP->Mapped ) {
+ port += 7;
+ continue; /* with the next port */
+ }
+
+ /*
+ ** If the host board isn't running, then its data structures
+ ** are no use to us - continue quietly.
+ */
+ if ( PortP->HostP != HostP ) {
+ port += 7;
+ continue; /* with the next port */
+ }
+
+ /*
+ ** Let us see - is the port open? If not, then don't service it.
+ */
+ if ( !( PortP->PortState & PORT_ISOPEN ) ) {
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_INTR, "rio: Looking into port %d.\n", port);
+ /*
+ ** Lock the port before we begin working on it.
+ */
+ rio_spin_lock(&PortP->portSem);
+
+ /*
+ ** If we can't add anything to the transmit queue, then
+ ** we need do none of this processing.
+ */
+ if ( !can_add_transmit( &PacketP, PortP ) ) {
+ rio_dprintk (RIO_DEBUG_INTR, "Can't add to port, so skipping.\n");
+ rio_spin_unlock(&PortP->portSem);
+ continue;
+ }
+
+ /*
+ ** find corresponding tty structure. The process of mapping
+ ** the ports puts these here.
+ */
+ ttyP = PortP->gs.tty;
+ /* If ttyP is NULL, the port is getting closed. Forget about it. */
+ if (!ttyP) {
+ rio_dprintk (RIO_DEBUG_INTR, "no tty, so skipping.\n");
+ rio_spin_unlock(&PortP->portSem);
+ continue;
+ }
+ /*
+ ** If there is more room available we start up the transmit
+ ** data process again. This can be direct I/O, if the cookmode
+ ** is set to COOK_RAW or COOK_MEDIUM, or will be a call to the
+ ** riotproc( T_OUTPUT ) if we are in COOK_WELL mode, to fetch
+ ** characters via the line discipline. We must always call
+ ** the line discipline,
+ ** so that user input characters can be echoed correctly.
+ **
+ ** ++++ Update +++++
+ ** With the advent of double buffering, we now see if
+ ** TxBufferOut-In is non-zero. If so, then we copy a packet
+ ** to the output place, and set it going. If this empties
+ ** the buffer, then we must issue a wakeup( ) on OUT.
+ ** If it frees space in the buffer then we must issue
+ ** a wakeup( ) on IN.
+ **
+ ** ++++ Extra! Extra! If PortP->WflushFlag is set, then we
+ ** have to send a WFLUSH command down the PHB, to mark the
+ ** end point of a WFLUSH. We also need to clear out any
+ ** data from the double buffer! ( note that WflushFlag is a
+ ** *count* of the number of WFLUSH commands outstanding! )
+ **
+ ** ++++ And there's more!
+ ** If an RTA is powered off, then on again, and rebooted,
+ ** whilst it has ports open, then we need to re-open the ports.
+ ** ( reasonable enough ). We can't do this when we spot the
+ ** re-boot, in interrupt time, because the queue is probably
+ ** full. So, when we come in here, we need to test if any
+ ** ports are in this condition, and re-open the port before
+ ** we try to send any more data to it. Now, the re-booted
+ ** RTA will be discarding packets from the PHB until it
+ ** receives this open packet, but don't worry tooo much
+ ** about that. The one thing that is interesting is the
+ ** combination of this effect and the WFLUSH effect!
+ */
+ /* For now don't handle RTA reboots. -- REW.
+ Reenabled. Otherwise RTA reboots didn't work. Duh. -- REW */
+ if ( PortP->MagicFlags ) {
+#if 1
+ if ( PortP->MagicFlags & MAGIC_REBOOT ) {
+ /*
+ ** well, the RTA has been rebooted, and there is room
+ ** on its queue to add the open packet that is required.
+ **
+ ** The messy part of this line is trying to decide if
+ ** we need to call the Param function as a tty or as
+ ** a modem.
+ ** DONT USE CLOCAL AS A TEST FOR THIS!
+ **
+ ** If we can't param the port, then move on to the
+ ** next port.
+ */
+ PortP->InUse = NOT_INUSE;
+
+ rio_spin_unlock(&PortP->portSem);
+ if ( RIOParam(PortP, OPEN, ((PortP->Cor2Copy &
+ (COR2_RTSFLOW|COR2_CTSFLOW ) )==
+ (COR2_RTSFLOW|COR2_CTSFLOW ) ) ?
+ TRUE : FALSE, DONT_SLEEP ) == RIO_FAIL ) {
+ continue; /* with next port */
+ }
+ rio_spin_lock(&PortP->portSem);
+ PortP->MagicFlags &= ~MAGIC_REBOOT;
+ }
+#endif
+
+ /*
+ ** As mentioned above, this is a tacky hack to cope
+ ** with WFLUSH
+ */
+ if ( PortP->WflushFlag ) {
+ rio_dprintk (RIO_DEBUG_INTR, "Want to WFLUSH mark this port\n");
+
+ if ( PortP->InUse )
+ rio_dprintk (RIO_DEBUG_INTR, "FAILS - PORT IS IN USE\n");
+ }
+
+ while ( PortP->WflushFlag &&
+ can_add_transmit( &PacketP, PortP ) &&
+ ( PortP->InUse == NOT_INUSE ) ) {
+ int p;
+ struct PktCmd *PktCmdP;
+
+ rio_dprintk (RIO_DEBUG_INTR, "Add WFLUSH marker to data queue\n");
+ /*
+ ** make it look just like a WFLUSH command
+ */
+ PktCmdP = ( struct PktCmd * )&PacketP->data[0];
+
+ WBYTE( PktCmdP->Command , WFLUSH );
+
+ p = PortP->HostPort % ( ushort )PORTS_PER_RTA;
+
+ /*
+ ** If second block of ports for 16 port RTA, add 8
+ ** to index 8-15.
+ */
+ if ( PortP->SecondBlock )
+ p += PORTS_PER_RTA;
+
+ WBYTE( PktCmdP->PhbNum, p );
+
+ /*
+ ** to make debuggery easier
+ */
+ WBYTE( PacketP->data[ 2], 'W' );
+ WBYTE( PacketP->data[ 3], 'F' );
+ WBYTE( PacketP->data[ 4], 'L' );
+ WBYTE( PacketP->data[ 5], 'U' );
+ WBYTE( PacketP->data[ 6], 'S' );
+ WBYTE( PacketP->data[ 7], 'H' );
+ WBYTE( PacketP->data[ 8], ' ' );
+ WBYTE( PacketP->data[ 9], '0'+PortP->WflushFlag );
+ WBYTE( PacketP->data[10], ' ' );
+ WBYTE( PacketP->data[11], ' ' );
+ WBYTE( PacketP->data[12], '\0' );
+
+ /*
+ ** its two bytes long!
+ */
+ WBYTE( PacketP->len , PKT_CMD_BIT | 2 );
+
+ /*
+ ** queue it!
+ */
+ if ( !( PortP->State & RIO_DELETED ) ) {
+ add_transmit( PortP );
+ /*
+ ** Count chars tx'd for port statistics reporting
+ */
+ if ( PortP->statsGather )
+ PortP->txchars += 2;
+ }
+
+ if ( --( PortP->WflushFlag ) == 0 ) {
+ PortP->MagicFlags &= ~MAGIC_FLUSH;
+ }
+
+ rio_dprintk (RIO_DEBUG_INTR, "Wflush count now stands at %d\n",
+ PortP->WflushFlag);
+ }
+ if ( PortP->MagicFlags & MORE_OUTPUT_EYGOR ) {
+ if ( PortP->MagicFlags & MAGIC_FLUSH ) {
+ PortP->MagicFlags |= MORE_OUTPUT_EYGOR;
+ }
+ else {
+ if ( !can_add_transmit( &PacketP, PortP ) ) {
+ rio_spin_unlock(&PortP->portSem);
+ continue;
+ }
+ rio_spin_unlock(&PortP->portSem);
+ RIOTxEnable((char *)PortP);
+ rio_spin_lock(&PortP->portSem);
+ PortP->MagicFlags &= ~MORE_OUTPUT_EYGOR;
+ }
+ }
+ }
+
+
+ /*
+ ** If we can't add anything to the transmit queue, then
+ ** we need do none of the remaining processing.
+ */
+ if (!can_add_transmit( &PacketP, PortP ) ) {
+ rio_spin_unlock(&PortP->portSem);
+ continue;
+ }
+
+ rio_spin_unlock(&PortP->portSem);
+ RIOTxEnable((char *)PortP);
+ }
+ }
+}
+
+/*
+** Routine for handling received data for clist drivers.
+** NB: Called with the tty locked. The spl from the lockb( ) is passed.
+** we return the ttySpl level that we re-locked at.
+*/
+static void
+RIOReceive(p, PortP)
+struct rio_info * p;
+struct Port * PortP;
+{
+ struct tty_struct *TtyP;
+ register ushort transCount;
+ struct PKT *PacketP;
+ register uint DataCnt;
+ uchar * ptr;
+ int copied =0;
+
+ static int intCount, RxIntCnt;
+
+ /*
+ ** The receive data process is to remove packets from the
+ ** PHB until there aren't any more or the current cblock
+ ** is full. When this occurs, there will be some left over
+ ** data in the packet, that we must do something with.
+ ** As we haven't unhooked the packet from the read list
+ ** yet, we can just leave the packet there, having first
+ ** made a note of how far we got. This means that we need
+ ** a pointer per port saying where we start taking the
+ ** data from - this will normally be zero, but when we
+ ** run out of space it will be set to the offset of the
+ ** next byte to copy from the packet data area. The packet
+ ** length field is decremented by the number of bytes that
+ ** we succesfully removed from the packet. When this reaches
+ ** zero, we reset the offset pointer to be zero, and free
+ ** the packet from the front of the queue.
+ */
+
+ intCount++;
+
+ TtyP = PortP->gs.tty;
+ if (!TtyP) {
+ rio_dprintk (RIO_DEBUG_INTR, "RIOReceive: tty is null. \n");
+ return;
+ }
+
+ if (PortP->State & RIO_THROTTLE_RX) {
+ rio_dprintk (RIO_DEBUG_INTR, "RIOReceive: Throttled. Can't handle more input.\n");
+ return;
+ }
+
+ if ( PortP->State & RIO_DELETED )
+ {
+ while ( can_remove_receive( &PacketP, PortP ) )
+ {
+ remove_receive( PortP );
+ put_free_end( PortP->HostP, PacketP );
+ }
+ }
+ else
+ {
+ /*
+ ** loop, just so long as:
+ ** i ) there's some data ( i.e. can_remove_receive )
+ ** ii ) we haven't been blocked
+ ** iii ) there's somewhere to put the data
+ ** iv ) we haven't outstayed our welcome
+ */
+ transCount = 1;
+ while ( can_remove_receive(&PacketP, PortP)
+ && transCount)
+ {
+#ifdef STATS
+ PortP->Stat.RxIntCnt++;
+#endif /* STATS */
+ RxIntCnt++;
+
+ /*
+ ** check that it is not a command!
+ */
+ if ( PacketP->len & PKT_CMD_BIT ) {
+ rio_dprintk (RIO_DEBUG_INTR, "RIO: unexpected command packet received on PHB\n");
+ /* rio_dprint(RIO_DEBUG_INTR, (" sysport = %d\n", p->RIOPortp->PortNum)); */
+ rio_dprintk (RIO_DEBUG_INTR, " dest_unit = %d\n", PacketP->dest_unit);
+ rio_dprintk (RIO_DEBUG_INTR, " dest_port = %d\n", PacketP->dest_port);
+ rio_dprintk (RIO_DEBUG_INTR, " src_unit = %d\n", PacketP->src_unit);
+ rio_dprintk (RIO_DEBUG_INTR, " src_port = %d\n", PacketP->src_port);
+ rio_dprintk (RIO_DEBUG_INTR, " len = %d\n", PacketP->len);
+ rio_dprintk (RIO_DEBUG_INTR, " control = %d\n", PacketP->control);
+ rio_dprintk (RIO_DEBUG_INTR, " csum = %d\n", PacketP->csum);
+ rio_dprintk (RIO_DEBUG_INTR, " data bytes: ");
+ for ( DataCnt=0; DataCnt<PKT_MAX_DATA_LEN; DataCnt++ )
+ rio_dprintk (RIO_DEBUG_INTR, "%d\n", PacketP->data[DataCnt]);
+ remove_receive( PortP );
+ put_free_end( PortP->HostP, PacketP );
+ continue; /* with next packet */
+ }
+
+ /*
+ ** How many characters can we move 'upstream' ?
+ **
+ ** Determine the minimum of the amount of data
+ ** available and the amount of space in which to
+ ** put it.
+ **
+ ** 1. Get the packet length by masking 'len'
+ ** for only the length bits.
+ ** 2. Available space is [buffer size] - [space used]
+ **
+ ** Transfer count is the minimum of packet length
+ ** and available space.
+ */
+
+ transCount = min_t(unsigned int, PacketP->len & PKT_LEN_MASK,
+ TTY_FLIPBUF_SIZE - TtyP->flip.count);
+ rio_dprintk (RIO_DEBUG_REC, "port %d: Copy %d bytes\n",
+ PortP->PortNum, transCount);
+ /*
+ ** To use the following 'kkprintfs' for debugging - change the '#undef'
+ ** to '#define', (this is the only place ___DEBUG_IT___ occurs in the
+ ** driver).
+ */
+#undef ___DEBUG_IT___
+#ifdef ___DEBUG_IT___
+ kkprintf("I:%d R:%d P:%d Q:%d C:%d F:%x ",
+ intCount,
+ RxIntCnt,
+ PortP->PortNum,
+ TtyP->rxqueue.count,
+ transCount,
+ TtyP->flags );
+#endif
+ ptr = (uchar *) PacketP->data + PortP->RxDataStart;
+
+ rio_memcpy_fromio (TtyP->flip.char_buf_ptr, ptr, transCount);
+ memset(TtyP->flip.flag_buf_ptr, TTY_NORMAL, transCount);
+
+#ifdef STATS
+ /*
+ ** keep a count for statistical purposes
+ */
+ PortP->Stat.RxCharCnt += transCount;
+#endif
+ PortP->RxDataStart += transCount;
+ PacketP->len -= transCount;
+ copied += transCount;
+ TtyP->flip.count += transCount;
+ TtyP->flip.char_buf_ptr += transCount;
+ TtyP->flip.flag_buf_ptr += transCount;
+
+
+#ifdef ___DEBUG_IT___
+ kkprintf("T:%d L:%d\n", DataCnt, PacketP->len );
+#endif
+
+ if ( PacketP->len == 0 )
+ {
+ /*
+ ** If we have emptied the packet, then we can
+ ** free it, and reset the start pointer for
+ ** the next packet.
+ */
+ remove_receive( PortP );
+ put_free_end( PortP->HostP, PacketP );
+ PortP->RxDataStart = 0;
+#ifdef STATS
+ /*
+ ** more lies ( oops, I mean statistics )
+ */
+ PortP->Stat.RxPktCnt++;
+#endif /* STATS */
+ }
+ }
+ }
+ if (copied) {
+ rio_dprintk (RIO_DEBUG_REC, "port %d: pushing tty flip buffer: %d total bytes copied.\n", PortP->PortNum, copied);
+ tty_flip_buffer_push (TtyP);
+ }
+
+ return;
+}
+
+#ifdef FUTURE_RELEASE
+/*
+** The proc routine called by the line discipline to do the work for it.
+** The proc routine works hand in hand with the interrupt routine.
+*/
+int
+riotproc(p, tp, cmd, port)
+struct rio_info * p;
+register struct ttystatics *tp;
+int cmd;
+int port;
+{
+ register struct Port *PortP;
+ int SysPort;
+ struct PKT *PacketP;
+
+ SysPort = port; /* Believe me, it works. */
+
+ if ( SysPort < 0 || SysPort >= RIO_PORTS ) {
+ rio_dprintk (RIO_DEBUG_INTR, "Illegal port %d derived from TTY in riotproc()\n",SysPort);
+ return 0;
+ }
+ PortP = p->RIOPortp[SysPort];
+
+ if ((uint)PortP->PhbP < (uint)PortP->Caddr ||
+ (uint)PortP->PhbP >= (uint)PortP->Caddr+SIXTY_FOUR_K ) {
+ rio_dprintk (RIO_DEBUG_INTR, "RIO: NULL or BAD PhbP on sys port %d in proc routine\n",
+ SysPort);
+ rio_dprintk (RIO_DEBUG_INTR, " PortP = 0x%x\n",PortP);
+ rio_dprintk (RIO_DEBUG_INTR, " PortP->PhbP = 0x%x\n",PortP->PhbP);
+ rio_dprintk (RIO_DEBUG_INTR, " PortP->Caddr = 0x%x\n",PortP->PhbP);
+ rio_dprintk (RIO_DEBUG_INTR, " PortP->HostPort = 0x%x\n",PortP->HostPort);
+ return 0;
+ }
+
+ switch(cmd) {
+ case T_WFLUSH:
+ rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH\n");
+ /*
+ ** Because of the spooky way the RIO works, we don't need
+ ** to issue a flush command on any of the SET*F commands,
+ ** as that causes trouble with getty and login, which issue
+ ** these commands to incur a READ flush, and rely on the fact
+ ** that the line discipline does a wait for drain for them.
+ ** As the rio doesn't wait for drain, the write flush would
+ ** destroy the Password: prompt. This isn't very friendly, so
+ ** here we only issue a WFLUSH command if we are in the interrupt
+ ** routine, or we aren't executing a SET*F command.
+ */
+ if ( PortP->HostP->InIntr || !PortP->FlushCmdBodge ) {
+ /*
+ ** form a wflush packet - 1 byte long, no data
+ */
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_INTR, "WFLUSH on deleted RTA\n");
+ }
+ else {
+ if ( RIOPreemptiveCmd(p, PortP, WFLUSH ) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command failed\n");
+ }
+ else
+ rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command\n");
+ }
+ /*
+ ** WFLUSH operation - flush the data!
+ */
+ PortP->TxBufferIn = PortP->TxBufferOut = 0;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command ignored\n");
+ }
+ /*
+ ** sort out the line discipline
+ */
+ if (PortP->CookMode == COOK_WELL)
+ goto start;
+ break;
+
+ case T_RESUME:
+ rio_dprintk (RIO_DEBUG_INTR, "T_RESUME\n");
+ /*
+ ** send pre-emptive resume packet
+ */
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_INTR, "RESUME on deleted RTA\n");
+ }
+ else {
+ if ( RIOPreemptiveCmd(p, PortP, RESUME ) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_INTR, "T_RESUME Command failed\n");
+ }
+ }
+ /*
+ ** and re-start the sender software!
+ */
+ if (PortP->CookMode == COOK_WELL)
+ goto start;
+ break;
+
+ case T_TIME:
+ rio_dprintk (RIO_DEBUG_INTR, "T_TIME\n");
+ /*
+ ** T_TIME is called when xDLY is set in oflags and
+ ** the line discipline timeout has expired. It's
+ ** function in life is to clear the TIMEOUT flag
+ ** and to re-start output to the port.
+ */
+ /*
+ ** Fall through and re-start output
+ */
+ case T_OUTPUT:
+start:
+ if ( PortP->MagicFlags & MAGIC_FLUSH ) {
+ PortP->MagicFlags |= MORE_OUTPUT_EYGOR;
+ return 0;
+ }
+ RIOTxEnable((char *)PortP);
+ PortP->MagicFlags &= ~MORE_OUTPUT_EYGOR;
+ /*rio_dprint(RIO_DEBUG_INTR, PortP,DBG_PROC,"T_OUTPUT finished\n");*/
+ break;
+
+ case T_SUSPEND:
+ rio_dprintk (RIO_DEBUG_INTR, "T_SUSPEND\n");
+ /*
+ ** send a suspend pre-emptive packet.
+ */
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_INTR, "SUSPEND deleted RTA\n");
+ }
+ else {
+ if ( RIOPreemptiveCmd(p, PortP, SUSPEND ) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_INTR, "T_SUSPEND Command failed\n");
+ }
+ }
+ /*
+ ** done!
+ */
+ break;
+
+ case T_BLOCK:
+ rio_dprintk (RIO_DEBUG_INTR, "T_BLOCK\n");
+ break;
+
+ case T_RFLUSH:
+ rio_dprintk (RIO_DEBUG_INTR, "T_RFLUSH\n");
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_INTR, "RFLUSH on deleted RTA\n");
+ PortP->RxDataStart = 0;
+ }
+ else {
+ if ( RIOPreemptiveCmd( p, PortP, RFLUSH ) == RIO_FAIL ) {
+ rio_dprintk (RIO_DEBUG_INTR, "T_RFLUSH Command failed\n");
+ return 0;
+ }
+ PortP->RxDataStart = 0;
+ while ( can_remove_receive(&PacketP, PortP) ) {
+ remove_receive(PortP);
+ ShowPacket(DBG_PROC, PacketP );
+ put_free_end(PortP->HostP, PacketP );
+ }
+ if ( PortP->PhbP->handshake == PHB_HANDSHAKE_SET ) {
+ /*
+ ** MAGIC!
+ */
+ rio_dprintk (RIO_DEBUG_INTR, "Set receive handshake bit\n");
+ PortP->PhbP->handshake |= PHB_HANDSHAKE_RESET;
+ }
+ }
+ break;
+ /* FALLTHROUGH */
+ case T_UNBLOCK:
+ rio_dprintk (RIO_DEBUG_INTR, "T_UNBLOCK\n");
+ /*
+ ** If there is any data to receive set a timeout to service it.
+ */
+ RIOReceive(p, PortP);
+ break;
+
+ case T_BREAK:
+ rio_dprintk (RIO_DEBUG_INTR, "T_BREAK\n");
+ /*
+ ** Send a break command. For Sys V
+ ** this is a timed break, so we
+ ** send a SBREAK[time] packet
+ */
+ /*
+ ** Build a BREAK command
+ */
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_INTR, "BREAK on deleted RTA\n");
+ }
+ else {
+ if (RIOShortCommand(PortP,SBREAK,2,
+ p->RIOConf.BreakInterval)==RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+ }
+ }
+
+ /*
+ ** done!
+ */
+ break;
+
+ case T_INPUT:
+ rio_dprintk (RIO_DEBUG_INTR, "Proc T_INPUT called - I don't know what to do!\n");
+ break;
+ case T_PARM:
+ rio_dprintk (RIO_DEBUG_INTR, "Proc T_PARM called - I don't know what to do!\n");
+ break;
+
+ case T_SWTCH:
+ rio_dprintk (RIO_DEBUG_INTR, "Proc T_SWTCH called - I don't know what to do!\n");
+ break;
+
+ default:
+ rio_dprintk (RIO_DEBUG_INTR, "Proc UNKNOWN command %d\n",cmd);
+ }
+ /*
+ ** T_OUTPUT returns without passing through this point!
+ */
+ /*rio_dprint(RIO_DEBUG_INTR, PortP,DBG_PROC,"riotproc done\n");*/
+ return(0);
+}
+#endif
diff --git a/drivers/char/rio/rioioctl.h b/drivers/char/rio/rioioctl.h
new file mode 100644
index 000000000000..c3d679733c9a
--- /dev/null
+++ b/drivers/char/rio/rioioctl.h
@@ -0,0 +1,103 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioioctl.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:13
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)rioioctl.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rioioctl_h__
+#define __rioioctl_h__
+
+#ifdef SCCS_LABELS
+static char *_rioioctl_h_sccs_ = "@(#)rioioctl.h 1.2";
+#endif
+
+/*
+** RIO device driver - user ioctls and associated structures.
+*/
+
+struct portStats {
+ int port;
+ int gather;
+ ulong txchars;
+ ulong rxchars;
+ ulong opens;
+ ulong closes;
+ ulong ioctls;
+};
+
+
+#define rIOC ('r'<<8)
+#define TCRIOSTATE (rIOC | 1)
+#define TCRIOXPON (rIOC | 2)
+#define TCRIOXPOFF (rIOC | 3)
+#define TCRIOXPCPS (rIOC | 4)
+#define TCRIOXPRINT (rIOC | 5)
+#define TCRIOIXANYON (rIOC | 6)
+#define TCRIOIXANYOFF (rIOC | 7)
+#define TCRIOIXONON (rIOC | 8)
+#define TCRIOIXONOFF (rIOC | 9)
+#define TCRIOMBIS (rIOC | 10)
+#define TCRIOMBIC (rIOC | 11)
+#define TCRIOTRIAD (rIOC | 12)
+#define TCRIOTSTATE (rIOC | 13)
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** Add RIO ioctls for manipulating RTS and CTS flow control, (as LynxOS
+** appears to not support hardware flow control).
+*/
+#define TCRIOCTSFLOWEN (rIOC | 14) /* enable CTS flow control */
+#define TCRIOCTSFLOWDIS (rIOC | 15) /* disable CTS flow control */
+#define TCRIORTSFLOWEN (rIOC | 16) /* enable RTS flow control */
+#define TCRIORTSFLOWDIS (rIOC | 17) /* disable RTS flow control */
+
+/*
+** 09.12.1998 ARG - ESIL 0776 part fix
+** Definition for 'RIOC' also appears in daemon.h, so we'd better do a
+** #ifndef here first.
+** 'RIO_QUICK_CHECK' also #define'd here as this ioctl is now
+** allowed to be used by customers.
+**
+** 05.02.1999 ARG -
+** This is what I've decied to do with ioctls etc., which are intended to be
+** invoked from users applications :
+** Anything that needs to be defined here will be removed from daemon.h, that
+** way it won't end up having to be defined/maintained in two places. The only
+** consequence of this is that this file should now be #include'd by daemon.h
+**
+** 'stats' ioctls now #define'd here as they are to be used by customers.
+*/
+#define RIOC ('R'<<8)|('i'<<16)|('o'<<24)
+
+#define RIO_QUICK_CHECK (RIOC | 105)
+#define RIO_GATHER_PORT_STATS (RIOC | 193)
+#define RIO_RESET_PORT_STATS (RIOC | 194)
+#define RIO_GET_PORT_STATS (RIOC | 195)
+
+#endif /* __rioioctl_h__ */
diff --git a/drivers/char/rio/riolocks.h b/drivers/char/rio/riolocks.h
new file mode 100644
index 000000000000..0e0cdacebe0b
--- /dev/null
+++ b/drivers/char/rio/riolocks.h
@@ -0,0 +1,43 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riolocks.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:13
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)riolocks.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_riolocks_h__
+#define __rio_riolocks_h__
+
+#ifdef SCCS_LABELS
+static char *_riolocks_h_sccs_ = "@(#)riolocks.h 1.2";
+#endif
+
+#define LOCKB(lk) lockb(lk);
+#define UNLOCKB(lk, oldspl) unlockb(lk, oldspl);
+
+#endif
diff --git a/drivers/char/rio/rioparam.c b/drivers/char/rio/rioparam.c
new file mode 100644
index 000000000000..f10916326ecc
--- /dev/null
+++ b/drivers/char/rio/rioparam.c
@@ -0,0 +1,744 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioparam.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:45
+** Retrieved : 11/6/98 10:33:50
+**
+** ident @(#)rioparam.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifdef SCCS_LABELS
+static char *_rioparam_c_sccs_ = "@(#)rioparam.c 1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+
+
+/*
+** The Scam, based on email from jeremyr@bugs.specialix.co.uk....
+**
+** To send a command on a particular port, you put a packet with the
+** command bit set onto the port. The command bit is in the len field,
+** and gets ORed in with the actual byte count.
+**
+** When you send a packet with the command bit set, then the first
+** data byte ( data[0] ) is interpretted as the command to execute.
+** It also governs what data structure overlay should accompany the packet.
+** Commands are defined in cirrus/cirrus.h
+**
+** If you want the command to pre-emt data already on the queue for the
+** port, set the pre-emptive bit in conjunction with the command bit.
+** It is not defined what will happen if you set the preemptive bit
+** on a packet that is NOT a command.
+**
+** Pre-emptive commands should be queued at the head of the queue using
+** add_start(), whereas normal commands and data are enqueued using
+** add_end().
+**
+** Most commands do not use the remaining bytes in the data array. The
+** exceptions are OPEN MOPEN and CONFIG. (NB. As with the SI CONFIG and
+** OPEN are currently analagous). With these three commands the following
+** 11 data bytes are all used to pass config information such as baud rate etc.
+** The fields are also defined in cirrus.h. Some contain straightforward
+** information such as the transmit XON character. Two contain the transmit and
+** receive baud rates respectively. For most baud rates there is a direct
+** mapping between the rates defined in <sys/termio.h> and the byte in the
+** packet. There are additional (non UNIX-standard) rates defined in
+** /u/dos/rio/cirrus/h/brates.h.
+**
+** The rest of the data fields contain approximations to the Cirrus registers
+** that are used to program number of bits etc. Each registers bit fields is
+** defined in cirrus.h.
+**
+** NB. Only use those bits that are defined as being driver specific
+** or common to the RTA and the driver.
+**
+** All commands going from RTA->Host will be dealt with by the Host code - you
+** will never see them. As with the SI there will be three fields to look out
+** for in each phb (not yet defined - needs defining a.s.a.p).
+**
+** modem_status - current state of handshake pins.
+**
+** port_status - current port status - equivalent to hi_stat for SI, indicates
+** if port is IDLE_OPEN, IDLE_CLOSED etc.
+**
+** break_status - bit X set if break has been received.
+**
+** Happy hacking.
+**
+*/
+
+/*
+** RIOParam is used to open or configure a port. You pass it a PortP,
+** which will have a tty struct attached to it. You also pass a command,
+** either OPEN or CONFIG. The port's setup is taken from the t_ fields
+** of the tty struct inside the PortP, and the port is either opened
+** or re-configured. You must also tell RIOParam if the device is a modem
+** device or not (i.e. top bit of minor number set or clear - take special
+** care when deciding on this!).
+** RIOParam neither flushes nor waits for drain, and is NOT preemptive.
+**
+** RIOParam assumes it will be called at splrio(), and also assumes
+** that CookMode is set correctly in the port structure.
+**
+** NB. for MPX
+** tty lock must NOT have been previously acquired.
+*/
+int
+RIOParam(PortP, cmd, Modem, SleepFlag)
+struct Port *PortP;
+int cmd;
+int Modem;
+int SleepFlag;
+{
+ register struct tty_struct *TtyP;
+ int retval;
+ register struct phb_param *phb_param_ptr;
+ PKT *PacketP;
+ int res;
+ uchar Cor1=0, Cor2=0, Cor4=0, Cor5=0;
+ uchar TxXon=0, TxXoff=0, RxXon=0, RxXoff=0;
+ uchar LNext=0, TxBaud=0, RxBaud=0;
+ int retries = 0xff;
+ unsigned long flags;
+
+ func_enter ();
+
+ TtyP = PortP->gs.tty;
+
+ rio_dprintk (RIO_DEBUG_PARAM, "RIOParam: Port:%d cmd:%d Modem:%d SleepFlag:%d Mapped: %d, tty=%p\n",
+ PortP->PortNum, cmd, Modem, SleepFlag, PortP->Mapped, TtyP);
+
+ if (!TtyP) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Can't call rioparam with null tty.\n");
+
+ func_exit ();
+
+ return RIO_FAIL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags );
+
+ if (cmd == OPEN) {
+ /*
+ ** If the port is set to store or lock the parameters, and it is
+ ** paramed with OPEN, we want to restore the saved port termio, but
+ ** only if StoredTermio has been saved, i.e. NOT 1st open after reboot.
+ */
+#if 0
+ if (PortP->FirstOpen) {
+ PortP->StoredTty.iflag = TtyP->tm.c_iflag;
+ PortP->StoredTty.oflag = TtyP->tm.c_oflag;
+ PortP->StoredTty.cflag = TtyP->tm.c_cflag;
+ PortP->StoredTty.lflag = TtyP->tm.c_lflag;
+ PortP->StoredTty.line = TtyP->tm.c_line;
+ for (i = 0; i < NCC + 5; i++)
+ PortP->StoredTty.cc[i] = TtyP->tm.c_cc[i];
+ PortP->FirstOpen = 0;
+ }
+ else if (PortP->Store || PortP->Lock) {
+ rio_dprintk (RIO_DEBUG_PARAM, "OPEN: Restoring stored/locked params\n");
+ TtyP->tm.c_iflag = PortP->StoredTty.iflag;
+ TtyP->tm.c_oflag = PortP->StoredTty.oflag;
+ TtyP->tm.c_cflag = PortP->StoredTty.cflag;
+ TtyP->tm.c_lflag = PortP->StoredTty.lflag;
+ TtyP->tm.c_line = PortP->StoredTty.line;
+ for (i = 0; i < NCC + 5; i++)
+ TtyP->tm.c_cc[i] = PortP->StoredTty.cc[i];
+ }
+#endif
+ }
+
+ /*
+ ** wait for space
+ */
+ while ( !(res=can_add_transmit(&PacketP,PortP)) ||
+ (PortP->InUse != NOT_INUSE) ) {
+ if (retries -- <= 0) {
+ break;
+ }
+ if ( PortP->InUse != NOT_INUSE ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Port IN_USE for pre-emptive command\n");
+ }
+
+ if ( !res ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Port has no space on transmit queue\n");
+ }
+
+ if ( SleepFlag != OK_TO_SLEEP ) {
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ func_exit();
+
+ return RIO_FAIL;
+ }
+
+ rio_dprintk (RIO_DEBUG_PARAM, "wait for can_add_transmit\n");
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ retval = RIODelay(PortP, HUNDRED_MS);
+ rio_spin_lock_irqsave( &PortP->portSem, flags);
+ if (retval == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_PARAM, "wait for can_add_transmit broken by signal\n");
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ pseterr(EINTR);
+ func_exit();
+
+ return RIO_FAIL;
+ }
+ if ( PortP->State & RIO_DELETED ) {
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ func_exit ();
+
+ return RIO_SUCCESS;
+ }
+ }
+
+ if (!res) {
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ func_exit ();
+
+ return RIO_FAIL;
+ }
+
+ rio_dprintk (RIO_DEBUG_PARAM, "can_add_transmit() returns %x\n",res);
+ rio_dprintk (RIO_DEBUG_PARAM, "Packet is 0x%x\n",(int) PacketP);
+
+ phb_param_ptr = (struct phb_param *)PacketP->data;
+
+
+#if 0
+ /*
+ ** COR 1
+ */
+ if ( TtyP->tm.c_iflag & INPCK ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Parity checking on input enabled\n");
+ Cor1 |= COR1_INPCK;
+ }
+#endif
+
+ switch ( TtyP->termios->c_cflag & CSIZE ) {
+ case CS5:
+ {
+ rio_dprintk (RIO_DEBUG_PARAM, "5 bit data\n");
+ Cor1 |= COR1_5BITS;
+ break;
+ }
+ case CS6:
+ {
+ rio_dprintk (RIO_DEBUG_PARAM, "6 bit data\n");
+ Cor1 |= COR1_6BITS;
+ break;
+ }
+ case CS7:
+ {
+ rio_dprintk (RIO_DEBUG_PARAM, "7 bit data\n");
+ Cor1 |= COR1_7BITS;
+ break;
+ }
+ case CS8:
+ {
+ rio_dprintk (RIO_DEBUG_PARAM, "8 bit data\n");
+ Cor1 |= COR1_8BITS;
+ break;
+ }
+ }
+
+ if ( TtyP->termios->c_cflag & CSTOPB ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "2 stop bits\n");
+ Cor1 |= COR1_2STOP;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_PARAM, "1 stop bit\n");
+ Cor1 |= COR1_1STOP;
+ }
+
+ if ( TtyP->termios->c_cflag & PARENB ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable parity\n");
+ Cor1 |= COR1_NORMAL;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_PARAM, "Disable parity\n");
+ Cor1 |= COR1_NOP;
+ }
+ if ( TtyP->termios->c_cflag & PARODD ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Odd parity\n");
+ Cor1 |= COR1_ODD;
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_PARAM, "Even parity\n");
+ Cor1 |= COR1_EVEN;
+ }
+
+ /*
+ ** COR 2
+ */
+ if ( TtyP->termios->c_iflag & IXON ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable start/stop output control\n");
+ Cor2 |= COR2_IXON;
+ }
+ else {
+ if ( PortP->Config & RIO_IXON ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Force enable start/stop output control\n");
+ Cor2 |= COR2_IXON;
+ }
+ else
+ rio_dprintk (RIO_DEBUG_PARAM, "IXON has been disabled.\n");
+ }
+
+ if (TtyP->termios->c_iflag & IXANY) {
+ if ( PortP->Config & RIO_IXANY ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable any key to restart output\n");
+ Cor2 |= COR2_IXANY;
+ }
+ else
+ rio_dprintk (RIO_DEBUG_PARAM, "IXANY has been disabled due to sanity reasons.\n");
+ }
+
+ if ( TtyP->termios->c_iflag & IXOFF ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable start/stop input control 2\n");
+ Cor2 |= COR2_IXOFF;
+ }
+
+ if ( TtyP->termios->c_cflag & HUPCL ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Hangup on last close\n");
+ Cor2 |= COR2_HUPCL;
+ }
+
+ if ( C_CRTSCTS (TtyP)) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Rx hardware flow control enabled\n");
+ Cor2 |= COR2_CTSFLOW;
+ Cor2 |= COR2_RTSFLOW;
+ } else {
+ rio_dprintk (RIO_DEBUG_PARAM, "Rx hardware flow control disabled\n");
+ Cor2 &= ~COR2_CTSFLOW;
+ Cor2 &= ~COR2_RTSFLOW;
+ }
+
+
+ if ( TtyP->termios->c_cflag & CLOCAL ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Local line\n");
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_PARAM, "Possible Modem line\n");
+ }
+
+ /*
+ ** COR 4 (there is no COR 3)
+ */
+ if ( TtyP->termios->c_iflag & IGNBRK ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Ignore break condition\n");
+ Cor4 |= COR4_IGNBRK;
+ }
+ if ( !(TtyP->termios->c_iflag & BRKINT) ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Break generates NULL condition\n");
+ Cor4 |= COR4_NBRKINT;
+ } else {
+ rio_dprintk (RIO_DEBUG_PARAM, "Interrupt on break condition\n");
+ }
+
+ if ( TtyP->termios->c_iflag & INLCR ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Map newline to carriage return on input\n");
+ Cor4 |= COR4_INLCR;
+ }
+
+ if ( TtyP->termios->c_iflag & IGNCR ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Ignore carriage return on input\n");
+ Cor4 |= COR4_IGNCR;
+ }
+
+ if ( TtyP->termios->c_iflag & ICRNL ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Map carriage return to newline on input\n");
+ Cor4 |= COR4_ICRNL;
+ }
+ if ( TtyP->termios->c_iflag & IGNPAR ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Ignore characters with parity errors\n");
+ Cor4 |= COR4_IGNPAR;
+ }
+ if ( TtyP->termios->c_iflag & PARMRK ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Mark parity errors\n");
+ Cor4 |= COR4_PARMRK;
+ }
+
+ /*
+ ** Set the RAISEMOD flag to ensure that the modem lines are raised
+ ** on reception of a config packet.
+ ** The download code handles the zero baud condition.
+ */
+ Cor4 |= COR4_RAISEMOD;
+
+ /*
+ ** COR 5
+ */
+
+ Cor5 = COR5_CMOE;
+
+ /*
+ ** Set to monitor tbusy/tstop (or not).
+ */
+
+ if (PortP->MonitorTstate)
+ Cor5 |= COR5_TSTATE_ON;
+ else
+ Cor5 |= COR5_TSTATE_OFF;
+
+ /*
+ ** Could set LNE here if you wanted LNext processing. SVR4 will use it.
+ */
+ if ( TtyP->termios->c_iflag & ISTRIP ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Strip input characters\n");
+ if (! (PortP->State & RIO_TRIAD_MODE)) {
+ Cor5 |= COR5_ISTRIP;
+ }
+ }
+
+ if ( TtyP->termios->c_oflag & ONLCR ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Map newline to carriage-return, newline on output\n");
+ if ( PortP->CookMode == COOK_MEDIUM )
+ Cor5 |= COR5_ONLCR;
+ }
+ if ( TtyP->termios->c_oflag & OCRNL ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Map carriage return to newline on output\n");
+ if ( PortP->CookMode == COOK_MEDIUM )
+ Cor5 |= COR5_OCRNL;
+ }
+ if ( ( TtyP->termios->c_oflag & TABDLY) == TAB3 ) {
+ rio_dprintk (RIO_DEBUG_PARAM, "Tab delay 3 set\n");
+ if ( PortP->CookMode == COOK_MEDIUM )
+ Cor5 |= COR5_TAB3;
+ }
+
+ /*
+ ** Flow control bytes.
+ */
+ TxXon = TtyP->termios->c_cc[VSTART];
+ TxXoff = TtyP->termios->c_cc[VSTOP];
+ RxXon = TtyP->termios->c_cc[VSTART];
+ RxXoff = TtyP->termios->c_cc[VSTOP];
+ /*
+ ** LNEXT byte
+ */
+ LNext = 0;
+
+ /*
+ ** Baud rate bytes
+ */
+ rio_dprintk (RIO_DEBUG_PARAM, "Mapping of rx/tx baud %x (%x)\n",
+ TtyP->termios->c_cflag, CBAUD);
+
+ switch (TtyP->termios->c_cflag & CBAUD) {
+#define e(b) case B ## b : RxBaud = TxBaud = RIO_B ## b ;break
+ e(50);e(75);e(110);e(134);e(150);e(200);e(300);e(600);e(1200);
+ e(1800);e(2400);e(4800);e(9600);e(19200);e(38400);e(57600);
+ e(115200); /* e(230400);e(460800); e(921600); */
+ }
+
+ /* XXX MIssing conversion table. XXX */
+ /* (TtyP->termios->c_cflag & V_CBAUD); */
+
+ rio_dprintk (RIO_DEBUG_PARAM, "tx baud 0x%x, rx baud 0x%x\n", TxBaud, RxBaud);
+
+
+ /*
+ ** Leftovers
+ */
+ if ( TtyP->termios->c_cflag & CREAD )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable receiver\n");
+#ifdef RCV1EN
+ if ( TtyP->termios->c_cflag & RCV1EN )
+ rio_dprintk (RIO_DEBUG_PARAM, "RCV1EN (?)\n");
+#endif
+#ifdef XMT1EN
+ if ( TtyP->termios->c_cflag & XMT1EN )
+ rio_dprintk (RIO_DEBUG_PARAM, "XMT1EN (?)\n");
+#endif
+#if 0
+ if ( TtyP->termios->c_cflag & LOBLK )
+ rio_dprintk (RIO_DEBUG_PARAM, "LOBLK - JCL output blocks when not current\n");
+#endif
+ if ( TtyP->termios->c_lflag & ISIG )
+ rio_dprintk (RIO_DEBUG_PARAM, "Input character signal generating enabled\n");
+ if ( TtyP->termios->c_lflag & ICANON )
+ rio_dprintk (RIO_DEBUG_PARAM, "Canonical input: erase and kill enabled\n");
+ if ( TtyP->termios->c_lflag & XCASE )
+ rio_dprintk (RIO_DEBUG_PARAM, "Canonical upper/lower presentation\n");
+ if ( TtyP->termios->c_lflag & ECHO )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable input echo\n");
+ if ( TtyP->termios->c_lflag & ECHOE )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable echo erase\n");
+ if ( TtyP->termios->c_lflag & ECHOK )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable echo kill\n");
+ if ( TtyP->termios->c_lflag & ECHONL )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable echo newline\n");
+ if ( TtyP->termios->c_lflag & NOFLSH )
+ rio_dprintk (RIO_DEBUG_PARAM, "Disable flush after interrupt or quit\n");
+#ifdef TOSTOP
+ if ( TtyP->termios->c_lflag & TOSTOP )
+ rio_dprintk (RIO_DEBUG_PARAM, "Send SIGTTOU for background output\n");
+#endif
+#ifdef XCLUDE
+ if ( TtyP->termios->c_lflag & XCLUDE )
+ rio_dprintk (RIO_DEBUG_PARAM, "Exclusive use of this line\n");
+#endif
+ if ( TtyP->termios->c_iflag & IUCLC )
+ rio_dprintk (RIO_DEBUG_PARAM, "Map uppercase to lowercase on input\n");
+ if ( TtyP->termios->c_oflag & OPOST )
+ rio_dprintk (RIO_DEBUG_PARAM, "Enable output post-processing\n");
+ if ( TtyP->termios->c_oflag & OLCUC )
+ rio_dprintk (RIO_DEBUG_PARAM, "Map lowercase to uppercase on output\n");
+ if ( TtyP->termios->c_oflag & ONOCR )
+ rio_dprintk (RIO_DEBUG_PARAM, "No carriage return output at column 0\n");
+ if ( TtyP->termios->c_oflag & ONLRET )
+ rio_dprintk (RIO_DEBUG_PARAM, "Newline performs carriage return function\n");
+ if ( TtyP->termios->c_oflag & OFILL )
+ rio_dprintk (RIO_DEBUG_PARAM, "Use fill characters for delay\n");
+ if ( TtyP->termios->c_oflag & OFDEL )
+ rio_dprintk (RIO_DEBUG_PARAM, "Fill character is DEL\n");
+ if ( TtyP->termios->c_oflag & NLDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Newline delay set\n");
+ if ( TtyP->termios->c_oflag & CRDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Carriage return delay set\n");
+ if ( TtyP->termios->c_oflag & TABDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Tab delay set\n");
+#if 0
+ if ( TtyP->termios->c_oflag & BSDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Back-space delay set\n");
+ if ( TtyP->termios->c_oflag & VTDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Vertical tab delay set\n");
+ if ( TtyP->termios->c_oflag & FFDLY )
+ rio_dprintk (RIO_DEBUG_PARAM, "Form-feed delay set\n");
+#endif
+ /*
+ ** These things are kind of useful in a later life!
+ */
+ PortP->Cor2Copy = Cor2;
+
+ if ( PortP->State & RIO_DELETED ) {
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ func_exit ();
+
+ return RIO_FAIL;
+ }
+
+ /*
+ ** Actually write the info into the packet to be sent
+ */
+ WBYTE(phb_param_ptr->Cmd, cmd);
+ WBYTE(phb_param_ptr->Cor1, Cor1);
+ WBYTE(phb_param_ptr->Cor2, Cor2);
+ WBYTE(phb_param_ptr->Cor4, Cor4);
+ WBYTE(phb_param_ptr->Cor5, Cor5);
+ WBYTE(phb_param_ptr->TxXon, TxXon);
+ WBYTE(phb_param_ptr->RxXon, RxXon);
+ WBYTE(phb_param_ptr->TxXoff, TxXoff);
+ WBYTE(phb_param_ptr->RxXoff, RxXoff);
+ WBYTE(phb_param_ptr->LNext, LNext);
+ WBYTE(phb_param_ptr->TxBaud, TxBaud);
+ WBYTE(phb_param_ptr->RxBaud, RxBaud);
+
+ /*
+ ** Set the length/command field
+ */
+ WBYTE(PacketP->len , 12 | PKT_CMD_BIT);
+
+ /*
+ ** The packet is formed - now, whack it off
+ ** to its final destination:
+ */
+ add_transmit(PortP);
+ /*
+ ** Count characters transmitted for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->txchars += 12;
+
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+
+ rio_dprintk (RIO_DEBUG_PARAM, "add_transmit returned.\n");
+ /*
+ ** job done.
+ */
+ func_exit ();
+
+ return RIO_SUCCESS;
+}
+
+
+/*
+** We can add another packet to a transmit queue if the packet pointer pointed
+** to by the TxAdd pointer has PKT_IN_USE clear in its address.
+*/
+int
+can_add_transmit(PktP, PortP)
+PKT **PktP;
+struct Port *PortP;
+{
+ register PKT *tp;
+
+ *PktP = tp = (PKT *)RIO_PTR(PortP->Caddr,RWORD(*PortP->TxAdd));
+
+ return !((uint)tp & PKT_IN_USE);
+}
+
+/*
+** To add a packet to the queue, you set the PKT_IN_USE bit in the address,
+** and then move the TxAdd pointer along one position to point to the next
+** packet pointer. You must wrap the pointer from the end back to the start.
+*/
+void
+add_transmit(PortP)
+struct Port *PortP;
+{
+ if (RWORD(*PortP->TxAdd) & PKT_IN_USE) {
+ rio_dprintk (RIO_DEBUG_PARAM, "add_transmit: Packet has been stolen!");
+ }
+ WWORD( *(ushort *)PortP->TxAdd, RWORD(*PortP->TxAdd) | PKT_IN_USE);
+ PortP->TxAdd = (PortP->TxAdd == PortP->TxEnd) ? PortP->TxStart :
+ PortP->TxAdd + 1;
+ WWORD( PortP->PhbP->tx_add , RIO_OFF(PortP->Caddr,PortP->TxAdd) );
+}
+
+/****************************************
+ * Put a packet onto the end of the
+ * free list
+ ****************************************/
+void
+put_free_end(HostP, PktP)
+struct Host *HostP;
+PKT *PktP;
+{
+ FREE_LIST *tmp_pointer;
+ ushort old_end, new_end;
+ unsigned long flags;
+
+ rio_spin_lock_irqsave(&HostP->HostLock, flags);
+
+ /*************************************************
+ * Put a packet back onto the back of the free list
+ *
+ ************************************************/
+
+ rio_dprintk (RIO_DEBUG_PFE, "put_free_end(PktP=%x)\n",(int)PktP);
+
+ if ((old_end=RWORD(HostP->ParmMapP->free_list_end)) != TPNULL) {
+ new_end = RIO_OFF(HostP->Caddr,PktP);
+ tmp_pointer = (FREE_LIST *)RIO_PTR(HostP->Caddr,old_end);
+ WWORD(tmp_pointer->next , new_end );
+ WWORD(((FREE_LIST *)PktP)->prev , old_end);
+ WWORD(((FREE_LIST *)PktP)->next , TPNULL);
+ WWORD(HostP->ParmMapP->free_list_end, new_end);
+ }
+ else { /* First packet on the free list this should never happen! */
+ rio_dprintk (RIO_DEBUG_PFE, "put_free_end(): This should never happen\n");
+ WWORD(HostP->ParmMapP->free_list_end , RIO_OFF(HostP->Caddr,PktP));
+ tmp_pointer = (FREE_LIST *)PktP;
+ WWORD(tmp_pointer->prev , TPNULL);
+ WWORD(tmp_pointer->next , TPNULL);
+ }
+ rio_dprintk (RIO_DEBUG_CMD, "Before unlock: %p\n", &HostP->HostLock);
+ rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+}
+
+/*
+** can_remove_receive(PktP,P) returns non-zero if PKT_IN_USE is set
+** for the next packet on the queue. It will also set PktP to point to the
+** relevant packet, [having cleared the PKT_IN_USE bit]. If PKT_IN_USE is clear,
+** then can_remove_receive() returns 0.
+*/
+int
+can_remove_receive(PktP, PortP)
+PKT **PktP;
+struct Port *PortP;
+{
+ if ( RWORD(*PortP->RxRemove) & PKT_IN_USE) {
+ *PktP = (PKT *)RIO_PTR(PortP->Caddr,
+ RWORD(*PortP->RxRemove) & ~PKT_IN_USE);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+** To remove a packet from the receive queue you clear its PKT_IN_USE bit,
+** and then bump the pointers. Once the pointers get to the end, they must
+** be wrapped back to the start.
+*/
+void
+remove_receive(PortP)
+struct Port *PortP;
+{
+ WWORD( *PortP->RxRemove, RWORD(*PortP->RxRemove) & ~PKT_IN_USE );
+ PortP->RxRemove = (PortP->RxRemove == PortP->RxEnd) ? PortP->RxStart :
+ PortP->RxRemove + 1;
+ WWORD( PortP->PhbP->rx_remove , RIO_OFF(PortP->Caddr, PortP->RxRemove) );
+}
diff --git a/drivers/char/rio/riopcicopy.c b/drivers/char/rio/riopcicopy.c
new file mode 100644
index 000000000000..2ea99a60aa32
--- /dev/null
+++ b/drivers/char/rio/riopcicopy.c
@@ -0,0 +1,8 @@
+
+/* Yeah. We have copyright on this one. Sure. */
+
+void rio_pcicopy( char *from, char *to, int amount)
+{
+ while ( amount-- )
+ *to++ = *from++;
+}
diff --git a/drivers/char/rio/rioroute.c b/drivers/char/rio/rioroute.c
new file mode 100644
index 000000000000..106b31f48a21
--- /dev/null
+++ b/drivers/char/rio/rioroute.c
@@ -0,0 +1,1238 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : rioroute.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:46
+** Retrieved : 11/6/98 10:33:50
+**
+** ident @(#)rioroute.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioroute_c_sccs_ = "@(#)rioroute.c 1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+static int RIOCheckIsolated(struct rio_info *, struct Host *, uint);
+static int RIOIsolate(struct rio_info *, struct Host *, uint);
+static int RIOCheck(struct Host *, uint);
+static void RIOConCon(struct rio_info *, struct Host *, uint, uint, uint, uint, int);
+
+
+/*
+** Incoming on the ROUTE_RUP
+** I wrote this while I was tired. Forgive me.
+*/
+int RIORouteRup( struct rio_info *p, uint Rup, struct Host *HostP, PKT *PacketP )
+{
+ struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+ struct PktCmd_M *PktReplyP;
+ struct CmdBlk *CmdBlkP;
+ struct Port *PortP;
+ struct Map *MapP;
+ struct Top *TopP;
+ int ThisLink, ThisLinkMin, ThisLinkMax;
+ int port;
+ int Mod, Mod1, Mod2;
+ ushort RtaType;
+ uint RtaUniq;
+ uint ThisUnit, ThisUnit2; /* 2 ids to accommodate 16 port RTA */
+ uint OldUnit, NewUnit, OldLink, NewLink;
+ char *MyType, *MyName;
+ int Lies;
+ unsigned long flags;
+
+#ifdef STACK
+ RIOStackCheck("RIORouteRup");
+#endif
+#ifdef CHECK
+ CheckPacketP(PacketP);
+ CheckHostP(HostP);
+ CheckRup(Rup);
+ CheckHost(Host);
+#endif
+ /*
+ ** Is this unit telling us it's current link topology?
+ */
+ if ( RBYTE(PktCmdP->Command) == ROUTE_TOPOLOGY )
+ {
+ MapP = HostP->Mapping;
+
+ /*
+ ** The packet can be sent either by the host or by an RTA.
+ ** If it comes from the host, then we need to fill in the
+ ** Topology array in the host structure. If it came in
+ ** from an RTA then we need to fill in the Mapping structure's
+ ** Topology array for the unit.
+ */
+ if ( Rup >= (ushort)MAX_RUP )
+ {
+ ThisUnit = HOST_ID;
+ TopP = HostP->Topology;
+ MyType = "Host";
+ MyName = HostP->Name;
+ ThisLinkMin = ThisLinkMax = Rup - MAX_RUP;
+ }
+ else
+ {
+ ThisUnit = Rup+1;
+ TopP = HostP->Mapping[Rup].Topology;
+ MyType = "RTA";
+ MyName = HostP->Mapping[Rup].Name;
+ ThisLinkMin = 0;
+ ThisLinkMax = LINKS_PER_UNIT - 1;
+ }
+
+ /*
+ ** Lies will not be tolerated.
+ ** If any pair of links claim to be connected to the same
+ ** place, then ignore this packet completely.
+ */
+ Lies = 0;
+ for ( ThisLink=ThisLinkMin + 1; ThisLink <= ThisLinkMax; ThisLink++)
+ {
+ /*
+ ** it won't lie about network interconnect, total disconnects
+ ** and no-IDs. (or at least, it doesn't *matter* if it does)
+ */
+ if ( RBYTE(PktCmdP->RouteTopology[ThisLink].Unit) > (ushort)MAX_RUP )
+ continue;
+
+ for ( NewLink=ThisLinkMin; NewLink < ThisLink; NewLink++ )
+ {
+ if ( (RBYTE(PktCmdP->RouteTopology[ThisLink].Unit) ==
+ RBYTE(PktCmdP->RouteTopology[NewLink].Unit)) &&
+ (RBYTE(PktCmdP->RouteTopology[ThisLink].Link) ==
+ RBYTE(PktCmdP->RouteTopology[NewLink].Link)) )
+ {
+ Lies++;
+ }
+ }
+ }
+
+ if ( Lies )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "LIES! DAMN LIES! %d LIES!\n",Lies);
+ rio_dprintk (RIO_DEBUG_ROUTE, "%d:%c %d:%c %d:%c %d:%c\n",
+ RBYTE(PktCmdP->RouteTopology[0].Unit),
+ 'A'+RBYTE(PktCmdP->RouteTopology[0].Link),
+ RBYTE(PktCmdP->RouteTopology[1].Unit),
+ 'A'+RBYTE(PktCmdP->RouteTopology[1].Link),
+ RBYTE(PktCmdP->RouteTopology[2].Unit),
+ 'A'+RBYTE(PktCmdP->RouteTopology[2].Link),
+ RBYTE(PktCmdP->RouteTopology[3].Unit),
+ 'A'+RBYTE(PktCmdP->RouteTopology[3].Link));
+ return TRUE;
+ }
+
+ /*
+ ** now, process each link.
+ */
+ for ( ThisLink=ThisLinkMin; ThisLink <= ThisLinkMax; ThisLink++)
+ {
+ /*
+ ** this is what it was connected to
+ */
+ OldUnit = TopP[ThisLink].Unit;
+ OldLink = TopP[ThisLink].Link;
+
+ /*
+ ** this is what it is now connected to
+ */
+ NewUnit = RBYTE(PktCmdP->RouteTopology[ThisLink].Unit);
+ NewLink = RBYTE(PktCmdP->RouteTopology[ThisLink].Link);
+
+ if ( OldUnit != NewUnit || OldLink != NewLink )
+ {
+ /*
+ ** something has changed!
+ */
+
+ if ( NewUnit > MAX_RUP &&
+ NewUnit != ROUTE_DISCONNECT &&
+ NewUnit != ROUTE_NO_ID &&
+ NewUnit != ROUTE_INTERCONNECT )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "I have a link from %s %s to unit %d:%d - I don't like it.\n",
+ MyType,
+ MyName,
+ NewUnit,
+ NewLink);
+ }
+ else
+ {
+ /*
+ ** put the new values in
+ */
+ TopP[ThisLink].Unit = NewUnit;
+ TopP[ThisLink].Link = NewLink;
+
+ RIOSetChange(p);
+
+ if ( OldUnit <= MAX_RUP )
+ {
+ /*
+ ** If something has become bust, then re-enable them messages
+ */
+ if (! p->RIONoMessage)
+ RIOConCon(p,HostP,ThisUnit,ThisLink,OldUnit,OldLink,DISCONNECT);
+ }
+
+ if ( ( NewUnit <= MAX_RUP ) && !p->RIONoMessage )
+ RIOConCon(p,HostP,ThisUnit,ThisLink,NewUnit,NewLink,CONNECT);
+
+ if ( NewUnit == ROUTE_NO_ID )
+ rio_dprintk (RIO_DEBUG_ROUTE, "%s %s (%c) is connected to an unconfigured unit.\n",
+ MyType,MyName,'A'+ThisLink);
+
+ if ( NewUnit == ROUTE_INTERCONNECT )
+ {
+ if (! p->RIONoMessage)
+ cprintf("%s '%s' (%c) is connected to another network.\n", MyType,MyName,'A'+ThisLink);
+ }
+
+ /*
+ ** perform an update for 'the other end', so that these messages
+ ** only appears once. Only disconnect the other end if it is pointing
+ ** at us!
+ */
+ if ( OldUnit == HOST_ID )
+ {
+ if ( HostP->Topology[OldLink].Unit == ThisUnit &&
+ HostP->Topology[OldLink].Link == ThisLink )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "SETTING HOST (%c) TO DISCONNECTED!\n", OldLink+'A');
+ HostP->Topology[OldLink].Unit = ROUTE_DISCONNECT;
+ HostP->Topology[OldLink].Link = NO_LINK;
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "HOST(%c) WAS NOT CONNECTED TO %s (%c)!\n",
+ OldLink+'A',HostP->Mapping[ThisUnit-1].Name,ThisLink+'A');
+ }
+ }
+ else if ( OldUnit <= MAX_RUP )
+ {
+ if ( HostP->Mapping[OldUnit-1].Topology[OldLink].Unit == ThisUnit &&
+ HostP->Mapping[OldUnit-1].Topology[OldLink].Link == ThisLink )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "SETTING RTA %s (%c) TO DISCONNECTED!\n",
+ HostP->Mapping[OldUnit-1].Name,OldLink+'A');
+ HostP->Mapping[OldUnit-1].Topology[OldLink].Unit=ROUTE_DISCONNECT;
+ HostP->Mapping[OldUnit-1].Topology[OldLink].Link=NO_LINK;
+ }
+ else
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "RTA %s (%c) WAS NOT CONNECTED TO %s (%c)\n",
+ HostP->Mapping[OldUnit-1].Name,OldLink+'A',
+ HostP->Mapping[ThisUnit-1].Name,ThisLink+'A');
+ }
+ }
+ if ( NewUnit == HOST_ID )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "MARKING HOST (%c) CONNECTED TO %s (%c)\n",
+ NewLink+'A',MyName,ThisLink+'A');
+ HostP->Topology[NewLink].Unit = ThisUnit;
+ HostP->Topology[NewLink].Link = ThisLink;
+ }
+ else if ( NewUnit <= MAX_RUP )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "MARKING RTA %s (%c) CONNECTED TO %s (%c)\n",
+ HostP->Mapping[NewUnit-1].Name,NewLink+'A',MyName,ThisLink+'A');
+ HostP->Mapping[NewUnit-1].Topology[NewLink].Unit=ThisUnit;
+ HostP->Mapping[NewUnit-1].Topology[NewLink].Link=ThisLink;
+ }
+ }
+ RIOSetChange(p);
+ RIOCheckIsolated(p, HostP, OldUnit );
+ }
+ }
+ return TRUE;
+ }
+
+ /*
+ ** The only other command we recognise is a route_request command
+ */
+ if ( RBYTE(PktCmdP->Command) != ROUTE_REQUEST )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unknown command %d received on rup %d host %d ROUTE_RUP\n",
+ RBYTE(PktCmdP->Command),Rup,(int)HostP);
+ return TRUE;
+ }
+
+ RtaUniq = (RBYTE(PktCmdP->UniqNum[0])) +
+ (RBYTE(PktCmdP->UniqNum[1]) << 8) +
+ (RBYTE(PktCmdP->UniqNum[2]) << 16) +
+ (RBYTE(PktCmdP->UniqNum[3]) << 24);
+
+ /*
+ ** Determine if 8 or 16 port RTA
+ */
+ RtaType = GetUnitType(RtaUniq);
+
+ rio_dprintk (RIO_DEBUG_ROUTE, "Received a request for an ID for serial number %x\n", RtaUniq);
+
+ Mod = RBYTE(PktCmdP->ModuleTypes);
+ Mod1 = LONYBLE(Mod);
+ if (RtaType == TYPE_RTA16)
+ {
+ /*
+ ** Only one ident is set for a 16 port RTA. To make compatible
+ ** with 8 port, set 2nd ident in Mod2 to the same as Mod1.
+ */
+ Mod2 = Mod1;
+ rio_dprintk (RIO_DEBUG_ROUTE, "Backplane type is %s (all ports)\n",
+ p->RIOModuleTypes[Mod1].Name);
+ }
+ else
+ {
+ Mod2 = HINYBLE(Mod);
+ rio_dprintk (RIO_DEBUG_ROUTE, "Module types are %s (ports 0-3) and %s (ports 4-7)\n",
+ p->RIOModuleTypes[Mod1].Name, p->RIOModuleTypes[Mod2].Name);
+ }
+
+ if ( RtaUniq == 0xffffffff )
+ {
+ ShowPacket( DBG_SPECIAL, PacketP );
+ }
+
+ /*
+ ** try to unhook a command block from the command free list.
+ */
+ if ( !(CmdBlkP = RIOGetCmdBlk()) )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "No command blocks to route RTA! come back later.\n");
+ return 0;
+ }
+
+ /*
+ ** Fill in the default info on the command block
+ */
+ CmdBlkP->Packet.dest_unit = Rup;
+ CmdBlkP->Packet.dest_port = ROUTE_RUP;
+ CmdBlkP->Packet.src_unit = HOST_ID;
+ CmdBlkP->Packet.src_port = ROUTE_RUP;
+ CmdBlkP->Packet.len = PKT_CMD_BIT | 1;
+ CmdBlkP->PreFuncP = CmdBlkP->PostFuncP = NULL;
+ PktReplyP = (struct PktCmd_M *)CmdBlkP->Packet.data;
+
+ if (! RIOBootOk(p, HostP, RtaUniq))
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "RTA %x tried to get an ID, but does not belong - FOAD it!\n",
+ RtaUniq);
+ PktReplyP->Command = ROUTE_FOAD;
+ HostP->Copy("RT_FOAD", PktReplyP->CommandText, 7);
+ RIOQueueCmdBlk(HostP, Rup, CmdBlkP);
+ return TRUE;
+ }
+
+ /*
+ ** Check to see if the RTA is configured for this host
+ */
+ for ( ThisUnit=0; ThisUnit<MAX_RUP; ThisUnit++ )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Entry %d Flags=%s %s UniqueNum=0x%x\n",
+ ThisUnit,
+ HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE ?
+ "Slot-In-Use":"Not In Use",
+ HostP->Mapping[ThisUnit].Flags & SLOT_TENTATIVE ?
+ "Slot-Tentative":"Not Tentative",
+ HostP->Mapping[ThisUnit].RtaUniqueNum);
+
+ /*
+ ** We have an entry for it.
+ */
+ if ( (HostP->Mapping[ThisUnit].Flags & (SLOT_IN_USE | SLOT_TENTATIVE)) &&
+ (HostP->Mapping[ThisUnit].RtaUniqueNum == RtaUniq) )
+ {
+ if (RtaType == TYPE_RTA16)
+ {
+ ThisUnit2 = HostP->Mapping[ThisUnit].ID2 - 1;
+ rio_dprintk (RIO_DEBUG_ROUTE, "Found unit 0x%x at slots %d+%d\n",
+ RtaUniq,ThisUnit,ThisUnit2);
+ }
+ else
+ rio_dprintk (RIO_DEBUG_ROUTE, "Found unit 0x%x at slot %d\n",
+ RtaUniq,ThisUnit);
+ /*
+ ** If we have no knowledge of booting it, then the host has
+ ** been re-booted, and so we must kill the RTA, so that it
+ ** will be booted again (potentially with new bins)
+ ** and it will then re-ask for an ID, which we will service.
+ */
+ if ( (HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE) &&
+ !(HostP->Mapping[ThisUnit].Flags & RTA_BOOTED) )
+ {
+ if ( !(HostP->Mapping[ThisUnit].Flags & MSG_DONE) )
+ {
+ if ( !p->RIONoMessage )
+ cprintf("RTA '%s' is being updated.\n",HostP->Mapping[ThisUnit].Name);
+ HostP->Mapping[ThisUnit].Flags |= MSG_DONE;
+ }
+ PktReplyP->Command = ROUTE_FOAD;
+ HostP->Copy("RT_FOAD",PktReplyP->CommandText,7);
+ RIOQueueCmdBlk(HostP, Rup, CmdBlkP);
+ return TRUE;
+ }
+
+ /*
+ ** Send the ID (entry) to this RTA. The ID number is implicit as
+ ** the offset into the table. It is worth noting at this stage
+ ** that offset zero in the table contains the entries for the
+ ** RTA with ID 1!!!!
+ */
+ PktReplyP->Command = ROUTE_ALLOCATE;
+ PktReplyP->IDNum = ThisUnit+1;
+ if (RtaType == TYPE_RTA16)
+ {
+ if (HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE)
+ /*
+ ** Adjust the phb and tx pkt dest_units for 2nd block of 8
+ ** only if the RTA has ports associated (SLOT_IN_USE)
+ */
+ RIOFixPhbs(p, HostP, ThisUnit2);
+ PktReplyP->IDNum2 = ThisUnit2+1;
+ rio_dprintk (RIO_DEBUG_ROUTE, "RTA '%s' has been allocated IDs %d+%d\n",
+ HostP->Mapping[ThisUnit].Name, PktReplyP->IDNum, PktReplyP->IDNum2);
+ }
+ else
+ {
+ PktReplyP->IDNum2 = ROUTE_NO_ID;
+ rio_dprintk (RIO_DEBUG_ROUTE, "RTA '%s' has been allocated ID %d\n",
+ HostP->Mapping[ThisUnit].Name,PktReplyP->IDNum);
+ }
+ HostP->Copy("RT_ALLOCAT",PktReplyP->CommandText,10);
+
+ RIOQueueCmdBlk( HostP, Rup, CmdBlkP);
+
+ /*
+ ** If this is a freshly booted RTA, then we need to re-open
+ ** the ports, if any where open, so that data may once more
+ ** flow around the system!
+ */
+ if ( (HostP->Mapping[ThisUnit].Flags & RTA_NEWBOOT) &&
+ (HostP->Mapping[ThisUnit].SysPort != NO_PORT) )
+ {
+ /*
+ ** look at the ports associated with this beast and
+ ** see if any where open. If they was, then re-open
+ ** them, using the info from the tty flags.
+ */
+ for ( port=0; port<PORTS_PER_RTA; port++ )
+ {
+ PortP = p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort];
+ if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Re-opened this port\n");
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->MagicFlags |= MAGIC_REBOOT;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ }
+ if (RtaType == TYPE_RTA16)
+ {
+ for ( port=0; port<PORTS_PER_RTA; port++ )
+ {
+ PortP = p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort];
+ if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) )
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Re-opened this port\n");
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->MagicFlags |= MAGIC_REBOOT;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ }
+ }
+ }
+
+ /*
+ ** keep a copy of the module types!
+ */
+ HostP->UnixRups[ThisUnit].ModTypes = Mod;
+ if (RtaType == TYPE_RTA16)
+ HostP->UnixRups[ThisUnit2].ModTypes = Mod;
+
+ /*
+ ** If either of the modules on this unit is read-only or write-only
+ ** or none-xprint, then we need to transfer that info over to the
+ ** relevant ports.
+ */
+ if ( HostP->Mapping[ThisUnit].SysPort != NO_PORT )
+ {
+ for ( port=0; port<PORTS_PER_MODULE; port++ )
+ {
+ p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort]->Config &= ~RIO_NOMASK;
+ p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort]->Config |=
+ p->RIOModuleTypes[Mod1].Flags[port];
+ p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit].SysPort]->Config &= ~RIO_NOMASK;
+ p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit].SysPort]->Config |= p->RIOModuleTypes[Mod2].Flags[port];
+ }
+ if (RtaType == TYPE_RTA16)
+ {
+ for ( port=0; port<PORTS_PER_MODULE; port++ )
+ {
+ p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort]->Config &= ~RIO_NOMASK;
+ p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort]->Config |= p->RIOModuleTypes[Mod1].Flags[port];
+ p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit2].SysPort]->Config &= ~RIO_NOMASK;
+ p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit2].SysPort]->Config |= p->RIOModuleTypes[Mod2].Flags[port];
+ }
+ }
+ }
+
+ /*
+ ** Job done, get on with the interrupts!
+ */
+ return TRUE;
+ }
+ }
+ /*
+ ** There is no table entry for this RTA at all.
+ **
+ ** Lets check to see if we actually booted this unit - if not,
+ ** then we reset it and it will go round the loop of being booted
+ ** we can then worry about trying to fit it into the table.
+ */
+ for ( ThisUnit=0; ThisUnit<HostP->NumExtraBooted; ThisUnit++ )
+ if ( HostP->ExtraUnits[ThisUnit] == RtaUniq )
+ break;
+ if ( ThisUnit == HostP->NumExtraBooted && ThisUnit != MAX_EXTRA_UNITS )
+ {
+ /*
+ ** if the unit wasn't in the table, and the table wasn't full, then
+ ** we reset the unit, because we didn't boot it.
+ ** However, if the table is full, it could be that we did boot
+ ** this unit, and so we won't reboot it, because it isn't really
+ ** all that disasterous to keep the old bins in most cases. This
+ ** is a rather tacky feature, but we are on the edge of reallity
+ ** here, because the implication is that someone has connected
+ ** 16+MAX_EXTRA_UNITS onto one host.
+ */
+ static int UnknownMesgDone = 0;
+
+ if ( !UnknownMesgDone )
+ {
+ if (! p->RIONoMessage)
+ cprintf("One or more unknown RTAs are being updated.\n");
+ UnknownMesgDone = 1;
+ }
+
+ PktReplyP->Command = ROUTE_FOAD;
+ HostP->Copy("RT_FOAD",PktReplyP->CommandText,7);
+ }
+ else
+ {
+ /*
+ ** we did boot it (as an extra), and there may now be a table
+ ** slot free (because of a delete), so we will try to make
+ ** a tentative entry for it, so that the configurator can see it
+ ** and fill in the details for us.
+ */
+ if (RtaType == TYPE_RTA16)
+ {
+ if (RIOFindFreeID(p, HostP, &ThisUnit, &ThisUnit2) == 0)
+ {
+ RIODefaultName(p, HostP, ThisUnit);
+ FillSlot(ThisUnit, ThisUnit2, RtaUniq, HostP);
+ }
+ }
+ else
+ {
+ if (RIOFindFreeID(p, HostP, &ThisUnit, NULL) == 0)
+ {
+ RIODefaultName(p, HostP, ThisUnit);
+ FillSlot(ThisUnit, 0, RtaUniq, HostP);
+ }
+ }
+ PktReplyP->Command = ROUTE_USED;
+ HostP->Copy("RT_USED",PktReplyP->CommandText,7);
+ }
+ RIOQueueCmdBlk( HostP, Rup, CmdBlkP);
+ return TRUE;
+}
+
+
+void
+RIOFixPhbs(p, HostP, unit)
+struct rio_info *p;
+struct Host *HostP;
+uint unit;
+{
+ ushort link, port;
+ struct Port *PortP;
+ unsigned long flags;
+ int PortN = HostP->Mapping[unit].SysPort;
+
+ rio_dprintk (RIO_DEBUG_ROUTE, "RIOFixPhbs unit %d sysport %d\n", unit, PortN);
+
+ if (PortN != -1) {
+ ushort dest_unit = HostP->Mapping[unit].ID2;
+
+ /*
+ ** Get the link number used for the 1st 8 phbs on this unit.
+ */
+ PortP = p->RIOPortp[HostP->Mapping[dest_unit - 1].SysPort];
+
+ link = RWORD(PortP->PhbP->link);
+
+ for (port = 0; port < PORTS_PER_RTA; port++, PortN++) {
+ ushort dest_port = port + 8;
+#if 0
+ uint PktInt;
+#endif
+ WORD *TxPktP;
+ PKT *Pkt;
+
+ PortP = p->RIOPortp[PortN];
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ /*
+ ** If RTA is not powered on, the tx packets will be
+ ** unset, so go no further.
+ */
+ if (PortP->TxStart == 0) {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Tx pkts not set up yet\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ break;
+ }
+
+ /*
+ ** For the second slot of a 16 port RTA, the driver needs to
+ ** sort out the phb to port mappings. The dest_unit for this
+ ** group of 8 phbs is set to the dest_unit of the accompanying
+ ** 8 port block. The dest_port of the second unit is set to
+ ** be in the range 8-15 (i.e. 8 is added). Thus, for a 16 port
+ ** RTA with IDs 5 and 6, traffic bound for port 6 of unit 6
+ ** (being the second map ID) will be sent to dest_unit 5, port
+ ** 14. When this RTA is deleted, dest_unit for ID 6 will be
+ ** restored, and the dest_port will be reduced by 8.
+ ** Transmit packets also have a destination field which needs
+ ** adjusting in the same manner.
+ ** Note that the unit/port bytes in 'dest' are swapped.
+ ** We also need to adjust the phb and rup link numbers for the
+ ** second block of 8 ttys.
+ */
+ for (TxPktP = PortP->TxStart; TxPktP <= PortP->TxEnd; TxPktP++) {
+ /*
+ ** *TxPktP is the pointer to the transmit packet on the host
+ ** card. This needs to be translated into a 32 bit pointer
+ ** so it can be accessed from the driver.
+ */
+ Pkt = (PKT *) RIO_PTR(HostP->Caddr,RINDW(TxPktP));
+
+ /*
+ ** If the packet is used, reset it.
+ */
+ Pkt = (PKT *)((uint)Pkt & ~PKT_IN_USE);
+ WBYTE(Pkt->dest_unit, dest_unit);
+ WBYTE(Pkt->dest_port, dest_port);
+ }
+ rio_dprintk (RIO_DEBUG_ROUTE, "phb dest: Old %x:%x New %x:%x\n",
+ RWORD(PortP->PhbP->destination) & 0xff,
+ (RWORD(PortP->PhbP->destination) >> 8) & 0xff,
+ dest_unit, dest_port);
+ WWORD(PortP->PhbP->destination, dest_unit + (dest_port << 8));
+ WWORD(PortP->PhbP->link, link);
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ /*
+ ** Now make sure the range of ports to be serviced includes
+ ** the 2nd 8 on this 16 port RTA.
+ */
+ if (link > 3) return;
+ if (((unit * 8) + 7) > RWORD(HostP->LinkStrP[link].last_port)) {
+ rio_dprintk (RIO_DEBUG_ROUTE, "last port on host link %d: %d\n", link, (unit * 8) + 7);
+ WWORD(HostP->LinkStrP[link].last_port, (unit * 8) + 7);
+ }
+ }
+}
+
+/*
+** Check to see if the new disconnection has isolated this unit.
+** If it has, then invalidate all its link information, and tell
+** the world about it. This is done to ensure that the configurator
+** only gets up-to-date information about what is going on.
+*/
+static int
+RIOCheckIsolated(p, HostP, UnitId)
+struct rio_info * p;
+struct Host *HostP;
+uint UnitId;
+{
+ unsigned long flags;
+ rio_spin_lock_irqsave(&HostP->HostLock, flags);
+
+#ifdef CHECK
+ CheckHostP( HostP );
+ CheckUnitId( UnitId );
+#endif
+ if ( RIOCheck( HostP, UnitId ) ) {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unit %d is NOT isolated\n", UnitId);
+ rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+ return(0);
+ }
+
+ RIOIsolate(p, HostP, UnitId );
+ RIOSetChange(p);
+ rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+ return 1;
+}
+
+/*
+** Invalidate all the link interconnectivity of this unit, and of
+** all the units attached to it. This will mean that the entire
+** subnet will re-introduce itself.
+*/
+static int
+RIOIsolate(p, HostP, UnitId)
+struct rio_info * p;
+struct Host * HostP;
+uint UnitId;
+{
+ uint link, unit;
+
+#ifdef CHECK
+ CheckHostP( HostP );
+ CheckUnitId( UnitId );
+#endif
+ UnitId--; /* this trick relies on the Unit Id being UNSIGNED! */
+
+ if ( UnitId >= MAX_RUP ) /* dontcha just lurv unsigned maths! */
+ return(0);
+
+ if ( HostP->Mapping[UnitId].Flags & BEEN_HERE )
+ return(0);
+
+ HostP->Mapping[UnitId].Flags |= BEEN_HERE;
+
+ if ( p->RIOPrintDisabled == DO_PRINT )
+ rio_dprintk (RIO_DEBUG_ROUTE, "RIOMesgIsolated %s", HostP->Mapping[UnitId].Name);
+
+ for ( link=0; link<LINKS_PER_UNIT; link++) {
+ unit = HostP->Mapping[UnitId].Topology[link].Unit;
+ HostP->Mapping[UnitId].Topology[link].Unit = ROUTE_DISCONNECT;
+ HostP->Mapping[UnitId].Topology[link].Link = NO_LINK;
+ RIOIsolate(p, HostP, unit );
+ }
+ HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+ return 1;
+}
+
+static int
+RIOCheck(HostP, UnitId)
+struct Host *HostP;
+uint UnitId;
+{
+ unsigned char link;
+
+#ifdef CHECK
+ CheckHostP( HostP );
+ CheckUnitId( UnitId );
+#endif
+/* rio_dprint(RIO_DEBUG_ROUTE, ("Check to see if unit %d has a route to the host\n",UnitId)); */
+ rio_dprintk (RIO_DEBUG_ROUTE, "RIOCheck : UnitID = %d\n", UnitId);
+
+ if ( UnitId == HOST_ID ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is NOT isolated - it IS the host!\n", UnitId)); */
+ return 1;
+ }
+
+ UnitId--;
+
+ if ( UnitId >= MAX_RUP ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d - ignored.\n", UnitId)); */
+ return 0;
+ }
+
+ for ( link=0; link<LINKS_PER_UNIT; link++ ) {
+ if ( HostP->Mapping[UnitId].Topology[link].Unit==HOST_ID ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is connected directly to host via link (%c).\n",
+ UnitId, 'A'+link)); */
+ return 1;
+ }
+ }
+
+ if ( HostP->Mapping[UnitId].Flags & BEEN_HERE ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Been to Unit %d before - ignoring\n", UnitId)); */
+ return 0;
+ }
+
+ HostP->Mapping[UnitId].Flags |= BEEN_HERE;
+
+ for ( link=0; link < LINKS_PER_UNIT; link++ ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d check link (%c)\n", UnitId,'A'+link)); */
+ if ( RIOCheck( HostP, HostP->Mapping[UnitId].Topology[link].Unit ) ) {
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is connected to something that knows the host via link (%c)\n", UnitId,link+'A')); */
+ HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+ return 1;
+ }
+ }
+
+ HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+
+ /* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d DOESNT KNOW THE HOST!\n", UnitId)); */
+
+ return 0;
+}
+
+/*
+** Returns the type of unit (host, 16/8 port RTA)
+*/
+
+uint
+GetUnitType(Uniq)
+uint Uniq;
+{
+ switch ( (Uniq >> 28) & 0xf)
+ {
+ case RIO_AT:
+ case RIO_MCA:
+ case RIO_EISA:
+ case RIO_PCI:
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: Host\n");
+ return(TYPE_HOST);
+ case RIO_RTA_16:
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: 16 port RTA\n");
+ return(TYPE_RTA16);
+ case RIO_RTA:
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: 8 port RTA\n");
+ return(TYPE_RTA8);
+ default :
+ rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: Unrecognised\n");
+ return(99);
+ }
+}
+
+int
+RIOSetChange(p)
+struct rio_info * p;
+{
+ if ( p->RIOQuickCheck != NOT_CHANGED )
+ return(0);
+ p->RIOQuickCheck = CHANGED;
+ if ( p->RIOSignalProcess ) {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Send SIG-HUP");
+ /*
+ psignal( RIOSignalProcess, SIGHUP );
+ */
+ }
+ return(0);
+}
+
+static void
+RIOConCon(p, HostP, FromId, FromLink, ToId, ToLink, Change)
+struct rio_info * p;
+struct Host *HostP;
+uint FromId;
+uint FromLink;
+uint ToId;
+uint ToLink;
+int Change;
+{
+ char *FromName;
+ char *FromType;
+ char *ToName;
+ char *ToType;
+ unsigned int tp;
+
+/*
+** 15.10.1998 ARG - ESIL 0759
+** (Part) fix for port being trashed when opened whilst RTA "disconnected"
+**
+** What's this doing in here anyway ?
+** It was causing the port to be 'unmapped' if opened whilst RTA "disconnected"
+**
+** 09.12.1998 ARG - ESIL 0776 - part fix
+** Okay, We've found out what this was all about now !
+** Someone had botched this to use RIOHalted to indicated the number of RTAs
+** 'disconnected'. The value in RIOHalted was then being used in the
+** 'RIO_QUICK_CHECK' ioctl. A none zero value indicating that a least one RTA
+** is 'disconnected'. The change was put in to satisfy a customer's needs.
+** Having taken this bit of code out 'RIO_QUICK_CHECK' now no longer works for
+** the customer.
+**
+ if (Change == CONNECT) {
+ if (p->RIOHalted) p->RIOHalted --;
+ }
+ else {
+ p->RIOHalted ++;
+ }
+**
+** So - we need to implement it slightly differently - a new member of the
+** rio_info struct - RIORtaDisCons (RIO RTA connections) keeps track of RTA
+** connections and disconnections.
+*/
+ if (Change == CONNECT) {
+ if (p->RIORtaDisCons) p->RIORtaDisCons--;
+ }
+ else {
+ p->RIORtaDisCons++;
+ }
+
+ if ( p->RIOPrintDisabled == DONT_PRINT )
+ return;
+
+ if ( FromId > ToId ) {
+ tp = FromId;
+ FromId = ToId;
+ ToId = tp;
+ tp = FromLink;
+ FromLink = ToLink;
+ ToLink = tp;
+ }
+
+ FromName = FromId ? HostP->Mapping[FromId-1].Name : HostP->Name;
+ FromType = FromId ? "RTA" : "HOST";
+ ToName = ToId ? HostP->Mapping[ToId-1].Name : HostP->Name;
+ ToType = ToId ? "RTA" : "HOST";
+
+ rio_dprintk (RIO_DEBUG_ROUTE, "Link between %s '%s' (%c) and %s '%s' (%c) %s.\n",
+ FromType, FromName, 'A'+FromLink,
+ ToType, ToName, 'A'+ToLink,
+ (Change==CONNECT) ? "established" : "disconnected");
+ cprintf("Link between %s '%s' (%c) and %s '%s' (%c) %s.\n",
+ FromType, FromName, 'A'+FromLink,
+ ToType, ToName, 'A'+ToLink,
+ (Change==CONNECT) ? "established" : "disconnected");
+}
+
+/*
+** RIORemoveFromSavedTable :
+**
+** Delete and RTA entry from the saved table given to us
+** by the configuration program.
+*/
+static int
+RIORemoveFromSavedTable(struct rio_info *p, struct Map *pMap)
+{
+ int entry;
+
+ /*
+ ** We loop for all entries even after finding an entry and
+ ** zeroing it because we may have two entries to delete if
+ ** it's a 16 port RTA.
+ */
+ for (entry = 0; entry < TOTAL_MAP_ENTRIES; entry++)
+ {
+ if (p->RIOSavedTable[entry].RtaUniqueNum == pMap->RtaUniqueNum)
+ {
+ bzero((caddr_t)&p->RIOSavedTable[entry], sizeof(struct Map));
+ }
+ }
+ return 0;
+}
+
+
+/*
+** RIOCheckDisconnected :
+**
+** Scan the unit links to and return zero if the unit is completely
+** disconnected.
+*/
+static int
+RIOFreeDisconnected(struct rio_info *p, struct Host *HostP, int unit)
+{
+ int link;
+
+
+ rio_dprintk (RIO_DEBUG_ROUTE, "RIOFreeDisconnect unit %d\n", unit);
+ /*
+ ** If the slot is tentative and does not belong to the
+ ** second half of a 16 port RTA then scan to see if
+ ** is disconnected.
+ */
+ for (link = 0; link < LINKS_PER_UNIT; link++)
+ {
+ if (HostP->Mapping[unit].Topology[link].Unit != ROUTE_DISCONNECT)
+ break;
+ }
+
+ /*
+ ** If not all links are disconnected then we can forget about it.
+ */
+ if (link < LINKS_PER_UNIT)
+ return 1;
+
+#if NEED_TO_FIX_THIS
+ /* Ok so all the links are disconnected. But we may have only just
+ ** made this slot tentative and not yet received a topology update.
+ ** Lets check how long ago we made it tentative.
+ */
+ rio_dprintk (RIO_DEBUG_ROUTE, "Just about to check LBOLT on entry %d\n", unit);
+ if (drv_getparm(LBOLT, (ulong_t *) &current_time))
+ rio_dprintk (RIO_DEBUG_ROUTE, "drv_getparm(LBOLT,....) Failed.\n");
+
+ elapse_time = current_time - TentTime[unit];
+ rio_dprintk (RIO_DEBUG_ROUTE, "elapse %d = current %d - tent %d (%d usec)\n",
+ elapse_time, current_time, TentTime[unit], drv_hztousec(elapse_time));
+ if (drv_hztousec(elapse_time) < WAIT_TO_FINISH)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Skipping slot %d, not timed out yet %d\n",
+ unit, drv_hztousec(elapse_time));
+ return 1;
+ }
+#endif
+
+ /*
+ ** We have found an usable slot.
+ ** If it is half of a 16 port RTA then delete the other half.
+ */
+ if (HostP->Mapping[unit].ID2 != 0)
+ {
+ int nOther = (HostP->Mapping[unit].ID2) -1;
+
+ rio_dprintk (RIO_DEBUG_ROUTE, "RioFreedis second slot %d.\n", nOther);
+ bzero((caddr_t)&HostP->Mapping[nOther], sizeof(struct Map));
+ }
+ RIORemoveFromSavedTable(p, &HostP->Mapping[unit]);
+
+ return 0;
+}
+
+
+/*
+** RIOFindFreeID :
+**
+** This function scans the given host table for either one
+** or two free unit ID's.
+*/
+int
+RIOFindFreeID(struct rio_info *p, struct Host *HostP, uint *pID1, uint *pID2)
+{
+ int unit,tempID;
+
+ /*
+ ** Initialise the ID's to MAX_RUP.
+ ** We do this to make the loop for setting the ID's as simple as
+ ** possible.
+ */
+ *pID1 = MAX_RUP;
+ if (pID2 != NULL)
+ *pID2 = MAX_RUP;
+
+ /*
+ ** Scan all entries of the host mapping table for free slots.
+ ** We scan for free slots first and then if that is not successful
+ ** we start all over again looking for tentative slots we can re-use.
+ */
+ for (unit = 0; unit < MAX_RUP; unit++)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Scanning unit %d\n",unit);
+ /*
+ ** If the flags are zero then the slot is empty.
+ */
+ if (HostP->Mapping[unit].Flags == 0)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, " This slot is empty.\n");
+ /*
+ ** If we haven't allocated the first ID then do it now.
+ */
+ if (*pID1 == MAX_RUP)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Make tentative entry for first unit %d\n", unit);
+ *pID1 = unit;
+
+ /*
+ ** If the second ID is not needed then we can return
+ ** now.
+ */
+ if (pID2 == NULL)
+ return 0;
+ }
+ else
+ {
+ /*
+ ** Allocate the second slot and return.
+ */
+ rio_dprintk (RIO_DEBUG_ROUTE, "Make tentative entry for second unit %d\n", unit);
+ *pID2 = unit;
+ return 0;
+ }
+ }
+ }
+
+ /*
+ ** If we manage to come out of the free slot loop then we
+ ** need to start all over again looking for tentative slots
+ ** that we can re-use.
+ */
+ rio_dprintk (RIO_DEBUG_ROUTE, "Starting to scan for tentative slots\n");
+ for (unit = 0; unit < MAX_RUP; unit++)
+ {
+ if (((HostP->Mapping[unit].Flags & SLOT_TENTATIVE) ||
+ (HostP->Mapping[unit].Flags == 0)) && !
+ (HostP->Mapping[unit].Flags & RTA16_SECOND_SLOT ))
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, " Slot %d looks promising.\n",unit);
+
+ if(unit == *pID1)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, " No it isn't, its the 1st half\n");
+ continue;
+ }
+
+ /*
+ ** Slot is Tentative or Empty, but not a tentative second
+ ** slot of a 16 porter.
+ ** Attempt to free up this slot (and its parnter if
+ ** it is a 16 port slot. The second slot will become
+ ** empty after a call to RIOFreeDisconnected so thats why
+ ** we look for empty slots above as well).
+ */
+ if (HostP->Mapping[unit].Flags != 0)
+ if (RIOFreeDisconnected(p, HostP, unit) != 0)
+ continue;
+ /*
+ ** If we haven't allocated the first ID then do it now.
+ */
+ if (*pID1 == MAX_RUP)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Grab tentative entry for first unit %d\n", unit);
+ *pID1 = unit;
+
+ /*
+ ** Clear out this slot now that we intend to use it.
+ */
+ bzero(&HostP->Mapping[unit], sizeof(struct Map));
+
+ /*
+ ** If the second ID is not needed then we can return
+ ** now.
+ */
+ if (pID2 == NULL)
+ return 0;
+ }
+ else
+ {
+ /*
+ ** Allocate the second slot and return.
+ */
+ rio_dprintk (RIO_DEBUG_ROUTE, "Grab tentative/empty entry for second unit %d\n",
+ unit);
+ *pID2 = unit;
+
+ /*
+ ** Clear out this slot now that we intend to use it.
+ */
+ bzero(&HostP->Mapping[unit], sizeof(struct Map));
+
+ /* At this point under the right(wrong?) conditions
+ ** we may have a first unit ID being higher than the
+ ** second unit ID. This is a bad idea if we are about
+ ** to fill the slots with a 16 port RTA.
+ ** Better check and swap them over.
+ */
+
+ if (*pID1 > *pID2)
+ {
+ rio_dprintk (RIO_DEBUG_ROUTE, "Swapping IDS %d %d\n", *pID1, *pID2);
+ tempID = *pID1;
+ *pID1 = *pID2;
+ *pID2 = tempID;
+ }
+ return 0;
+ }
+ }
+ }
+
+ /*
+ ** If we manage to get to the end of the second loop then we
+ ** can give up and return a failure.
+ */
+ return 1;
+}
+
+
+/*
+** The link switch scenario.
+**
+** Rta Wun (A) is connected to Tuw (A).
+** The tables are all up to date, and the system is OK.
+**
+** If Wun (A) is now moved to Wun (B) before Wun (A) can
+** become disconnected, then the follow happens:
+**
+** Tuw (A) spots the change of unit:link at the other end
+** of its link and Tuw sends a topology packet reflecting
+** the change: Tuw (A) now disconnected from Wun (A), and
+** this is closely followed by a packet indicating that
+** Tuw (A) is now connected to Wun (B).
+**
+** Wun (B) will spot that it has now become connected, and
+** Wun will send a topology packet, which indicates that
+** both Wun (A) and Wun (B) is connected to Tuw (A).
+**
+** Eventually Wun (A) realises that it is now disconnected
+** and Wun will send out a topology packet indicating that
+** Wun (A) is now disconnected.
+*/
diff --git a/drivers/char/rio/riospace.h b/drivers/char/rio/riospace.h
new file mode 100644
index 000000000000..32b09b0f23aa
--- /dev/null
+++ b/drivers/char/rio/riospace.h
@@ -0,0 +1,161 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riospace.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:13
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)riospace.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_riospace_h__
+#define __rio_riospace_h__
+
+#ifdef SCCS_LABELS
+static char *_riospace_h_sccs_ = "@(#)riospace.h 1.2";
+#endif
+
+#define RIO_LOCATOR_LEN 16
+#define MAX_RIO_BOARDS 4
+
+/*
+** DONT change this file. At all. Unless you can rebuild the entire
+** device driver, which you probably can't, then the rest of the
+** driver won't see any changes you make here. So don't make any.
+** In particular, it won't be able to see changes to RIO_SLOTS
+*/
+
+struct Conf
+{
+ char Locator[24];
+ unsigned int StartupTime;
+ unsigned int SlowCook;
+ unsigned int IntrPollTime;
+ unsigned int BreakInterval;
+ unsigned int Timer;
+ unsigned int RtaLoadBase;
+ unsigned int HostLoadBase;
+ unsigned int XpHz;
+ unsigned int XpCps;
+ char *XpOn;
+ char *XpOff;
+ unsigned int MaxXpCps;
+ unsigned int MinXpCps;
+ unsigned int SpinCmds;
+ unsigned int FirstAddr;
+ unsigned int LastAddr;
+ unsigned int BufferSize;
+ unsigned int LowWater;
+ unsigned int LineLength;
+ unsigned int CmdTime;
+};
+
+/*
+** Board types - these MUST correspond to product codes!
+*/
+#define RIO_EMPTY 0x0
+#define RIO_EISA 0x3
+#define RIO_RTA_16 0x9
+#define RIO_AT 0xA
+#define RIO_MCA 0xB
+#define RIO_PCI 0xD
+#define RIO_RTA 0xE
+
+/*
+** Board data structure. This is used for configuration info
+*/
+struct Brd
+{
+ unsigned char Type; /* RIO_EISA, RIO_MCA, RIO_AT, RIO_EMPTY... */
+ unsigned char Ivec; /* POLLED or ivec number */
+ unsigned char Mode; /* Control stuff, see below */
+};
+
+struct Board
+{
+ char Locator[RIO_LOCATOR_LEN];
+ int NumSlots;
+ struct Brd Boards[MAX_RIO_BOARDS];
+};
+
+#define BOOT_FROM_LINK 0x00
+#define BOOT_FROM_RAM 0x01
+#define EXTERNAL_BUS_OFF 0x00
+#define EXTERNAL_BUS_ON 0x02
+#define INTERRUPT_DISABLE 0x00
+#define INTERRUPT_ENABLE 0x04
+#define BYTE_OPERATION 0x00
+#define WORD_OPERATION 0x08
+#define POLLED INTERRUPT_DISABLE
+#define IRQ_15 (0x00 | INTERRUPT_ENABLE)
+#define IRQ_12 (0x10 | INTERRUPT_ENABLE)
+#define IRQ_11 (0x20 | INTERRUPT_ENABLE)
+#define IRQ_9 (0x30 | INTERRUPT_ENABLE)
+#define SLOW_LINKS 0x00
+#define FAST_LINKS 0x40
+#define SLOW_AT_BUS 0x00
+#define FAST_AT_BUS 0x80
+#define SLOW_PCI_TP 0x00
+#define FAST_PCI_TP 0x80
+/*
+** Debug levels
+*/
+#define DBG_NONE 0x00000000
+
+#define DBG_INIT 0x00000001
+#define DBG_OPEN 0x00000002
+#define DBG_CLOSE 0x00000004
+#define DBG_IOCTL 0x00000008
+
+#define DBG_READ 0x00000010
+#define DBG_WRITE 0x00000020
+#define DBG_INTR 0x00000040
+#define DBG_PROC 0x00000080
+
+#define DBG_PARAM 0x00000100
+#define DBG_CMD 0x00000200
+#define DBG_XPRINT 0x00000400
+#define DBG_POLL 0x00000800
+
+#define DBG_DAEMON 0x00001000
+#define DBG_FAIL 0x00002000
+#define DBG_MODEM 0x00004000
+#define DBG_LIST 0x00008000
+
+#define DBG_ROUTE 0x00010000
+#define DBG_UTIL 0x00020000
+#define DBG_BOOT 0x00040000
+#define DBG_BUFFER 0x00080000
+
+#define DBG_MON 0x00100000
+#define DBG_SPECIAL 0x00200000
+#define DBG_VPIX 0x00400000
+#define DBG_FLUSH 0x00800000
+
+#define DBG_QENABLE 0x01000000
+
+#define DBG_ALWAYS 0x80000000
+
+#endif /* __rio_riospace_h__ */
diff --git a/drivers/char/rio/riotable.c b/drivers/char/rio/riotable.c
new file mode 100644
index 000000000000..8fb26ad2aa12
--- /dev/null
+++ b/drivers/char/rio/riotable.c
@@ -0,0 +1,1044 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riotable.c
+** SID : 1.2
+** Last Modified : 11/6/98 10:33:47
+** Retrieved : 11/6/98 10:33:50
+**
+** ident @(#)riotable.c 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riotable_c_sccs_ = "@(#)riotable.c 1.2";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+#include "protsts.h"
+
+/*
+** A configuration table has been loaded. It is now up to us
+** to sort it out and use the information contained therein.
+*/
+int
+RIONewTable(p)
+struct rio_info * p;
+{
+ int Host, Host1, Host2, NameIsUnique, Entry, SubEnt;
+ struct Map *MapP;
+ struct Map *HostMapP;
+ struct Host *HostP;
+
+ char *cptr;
+
+ /*
+ ** We have been sent a new table to install. We need to break
+ ** it down into little bits and spread it around a bit to see
+ ** what we have got.
+ */
+ /*
+ ** Things to check:
+ ** (things marked 'xx' aren't checked any more!)
+ ** (1) That there are no booted Hosts/RTAs out there.
+ ** (2) That the names are properly formed
+ ** (3) That blank entries really are.
+ ** xx (4) That hosts mentioned in the table actually exist. xx
+ ** (5) That the IDs are unique (per host).
+ ** (6) That host IDs are zero
+ ** (7) That port numbers are valid
+ ** (8) That port numbers aren't duplicated
+ ** (9) That names aren't duplicated
+ ** xx (10) That hosts that actually exist are mentioned in the table. xx
+ */
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(1)\n");
+ if ( p->RIOSystemUp ) { /* (1) */
+ p->RIOError.Error = HOST_HAS_ALREADY_BEEN_BOOTED;
+ return -EBUSY;
+ }
+
+ p->RIOError.Error = NOTHING_WRONG_AT_ALL;
+ p->RIOError.Entry = -1;
+ p->RIOError.Other = -1;
+
+ for ( Entry=0; Entry<TOTAL_MAP_ENTRIES; Entry++ ) {
+ MapP = &p->RIOConnectTable[Entry];
+ if ((MapP->Flags & RTA16_SECOND_SLOT) == 0) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(2)\n");
+ cptr = MapP->Name; /* (2) */
+ cptr[MAX_NAME_LEN-1]='\0';
+ if ( cptr[0]=='\0' ) {
+ bcopy(MapP->RtaUniqueNum?"RTA NN":"HOST NN",MapP->Name,8);
+ MapP->Name[5] = '0'+Entry/10;
+ MapP->Name[6] = '0'+Entry%10;
+ }
+ while ( *cptr ) {
+ if ( *cptr<' ' || *cptr>'~' ) {
+ p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ cptr++;
+ }
+ }
+
+ /*
+ ** If the entry saved was a tentative entry then just forget
+ ** about it.
+ */
+ if ( MapP->Flags & SLOT_TENTATIVE ) {
+ MapP->HostUniqueNum = 0;
+ MapP->RtaUniqueNum = 0;
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(3)\n");
+ if ( !MapP->RtaUniqueNum && !MapP->HostUniqueNum ) { /* (3) */
+ if ( MapP->ID || MapP->SysPort || MapP->Flags ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "%s pretending to be empty but isn't\n",MapP->Name);
+ p->RIOError.Error = TABLE_ENTRY_ISNT_PROPERLY_NULL;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "!RIO: Daemon: test (3) passes\n");
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(4)\n");
+ for ( Host=0; Host<p->RIONumHosts; Host++ ) { /* (4) */
+ if ( p->RIOHosts[Host].UniqueNum==MapP->HostUniqueNum ) {
+ HostP = &p->RIOHosts[Host];
+ /*
+ ** having done the lookup, we don't really want to do
+ ** it again, so hang the host number in a safe place
+ */
+ MapP->Topology[0].Unit = Host;
+ break;
+ }
+ }
+
+ if ( Host >= p->RIONumHosts ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has unknown host unique number 0x%x\n",
+ MapP->Name, MapP->HostUniqueNum);
+ MapP->HostUniqueNum = 0;
+ /* MapP->RtaUniqueNum = 0; */
+ /* MapP->ID = 0; */
+ /* MapP->Flags = 0; */
+ /* MapP->SysPort = 0; */
+ /* MapP->Name[0] = 0; */
+ continue;
+ }
+
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(5)\n");
+ if ( MapP->RtaUniqueNum ) { /* (5) */
+ if ( !MapP->ID ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIO: RTA %s has been allocated an ID of zero!\n",
+ MapP->Name);
+ p->RIOError.Error = ZERO_RTA_ID;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ if ( MapP->ID > MAX_RUP ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIO: RTA %s has been allocated an invalid ID %d\n",
+ MapP->Name, MapP->ID);
+ p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ for ( SubEnt=0; SubEnt<Entry; SubEnt++ ) {
+ if ( MapP->HostUniqueNum ==
+ p->RIOConnectTable[SubEnt].HostUniqueNum &&
+ MapP->ID == p->RIOConnectTable[SubEnt].ID ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Dupl. ID number allocated to RTA %s and RTA %s\n",
+ MapP->Name, p->RIOConnectTable[SubEnt].Name);
+ p->RIOError.Error = DUPLICATED_RTA_ID;
+ p->RIOError.Entry = Entry;
+ p->RIOError.Other = SubEnt;
+ return -ENXIO;
+ }
+ /*
+ ** If the RtaUniqueNum is the same, it may be looking at both
+ ** entries for a 16 port RTA, so check the ids
+ */
+ if ((MapP->RtaUniqueNum ==
+ p->RIOConnectTable[SubEnt].RtaUniqueNum)
+ && (MapP->ID2 != p->RIOConnectTable[SubEnt].ID)) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has duplicate unique number\n",MapP->Name);
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has duplicate unique number\n",
+ p->RIOConnectTable[SubEnt].Name);
+ p->RIOError.Error = DUPLICATE_UNIQUE_NUMBER;
+ p->RIOError.Entry = Entry;
+ p->RIOError.Other = SubEnt;
+ return -ENXIO;
+ }
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(7a)\n");
+ /* (7a) */
+ if ((MapP->SysPort != NO_PORT)&&(MapP->SysPort % PORTS_PER_RTA)) {
+ rio_dprintk (RIO_DEBUG_TABLE, "TTY Port number %d-RTA %s is not a multiple of %d!\n",
+ (int)MapP->SysPort,MapP->Name, PORTS_PER_RTA);
+ p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(7b)\n");
+ /* (7b) */
+ if ((MapP->SysPort != NO_PORT)&&(MapP->SysPort >= RIO_PORTS)) {
+ rio_dprintk (RIO_DEBUG_TABLE, "TTY Port number %d for RTA %s is too big\n",
+ (int)MapP->SysPort, MapP->Name);
+ p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ for ( SubEnt=0; SubEnt<Entry; SubEnt++ ) {
+ if ( p->RIOConnectTable[SubEnt].Flags & RTA16_SECOND_SLOT )
+ continue;
+ if ( p->RIOConnectTable[SubEnt].RtaUniqueNum ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(8)\n");
+ /* (8) */
+ if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort ==
+ p->RIOConnectTable[SubEnt].SysPort) ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA %s:same TTY port # as RTA %s (%d)\n",
+ MapP->Name, p->RIOConnectTable[SubEnt].Name,
+ (int)MapP->SysPort);
+ p->RIOError.Error = TTY_NUMBER_IN_USE;
+ p->RIOError.Entry = Entry;
+ p->RIOError.Other = SubEnt;
+ return -ENXIO;
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(9)\n");
+ if (strcmp(MapP->Name,
+ p->RIOConnectTable[SubEnt].Name)==0 && !(MapP->Flags & RTA16_SECOND_SLOT)) { /* (9) */
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA name %s used twice\n", MapP->Name);
+ p->RIOError.Error = NAME_USED_TWICE;
+ p->RIOError.Entry = Entry;
+ p->RIOError.Other = SubEnt;
+ return -ENXIO;
+ }
+ }
+ }
+ }
+ else { /* (6) */
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(6)\n");
+ if ( MapP->ID ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIO:HOST %s has been allocated ID that isn't zero!\n",
+ MapP->Name);
+ p->RIOError.Error = HOST_ID_NOT_ZERO;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ if ( MapP->SysPort != NO_PORT ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIO: HOST %s has been allocated port numbers!\n",
+ MapP->Name);
+ p->RIOError.Error = HOST_SYSPORT_BAD;
+ p->RIOError.Entry = Entry;
+ return -ENXIO;
+ }
+ }
+ }
+
+ /*
+ ** wow! if we get here then it's a goody!
+ */
+
+ /*
+ ** Zero the (old) entries for each host...
+ */
+ for ( Host=0; Host<RIO_HOSTS; Host++ ) {
+ for ( Entry=0; Entry<MAX_RUP; Entry++ ) {
+ bzero((caddr_t)&p->RIOHosts[Host].Mapping[Entry],
+ sizeof(struct Map));
+ }
+ bzero((caddr_t)&p->RIOHosts[Host].Name[0],
+ sizeof(p->RIOHosts[Host].Name) );
+ }
+
+ /*
+ ** Copy in the new table entries
+ */
+ for ( Entry=0; Entry< TOTAL_MAP_ENTRIES; Entry++ ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: Copy table for Host entry %d\n", Entry);
+ MapP = &p->RIOConnectTable[Entry];
+
+ /*
+ ** Now, if it is an empty slot ignore it!
+ */
+ if ( MapP->HostUniqueNum==0 )
+ continue;
+
+ /*
+ ** we saved the host number earlier, so grab it back
+ */
+ HostP = &p->RIOHosts[MapP->Topology[0].Unit];
+
+ /*
+ ** If it is a host, then we only need to fill in the name field.
+ */
+ if ( MapP->ID==0 ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Host entry found. Name %s\n", MapP->Name);
+ bcopy(MapP->Name,HostP->Name,MAX_NAME_LEN);
+ continue;
+ }
+
+ /*
+ ** Its an RTA entry, so fill in the host mapping entries for it
+ ** and the port mapping entries. Notice that entry zero is for
+ ** ID one.
+ */
+ HostMapP = &HostP->Mapping[MapP->ID-1];
+
+ if (MapP->Flags & SLOT_IN_USE) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Rta entry found. Name %s\n", MapP->Name);
+ /*
+ ** structure assign, then sort out the bits we shouldn't have done
+ */
+ *HostMapP = *MapP;
+
+ HostMapP->Flags = SLOT_IN_USE;
+ if (MapP->Flags & RTA16_SECOND_SLOT)
+ HostMapP->Flags |= RTA16_SECOND_SLOT;
+
+ RIOReMapPorts(p, HostP, HostMapP );
+ }
+ else {
+ rio_dprintk (RIO_DEBUG_TABLE, "TENTATIVE Rta entry found. Name %s\n", MapP->Name);
+ }
+ }
+
+ for ( Entry=0; Entry< TOTAL_MAP_ENTRIES; Entry++ ) {
+ p->RIOSavedTable[Entry] = p->RIOConnectTable[Entry];
+ }
+
+ for ( Host=0; Host<p->RIONumHosts; Host++ ) {
+ for ( SubEnt=0; SubEnt<LINKS_PER_UNIT; SubEnt++ ) {
+ p->RIOHosts[Host].Topology[SubEnt].Unit = ROUTE_DISCONNECT;
+ p->RIOHosts[Host].Topology[SubEnt].Link = NO_LINK;
+ }
+ for ( Entry=0; Entry<MAX_RUP; Entry++ ) {
+ for ( SubEnt=0; SubEnt<LINKS_PER_UNIT; SubEnt++ ) {
+ p->RIOHosts[Host].Mapping[Entry].Topology[SubEnt].Unit =
+ ROUTE_DISCONNECT;
+ p->RIOHosts[Host].Mapping[Entry].Topology[SubEnt].Link =
+ NO_LINK;
+ }
+ }
+ if ( !p->RIOHosts[Host].Name[0] ) {
+ bcopy("HOST 1",p->RIOHosts[Host].Name,7);
+ p->RIOHosts[Host].Name[5] += Host;
+ }
+ /*
+ ** Check that default name assigned is unique.
+ */
+ Host1 = Host;
+ NameIsUnique = 0;
+ while (!NameIsUnique) {
+ NameIsUnique = 1;
+ for ( Host2=0; Host2<p->RIONumHosts; Host2++ ) {
+ if (Host2 == Host)
+ continue;
+ if (strcmp(p->RIOHosts[Host].Name, p->RIOHosts[Host2].Name)
+ == 0) {
+ NameIsUnique = 0;
+ Host1++;
+ if (Host1 >= p->RIONumHosts)
+ Host1 = 0;
+ p->RIOHosts[Host].Name[5] = '1' + Host1;
+ }
+ }
+ }
+ /*
+ ** Rename host if name already used.
+ */
+ if (Host1 != Host)
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Default name %s already used\n", p->RIOHosts[Host].Name);
+ bcopy("HOST 1",p->RIOHosts[Host].Name,7);
+ p->RIOHosts[Host].Name[5] += Host1;
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "Assigning default name %s\n", p->RIOHosts[Host].Name);
+ }
+ return 0;
+}
+
+/*
+** User process needs the config table - build it from first
+** principles.
+*/
+int
+RIOApel(p)
+struct rio_info * p;
+{
+ int Host;
+ int link;
+ int Rup;
+ int Next = 0;
+ struct Map *MapP;
+ struct Host *HostP;
+ long oldspl;
+
+ disable(oldspl); /* strange but true! */
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Generating a table to return to config.rio\n");
+
+ bzero((caddr_t)&p->RIOConnectTable[0],
+ sizeof(struct Map) * TOTAL_MAP_ENTRIES );
+
+ for ( Host=0; Host<RIO_HOSTS; Host++ ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Processing host %d\n", Host);
+ HostP = &p->RIOHosts[Host];
+ MapP = &p->RIOConnectTable[Next++];
+ MapP->HostUniqueNum = HostP->UniqueNum;
+ if ( (HostP->Flags & RUN_STATE) != RC_RUNNING )
+ continue;
+ MapP->RtaUniqueNum = 0;
+ MapP->ID = 0;
+ MapP->Flags = SLOT_IN_USE;
+ MapP->SysPort = NO_PORT;
+ for ( link=0; link<LINKS_PER_UNIT; link++ )
+ MapP->Topology[link] = HostP->Topology[link];
+ bcopy(HostP->Name,MapP->Name,MAX_NAME_LEN);
+ for ( Rup=0; Rup<MAX_RUP; Rup++ ) {
+ if ( HostP->Mapping[Rup].Flags & (SLOT_IN_USE|SLOT_TENTATIVE) ) {
+ p->RIOConnectTable[Next] = HostP->Mapping[Rup];
+ if ( HostP->Mapping[Rup].Flags & SLOT_IN_USE)
+ p->RIOConnectTable[Next].Flags |= SLOT_IN_USE;
+ if ( HostP->Mapping[Rup].Flags & SLOT_TENTATIVE)
+ p->RIOConnectTable[Next].Flags |= SLOT_TENTATIVE;
+ if ( HostP->Mapping[Rup].Flags & RTA16_SECOND_SLOT )
+ p->RIOConnectTable[Next].Flags |= RTA16_SECOND_SLOT;
+ Next++;
+ }
+ }
+ }
+ restore(oldspl);
+ return 0;
+}
+
+/*
+** config.rio has taken a dislike to one of the gross maps entries.
+** if the entry is suitably inactive, then we can gob on it and remove
+** it from the table.
+*/
+int
+RIODeleteRta(p, MapP)
+struct rio_info *p;
+struct Map *MapP;
+{
+ int host, entry, port, link;
+ int SysPort;
+ struct Host *HostP;
+ struct Map *HostMapP;
+ struct Port *PortP;
+ int work_done = 0;
+ unsigned long lock_flags, sem_flags;
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Delete entry on host %x, rta %x\n",
+ MapP->HostUniqueNum, MapP->RtaUniqueNum);
+
+ for ( host=0; host < p->RIONumHosts; host++ ) {
+ HostP = &p->RIOHosts[host];
+
+ rio_spin_lock_irqsave( &HostP->HostLock, lock_flags );
+
+ if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+ rio_spin_unlock_irqrestore(&HostP->HostLock, lock_flags);
+ continue;
+ }
+
+ for ( entry=0; entry<MAX_RUP; entry++ ) {
+ if ( MapP->RtaUniqueNum == HostP->Mapping[entry].RtaUniqueNum ) {
+ HostMapP = &HostP->Mapping[entry];
+ rio_dprintk (RIO_DEBUG_TABLE, "Found entry offset %d on host %s\n",
+ entry, HostP->Name);
+
+ /*
+ ** Check all four links of the unit are disconnected
+ */
+ for ( link=0; link< LINKS_PER_UNIT; link++ ) {
+ if ( HostMapP->Topology[link].Unit != ROUTE_DISCONNECT ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Entry is in use and cannot be deleted!\n");
+ p->RIOError.Error = UNIT_IS_IN_USE;
+ rio_spin_unlock_irqrestore( &HostP->HostLock, lock_flags);
+ return -EBUSY;
+ }
+ }
+ /*
+ ** Slot has been allocated, BUT not booted/routed/
+ ** connected/selected or anything else-ed
+ */
+ SysPort = HostMapP->SysPort;
+
+ if ( SysPort != NO_PORT ) {
+ for (port=SysPort; port < SysPort+PORTS_PER_RTA; port++) {
+ PortP = p->RIOPortp[port];
+ rio_dprintk (RIO_DEBUG_TABLE, "Unmap port\n");
+
+ rio_spin_lock_irqsave( &PortP->portSem, sem_flags );
+
+ PortP->Mapped = 0;
+
+ if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) ) {
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Gob on port\n");
+ PortP->TxBufferIn = PortP->TxBufferOut = 0;
+ /* What should I do
+ wakeup( &PortP->TxBufferIn );
+ wakeup( &PortP->TxBufferOut);
+ */
+ PortP->InUse = NOT_INUSE;
+ /* What should I do
+ wakeup( &PortP->InUse );
+ signal(PortP->TtyP->t_pgrp,SIGKILL);
+ ttyflush(PortP->TtyP,(FREAD|FWRITE));
+ */
+ PortP->State |= RIO_CLOSING | RIO_DELETED;
+ }
+
+ /*
+ ** For the second slot of a 16 port RTA, the
+ ** driver needs to reset the changes made to
+ ** the phb to port mappings in RIORouteRup.
+ */
+ if (PortP->SecondBlock) {
+ ushort dest_unit = HostMapP->ID;
+ ushort dest_port = port - SysPort;
+ WORD *TxPktP;
+ PKT *Pkt;
+
+ for (TxPktP = PortP->TxStart;
+ TxPktP <= PortP->TxEnd; TxPktP++) {
+ /*
+ ** *TxPktP is the pointer to the
+ ** transmit packet on the host card.
+ ** This needs to be translated into
+ ** a 32 bit pointer so it can be
+ ** accessed from the driver.
+ */
+ Pkt = (PKT *) RIO_PTR(HostP->Caddr,
+ RWORD(*TxPktP));
+ rio_dprintk (RIO_DEBUG_TABLE,
+ "Tx packet (%x) destination: Old %x:%x New %x:%x\n",
+ *TxPktP, Pkt->dest_unit,
+ Pkt->dest_port, dest_unit, dest_port);
+ WWORD(Pkt->dest_unit, dest_unit);
+ WWORD(Pkt->dest_port, dest_port);
+ }
+ rio_dprintk (RIO_DEBUG_TABLE,
+ "Port %d phb destination: Old %x:%x New %x:%x\n",
+ port, PortP->PhbP->destination & 0xff,
+ (PortP->PhbP->destination >> 8) & 0xff,
+ dest_unit, dest_port);
+ WWORD(PortP->PhbP->destination,
+ dest_unit + (dest_port << 8));
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, sem_flags);
+ }
+ }
+ rio_dprintk (RIO_DEBUG_TABLE, "Entry nulled.\n");
+ bzero((char *)HostMapP,sizeof(struct Map));
+ work_done++;
+ }
+ }
+ rio_spin_unlock_irqrestore(&HostP->HostLock, lock_flags);
+ }
+
+ /* XXXXX lock me up */
+ for ( entry=0; entry< TOTAL_MAP_ENTRIES; entry++ ) {
+ if ( p->RIOSavedTable[entry].RtaUniqueNum == MapP->RtaUniqueNum ) {
+ bzero((char *)&p->RIOSavedTable[entry],sizeof(struct Map));
+ work_done++;
+ }
+ if ( p->RIOConnectTable[entry].RtaUniqueNum == MapP->RtaUniqueNum ) {
+ bzero((char *)&p->RIOConnectTable[entry],sizeof(struct Map));
+ work_done++;
+ }
+ }
+ if ( work_done )
+ return 0;
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Couldn't find entry to be deleted\n");
+ p->RIOError.Error = COULDNT_FIND_ENTRY;
+ return -ENXIO;
+}
+
+int RIOAssignRta( struct rio_info *p, struct Map *MapP )
+{
+ int host;
+ struct Map *HostMapP;
+ char *sptr;
+ int link;
+
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Assign entry on host %x, rta %x, ID %d, Sysport %d\n",
+ MapP->HostUniqueNum,MapP->RtaUniqueNum,
+ MapP->ID, (int)MapP->SysPort);
+
+ if ((MapP->ID != (ushort)-1) &&
+ ((int)MapP->ID < (int)1 || (int)MapP->ID > MAX_RUP ))
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Bad ID in map entry!\n");
+ p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ if (MapP->RtaUniqueNum == 0)
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Rta Unique number zero!\n");
+ p->RIOError.Error = RTA_UNIQUE_NUMBER_ZERO;
+ return -EINVAL;
+ }
+ if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort % PORTS_PER_RTA) )
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Port %d not multiple of %d!\n",(int)MapP->SysPort,PORTS_PER_RTA);
+ p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort >= RIO_PORTS) )
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Port %d not valid!\n",(int)MapP->SysPort);
+ p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ /*
+ ** Copy the name across to the map entry.
+ */
+ MapP->Name[MAX_NAME_LEN-1] = '\0';
+ sptr = MapP->Name;
+ while ( *sptr )
+ {
+ if ( *sptr<' ' || *sptr>'~' )
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Name entry contains non-printing characters!\n");
+ p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+ return -EINVAL;
+ }
+ sptr++;
+ }
+
+ for ( host=0; host < p->RIONumHosts; host++ )
+ {
+ if ( MapP->HostUniqueNum == p->RIOHosts[host].UniqueNum )
+ {
+ if ( (p->RIOHosts[host].Flags & RUN_STATE) != RC_RUNNING )
+ {
+ p->RIOError.Error = HOST_NOT_RUNNING;
+ return -ENXIO;
+ }
+
+ /*
+ ** Now we have a host we need to allocate an ID
+ ** if the entry does not already have one.
+ */
+ if (MapP->ID == (ushort)-1)
+ {
+ int nNewID;
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Attempting to get a new ID for rta \"%s\"\n",
+ MapP->Name);
+ /*
+ ** The idea here is to allow RTA's to be assigned
+ ** before they actually appear on the network.
+ ** This allows the addition of RTA's without having
+ ** to plug them in.
+ ** What we do is:
+ ** - Find a free ID and allocate it to the RTA.
+ ** - If this map entry is the second half of a
+ ** 16 port entry then find the other half and
+ ** make sure the 2 cross reference each other.
+ */
+ if (RIOFindFreeID(p, &p->RIOHosts[host], &nNewID, NULL) != 0)
+ {
+ p->RIOError.Error = COULDNT_FIND_ENTRY;
+ return -EBUSY;
+ }
+ MapP->ID = (ushort)nNewID + 1;
+ rio_dprintk (RIO_DEBUG_TABLE, "Allocated ID %d for this new RTA.\n", MapP->ID);
+ HostMapP = &p->RIOHosts[host].Mapping[nNewID];
+ HostMapP->RtaUniqueNum = MapP->RtaUniqueNum;
+ HostMapP->HostUniqueNum = MapP->HostUniqueNum;
+ HostMapP->ID = MapP->ID;
+ for (link = 0; link < LINKS_PER_UNIT; link++)
+ {
+ HostMapP->Topology[link].Unit = ROUTE_DISCONNECT;
+ HostMapP->Topology[link].Link = NO_LINK;
+ }
+ if (MapP->Flags & RTA16_SECOND_SLOT)
+ {
+ int unit;
+
+ for (unit = 0; unit < MAX_RUP; unit++)
+ if (p->RIOHosts[host].Mapping[unit].RtaUniqueNum ==
+ MapP->RtaUniqueNum)
+ break;
+ if (unit == MAX_RUP)
+ {
+ p->RIOError.Error = COULDNT_FIND_ENTRY;
+ return -EBUSY;
+ }
+ HostMapP->Flags |= RTA16_SECOND_SLOT;
+ HostMapP->ID2 = MapP->ID2 = p->RIOHosts[host].Mapping[unit].ID;
+ p->RIOHosts[host].Mapping[unit].ID2 = MapP->ID;
+ rio_dprintk (RIO_DEBUG_TABLE, "Cross referenced id %d to ID %d.\n",
+ MapP->ID,
+ p->RIOHosts[host].Mapping[unit].ID);
+ }
+ }
+
+ HostMapP = &p->RIOHosts[host].Mapping[MapP->ID-1];
+
+ if ( HostMapP->Flags & SLOT_IN_USE )
+ {
+ rio_dprintk (RIO_DEBUG_TABLE, "Map table slot for ID %d is already in use.\n", MapP->ID);
+ p->RIOError.Error = ID_ALREADY_IN_USE;
+ return -EBUSY;
+ }
+
+ /*
+ ** Assign the sys ports and the name, and mark the slot as
+ ** being in use.
+ */
+ HostMapP->SysPort = MapP->SysPort;
+ if ((MapP->Flags & RTA16_SECOND_SLOT) == 0)
+ CCOPY( MapP->Name, HostMapP->Name, MAX_NAME_LEN );
+ HostMapP->Flags = SLOT_IN_USE | RTA_BOOTED;
+#if NEED_TO_FIX
+ RIO_SV_BROADCAST(p->RIOHosts[host].svFlags[MapP->ID-1]);
+#endif
+ if (MapP->Flags & RTA16_SECOND_SLOT)
+ HostMapP->Flags |= RTA16_SECOND_SLOT;
+
+ RIOReMapPorts( p, &p->RIOHosts[host], HostMapP );
+ /*
+ ** Adjust 2nd block of 8 phbs
+ */
+ if (MapP->Flags & RTA16_SECOND_SLOT)
+ RIOFixPhbs(p, &p->RIOHosts[host], HostMapP->ID - 1);
+
+ if ( HostMapP->SysPort != NO_PORT )
+ {
+ if ( HostMapP->SysPort < p->RIOFirstPortsBooted )
+ p->RIOFirstPortsBooted = HostMapP->SysPort;
+ if ( HostMapP->SysPort > p->RIOLastPortsBooted )
+ p->RIOLastPortsBooted = HostMapP->SysPort;
+ }
+ if (MapP->Flags & RTA16_SECOND_SLOT)
+ rio_dprintk (RIO_DEBUG_TABLE, "Second map of RTA %s added to configuration\n",
+ p->RIOHosts[host].Mapping[MapP->ID2 - 1].Name);
+ else
+ rio_dprintk (RIO_DEBUG_TABLE, "RTA %s added to configuration\n", MapP->Name);
+ return 0;
+ }
+ }
+ p->RIOError.Error = UNKNOWN_HOST_NUMBER;
+ rio_dprintk (RIO_DEBUG_TABLE, "Unknown host %x\n", MapP->HostUniqueNum);
+ return -ENXIO;
+}
+
+
+int
+RIOReMapPorts(p, HostP, HostMapP)
+struct rio_info * p;
+struct Host *HostP;
+struct Map *HostMapP;
+{
+ register struct Port *PortP;
+ uint SubEnt;
+ uint HostPort;
+ uint SysPort;
+ ushort RtaType;
+ unsigned long flags;
+
+#ifdef CHECK
+ CheckHostP( HostP );
+ CheckHostMapP( HostMapP );
+#endif
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Mapping sysport %d to id %d\n", (int)HostMapP->SysPort, HostMapP->ID);
+
+ /*
+ ** We need to tell the UnixRups which sysport the rup corresponds to
+ */
+ HostP->UnixRups[HostMapP->ID-1].BaseSysPort = HostMapP->SysPort;
+
+ if ( HostMapP->SysPort == NO_PORT )
+ return(0);
+
+ RtaType = GetUnitType(HostMapP->RtaUniqueNum);
+ rio_dprintk (RIO_DEBUG_TABLE, "Mapping sysport %d-%d\n",
+ (int)HostMapP->SysPort, (int)HostMapP->SysPort+PORTS_PER_RTA-1);
+
+ /*
+ ** now map each of its eight ports
+ */
+ for ( SubEnt=0; SubEnt<PORTS_PER_RTA; SubEnt++) {
+ rio_dprintk (RIO_DEBUG_TABLE, "subent = %d, HostMapP->SysPort = %d\n",
+ SubEnt, (int)HostMapP->SysPort);
+ SysPort = HostMapP->SysPort+SubEnt; /* portnumber within system */
+ /* portnumber on host */
+
+ HostPort = (HostMapP->ID-1)*PORTS_PER_RTA+SubEnt;
+
+ rio_dprintk (RIO_DEBUG_TABLE, "c1 p = %p, p->rioPortp = %p\n", p, p->RIOPortp);
+ PortP = p->RIOPortp[SysPort];
+#if 0
+ PortP->TtyP = &p->channel[SysPort];
+#endif
+ rio_dprintk (RIO_DEBUG_TABLE, "Map port\n");
+
+ /*
+ ** Point at all the real neat data structures
+ */
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ PortP->HostP = HostP;
+ PortP->Caddr = HostP->Caddr;
+
+ /*
+ ** The PhbP cannot be filled in yet
+ ** unless the host has been booted
+ */
+ if ((HostP->Flags & RUN_STATE) == RC_RUNNING) {
+ struct PHB *PhbP = PortP->PhbP = &HostP->PhbP[HostPort];
+ PortP->TxAdd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_add));
+ PortP->TxStart =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_start));
+ PortP->TxEnd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_end));
+ PortP->RxRemove=(WORD *)RIO_PTR(HostP->Caddr,
+ RWORD(PhbP->rx_remove));
+ PortP->RxStart =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->rx_start));
+ PortP->RxEnd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->rx_end));
+ }
+ else
+ PortP->PhbP = NULL;
+
+ /*
+ ** port related flags
+ */
+ PortP->HostPort = HostPort;
+ /*
+ ** For each part of a 16 port RTA, RupNum is ID - 1.
+ */
+ PortP->RupNum = HostMapP->ID - 1;
+ if (HostMapP->Flags & RTA16_SECOND_SLOT) {
+ PortP->ID2 = HostMapP->ID2 - 1;
+ PortP->SecondBlock = TRUE;
+ }
+ else {
+ PortP->ID2 = 0;
+ PortP->SecondBlock = FALSE;
+ }
+ PortP->RtaUniqueNum = HostMapP->RtaUniqueNum;
+
+ /*
+ ** If the port was already mapped then thats all we need to do.
+ */
+ if (PortP->Mapped) {
+ rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+ continue;
+ }
+ else HostMapP->Flags &= ~RTA_NEWBOOT;
+
+ PortP->State = 0;
+ PortP->Config = 0;
+ /*
+ ** Check out the module type - if it is special (read only etc.)
+ ** then we need to set flags in the PortP->Config.
+ ** Note: For 16 port RTA, all ports are of the same type.
+ */
+ if (RtaType == TYPE_RTA16) {
+ PortP->Config |= p->RIOModuleTypes[HostP->UnixRups
+ [HostMapP->ID-1].ModTypes].Flags[SubEnt % PORTS_PER_MODULE];
+ } else {
+ if ( SubEnt < PORTS_PER_MODULE )
+ PortP->Config |= p->RIOModuleTypes[LONYBLE(HostP->UnixRups
+ [HostMapP->ID-1].ModTypes)].Flags[SubEnt % PORTS_PER_MODULE];
+ else
+ PortP->Config |= p->RIOModuleTypes[HINYBLE(HostP->UnixRups
+ [HostMapP->ID-1].ModTypes)].Flags[SubEnt % PORTS_PER_MODULE];
+ }
+
+ /*
+ ** more port related flags
+ */
+ PortP->PortState = 0;
+ PortP->ModemLines = 0;
+ PortP->ModemState = 0;
+ PortP->CookMode = COOK_WELL;
+ PortP->ParamSem = 0;
+ PortP->FlushCmdBodge= 0;
+ PortP->WflushFlag = 0;
+ PortP->MagicFlags = 0;
+ PortP->Lock = 0;
+ PortP->Store = 0;
+ PortP->FirstOpen = 1;
+
+ /*
+ ** Buffers 'n things
+ */
+ PortP->RxDataStart = 0;
+ PortP->Cor2Copy = 0;
+ PortP->Name = &HostMapP->Name[0];
+#ifdef STATS
+ bzero( (caddr_t)&PortP->Stat, sizeof(struct RIOStats) );
+#endif
+ PortP->statsGather = 0;
+ PortP->txchars = 0;
+ PortP->rxchars = 0;
+ PortP->opens = 0;
+ PortP->closes = 0;
+ PortP->ioctls = 0;
+ if ( PortP->TxRingBuffer )
+ bzero( PortP->TxRingBuffer, p->RIOBufferSize );
+ else if ( p->RIOBufferSize ) {
+ PortP->TxRingBuffer = sysbrk(p->RIOBufferSize);
+ bzero( PortP->TxRingBuffer, p->RIOBufferSize );
+ }
+ PortP->TxBufferOut = 0;
+ PortP->TxBufferIn = 0;
+ PortP->Debug = 0;
+ /*
+ ** LastRxTgl stores the state of the rx toggle bit for this
+ ** port, to be compared with the state of the next pkt received.
+ ** If the same, we have received the same rx pkt from the RTA
+ ** twice. Initialise to a value not equal to PHB_RX_TGL or 0.
+ */
+ PortP->LastRxTgl = ~(uchar)PHB_RX_TGL;
+
+ /*
+ ** and mark the port as usable
+ */
+ PortP->Mapped = 1;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ }
+ if ( HostMapP->SysPort < p->RIOFirstPortsMapped )
+ p->RIOFirstPortsMapped = HostMapP->SysPort;
+ if ( HostMapP->SysPort > p->RIOLastPortsMapped )
+ p->RIOLastPortsMapped = HostMapP->SysPort;
+
+ return 0;
+}
+
+int
+RIOChangeName(p, MapP)
+struct rio_info *p;
+struct Map* MapP;
+{
+ int host;
+ struct Map *HostMapP;
+ char *sptr;
+
+ rio_dprintk (RIO_DEBUG_TABLE, "Change name entry on host %x, rta %x, ID %d, Sysport %d\n",
+ MapP->HostUniqueNum,MapP->RtaUniqueNum,
+ MapP->ID, (int)MapP->SysPort);
+
+ if ( MapP->ID > MAX_RUP ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Bad ID in map entry!\n");
+ p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ MapP->Name[MAX_NAME_LEN-1] = '\0';
+ sptr = MapP->Name;
+
+ while ( *sptr ) {
+ if ( *sptr<' ' || *sptr>'~' ) {
+ rio_dprintk (RIO_DEBUG_TABLE, "Name entry contains non-printing characters!\n");
+ p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+ return -EINVAL;
+ }
+ sptr++;
+ }
+
+ for ( host=0; host < p->RIONumHosts; host++ ) {
+ if ( MapP->HostUniqueNum == p->RIOHosts[host].UniqueNum ) {
+ if ( (p->RIOHosts[host].Flags & RUN_STATE) != RC_RUNNING ) {
+ p->RIOError.Error = HOST_NOT_RUNNING;
+ return -ENXIO;
+ }
+ if ( MapP->ID==0 ) {
+ CCOPY( MapP->Name, p->RIOHosts[host].Name, MAX_NAME_LEN );
+ return 0;
+ }
+
+ HostMapP = &p->RIOHosts[host].Mapping[MapP->ID-1];
+
+ if ( HostMapP->RtaUniqueNum != MapP->RtaUniqueNum ) {
+ p->RIOError.Error = RTA_NUMBER_WRONG;
+ return -ENXIO;
+ }
+ CCOPY( MapP->Name, HostMapP->Name, MAX_NAME_LEN );
+ return 0;
+ }
+ }
+ p->RIOError.Error = UNKNOWN_HOST_NUMBER;
+ rio_dprintk (RIO_DEBUG_TABLE, "Unknown host %x\n", MapP->HostUniqueNum);
+ return -ENXIO;
+}
diff --git a/drivers/char/rio/riotime.h b/drivers/char/rio/riotime.h
new file mode 100644
index 000000000000..66d52bc0549b
--- /dev/null
+++ b/drivers/char/rio/riotime.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+ ******* *******
+ ******* T I M E
+ ******* *******
+ ****************************************************************************
+
+ Author : Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _riotime_h
+#define _riotime_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_riotime_h_sccs = "@(#)riotime.h 1.1" ;
+#endif
+#endif
+
+#define TWO_POWER_FIFTEEN (ushort)32768
+#define RioTime() riotime
+#define RioTimeAfter(time1,time2) ((ushort)time1 - (ushort)time2) < TWO_POWER_FIFTEEN
+#define RioTimePlus(time1,time2) ((ushort)time1 + (ushort)time2)
+
+/**************************************
+ * Convert a RIO tick (1/10th second)
+ * into transputer low priority ticks
+ *************************************/
+#define RioTimeToLow(time) (time*(100000 / 64))
+#define RioLowToTime(time) ((time*64)/100000)
+
+#define RIOTENTHSECOND (ushort)1
+#define RIOSECOND (ushort)(RIOTENTHSECOND * 10)
+#endif
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/riotty.c b/drivers/char/rio/riotty.c
new file mode 100644
index 000000000000..db655002671f
--- /dev/null
+++ b/drivers/char/rio/riotty.c
@@ -0,0 +1,1376 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riotty.c
+** SID : 1.3
+** Last Modified : 11/6/98 10:33:47
+** Retrieved : 11/6/98 10:33:50
+**
+** ident @(#)riotty.c 1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riotty_c_sccs_ = "@(#)riotty.c 1.3";
+#endif
+
+
+#define __EXPLICIT_DEF_H__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/string.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+#if 0
+static void ttyseth_pv(struct Port *, struct ttystatics *,
+ struct termios *sg, int);
+#endif
+
+static void RIOClearUp(struct Port *PortP);
+int RIOShortCommand(struct rio_info *p, struct Port *PortP,
+ int command, int len, int arg);
+
+#if 0
+static int RIOCookMode(struct ttystatics *);
+#endif
+
+extern int conv_vb[]; /* now defined in ttymgr.c */
+extern int conv_bv[]; /* now defined in ttymgr.c */
+
+/*
+** 16.09.1998 ARG - Fix to build riotty.k.o for Modular Kernel Support
+**
+** ep.def.h is necessary for Modular Kernel Support
+** DO NOT place any kernel 'extern's after this line
+** or this source file will not build riotty.k.o
+*/
+#ifdef uLYNX
+#include <ep.def.h>
+#endif
+
+#ifdef NEED_THIS2
+static struct old_sgttyb
+default_sg =
+{
+ B19200, B19200, /* input and output speed */
+ 'H' - '@', /* erase char */
+ -1, /* 2nd erase char */
+ 'U' - '@', /* kill char */
+ ECHO | CRMOD, /* mode */
+ 'C' - '@', /* interrupt character */
+ '\\' - '@', /* quit char */
+ 'Q' - '@', /* start char */
+ 'S' - '@', /* stop char */
+ 'D' - '@', /* EOF */
+ -1, /* brk */
+ (LCRTBS | LCRTERA | LCRTKIL | LCTLECH), /* local mode word */
+ 'Z' - '@', /* process stop */
+ 'Y' - '@', /* delayed stop */
+ 'R' - '@', /* reprint line */
+ 'O' - '@', /* flush output */
+ 'W' - '@', /* word erase */
+ 'V' - '@' /* literal next char */
+};
+#endif
+
+
+extern struct rio_info *p;
+
+
+int
+riotopen(struct tty_struct * tty, struct file * filp)
+{
+ register uint SysPort;
+ int Modem;
+ int repeat_this = 250;
+ struct Port *PortP; /* pointer to the port structure */
+ unsigned long flags;
+ int retval = 0;
+
+ func_enter ();
+
+ /* Make sure driver_data is NULL in case the rio isn't booted jet. Else gs_close
+ is going to oops.
+ */
+ tty->driver_data = NULL;
+
+ SysPort = rio_minor(tty);
+ Modem = rio_ismodem(tty);
+
+ if ( p->RIOFailed ) {
+ rio_dprintk (RIO_DEBUG_TTY, "System initialisation failed\n");
+ pseterr(ENXIO);
+ func_exit ();
+ return -ENXIO;
+ }
+
+ rio_dprintk (RIO_DEBUG_TTY, "port open SysPort %d (%s) (mapped:%d)\n",
+ SysPort, Modem ? "Modem" : "tty",
+ p->RIOPortp[SysPort]->Mapped);
+
+ /*
+ ** Validate that we have received a legitimate request.
+ ** Currently, just check that we are opening a port on
+ ** a host card that actually exists, and that the port
+ ** has been mapped onto a host.
+ */
+ if (SysPort >= RIO_PORTS) { /* out of range ? */
+ rio_dprintk (RIO_DEBUG_TTY, "Illegal port number %d\n",SysPort);
+ pseterr(ENXIO);
+ func_exit();
+ return -ENXIO;
+ }
+
+ /*
+ ** Grab pointer to the port stucture
+ */
+ PortP = p->RIOPortp[SysPort]; /* Get control struc */
+ rio_dprintk (RIO_DEBUG_TTY, "PortP: %p\n", PortP);
+ if ( !PortP->Mapped ) { /* we aren't mapped yet! */
+ /*
+ ** The system doesn't know which RTA this port
+ ** corresponds to.
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "port not mapped into system\n");
+ func_exit ();
+ pseterr(ENXIO);
+ return -ENXIO;
+ }
+
+ tty->driver_data = PortP;
+
+ PortP->gs.tty = tty;
+ PortP->gs.count++;
+
+ rio_dprintk (RIO_DEBUG_TTY, "%d bytes in tx buffer\n",
+ PortP->gs.xmit_cnt);
+
+ retval = gs_init_port (&PortP->gs);
+ if (retval) {
+ PortP->gs.count--;
+ return -ENXIO;
+ }
+ /*
+ ** If the host hasn't been booted yet, then
+ ** fail
+ */
+ if ( (PortP->HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Host not running\n");
+ pseterr(ENXIO);
+ func_exit ();
+ return -ENXIO;
+ }
+
+ /*
+ ** If the RTA has not booted yet and the user has choosen to block
+ ** until the RTA is present then we must spin here waiting for
+ ** the RTA to boot.
+ */
+#if 0
+ if (!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED)) {
+ if (PortP->WaitUntilBooted) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot\n");
+ do {
+ if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+ func_exit ();
+ return -EINTR;
+ }
+ if (repeat_this -- <= 0) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot timeout\n");
+ RIOPreemptiveCmd(p, PortP, FCLOSE );
+ pseterr(EINTR);
+ func_exit ();
+ return -EIO;
+ }
+ } while(!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED));
+ rio_dprintk (RIO_DEBUG_TTY, "RTA has been booted\n");
+ } else {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA never booted\n");
+ pseterr(ENXIO);
+ func_exit ();
+ return 0;
+ }
+ }
+#else
+ /* I find the above code a bit hairy. I find the below code
+ easier to read and shorter. Now, if it works too that would
+ be great... -- REW
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "Checking if RTA has booted... \n");
+ while (!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED)) {
+ if (!PortP->WaitUntilBooted) {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA never booted\n");
+ func_exit ();
+ return -ENXIO;
+ }
+
+ /* Under Linux you'd normally use a wait instead of this
+ busy-waiting. I'll stick with the old implementation for
+ now. --REW
+ */
+ if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA_wait_for_boot: EINTR in delay \n");
+ func_exit ();
+ return -EINTR;
+ }
+ if (repeat_this -- <= 0) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot timeout\n");
+ func_exit ();
+ return -EIO;
+ }
+ }
+ rio_dprintk (RIO_DEBUG_TTY, "RTA has been booted\n");
+#endif
+#if 0
+ tp = PortP->TtyP; /* get tty struct */
+#endif
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ if ( p->RIOHalted ) {
+ goto bombout;
+ }
+#if 0
+ retval = gs_init_port(&PortP->gs);
+ if (retval){
+ func_exit ();
+ return retval;
+ }
+#endif
+
+ /*
+ ** If the port is in the final throws of being closed,
+ ** we should wait here (politely), waiting
+ ** for it to finish, so that it doesn't close us!
+ */
+ while ( (PortP->State & RIO_CLOSING) && !p->RIOHalted ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for RIO_CLOSING to go away\n");
+ if (repeat_this -- <= 0) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for not idle closed broken by signal\n");
+ RIOPreemptiveCmd(p, PortP, FCLOSE );
+ retval = -EINTR;
+ goto bombout;
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ retval = -EINTR;
+ goto bombout;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+
+ if ( !PortP->Mapped ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Port unmapped while closing!\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ retval = -ENXIO;
+ func_exit ();
+ return retval;
+ }
+
+ if ( p->RIOHalted ) {
+ goto bombout;
+ }
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** RIO has it's own CTSFLOW and RTSFLOW flags in 'Config' in the port structure,
+** we need to make sure that the flags are clear when the port is opened.
+*/
+ /* Uh? Suppose I turn these on and then another process opens
+ the port again? The flags get cleared! Not good. -- REW */
+ if ( !(PortP->State & (RIO_LOPEN | RIO_MOPEN)) ) {
+ PortP->Config &= ~(RIO_CTSFLOW|RIO_RTSFLOW);
+ }
+
+ if (!(PortP->firstOpen)) { /* First time ? */
+ rio_dprintk (RIO_DEBUG_TTY, "First open for this port\n");
+
+
+ PortP->firstOpen++;
+ PortP->CookMode = 0; /* XXX RIOCookMode(tp); */
+ PortP->InUse = NOT_INUSE;
+
+ /* Tentative fix for bug PR27. Didn't work. */
+ /* PortP->gs.xmit_cnt = 0; */
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+#ifdef NEED_THIS
+ ttyseth(PortP, tp, (struct old_sgttyb *)&default_sg);
+#endif
+
+ /* Someone explain to me why this delay/config is
+ here. If I read the docs correctly the "open"
+ command piggybacks the parameters immediately.
+ -- REW */
+ RIOParam(PortP,OPEN,Modem,OK_TO_SLEEP); /* Open the port */
+#if 0
+ /* This delay of 1 second was annoying. I removed it. -- REW */
+ RIODelay(PortP, HUNDRED_MS*10);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP); /* Config the port */
+#endif
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ /*
+ ** wait for the port to be not closed.
+ */
+ while ( !(PortP->PortState & PORT_ISOPEN) && !p->RIOHalted ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for PORT_ISOPEN-currently %x\n",PortP->PortState);
+/*
+** 15.10.1998 ARG - ESIL 0759
+** (Part) fix for port being trashed when opened whilst RTA "disconnected"
+** Take out the limited wait - now wait for ever or until user
+** bangs us out.
+**
+ if (repeat_this -- <= 0) {
+ rio_dprint(RIO_DEBUG_TTY, ("Waiting for open to finish timed out.\n"));
+ RIOPreemptiveCmd(p, PortP, FCLOSE );
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return -EINTR;
+ }
+**
+*/
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for open to finish broken by signal\n");
+ RIOPreemptiveCmd(p, PortP, FCLOSE );
+ func_exit ();
+ return -EINTR;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+
+ if ( p->RIOHalted ) {
+ retval = -EIO;
+bombout:
+ /* RIOClearUp( PortP ); */
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return retval;
+ }
+ rio_dprintk (RIO_DEBUG_TTY, "PORT_ISOPEN found\n");
+ }
+
+#ifdef MODEM_SUPPORT
+ if (Modem) {
+ rio_dprintk (RIO_DEBUG_TTY, "Modem - test for carrier\n");
+ /*
+ ** ACTION
+ ** insert test for carrier here. -- ???
+ ** I already see that test here. What's the deal? -- REW
+ */
+ if ((PortP->gs.tty->termios->c_cflag & CLOCAL) || (PortP->ModemState & MSVR1_CD))
+ {
+ rio_dprintk (RIO_DEBUG_TTY, "open(%d) Modem carr on\n", SysPort);
+ /*
+ tp->tm.c_state |= CARR_ON;
+ wakeup((caddr_t) &tp->tm.c_canq);
+ */
+ PortP->State |= RIO_CARR_ON;
+ wake_up_interruptible (&PortP->gs.open_wait);
+ }
+ else /* no carrier - wait for DCD */
+ {
+ /*
+ while (!(PortP->gs.tty->termios->c_state & CARR_ON) &&
+ !(filp->f_flags & O_NONBLOCK) && !p->RIOHalted )
+ */
+ while (!(PortP->State & RIO_CARR_ON) &&
+ !(filp->f_flags & O_NONBLOCK) && !p->RIOHalted ) {
+
+ rio_dprintk (RIO_DEBUG_TTY, "open(%d) sleeping for carr on\n",SysPort);
+ /*
+ PortP->gs.tty->termios->c_state |= WOPEN;
+ */
+ PortP->State |= RIO_WOPEN;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (RIODelay (PortP, HUNDRED_MS) == RIO_FAIL)
+#if 0
+ if ( sleep((caddr_t)&tp->tm.c_canqo, TTIPRI|PCATCH))
+#endif
+ {
+ /*
+ ** ACTION: verify that this is a good thing
+ ** to do here. -- ???
+ ** I think it's OK. -- REW
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "open(%d) sleeping for carr broken by signal\n",
+ SysPort);
+ RIOPreemptiveCmd( p, PortP, FCLOSE );
+ /*
+ tp->tm.c_state &= ~WOPEN;
+ */
+ PortP->State &= ~RIO_WOPEN;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ func_exit ();
+ return -EINTR;
+ }
+ }
+ PortP->State &= ~RIO_WOPEN;
+ }
+ if ( p->RIOHalted )
+ goto bombout;
+ rio_dprintk (RIO_DEBUG_TTY, "Setting RIO_MOPEN\n");
+ PortP->State |= RIO_MOPEN;
+ }
+ else
+#endif
+ {
+ /*
+ ** ACTION
+ ** Direct line open - force carrier (will probably mean
+ ** that sleeping Modem line fubar)
+ */
+ PortP->State |= RIO_LOPEN;
+ }
+
+ if ( p->RIOHalted ) {
+ goto bombout;
+ }
+
+ rio_dprintk (RIO_DEBUG_TTY, "high level open done\n");
+
+#ifdef STATS
+ PortP->Stat.OpenCnt++;
+#endif
+ /*
+ ** Count opens for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->opens++;
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ rio_dprintk (RIO_DEBUG_TTY, "Returning from open\n");
+ func_exit ();
+ return 0;
+}
+
+/*
+** RIOClose the port.
+** The operating system thinks that this is last close for the device.
+** As there are two interfaces to the port (Modem and tty), we need to
+** check that both are closed before we close the device.
+*/
+int
+riotclose(void *ptr)
+{
+#if 0
+ register uint SysPort = dev;
+ struct ttystatics *tp; /* pointer to our ttystruct */
+#endif
+ struct Port *PortP =ptr; /* pointer to the port structure */
+ int deleted = 0;
+ int try = -1; /* Disable the timeouts by setting them to -1 */
+ int repeat_this = -1; /* Congrats to those having 15 years of
+ uptime! (You get to break the driver.) */
+ long end_time;
+ struct tty_struct * tty;
+ unsigned long flags;
+ int Modem;
+ int rv =0;
+
+ rio_dprintk (RIO_DEBUG_TTY, "port close SysPort %d\n",PortP->PortNum);
+
+ /* PortP = p->RIOPortp[SysPort]; */
+ rio_dprintk (RIO_DEBUG_TTY, "Port is at address 0x%x\n",(int)PortP);
+ /* tp = PortP->TtyP;*/ /* Get tty */
+ tty = PortP->gs.tty;
+ rio_dprintk (RIO_DEBUG_TTY, "TTY is at address 0x%x\n",(int)tty);
+
+ if (PortP->gs.closing_wait)
+ end_time = jiffies + PortP->gs.closing_wait;
+ else
+ end_time = jiffies + MAX_SCHEDULE_TIMEOUT;
+
+ Modem = rio_ismodem(tty);
+#if 0
+ /* What F.CKING cache? Even then, a higly idle multiprocessor,
+ system with large caches this won't work . Better find out when
+ this doesn't work asap, and fix the cause. -- REW */
+
+ RIODelay(PortP, HUNDRED_MS*10); /* To flush the cache */
+#endif
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ /*
+ ** Setting this flag will make any process trying to open
+ ** this port block until we are complete closing it.
+ */
+ PortP->State |= RIO_CLOSING;
+
+ if ( (PortP->State & RIO_DELETED) ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Close on deleted RTA\n");
+ deleted = 1;
+ }
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ rv = -EIO;
+ goto close_end;
+ }
+
+ rio_dprintk (RIO_DEBUG_TTY, "Clear bits\n");
+ /*
+ ** clear the open bits for this device
+ */
+ PortP->State &= (Modem ? ~RIO_MOPEN : ~RIO_LOPEN);
+ PortP->State &= ~RIO_CARR_ON;
+ PortP->ModemState &= ~MSVR1_CD;
+ /*
+ ** If the device was open as both a Modem and a tty line
+ ** then we need to wimp out here, as the port has not really
+ ** been finally closed (gee, whizz!) The test here uses the
+ ** bit for the OTHER mode of operation, to see if THAT is
+ ** still active!
+ */
+ if ( (PortP->State & (RIO_LOPEN|RIO_MOPEN)) ) {
+ /*
+ ** The port is still open for the other task -
+ ** return, pretending that we are still active.
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "Channel %d still open !\n",PortP->PortNum);
+ PortP->State &= ~RIO_CLOSING;
+ if (PortP->firstOpen)
+ PortP->firstOpen--;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return -EIO;
+ }
+
+ rio_dprintk (RIO_DEBUG_TTY, "Closing down - everything must go!\n");
+
+ PortP->State &= ~RIO_DYNOROD;
+
+ /*
+ ** This is where we wait for the port
+ ** to drain down before closing. Bye-bye....
+ ** (We never meant to do this)
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "Timeout 1 starts\n");
+
+ if (!deleted)
+ while ( (PortP->InUse != NOT_INUSE) && !p->RIOHalted &&
+ (PortP->TxBufferIn != PortP->TxBufferOut) ) {
+ cprintf("Need to flush the ttyport\n");
+ if (repeat_this -- <= 0) {
+ rv = -EINTR;
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for not idle closed broken by signal\n");
+ RIOPreemptiveCmd(p, PortP, FCLOSE );
+ goto close_end;
+ }
+ rio_dprintk (RIO_DEBUG_TTY, "Calling timeout to flush in closing\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (RIODelay_ni(PortP, HUNDRED_MS*10) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+ rv = -EINTR;
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ goto close_end;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+
+ PortP->TxBufferIn = PortP->TxBufferOut = 0;
+ repeat_this = 0xff;
+
+ PortP->InUse = 0;
+ if ( (PortP->State & (RIO_LOPEN|RIO_MOPEN)) ) {
+ /*
+ ** The port has been re-opened for the other task -
+ ** return, pretending that we are still active.
+ */
+ rio_dprintk (RIO_DEBUG_TTY, "Channel %d re-open!\n", PortP->PortNum);
+ PortP->State &= ~RIO_CLOSING;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (PortP->firstOpen)
+ PortP->firstOpen--;
+ return -EIO;
+ }
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ goto close_end;
+ }
+
+
+
+ /* Can't call RIOShortCommand with the port locked. */
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+ if (RIOShortCommand(p, PortP, CLOSE, 1, 0) == RIO_FAIL) {
+ RIOPreemptiveCmd(p, PortP,FCLOSE);
+ goto close_end;
+ }
+
+ if (!deleted)
+ while (try && (PortP->PortState & PORT_ISOPEN)) {
+ try--;
+ if (time_after (jiffies, end_time)) {
+ rio_dprintk (RIO_DEBUG_TTY, "Run out of tries - force the bugger shut!\n" );
+ RIOPreemptiveCmd(p, PortP,FCLOSE);
+ break;
+ }
+ rio_dprintk (RIO_DEBUG_TTY, "Close: PortState:ISOPEN is %d\n",
+ PortP->PortState & PORT_ISOPEN);
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ goto close_end;
+ }
+ if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+ rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+ RIOPreemptiveCmd(p, PortP,FCLOSE);
+ break;
+ }
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ rio_dprintk (RIO_DEBUG_TTY, "Close: try was %d on completion\n", try );
+
+ /* RIOPreemptiveCmd(p, PortP, FCLOSE); */
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** RIO has it's own CTSFLOW and RTSFLOW flags in 'Config' in the port structure,** we need to make sure that the flags are clear when the port is opened.
+*/
+ PortP->Config &= ~(RIO_CTSFLOW|RIO_RTSFLOW);
+
+
+#ifdef STATS
+ PortP->Stat.CloseCnt++;
+#endif
+ /*
+ ** Count opens for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->closes++;
+
+close_end:
+ /* XXX: Why would a "DELETED" flag be reset here? I'd have
+ thought that a "deleted" flag means that the port was
+ permanently gone, but here we can make it reappear by it
+ being in close during the "deletion".
+ */
+ PortP->State &= ~(RIO_CLOSING|RIO_DELETED);
+ if (PortP->firstOpen)
+ PortP->firstOpen--;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ rio_dprintk (RIO_DEBUG_TTY, "Return from close\n");
+ return rv;
+}
+
+
+/*
+** decide if we need to use the line discipline.
+** This routine can return one of three values:
+** COOK_RAW if no processing has to be done by the line discipline or the card
+** COOK_WELL if the line discipline must be used to do the processing
+** COOK_MEDIUM if the card can do all the processing necessary.
+*/
+#if 0
+static int
+RIOCookMode(struct ttystatics *tp)
+{
+ /*
+ ** We can't handle tm.c_mstate != 0 on SCO
+ ** We can't handle mapping
+ ** We can't handle non-ttwrite line disc.
+ ** We can't handle lflag XCASE
+ ** We can handle oflag OPOST & (OCRNL, ONLCR, TAB3)
+ */
+
+#ifdef CHECK
+ CheckTtyP( tp );
+#endif
+ if (!(tp->tm.c_oflag & OPOST)) /* No post processing */
+ return COOK_RAW; /* Raw mode o/p */
+
+ if ( tp->tm.c_lflag & XCASE )
+ return COOK_WELL; /* Use line disc */
+
+ if (tp->tm.c_oflag & ~(OPOST | ONLCR | OCRNL | TAB3 ) )
+ return COOK_WELL; /* Use line disc for strange modes */
+
+ if ( tp->tm.c_oflag == OPOST ) /* If only OPOST is set, do RAW */
+ return COOK_RAW;
+
+ /*
+ ** So, we need to output process!
+ */
+ return COOK_MEDIUM;
+}
+#endif
+
+static void
+RIOClearUp(PortP)
+struct Port *PortP;
+{
+ rio_dprintk (RIO_DEBUG_TTY, "RIOHalted set\n");
+ PortP->Config = 0; /* Direct semaphore */
+ PortP->PortState = 0;
+ PortP->firstOpen = 0;
+ PortP->FlushCmdBodge = 0;
+ PortP->ModemState = PortP->CookMode = 0;
+ PortP->Mapped = 0;
+ PortP->WflushFlag = 0;
+ PortP->MagicFlags = 0;
+ PortP->RxDataStart = 0;
+ PortP->TxBufferIn = 0;
+ PortP->TxBufferOut = 0;
+}
+
+/*
+** Put a command onto a port.
+** The PortPointer, command, length and arg are passed.
+** The len is the length *inclusive* of the command byte,
+** and so for a command that takes no data, len==1.
+** The arg is a single byte, and is only used if len==2.
+** Other values of len aren't allowed, and will cause
+** a panic.
+*/
+int RIOShortCommand(struct rio_info *p, struct Port *PortP,
+ int command, int len, int arg)
+{
+ PKT *PacketP;
+ int retries = 20; /* at 10 per second -> 2 seconds */
+ unsigned long flags;
+
+ rio_dprintk (RIO_DEBUG_TTY, "entering shortcommand.\n");
+#ifdef CHECK
+ CheckPortP( PortP );
+ if ( len < 1 || len > 2 )
+ cprintf(("STUPID LENGTH %d\n",len));
+#endif
+
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Short command to deleted RTA ignored\n");
+ return RIO_FAIL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+ /*
+ ** If the port is in use for pre-emptive command, then wait for it to
+ ** be free again.
+ */
+ while ( (PortP->InUse != NOT_INUSE) && !p->RIOHalted ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting for not in use (%d)\n",
+ retries);
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (retries-- <= 0) {
+ return RIO_FAIL;
+ }
+ if (RIODelay_ni(PortP, HUNDRED_MS) == RIO_FAIL) {
+ return RIO_FAIL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+ if ( PortP->State & RIO_DELETED ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Short command to deleted RTA ignored\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return RIO_FAIL;
+ }
+
+ while ( !can_add_transmit(&PacketP,PortP) && !p->RIOHalted ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Waiting to add short command to queue (%d)\n", retries);
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ if (retries-- <= 0) {
+ rio_dprintk (RIO_DEBUG_TTY, "out of tries. Failing\n");
+ return RIO_FAIL;
+ }
+ if ( RIODelay_ni(PortP, HUNDRED_MS)==RIO_FAIL ) {
+ return RIO_FAIL;
+ }
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+
+ if ( p->RIOHalted ) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return RIO_FAIL;
+ }
+
+ /*
+ ** set the command byte and the argument byte
+ */
+ WBYTE(PacketP->data[0] , command);
+
+ if ( len==2 )
+ WBYTE(PacketP->data[1] , arg);
+
+ /*
+ ** set the length of the packet and set the command bit.
+ */
+ WBYTE(PacketP->len , PKT_CMD_BIT | len);
+
+ add_transmit(PortP);
+ /*
+ ** Count characters transmitted for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->txchars += len;
+
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return p->RIOHalted ? RIO_FAIL : ~RIO_FAIL;
+}
+
+
+#if 0
+/*
+** This is an ioctl interface. This is the twentieth century. You know what
+** its all about.
+*/
+int
+riotioctl(struct rio_info *p, struct tty_struct *tty, int cmd, caddr_t arg)
+{
+ register struct Port *PortP;
+ register struct ttystatics *tp;
+ int current;
+ int ParamSemIncremented = 0;
+ int old_oflag, old_cflag, old_iflag, changed, oldcook;
+ int i;
+ unsigned char sio_regs[5]; /* Here be magic */
+ short vpix_cflag;
+ short divisor;
+ int baud;
+ uint SysPort = rio_minor(tty);
+ int Modem = rio_ismodem(tty);
+ int ioctl_processed;
+
+ rio_dprintk (RIO_DEBUG_TTY, "port ioctl SysPort %d command 0x%x argument 0x%x %s\n",
+ SysPort, cmd, arg, Modem?"Modem":"tty") ;
+
+ if ( SysPort >= RIO_PORTS ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Bad port number %d\n", SysPort);
+ return -ENXIO;
+ }
+
+ PortP = p->RIOPortp[SysPort];
+ tp = PortP->TtyP;
+
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+#ifdef STATS
+ PortP->Stat.IoctlCnt++;
+#endif
+
+ if ( PortP->State & RIO_DELETED ) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return -EIO;
+ }
+
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return -EIO;
+ }
+
+ /*
+ ** Count ioctls for port statistics reporting
+ */
+ if (PortP->statsGather)
+ PortP->ioctls++;
+
+ /*
+ ** Specialix RIO Ioctl calls
+ */
+ switch (cmd) {
+
+ case TCRIOTRIAD:
+ if ( arg )
+ PortP->State |= RIO_TRIAD_MODE;
+ else
+ PortP->State &= ~RIO_TRIAD_MODE;
+ /*
+ ** Normally, when istrip is set on a port, a config is
+ ** sent to the RTA instructing the CD1400 to do the
+ ** stripping. In TRIAD mode, the interrupt receive routine
+ ** must do the stripping instead, since it has to detect
+ ** an 8 bit function key sequence. If istrip is set with
+ ** TRIAD mode on(off), and 8 bit data is being read by
+ ** the port, the user then turns TRIAD mode off(on), the RTA
+ ** must be reconfigured (not) to do the stripping.
+ ** Hence we call RIOParam here.
+ */
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ return 0;
+
+ case TCRIOTSTATE:
+ rio_dprintk (RIO_DEBUG_TTY, "tbusy/tstop monitoring %sabled\n",
+ arg ? "en" : "dis");
+ /* MonitorTstate = 0 ;*/
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP, CONFIG, Modem, OK_TO_SLEEP);
+ return 0;
+
+ case TCRIOSTATE: /* current state of Modem input pins */
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOSTATE\n");
+ if (RIOPreemptiveCmd(p, PortP, MGET) == RIO_FAIL)
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOSTATE command failed\n");
+ PortP->State |= RIO_BUSY;
+ current = PortP->ModemState;
+ if ( copyout((caddr_t)&current, (int)arg,
+ sizeof(current))==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Copyout failed\n");
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EFAULT);
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOMBIS: /* Set modem lines */
+ case TCRIOMBIC: /* Clear modem lines */
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOMBIS/TCRIOMBIC\n");
+ if (cmd == TCRIOMBIS) {
+ uint state;
+ state = (uint)arg;
+ PortP->ModemState |= (ushort)state;
+ PortP->ModemLines = (ulong) arg;
+ if (RIOPreemptiveCmd(p, PortP, MBIS) == RIO_FAIL)
+ rio_dprintk (RIO_DEBUG_TTY,
+ "TCRIOMBIS command failed\n");
+ }
+ else {
+ uint state;
+
+ state = (uint)arg;
+ PortP->ModemState &= ~(ushort)state;
+ PortP->ModemLines = (ulong) arg;
+ if (RIOPreemptiveCmd(p, PortP, MBIC) == RIO_FAIL)
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOMBIC command failed\n");
+ }
+ PortP->State |= RIO_BUSY;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOXPON: /* set Xprint ON string */
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPON\n");
+ if ( copyin((int)arg, (caddr_t)PortP->Xprint.XpOn,
+ MAX_XP_CTRL_LEN)==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Copyin failed\n");
+ PortP->Xprint.XpOn[0] = '\0';
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EFAULT);
+ }
+ PortP->Xprint.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+ PortP->Xprint.XpLen = strlen(PortP->Xprint.XpOn)+
+ strlen(PortP->Xprint.XpOff);
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOXPOFF: /* set Xprint OFF string */
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPOFF\n");
+ if ( copyin( (int)arg, (caddr_t)PortP->Xprint.XpOff,
+ MAX_XP_CTRL_LEN)==COPYFAIL ) {
+ rio_dprintk (RIO_DEBUG_TTY, "Copyin failed\n");
+ PortP->Xprint.XpOff[0] = '\0';
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EFAULT);
+ }
+ PortP->Xprint.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+ PortP->Xprint.XpLen = strlen(PortP->Xprint.XpOn)+
+ strlen(PortP->Xprint.XpOff);
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOXPCPS: /* set Xprint CPS string */
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPCPS\n");
+ if ( (uint)arg > p->RIOConf.MaxXpCps ||
+ (uint)arg < p->RIOConf.MinXpCps ) {
+ rio_dprintk (RIO_DEBUG_TTY, "%d CPS out of range\n",arg);
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EINVAL);
+ return 0;
+ }
+ PortP->Xprint.XpCps = (uint)arg;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOXPRINT:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPRINT\n");
+ if ( copyout((caddr_t)&PortP->Xprint, (int)arg,
+ sizeof(struct Xprint))==COPYFAIL ) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EFAULT);
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOIXANYON:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXANYON\n");
+ PortP->Config |= RIO_IXANY;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOIXANYOFF:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXANYOFF\n");
+ PortP->Config &= ~RIO_IXANY;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOIXONON:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXONON\n");
+ PortP->Config |= RIO_IXON;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+ case TCRIOIXONOFF:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXONOFF\n");
+ PortP->Config &= ~RIO_IXON;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** Added support for CTS and RTS flow control ioctls :
+*/
+ case TCRIOCTSFLOWEN:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOCTSFLOWEN\n");
+ PortP->Config |= RIO_CTSFLOW;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ return 0;
+
+ case TCRIOCTSFLOWDIS:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIOCTSFLOWDIS\n");
+ PortP->Config &= ~RIO_CTSFLOW;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ return 0;
+
+ case TCRIORTSFLOWEN:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIORTSFLOWEN\n");
+ PortP->Config |= RIO_RTSFLOW;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ return 0;
+
+ case TCRIORTSFLOWDIS:
+ rio_dprintk (RIO_DEBUG_TTY, "TCRIORTSFLOWDIS\n");
+ PortP->Config &= ~RIO_RTSFLOW;
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ return 0;
+
+/* end ESIL 0761 part fix */
+
+ }
+
+
+ /* Lynx IOCTLS */
+ switch (cmd) {
+ case TIOCSETP:
+ case TIOCSETN:
+ case OTIOCSETP:
+ case OTIOCSETN:
+ ioctl_processed++;
+ ttyseth(PortP, tp, (struct old_sgttyb *)arg);
+ break;
+ case TCSETA:
+ case TCSETAW:
+ case TCSETAF:
+ ioctl_processed++;
+ rio_dprintk (RIO_DEBUG_TTY, "NON POSIX ioctl\n");
+ ttyseth_pv(PortP, tp, (struct termios *)arg, 0);
+ break;
+ case TCSETAP: /* posix tcsetattr() */
+ case TCSETAWP: /* posix tcsetattr() */
+ case TCSETAFP: /* posix tcsetattr() */
+ rio_dprintk (RIO_DEBUG_TTY, "NON POSIX SYSV ioctl\n");
+ ttyseth_pv(PortP, tp, (struct termios *)arg, 1);
+ ioctl_processed++;
+ break;
+ }
+
+ /*
+ ** If its any of the commands that require the port to be in the
+ ** non-busy state wait until all output has drained
+ */
+ if (!ioctl_processed)
+ switch(cmd) {
+ case TCSETAW:
+ case TCSETAF:
+ case TCSETA:
+ case TCSBRK:
+#define OLD_POSIX ('x' << 8)
+#define OLD_POSIX_SETA (OLD_POSIX | 2)
+#define OLD_POSIX_SETAW (OLD_POSIX | 3)
+#define OLD_POSIX_SETAF (OLD_POSIX | 4)
+#define NEW_POSIX (('i' << 24) | ('X' << 16))
+#define NEW_POSIX_SETA (NEW_POSIX | 2)
+#define NEW_POSIX_SETAW (NEW_POSIX | 3)
+#define NEW_POSIX_SETAF (NEW_POSIX | 4)
+ case OLD_POSIX_SETA:
+ case OLD_POSIX_SETAW:
+ case OLD_POSIX_SETAF:
+ case NEW_POSIX_SETA:
+ case NEW_POSIX_SETAW:
+ case NEW_POSIX_SETAF:
+#ifdef TIOCSETP
+ case TIOCSETP:
+#endif
+ case TIOCSETD:
+ case TIOCSETN:
+ rio_dprintk (RIO_DEBUG_TTY, "wait for non-BUSY, semaphore set\n");
+ /*
+ ** Wait for drain here, at least as far as the double buffer
+ ** being empty.
+ */
+ /* XXX Does the above comment mean that this has
+ still to be implemented? -- REW */
+ /* XXX Is the locking OK together with locking
+ in txenable? (Deadlock?) -- REW */
+
+ RIOTxEnable((char *)PortP);
+ break;
+ default:
+ break;
+ }
+
+ old_cflag = tp->tm.c_cflag;
+ old_iflag = tp->tm.c_iflag;
+ old_oflag = tp->tm.c_oflag;
+ oldcook = PortP->CookMode;
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EIO);
+ return 0;
+ }
+
+ PortP->FlushCmdBodge = 0;
+
+ /*
+ ** If the port is locked, and it is reconfigured, we want
+ ** to restore the state of the tty structure so the change is NOT
+ ** made.
+ */
+ if (PortP->Lock) {
+ tp->tm.c_iflag = PortP->StoredTty.iflag;
+ tp->tm.c_oflag = PortP->StoredTty.oflag;
+ tp->tm.c_cflag = PortP->StoredTty.cflag;
+ tp->tm.c_lflag = PortP->StoredTty.lflag;
+ tp->tm.c_line = PortP->StoredTty.line;
+ for (i = 0; i < NCC + 1; i++)
+ tp->tm.c_cc[i] = PortP->StoredTty.cc[i];
+ }
+ else {
+ /*
+ ** If the port is set to store the parameters, and it is
+ ** reconfigured, we want to save the current tty struct so it
+ ** may be restored on the next open.
+ */
+ if (PortP->Store) {
+ PortP->StoredTty.iflag = tp->tm.c_iflag;
+ PortP->StoredTty.oflag = tp->tm.c_oflag;
+ PortP->StoredTty.cflag = tp->tm.c_cflag;
+ PortP->StoredTty.lflag = tp->tm.c_lflag;
+ PortP->StoredTty.line = tp->tm.c_line;
+ for (i = 0; i < NCC + 1; i++)
+ PortP->StoredTty.cc[i] = tp->tm.c_cc[i];
+ }
+ }
+
+ changed = (tp->tm.c_cflag != old_cflag) ||
+ (tp->tm.c_iflag != old_iflag) ||
+ (tp->tm.c_oflag != old_oflag);
+
+ PortP->CookMode = RIOCookMode(tp); /* Set new cooking mode */
+
+ rio_dprintk (RIO_DEBUG_TTY, "RIOIoctl changed %d newcook %d oldcook %d\n",
+ changed,PortP->CookMode,oldcook);
+
+#ifdef MODEM_SUPPORT
+ /*
+ ** kludge to force CARR_ON if CLOCAL set
+ */
+ if ((tp->tm.c_cflag & CLOCAL) || (PortP->ModemState & MSVR1_CD)) {
+ tp->tm.c_state |= CARR_ON;
+ wakeup ((caddr_t)&tp->tm.c_canq);
+ }
+#endif
+
+ if ( p->RIOHalted ) {
+ RIOClearUp( PortP );
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ pseterr(EIO);
+ return 0;
+ }
+ /*
+ ** Re-configure if modes or cooking have changed
+ */
+ if (changed || oldcook != PortP->CookMode || (ioctl_processed)) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ rio_dprintk (RIO_DEBUG_TTY, "Ioctl changing the PORT settings\n");
+ RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);
+ rio_spin_lock_irqsave(&PortP->portSem, flags);
+ }
+
+ if (p->RIOHalted) {
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ RIOClearUp( PortP );
+ pseterr(EIO);
+ return 0;
+ }
+ rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+ return 0;
+}
+
+/*
+ ttyseth -- set hardware dependent tty settings
+*/
+void
+ttyseth(PortP, s, sg)
+struct Port * PortP;
+struct ttystatics * s;
+struct old_sgttyb *sg;
+{
+ struct old_sgttyb * tsg;
+ struct termios *tp = &s->tm;
+
+ tsg = &s->sg;
+
+ if (sg->sg_flags & (EVENP|ODDP)) {
+ tp->c_cflag &= PARENB;
+ if (sg->sg_flags & EVENP) {
+ if (sg->sg_flags & ODDP) {
+ tp->c_cflag &= V_CS7;
+ tp->c_cflag &= ~PARENB;
+ }
+ else {
+ tp->c_cflag &= V_CS7;
+ tp->c_cflag &= PARENB;
+ tp->c_cflag &= PARODD;
+ }
+ }
+ else if (sg->sg_flags & ODDP) {
+ tp->c_cflag &= V_CS7;
+ tp->c_cflag &= PARENB;
+ tp->c_cflag &= PARODD;
+ }
+ else {
+ tp->c_cflag &= V_CS7;
+ tp->c_cflag &= PARENB;
+ }
+ }
+/*
+ * Use ispeed as the desired speed. Most implementations don't handle
+ * separate input and output speeds very well. If the RIO handles this,
+ * I will have to use separate sets of flags to store them in the
+ * Port structure.
+ */
+ if ( !sg->sg_ospeed )
+ sg->sg_ospeed = sg->sg_ispeed;
+ else
+ sg->sg_ispeed = sg->sg_ospeed;
+ if (sg->sg_ispeed > V_EXTB )
+ sg->sg_ispeed = V_EXTB;
+ if (sg->sg_ispeed < V_B0)
+ sg->sg_ispeed = V_B0;
+ *tsg = *sg;
+ tp->c_cflag = (tp->c_cflag & ~V_CBAUD) | conv_bv[(int)sg->sg_ispeed];
+}
+
+/*
+ ttyseth_pv -- set hardware dependent tty settings using either the
+ POSIX termios structure or the System V termio structure.
+ sysv = 0 => (POSIX): struct termios *sg
+ sysv != 0 => (System V): struct termio *sg
+*/
+static void
+ttyseth_pv(PortP, s, sg, sysv)
+struct Port *PortP;
+struct ttystatics *s;
+struct termios *sg;
+int sysv;
+{
+ int speed;
+ unsigned char csize;
+ unsigned char cread;
+ unsigned int lcr_flags;
+ int ps;
+
+ if (sysv) {
+ /* sg points to a System V termio structure */
+ csize = ((struct termio *)sg)->c_cflag & CSIZE;
+ cread = ((struct termio *)sg)->c_cflag & CREAD;
+ speed = conv_vb[((struct termio *)sg)->c_cflag & V_CBAUD];
+ }
+ else {
+ /* sg points to a POSIX termios structure */
+ csize = sg->c_cflag & CSIZE;
+ cread = sg->c_cflag & CREAD;
+ speed = conv_vb[sg->c_cflag & V_CBAUD];
+ }
+ if (s->sg.sg_ispeed != speed || s->sg.sg_ospeed != speed) {
+ s->sg.sg_ispeed = speed;
+ s->sg.sg_ospeed = speed;
+ s->tm.c_cflag = (s->tm.c_cflag & ~V_CBAUD) |
+ conv_bv[(int)s->sg.sg_ispeed];
+ }
+}
+#endif
diff --git a/drivers/char/rio/riotypes.h b/drivers/char/rio/riotypes.h
new file mode 100644
index 000000000000..1c7c42c638f0
--- /dev/null
+++ b/drivers/char/rio/riotypes.h
@@ -0,0 +1,135 @@
+/****************************************************************************
+ ******* *******
+ ******* R I O T Y P E S
+ ******* *******
+ ****************************************************************************
+
+ Author : Jon Brawn
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _riotypes_h
+#define _riotypes_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_riotypes_h_sccs = "@(#)riotypes.h 1.10"; */
+#endif
+#endif
+
+#ifdef INKERNEL
+
+#if !defined(MIPSAT)
+typedef unsigned short NUMBER_ptr;
+typedef unsigned short WORD_ptr;
+typedef unsigned short BYTE_ptr;
+typedef unsigned short char_ptr;
+typedef unsigned short Channel_ptr;
+typedef unsigned short FREE_LIST_ptr_ptr;
+typedef unsigned short FREE_LIST_ptr;
+typedef unsigned short LPB_ptr;
+typedef unsigned short Process_ptr;
+typedef unsigned short PHB_ptr;
+typedef unsigned short PKT_ptr;
+typedef unsigned short PKT_ptr_ptr;
+typedef unsigned short Q_BUF_ptr;
+typedef unsigned short Q_BUF_ptr_ptr;
+typedef unsigned short ROUTE_STR_ptr;
+typedef unsigned short RUP_ptr;
+typedef unsigned short short_ptr;
+typedef unsigned short u_short_ptr;
+typedef unsigned short ushort_ptr;
+#else
+/* MIPSAT types */
+typedef char RIO_POINTER[8];
+typedef RIO_POINTER NUMBER_ptr;
+typedef RIO_POINTER WORD_ptr;
+typedef RIO_POINTER BYTE_ptr;
+typedef RIO_POINTER char_ptr;
+typedef RIO_POINTER Channel_ptr;
+typedef RIO_POINTER FREE_LIST_ptr_ptr;
+typedef RIO_POINTER FREE_LIST_ptr;
+typedef RIO_POINTER LPB_ptr;
+typedef RIO_POINTER Process_ptr;
+typedef RIO_POINTER PHB_ptr;
+typedef RIO_POINTER PKT_ptr;
+typedef RIO_POINTER PKT_ptr_ptr;
+typedef RIO_POINTER Q_BUF_ptr;
+typedef RIO_POINTER Q_BUF_ptr_ptr;
+typedef RIO_POINTER ROUTE_STR_ptr;
+typedef RIO_POINTER RUP_ptr;
+typedef RIO_POINTER short_ptr;
+typedef RIO_POINTER u_short_ptr;
+typedef RIO_POINTER ushort_ptr;
+#endif
+
+#else /* not INKERNEL */
+typedef unsigned char BYTE;
+typedef unsigned short WORD;
+typedef unsigned long DWORD;
+typedef short NUMBER;
+typedef short *NUMBER_ptr;
+typedef unsigned short *WORD_ptr;
+typedef unsigned char *BYTE_ptr;
+typedef unsigned char uchar ;
+typedef unsigned short ushort ;
+typedef unsigned int uint ;
+typedef unsigned long ulong ;
+typedef unsigned char u_char ;
+typedef unsigned short u_short ;
+typedef unsigned int u_int ;
+typedef unsigned long u_long ;
+typedef unsigned short ERROR ;
+typedef unsigned long ID ;
+typedef char *char_ptr;
+typedef Channel *Channel_ptr;
+typedef struct FREE_LIST *FREE_LIST_ptr;
+typedef struct FREE_LIST **FREE_LIST_ptr_ptr;
+typedef struct LPB *LPB_ptr;
+typedef struct Process *Process_ptr;
+typedef struct PHB *PHB_ptr;
+typedef struct PKT *PKT_ptr;
+typedef struct PKT **PKT_ptr_ptr;
+typedef struct Q_BUF *Q_BUF_ptr;
+typedef struct Q_BUF **Q_BUF_ptr_ptr;
+typedef struct ROUTE_STR *ROUTE_STR_ptr;
+typedef struct RUP *RUP_ptr;
+typedef short *short_ptr;
+typedef u_short *u_short_ptr;
+typedef ushort *ushort_ptr;
+typedef struct PKT PKT;
+typedef struct LPB LPB;
+typedef struct RUP RUP;
+#endif
+
+
+#endif /* __riotypes__ */
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/riowinif.h b/drivers/char/rio/riowinif.h
new file mode 100644
index 000000000000..18a4f147edc2
--- /dev/null
+++ b/drivers/char/rio/riowinif.h
@@ -0,0 +1,1335 @@
+/************************************************************************/
+/* */
+/* Title : RIO Shared Memory Window Inteface */
+/* */
+/* Author : N.P.Vassallo */
+/* */
+/* Creation : 7th June 1999 */
+/* */
+/* Version : 1.0.0 */
+/* */
+/* Copyright : (c) Specialix International Ltd. 1999 *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * */
+/* Description : Prototypes, structures and definitions */
+/* describing RIO host card shared memory */
+/* window interface structures: */
+/* PARMMAP */
+/* RUP */
+/* PHB */
+/* LPB */
+/* PKT */
+/* */
+/************************************************************************/
+
+/* History...
+
+1.0.0 07/06/99 NPV Creation. (based on PARMMAP.H)
+
+*/
+
+#ifndef _riowinif_h /* If RIOWINDIF.H not already defined */
+#define _riowinif_h 1
+
+/*****************************************************************************
+******************************** *********************************
+******************************** General *********************************
+******************************** *********************************
+*****************************************************************************/
+
+#define TPNULL ((_u16)(0x8000))
+
+/*****************************************************************************
+******************************** ********************************
+******************************** PARM_MAP ********************************
+******************************** ********************************
+*****************************************************************************/
+
+/* The PARM_MAP structure defines global values relating to the Host Card / RTA
+ and is the main structure from which all other structures are referenced. */
+
+typedef struct _PARM_MAP
+{
+ _u16 phb_ptr; /* 0x00 Pointer to the PHB array */
+ _u16 phb_num_ptr; /* 0x02 Ptr to Number of PHB's */
+ _u16 free_list; /* 0x04 Free List pointer */
+ _u16 free_list_end; /* 0x06 Free List End pointer */
+ _u16 q_free_list_ptr; /* 0x08 Ptr to Q_BUF variable */
+ _u16 unit_id_ptr; /* 0x0A Unit Id */
+ _u16 link_str_ptr; /* 0x0C Link Structure Array */
+ _u16 bootloader_1; /* 0x0E 1st Stage Boot Loader */
+ _u16 bootloader_2; /* 0x10 2nd Stage Boot Loader */
+ _u16 port_route_map_ptr; /* 0x12 Port Route Map */
+ _u16 route_ptr; /* 0x14 Route Map */
+ _u16 map_present; /* 0x16 Route Map present */
+ _u16 pkt_num; /* 0x18 Total number of packets */
+ _u16 q_num; /* 0x1A Total number of Q packets */
+ _u16 buffers_per_port; /* 0x1C Number of buffers per port */
+ _u16 heap_size; /* 0x1E Initial size of heap */
+ _u16 heap_left; /* 0x20 Current Heap left */
+ _u16 error; /* 0x22 Error code */
+ _u16 tx_max; /* 0x24 Max number of tx pkts per phb */
+ _u16 rx_max; /* 0x26 Max number of rx pkts per phb */
+ _u16 rx_limit; /* 0x28 For high / low watermarks */
+ _u16 links; /* 0x2A Links to use */
+ _u16 timer; /* 0x2C Interrupts per second */
+ _u16 rups; /* 0x2E Pointer to the RUPs */
+ _u16 max_phb; /* 0x30 Mostly for debugging */
+ _u16 living; /* 0x32 Just increments!! */
+ _u16 init_done; /* 0x34 Initialisation over */
+ _u16 booting_link; /* 0x36 */
+ _u16 idle_count; /* 0x38 Idle time counter */
+ _u16 busy_count; /* 0x3A Busy counter */
+ _u16 idle_control; /* 0x3C Control Idle Process */
+ _u16 tx_intr; /* 0x3E TX interrupt pending */
+ _u16 rx_intr; /* 0x40 RX interrupt pending */
+ _u16 rup_intr; /* 0x42 RUP interrupt pending */
+
+} PARM_MAP;
+
+/* Same thing again, but defined as offsets... */
+
+#define PM_phb_ptr 0x00 /* 0x00 Pointer to the PHB array */
+#define PM_phb_num_ptr 0x02 /* 0x02 Ptr to Number of PHB's */
+#define PM_free_list 0x04 /* 0x04 Free List pointer */
+#define PM_free_list_end 0x06 /* 0x06 Free List End pointer */
+#define PM_q_free_list_ptr 0x08 /* 0x08 Ptr to Q_BUF variable */
+#define PM_unit_id_ptr 0x0A /* 0x0A Unit Id */
+#define PM_link_str_ptr 0x0C /* 0x0C Link Structure Array */
+#define PM_bootloader_1 0x0E /* 0x0E 1st Stage Boot Loader */
+#define PM_bootloader_2 0x10 /* 0x10 2nd Stage Boot Loader */
+#define PM_port_route_map_ptr 0x12 /* 0x12 Port Route Map */
+#define PM_route_ptr 0x14 /* 0x14 Route Map */
+#define PM_map_present 0x16 /* 0x16 Route Map present */
+#define PM_pkt_num 0x18 /* 0x18 Total number of packets */
+#define PM_q_num 0x1A /* 0x1A Total number of Q packets */
+#define PM_buffers_per_port 0x1C /* 0x1C Number of buffers per port */
+#define PM_heap_size 0x1E /* 0x1E Initial size of heap */
+#define PM_heap_left 0x20 /* 0x20 Current Heap left */
+#define PM_error 0x22 /* 0x22 Error code */
+#define PM_tx_max 0x24 /* 0x24 Max number of tx pkts per phb */
+#define PM_rx_max 0x26 /* 0x26 Max number of rx pkts per phb */
+#define PM_rx_limit 0x28 /* 0x28 For high / low watermarks */
+#define PM_links 0x2A /* 0x2A Links to use */
+#define PM_timer 0x2C /* 0x2C Interrupts per second */
+#define PM_rups 0x2E /* 0x2E Pointer to the RUPs */
+#define PM_max_phb 0x30 /* 0x30 Mostly for debugging */
+#define PM_living 0x32 /* 0x32 Just increments!! */
+#define PM_init_done 0x34 /* 0x34 Initialisation over */
+#define PM_booting_link 0x36 /* 0x36 */
+#define PM_idle_count 0x38 /* 0x38 Idle time counter */
+#define PM_busy_count 0x3A /* 0x3A Busy counter */
+#define PM_idle_control 0x3C /* 0x3C Control Idle Process */
+#define PM_tx_intr 0x3E /* 0x4E TX interrupt pending */
+#define PM_rx_intr 0x40 /* 0x40 RX interrupt pending */
+#define PM_rup_intr 0x42 /* 0x42 RUP interrupt pending */
+#define sizeof_PARM_MAP 0x44 /* structure size = 0x44 */
+
+/* PARM_MAP.error definitions... */
+#define E_NO_ERROR 0x00
+#define E_PROCESS_NOT_INIT 0x01
+#define E_LINK_TIMEOUT 0x02
+#define E_NO_ROUTE 0x03
+#define E_CONFUSED 0x04
+#define E_HOME 0x05
+#define E_CSUM_FAIL 0x06
+#define E_DISCONNECTED 0x07
+#define E_BAD_RUP 0x08
+#define E_NO_VIRGIN 0x09
+#define E_BOOT_RUP_BUSY 0x10
+#define E_CHANALLOC 0x80
+#define E_POLL_ALLOC 0x81
+#define E_LTTWAKE 0x82
+#define E_LTT_ALLOC 0x83
+#define E_LRT_ALLOC 0x84
+#define E_CIRRUS 0x85
+#define E_MONITOR 0x86
+#define E_PHB_ALLOC 0x87
+#define E_ARRAY_ALLOC 0x88
+#define E_QBUF_ALLOC 0x89
+#define E_PKT_ALLOC 0x8a
+#define E_GET_TX_Q_BUF 0x8b
+#define E_GET_RX_Q_BUF 0x8c
+#define E_MEM_OUT 0x8d
+#define E_MMU_INIT 0x8e
+#define E_LTT_INIT 0x8f
+#define E_LRT_INIT 0x90
+#define E_LINK_RUN 0x91
+#define E_MONITOR_ALLOC 0x92
+#define E_MONITOR_INIT 0x93
+#define E_POLL_INIT 0x94
+
+/* PARM_MAP.links definitions... */
+#define RIO_LINK_ENABLE 0x80FF
+
+/*****************************************************************************
+********************************** ***********************************
+********************************** RUP ***********************************
+********************************** ***********************************
+*****************************************************************************/
+
+/* The RUP (Remote Unit Port) structure relates to the Remote Terminal Adapters
+ attached to the system and there is normally an array of MAX_RUPS (=16) structures
+ in a host card, defined by PARM_MAP->rup. */
+
+typedef struct _RUP
+{
+ _u16 txpkt; /* 0x00 Outgoing packet */
+ _u16 rxpkt; /* 0x02 ncoming packet */
+ _u16 link; /* 0x04 Which link to send packet down ? */
+ _u8 rup_dest_unit[2]; /* 0x06 Destination Unit */
+ _u16 handshake; /* 0x08 Handshaking */
+ _u16 timeout; /* 0x0A Timeout */
+ _u16 status; /* 0x0C Status */
+ _u16 txcontrol; /* 0x0E Transmit control */
+ _u16 rxcontrol; /* 0x10 Receive control */
+
+} RUP;
+
+/* Same thing again, but defined as offsets... */
+
+#define RUP_txpkt 0x00 /* 0x00 Outgoing packet */
+#define RUP_rxpkt 0x02 /* 0x02 Incoming packet */
+#define RUP_link 0x04 /* 0x04 Which link to send packet down ? */
+#define RUP_rup_dest_unit 0x06 /* 0x06 Destination Unit */
+#define RUP_handshake 0x08 /* 0x08 Handshaking */
+#define RUP_timeout 0x0A /* 0x0A Timeout */
+#define RUP_status 0x0C /* 0x0C Status */
+#define RUP_txcontrol 0x0E /* 0x0E Transmit control */
+#define RUP_rxcontrol 0x10 /* 0x10 Receive control */
+#define sizeof_RUP 0x12 /* structure size = 0x12 */
+
+#define MAX_RUP 16
+
+/* RUP.txcontrol definitions... */
+#define TX_RUP_INACTIVE 0 /* Nothing to transmit */
+#define TX_PACKET_READY 1 /* Transmit packet ready */
+#define TX_LOCK_RUP 2 /* Transmit side locked */
+
+/* RUP.txcontrol definitions... */
+#define RX_RUP_INACTIVE 0 /* Nothing received */
+#define RX_PACKET_READY 1 /* Packet received */
+
+#define RUP_NO_OWNER 0xFF /* RUP not owned by any process */
+
+/*****************************************************************************
+********************************** ***********************************
+********************************** PHB ***********************************
+********************************** ***********************************
+*****************************************************************************/
+
+/* The PHB (Port Header Block) structure relates to the serial ports attached
+ to the system and there is normally an array of MAX_PHBS (=128) structures
+ in a host card, defined by PARM_MAP->phb_ptr and PARM_MAP->phb_num_ptr. */
+
+typedef struct _PHB
+{
+ _u16 source; /* 0x00 Location of the PHB in the host card */
+ _u16 handshake; /* 0x02 Used to manage receive packet flow control */
+ _u16 status; /* 0x04 Internal port transmit/receive status */
+ _u16 timeout; /* 0x06 Time period to wait for an ACK */
+ _u16 link; /* 0x08 The host link associated with the PHB */
+ _u16 destination; /* 0x0A Location of the remote port on the network */
+
+ _u16 tx_start; /* 0x0C first entry in the packet array for transmit packets */
+ _u16 tx_end; /* 0x0E last entry in the packet array for transmit packets */
+ _u16 tx_add; /* 0x10 position in the packet array for new transmit packets */
+ _u16 tx_remove; /* 0x12 current position in the packet pointer array */
+
+ _u16 rx_start; /* 0x14 first entry in the packet array for receive packets */
+ _u16 rx_end; /* 0x16 last entry in the packet array for receive packets */
+ _u16 rx_add; /* 0x18 position in the packet array for new receive packets */
+ _u16 rx_remove; /* 0x1A current position in the packet pointer array */
+
+} PHB;
+
+/* Same thing again, but defined as offsets... */
+
+#define PHB_source 0x00 /* 0x00 Location of the PHB in the host card */
+#define PHB_handshake 0x02 /* 0x02 Used to manage receive packet flow control */
+#define PHB_status 0x04 /* 0x04 Internal port transmit/receive status */
+#define PHB_timeout 0x06 /* 0x06 Time period to wait for an ACK */
+#define PHB_link 0x08 /* 0x08 The host link associated with the PHB */
+#define PHB_destination 0x0A /* 0x0A Location of the remote port on the network */
+#define PHB_tx_start 0x0C /* 0x0C first entry in the packet array for transmit packets */
+#define PHB_tx_end 0x0E /* 0x0E last entry in the packet array for transmit packets */
+#define PHB_tx_add 0x10 /* 0x10 position in the packet array for new transmit packets */
+#define PHB_tx_remove 0x12 /* 0x12 current position in the packet pointer array */
+#define PHB_rx_start 0x14 /* 0x14 first entry in the packet array for receive packets */
+#define PHB_rx_end 0x16 /* 0x16 last entry in the packet array for receive packets */
+#define PHB_rx_add 0x18 /* 0x18 position in the packet array for new receive packets */
+#define PHB_rx_remove 0x1A /* 0x1A current position in the packet pointer array */
+#define sizeof_PHB 0x1C /* structure size = 0x1C */
+
+/* PHB.handshake definitions... */
+#define PHB_HANDSHAKE_SET 0x0001 /* Set by LRT */
+#define PHB_HANDSHAKE_RESET 0x0002 /* Set by ISR / driver */
+#define PHB_HANDSHAKE_FLAGS (PHB_HANDSHAKE_RESET|PHB_HANDSHAKE_SET)
+ /* Reset by ltt */
+
+#define MAX_PHB 128 /* range 0-127 */
+
+/*****************************************************************************
+********************************** ***********************************
+********************************** LPB ***********************************
+********************************** ***********************************
+*****************************************************************************/
+
+/* The LPB (Link Parameter Block) structure relates to a RIO Network Link
+ and there is normally an array of MAX_LINKS (=4) structures in a host card,
+ defined by PARM_MAP->link_str_ptr. */
+
+typedef struct _LPB
+{
+ _u16 link_number; /* 0x00 Link Number */
+ _u16 in_ch; /* 0x02 Link In Channel */
+ _u16 out_ch; /* 0x04 Link Out Channel */
+ _u8 attached_serial[4]; /* 0x06 Attached serial number */
+ _u8 attached_host_serial[4];/* 0x0A Serial number of Host who booted other end */
+ _u16 descheduled; /* 0x0E Currently Descheduled */
+ _u16 state; /* 0x10 Current state */
+ _u16 send_poll; /* 0x12 Send a Poll Packet */
+ _u16 ltt_p; /* 0x14 Process Descriptor */
+ _u16 lrt_p; /* 0x16 Process Descriptor */
+ _u16 lrt_status; /* 0x18 Current lrt status */
+ _u16 ltt_status; /* 0x1A Current ltt status */
+ _u16 timeout; /* 0x1C Timeout value */
+ _u16 topology; /* 0x1E Topology bits */
+ _u16 mon_ltt; /* 0x20 */
+ _u16 mon_lrt; /* 0x22 */
+ _u16 num_pkts; /* 0x24 */
+ _u16 add_packet_list; /* 0x26 Add packets to here */
+ _u16 remove_packet_list; /* 0x28 Send packets from here */
+
+ _u16 lrt_fail_chan; /* 0x2A Lrt's failure channel */
+ _u16 ltt_fail_chan; /* 0x2C Ltt's failure channel */
+
+ RUP rup; /* 0x2E RUP structure for HOST to driver comms */
+ RUP link_rup; /* 0x40 RUP for the link (POLL, topology etc.) */
+ _u16 attached_link; /* 0x52 Number of attached link */
+ _u16 csum_errors; /* 0x54 csum errors */
+ _u16 num_disconnects; /* 0x56 number of disconnects */
+ _u16 num_sync_rcvd; /* 0x58 # sync's received */
+ _u16 num_sync_rqst; /* 0x5A # sync requests */
+ _u16 num_tx; /* 0x5C Num pkts sent */
+ _u16 num_rx; /* 0x5E Num pkts received */
+ _u16 module_attached; /* 0x60 Module tpyes of attached */
+ _u16 led_timeout; /* 0x62 LED timeout */
+ _u16 first_port; /* 0x64 First port to service */
+ _u16 last_port; /* 0x66 Last port to service */
+
+} LPB;
+
+/* Same thing again, but defined as offsets... */
+
+#define LPB_link_number 0x00 /* 0x00 Link Number */
+#define LPB_in_ch 0x02 /* 0x02 Link In Channel */
+#define LPB_out_ch 0x04 /* 0x04 Link Out Channel */
+#define LPB_attached_serial 0x06 /* 0x06 Attached serial number */
+#define LPB_attached_host_serial 0x0A /* 0x0A Serial number of Host who booted other end */
+#define LPB_descheduled 0x0E /* 0x0E Currently Descheduled */
+#define LPB_state 0x10 /* 0x10 Current state */
+#define LPB_send_poll 0x12 /* 0x12 Send a Poll Packet */
+#define LPB_ltt_p 0x14 /* 0x14 Process Descriptor */
+#define LPB_lrt_p 0x16 /* 0x16 Process Descriptor */
+#define LPB_lrt_status 0x18 /* 0x18 Current lrt status */
+#define LPB_ltt_status 0x1A /* 0x1A Current ltt status */
+#define LPB_timeout 0x1C /* 0x1C Timeout value */
+#define LPB_topology 0x1E /* 0x1E Topology bits */
+#define LPB_mon_ltt 0x20 /* 0x20 */
+#define LPB_mon_lrt 0x22 /* 0x22 */
+#define LPB_num_pkts 0x24 /* 0x24 */
+#define LPB_add_packet_list 0x26 /* 0x26 Add packets to here */
+#define LPB_remove_packet_list 0x28 /* 0x28 Send packets from here */
+#define LPB_lrt_fail_chan 0x2A /* 0x2A Lrt's failure channel */
+#define LPB_ltt_fail_chan 0x2C /* 0x2C Ltt's failure channel */
+#define LPB_rup 0x2E /* 0x2E RUP structure for HOST to driver comms */
+#define LPB_link_rup 0x40 /* 0x40 RUP for the link (POLL, topology etc.) */
+#define LPB_attached_link 0x52 /* 0x52 Number of attached link */
+#define LPB_csum_errors 0x54 /* 0x54 csum errors */
+#define LPB_num_disconnects 0x56 /* 0x56 number of disconnects */
+#define LPB_num_sync_rcvd 0x58 /* 0x58 # sync's received */
+#define LPB_num_sync_rqst 0x5A /* 0x5A # sync requests */
+#define LPB_num_tx 0x5C /* 0x5C Num pkts sent */
+#define LPB_num_rx 0x5E /* 0x5E Num pkts received */
+#define LPB_module_attached 0x60 /* 0x60 Module tpyes of attached */
+#define LPB_led_timeout 0x62 /* 0x62 LED timeout */
+#define LPB_first_port 0x64 /* 0x64 First port to service */
+#define LPB_last_port 0x66 /* 0x66 Last port to service */
+#define sizeof_LPB 0x68 /* structure size = 0x68 */
+
+#define LINKS_PER_UNIT 4 /* number of links from a host */
+
+/*****************************************************************************
+******************************** *******************************
+******************************** FREE_LIST *******************************
+******************************** *******************************
+*****************************************************************************/
+
+/* Used to overlay packet headers when allocating/freeing packets from the free list */
+
+typedef struct _FREE_LIST
+{
+ _u16 next; /* 0x00 offset of next list item */
+ _u16 prev; /* 0x02 offset of previous list item */
+
+} FREE_LIST;
+
+/* Same thing again, but defined as offsets... */
+
+#define FL_next 0x00 /* 0x00 offset of next list item */
+#define FL_prev 0x02 /* 0x02 offset of previous list item */
+
+/*****************************************************************************
+********************************** ***********************************
+********************************** PKT ***********************************
+********************************** ***********************************
+*****************************************************************************/
+
+/* The PKT is the main unit of communication between Host Cards and RTAs across
+ the RIO network. */
+
+#define PKT_MAX_DATA_LEN 72 /* Size of packet data */
+
+typedef struct _PKT
+{
+ _u8 dest_unit; /* 0x00 Destination Unit Id */
+ _u8 dest_port; /* 0x01 Destination Port */
+ _u8 src_unit; /* 0x02 Source Unit Id */
+ _u8 src_port; /* 0x03 Source Port */
+ _u8 len; /* 0x04 Length (in bytes) of data field */
+ _u8 control; /* 0x05 */
+ _u8 data[PKT_MAX_DATA_LEN]; /* 0x06 Actual data */
+ _u16 csum; /* 0x4E C-SUM */
+
+} PKT;
+
+/* Same thing again, but defined as offsets... */
+
+#define PKT_dest_unit 0x00 /* 0x00 Destination Unit Id */
+#define PKT_dest_port 0x01 /* 0x01 Destination Port */
+#define PKT_src_unit 0x02 /* 0x02 Source Unit Id */
+#define PKT_src_port 0x03 /* 0x03 Source Port */
+#define PKT_len 0x04 /* 0x04 Length (in bytes) of data field */
+#define PKT_control 0x05 /* 0x05 */
+#define PKT_data 0x06 /* 0x06 Actual data */
+#define PKT_csum 0x4E /* 0x4E C-SUM */
+#define sizeof_PKT 0x50 /* structure size = 0x50 */
+
+/* PKT.len definitions... */
+#define PKT_CMD_BIT 0x80
+#define PKT_CMD_DATA 0x80
+#define PKT_LEN_MASK 0x7F
+
+/* PKT.control definitions... */
+#define PKT_ACK 0x40
+#define PKT_TGL 0x20
+#define DATA_WNDW 0x10
+#define PKT_TTL_MASK 0x0F
+#define MAX_TTL 0x0F
+
+/*****************************************************************************
+***************************** ****************************
+***************************** Control Packets ****************************
+***************************** ****************************
+*****************************************************************************/
+
+/* The following definitions and structures define the control packets sent
+ between the driver and RIO Ports, RTAs and Host Cards. */
+
+#define PRE_EMPTIVE 0x80 /* Pre-emptive command (sent via port's RUP) */
+
+/* "in-band" and "pre-emptive" port commands... */
+#define OPEN 0x00 /* Driver->RIO Open a port */
+#define CONFIG 0x01 /* Driver->RIO Configure a port */
+#define MOPEN 0x02 /* Driver->RIO Modem open (wait for DCD) */
+#define CLOSE 0x03 /* Driver->RIO Close a port */
+#define WFLUSH (0x04|PRE_EMPTIVE) /* Driver->RIO Write flush */
+#define RFLUSH (0x05|PRE_EMPTIVE) /* Driver->RIO Read flush */
+#define RESUME (0x06|PRE_EMPTIVE) /* Driver->RIO Behave as if XON received */
+#define SBREAK 0x07 /* Driver->RIO Start break */
+#define EBREAK 0x08 /* Driver->RIO End break */
+#define SUSPEND (0x09|PRE_EMPTIVE) /* Driver->RIO Behave as if XOFF received */
+#define FCLOSE (0x0A|PRE_EMPTIVE) /* Driver->RIO Force close */
+#define XPRINT 0x0B /* Driver->RIO Xprint packet */
+#define MBIS (0x0C|PRE_EMPTIVE) /* Driver->RIO Set modem lines */
+#define MBIC (0x0D|PRE_EMPTIVE) /* Driver->RIO Clear modem lines */
+#define MSET (0x0E|PRE_EMPTIVE) /* Driver->RIO Set modem lines */
+#define PCLOSE 0x0F /* Driver->RIO Pseudo close */
+#define MGET (0x10|PRE_EMPTIVE) /* Driver->RIO Force update of modem status */
+#define MEMDUMP (0x11|PRE_EMPTIVE) /* Driver->RIO DEBUG request for RTA memory */
+#define READ_REGISTER (0x12|PRE_EMPTIVE) /* Driver->RIO DEBUG read CD1400 register */
+
+/* Remote Unit Port (RUP) packet definitions... (specified in PKT.dest_unit and PKT.src_unit) */
+#define SYNC_RUP 0xFF /* Download internal */
+#define COMMAND_RUP 0xFE /* Command ack/status */
+#define ERROR_RUP 0xFD /* Download internal */
+#define POLL_RUP 0xFC /* Download internal */
+#define BOOT_RUP 0xFB /* Used to boot RTAs */
+#define ROUTE_RUP 0xFA /* Used to specify routing/topology */
+#define STATUS_RUP 0xF9 /* Not used */
+#define POWER_RUP 0xF8 /* Download internal */
+
+/* COMMAND_RUP definitions... */
+#define COMPLETE (0x20|PRE_EMPTIVE) /* RIO->Driver Command complete */
+#define BREAK_RECEIVED (0x21|PRE_EMPTIVE) /* RIO->Driver Break received */
+#define MODEM_STATUS (0x22|PRE_EMPTIVE) /* RIO->Driver Modem status change */
+
+/* BOOT_RUP definitions... */
+#define BOOT_REQUEST 0x00 /* RIO->Driver Request for boot */
+#define BOOT_ABORT 0x01 /* Driver->RIO Abort a boot */
+#define BOOT_SEQUENCE 0x02 /* Driver->RIO Packet with firmware details */
+#define BOOT_COMPLETED 0x03 /* RIO->Driver Boot completed */
+#define IFOAD 0x2F /* Driver->RIO Shutdown/Reboot RTA (Fall Over And Die) */
+#define IDENTIFY 0x30 /* Driver->RIO Identify RTA */
+#define ZOMBIE 0x31 /* Driver->RIO Shutdown/Flash LEDs */
+#define UFOAD 0x32 /* Driver->RIO Shutdown/Reboot neighbouring RTA */
+#define IWAIT 0x33 /* Driver->RIO Pause booting process */
+
+/* ROUTE_RUP definitions... */
+#define ROUTE_REQUEST 0x00 /* RIO->Driver Request an ID */
+#define ROUTE_FOAD 0x01 /* Driver->RIO Shutdown/reboot RTA */
+#define ROUTE_ALREADY 0x02 /* Driver->RIO Not used */
+#define ROUTE_USED 0x03 /* Driver->RIO Not used */
+#define ROUTE_ALLOCATE 0x04 /* Driver->RIO Allocate RTA RUP numbers */
+#define ROUTE_REQ_TOP 0x05 /* Driver->RIO Not used */
+#define ROUTE_TOPOLOGY 0x06 /* RIO->Driver Route/Topology status */
+
+/*****************************************************************************
+********************************** **********************************
+********************************** OPEN **********************************
+********************************** **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ Sent to open a port.
+ Structure of configuration info used with OPEN, CONFIG and MOPEN packets... */
+
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_Cor1 (PKT_Data+1) /* Channel Option Register 1 */
+#define PKT_Cor2 (PKT_Data+2) /* Channel Option Register 2 */
+#define PKT_Cor4 (PKT_Data+3) /* Channel Option Register 4 */
+#define PKT_Cor5 (PKT_Data+4) /* Channel Option Register 5 */
+#define PKT_TxXon (PKT_Data+5) /* Transmit XON character */
+#define PKT_TxXoff (PKT_Data+6) /* Transmit XOFF character */
+#define PKT_RxXon (PKT_Data+7) /* Receive XON character */
+#define PKT_RxXoff (PKT_Data+8) /* Receive XOFF character */
+#define PKT_Lnext (PKT_Data+9) /* Lnext character */
+#define PKT_TxBaud (PKT_Data+10) /* Transmit baud rate */
+#define PKT_RxBaud (PKT_Data+11) /* Receive baud rate */
+
+/* COR1 definitions... */
+#define COR1_PARITY 0xE0 /* Parity mask */
+#define COR1_NONE 0x00 /* No parity */
+#define COR1_SPACE 0x20 /* Space parity */
+#define COR1_EVEN 0x40 /* Even parity */
+#define COR1_MARK 0xA0 /* Mark parity */
+#define COR1_ODD 0xC0 /* Odd parity */
+
+#define COR1_STOPBITS 0x0C /* Stop bits mask */
+#define COR1_STOP1 0x00 /* 1 stop bit */
+#define COR1_STOP1_5 0x04 /* 1.5 stop bits */
+#define COR1_STOP2 0x08 /* 2 stop bits */
+
+#define COR1_DATABITS 0x03 /* Data bits mask */
+#define COR1_DATA5 0x00 /* 5 data bits */
+#define COR1_DATA6 0x01 /* 6 data bits */
+#define COR1_DATA7 0x02 /* 7 data bits */
+#define COR1_DATA8 0x03 /* 8 data bits */
+
+/* COR2 definitions... */
+#define COR2_XON_TXFLOW 0x40 /* XON/XOFF Transmit Flow */
+#define COR2_XANY_TXFLOW 0xC0 /* XON/XANY Transmit Flow */
+#define COR2_HUPCL 0x20 /* Hang Up On Close */
+#define COR2_DSR_TXFLOW 0x08 /* DSR Transmit Flow Control */
+#define COR2_RTS_RXFLOW 0x04 /* RTS Receive Flow Control */
+#define COR2_CTS_TXFLOW 0x02 /* CTS Transmit Flow Control */
+#define COR2_XON_RXFLOW 0x01 /* XON/XOFF Receive Flow */
+
+/* COR4 definition... */
+#define COR4_IGNCR 0x80 /* Discard received CR */
+#define COR4_ICRNL 0x40 /* Map received CR -> NL */
+#define COR4_INLCR 0x20 /* Map received NL -> CR */
+#define COR4_IGNBRK 0x10 /* Ignore Received Break */
+#define COR4_NBRKINT 0x08 /* No interrupt on rx Break */
+#define COR4_IGNPAR 0x04 /* ignore rx parity error chars */
+#define COR4_PARMRK 0x02 /* Mark rx parity error chars */
+#define COR4_RAISEMOD 0x01 /* Raise modem lines on !0 baud */
+
+/* COR5 definitions... */
+#define COR5_ISTRIP 0x80 /* Strip input chars to 7 bits */
+#define COR5_LNE 0x40 /* Enable LNEXT processing */
+#define COR5_CMOE 0x20 /* Match good & error characters */
+#define COR5_TAB3 0x10 /* TAB3 mode */
+#define COR5_TSTATE_ON 0x08 /* Enable tbusy/tstop monitoring */
+#define COR5_TSTATE_OFF 0x04 /* Disable tbusy/tstop monitoring */
+#define COR5_ONLCR 0x02 /* NL -> CR NL on output */
+#define COR5_OCRNL 0x01 /* CR -> NL on output */
+
+/* RxBaud and TxBaud definitions... */
+#define RIO_B0 0x00 /* RTS / DTR signals dropped */
+#define RIO_B50 0x01 /* 50 baud */
+#define RIO_B75 0x02 /* 75 baud */
+#define RIO_B110 0x03 /* 110 baud */
+#define RIO_B134 0x04 /* 134.5 baud */
+#define RIO_B150 0x05 /* 150 baud */
+#define RIO_B200 0x06 /* 200 baud */
+#define RIO_B300 0x07 /* 300 baud */
+#define RIO_B600 0x08 /* 600 baud */
+#define RIO_B1200 0x09 /* 1200 baud */
+#define RIO_B1800 0x0A /* 1800 baud */
+#define RIO_B2400 0x0B /* 2400 baud */
+#define RIO_B4800 0x0C /* 4800 baud */
+#define RIO_B9600 0x0D /* 9600 baud */
+#define RIO_B19200 0x0E /* 19200 baud */
+#define RIO_B38400 0x0F /* 38400 baud */
+#define RIO_B56000 0x10 /* 56000 baud */
+#define RIO_B57600 0x11 /* 57600 baud */
+#define RIO_B64000 0x12 /* 64000 baud */
+#define RIO_B115200 0x13 /* 115200 baud */
+#define RIO_B2000 0x14 /* 2000 baud */
+
+/*****************************************************************************
+********************************* *********************************
+********************************* CONFIG *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ CONFIG is sent from the driver to configure an already opened port.
+ Packet structure is same as OPEN. */
+
+/*****************************************************************************
+********************************* **********************************
+********************************* MOPEN **********************************
+********************************* **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ MOPEN is sent from the driver to open a port attached to a modem. (in-band)
+ Packet structure is same as OPEN. */
+
+/*****************************************************************************
+********************************* **********************************
+********************************* CLOSE **********************************
+********************************* **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ CLOSE is sent from the driver to close a previously opened port.
+ No parameters.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+/*****************************************************************************
+********************************* *********************************
+********************************* WFLUSH *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ WFLUSH is sent pre-emptively from the driver to flush the write buffers and
+ packets of a port. (pre-emptive)
+
+ WFLUSH is also sent in-band from the driver to a port as a marker to end
+ write flushing previously started by a pre-emptive WFLUSH packet. (in-band)
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+
+/*****************************************************************************
+********************************* *********************************
+********************************* RFLUSH *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ RFLUSH is sent pre-emptively from the driver to flush the read buffers and
+ packets of a port.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+********************************* *********************************
+********************************* RESUME *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ RESUME is sent pre-emptively from the driver to cause a port to resume
+ transmission of data if blocked by XOFF. (as if XON had been received)
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+********************************* *********************************
+********************************* SBREAK *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ SBREAK is sent in-band from the driver to a port to suspend data and start
+ break signal transmission.
+
+ If the break delay is 0, the break signal will be acknowledged with a
+ RUP_COMMAND, COMPLETE packet and continue until an EBREAK packet is received.
+
+ Otherwise, there is no acknowledgement and the break signal will last for the
+ specified number of mS.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_BreakDelay (PKT_Data+1) /* Break delay in mS */
+
+/*****************************************************************************
+********************************* *********************************
+********************************* EBREAK *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ EBREAK is sent in-band from the driver to a port to stop transmission of a
+ break signal.
+
+ No parameters. */
+
+/*****************************************************************************
+********************************* ********************************
+********************************* SUSPEND ********************************
+********************************* ********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ SUSPEND is sent pre-emptively from the driver to cause a port to suspend
+ transmission of data. (as if XOFF had been received)
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+********************************* *********************************
+********************************* FCLOSE *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ FCLOSE is sent pre-emptively from the driver to force close a port.
+ A force close flushes receive and transmit queues, and also lowers all output
+ modem signals if the COR5_HUPCL (Hang Up On Close) flag is set.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+********************************* *********************************
+********************************* XPRINT *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ XPRINT is sent as a normal I/O data packet except that the PKT_CMD_BIT of
+ the "len" field is set, and the first "data" byte is XPRINT.
+
+ The I/O data in the XPRINT packet will contain the following:
+ - Transparent Print Start Sequence
+ - Transparent Print Data
+ - Transparent Print Stop Sequence.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+********************************** **********************************
+********************************** MBIS **********************************
+********************************** **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ MBIS is sent pre-emptively from the driver to set a port's modem signals.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+#define PKT_ModemSet (PKT_Data+4) /* Modem set signals mask */
+
+/* ModemSet definitions... */
+#define MBIS_RTS 0x01 /* RTS modem signal */
+#define MBIS_DTR 0x02 /* DTR modem signal */
+
+/*****************************************************************************
+********************************** **********************************
+********************************** MBIC **********************************
+********************************** **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ MBIC is sent pre-emptively from the driver to clear a port's modem signals.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+#define PKT_ModemClear (PKT_Data+4) /* Modem clear signals mask */
+
+/* ModemClear definitions... */
+#define MBIC_RTS 0x01 /* RTS modem signal */
+#define MBIC_DTR 0x02 /* DTR modem signal */
+
+/*****************************************************************************
+********************************** **********************************
+********************************** MSET **********************************
+********************************** **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ MSET is sent pre-emptively from the driver to set/clear a port's modem signals. */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#endif
+
+#define PKT_ModemSet (PKT_Data+4) /* Modem set signals mask */
+
+/* ModemSet definitions... */
+#define MSET_RTS 0x01 /* RTS modem signal */
+#define MSET_DTR 0x02 /* DTR modem signal */
+
+/*****************************************************************************
+********************************* *********************************
+********************************* PCLOSE *********************************
+********************************* *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+ PCLOSE is sent from the driver to pseudo close a previously opened port.
+
+ The port will close when all data has been sent/received, however, the
+ port's transmit / receive and modem signals will be left enabled and the
+ port marked internally as Pseudo Closed. */
+
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+
+/*****************************************************************************
+********************************** **********************************
+********************************** MGET **********************************
+********************************** **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ MGET is sent pre-emptively from the driver to request the port's current modem signals. */
+
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+
+/*****************************************************************************
+********************************* ********************************
+********************************* MEMDUMP ********************************
+********************************* ********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ MEMDUMP is sent pre-emptively from the driver to request a dump of 32 bytes
+ of the specified port's RTA address space.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+#define PKT_Address (PKT_Data+6) /* Requested address */
+
+/*****************************************************************************
+****************************** *****************************
+****************************** READ_REGISTER *****************************
+****************************** *****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ READ_REGISTER is sent pre-emptively from the driver to request the contents
+ of the CD1400 register specified in address.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+#define PKT_Address (PKT_Data+6) /* Requested address */
+
+/*****************************************************************************
+************************ **************************
+************************ COMMAND_RUP - COMPLETE **************************
+************************ **************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - COMPLETE is sent in response to all port I/O control command
+ packets, except MEMDUMP and READ_REGISTER.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#define PKT_ModemStatus (PKT_Data+3) /* Modem signal status */
+#define PKT_PortStatus (PKT_Data+4) /* Port signal status */
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+
+/* ModemStatus definitions... */
+#define MODEM_DSR 0x80 /* Data Set Ready modem state */
+#define MODEM_CTS 0x40 /* Clear To Send modem state */
+#define MODEM_RI 0x20 /* Ring Indicate modem state */
+#define MODEM_CD 0x10 /* Carrier Detect modem state */
+#define MODEM_TSTOP 0x08 /* Transmit Stopped state */
+#define MODEM_TEMPTY 0x04 /* Transmit Empty state */
+#define MODEM_DTR 0x02 /* DTR modem output state */
+#define MODEM_RTS 0x01 /* RTS modem output state */
+
+/* PortStatus definitions... */
+#define PORT_ISOPEN 0x01 /* Port open ? */
+#define PORT_HUPCL 0x02 /* Hangup on close? */
+#define PORT_MOPENPEND 0x04 /* Modem open pending */
+#define PORT_ISPARALLEL 0x08 /* Parallel port */
+#define PORT_BREAK 0x10 /* Port on break */
+#define PORT_STATUSPEND 0020 /* Status packet pending */
+#define PORT_BREAKPEND 0x40 /* Break packet pending */
+#define PORT_MODEMPEND 0x80 /* Modem status packet pending */
+
+/*****************************************************************************
+************************ **************************
+************************ COMMAND_RUP - COMPLETE **************************
+************************ **************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - COMPLETE is sent in response to all port I/O control command
+ packets, except MEMDUMP and READ_REGISTER.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#endif
+#define PKT_ModemStatus (PKT_Data+3) /* Modem signal status */
+#define PKT_PortStatus (PKT_Data+4) /* Port signal status */
+#if 0
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+#endif
+
+/* ModemStatus definitions... */
+#define MODEM_DSR 0x80 /* Data Set Ready modem state */
+#define MODEM_CTS 0x40 /* Clear To Send modem state */
+#define MODEM_RI 0x20 /* Ring Indicate modem state */
+#define MODEM_CD 0x10 /* Carrier Detect modem state */
+#define MODEM_TSTOP 0x08 /* Transmit Stopped state */
+#define MODEM_TEMPTY 0x04 /* Transmit Empty state */
+#define MODEM_DTR 0x02 /* DTR modem output state */
+#define MODEM_RTS 0x01 /* RTS modem output state */
+
+/* PortStatus definitions... */
+#define PORT_ISOPEN 0x01 /* Port open ? */
+#define PORT_HUPCL 0x02 /* Hangup on close? */
+#define PORT_MOPENPEND 0x04 /* Modem open pending */
+#define PORT_ISPARALLEL 0x08 /* Parallel port */
+#define PORT_BREAK 0x10 /* Port on break */
+#define PORT_STATUSPEND 0020 /* Status packet pending */
+#define PORT_BREAKPEND 0x40 /* Break packet pending */
+#define PORT_MODEMPEND 0x80 /* Modem status packet pending */
+
+/*****************************************************************************
+******************** ********************
+******************** COMMAND_RUP - COMPLETE - MEMDUMP ********************
+******************** ********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - COMPLETE - MEMDUMP is sent as an acknowledgement for a MEMDUMP
+ port I/O control command packet.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#define PKT_ModemStatus (PKT_Data+3) /* Modem signal status */
+#define PKT_PortStatus (PKT_Data+4) /* Port signal status */
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+#define PKT_Address (PKT_Data+6) /* Requested address */
+#endif
+#define PKT_Dump (PKT_Data+8) /* 32bytes of requested dump data */
+
+/*****************************************************************************
+***************** *****************
+***************** COMMAND_RUP - COMPLETE - READ_REGISTER *****************
+***************** *****************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - COMPLETE - READ_REGISTER is sent as an acknowledgement for a
+ READ_REGISTER port I/O control command packet.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /*Command code */
+#define PKT_PhbNum (PKT_Data+1) /*Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#endif
+#define PKT_RegisterValue (PKT_Data+3) /* Modem signal status */
+#if 0
+#define PKT_PortStatus (PKT_Data+4) /* Port signal status */
+#define PKT_SubCmd (PKT_Data+5) /* Sub Command */
+#endif
+
+/*****************************************************************************
+********************* ***********************
+********************* COMMAND_RUP - BREAK_RECEIVED ***********************
+********************* ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - BREAK_RECEIVED packets are sent when the port detects a receive BREAK signal.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#endif
+
+/*****************************************************************************
+********************* *************************
+********************* COMMAND_RUP - MODEM_STATUS *************************
+********************* *************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ COMMAND_RUP - MODEM_STATUS packets are sent whenever the port detects a
+ change in the input modem signal states.
+
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_PhbNum (PKT_Data+1) /* Port number wrt RTA */
+#define PKT_Cmd2 (PKT_Data+2) /* Command code copy */
+#define PKT_ModemStatus (PKT_Data+3) /* Modem signal status */
+#endif
+
+/*****************************************************************************
+************************ *************************
+************************ BOOT_RUP - BOOT_REQUEST *************************
+************************ *************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ BOOT_RUP - BOOT_REQUEST packets are sent to the Driver from RIO to request
+ firmware code to load onto attached RTAs.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+
+/*****************************************************************************
+************************ ************************
+************************ BOOT_RUP - BOOT_SEQUENCE ************************
+************************ ************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - BOOT_SEQUENCE packets are sent from the Driver to RIO in response
+ to a BOOT_RUP - BOOT_REQUEST packet.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_NumPackets (PKT_Data+2) /* Packets required to load firmware */
+#define PKT_LoadBase (PKT_Data+4) /* RTA firmware load address */
+#define PKT_CodeSize (PKT_Data+6) /* Size of firmware in bytes */
+#define PKT_CmdString (PKT_Data+8) /* Command string */
+
+/*****************************************************************************
+************************ ***********************
+************************ BOOT_RUP - BOOT_COMPLETED ***********************
+************************ ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ BOOT_RUP - BOOT_COMPLETE is sent to the Driver from RIO when downloading of
+ RTA firmware has completed.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_LinkNumber (PKT_Data+1) /* Link number RTA booted on */
+#define PKT_SerialNumber (PKT_Data+2) /* 4 byte serial number */
+
+/*****************************************************************************
+************************ ***********************
+************************ BOOT_RUP - Packet Request ***********************
+************************ ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ BOOT_RUP packet without the PKT_CMD_BIT set in the PKT->len field is sent
+ from RIO to the Driver as a request for a firmware boot packet. */
+
+#define PKT_SequenceNumber (PKT_Data+0) /* Packet sequence number */
+
+/*****************************************************************************
+*********************** ***********************
+*********************** BOOT_RUP - Packet Response ***********************
+*********************** ***********************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ In response to a BOOT_RUP boot packet request, the driver fills out the response
+ packet with the 70 bytes of the requested sequence.
+ */
+#if 0
+#define PKT_SequenceNumber (PKT_Data+0) /* Packet sequence number */
+#endif
+#define PKT_FirmwarePacket (PKT_Data+2) /* Firmware packet */
+
+/*****************************************************************************
+**************************** ****************************
+**************************** BOOT_RUP - IFOAD ****************************
+**************************** ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - IFOAD packets are sent from the Driver to an RTA to cause the
+ RTA to shut down and reboot.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_IfoadId1 (PKT_Data+2) /* IFOAD Id 1 */
+#define PKT_IfoadId2 (PKT_Data+3) /* IFOAD Id 2 */
+
+#define IFOADID1 0xAD
+#define IFOADID2 0xF0
+
+/*****************************************************************************
+************************** ***************************
+************************** BOOT_RUP - IDENTIFY ***************************
+************************** ***************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - IDENTIFY packets are sent from the Driver to an RTA to cause the
+ RTA to flash its LEDs for a period of time.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_IdentifyId (PKT_Data+2) /* defines pattern to flash */
+
+/*****************************************************************************
+**************************** ***************************
+**************************** BOOT_RUP - ZOMBIE ***************************
+**************************** ***************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - ZOMBIE packets are sent from the Driver to an RTA to cause the
+ RTA to shut down and flash it's LEDs.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_ZombieId1 (PKT_Data+2) /* ZOMBIE Id 1 */
+#define PKT_ZombieId2 (PKT_Data+3) /* ZOMBIE Id 2 */
+
+#define ZOMBIEID1 0x52
+#define ZOMBIEID2 0x21
+
+/*****************************************************************************
+**************************** ****************************
+**************************** BOOT_RUP - UFOAD ****************************
+**************************** ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - UFOAD packets are sent from the Driver to an RTA to cause the RTA
+ to ask it's neighbouring RTA to shut down and reboot.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_LinkNumber (PKT_Data+1) /* Link number of RTA to UFOAD */
+#endif
+#define PKT_UfoadId1 (PKT_Data+2) /* UFOAD Id 1 */
+#define PKT_UfoadId2 (PKT_Data+3) /* UFOAD Id 2 */
+
+#define UFOADID1 0x1E
+#define UFOADID2 0x0D
+
+/*****************************************************************************
+**************************** ****************************
+**************************** BOOT_RUP - IWAIT ****************************
+**************************** ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ BOOT_RUP - IWAIT packets are sent from the Driver to an RTA to cause the RTA
+ to pause booting on the specified link for 30 seconds.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#define PKT_LinkNumber (PKT_Data+1) /* Link number of RTA to UFOAD */
+#endif
+#define PKT_IwaitId1 (PKT_Data+2) /* IWAIT Id 1 */
+#define PKT_IwaitId2 (PKT_Data+3) /* IWAIT Id 2 */
+
+#define IWAITID1 0xDE
+#define IWAITID2 0xB1
+
+/*****************************************************************************
+************************ ***********************
+************************ ROUTE_RUP - ROUTE_REQUEST ***********************
+************************ ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ ROUTE_RUP - ROUTE_REQUEST packets are sent from a newly booted or connected
+ RTA to a Driver to request an ID (RUP or unit number).
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_SerialNumber (PKT_Data+2) /* 4 byte serial number */
+#define PKT_ModuleTypes (PKT_Data+6) /* RTA Module types */
+
+/* ModuleTypes definitions... */
+#define MOD_BLANK 0x0F /* Blank plate attached */
+#define MOD_RS232DB25 0x00 /* RS232 DB25 connector */
+#define MOD_RS232RJ45 0x01 /* RS232 RJ45 connector */
+#define MOD_RS422DB25 0x02 /* RS422 DB25 connector */
+#define MOD_RS485DB25 0x03 /* RS485 DB25 connector */
+#define MOD_PARALLEL 0x04 /* Centronics parallel */
+
+#define MOD2 0x08 /* Set to indicate Rev2 module */
+
+/*****************************************************************************
+************************* *************************
+************************* ROUTE_RUP - ROUTE_FOAD *************************
+************************* *************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ ROUTE_RUP - ROUTE_FOAD packet is sent as a response to a ROUTE_RUP - ROUTE_REQUEST
+ packet to cause the RTA to "Fall Over And Die"., i.e. shutdown and reboot.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_RouteCmdString (PKT_Data+2) /* Command string */
+
+/*****************************************************************************
+*********************** ***********************
+*********************** ROUTE_RUP - ROUTE_ALLOCATE ***********************
+*********************** ***********************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+ ROUTE_RUP - ROUTE_ALLOCATE packet is sent as a response to a ROUTE_RUP - ROUTE_REQUEST
+ packet to allocate the RTA's Id number (RUP number 1..16)
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_IdNum (PKT_Data+1) /* RUP number for ports 1..8 */
+#if 0
+#define PKT_RouteCmdString (PKT_Data+2) /* Command string */
+#endif
+#define PKT_IdNum2 (PKT_Data+0x17) /* RUP number for ports 9..16 */
+
+/*****************************************************************************
+*********************** ***********************
+*********************** ROUTE_RUP - ROUTE_TOPOLOGY ***********************
+*********************** ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+ ROUTE_RUP - ROUTE_TOPOLOGY packet is sent to inform the driver of an RTA's
+ current link status.
+ */
+#if 0
+#define PKT_Cmd (PKT_Data+0) /* Command code */
+#endif
+#define PKT_Link1Rup (PKT_Data+2) /* Link 1 RUP number */
+#define PKT_Link1Link (PKT_Data+3) /* Link 1 link number */
+#define PKT_Link2Rup (PKT_Data+4) /* Link 2 RUP number */
+#define PKT_Link2Link (PKT_Data+5) /* Link 2 link number */
+#define PKT_Link3Rup (PKT_Data+6) /* Link 3 RUP number */
+#define PKT_Link3Link (PKT_Data+7) /* Link 3 link number */
+#define PKT_Link4Rup (PKT_Data+8) /* Link 4 RUP number */
+#define PKT_Link4Link (PKT_Data+9) /* Link 4 link number */
+#define PKT_RtaVpdProm (PKT_Data+10) /* 32 bytes of RTA VPD PROM Contents */
+
+#endif /* _sxwinif_h */
+
+/* End of RIOWINIF.H */
diff --git a/drivers/char/rio/riscos.h b/drivers/char/rio/riscos.h
new file mode 100644
index 000000000000..7685cc1d9e7b
--- /dev/null
+++ b/drivers/char/rio/riscos.h
@@ -0,0 +1,63 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : riscos.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:19
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)riscos.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_riscos_h__
+#define __rio_riscos_h__
+
+#ifdef SCCS_LABELS
+static char *_riscos_h_sccs_ = "@(#)riscos.h 1.2";
+#endif
+
+/*
+** This module used to define all those little itsy bits required for RISC/OS
+** now it's full of null macros.
+*/
+
+/*
+** RBYTE reads a byte from a location.
+** RWORD reads a word from a location.
+** WBYTE writes a byte to a location.
+** WWORD writes a word to a location.
+** RINDW reads a word through a pointer.
+** WINDW writes a word through a pointer.
+** RIOSWAB swaps the two bytes of a word, if needed.
+*/
+
+#define RIOSWAB(N) (N)
+#define WBYTE(A,V) (A)=(uchar)(V)
+#define WWORD(A,V) (A)=(ushort)(V)
+#define RBYTE(A) (uchar)(A)
+#define RWORD(A) (ushort)(A)
+#define RINDW(A) (*(ushort *)(A))
+#define WINDW(A,V) (*(ushort *)(A)=(ushort)(V))
+
+#endif /* __rio_riscos_h__ */
diff --git a/drivers/char/rio/rom.h b/drivers/char/rio/rom.h
new file mode 100644
index 000000000000..ee79b8e5b972
--- /dev/null
+++ b/drivers/char/rio/rom.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+ ******* *******
+ ******* R O M
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rom_h
+#define _rom_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_rom_h_sccs = "@(#)rom.h 1.1" ;
+#endif
+#endif
+
+typedef struct ROM ROM ;
+struct ROM {
+ u_short slx ;
+ char pcb_letter_rev ;
+ char pcb_number_rev ;
+ char serial[4] ;
+ char year ;
+ char week ;
+ } ;
+
+#endif
+
+#define HOST_ROM (ROM *) 0x7c00
+#define RTA_ROM (ROM *) 0x7801
+#define ROM_LENGTH 0x20
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/route.h b/drivers/char/rio/route.h
new file mode 100644
index 000000000000..c42dbb971718
--- /dev/null
+++ b/drivers/char/rio/route.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+ ******* *******
+ ******* R O U T E H E A D E R
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra / Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _route_h
+#define _route_h
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_route_h_sccs = "@(#)route.h 1.3"; */
+#endif
+#endif
+
+#define MAX_LINKS 4
+#define MAX_NODES 17 /* Maximum nodes in a subnet */
+#define NODE_BYTES ((MAX_NODES / 8) + 1) /* Number of bytes needed for
+ 1 bit per node */
+#define ROUTE_DATA_SIZE (NODE_BYTES + 2) /* Number of bytes for complete
+ info about cost etc. */
+#define ROUTES_PER_PACKET ((PKT_MAX_DATA_LEN -2)/ ROUTE_DATA_SIZE)
+ /* Number of nodes we can squeeze
+ into one packet */
+#define MAX_TOPOLOGY_PACKETS (MAX_NODES / ROUTES_PER_PACKET + 1)
+/************************************************
+ * Define the types of command for the ROUTE RUP.
+ ************************************************/
+#define ROUTE_REQUEST 0 /* Request an ID */
+#define ROUTE_FOAD 1 /* Kill the RTA */
+#define ROUTE_ALREADY 2 /* ID given already */
+#define ROUTE_USED 3 /* All ID's used */
+#define ROUTE_ALLOCATE 4 /* Here it is */
+#define ROUTE_REQ_TOP 5 /* I bet you didn't expect....
+ the Topological Inquisition */
+#define ROUTE_TOPOLOGY 6 /* Topology request answered FD */
+/*******************************************************************
+ * Define the Route Map Structure
+ *
+ * The route map gives a pointer to a Link Structure to use.
+ * This allows Disconnected Links to be checked quickly
+ ******************************************************************/
+typedef struct COST_ROUTE COST_ROUTE;
+struct COST_ROUTE {
+ unsigned char cost; /* Cost down this link */
+ unsigned char route[NODE_BYTES]; /* Nodes thorough this route */
+ } ;
+
+typedef struct ROUTE_STR ROUTE_STR ;
+struct ROUTE_STR {
+ COST_ROUTE cost_route[MAX_LINKS];
+ /* cost / route for this link */
+ ushort favoured; /* favoured link */
+ } ;
+
+
+#define NO_LINK (short) 5 /* Link unattached */
+#define ROUTE_NO_ID (short) 100 /* No Id */
+#define ROUTE_DISCONNECT (ushort) 0xff /* Not connected */
+#define ROUTE_INTERCONNECT (ushort) 0x40 /* Sub-net interconnect */
+
+
+#define SYNC_RUP (ushort) 255
+#define COMMAND_RUP (ushort) 254
+#define ERROR_RUP (ushort) 253
+#define POLL_RUP (ushort) 252
+#define BOOT_RUP (ushort) 251
+#define ROUTE_RUP (ushort) 250
+#define STATUS_RUP (ushort) 249
+#define POWER_RUP (ushort) 248
+
+#define HIGHEST_RUP (ushort) 255 /* Set to Top one */
+#define LOWEST_RUP (ushort) 248 /* Set to bottom one */
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rtahw.h b/drivers/char/rio/rtahw.h
new file mode 100644
index 000000000000..06860118cbc5
--- /dev/null
+++ b/drivers/char/rio/rtahw.h
@@ -0,0 +1,75 @@
+
+/****************************************************************************
+ ******* *******
+ ******* R T A H A R D W A R E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_rtahw_h_sccs = "@(#)rtahw.h 1.5" ;
+#endif
+#endif
+
+#define WATCHDOG_ADDR ((unsigned short *)0x7a00)
+#define RTA_LED_ADDR ((unsigned short *)0x7c00)
+#define SERIALNUM_ADDR ((unsigned char *)0x7809)
+#define LATCH_ADDR ((unsigned char *)0x7800)
+
+/*
+** Here we define where the cd1400 chips are in memory.
+*/
+#define CD1400_ONE_ADDR (0x7300)
+#define CD1400_TWO_ADDR (0x7200)
+#define CD1400_THREE_ADDR (0x7100)
+#define CD1400_FOUR_ADDR (0x7000)
+
+/*
+** Define the different types of modules we can have
+*/
+enum module {
+ MOD_BLANK = 0x0f, /* Blank plate attached */
+ MOD_RS232DB25 = 0x00, /* RS232 DB25 connector */
+ MOD_RS232RJ45 = 0x01, /* RS232 RJ45 connector */
+ MOD_RS422DB25 = 0x02, /* RS422 DB25 connector */
+ MOD_RS485DB25 = 0x03, /* RS485 DB25 connector */
+ MOD_PARALLEL = 0x04 /* Centronics parallel */
+};
+
+#define TYPE_HOST 0
+#define TYPE_RTA8 1
+#define TYPE_RTA16 2
+
+#define WATCH_DOG WATCHDOG_ADDR
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/rup.h b/drivers/char/rio/rup.h
new file mode 100644
index 000000000000..b9d2bc03d14b
--- /dev/null
+++ b/drivers/char/rio/rup.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+ ******* *******
+ ******* R U P S T R U C T U R E
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rup_h
+#define _rup_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_rup_h_sccs = "@(#)rup.h 1.5"; */
+#endif
+#endif
+
+#if defined( HOST ) || defined( INKERNEL )
+#define MAX_RUP ((short) 16)
+#endif
+#ifdef RTA
+#define MAX_RUP ((short) 1)
+#endif
+
+#define PKTS_PER_RUP ((short) 2) /* They are always used in pairs */
+
+/*************************************************
+ * Define all the packet request stuff
+ ************************************************/
+#define TX_RUP_INACTIVE 0 /* Nothing to transmit */
+#define TX_PACKET_READY 1 /* Transmit packet ready */
+#define TX_LOCK_RUP 2 /* Transmit side locked */
+
+#define RX_RUP_INACTIVE 0 /* Nothing received */
+#define RX_PACKET_READY 1 /* Packet received */
+
+#define RUP_NO_OWNER 0xff /* RUP not owned by any process */
+
+struct RUP {
+ PKT_ptr txpkt; /* Outgoing packet */
+ PKT_ptr rxpkt; /* Incoming packet */
+ WORD link; /* Which link to send down? */
+ BYTE rup_dest_unit[2]; /* Destination unit */
+ WORD handshake; /* For handshaking */
+ WORD timeout; /* Timeout */
+ WORD status; /* Status */
+ WORD txcontrol; /* Transmit control */
+ WORD rxcontrol; /* Receive control */
+ };
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rupstat.h b/drivers/char/rio/rupstat.h
new file mode 100644
index 000000000000..b4aafaff0d78
--- /dev/null
+++ b/drivers/char/rio/rupstat.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+ ******* *******
+ ******* RUPSTAT
+ ******* *******
+ ****************************************************************************
+
+ Author : Jeremy Rolls
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rupstat_h
+#define _rupstat_h
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_rupstat_h_sccs = "@(#)rupstat.h 1.1" ;
+#endif
+#endif
+
+#define STATUS_SYNC 0
+#define STATUS_REQ_TOP 1
+#define STATUS_TOPOLOGY 2
+
+#endif
+
diff --git a/drivers/char/rio/sam.h b/drivers/char/rio/sam.h
new file mode 100644
index 000000000000..c1accb8cb624
--- /dev/null
+++ b/drivers/char/rio/sam.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+ ******* *******
+ ******* S A M . H
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+#ifndef _sam_h
+#define _sam_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_sam_h_sccs = "@(#)sam.h 1.3"; */
+#endif
+#endif
+
+
+#if !defined( HOST ) && !defined( INKERNEL )
+#define RTA 1
+#endif
+
+#define NUM_FREE_LIST_UNITS 500
+
+#ifndef FALSE
+#define FALSE (short) 0x00
+#endif
+#ifndef TRUE
+#define TRUE (short) !FALSE
+#endif
+
+#define TX TRUE
+#define RX FALSE
+
+
+typedef struct FREE_LIST FREE_LIST ;
+struct FREE_LIST {
+ FREE_LIST_ptr next ;
+ FREE_LIST_ptr prev ;
+ } ;
+
+
+#endif
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/selftest.h b/drivers/char/rio/selftest.h
new file mode 100644
index 000000000000..deae48722a21
--- /dev/null
+++ b/drivers/char/rio/selftest.h
@@ -0,0 +1,73 @@
+/*
+** File: selftest.h
+**
+** Author: David Dix
+**
+** Created: 15th March 1993
+**
+** Last modified: 94/06/14
+**
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _selftests_h_
+#define _selftests_h_
+
+/*
+** Selftest identifier...
+*/
+#define SELFTEST_MAGIC 0x5a5a
+
+/*
+** This is the structure of the packet that is sent back after each
+** selftest on a booting RTA.
+*/
+typedef struct {
+ short magic; /* Identifies packet type */
+ int test; /* Test number, see below */
+ unsigned int result; /* Result value */
+ unsigned int dataIn;
+ unsigned int dataOut;
+}selftestStruct;
+
+/*
+** The different tests are identified by the following data values.
+*/
+enum test {
+ TESTS_COMPLETE = 0x00,
+ MEMTEST_ADDR = 0x01,
+ MEMTEST_BIT = 0x02,
+ MEMTEST_FILL = 0x03,
+ MEMTEST_DATABUS = 0x04,
+ MEMTEST_ADDRBUS = 0x05,
+ CD1400_INIT = 0x10,
+ CD1400_LOOP = 0x11,
+ CD1400_INTERRUPT = 0x12
+};
+
+enum result {
+ E_PORT = 0x10,
+ E_TX = 0x11,
+ E_RX = 0x12,
+ E_EXCEPT = 0x13,
+ E_COMPARE = 0x14,
+ E_MODEM = 0x15,
+ E_TIMEOUT = 0x16,
+ E_INTERRUPT = 0x17
+};
+#endif /* _selftests_h_ */
diff --git a/drivers/char/rio/space.h b/drivers/char/rio/space.h
new file mode 100644
index 000000000000..72398d342051
--- /dev/null
+++ b/drivers/char/rio/space.h
@@ -0,0 +1,45 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : space.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:19
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)space.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_space_h__
+#define __rio_space_h__
+
+#ifdef SCCS_LABELS
+static char *_space_h_sccs_ = "@(#)space.h 1.2";
+#endif
+
+extern int rio_cntls;
+extern int rio_bases[];
+extern int rio_limits[];
+extern int rio_vects[];
+
+#endif /* __rio_space_h__ */
diff --git a/drivers/char/rio/sysmap.h b/drivers/char/rio/sysmap.h
new file mode 100644
index 000000000000..fdc731393576
--- /dev/null
+++ b/drivers/char/rio/sysmap.h
@@ -0,0 +1,63 @@
+
+/****************************************************************************
+ ******* *******
+ ******* S Y S T E M M A P H E A D E R
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_sysmap_h_sccs = "@(#)sysmap.h 1.1" ;
+#endif
+#endif
+
+#define SYSTEM_MAP_LEN 64 /* Len of System Map array */
+
+
+typedef struct SYS_MAP SYS_MAP ;
+typedef struct SYS_MAP_LINK SYS_MAP_LINK ;
+
+struct SYS_MAP_LINK {
+ short id ; /* Unit Id */
+ short link ; /* Id's Link */
+ short been_here ; /* Used by map_gen */
+ } ;
+
+struct SYS_MAP {
+ char serial_num[4] ;
+ SYS_MAP_LINK link[4] ;
+ } ;
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/timeouts.h b/drivers/char/rio/timeouts.h
new file mode 100644
index 000000000000..11b31330c901
--- /dev/null
+++ b/drivers/char/rio/timeouts.h
@@ -0,0 +1,51 @@
+
+/****************************************************************************
+ ******* *******
+ ******* T I M E O U T S
+ ******* *******
+ ****************************************************************************
+
+ Author : Ian Nandhra
+ Date :
+
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Version : 0.01
+
+
+ Mods
+ ----------------------------------------------------------------------------
+ Date By Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_defaults_h_sccs = "@(#)timeouts.h 1.3" ;
+#endif
+#endif
+
+#define MILLISECOND (int) (1000/64) /* 15.625 low ticks */
+#define SECOND (int) 15625 /* Low priority ticks */
+
+#define TX_TIMEOUT (int) (200 * MILLISECOND)
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/top.h b/drivers/char/rio/top.h
new file mode 100644
index 000000000000..255c40d463a6
--- /dev/null
+++ b/drivers/char/rio/top.h
@@ -0,0 +1,49 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : top.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:19
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)top.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_top_h__
+#define __rio_top_h__
+
+#ifdef SCCS_LABELS
+static char *_top_h_sccs_ = "@(#)top.h 1.2";
+#endif
+
+/*
+** Topology information
+*/
+struct Top
+{
+ uchar Unit;
+ uchar Link;
+};
+
+#endif /* __rio_top_h__ */
diff --git a/drivers/char/rio/typdef.h b/drivers/char/rio/typdef.h
new file mode 100644
index 000000000000..2cb9dd693fa1
--- /dev/null
+++ b/drivers/char/rio/typdef.h
@@ -0,0 +1,82 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : typdef.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:20
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)typdef.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_typdef_h__
+#define __rio_typdef_h__
+
+#ifdef SCCS_LABELS
+static char *_typdef_h_sccs_ = "@(#)typdef.h 1.2";
+#endif
+
+#undef VPIX
+
+/*
+** IT IS REALLY, REALLY, IMPORTANT THAT BYTES ARE UNSIGNED!
+**
+** These types are ONLY to be used for refering to data structures
+** on the RIO Host card!
+*/
+typedef volatile unsigned char BYTE;
+typedef volatile unsigned short WORD;
+typedef volatile unsigned int DWORD;
+typedef volatile unsigned short RIOP;
+typedef volatile short NUMBER;
+
+
+/*
+** 27.01.199 ARG - mods to compile 'newutils' on LyxnOS -
+** These #defines are for the benefit of the 'libfuncs' library
+** only. They are not necessarily correct type mappings and
+** are here only to make the source compile.
+*/
+/* typedef unsigned int uint; */
+typedef unsigned long ulong_t;
+typedef unsigned short ushort_t;
+typedef unsigned char uchar_t;
+typedef unsigned char queue_t;
+typedef unsigned char mblk_t;
+typedef unsigned int paddr_t;
+typedef unsigned char uchar;
+
+#define TPNULL ((ushort)(0x8000))
+
+
+/*
+** RIO structures defined in other include files.
+*/
+typedef struct PKT PKT;
+typedef struct LPB LPB;
+typedef struct RUP RUP;
+typedef struct Port Port;
+typedef struct DpRam DpRam;
+
+#endif /* __rio_typdef_h__ */
diff --git a/drivers/char/rio/unixrup.h b/drivers/char/rio/unixrup.h
new file mode 100644
index 000000000000..eddf86278abc
--- /dev/null
+++ b/drivers/char/rio/unixrup.h
@@ -0,0 +1,56 @@
+/*
+** -----------------------------------------------------------------------------
+**
+** Perle Specialix driver for Linux
+** Ported from existing RIO Driver for SCO sources.
+ *
+ * (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** Module : unixrup.h
+** SID : 1.2
+** Last Modified : 11/6/98 11:34:20
+** Retrieved : 11/6/98 11:34:22
+**
+** ident @(#)unixrup.h 1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_unixrup_h__
+#define __rio_unixrup_h__
+
+#ifdef SCCS_LABELS
+static char *_unixrup_h_sccs_ = "@(#)unixrup.h 1.2";
+#endif
+
+/*
+** UnixRup data structure. This contains pointers to actual RUPs on the
+** host card, and all the command/boot control stuff.
+*/
+struct UnixRup
+{
+ struct CmdBlk *CmdsWaitingP; /* Commands waiting to be done */
+ struct CmdBlk *CmdPendingP; /* The command currently being sent */
+ struct RUP *RupP; /* the Rup to send it to */
+ uint Id; /* Id number */
+ uint BaseSysPort; /* SysPort of first tty on this RTA */
+ uint ModTypes; /* Modules on this RTA */
+ spinlock_t RupLock; /* Lock structure for MPX */
+/* struct lockb RupLock; */ /* Lock structure for MPX */
+};
+
+#endif /* __rio_unixrup_h__ */
diff --git a/drivers/char/riscom8.c b/drivers/char/riscom8.c
new file mode 100644
index 000000000000..55a3a0188eda
--- /dev/null
+++ b/drivers/char/riscom8.c
@@ -0,0 +1,1809 @@
+/*
+ * linux/drivers/char/riscom.c -- RISCom/8 multiport serial driver.
+ *
+ * Copyright (C) 1994-1996 Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others. The RISCom/8 card
+ * programming info was obtained from various drivers for other OSes
+ * (FreeBSD, ISC, etc), but no source code from those drivers were
+ * directly included in this driver.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Revision 1.1
+ *
+ * ChangeLog:
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 27-Jun-2001
+ * - get rid of check_region and several cleanups
+ */
+
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/uaccess.h>
+
+#include "riscom8.h"
+#include "riscom8_reg.h"
+
+/* Am I paranoid or not ? ;-) */
+#define RISCOM_PARANOIA_CHECK
+
+/*
+ * Crazy InteliCom/8 boards sometimes has swapped CTS & DSR signals.
+ * You can slightly speed up things by #undefing the following option,
+ * if you are REALLY sure that your board is correct one.
+ */
+
+#define RISCOM_BRAIN_DAMAGED_CTS
+
+/*
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#undef RC_REPORT_FIFO
+#undef RC_REPORT_OVERRUN
+
+
+#define RISCOM_LEGAL_FLAGS \
+ (ASYNC_HUP_NOTIFY | ASYNC_SAK | ASYNC_SPLIT_TERMIOS | \
+ ASYNC_SPD_HI | ASYNC_SPEED_VHI | ASYNC_SESSION_LOCKOUT | \
+ ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP)
+
+#define RS_EVENT_WRITE_WAKEUP 0
+
+static struct riscom_board * IRQ_to_board[16];
+static struct tty_driver *riscom_driver;
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static unsigned long baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 76800, 0,
+};
+
+static struct riscom_board rc_board[RC_NBOARD] = {
+ {
+ .base = RC_IOBASE1,
+ },
+ {
+ .base = RC_IOBASE2,
+ },
+ {
+ .base = RC_IOBASE3,
+ },
+ {
+ .base = RC_IOBASE4,
+ },
+};
+
+static struct riscom_port rc_port[RC_NBOARD * RC_NPORT];
+
+/* RISCom/8 I/O ports addresses (without address translation) */
+static unsigned short rc_ioport[] = {
+#if 1
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c,
+#else
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c, 0x10,
+ 0x11, 0x12, 0x18, 0x28, 0x31, 0x32, 0x39, 0x3a, 0x40, 0x41, 0x61, 0x62,
+ 0x63, 0x64, 0x6b, 0x70, 0x71, 0x78, 0x7a, 0x7b, 0x7f, 0x100, 0x101
+#endif
+};
+#define RC_NIOPORT (sizeof(rc_ioport) / sizeof(rc_ioport[0]))
+
+
+static inline int rc_paranoia_check(struct riscom_port const * port,
+ char *name, const char *routine)
+{
+#ifdef RISCOM_PARANOIA_CHECK
+ static const char badmagic[] = KERN_INFO
+ "rc: Warning: bad riscom port magic number for device %s in %s\n";
+ static const char badinfo[] = KERN_INFO
+ "rc: Warning: null riscom port for device %s in %s\n";
+
+ if (!port) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (port->magic != RISCOM8_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/*
+ *
+ * Service functions for RISCom/8 driver.
+ *
+ */
+
+/* Get board number from pointer */
+static inline int board_No (struct riscom_board const * bp)
+{
+ return bp - rc_board;
+}
+
+/* Get port number from pointer */
+static inline int port_No (struct riscom_port const * port)
+{
+ return RC_PORT(port - rc_port);
+}
+
+/* Get pointer to board from pointer to port */
+static inline struct riscom_board * port_Board(struct riscom_port const * port)
+{
+ return &rc_board[RC_BOARD(port - rc_port)];
+}
+
+/* Input Byte from CL CD180 register */
+static inline unsigned char rc_in(struct riscom_board const * bp, unsigned short reg)
+{
+ return inb(bp->base + RC_TO_ISA(reg));
+}
+
+/* Output Byte to CL CD180 register */
+static inline void rc_out(struct riscom_board const * bp, unsigned short reg,
+ unsigned char val)
+{
+ outb(val, bp->base + RC_TO_ISA(reg));
+}
+
+/* Wait for Channel Command Register ready */
+static inline void rc_wait_CCR(struct riscom_board const * bp)
+{
+ unsigned long delay;
+
+ /* FIXME: need something more descriptive then 100000 :) */
+ for (delay = 100000; delay; delay--)
+ if (!rc_in(bp, CD180_CCR))
+ return;
+
+ printk(KERN_INFO "rc%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+/*
+ * RISCom/8 probe functions.
+ */
+
+static inline int rc_request_io_range(struct riscom_board * const bp)
+{
+ int i;
+
+ for (i = 0; i < RC_NIOPORT; i++)
+ if (!request_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1,
+ "RISCom/8")) {
+ goto out_release;
+ }
+ return 0;
+out_release:
+ printk(KERN_INFO "rc%d: Skipping probe at 0x%03x. IO address in use.\n",
+ board_No(bp), bp->base);
+ while(--i >= 0)
+ release_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1);
+ return 1;
+}
+
+static inline void rc_release_io_range(struct riscom_board * const bp)
+{
+ int i;
+
+ for (i = 0; i < RC_NIOPORT; i++)
+ release_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1);
+}
+
+/* Must be called with enabled interrupts */
+static inline void rc_long_delay(unsigned long delay)
+{
+ unsigned long i;
+
+ for (i = jiffies + delay; time_after(i,jiffies); ) ;
+}
+
+/* Reset and setup CD180 chip */
+static void __init rc_init_CD180(struct riscom_board const * bp)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+ rc_out(bp, RC_CTOUT, 0); /* Clear timeout */
+ rc_wait_CCR(bp); /* Wait for CCR ready */
+ rc_out(bp, CD180_CCR, CCR_HARDRESET); /* Reset CD180 chip */
+ sti();
+ rc_long_delay(HZ/20); /* Delay 0.05 sec */
+ cli();
+ rc_out(bp, CD180_GIVR, RC_ID); /* Set ID for this chip */
+ rc_out(bp, CD180_GICR, 0); /* Clear all bits */
+ rc_out(bp, CD180_PILR1, RC_ACK_MINT); /* Prio for modem intr */
+ rc_out(bp, CD180_PILR2, RC_ACK_TINT); /* Prio for transmitter intr */
+ rc_out(bp, CD180_PILR3, RC_ACK_RINT); /* Prio for receiver intr */
+
+ /* Setting up prescaler. We need 4 ticks per 1 ms */
+ rc_out(bp, CD180_PPRH, (RC_OSCFREQ/(1000000/RISCOM_TPS)) >> 8);
+ rc_out(bp, CD180_PPRL, (RC_OSCFREQ/(1000000/RISCOM_TPS)) & 0xff);
+
+ restore_flags(flags);
+}
+
+/* Main probing routine, also sets irq. */
+static int __init rc_probe(struct riscom_board *bp)
+{
+ unsigned char val1, val2;
+ int irqs = 0;
+ int retries;
+
+ bp->irq = 0;
+
+ if (rc_request_io_range(bp))
+ return 1;
+
+ /* Are the I/O ports here ? */
+ rc_out(bp, CD180_PPRL, 0x5a);
+ outb(0xff, 0x80);
+ val1 = rc_in(bp, CD180_PPRL);
+ rc_out(bp, CD180_PPRL, 0xa5);
+ outb(0x00, 0x80);
+ val2 = rc_in(bp, CD180_PPRL);
+
+ if ((val1 != 0x5a) || (val2 != 0xa5)) {
+ printk(KERN_ERR "rc%d: RISCom/8 Board at 0x%03x not found.\n",
+ board_No(bp), bp->base);
+ goto out_release;
+ }
+
+ /* It's time to find IRQ for this board */
+ for (retries = 0; retries < 5 && irqs <= 0; retries++) {
+ irqs = probe_irq_on();
+ rc_init_CD180(bp); /* Reset CD180 chip */
+ rc_out(bp, CD180_CAR, 2); /* Select port 2 */
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_TXEN); /* Enable transmitter */
+ rc_out(bp, CD180_IER, IER_TXRDY); /* Enable tx empty intr */
+ rc_long_delay(HZ/20);
+ irqs = probe_irq_off(irqs);
+ val1 = rc_in(bp, RC_BSR); /* Get Board Status reg */
+ val2 = rc_in(bp, RC_ACK_TINT); /* ACK interrupt */
+ rc_init_CD180(bp); /* Reset CD180 again */
+
+ if ((val1 & RC_BSR_TINT) || (val2 != (RC_ID | GIVR_IT_TX))) {
+ printk(KERN_ERR "rc%d: RISCom/8 Board at 0x%03x not "
+ "found.\n", board_No(bp), bp->base);
+ goto out_release;
+ }
+ }
+
+ if (irqs <= 0) {
+ printk(KERN_ERR "rc%d: Can't find IRQ for RISCom/8 board "
+ "at 0x%03x.\n", board_No(bp), bp->base);
+ goto out_release;
+ }
+ bp->irq = irqs;
+ bp->flags |= RC_BOARD_PRESENT;
+
+ printk(KERN_INFO "rc%d: RISCom/8 Rev. %c board detected at "
+ "0x%03x, IRQ %d.\n",
+ board_No(bp),
+ (rc_in(bp, CD180_GFRCR) & 0x0f) + 'A', /* Board revision */
+ bp->base, bp->irq);
+
+ return 0;
+out_release:
+ rc_release_io_range(bp);
+ return 1;
+}
+
+/*
+ *
+ * Interrupt processing routines.
+ *
+ */
+
+static inline void rc_mark_event(struct riscom_port * port, int event)
+{
+ set_bit(event, &port->event);
+ schedule_work(&port->tqueue);
+}
+
+static inline struct riscom_port * rc_get_port(struct riscom_board const * bp,
+ unsigned char const * what)
+{
+ unsigned char channel;
+ struct riscom_port * port;
+
+ channel = rc_in(bp, CD180_GICR) >> GICR_CHAN_OFF;
+ if (channel < CD180_NCH) {
+ port = &rc_port[board_No(bp) * RC_NPORT + channel];
+ if (port->flags & ASYNC_INITIALIZED) {
+ return port;
+ }
+ }
+ printk(KERN_ERR "rc%d: %s interrupt from invalid port %d\n",
+ board_No(bp), what, channel);
+ return NULL;
+}
+
+static inline void rc_receive_exc(struct riscom_board const * bp)
+{
+ struct riscom_port *port;
+ struct tty_struct *tty;
+ unsigned char status;
+ unsigned char ch;
+
+ if (!(port = rc_get_port(bp, "Receive")))
+ return;
+
+ tty = port->tty;
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ printk(KERN_WARNING "rc%d: port %d: Working around flip "
+ "buffer overflow.\n",
+ board_No(bp), port_No(port));
+ return;
+ }
+
+#ifdef RC_REPORT_OVERRUN
+ status = rc_in(bp, CD180_RCSR);
+ if (status & RCSR_OE) {
+ port->overrun++;
+#if 0
+ printk(KERN_ERR "rc%d: port %d: Overrun. Total %ld overruns\n",
+ board_No(bp), port_No(port), port->overrun);
+#endif
+ }
+ status &= port->mark_mask;
+#else
+ status = rc_in(bp, CD180_RCSR) & port->mark_mask;
+#endif
+ ch = rc_in(bp, CD180_RDR);
+ if (!status) {
+ return;
+ }
+ if (status & RCSR_TOUT) {
+ printk(KERN_WARNING "rc%d: port %d: Receiver timeout. "
+ "Hardware problems ?\n",
+ board_No(bp), port_No(port));
+ return;
+
+ } else if (status & RCSR_BREAK) {
+ printk(KERN_INFO "rc%d: port %d: Handling break...\n",
+ board_No(bp), port_No(port));
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ if (port->flags & ASYNC_SAK)
+ do_SAK(tty);
+
+ } else if (status & RCSR_PE)
+ *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+
+ else if (status & RCSR_FE)
+ *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+
+ else if (status & RCSR_OE)
+ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+
+ else
+ *tty->flip.flag_buf_ptr++ = 0;
+
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ schedule_delayed_work(&tty->flip.work, 1);
+}
+
+static inline void rc_receive(struct riscom_board const * bp)
+{
+ struct riscom_port *port;
+ struct tty_struct *tty;
+ unsigned char count;
+
+ if (!(port = rc_get_port(bp, "Receive")))
+ return;
+
+ tty = port->tty;
+
+ count = rc_in(bp, CD180_RDCR);
+
+#ifdef RC_REPORT_FIFO
+ port->hits[count > 8 ? 9 : count]++;
+#endif
+
+ while (count--) {
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ printk(KERN_WARNING "rc%d: port %d: Working around "
+ "flip buffer overflow.\n",
+ board_No(bp), port_No(port));
+ break;
+ }
+ *tty->flip.char_buf_ptr++ = rc_in(bp, CD180_RDR);
+ *tty->flip.flag_buf_ptr++ = 0;
+ tty->flip.count++;
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+}
+
+static inline void rc_transmit(struct riscom_board const * bp)
+{
+ struct riscom_port *port;
+ struct tty_struct *tty;
+ unsigned char count;
+
+
+ if (!(port = rc_get_port(bp, "Transmit")))
+ return;
+
+ tty = port->tty;
+
+ if (port->IER & IER_TXEMPTY) {
+ /* FIFO drained */
+ rc_out(bp, CD180_CAR, port_No(port));
+ port->IER &= ~IER_TXEMPTY;
+ rc_out(bp, CD180_IER, port->IER);
+ return;
+ }
+
+ if ((port->xmit_cnt <= 0 && !port->break_length)
+ || tty->stopped || tty->hw_stopped) {
+ rc_out(bp, CD180_CAR, port_No(port));
+ port->IER &= ~IER_TXRDY;
+ rc_out(bp, CD180_IER, port->IER);
+ return;
+ }
+
+ if (port->break_length) {
+ if (port->break_length > 0) {
+ if (port->COR2 & COR2_ETC) {
+ rc_out(bp, CD180_TDR, CD180_C_ESC);
+ rc_out(bp, CD180_TDR, CD180_C_SBRK);
+ port->COR2 &= ~COR2_ETC;
+ }
+ count = min_t(int, port->break_length, 0xff);
+ rc_out(bp, CD180_TDR, CD180_C_ESC);
+ rc_out(bp, CD180_TDR, CD180_C_DELAY);
+ rc_out(bp, CD180_TDR, count);
+ if (!(port->break_length -= count))
+ port->break_length--;
+ } else {
+ rc_out(bp, CD180_TDR, CD180_C_ESC);
+ rc_out(bp, CD180_TDR, CD180_C_EBRK);
+ rc_out(bp, CD180_COR2, port->COR2);
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_CORCHG2);
+ port->break_length = 0;
+ }
+ return;
+ }
+
+ count = CD180_NFIFO;
+ do {
+ rc_out(bp, CD180_TDR, port->xmit_buf[port->xmit_tail++]);
+ port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ if (--port->xmit_cnt <= 0)
+ break;
+ } while (--count > 0);
+
+ if (port->xmit_cnt <= 0) {
+ rc_out(bp, CD180_CAR, port_No(port));
+ port->IER &= ~IER_TXRDY;
+ rc_out(bp, CD180_IER, port->IER);
+ }
+ if (port->xmit_cnt <= port->wakeup_chars)
+ rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+}
+
+static inline void rc_check_modem(struct riscom_board const * bp)
+{
+ struct riscom_port *port;
+ struct tty_struct *tty;
+ unsigned char mcr;
+
+ if (!(port = rc_get_port(bp, "Modem")))
+ return;
+
+ tty = port->tty;
+
+ mcr = rc_in(bp, CD180_MCR);
+ if (mcr & MCR_CDCHG) {
+ if (rc_in(bp, CD180_MSVR) & MSVR_CD)
+ wake_up_interruptible(&port->open_wait);
+ else
+ schedule_work(&port->tqueue_hangup);
+ }
+
+#ifdef RISCOM_BRAIN_DAMAGED_CTS
+ if (mcr & MCR_CTSCHG) {
+ if (rc_in(bp, CD180_MSVR) & MSVR_CTS) {
+ tty->hw_stopped = 0;
+ port->IER |= IER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->IER &= ~IER_TXRDY;
+ }
+ rc_out(bp, CD180_IER, port->IER);
+ }
+ if (mcr & MCR_DSRCHG) {
+ if (rc_in(bp, CD180_MSVR) & MSVR_DSR) {
+ tty->hw_stopped = 0;
+ port->IER |= IER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->IER &= ~IER_TXRDY;
+ }
+ rc_out(bp, CD180_IER, port->IER);
+ }
+#endif /* RISCOM_BRAIN_DAMAGED_CTS */
+
+ /* Clear change bits */
+ rc_out(bp, CD180_MCR, 0);
+}
+
+/* The main interrupt processing routine */
+static irqreturn_t rc_interrupt(int irq, void * dev_id, struct pt_regs * regs)
+{
+ unsigned char status;
+ unsigned char ack;
+ struct riscom_board *bp;
+ unsigned long loop = 0;
+ int handled = 0;
+
+ bp = IRQ_to_board[irq];
+
+ if (!bp || !(bp->flags & RC_BOARD_ACTIVE)) {
+ return IRQ_NONE;
+ }
+
+ while ((++loop < 16) && ((status = ~(rc_in(bp, RC_BSR))) &
+ (RC_BSR_TOUT | RC_BSR_TINT |
+ RC_BSR_MINT | RC_BSR_RINT))) {
+ handled = 1;
+ if (status & RC_BSR_TOUT)
+ printk(KERN_WARNING "rc%d: Got timeout. Hardware "
+ "error?\n", board_No(bp));
+
+ else if (status & RC_BSR_RINT) {
+ ack = rc_in(bp, RC_ACK_RINT);
+
+ if (ack == (RC_ID | GIVR_IT_RCV))
+ rc_receive(bp);
+ else if (ack == (RC_ID | GIVR_IT_REXC))
+ rc_receive_exc(bp);
+ else
+ printk(KERN_WARNING "rc%d: Bad receive ack "
+ "0x%02x.\n",
+ board_No(bp), ack);
+
+ } else if (status & RC_BSR_TINT) {
+ ack = rc_in(bp, RC_ACK_TINT);
+
+ if (ack == (RC_ID | GIVR_IT_TX))
+ rc_transmit(bp);
+ else
+ printk(KERN_WARNING "rc%d: Bad transmit ack "
+ "0x%02x.\n",
+ board_No(bp), ack);
+
+ } else /* if (status & RC_BSR_MINT) */ {
+ ack = rc_in(bp, RC_ACK_MINT);
+
+ if (ack == (RC_ID | GIVR_IT_MODEM))
+ rc_check_modem(bp);
+ else
+ printk(KERN_WARNING "rc%d: Bad modem ack "
+ "0x%02x.\n",
+ board_No(bp), ack);
+
+ }
+
+ rc_out(bp, CD180_EOIR, 0); /* Mark end of interrupt */
+ rc_out(bp, RC_CTOUT, 0); /* Clear timeout flag */
+ }
+ return IRQ_RETVAL(handled);
+}
+
+/*
+ * Routines for open & close processing.
+ */
+
+/* Called with disabled interrupts */
+static inline int rc_setup_board(struct riscom_board * bp)
+{
+ int error;
+
+ if (bp->flags & RC_BOARD_ACTIVE)
+ return 0;
+
+ error = request_irq(bp->irq, rc_interrupt, SA_INTERRUPT,
+ "RISCom/8", NULL);
+ if (error)
+ return error;
+
+ rc_out(bp, RC_CTOUT, 0); /* Just in case */
+ bp->DTR = ~0;
+ rc_out(bp, RC_DTR, bp->DTR); /* Drop DTR on all ports */
+
+ IRQ_to_board[bp->irq] = bp;
+ bp->flags |= RC_BOARD_ACTIVE;
+
+ return 0;
+}
+
+/* Called with disabled interrupts */
+static inline void rc_shutdown_board(struct riscom_board *bp)
+{
+ if (!(bp->flags & RC_BOARD_ACTIVE))
+ return;
+
+ bp->flags &= ~RC_BOARD_ACTIVE;
+
+ free_irq(bp->irq, NULL);
+ IRQ_to_board[bp->irq] = NULL;
+
+ bp->DTR = ~0;
+ rc_out(bp, RC_DTR, bp->DTR); /* Drop DTR on all ports */
+
+}
+
+/*
+ * Setting up port characteristics.
+ * Must be called with disabled interrupts
+ */
+static void rc_change_speed(struct riscom_board *bp, struct riscom_port *port)
+{
+ struct tty_struct *tty;
+ unsigned long baud;
+ long tmp;
+ unsigned char cor1 = 0, cor3 = 0;
+ unsigned char mcor1 = 0, mcor2 = 0;
+
+ if (!(tty = port->tty) || !tty->termios)
+ return;
+
+ port->IER = 0;
+ port->COR2 = 0;
+ port->MSVR = MSVR_RTS;
+
+ baud = C_BAUD(tty);
+
+ if (baud & CBAUDEX) {
+ baud &= ~CBAUDEX;
+ if (baud < 1 || baud > 2)
+ port->tty->termios->c_cflag &= ~CBAUDEX;
+ else
+ baud += 15;
+ }
+ if (baud == 15) {
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baud ++;
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baud += 2;
+ }
+
+ /* Select port on the board */
+ rc_out(bp, CD180_CAR, port_No(port));
+
+ if (!baud_table[baud]) {
+ /* Drop DTR & exit */
+ bp->DTR |= (1u << port_No(port));
+ rc_out(bp, RC_DTR, bp->DTR);
+ return;
+ } else {
+ /* Set DTR on */
+ bp->DTR &= ~(1u << port_No(port));
+ rc_out(bp, RC_DTR, bp->DTR);
+ }
+
+ /*
+ * Now we must calculate some speed depended things
+ */
+
+ /* Set baud rate for port */
+ tmp = (((RC_OSCFREQ + baud_table[baud]/2) / baud_table[baud] +
+ CD180_TPC/2) / CD180_TPC);
+
+ rc_out(bp, CD180_RBPRH, (tmp >> 8) & 0xff);
+ rc_out(bp, CD180_TBPRH, (tmp >> 8) & 0xff);
+ rc_out(bp, CD180_RBPRL, tmp & 0xff);
+ rc_out(bp, CD180_TBPRL, tmp & 0xff);
+
+ baud = (baud_table[baud] + 5) / 10; /* Estimated CPS */
+
+ /* Two timer ticks seems enough to wakeup something like SLIP driver */
+ tmp = ((baud + HZ/2) / HZ) * 2 - CD180_NFIFO;
+ port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ?
+ SERIAL_XMIT_SIZE - 1 : tmp);
+
+ /* Receiver timeout will be transmission time for 1.5 chars */
+ tmp = (RISCOM_TPS + RISCOM_TPS/2 + baud/2) / baud;
+ tmp = (tmp > 0xff) ? 0xff : tmp;
+ rc_out(bp, CD180_RTPR, tmp);
+
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ cor1 |= COR1_5BITS;
+ break;
+ case CS6:
+ cor1 |= COR1_6BITS;
+ break;
+ case CS7:
+ cor1 |= COR1_7BITS;
+ break;
+ case CS8:
+ cor1 |= COR1_8BITS;
+ break;
+ }
+
+ if (C_CSTOPB(tty))
+ cor1 |= COR1_2SB;
+
+ cor1 |= COR1_IGNORE;
+ if (C_PARENB(tty)) {
+ cor1 |= COR1_NORMPAR;
+ if (C_PARODD(tty))
+ cor1 |= COR1_ODDP;
+ if (I_INPCK(tty))
+ cor1 &= ~COR1_IGNORE;
+ }
+ /* Set marking of some errors */
+ port->mark_mask = RCSR_OE | RCSR_TOUT;
+ if (I_INPCK(tty))
+ port->mark_mask |= RCSR_FE | RCSR_PE;
+ if (I_BRKINT(tty) || I_PARMRK(tty))
+ port->mark_mask |= RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ port->mark_mask &= ~(RCSR_FE | RCSR_PE);
+ if (I_IGNBRK(tty)) {
+ port->mark_mask &= ~RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ /* Real raw mode. Ignore all */
+ port->mark_mask &= ~RCSR_OE;
+ }
+ /* Enable Hardware Flow Control */
+ if (C_CRTSCTS(tty)) {
+#ifdef RISCOM_BRAIN_DAMAGED_CTS
+ port->IER |= IER_DSR | IER_CTS;
+ mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+ mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+ tty->hw_stopped = !(rc_in(bp, CD180_MSVR) & (MSVR_CTS|MSVR_DSR));
+#else
+ port->COR2 |= COR2_CTSAE;
+#endif
+ }
+ /* Enable Software Flow Control. FIXME: I'm not sure about this */
+ /* Some people reported that it works, but I still doubt */
+ if (I_IXON(tty)) {
+ port->COR2 |= COR2_TXIBE;
+ cor3 |= (COR3_FCT | COR3_SCDE);
+ if (I_IXANY(tty))
+ port->COR2 |= COR2_IXM;
+ rc_out(bp, CD180_SCHR1, START_CHAR(tty));
+ rc_out(bp, CD180_SCHR2, STOP_CHAR(tty));
+ rc_out(bp, CD180_SCHR3, START_CHAR(tty));
+ rc_out(bp, CD180_SCHR4, STOP_CHAR(tty));
+ }
+ if (!C_CLOCAL(tty)) {
+ /* Enable CD check */
+ port->IER |= IER_CD;
+ mcor1 |= MCOR1_CDZD;
+ mcor2 |= MCOR2_CDOD;
+ }
+
+ if (C_CREAD(tty))
+ /* Enable receiver */
+ port->IER |= IER_RXD;
+
+ /* Set input FIFO size (1-8 bytes) */
+ cor3 |= RISCOM_RXFIFO;
+ /* Setting up CD180 channel registers */
+ rc_out(bp, CD180_COR1, cor1);
+ rc_out(bp, CD180_COR2, port->COR2);
+ rc_out(bp, CD180_COR3, cor3);
+ /* Make CD180 know about registers change */
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3);
+ /* Setting up modem option registers */
+ rc_out(bp, CD180_MCOR1, mcor1);
+ rc_out(bp, CD180_MCOR2, mcor2);
+ /* Enable CD180 transmitter & receiver */
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_TXEN | CCR_RXEN);
+ /* Enable interrupts */
+ rc_out(bp, CD180_IER, port->IER);
+ /* And finally set RTS on */
+ rc_out(bp, CD180_MSVR, port->MSVR);
+}
+
+/* Must be called with interrupts enabled */
+static int rc_setup_port(struct riscom_board *bp, struct riscom_port *port)
+{
+ unsigned long flags;
+
+ if (port->flags & ASYNC_INITIALIZED)
+ return 0;
+
+ if (!port->xmit_buf) {
+ /* We may sleep in get_zeroed_page() */
+ unsigned long tmp;
+
+ if (!(tmp = get_zeroed_page(GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (port->xmit_buf) {
+ free_page(tmp);
+ return -ERESTARTSYS;
+ }
+ port->xmit_buf = (unsigned char *) tmp;
+ }
+
+ save_flags(flags); cli();
+
+ if (port->tty)
+ clear_bit(TTY_IO_ERROR, &port->tty->flags);
+
+ if (port->count == 1)
+ bp->count++;
+
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ rc_change_speed(bp, port);
+ port->flags |= ASYNC_INITIALIZED;
+
+ restore_flags(flags);
+ return 0;
+}
+
+/* Must be called with interrupts disabled */
+static void rc_shutdown_port(struct riscom_board *bp, struct riscom_port *port)
+{
+ struct tty_struct *tty;
+
+ if (!(port->flags & ASYNC_INITIALIZED))
+ return;
+
+#ifdef RC_REPORT_OVERRUN
+ printk(KERN_INFO "rc%d: port %d: Total %ld overruns were detected.\n",
+ board_No(bp), port_No(port), port->overrun);
+#endif
+#ifdef RC_REPORT_FIFO
+ {
+ int i;
+
+ printk(KERN_INFO "rc%d: port %d: FIFO hits [ ",
+ board_No(bp), port_No(port));
+ for (i = 0; i < 10; i++) {
+ printk("%ld ", port->hits[i]);
+ }
+ printk("].\n");
+ }
+#endif
+ if (port->xmit_buf) {
+ free_page((unsigned long) port->xmit_buf);
+ port->xmit_buf = NULL;
+ }
+
+ if (!(tty = port->tty) || C_HUPCL(tty)) {
+ /* Drop DTR */
+ bp->DTR |= (1u << port_No(port));
+ rc_out(bp, RC_DTR, bp->DTR);
+ }
+
+ /* Select port */
+ rc_out(bp, CD180_CAR, port_No(port));
+ /* Reset port */
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_SOFTRESET);
+ /* Disable all interrupts from this port */
+ port->IER = 0;
+ rc_out(bp, CD180_IER, port->IER);
+
+ if (tty)
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ port->flags &= ~ASYNC_INITIALIZED;
+
+ if (--bp->count < 0) {
+ printk(KERN_INFO "rc%d: rc_shutdown_port: "
+ "bad board count: %d\n",
+ board_No(bp), bp->count);
+ bp->count = 0;
+ }
+
+ /*
+ * If this is the last opened port on the board
+ * shutdown whole board
+ */
+ if (!bp->count)
+ rc_shutdown_board(bp);
+}
+
+
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct riscom_port *port)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct riscom_board *bp = port_Board(port);
+ int retval;
+ int do_clocal = 0;
+ int CD;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ return -EAGAIN;
+ else
+ return -ERESTARTSYS;
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&port->open_wait, &wait);
+ cli();
+ if (!tty_hung_up_p(filp))
+ port->count--;
+ sti();
+ port->blocked_open++;
+ while (1) {
+ cli();
+ rc_out(bp, CD180_CAR, port_No(port));
+ CD = rc_in(bp, CD180_MSVR) & MSVR_CD;
+ rc_out(bp, CD180_MSVR, MSVR_RTS);
+ bp->DTR &= ~(1u << port_No(port));
+ rc_out(bp, RC_DTR, bp->DTR);
+ sti();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || CD))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&port->open_wait, &wait);
+ if (!tty_hung_up_p(filp))
+ port->count++;
+ port->blocked_open--;
+ if (retval)
+ return retval;
+
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+}
+
+static int rc_open(struct tty_struct * tty, struct file * filp)
+{
+ int board;
+ int error;
+ struct riscom_port * port;
+ struct riscom_board * bp;
+
+ board = RC_BOARD(tty->index);
+ if (board >= RC_NBOARD || !(rc_board[board].flags & RC_BOARD_PRESENT))
+ return -ENODEV;
+
+ bp = &rc_board[board];
+ port = rc_port + board * RC_NPORT + RC_PORT(tty->index);
+ if (rc_paranoia_check(port, tty->name, "rc_open"))
+ return -ENODEV;
+
+ if ((error = rc_setup_board(bp)))
+ return error;
+
+ port->count++;
+ tty->driver_data = port;
+ port->tty = tty;
+
+ if ((error = rc_setup_port(bp, port)))
+ return error;
+
+ if ((error = block_til_ready(tty, filp, port)))
+ return error;
+
+ return 0;
+}
+
+static void rc_close(struct tty_struct * tty, struct file * filp)
+{
+ struct riscom_port *port = (struct riscom_port *) tty->driver_data;
+ struct riscom_board *bp;
+ unsigned long flags;
+ unsigned long timeout;
+
+ if (!port || rc_paranoia_check(port, tty->name, "close"))
+ return;
+
+ save_flags(flags); cli();
+ if (tty_hung_up_p(filp))
+ goto out;
+
+ bp = port_Board(port);
+ if ((tty->count == 1) && (port->count != 1)) {
+ printk(KERN_INFO "rc%d: rc_close: bad port count;"
+ " tty->count is 1, port count is %d\n",
+ board_No(bp), port->count);
+ port->count = 1;
+ }
+ if (--port->count < 0) {
+ printk(KERN_INFO "rc%d: rc_close: bad port count "
+ "for tty%d: %d\n",
+ board_No(bp), port_No(port), port->count);
+ port->count = 0;
+ }
+ if (port->count)
+ goto out;
+ port->flags |= ASYNC_CLOSING;
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, port->closing_wait);
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ port->IER &= ~IER_RXD;
+ if (port->flags & ASYNC_INITIALIZED) {
+ port->IER &= ~IER_TXRDY;
+ port->IER |= IER_TXEMPTY;
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_IER, port->IER);
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = jiffies+HZ;
+ while(port->IER & IER_TXEMPTY) {
+ msleep_interruptible(jiffies_to_msecs(port->timeout));
+ if (time_after(jiffies, timeout))
+ break;
+ }
+ }
+ rc_shutdown_port(bp, port);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ tty->closing = 0;
+ port->event = 0;
+ port->tty = NULL;
+ if (port->blocked_open) {
+ if (port->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(port->close_delay));
+ }
+ wake_up_interruptible(&port->open_wait);
+ }
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&port->close_wait);
+out: restore_flags(flags);
+}
+
+static int rc_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+ int c, total = 0;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_write"))
+ return 0;
+
+ bp = port_Board(port);
+
+ if (!tty || !port->xmit_buf || !tmp_buf)
+ return 0;
+
+ save_flags(flags);
+ while (1) {
+ cli();
+ c = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - port->xmit_head));
+ if (c <= 0) {
+ restore_flags(flags);
+ break;
+ }
+
+ memcpy(port->xmit_buf + port->xmit_head, buf, c);
+ port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ port->xmit_cnt += c;
+ restore_flags(flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ cli();
+ if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
+ !(port->IER & IER_TXRDY)) {
+ port->IER |= IER_TXRDY;
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_IER, port->IER);
+ }
+ restore_flags(flags);
+
+ return total;
+}
+
+static void rc_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_put_char"))
+ return;
+
+ if (!tty || !port->xmit_buf)
+ return;
+
+ save_flags(flags); cli();
+
+ if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
+ goto out;
+
+ port->xmit_buf[port->xmit_head++] = ch;
+ port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ port->xmit_cnt++;
+out: restore_flags(flags);
+}
+
+static void rc_flush_chars(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_flush_chars"))
+ return;
+
+ if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+ !port->xmit_buf)
+ return;
+
+ save_flags(flags); cli();
+ port->IER |= IER_TXRDY;
+ rc_out(port_Board(port), CD180_CAR, port_No(port));
+ rc_out(port_Board(port), CD180_IER, port->IER);
+ restore_flags(flags);
+}
+
+static int rc_write_room(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ int ret;
+
+ if (rc_paranoia_check(port, tty->name, "rc_write_room"))
+ return 0;
+
+ ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ return ret;
+}
+
+static int rc_chars_in_buffer(struct tty_struct *tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+
+ if (rc_paranoia_check(port, tty->name, "rc_chars_in_buffer"))
+ return 0;
+
+ return port->xmit_cnt;
+}
+
+static void rc_flush_buffer(struct tty_struct *tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_flush_buffer"))
+ return;
+
+ save_flags(flags); cli();
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ restore_flags(flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+static int rc_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board * bp;
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ bp = port_Board(port);
+ save_flags(flags); cli();
+ rc_out(bp, CD180_CAR, port_No(port));
+ status = rc_in(bp, CD180_MSVR);
+ result = rc_in(bp, RC_RI) & (1u << port_No(port)) ? 0 : TIOCM_RNG;
+ restore_flags(flags);
+ result |= ((status & MSVR_RTS) ? TIOCM_RTS : 0)
+ | ((status & MSVR_DTR) ? TIOCM_DTR : 0)
+ | ((status & MSVR_CD) ? TIOCM_CAR : 0)
+ | ((status & MSVR_DSR) ? TIOCM_DSR : 0)
+ | ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+ return result;
+}
+
+static int rc_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ unsigned long flags;
+ struct riscom_board *bp;
+
+ if (rc_paranoia_check(port, tty->name, __FUNCTION__))
+ return -ENODEV;
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+ if (set & TIOCM_RTS)
+ port->MSVR |= MSVR_RTS;
+ if (set & TIOCM_DTR)
+ bp->DTR &= ~(1u << port_No(port));
+
+ if (clear & TIOCM_RTS)
+ port->MSVR &= ~MSVR_RTS;
+ if (clear & TIOCM_DTR)
+ bp->DTR |= (1u << port_No(port));
+
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_MSVR, port->MSVR);
+ rc_out(bp, RC_DTR, bp->DTR);
+ restore_flags(flags);
+ return 0;
+}
+
+static inline void rc_send_break(struct riscom_port * port, unsigned long length)
+{
+ struct riscom_board *bp = port_Board(port);
+ unsigned long flags;
+
+ save_flags(flags); cli();
+ port->break_length = RISCOM_TPS / HZ * length;
+ port->COR2 |= COR2_ETC;
+ port->IER |= IER_TXRDY;
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_COR2, port->COR2);
+ rc_out(bp, CD180_IER, port->IER);
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_CORCHG2);
+ rc_wait_CCR(bp);
+ restore_flags(flags);
+}
+
+static inline int rc_set_serial_info(struct riscom_port * port,
+ struct serial_struct __user * newinfo)
+{
+ struct serial_struct tmp;
+ struct riscom_board *bp = port_Board(port);
+ int change_speed;
+ unsigned long flags;
+
+ if (copy_from_user(&tmp, newinfo, sizeof(tmp)))
+ return -EFAULT;
+
+#if 0
+ if ((tmp.irq != bp->irq) ||
+ (tmp.port != bp->base) ||
+ (tmp.type != PORT_CIRRUS) ||
+ (tmp.baud_base != (RC_OSCFREQ + CD180_TPC/2) / CD180_TPC) ||
+ (tmp.custom_divisor != 0) ||
+ (tmp.xmit_fifo_size != CD180_NFIFO) ||
+ (tmp.flags & ~RISCOM_LEGAL_FLAGS))
+ return -EINVAL;
+#endif
+
+ change_speed = ((port->flags & ASYNC_SPD_MASK) !=
+ (tmp.flags & ASYNC_SPD_MASK));
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((tmp.close_delay != port->close_delay) ||
+ (tmp.closing_wait != port->closing_wait) ||
+ ((tmp.flags & ~ASYNC_USR_MASK) !=
+ (port->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ port->flags = ((port->flags & ~ASYNC_USR_MASK) |
+ (tmp.flags & ASYNC_USR_MASK));
+ } else {
+ port->flags = ((port->flags & ~ASYNC_FLAGS) |
+ (tmp.flags & ASYNC_FLAGS));
+ port->close_delay = tmp.close_delay;
+ port->closing_wait = tmp.closing_wait;
+ }
+ if (change_speed) {
+ save_flags(flags); cli();
+ rc_change_speed(bp, port);
+ restore_flags(flags);
+ }
+ return 0;
+}
+
+static inline int rc_get_serial_info(struct riscom_port * port,
+ struct serial_struct __user *retinfo)
+{
+ struct serial_struct tmp;
+ struct riscom_board *bp = port_Board(port);
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = PORT_CIRRUS;
+ tmp.line = port - rc_port;
+ tmp.port = bp->base;
+ tmp.irq = bp->irq;
+ tmp.flags = port->flags;
+ tmp.baud_base = (RC_OSCFREQ + CD180_TPC/2) / CD180_TPC;
+ tmp.close_delay = port->close_delay * HZ/100;
+ tmp.closing_wait = port->closing_wait * HZ/100;
+ tmp.xmit_fifo_size = CD180_NFIFO;
+ return copy_to_user(retinfo, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int rc_ioctl(struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ void __user *argp = (void __user *)arg;
+ int retval;
+
+ if (rc_paranoia_check(port, tty->name, "rc_ioctl"))
+ return -ENODEV;
+
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ rc_send_break(port, HZ/4); /* 1/4 second */
+ break;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ tty_wait_until_sent(tty, 0);
+ rc_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+ break;
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned __user *)argp);
+ case TIOCSSOFTCAR:
+ if (get_user(arg,(unsigned __user *) argp))
+ return -EFAULT;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ break;
+ case TIOCGSERIAL:
+ return rc_get_serial_info(port, argp);
+ case TIOCSSERIAL:
+ return rc_set_serial_info(port, argp);
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void rc_throttle(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_throttle"))
+ return;
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+ port->MSVR &= ~MSVR_RTS;
+ rc_out(bp, CD180_CAR, port_No(port));
+ if (I_IXOFF(tty)) {
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_SSCH2);
+ rc_wait_CCR(bp);
+ }
+ rc_out(bp, CD180_MSVR, port->MSVR);
+ restore_flags(flags);
+}
+
+static void rc_unthrottle(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_unthrottle"))
+ return;
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+ port->MSVR |= MSVR_RTS;
+ rc_out(bp, CD180_CAR, port_No(port));
+ if (I_IXOFF(tty)) {
+ rc_wait_CCR(bp);
+ rc_out(bp, CD180_CCR, CCR_SSCH1);
+ rc_wait_CCR(bp);
+ }
+ rc_out(bp, CD180_MSVR, port->MSVR);
+ restore_flags(flags);
+}
+
+static void rc_stop(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_stop"))
+ return;
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+ port->IER &= ~IER_TXRDY;
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_IER, port->IER);
+ restore_flags(flags);
+}
+
+static void rc_start(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_start"))
+ return;
+
+ bp = port_Board(port);
+
+ save_flags(flags); cli();
+ if (port->xmit_cnt && port->xmit_buf && !(port->IER & IER_TXRDY)) {
+ port->IER |= IER_TXRDY;
+ rc_out(bp, CD180_CAR, port_No(port));
+ rc_out(bp, CD180_IER, port->IER);
+ }
+ restore_flags(flags);
+}
+
+/*
+ * This routine is called from the work queue when the interrupt
+ * routine has signalled that a hangup has occurred. The path of
+ * hangup processing is:
+ *
+ * serial interrupt routine -> (workqueue) ->
+ * do_rc_hangup() -> tty->hangup() -> rc_hangup()
+ *
+ */
+static void do_rc_hangup(void *private_)
+{
+ struct riscom_port *port = (struct riscom_port *) private_;
+ struct tty_struct *tty;
+
+ tty = port->tty;
+ if (tty)
+ tty_hangup(tty); /* FIXME: module removal race still here */
+}
+
+static void rc_hangup(struct tty_struct * tty)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ struct riscom_board *bp;
+
+ if (rc_paranoia_check(port, tty->name, "rc_hangup"))
+ return;
+
+ bp = port_Board(port);
+
+ rc_shutdown_port(bp, port);
+ port->event = 0;
+ port->count = 0;
+ port->flags &= ~ASYNC_NORMAL_ACTIVE;
+ port->tty = NULL;
+ wake_up_interruptible(&port->open_wait);
+}
+
+static void rc_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+ struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+ unsigned long flags;
+
+ if (rc_paranoia_check(port, tty->name, "rc_set_termios"))
+ return;
+
+ if (tty->termios->c_cflag == old_termios->c_cflag &&
+ tty->termios->c_iflag == old_termios->c_iflag)
+ return;
+
+ save_flags(flags); cli();
+ rc_change_speed(port_Board(port), port);
+ restore_flags(flags);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ rc_start(tty);
+ }
+}
+
+static void do_softint(void *private_)
+{
+ struct riscom_port *port = (struct riscom_port *) private_;
+ struct tty_struct *tty;
+
+ if(!(tty = port->tty))
+ return;
+
+ if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+}
+
+static struct tty_operations riscom_ops = {
+ .open = rc_open,
+ .close = rc_close,
+ .write = rc_write,
+ .put_char = rc_put_char,
+ .flush_chars = rc_flush_chars,
+ .write_room = rc_write_room,
+ .chars_in_buffer = rc_chars_in_buffer,
+ .flush_buffer = rc_flush_buffer,
+ .ioctl = rc_ioctl,
+ .throttle = rc_throttle,
+ .unthrottle = rc_unthrottle,
+ .set_termios = rc_set_termios,
+ .stop = rc_stop,
+ .start = rc_start,
+ .hangup = rc_hangup,
+ .tiocmget = rc_tiocmget,
+ .tiocmset = rc_tiocmset,
+};
+
+static inline int rc_init_drivers(void)
+{
+ int error;
+ int i;
+
+ riscom_driver = alloc_tty_driver(RC_NBOARD * RC_NPORT);
+ if (!riscom_driver)
+ return -ENOMEM;
+
+ if (!(tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL))) {
+ printk(KERN_ERR "rc: Couldn't get free page.\n");
+ put_tty_driver(riscom_driver);
+ return 1;
+ }
+ memset(IRQ_to_board, 0, sizeof(IRQ_to_board));
+ riscom_driver->owner = THIS_MODULE;
+ riscom_driver->name = "ttyL";
+ riscom_driver->devfs_name = "tts/L";
+ riscom_driver->major = RISCOM8_NORMAL_MAJOR;
+ riscom_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ riscom_driver->subtype = SERIAL_TYPE_NORMAL;
+ riscom_driver->init_termios = tty_std_termios;
+ riscom_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ riscom_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(riscom_driver, &riscom_ops);
+ if ((error = tty_register_driver(riscom_driver))) {
+ free_page((unsigned long)tmp_buf);
+ put_tty_driver(riscom_driver);
+ printk(KERN_ERR "rc: Couldn't register RISCom/8 driver, "
+ "error = %d\n",
+ error);
+ return 1;
+ }
+
+ memset(rc_port, 0, sizeof(rc_port));
+ for (i = 0; i < RC_NPORT * RC_NBOARD; i++) {
+ rc_port[i].magic = RISCOM8_MAGIC;
+ INIT_WORK(&rc_port[i].tqueue, do_softint, &rc_port[i]);
+ INIT_WORK(&rc_port[i].tqueue_hangup, do_rc_hangup, &rc_port[i]);
+ rc_port[i].close_delay = 50 * HZ/100;
+ rc_port[i].closing_wait = 3000 * HZ/100;
+ init_waitqueue_head(&rc_port[i].open_wait);
+ init_waitqueue_head(&rc_port[i].close_wait);
+ }
+
+ return 0;
+}
+
+static void rc_release_drivers(void)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+ free_page((unsigned long)tmp_buf);
+ tty_unregister_driver(riscom_driver);
+ put_tty_driver(riscom_driver);
+ restore_flags(flags);
+}
+
+#ifndef MODULE
+/*
+ * Called at boot time.
+ *
+ * You can specify IO base for up to RC_NBOARD cards,
+ * using line "riscom8=0xiobase1,0xiobase2,.." at LILO prompt.
+ * Note that there will be no probing at default
+ * addresses in this case.
+ *
+ */
+static int __init riscom8_setup(char *str)
+{
+ int ints[RC_NBOARD];
+ int i;
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ for (i = 0; i < RC_NBOARD; i++) {
+ if (i < ints[0])
+ rc_board[i].base = ints[i+1];
+ else
+ rc_board[i].base = 0;
+ }
+ return 1;
+}
+
+__setup("riscom8=", riscom8_setup);
+#endif
+
+static char banner[] __initdata =
+ KERN_INFO "rc: SDL RISCom/8 card driver v1.1, (c) D.Gorodchanin "
+ "1994-1996.\n";
+static char no_boards_msg[] __initdata =
+ KERN_INFO "rc: No RISCom/8 boards detected.\n";
+
+/*
+ * This routine must be called by kernel at boot time
+ */
+static int __init riscom8_init(void)
+{
+ int i;
+ int found = 0;
+
+ printk(banner);
+
+ if (rc_init_drivers())
+ return -EIO;
+
+ for (i = 0; i < RC_NBOARD; i++)
+ if (rc_board[i].base && !rc_probe(&rc_board[i]))
+ found++;
+
+ if (!found) {
+ rc_release_drivers();
+ printk(no_boards_msg);
+ return -EIO;
+ }
+ return 0;
+}
+
+#ifdef MODULE
+static int iobase;
+static int iobase1;
+static int iobase2;
+static int iobase3;
+MODULE_PARM(iobase, "i");
+MODULE_PARM(iobase1, "i");
+MODULE_PARM(iobase2, "i");
+MODULE_PARM(iobase3, "i");
+
+MODULE_LICENSE("GPL");
+#endif /* MODULE */
+
+/*
+ * You can setup up to 4 boards (current value of RC_NBOARD)
+ * by specifying "iobase=0xXXX iobase1=0xXXX ..." as insmod parameter.
+ *
+ */
+static int __init riscom8_init_module (void)
+{
+#ifdef MODULE
+ int i;
+
+ if (iobase || iobase1 || iobase2 || iobase3) {
+ for(i = 0; i < RC_NBOARD; i++)
+ rc_board[0].base = 0;
+ }
+
+ if (iobase)
+ rc_board[0].base = iobase;
+ if (iobase1)
+ rc_board[1].base = iobase1;
+ if (iobase2)
+ rc_board[2].base = iobase2;
+ if (iobase3)
+ rc_board[3].base = iobase3;
+#endif /* MODULE */
+
+ return riscom8_init();
+}
+
+static void __exit riscom8_exit_module (void)
+{
+ int i;
+
+ rc_release_drivers();
+ for (i = 0; i < RC_NBOARD; i++)
+ if (rc_board[i].flags & RC_BOARD_PRESENT)
+ rc_release_io_range(&rc_board[i]);
+
+}
+
+module_init(riscom8_init_module);
+module_exit(riscom8_exit_module);
+
diff --git a/drivers/char/riscom8.h b/drivers/char/riscom8.h
new file mode 100644
index 000000000000..6317aade201a
--- /dev/null
+++ b/drivers/char/riscom8.h
@@ -0,0 +1,102 @@
+/*
+ * linux/drivers/char/riscom8.h -- RISCom/8 multiport serial driver.
+ *
+ * Copyright (C) 1994-1996 Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others. The RISCom/8 card
+ * programming info was obtained from various drivers for other OSes
+ * (FreeBSD, ISC, etc), but no source code from those drivers were
+ * directly included in this driver.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef __LINUX_RISCOM8_H
+#define __LINUX_RISCOM8_H
+
+#include <linux/serial.h>
+
+#ifdef __KERNEL__
+
+#define RC_NBOARD 4
+/* NOTE: RISCom decoder recognizes 16 addresses... */
+#define RC_NPORT 8
+#define RC_BOARD(line) (((line) >> 3) & 0x07)
+#define RC_PORT(line) ((line) & (RC_NPORT - 1))
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define RISCOM_TPS 4000
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define RISCOM_RXFIFO 6 /* Max. receiver FIFO size (1-8) */
+
+#define RISCOM8_MAGIC 0x0907
+
+#define RC_IOBASE1 0x220
+#define RC_IOBASE2 0x240
+#define RC_IOBASE3 0x250
+#define RC_IOBASE4 0x260
+
+struct riscom_board {
+ unsigned long flags;
+ unsigned short base;
+ unsigned char irq;
+ signed char count;
+ unsigned char DTR;
+};
+
+#define RC_BOARD_PRESENT 0x00000001
+#define RC_BOARD_ACTIVE 0x00000002
+
+struct riscom_port {
+ int magic;
+ int baud_base;
+ int flags;
+ struct tty_struct * tty;
+ int count;
+ int blocked_open;
+ long event; /* long req'd for set_bit --RR */
+ int timeout;
+ int close_delay;
+ unsigned char * xmit_buf;
+ int custom_divisor;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct work_struct tqueue;
+ struct work_struct tqueue_hangup;
+ short wakeup_chars;
+ short break_length;
+ unsigned short closing_wait;
+ unsigned char mark_mask;
+ unsigned char IER;
+ unsigned char MSVR;
+ unsigned char COR2;
+#ifdef RC_REPORT_OVERRUN
+ unsigned long overrun;
+#endif
+#ifdef RC_REPORT_FIFO
+ unsigned long hits[10];
+#endif
+};
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_RISCOM8_H */
diff --git a/drivers/char/riscom8_reg.h b/drivers/char/riscom8_reg.h
new file mode 100644
index 000000000000..a32475ed0d18
--- /dev/null
+++ b/drivers/char/riscom8_reg.h
@@ -0,0 +1,254 @@
+/*
+ * linux/drivers/char/riscom8_reg.h -- RISCom/8 multiport serial driver.
+ */
+
+/*
+ * Definitions for RISCom/8 Async Mux card by SDL Communications, Inc.
+ */
+
+/*
+ * Address mapping between Cirrus Logic CD180 chip internal registers
+ * and ISA port addresses:
+ *
+ * CL-CD180 A6 A5 A4 A3 A2 A1 A0
+ * ISA A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
+ */
+#define RC_TO_ISA(r) ((((r)&0x07)<<1) | (((r)&~0x07)<<7))
+
+
+/* RISCom/8 On-Board Registers (assuming address translation) */
+
+#define RC_RI 0x100 /* Ring Indicator Register (R/O) */
+#define RC_DTR 0x100 /* DTR Register (W/O) */
+#define RC_BSR 0x101 /* Board Status Register (R/O) */
+#define RC_CTOUT 0x101 /* Clear Timeout (W/O) */
+
+
+/* Board Status Register */
+
+#define RC_BSR_TOUT 0x08 /* Hardware Timeout */
+#define RC_BSR_RINT 0x04 /* Receiver Interrupt */
+#define RC_BSR_TINT 0x02 /* Transmitter Interrupt */
+#define RC_BSR_MINT 0x01 /* Modem Ctl Interrupt */
+
+
+/* On-board oscillator frequency (in Hz) */
+#define RC_OSCFREQ 9830400
+
+/* Values of choice for Interrupt ACKs */
+#define RC_ACK_MINT 0x81 /* goes to PILR1 */
+#define RC_ACK_RINT 0x82 /* goes to PILR3 */
+#define RC_ACK_TINT 0x84 /* goes to PILR2 */
+
+/* Chip ID (sorry, only one chip now) */
+#define RC_ID 0x10
+
+/* Definitions for Cirrus Logic CL-CD180 8-port async mux chip */
+
+#define CD180_NCH 8 /* Total number of channels */
+#define CD180_TPC 16 /* Ticks per character */
+#define CD180_NFIFO 8 /* TX FIFO size */
+
+
+/* Global registers */
+
+#define CD180_GIVR 0x40 /* Global Interrupt Vector Register */
+#define CD180_GICR 0x41 /* Global Interrupting Channel Register */
+#define CD180_PILR1 0x61 /* Priority Interrupt Level Register 1 */
+#define CD180_PILR2 0x62 /* Priority Interrupt Level Register 2 */
+#define CD180_PILR3 0x63 /* Priority Interrupt Level Register 3 */
+#define CD180_CAR 0x64 /* Channel Access Register */
+#define CD180_GFRCR 0x6b /* Global Firmware Revision Code Register */
+#define CD180_PPRH 0x70 /* Prescaler Period Register High */
+#define CD180_PPRL 0x71 /* Prescaler Period Register Low */
+#define CD180_RDR 0x78 /* Receiver Data Register */
+#define CD180_RCSR 0x7a /* Receiver Character Status Register */
+#define CD180_TDR 0x7b /* Transmit Data Register */
+#define CD180_EOIR 0x7f /* End of Interrupt Register */
+
+
+/* Channel Registers */
+
+#define CD180_CCR 0x01 /* Channel Command Register */
+#define CD180_IER 0x02 /* Interrupt Enable Register */
+#define CD180_COR1 0x03 /* Channel Option Register 1 */
+#define CD180_COR2 0x04 /* Channel Option Register 2 */
+#define CD180_COR3 0x05 /* Channel Option Register 3 */
+#define CD180_CCSR 0x06 /* Channel Control Status Register */
+#define CD180_RDCR 0x07 /* Receive Data Count Register */
+#define CD180_SCHR1 0x09 /* Special Character Register 1 */
+#define CD180_SCHR2 0x0a /* Special Character Register 2 */
+#define CD180_SCHR3 0x0b /* Special Character Register 3 */
+#define CD180_SCHR4 0x0c /* Special Character Register 4 */
+#define CD180_MCOR1 0x10 /* Modem Change Option 1 Register */
+#define CD180_MCOR2 0x11 /* Modem Change Option 2 Register */
+#define CD180_MCR 0x12 /* Modem Change Register */
+#define CD180_RTPR 0x18 /* Receive Timeout Period Register */
+#define CD180_MSVR 0x28 /* Modem Signal Value Register */
+#define CD180_RBPRH 0x31 /* Receive Baud Rate Period Register High */
+#define CD180_RBPRL 0x32 /* Receive Baud Rate Period Register Low */
+#define CD180_TBPRH 0x39 /* Transmit Baud Rate Period Register High */
+#define CD180_TBPRL 0x3a /* Transmit Baud Rate Period Register Low */
+
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GIVR_ITMASK 0x07 /* Interrupt type mask */
+#define GIVR_IT_MODEM 0x01 /* Modem Signal Change Interrupt */
+#define GIVR_IT_TX 0x02 /* Transmit Data Interrupt */
+#define GIVR_IT_RCV 0x03 /* Receive Good Data Interrupt */
+#define GIVR_IT_REXC 0x07 /* Receive Exception Interrupt */
+
+
+/* Global Interrupt Channel Register (R/W) */
+
+#define GICR_CHAN 0x1c /* Channel Number Mask */
+#define GICR_CHAN_OFF 2 /* Channel Number Offset */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN 0x07 /* Channel Number Mask */
+#define CAR_A7 0x08 /* A7 Address Extension (unused) */
+
+
+/* Receive Character Status Register (R/O) */
+
+#define RCSR_TOUT 0x80 /* Rx Timeout */
+#define RCSR_SCDET 0x70 /* Special Character Detected Mask */
+#define RCSR_NO_SC 0x00 /* No Special Characters Detected */
+#define RCSR_SC_1 0x10 /* Special Char 1 (or 1 & 3) Detected */
+#define RCSR_SC_2 0x20 /* Special Char 2 (or 2 & 4) Detected */
+#define RCSR_SC_3 0x30 /* Special Char 3 Detected */
+#define RCSR_SC_4 0x40 /* Special Char 4 Detected */
+#define RCSR_BREAK 0x08 /* Break has been detected */
+#define RCSR_PE 0x04 /* Parity Error */
+#define RCSR_FE 0x02 /* Frame Error */
+#define RCSR_OE 0x01 /* Overrun Error */
+
+
+/* Channel Command Register (R/W) (commands in groups can be OR-ed) */
+
+#define CCR_HARDRESET 0x81 /* Reset the chip */
+
+#define CCR_SOFTRESET 0x80 /* Soft Channel Reset */
+
+#define CCR_CORCHG1 0x42 /* Channel Option Register 1 Changed */
+#define CCR_CORCHG2 0x44 /* Channel Option Register 2 Changed */
+#define CCR_CORCHG3 0x48 /* Channel Option Register 3 Changed */
+
+#define CCR_SSCH1 0x21 /* Send Special Character 1 */
+
+#define CCR_SSCH2 0x22 /* Send Special Character 2 */
+
+#define CCR_SSCH3 0x23 /* Send Special Character 3 */
+
+#define CCR_SSCH4 0x24 /* Send Special Character 4 */
+
+#define CCR_TXEN 0x18 /* Enable Transmitter */
+#define CCR_RXEN 0x12 /* Enable Receiver */
+
+#define CCR_TXDIS 0x14 /* Disable Transmitter */
+#define CCR_RXDIS 0x11 /* Disable Receiver */
+
+
+/* Interrupt Enable Register (R/W) */
+
+#define IER_DSR 0x80 /* Enable interrupt on DSR change */
+#define IER_CD 0x40 /* Enable interrupt on CD change */
+#define IER_CTS 0x20 /* Enable interrupt on CTS change */
+#define IER_RXD 0x10 /* Enable interrupt on Receive Data */
+#define IER_RXSC 0x08 /* Enable interrupt on Receive Spec. Char */
+#define IER_TXRDY 0x04 /* Enable interrupt on TX FIFO empty */
+#define IER_TXEMPTY 0x02 /* Enable interrupt on TX completely empty */
+#define IER_RET 0x01 /* Enable interrupt on RX Exc. Timeout */
+
+
+/* Channel Option Register 1 (R/W) */
+
+#define COR1_ODDP 0x80 /* Odd Parity */
+#define COR1_PARMODE 0x60 /* Parity Mode mask */
+#define COR1_NOPAR 0x00 /* No Parity */
+#define COR1_FORCEPAR 0x20 /* Force Parity */
+#define COR1_NORMPAR 0x40 /* Normal Parity */
+#define COR1_IGNORE 0x10 /* Ignore Parity on RX */
+#define COR1_STOPBITS 0x0c /* Number of Stop Bits */
+#define COR1_1SB 0x00 /* 1 Stop Bit */
+#define COR1_15SB 0x04 /* 1.5 Stop Bits */
+#define COR1_2SB 0x08 /* 2 Stop Bits */
+#define COR1_CHARLEN 0x03 /* Character Length */
+#define COR1_5BITS 0x00 /* 5 bits */
+#define COR1_6BITS 0x01 /* 6 bits */
+#define COR1_7BITS 0x02 /* 7 bits */
+#define COR1_8BITS 0x03 /* 8 bits */
+
+
+/* Channel Option Register 2 (R/W) */
+
+#define COR2_IXM 0x80 /* Implied XON mode */
+#define COR2_TXIBE 0x40 /* Enable In-Band (XON/XOFF) Flow Control */
+#define COR2_ETC 0x20 /* Embedded Tx Commands Enable */
+#define COR2_LLM 0x10 /* Local Loopback Mode */
+#define COR2_RLM 0x08 /* Remote Loopback Mode */
+#define COR2_RTSAO 0x04 /* RTS Automatic Output Enable */
+#define COR2_CTSAE 0x02 /* CTS Automatic Enable */
+#define COR2_DSRAE 0x01 /* DSR Automatic Enable */
+
+
+/* Channel Option Register 3 (R/W) */
+
+#define COR3_XONCH 0x80 /* XON is a pair of characters (1 & 3) */
+#define COR3_XOFFCH 0x40 /* XOFF is a pair of characters (2 & 4) */
+#define COR3_FCT 0x20 /* Flow-Control Transparency Mode */
+#define COR3_SCDE 0x10 /* Special Character Detection Enable */
+#define COR3_RXTH 0x0f /* RX FIFO Threshold value (1-8) */
+
+
+/* Channel Control Status Register (R/O) */
+
+#define CCSR_RXEN 0x80 /* Receiver Enabled */
+#define CCSR_RXFLOFF 0x40 /* Receive Flow Off (XOFF was sent) */
+#define CCSR_RXFLON 0x20 /* Receive Flow On (XON was sent) */
+#define CCSR_TXEN 0x08 /* Transmitter Enabled */
+#define CCSR_TXFLOFF 0x04 /* Transmit Flow Off (got XOFF) */
+#define CCSR_TXFLON 0x02 /* Transmit Flow On (got XON) */
+
+
+/* Modem Change Option Register 1 (R/W) */
+
+#define MCOR1_DSRZD 0x80 /* Detect 0->1 transition of DSR */
+#define MCOR1_CDZD 0x40 /* Detect 0->1 transition of CD */
+#define MCOR1_CTSZD 0x20 /* Detect 0->1 transition of CTS */
+#define MCOR1_DTRTH 0x0f /* Auto DTR flow control Threshold (1-8) */
+#define MCOR1_NODTRFC 0x0 /* Automatic DTR flow control disabled */
+
+
+/* Modem Change Option Register 2 (R/W) */
+
+#define MCOR2_DSROD 0x80 /* Detect 1->0 transition of DSR */
+#define MCOR2_CDOD 0x40 /* Detect 1->0 transition of CD */
+#define MCOR2_CTSOD 0x20 /* Detect 1->0 transition of CTS */
+
+
+/* Modem Change Register (R/W) */
+
+#define MCR_DSRCHG 0x80 /* DSR Changed */
+#define MCR_CDCHG 0x40 /* CD Changed */
+#define MCR_CTSCHG 0x20 /* CTS Changed */
+
+
+/* Modem Signal Value Register (R/W) */
+
+#define MSVR_DSR 0x80 /* Current state of DSR input */
+#define MSVR_CD 0x40 /* Current state of CD input */
+#define MSVR_CTS 0x20 /* Current state of CTS input */
+#define MSVR_DTR 0x02 /* Current state of DTR output */
+#define MSVR_RTS 0x01 /* Current state of RTS output */
+
+
+/* Escape characters */
+
+#define CD180_C_ESC 0x00 /* Escape character */
+#define CD180_C_SBRK 0x81 /* Start sending BREAK */
+#define CD180_C_DELAY 0x82 /* Delay output */
+#define CD180_C_EBRK 0x83 /* Stop sending BREAK */
diff --git a/drivers/char/rocket.c b/drivers/char/rocket.c
new file mode 100644
index 000000000000..5bcbeb0cb9ae
--- /dev/null
+++ b/drivers/char/rocket.c
@@ -0,0 +1,3299 @@
+/*
+ * RocketPort device driver for Linux
+ *
+ * Written by Theodore Ts'o, 1995, 1996, 1997, 1998, 1999, 2000.
+ *
+ * Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2003 by Comtrol, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Kernel Synchronization:
+ *
+ * This driver has 2 kernel control paths - exception handlers (calls into the driver
+ * from user mode) and the timer bottom half (tasklet). This is a polled driver, interrupts
+ * are not used.
+ *
+ * Critical data:
+ * - rp_table[], accessed through passed "info" pointers, is a global (static) array of
+ * serial port state information and the xmit_buf circular buffer. Protected by
+ * a per port spinlock.
+ * - xmit_flags[], an array of ints indexed by line (port) number, indicating that there
+ * is data to be transmitted. Protected by atomic bit operations.
+ * - rp_num_ports, int indicating number of open ports, protected by atomic operations.
+ *
+ * rp_write() and rp_write_char() functions use a per port semaphore to protect against
+ * simultaneous access to the same port by more than one process.
+ */
+
+/****** Defines ******/
+#ifdef PCI_NUM_RESOURCES
+#define PCI_BASE_ADDRESS(dev, r) ((dev)->resource[r].start)
+#else
+#define PCI_BASE_ADDRESS(dev, r) ((dev)->base_address[r])
+#endif
+
+#define ROCKET_PARANOIA_CHECK
+#define ROCKET_DISABLE_SIMUSAGE
+
+#undef ROCKET_SOFT_FLOW
+#undef ROCKET_DEBUG_OPEN
+#undef ROCKET_DEBUG_INTR
+#undef ROCKET_DEBUG_WRITE
+#undef ROCKET_DEBUG_FLOW
+#undef ROCKET_DEBUG_THROTTLE
+#undef ROCKET_DEBUG_WAIT_UNTIL_SENT
+#undef ROCKET_DEBUG_RECEIVE
+#undef ROCKET_DEBUG_HANGUP
+#undef REV_PCI_ORDER
+#undef ROCKET_DEBUG_IO
+
+#define POLL_PERIOD HZ/100 /* Polling period .01 seconds (10ms) */
+
+/****** Kernel includes ******/
+
+#ifdef MODVERSIONS
+#include <config/modversions.h>
+#endif
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/pci.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+/****** RocketPort includes ******/
+
+#include "rocket_int.h"
+#include "rocket.h"
+
+#define ROCKET_VERSION "2.09"
+#define ROCKET_DATE "12-June-2003"
+
+/****** RocketPort Local Variables ******/
+
+static struct tty_driver *rocket_driver;
+
+static struct rocket_version driver_version = {
+ ROCKET_VERSION, ROCKET_DATE
+};
+
+static struct r_port *rp_table[MAX_RP_PORTS]; /* The main repository of serial port state information. */
+static unsigned int xmit_flags[NUM_BOARDS]; /* Bit significant, indicates port had data to transmit. */
+ /* eg. Bit 0 indicates port 0 has xmit data, ... */
+static atomic_t rp_num_ports_open; /* Number of serial ports open */
+static struct timer_list rocket_timer;
+
+static unsigned long board1; /* ISA addresses, retrieved from rocketport.conf */
+static unsigned long board2;
+static unsigned long board3;
+static unsigned long board4;
+static unsigned long controller;
+static int support_low_speed;
+static unsigned long modem1;
+static unsigned long modem2;
+static unsigned long modem3;
+static unsigned long modem4;
+static unsigned long pc104_1[8];
+static unsigned long pc104_2[8];
+static unsigned long pc104_3[8];
+static unsigned long pc104_4[8];
+static unsigned long *pc104[4] = { pc104_1, pc104_2, pc104_3, pc104_4 };
+
+static int rp_baud_base[NUM_BOARDS]; /* Board config info (Someday make a per-board structure) */
+static unsigned long rcktpt_io_addr[NUM_BOARDS];
+static int rcktpt_type[NUM_BOARDS];
+static int is_PCI[NUM_BOARDS];
+static rocketModel_t rocketModel[NUM_BOARDS];
+static int max_board;
+
+/*
+ * The following arrays define the interrupt bits corresponding to each AIOP.
+ * These bits are different between the ISA and regular PCI boards and the
+ * Universal PCI boards.
+ */
+
+static Word_t aiop_intr_bits[AIOP_CTL_SIZE] = {
+ AIOP_INTR_BIT_0,
+ AIOP_INTR_BIT_1,
+ AIOP_INTR_BIT_2,
+ AIOP_INTR_BIT_3
+};
+
+static Word_t upci_aiop_intr_bits[AIOP_CTL_SIZE] = {
+ UPCI_AIOP_INTR_BIT_0,
+ UPCI_AIOP_INTR_BIT_1,
+ UPCI_AIOP_INTR_BIT_2,
+ UPCI_AIOP_INTR_BIT_3
+};
+
+/*
+ * Line number is the ttySIx number (x), the Minor number. We
+ * assign them sequentially, starting at zero. The following
+ * array keeps track of the line number assigned to a given board/aiop/channel.
+ */
+static unsigned char lineNumbers[MAX_RP_PORTS];
+static unsigned long nextLineNumber;
+
+/***** RocketPort Static Prototypes *********/
+static int __init init_ISA(int i);
+static void rp_wait_until_sent(struct tty_struct *tty, int timeout);
+static void rp_flush_buffer(struct tty_struct *tty);
+static void rmSpeakerReset(CONTROLLER_T * CtlP, unsigned long model);
+static unsigned char GetLineNumber(int ctrl, int aiop, int ch);
+static unsigned char SetLineNumber(int ctrl, int aiop, int ch);
+static void rp_start(struct tty_struct *tty);
+
+#ifdef MODULE
+MODULE_AUTHOR("Theodore Ts'o");
+MODULE_DESCRIPTION("Comtrol RocketPort driver");
+module_param(board1, ulong, 0);
+MODULE_PARM_DESC(board1, "I/O port for (ISA) board #1");
+module_param(board2, ulong, 0);
+MODULE_PARM_DESC(board2, "I/O port for (ISA) board #2");
+module_param(board3, ulong, 0);
+MODULE_PARM_DESC(board3, "I/O port for (ISA) board #3");
+module_param(board4, ulong, 0);
+MODULE_PARM_DESC(board4, "I/O port for (ISA) board #4");
+module_param(controller, ulong, 0);
+MODULE_PARM_DESC(controller, "I/O port for (ISA) rocketport controller");
+module_param(support_low_speed, bool, 0);
+MODULE_PARM_DESC(support_low_speed, "1 means support 50 baud, 0 means support 460400 baud");
+module_param(modem1, ulong, 0);
+MODULE_PARM_DESC(modem1, "1 means (ISA) board #1 is a RocketModem");
+module_param(modem2, ulong, 0);
+MODULE_PARM_DESC(modem2, "1 means (ISA) board #2 is a RocketModem");
+module_param(modem3, ulong, 0);
+MODULE_PARM_DESC(modem3, "1 means (ISA) board #3 is a RocketModem");
+module_param(modem4, ulong, 0);
+MODULE_PARM_DESC(modem4, "1 means (ISA) board #4 is a RocketModem");
+module_param_array(pc104_1, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_1, "set interface types for ISA(PC104) board #1 (e.g. pc104_1=232,232,485,485,...");
+module_param_array(pc104_2, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_2, "set interface types for ISA(PC104) board #2 (e.g. pc104_2=232,232,485,485,...");
+module_param_array(pc104_3, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_3, "set interface types for ISA(PC104) board #3 (e.g. pc104_3=232,232,485,485,...");
+module_param_array(pc104_4, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_4, "set interface types for ISA(PC104) board #4 (e.g. pc104_4=232,232,485,485,...");
+
+int rp_init(void);
+static void rp_cleanup_module(void);
+
+module_init(rp_init);
+module_exit(rp_cleanup_module);
+
+#endif
+
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("Dual BSD/GPL");
+#endif
+
+/*************************************************************************/
+/* Module code starts here */
+
+static inline int rocket_paranoia_check(struct r_port *info,
+ const char *routine)
+{
+#ifdef ROCKET_PARANOIA_CHECK
+ if (!info)
+ return 1;
+ if (info->magic != RPORT_MAGIC) {
+ printk(KERN_INFO "Warning: bad magic number for rocketport struct in %s\n",
+ routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+
+/* Serial port receive data function. Called (from timer poll) when an AIOPIC signals
+ * that receive data is present on a serial port. Pulls data from FIFO, moves it into the
+ * tty layer.
+ */
+static void rp_do_receive(struct r_port *info,
+ struct tty_struct *tty,
+ CHANNEL_t * cp, unsigned int ChanStatus)
+{
+ unsigned int CharNStat;
+ int ToRecv, wRecv, space = 0, count;
+ unsigned char *cbuf;
+ char *fbuf;
+ struct tty_ldisc *ld;
+
+ ld = tty_ldisc_ref(tty);
+
+ ToRecv = sGetRxCnt(cp);
+ if (ld)
+ space = ld->receive_room(tty);
+ if (space > 2 * TTY_FLIPBUF_SIZE)
+ space = 2 * TTY_FLIPBUF_SIZE;
+ cbuf = tty->flip.char_buf;
+ fbuf = tty->flip.flag_buf;
+ count = 0;
+#ifdef ROCKET_DEBUG_INTR
+ printk(KERN_INFO "rp_do_receive(%d, %d)...", ToRecv, space);
+#endif
+
+ /*
+ * determine how many we can actually read in. If we can't
+ * read any in then we have a software overrun condition.
+ */
+ if (ToRecv > space)
+ ToRecv = space;
+
+ if (ToRecv <= 0)
+ return;
+
+ /*
+ * if status indicates there are errored characters in the
+ * FIFO, then enter status mode (a word in FIFO holds
+ * character and status).
+ */
+ if (ChanStatus & (RXFOVERFL | RXBREAK | RXFRAME | RXPARITY)) {
+ if (!(ChanStatus & STATMODE)) {
+#ifdef ROCKET_DEBUG_RECEIVE
+ printk(KERN_INFO "Entering STATMODE...");
+#endif
+ ChanStatus |= STATMODE;
+ sEnRxStatusMode(cp);
+ }
+ }
+
+ /*
+ * if we previously entered status mode, then read down the
+ * FIFO one word at a time, pulling apart the character and
+ * the status. Update error counters depending on status
+ */
+ if (ChanStatus & STATMODE) {
+#ifdef ROCKET_DEBUG_RECEIVE
+ printk(KERN_INFO "Ignore %x, read %x...", info->ignore_status_mask,
+ info->read_status_mask);
+#endif
+ while (ToRecv) {
+ CharNStat = sInW(sGetTxRxDataIO(cp));
+#ifdef ROCKET_DEBUG_RECEIVE
+ printk(KERN_INFO "%x...", CharNStat);
+#endif
+ if (CharNStat & STMBREAKH)
+ CharNStat &= ~(STMFRAMEH | STMPARITYH);
+ if (CharNStat & info->ignore_status_mask) {
+ ToRecv--;
+ continue;
+ }
+ CharNStat &= info->read_status_mask;
+ if (CharNStat & STMBREAKH)
+ *fbuf++ = TTY_BREAK;
+ else if (CharNStat & STMPARITYH)
+ *fbuf++ = TTY_PARITY;
+ else if (CharNStat & STMFRAMEH)
+ *fbuf++ = TTY_FRAME;
+ else if (CharNStat & STMRCVROVRH)
+ *fbuf++ = TTY_OVERRUN;
+ else
+ *fbuf++ = 0;
+ *cbuf++ = CharNStat & 0xff;
+ count++;
+ ToRecv--;
+ }
+
+ /*
+ * after we've emptied the FIFO in status mode, turn
+ * status mode back off
+ */
+ if (sGetRxCnt(cp) == 0) {
+#ifdef ROCKET_DEBUG_RECEIVE
+ printk(KERN_INFO "Status mode off.\n");
+#endif
+ sDisRxStatusMode(cp);
+ }
+ } else {
+ /*
+ * we aren't in status mode, so read down the FIFO two
+ * characters at time by doing repeated word IO
+ * transfer.
+ */
+ wRecv = ToRecv >> 1;
+ if (wRecv)
+ sInStrW(sGetTxRxDataIO(cp), (unsigned short *) cbuf, wRecv);
+ if (ToRecv & 1)
+ cbuf[ToRecv - 1] = sInB(sGetTxRxDataIO(cp));
+ memset(fbuf, 0, ToRecv);
+ cbuf += ToRecv;
+ fbuf += ToRecv;
+ count += ToRecv;
+ }
+ /* Push the data up to the tty layer */
+ ld->receive_buf(tty, tty->flip.char_buf, tty->flip.flag_buf, count);
+ tty_ldisc_deref(ld);
+}
+
+/*
+ * Serial port transmit data function. Called from the timer polling loop as a
+ * result of a bit set in xmit_flags[], indicating data (from the tty layer) is ready
+ * to be sent out the serial port. Data is buffered in rp_table[line].xmit_buf, it is
+ * moved to the port's xmit FIFO. *info is critical data, protected by spinlocks.
+ */
+static void rp_do_transmit(struct r_port *info)
+{
+ int c;
+ CHANNEL_t *cp = &info->channel;
+ struct tty_struct *tty;
+ unsigned long flags;
+
+#ifdef ROCKET_DEBUG_INTR
+ printk(KERN_INFO "rp_do_transmit ");
+#endif
+ if (!info)
+ return;
+ if (!info->tty) {
+ printk(KERN_INFO "rp: WARNING rp_do_transmit called with info->tty==NULL\n");
+ clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+ return;
+ }
+
+ spin_lock_irqsave(&info->slock, flags);
+ tty = info->tty;
+ info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+ /* Loop sending data to FIFO until done or FIFO full */
+ while (1) {
+ if (tty->stopped || tty->hw_stopped)
+ break;
+ c = min(info->xmit_fifo_room, min(info->xmit_cnt, XMIT_BUF_SIZE - info->xmit_tail));
+ if (c <= 0 || info->xmit_fifo_room <= 0)
+ break;
+ sOutStrW(sGetTxRxDataIO(cp), (unsigned short *) (info->xmit_buf + info->xmit_tail), c / 2);
+ if (c & 1)
+ sOutB(sGetTxRxDataIO(cp), info->xmit_buf[info->xmit_tail + c - 1]);
+ info->xmit_tail += c;
+ info->xmit_tail &= XMIT_BUF_SIZE - 1;
+ info->xmit_cnt -= c;
+ info->xmit_fifo_room -= c;
+#ifdef ROCKET_DEBUG_INTR
+ printk(KERN_INFO "tx %d chars...", c);
+#endif
+ }
+
+ if (info->xmit_cnt == 0)
+ clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+ }
+
+ spin_unlock_irqrestore(&info->slock, flags);
+
+#ifdef ROCKET_DEBUG_INTR
+ printk(KERN_INFO "(%d,%d,%d,%d)...", info->xmit_cnt, info->xmit_head,
+ info->xmit_tail, info->xmit_fifo_room);
+#endif
+}
+
+/*
+ * Called when a serial port signals it has read data in it's RX FIFO.
+ * It checks what interrupts are pending and services them, including
+ * receiving serial data.
+ */
+static void rp_handle_port(struct r_port *info)
+{
+ CHANNEL_t *cp;
+ struct tty_struct *tty;
+ unsigned int IntMask, ChanStatus;
+
+ if (!info)
+ return;
+
+ if ((info->flags & ROCKET_INITIALIZED) == 0) {
+ printk(KERN_INFO "rp: WARNING: rp_handle_port called with info->flags & NOT_INIT\n");
+ return;
+ }
+ if (!info->tty) {
+ printk(KERN_INFO "rp: WARNING: rp_handle_port called with info->tty==NULL\n");
+ return;
+ }
+ cp = &info->channel;
+ tty = info->tty;
+
+ IntMask = sGetChanIntID(cp) & info->intmask;
+#ifdef ROCKET_DEBUG_INTR
+ printk(KERN_INFO "rp_interrupt %02x...", IntMask);
+#endif
+ ChanStatus = sGetChanStatus(cp);
+ if (IntMask & RXF_TRIG) { /* Rx FIFO trigger level */
+ rp_do_receive(info, tty, cp, ChanStatus);
+ }
+ if (IntMask & DELTA_CD) { /* CD change */
+#if (defined(ROCKET_DEBUG_OPEN) || defined(ROCKET_DEBUG_INTR) || defined(ROCKET_DEBUG_HANGUP))
+ printk(KERN_INFO "ttyR%d CD now %s...", info->line,
+ (ChanStatus & CD_ACT) ? "on" : "off");
+#endif
+ if (!(ChanStatus & CD_ACT) && info->cd_status) {
+#ifdef ROCKET_DEBUG_HANGUP
+ printk(KERN_INFO "CD drop, calling hangup.\n");
+#endif
+ tty_hangup(tty);
+ }
+ info->cd_status = (ChanStatus & CD_ACT) ? 1 : 0;
+ wake_up_interruptible(&info->open_wait);
+ }
+#ifdef ROCKET_DEBUG_INTR
+ if (IntMask & DELTA_CTS) { /* CTS change */
+ printk(KERN_INFO "CTS change...\n");
+ }
+ if (IntMask & DELTA_DSR) { /* DSR change */
+ printk(KERN_INFO "DSR change...\n");
+ }
+#endif
+}
+
+/*
+ * The top level polling routine. Repeats every 1/100 HZ (10ms).
+ */
+static void rp_do_poll(unsigned long dummy)
+{
+ CONTROLLER_t *ctlp;
+ int ctrl, aiop, ch, line, i;
+ unsigned int xmitmask;
+ unsigned int CtlMask;
+ unsigned char AiopMask;
+ Word_t bit;
+
+ /* Walk through all the boards (ctrl's) */
+ for (ctrl = 0; ctrl < max_board; ctrl++) {
+ if (rcktpt_io_addr[ctrl] <= 0)
+ continue;
+
+ /* Get a ptr to the board's control struct */
+ ctlp = sCtlNumToCtlPtr(ctrl);
+
+ /* Get the interupt status from the board */
+#ifdef CONFIG_PCI
+ if (ctlp->BusType == isPCI)
+ CtlMask = sPCIGetControllerIntStatus(ctlp);
+ else
+#endif
+ CtlMask = sGetControllerIntStatus(ctlp);
+
+ /* Check if any AIOP read bits are set */
+ for (aiop = 0; CtlMask; aiop++) {
+ bit = ctlp->AiopIntrBits[aiop];
+ if (CtlMask & bit) {
+ CtlMask &= ~bit;
+ AiopMask = sGetAiopIntStatus(ctlp, aiop);
+
+ /* Check if any port read bits are set */
+ for (ch = 0; AiopMask; AiopMask >>= 1, ch++) {
+ if (AiopMask & 1) {
+
+ /* Get the line number (/dev/ttyRx number). */
+ /* Read the data from the port. */
+ line = GetLineNumber(ctrl, aiop, ch);
+ rp_handle_port(rp_table[line]);
+ }
+ }
+ }
+ }
+
+ xmitmask = xmit_flags[ctrl];
+
+ /*
+ * xmit_flags contains bit-significant flags, indicating there is data
+ * to xmit on the port. Bit 0 is port 0 on this board, bit 1 is port
+ * 1, ... (32 total possible). The variable i has the aiop and ch
+ * numbers encoded in it (port 0-7 are aiop0, 8-15 are aiop1, etc).
+ */
+ if (xmitmask) {
+ for (i = 0; i < rocketModel[ctrl].numPorts; i++) {
+ if (xmitmask & (1 << i)) {
+ aiop = (i & 0x18) >> 3;
+ ch = i & 0x07;
+ line = GetLineNumber(ctrl, aiop, ch);
+ rp_do_transmit(rp_table[line]);
+ }
+ }
+ }
+ }
+
+ /*
+ * Reset the timer so we get called at the next clock tick (10ms).
+ */
+ if (atomic_read(&rp_num_ports_open))
+ mod_timer(&rocket_timer, jiffies + POLL_PERIOD);
+}
+
+/*
+ * Initializes the r_port structure for a port, as well as enabling the port on
+ * the board.
+ * Inputs: board, aiop, chan numbers
+ */
+static void init_r_port(int board, int aiop, int chan, struct pci_dev *pci_dev)
+{
+ unsigned rocketMode;
+ struct r_port *info;
+ int line;
+ CONTROLLER_T *ctlp;
+
+ /* Get the next available line number */
+ line = SetLineNumber(board, aiop, chan);
+
+ ctlp = sCtlNumToCtlPtr(board);
+
+ /* Get a r_port struct for the port, fill it in and save it globally, indexed by line number */
+ info = kmalloc(sizeof (struct r_port), GFP_KERNEL);
+ if (!info) {
+ printk(KERN_INFO "Couldn't allocate info struct for line #%d\n", line);
+ return;
+ }
+ memset(info, 0, sizeof (struct r_port));
+
+ info->magic = RPORT_MAGIC;
+ info->line = line;
+ info->ctlp = ctlp;
+ info->board = board;
+ info->aiop = aiop;
+ info->chan = chan;
+ info->closing_wait = 3000;
+ info->close_delay = 50;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ info->flags &= ~ROCKET_MODE_MASK;
+ switch (pc104[board][line]) {
+ case 422:
+ info->flags |= ROCKET_MODE_RS422;
+ break;
+ case 485:
+ info->flags |= ROCKET_MODE_RS485;
+ break;
+ case 232:
+ default:
+ info->flags |= ROCKET_MODE_RS232;
+ break;
+ }
+
+ info->intmask = RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR;
+ if (sInitChan(ctlp, &info->channel, aiop, chan) == 0) {
+ printk(KERN_INFO "RocketPort sInitChan(%d, %d, %d) failed!\n", board, aiop, chan);
+ kfree(info);
+ return;
+ }
+
+ rocketMode = info->flags & ROCKET_MODE_MASK;
+
+ if ((info->flags & ROCKET_RTS_TOGGLE) || (rocketMode == ROCKET_MODE_RS485))
+ sEnRTSToggle(&info->channel);
+ else
+ sDisRTSToggle(&info->channel);
+
+ if (ctlp->boardType == ROCKET_TYPE_PC104) {
+ switch (rocketMode) {
+ case ROCKET_MODE_RS485:
+ sSetInterfaceMode(&info->channel, InterfaceModeRS485);
+ break;
+ case ROCKET_MODE_RS422:
+ sSetInterfaceMode(&info->channel, InterfaceModeRS422);
+ break;
+ case ROCKET_MODE_RS232:
+ default:
+ if (info->flags & ROCKET_RTS_TOGGLE)
+ sSetInterfaceMode(&info->channel, InterfaceModeRS232T);
+ else
+ sSetInterfaceMode(&info->channel, InterfaceModeRS232);
+ break;
+ }
+ }
+ spin_lock_init(&info->slock);
+ sema_init(&info->write_sem, 1);
+ rp_table[line] = info;
+ if (pci_dev)
+ tty_register_device(rocket_driver, line, &pci_dev->dev);
+}
+
+/*
+ * Configures a rocketport port according to its termio settings. Called from
+ * user mode into the driver (exception handler). *info CD manipulation is spinlock protected.
+ */
+static void configure_r_port(struct r_port *info,
+ struct termios *old_termios)
+{
+ unsigned cflag;
+ unsigned long flags;
+ unsigned rocketMode;
+ int bits, baud, divisor;
+ CHANNEL_t *cp;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+ cp = &info->channel;
+ cflag = info->tty->termios->c_cflag;
+
+ /* Byte size and parity */
+ if ((cflag & CSIZE) == CS8) {
+ sSetData8(cp);
+ bits = 10;
+ } else {
+ sSetData7(cp);
+ bits = 9;
+ }
+ if (cflag & CSTOPB) {
+ sSetStop2(cp);
+ bits++;
+ } else {
+ sSetStop1(cp);
+ }
+
+ if (cflag & PARENB) {
+ sEnParity(cp);
+ bits++;
+ if (cflag & PARODD) {
+ sSetOddParity(cp);
+ } else {
+ sSetEvenParity(cp);
+ }
+ } else {
+ sDisParity(cp);
+ }
+
+ /* baud rate */
+ baud = tty_get_baud_rate(info->tty);
+ if (!baud)
+ baud = 9600;
+ divisor = ((rp_baud_base[info->board] + (baud >> 1)) / baud) - 1;
+ if ((divisor >= 8192 || divisor < 0) && old_termios) {
+ info->tty->termios->c_cflag &= ~CBAUD;
+ info->tty->termios->c_cflag |=
+ (old_termios->c_cflag & CBAUD);
+ baud = tty_get_baud_rate(info->tty);
+ if (!baud)
+ baud = 9600;
+ divisor = (rp_baud_base[info->board] / baud) - 1;
+ }
+ if (divisor >= 8192 || divisor < 0) {
+ baud = 9600;
+ divisor = (rp_baud_base[info->board] / baud) - 1;
+ }
+ info->cps = baud / bits;
+ sSetBaud(cp, divisor);
+
+ if (cflag & CRTSCTS) {
+ info->intmask |= DELTA_CTS;
+ sEnCTSFlowCtl(cp);
+ } else {
+ info->intmask &= ~DELTA_CTS;
+ sDisCTSFlowCtl(cp);
+ }
+ if (cflag & CLOCAL) {
+ info->intmask &= ~DELTA_CD;
+ } else {
+ spin_lock_irqsave(&info->slock, flags);
+ if (sGetChanStatus(cp) & CD_ACT)
+ info->cd_status = 1;
+ else
+ info->cd_status = 0;
+ info->intmask |= DELTA_CD;
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+
+ /*
+ * Handle software flow control in the board
+ */
+#ifdef ROCKET_SOFT_FLOW
+ if (I_IXON(info->tty)) {
+ sEnTxSoftFlowCtl(cp);
+ if (I_IXANY(info->tty)) {
+ sEnIXANY(cp);
+ } else {
+ sDisIXANY(cp);
+ }
+ sSetTxXONChar(cp, START_CHAR(info->tty));
+ sSetTxXOFFChar(cp, STOP_CHAR(info->tty));
+ } else {
+ sDisTxSoftFlowCtl(cp);
+ sDisIXANY(cp);
+ sClrTxXOFF(cp);
+ }
+#endif
+
+ /*
+ * Set up ignore/read mask words
+ */
+ info->read_status_mask = STMRCVROVRH | 0xFF;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= STMFRAMEH | STMPARITYH;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask |= STMBREAKH;
+
+ /*
+ * Characters to ignore
+ */
+ info->ignore_status_mask = 0;
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= STMFRAMEH | STMPARITYH;
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask |= STMBREAKH;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= STMRCVROVRH;
+ }
+
+ rocketMode = info->flags & ROCKET_MODE_MASK;
+
+ if ((info->flags & ROCKET_RTS_TOGGLE)
+ || (rocketMode == ROCKET_MODE_RS485))
+ sEnRTSToggle(cp);
+ else
+ sDisRTSToggle(cp);
+
+ sSetRTS(&info->channel);
+
+ if (cp->CtlP->boardType == ROCKET_TYPE_PC104) {
+ switch (rocketMode) {
+ case ROCKET_MODE_RS485:
+ sSetInterfaceMode(cp, InterfaceModeRS485);
+ break;
+ case ROCKET_MODE_RS422:
+ sSetInterfaceMode(cp, InterfaceModeRS422);
+ break;
+ case ROCKET_MODE_RS232:
+ default:
+ if (info->flags & ROCKET_RTS_TOGGLE)
+ sSetInterfaceMode(cp, InterfaceModeRS232T);
+ else
+ sSetInterfaceMode(cp, InterfaceModeRS232);
+ break;
+ }
+ }
+}
+
+/* info->count is considered critical, protected by spinlocks. */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+ struct r_port *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp))
+ return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+ if (info->flags & ROCKET_CLOSING) {
+ interruptible_sleep_on(&info->close_wait);
+ return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) {
+ info->flags |= ROCKET_NORMAL_ACTIVE;
+ return 0;
+ }
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become free. While we are in
+ * this loop, info->count is dropped by one, so that rp_close() knows when to free things.
+ * We restore it upon exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "block_til_ready before block: ttyR%d, count = %d\n", info->line, info->count);
+#endif
+ spin_lock_irqsave(&info->slock, flags);
+
+#ifdef ROCKET_DISABLE_SIMUSAGE
+ info->flags |= ROCKET_NORMAL_ACTIVE;
+#else
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ info->count--;
+ }
+#endif
+ info->blocked_open++;
+
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ while (1) {
+ if (tty->termios->c_cflag & CBAUD) {
+ sSetDTR(&info->channel);
+ sSetRTS(&info->channel);
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) || !(info->flags & ROCKET_INITIALIZED)) {
+ if (info->flags & ROCKET_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(info->flags & ROCKET_CLOSING) && (do_clocal || (sGetChanStatusLo(&info->channel) & CD_ACT)))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "block_til_ready blocking: ttyR%d, count = %d, flags=0x%0x\n",
+ info->line, info->count, info->flags);
+#endif
+ schedule(); /* Don't hold spinlock here, will hang PC */
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+
+ spin_lock_irqsave(&info->slock, flags);
+
+ if (extra_count)
+ info->count++;
+ info->blocked_open--;
+
+ spin_unlock_irqrestore(&info->slock, flags);
+
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "block_til_ready after blocking: ttyR%d, count = %d\n",
+ info->line, info->count);
+#endif
+ if (retval)
+ return retval;
+ info->flags |= ROCKET_NORMAL_ACTIVE;
+ return 0;
+}
+
+/*
+ * Exception handler that opens a serial port. Creates xmit_buf storage, fills in
+ * port's r_port struct. Initializes the port hardware.
+ */
+static int rp_open(struct tty_struct *tty, struct file *filp)
+{
+ struct r_port *info;
+ int line = 0, retval;
+ CHANNEL_t *cp;
+ unsigned long page;
+
+ line = TTY_GET_LINE(tty);
+ if ((line < 0) || (line >= MAX_RP_PORTS) || ((info = rp_table[line]) == NULL))
+ return -ENXIO;
+
+ page = __get_free_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+
+ if (info->flags & ROCKET_CLOSING) {
+ interruptible_sleep_on(&info->close_wait);
+ free_page(page);
+ return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+ }
+
+ /*
+ * We must not sleep from here until the port is marked fully in use.
+ */
+ if (info->xmit_buf)
+ free_page(page);
+ else
+ info->xmit_buf = (unsigned char *) page;
+
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (info->count++ == 0) {
+ atomic_inc(&rp_num_ports_open);
+
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "rocket mod++ = %d...", atomic_read(&rp_num_ports_open));
+#endif
+ }
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "rp_open ttyR%d, count=%d\n", info->line, info->count);
+#endif
+
+ /*
+ * Info->count is now 1; so it's safe to sleep now.
+ */
+ info->session = current->signal->session;
+ info->pgrp = process_group(current);
+
+ if ((info->flags & ROCKET_INITIALIZED) == 0) {
+ cp = &info->channel;
+ sSetRxTrigger(cp, TRIG_1);
+ if (sGetChanStatus(cp) & CD_ACT)
+ info->cd_status = 1;
+ else
+ info->cd_status = 0;
+ sDisRxStatusMode(cp);
+ sFlushRxFIFO(cp);
+ sFlushTxFIFO(cp);
+
+ sEnInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+ sSetRxTrigger(cp, TRIG_1);
+
+ sGetChanStatus(cp);
+ sDisRxStatusMode(cp);
+ sClrTxXOFF(cp);
+
+ sDisCTSFlowCtl(cp);
+ sDisTxSoftFlowCtl(cp);
+
+ sEnRxFIFO(cp);
+ sEnTransmit(cp);
+
+ info->flags |= ROCKET_INITIALIZED;
+
+ /*
+ * Set up the tty->alt_speed kludge
+ */
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_WARP)
+ info->tty->alt_speed = 460800;
+
+ configure_r_port(info, NULL);
+ if (tty->termios->c_cflag & CBAUD) {
+ sSetDTR(cp);
+ sSetRTS(cp);
+ }
+ }
+ /* Starts (or resets) the maint polling loop */
+ mod_timer(&rocket_timer, jiffies + POLL_PERIOD);
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "rp_open returning after block_til_ready with %d\n", retval);
+#endif
+ return retval;
+ }
+ return 0;
+}
+
+/*
+ * Exception handler that closes a serial port. info->count is considered critical.
+ */
+static void rp_close(struct tty_struct *tty, struct file *filp)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ unsigned long flags;
+ int timeout;
+ CHANNEL_t *cp;
+
+ if (rocket_paranoia_check(info, "rp_close"))
+ return;
+
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "rp_close ttyR%d, count = %d\n", info->line, info->count);
+#endif
+
+ if (tty_hung_up_p(filp))
+ return;
+ spin_lock_irqsave(&info->slock, flags);
+
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk(KERN_INFO "rp_close: bad serial port count; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+ if (--info->count < 0) {
+ printk(KERN_INFO "rp_close: bad serial port count for ttyR%d: %d\n",
+ info->line, info->count);
+ info->count = 0;
+ }
+ if (info->count) {
+ spin_unlock_irqrestore(&info->slock, flags);
+ return;
+ }
+ info->flags |= ROCKET_CLOSING;
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ cp = &info->channel;
+
+ /*
+ * Notify the line discpline to only process XON/XOFF characters
+ */
+ tty->closing = 1;
+
+ /*
+ * If transmission was throttled by the application request,
+ * just flush the xmit buffer.
+ */
+ if (tty->flow_stopped)
+ rp_flush_buffer(tty);
+
+ /*
+ * Wait for the transmit buffer to clear
+ */
+ if (info->closing_wait != ROCKET_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, info->closing_wait);
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = (sGetTxCnt(cp) + 1) * HZ / info->cps;
+ if (timeout == 0)
+ timeout = 1;
+ rp_wait_until_sent(tty, timeout);
+ clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+ sDisTransmit(cp);
+ sDisInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+ sDisCTSFlowCtl(cp);
+ sDisTxSoftFlowCtl(cp);
+ sClrTxXOFF(cp);
+ sFlushRxFIFO(cp);
+ sFlushTxFIFO(cp);
+ sClrRTS(cp);
+ if (C_HUPCL(tty))
+ sClrDTR(cp);
+
+ if (TTY_DRIVER_FLUSH_BUFFER_EXISTS(tty))
+ TTY_DRIVER_FLUSH_BUFFER(tty);
+
+ tty_ldisc_flush(tty);
+
+ clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ } else {
+ if (info->xmit_buf) {
+ free_page((unsigned long) info->xmit_buf);
+ info->xmit_buf = NULL;
+ }
+ }
+ info->flags &= ~(ROCKET_INITIALIZED | ROCKET_CLOSING | ROCKET_NORMAL_ACTIVE);
+ tty->closing = 0;
+ wake_up_interruptible(&info->close_wait);
+ atomic_dec(&rp_num_ports_open);
+
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "rocket mod-- = %d...", atomic_read(&rp_num_ports_open));
+ printk(KERN_INFO "rp_close ttyR%d complete shutdown\n", info->line);
+#endif
+
+}
+
+static void rp_set_termios(struct tty_struct *tty,
+ struct termios *old_termios)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+ unsigned cflag;
+
+ if (rocket_paranoia_check(info, "rp_set_termios"))
+ return;
+
+ cflag = tty->termios->c_cflag;
+
+ if (cflag == old_termios->c_cflag)
+ return;
+
+ /*
+ * This driver doesn't support CS5 or CS6
+ */
+ if (((cflag & CSIZE) == CS5) || ((cflag & CSIZE) == CS6))
+ tty->termios->c_cflag =
+ ((cflag & ~CSIZE) | (old_termios->c_cflag & CSIZE));
+
+ configure_r_port(info, old_termios);
+
+ cp = &info->channel;
+
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) && !(tty->termios->c_cflag & CBAUD)) {
+ sClrDTR(cp);
+ sClrRTS(cp);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) && (tty->termios->c_cflag & CBAUD)) {
+ if (!tty->hw_stopped || !(tty->termios->c_cflag & CRTSCTS))
+ sSetRTS(cp);
+ sSetDTR(cp);
+ }
+
+ if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ rp_start(tty);
+ }
+}
+
+static void rp_break(struct tty_struct *tty, int break_state)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ unsigned long flags;
+
+ if (rocket_paranoia_check(info, "rp_break"))
+ return;
+
+ spin_lock_irqsave(&info->slock, flags);
+ if (break_state == -1)
+ sSendBreak(&info->channel);
+ else
+ sClrBreak(&info->channel);
+ spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * sGetChanRI used to be a macro in rocket_int.h. When the functionality for
+ * the UPCI boards was added, it was decided to make this a function because
+ * the macro was getting too complicated. All cases except the first one
+ * (UPCIRingInd) are taken directly from the original macro.
+ */
+static int sGetChanRI(CHANNEL_T * ChP)
+{
+ CONTROLLER_t *CtlP = ChP->CtlP;
+ int ChanNum = ChP->ChanNum;
+ int RingInd = 0;
+
+ if (CtlP->UPCIRingInd)
+ RingInd = !(sInB(CtlP->UPCIRingInd) & sBitMapSetTbl[ChanNum]);
+ else if (CtlP->AltChanRingIndicator)
+ RingInd = sInB((ByteIO_t) (ChP->ChanStat + 8)) & DSR_ACT;
+ else if (CtlP->boardType == ROCKET_TYPE_PC104)
+ RingInd = !(sInB(CtlP->AiopIO[3]) & sBitMapSetTbl[ChanNum]);
+
+ return RingInd;
+}
+
+/********************************************************************************************/
+/* Here are the routines used by rp_ioctl. These are all called from exception handlers. */
+
+/*
+ * Returns the state of the serial modem control lines. These next 2 functions
+ * are the way kernel versions > 2.5 handle modem control lines rather than IOCTLs.
+ */
+static int rp_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct r_port *info = (struct r_port *)tty->driver_data;
+ unsigned int control, result, ChanStatus;
+
+ ChanStatus = sGetChanStatusLo(&info->channel);
+ control = info->channel.TxControl[3];
+ result = ((control & SET_RTS) ? TIOCM_RTS : 0) |
+ ((control & SET_DTR) ? TIOCM_DTR : 0) |
+ ((ChanStatus & CD_ACT) ? TIOCM_CAR : 0) |
+ (sGetChanRI(&info->channel) ? TIOCM_RNG : 0) |
+ ((ChanStatus & DSR_ACT) ? TIOCM_DSR : 0) |
+ ((ChanStatus & CTS_ACT) ? TIOCM_CTS : 0);
+
+ return result;
+}
+
+/*
+ * Sets the modem control lines
+ */
+static int rp_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct r_port *info = (struct r_port *)tty->driver_data;
+
+ if (set & TIOCM_RTS)
+ info->channel.TxControl[3] |= SET_RTS;
+ if (set & TIOCM_DTR)
+ info->channel.TxControl[3] |= SET_DTR;
+ if (clear & TIOCM_RTS)
+ info->channel.TxControl[3] &= ~SET_RTS;
+ if (clear & TIOCM_DTR)
+ info->channel.TxControl[3] &= ~SET_DTR;
+
+ sOutDW(info->channel.IndexAddr, *(DWord_t *) & (info->channel.TxControl[0]));
+ return 0;
+}
+
+static int get_config(struct r_port *info, struct rocket_config __user *retinfo)
+{
+ struct rocket_config tmp;
+
+ if (!retinfo)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof (tmp));
+ tmp.line = info->line;
+ tmp.flags = info->flags;
+ tmp.close_delay = info->close_delay;
+ tmp.closing_wait = info->closing_wait;
+ tmp.port = rcktpt_io_addr[(info->line >> 5) & 3];
+
+ if (copy_to_user(retinfo, &tmp, sizeof (*retinfo)))
+ return -EFAULT;
+ return 0;
+}
+
+static int set_config(struct r_port *info, struct rocket_config __user *new_info)
+{
+ struct rocket_config new_serial;
+
+ if (copy_from_user(&new_serial, new_info, sizeof (new_serial)))
+ return -EFAULT;
+
+ if (!capable(CAP_SYS_ADMIN))
+ {
+ if ((new_serial.flags & ~ROCKET_USR_MASK) != (info->flags & ~ROCKET_USR_MASK))
+ return -EPERM;
+ info->flags = ((info->flags & ~ROCKET_USR_MASK) | (new_serial.flags & ROCKET_USR_MASK));
+ configure_r_port(info, NULL);
+ return 0;
+ }
+
+ info->flags = ((info->flags & ~ROCKET_FLAGS) | (new_serial.flags & ROCKET_FLAGS));
+ info->close_delay = new_serial.close_delay;
+ info->closing_wait = new_serial.closing_wait;
+
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_HI)
+ info->tty->alt_speed = 57600;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_VHI)
+ info->tty->alt_speed = 115200;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_SHI)
+ info->tty->alt_speed = 230400;
+ if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_WARP)
+ info->tty->alt_speed = 460800;
+
+ configure_r_port(info, NULL);
+ return 0;
+}
+
+/*
+ * This function fills in a rocket_ports struct with information
+ * about what boards/ports are in the system. This info is passed
+ * to user space. See setrocket.c where the info is used to create
+ * the /dev/ttyRx ports.
+ */
+static int get_ports(struct r_port *info, struct rocket_ports __user *retports)
+{
+ struct rocket_ports tmp;
+ int board;
+
+ if (!retports)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof (tmp));
+ tmp.tty_major = rocket_driver->major;
+
+ for (board = 0; board < 4; board++) {
+ tmp.rocketModel[board].model = rocketModel[board].model;
+ strcpy(tmp.rocketModel[board].modelString, rocketModel[board].modelString);
+ tmp.rocketModel[board].numPorts = rocketModel[board].numPorts;
+ tmp.rocketModel[board].loadrm2 = rocketModel[board].loadrm2;
+ tmp.rocketModel[board].startingPortNumber = rocketModel[board].startingPortNumber;
+ }
+ if (copy_to_user(retports, &tmp, sizeof (*retports)))
+ return -EFAULT;
+ return 0;
+}
+
+static int reset_rm2(struct r_port *info, void __user *arg)
+{
+ int reset;
+
+ if (copy_from_user(&reset, arg, sizeof (int)))
+ return -EFAULT;
+ if (reset)
+ reset = 1;
+
+ if (rcktpt_type[info->board] != ROCKET_TYPE_MODEMII &&
+ rcktpt_type[info->board] != ROCKET_TYPE_MODEMIII)
+ return -EINVAL;
+
+ if (info->ctlp->BusType == isISA)
+ sModemReset(info->ctlp, info->chan, reset);
+ else
+ sPCIModemReset(info->ctlp, info->chan, reset);
+
+ return 0;
+}
+
+static int get_version(struct r_port *info, struct rocket_version __user *retvers)
+{
+ if (copy_to_user(retvers, &driver_version, sizeof (*retvers)))
+ return -EFAULT;
+ return 0;
+}
+
+/* IOCTL call handler into the driver */
+static int rp_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ void __user *argp = (void __user *)arg;
+
+ if (cmd != RCKP_GET_PORTS && rocket_paranoia_check(info, "rp_ioctl"))
+ return -ENXIO;
+
+ switch (cmd) {
+ case RCKP_GET_STRUCT:
+ if (copy_to_user(argp, info, sizeof (struct r_port)))
+ return -EFAULT;
+ return 0;
+ case RCKP_GET_CONFIG:
+ return get_config(info, argp);
+ case RCKP_SET_CONFIG:
+ return set_config(info, argp);
+ case RCKP_GET_PORTS:
+ return get_ports(info, argp);
+ case RCKP_RESET_RM2:
+ return reset_rm2(info, argp);
+ case RCKP_GET_VERSION:
+ return get_version(info, argp);
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static void rp_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+
+ if (rocket_paranoia_check(info, "rp_send_xchar"))
+ return;
+
+ cp = &info->channel;
+ if (sGetTxCnt(cp))
+ sWriteTxPrioByte(cp, ch);
+ else
+ sWriteTxByte(sGetTxRxDataIO(cp), ch);
+}
+
+static void rp_throttle(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+
+#ifdef ROCKET_DEBUG_THROTTLE
+ printk(KERN_INFO "throttle %s: %d....\n", tty->name,
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (rocket_paranoia_check(info, "rp_throttle"))
+ return;
+
+ cp = &info->channel;
+ if (I_IXOFF(tty))
+ rp_send_xchar(tty, STOP_CHAR(tty));
+
+ sClrRTS(&info->channel);
+}
+
+static void rp_unthrottle(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+#ifdef ROCKET_DEBUG_THROTTLE
+ printk(KERN_INFO "unthrottle %s: %d....\n", tty->name,
+ tty->ldisc.chars_in_buffer(tty));
+#endif
+
+ if (rocket_paranoia_check(info, "rp_throttle"))
+ return;
+
+ cp = &info->channel;
+ if (I_IXOFF(tty))
+ rp_send_xchar(tty, START_CHAR(tty));
+
+ sSetRTS(&info->channel);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rp_stop() and rp_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rp_stop(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+
+#ifdef ROCKET_DEBUG_FLOW
+ printk(KERN_INFO "stop %s: %d %d....\n", tty->name,
+ info->xmit_cnt, info->xmit_fifo_room);
+#endif
+
+ if (rocket_paranoia_check(info, "rp_stop"))
+ return;
+
+ if (sGetTxCnt(&info->channel))
+ sDisTransmit(&info->channel);
+}
+
+static void rp_start(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+
+#ifdef ROCKET_DEBUG_FLOW
+ printk(KERN_INFO "start %s: %d %d....\n", tty->name,
+ info->xmit_cnt, info->xmit_fifo_room);
+#endif
+
+ if (rocket_paranoia_check(info, "rp_stop"))
+ return;
+
+ sEnTransmit(&info->channel);
+ set_bit((info->aiop * 8) + info->chan,
+ (void *) &xmit_flags[info->board]);
+}
+
+/*
+ * rp_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void rp_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+ unsigned long orig_jiffies;
+ int check_time, exit_time;
+ int txcnt;
+
+ if (rocket_paranoia_check(info, "rp_wait_until_sent"))
+ return;
+
+ cp = &info->channel;
+
+ orig_jiffies = jiffies;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+ printk(KERN_INFO "In RP_wait_until_sent(%d) (jiff=%lu)...", timeout,
+ jiffies);
+ printk(KERN_INFO "cps=%d...", info->cps);
+#endif
+ while (1) {
+ txcnt = sGetTxCnt(cp);
+ if (!txcnt) {
+ if (sGetChanStatusLo(cp) & TXSHRMT)
+ break;
+ check_time = (HZ / info->cps) / 5;
+ } else {
+ check_time = HZ * txcnt / info->cps;
+ }
+ if (timeout) {
+ exit_time = orig_jiffies + timeout - jiffies;
+ if (exit_time <= 0)
+ break;
+ if (exit_time < check_time)
+ check_time = exit_time;
+ }
+ if (check_time == 0)
+ check_time = 1;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+ printk(KERN_INFO "txcnt = %d (jiff=%lu,check=%d)...", txcnt, jiffies, check_time);
+#endif
+ msleep_interruptible(jiffies_to_msecs(check_time));
+ if (signal_pending(current))
+ break;
+ }
+ current->state = TASK_RUNNING;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+ printk(KERN_INFO "txcnt = %d (jiff=%lu)...done\n", txcnt, jiffies);
+#endif
+}
+
+/*
+ * rp_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void rp_hangup(struct tty_struct *tty)
+{
+ CHANNEL_t *cp;
+ struct r_port *info = (struct r_port *) tty->driver_data;
+
+ if (rocket_paranoia_check(info, "rp_hangup"))
+ return;
+
+#if (defined(ROCKET_DEBUG_OPEN) || defined(ROCKET_DEBUG_HANGUP))
+ printk(KERN_INFO "rp_hangup of ttyR%d...", info->line);
+#endif
+ rp_flush_buffer(tty);
+ if (info->flags & ROCKET_CLOSING)
+ return;
+ if (info->count)
+ atomic_dec(&rp_num_ports_open);
+ clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+ info->count = 0;
+ info->flags &= ~ROCKET_NORMAL_ACTIVE;
+ info->tty = NULL;
+
+ cp = &info->channel;
+ sDisRxFIFO(cp);
+ sDisTransmit(cp);
+ sDisInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+ sDisCTSFlowCtl(cp);
+ sDisTxSoftFlowCtl(cp);
+ sClrTxXOFF(cp);
+ info->flags &= ~ROCKET_INITIALIZED;
+
+ wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ * Exception handler - write char routine. The RocketPort driver uses a
+ * double-buffering strategy, with the twist that if the in-memory CPU
+ * buffer is empty, and there's space in the transmit FIFO, the
+ * writing routines will write directly to transmit FIFO.
+ * Write buffer and counters protected by spinlocks
+ */
+static void rp_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+ unsigned long flags;
+
+ if (rocket_paranoia_check(info, "rp_put_char"))
+ return;
+
+ /* Grab the port write semaphore, locking out other processes that try to write to this port */
+ down(&info->write_sem);
+
+#ifdef ROCKET_DEBUG_WRITE
+ printk(KERN_INFO "rp_put_char %c...", ch);
+#endif
+
+ spin_lock_irqsave(&info->slock, flags);
+ cp = &info->channel;
+
+ if (!tty->stopped && !tty->hw_stopped && info->xmit_fifo_room == 0)
+ info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+ if (tty->stopped || tty->hw_stopped || info->xmit_fifo_room == 0 || info->xmit_cnt != 0) {
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= XMIT_BUF_SIZE - 1;
+ info->xmit_cnt++;
+ set_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+ } else {
+ sOutB(sGetTxRxDataIO(cp), ch);
+ info->xmit_fifo_room--;
+ }
+ spin_unlock_irqrestore(&info->slock, flags);
+ up(&info->write_sem);
+}
+
+/*
+ * Exception handler - write routine, called when user app writes to the device.
+ * A per port write semaphore is used to protect from another process writing to
+ * this port at the same time. This other process could be running on the other CPU
+ * or get control of the CPU if the copy_from_user() blocks due to a page fault (swapped out).
+ * Spinlocks protect the info xmit members.
+ */
+static int rp_write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+ const unsigned char *b;
+ int c, retval = 0;
+ unsigned long flags;
+
+ if (count <= 0 || rocket_paranoia_check(info, "rp_write"))
+ return 0;
+
+ down_interruptible(&info->write_sem);
+
+#ifdef ROCKET_DEBUG_WRITE
+ printk(KERN_INFO "rp_write %d chars...", count);
+#endif
+ cp = &info->channel;
+
+ if (!tty->stopped && !tty->hw_stopped && info->xmit_fifo_room < count)
+ info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+ /*
+ * If the write queue for the port is empty, and there is FIFO space, stuff bytes
+ * into FIFO. Use the write queue for temp storage.
+ */
+ if (!tty->stopped && !tty->hw_stopped && info->xmit_cnt == 0 && info->xmit_fifo_room > 0) {
+ c = min(count, info->xmit_fifo_room);
+ b = buf;
+
+ /* Push data into FIFO, 2 bytes at a time */
+ sOutStrW(sGetTxRxDataIO(cp), (unsigned short *) b, c / 2);
+
+ /* If there is a byte remaining, write it */
+ if (c & 1)
+ sOutB(sGetTxRxDataIO(cp), b[c - 1]);
+
+ retval += c;
+ buf += c;
+ count -= c;
+
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_fifo_room -= c;
+ spin_unlock_irqrestore(&info->slock, flags);
+ }
+
+ /* If count is zero, we wrote it all and are done */
+ if (!count)
+ goto end;
+
+ /* Write remaining data into the port's xmit_buf */
+ while (1) {
+ if (info->tty == 0) /* Seemingly obligatory check... */
+ goto end;
+
+ c = min(count, min(XMIT_BUF_SIZE - info->xmit_cnt - 1, XMIT_BUF_SIZE - info->xmit_head));
+ if (c <= 0)
+ break;
+
+ b = buf;
+ memcpy(info->xmit_buf + info->xmit_head, b, c);
+
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_head =
+ (info->xmit_head + c) & (XMIT_BUF_SIZE - 1);
+ info->xmit_cnt += c;
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ buf += c;
+ count -= c;
+ retval += c;
+ }
+
+ if ((retval > 0) && !tty->stopped && !tty->hw_stopped)
+ set_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+end:
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+ }
+ up(&info->write_sem);
+ return retval;
+}
+
+/*
+ * Return the number of characters that can be sent. We estimate
+ * only using the in-memory transmit buffer only, and ignore the
+ * potential space in the transmit FIFO.
+ */
+static int rp_write_room(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ int ret;
+
+ if (rocket_paranoia_check(info, "rp_write_room"))
+ return 0;
+
+ ret = XMIT_BUF_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+#ifdef ROCKET_DEBUG_WRITE
+ printk(KERN_INFO "rp_write_room returns %d...", ret);
+#endif
+ return ret;
+}
+
+/*
+ * Return the number of characters in the buffer. Again, this only
+ * counts those characters in the in-memory transmit buffer.
+ */
+static int rp_chars_in_buffer(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+
+ if (rocket_paranoia_check(info, "rp_chars_in_buffer"))
+ return 0;
+
+ cp = &info->channel;
+
+#ifdef ROCKET_DEBUG_WRITE
+ printk(KERN_INFO "rp_chars_in_buffer returns %d...", info->xmit_cnt);
+#endif
+ return info->xmit_cnt;
+}
+
+/*
+ * Flushes the TX fifo for a port, deletes data in the xmit_buf stored in the
+ * r_port struct for the port. Note that spinlock are used to protect info members,
+ * do not call this function if the spinlock is already held.
+ */
+static void rp_flush_buffer(struct tty_struct *tty)
+{
+ struct r_port *info = (struct r_port *) tty->driver_data;
+ CHANNEL_t *cp;
+ unsigned long flags;
+
+ if (rocket_paranoia_check(info, "rp_flush_buffer"))
+ return;
+
+ spin_lock_irqsave(&info->slock, flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ spin_unlock_irqrestore(&info->slock, flags);
+
+ wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+ tty_wakeup(tty);
+
+ cp = &info->channel;
+ sFlushTxFIFO(cp);
+}
+
+#ifdef CONFIG_PCI
+
+/*
+ * Called when a PCI card is found. Retrieves and stores model information,
+ * init's aiopic and serial port hardware.
+ * Inputs: i is the board number (0-n)
+ */
+__init int register_PCI(int i, struct pci_dev *dev)
+{
+ int num_aiops, aiop, max_num_aiops, num_chan, chan;
+ unsigned int aiopio[MAX_AIOPS_PER_BOARD];
+ char *str, *board_type;
+ CONTROLLER_t *ctlp;
+
+ int fast_clock = 0;
+ int altChanRingIndicator = 0;
+ int ports_per_aiop = 8;
+ int ret;
+ unsigned int class_rev;
+ WordIO_t ConfigIO = 0;
+ ByteIO_t UPCIRingInd = 0;
+
+ if (!dev || pci_enable_device(dev))
+ return 0;
+
+ rcktpt_io_addr[i] = pci_resource_start(dev, 0);
+ ret = pci_read_config_dword(dev, PCI_CLASS_REVISION, &class_rev);
+
+ if (ret) {
+ printk(KERN_INFO " Error during register_PCI(), unable to read config dword \n");
+ return 0;
+ }
+
+ rcktpt_type[i] = ROCKET_TYPE_NORMAL;
+ rocketModel[i].loadrm2 = 0;
+ rocketModel[i].startingPortNumber = nextLineNumber;
+
+ /* Depending on the model, set up some config variables */
+ switch (dev->device) {
+ case PCI_DEVICE_ID_RP4QUAD:
+ str = "Quadcable";
+ max_num_aiops = 1;
+ ports_per_aiop = 4;
+ rocketModel[i].model = MODEL_RP4QUAD;
+ strcpy(rocketModel[i].modelString, "RocketPort 4 port w/quad cable");
+ rocketModel[i].numPorts = 4;
+ break;
+ case PCI_DEVICE_ID_RP8OCTA:
+ str = "Octacable";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_RP8OCTA;
+ strcpy(rocketModel[i].modelString, "RocketPort 8 port w/octa cable");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_URP8OCTA:
+ str = "Octacable";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_UPCI_RP8OCTA;
+ strcpy(rocketModel[i].modelString, "RocketPort UPCI 8 port w/octa cable");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_RP8INTF:
+ str = "8";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_RP8INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort 8 port w/external I/F");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_URP8INTF:
+ str = "8";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_UPCI_RP8INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort UPCI 8 port w/external I/F");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_RP8J:
+ str = "8J";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_RP8J;
+ strcpy(rocketModel[i].modelString, "RocketPort 8 port w/RJ11 connectors");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_RP4J:
+ str = "4J";
+ max_num_aiops = 1;
+ ports_per_aiop = 4;
+ rocketModel[i].model = MODEL_RP4J;
+ strcpy(rocketModel[i].modelString, "RocketPort 4 port w/RJ45 connectors");
+ rocketModel[i].numPorts = 4;
+ break;
+ case PCI_DEVICE_ID_RP8SNI:
+ str = "8 (DB78 Custom)";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_RP8SNI;
+ strcpy(rocketModel[i].modelString, "RocketPort 8 port w/ custom DB78");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_RP16SNI:
+ str = "16 (DB78 Custom)";
+ max_num_aiops = 2;
+ rocketModel[i].model = MODEL_RP16SNI;
+ strcpy(rocketModel[i].modelString, "RocketPort 16 port w/ custom DB78");
+ rocketModel[i].numPorts = 16;
+ break;
+ case PCI_DEVICE_ID_RP16INTF:
+ str = "16";
+ max_num_aiops = 2;
+ rocketModel[i].model = MODEL_RP16INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort 16 port w/external I/F");
+ rocketModel[i].numPorts = 16;
+ break;
+ case PCI_DEVICE_ID_URP16INTF:
+ str = "16";
+ max_num_aiops = 2;
+ rocketModel[i].model = MODEL_UPCI_RP16INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort UPCI 16 port w/external I/F");
+ rocketModel[i].numPorts = 16;
+ break;
+ case PCI_DEVICE_ID_CRP16INTF:
+ str = "16";
+ max_num_aiops = 2;
+ rocketModel[i].model = MODEL_CPCI_RP16INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort Compact PCI 16 port w/external I/F");
+ rocketModel[i].numPorts = 16;
+ break;
+ case PCI_DEVICE_ID_RP32INTF:
+ str = "32";
+ max_num_aiops = 4;
+ rocketModel[i].model = MODEL_RP32INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort 32 port w/external I/F");
+ rocketModel[i].numPorts = 32;
+ break;
+ case PCI_DEVICE_ID_URP32INTF:
+ str = "32";
+ max_num_aiops = 4;
+ rocketModel[i].model = MODEL_UPCI_RP32INTF;
+ strcpy(rocketModel[i].modelString, "RocketPort UPCI 32 port w/external I/F");
+ rocketModel[i].numPorts = 32;
+ break;
+ case PCI_DEVICE_ID_RPP4:
+ str = "Plus Quadcable";
+ max_num_aiops = 1;
+ ports_per_aiop = 4;
+ altChanRingIndicator++;
+ fast_clock++;
+ rocketModel[i].model = MODEL_RPP4;
+ strcpy(rocketModel[i].modelString, "RocketPort Plus 4 port");
+ rocketModel[i].numPorts = 4;
+ break;
+ case PCI_DEVICE_ID_RPP8:
+ str = "Plus Octacable";
+ max_num_aiops = 2;
+ ports_per_aiop = 4;
+ altChanRingIndicator++;
+ fast_clock++;
+ rocketModel[i].model = MODEL_RPP8;
+ strcpy(rocketModel[i].modelString, "RocketPort Plus 8 port");
+ rocketModel[i].numPorts = 8;
+ break;
+ case PCI_DEVICE_ID_RP2_232:
+ str = "Plus 2 (RS-232)";
+ max_num_aiops = 1;
+ ports_per_aiop = 2;
+ altChanRingIndicator++;
+ fast_clock++;
+ rocketModel[i].model = MODEL_RP2_232;
+ strcpy(rocketModel[i].modelString, "RocketPort Plus 2 port RS232");
+ rocketModel[i].numPorts = 2;
+ break;
+ case PCI_DEVICE_ID_RP2_422:
+ str = "Plus 2 (RS-422)";
+ max_num_aiops = 1;
+ ports_per_aiop = 2;
+ altChanRingIndicator++;
+ fast_clock++;
+ rocketModel[i].model = MODEL_RP2_422;
+ strcpy(rocketModel[i].modelString, "RocketPort Plus 2 port RS422");
+ rocketModel[i].numPorts = 2;
+ break;
+ case PCI_DEVICE_ID_RP6M:
+
+ max_num_aiops = 1;
+ ports_per_aiop = 6;
+ str = "6-port";
+
+ /* If class_rev is 1, the rocketmodem flash must be loaded. If it is 2 it is a "socketed" version. */
+ if ((class_rev & 0xFF) == 1) {
+ rcktpt_type[i] = ROCKET_TYPE_MODEMII;
+ rocketModel[i].loadrm2 = 1;
+ } else {
+ rcktpt_type[i] = ROCKET_TYPE_MODEM;
+ }
+
+ rocketModel[i].model = MODEL_RP6M;
+ strcpy(rocketModel[i].modelString, "RocketModem 6 port");
+ rocketModel[i].numPorts = 6;
+ break;
+ case PCI_DEVICE_ID_RP4M:
+ max_num_aiops = 1;
+ ports_per_aiop = 4;
+ str = "4-port";
+ if ((class_rev & 0xFF) == 1) {
+ rcktpt_type[i] = ROCKET_TYPE_MODEMII;
+ rocketModel[i].loadrm2 = 1;
+ } else {
+ rcktpt_type[i] = ROCKET_TYPE_MODEM;
+ }
+
+ rocketModel[i].model = MODEL_RP4M;
+ strcpy(rocketModel[i].modelString, "RocketModem 4 port");
+ rocketModel[i].numPorts = 4;
+ break;
+ default:
+ str = "(unknown/unsupported)";
+ max_num_aiops = 0;
+ break;
+ }
+
+ /*
+ * Check for UPCI boards.
+ */
+
+ switch (dev->device) {
+ case PCI_DEVICE_ID_URP32INTF:
+ case PCI_DEVICE_ID_URP8INTF:
+ case PCI_DEVICE_ID_URP16INTF:
+ case PCI_DEVICE_ID_CRP16INTF:
+ case PCI_DEVICE_ID_URP8OCTA:
+ rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+ ConfigIO = pci_resource_start(dev, 1);
+ if (dev->device == PCI_DEVICE_ID_URP8OCTA) {
+ UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+
+ /*
+ * Check for octa or quad cable.
+ */
+ if (!
+ (sInW(ConfigIO + _PCI_9030_GPIO_CTRL) &
+ PCI_GPIO_CTRL_8PORT)) {
+ str = "Quadcable";
+ ports_per_aiop = 4;
+ rocketModel[i].numPorts = 4;
+ }
+ }
+ break;
+ case PCI_DEVICE_ID_UPCI_RM3_8PORT:
+ str = "8 ports";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_UPCI_RM3_8PORT;
+ strcpy(rocketModel[i].modelString, "RocketModem III 8 port");
+ rocketModel[i].numPorts = 8;
+ rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+ UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+ ConfigIO = pci_resource_start(dev, 1);
+ rcktpt_type[i] = ROCKET_TYPE_MODEMIII;
+ break;
+ case PCI_DEVICE_ID_UPCI_RM3_4PORT:
+ str = "4 ports";
+ max_num_aiops = 1;
+ rocketModel[i].model = MODEL_UPCI_RM3_4PORT;
+ strcpy(rocketModel[i].modelString, "RocketModem III 4 port");
+ rocketModel[i].numPorts = 4;
+ rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+ UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+ ConfigIO = pci_resource_start(dev, 1);
+ rcktpt_type[i] = ROCKET_TYPE_MODEMIII;
+ break;
+ default:
+ break;
+ }
+
+ switch (rcktpt_type[i]) {
+ case ROCKET_TYPE_MODEM:
+ board_type = "RocketModem";
+ break;
+ case ROCKET_TYPE_MODEMII:
+ board_type = "RocketModem II";
+ break;
+ case ROCKET_TYPE_MODEMIII:
+ board_type = "RocketModem III";
+ break;
+ default:
+ board_type = "RocketPort";
+ break;
+ }
+
+ if (fast_clock) {
+ sClockPrescale = 0x12; /* mod 2 (divide by 3) */
+ rp_baud_base[i] = 921600;
+ } else {
+ /*
+ * If support_low_speed is set, use the slow clock
+ * prescale, which supports 50 bps
+ */
+ if (support_low_speed) {
+ /* mod 9 (divide by 10) prescale */
+ sClockPrescale = 0x19;
+ rp_baud_base[i] = 230400;
+ } else {
+ /* mod 4 (devide by 5) prescale */
+ sClockPrescale = 0x14;
+ rp_baud_base[i] = 460800;
+ }
+ }
+
+ for (aiop = 0; aiop < max_num_aiops; aiop++)
+ aiopio[aiop] = rcktpt_io_addr[i] + (aiop * 0x40);
+ ctlp = sCtlNumToCtlPtr(i);
+ num_aiops = sPCIInitController(ctlp, i, aiopio, max_num_aiops, ConfigIO, 0, FREQ_DIS, 0, altChanRingIndicator, UPCIRingInd);
+ for (aiop = 0; aiop < max_num_aiops; aiop++)
+ ctlp->AiopNumChan[aiop] = ports_per_aiop;
+
+ printk("Comtrol PCI controller #%d ID 0x%x found in bus:slot:fn %s at address %04lx, "
+ "%d AIOP(s) (%s)\n", i, dev->device, pci_name(dev),
+ rcktpt_io_addr[i], num_aiops, rocketModel[i].modelString);
+ printk(KERN_INFO "Installing %s, creating /dev/ttyR%d - %ld\n",
+ rocketModel[i].modelString,
+ rocketModel[i].startingPortNumber,
+ rocketModel[i].startingPortNumber +
+ rocketModel[i].numPorts - 1);
+
+ if (num_aiops <= 0) {
+ rcktpt_io_addr[i] = 0;
+ return (0);
+ }
+ is_PCI[i] = 1;
+
+ /* Reset the AIOPIC, init the serial ports */
+ for (aiop = 0; aiop < num_aiops; aiop++) {
+ sResetAiopByNum(ctlp, aiop);
+ num_chan = ports_per_aiop;
+ for (chan = 0; chan < num_chan; chan++)
+ init_r_port(i, aiop, chan, dev);
+ }
+
+ /* Rocket modems must be reset */
+ if ((rcktpt_type[i] == ROCKET_TYPE_MODEM) ||
+ (rcktpt_type[i] == ROCKET_TYPE_MODEMII) ||
+ (rcktpt_type[i] == ROCKET_TYPE_MODEMIII)) {
+ num_chan = ports_per_aiop;
+ for (chan = 0; chan < num_chan; chan++)
+ sPCIModemReset(ctlp, chan, 1);
+ mdelay(500);
+ for (chan = 0; chan < num_chan; chan++)
+ sPCIModemReset(ctlp, chan, 0);
+ mdelay(500);
+ rmSpeakerReset(ctlp, rocketModel[i].model);
+ }
+ return (1);
+}
+
+/*
+ * Probes for PCI cards, inits them if found
+ * Input: board_found = number of ISA boards already found, or the
+ * starting board number
+ * Returns: Number of PCI boards found
+ */
+static int __init init_PCI(int boards_found)
+{
+ struct pci_dev *dev = NULL;
+ int count = 0;
+
+ /* Work through the PCI device list, pulling out ours */
+ while ((dev = pci_find_device(PCI_VENDOR_ID_RP, PCI_ANY_ID, dev))) {
+ if (register_PCI(count + boards_found, dev))
+ count++;
+ }
+ return (count);
+}
+
+#endif /* CONFIG_PCI */
+
+/*
+ * Probes for ISA cards
+ * Input: i = the board number to look for
+ * Returns: 1 if board found, 0 else
+ */
+static int __init init_ISA(int i)
+{
+ int num_aiops, num_chan = 0, total_num_chan = 0;
+ int aiop, chan;
+ unsigned int aiopio[MAX_AIOPS_PER_BOARD];
+ CONTROLLER_t *ctlp;
+ char *type_string;
+
+ /* If io_addr is zero, no board configured */
+ if (rcktpt_io_addr[i] == 0)
+ return (0);
+
+ /* Reserve the IO region */
+ if (!request_region(rcktpt_io_addr[i], 64, "Comtrol RocketPort")) {
+ printk(KERN_INFO "Unable to reserve IO region for configured ISA RocketPort at address 0x%lx, board not installed...\n", rcktpt_io_addr[i]);
+ rcktpt_io_addr[i] = 0;
+ return (0);
+ }
+
+ ctlp = sCtlNumToCtlPtr(i);
+
+ ctlp->boardType = rcktpt_type[i];
+
+ switch (rcktpt_type[i]) {
+ case ROCKET_TYPE_PC104:
+ type_string = "(PC104)";
+ break;
+ case ROCKET_TYPE_MODEM:
+ type_string = "(RocketModem)";
+ break;
+ case ROCKET_TYPE_MODEMII:
+ type_string = "(RocketModem II)";
+ break;
+ default:
+ type_string = "";
+ break;
+ }
+
+ /*
+ * If support_low_speed is set, use the slow clock prescale,
+ * which supports 50 bps
+ */
+ if (support_low_speed) {
+ sClockPrescale = 0x19; /* mod 9 (divide by 10) prescale */
+ rp_baud_base[i] = 230400;
+ } else {
+ sClockPrescale = 0x14; /* mod 4 (devide by 5) prescale */
+ rp_baud_base[i] = 460800;
+ }
+
+ for (aiop = 0; aiop < MAX_AIOPS_PER_BOARD; aiop++)
+ aiopio[aiop] = rcktpt_io_addr[i] + (aiop * 0x400);
+
+ num_aiops = sInitController(ctlp, i, controller + (i * 0x400), aiopio, MAX_AIOPS_PER_BOARD, 0, FREQ_DIS, 0);
+
+ if (ctlp->boardType == ROCKET_TYPE_PC104) {
+ sEnAiop(ctlp, 2); /* only one AIOPIC, but these */
+ sEnAiop(ctlp, 3); /* CSels used for other stuff */
+ }
+
+ /* If something went wrong initing the AIOP's release the ISA IO memory */
+ if (num_aiops <= 0) {
+ release_region(rcktpt_io_addr[i], 64);
+ rcktpt_io_addr[i] = 0;
+ return (0);
+ }
+
+ rocketModel[i].startingPortNumber = nextLineNumber;
+
+ for (aiop = 0; aiop < num_aiops; aiop++) {
+ sResetAiopByNum(ctlp, aiop);
+ sEnAiop(ctlp, aiop);
+ num_chan = sGetAiopNumChan(ctlp, aiop);
+ total_num_chan += num_chan;
+ for (chan = 0; chan < num_chan; chan++)
+ init_r_port(i, aiop, chan, NULL);
+ }
+ is_PCI[i] = 0;
+ if ((rcktpt_type[i] == ROCKET_TYPE_MODEM) || (rcktpt_type[i] == ROCKET_TYPE_MODEMII)) {
+ num_chan = sGetAiopNumChan(ctlp, 0);
+ total_num_chan = num_chan;
+ for (chan = 0; chan < num_chan; chan++)
+ sModemReset(ctlp, chan, 1);
+ mdelay(500);
+ for (chan = 0; chan < num_chan; chan++)
+ sModemReset(ctlp, chan, 0);
+ mdelay(500);
+ strcpy(rocketModel[i].modelString, "RocketModem ISA");
+ } else {
+ strcpy(rocketModel[i].modelString, "RocketPort ISA");
+ }
+ rocketModel[i].numPorts = total_num_chan;
+ rocketModel[i].model = MODEL_ISA;
+
+ printk(KERN_INFO "RocketPort ISA card #%d found at 0x%lx - %d AIOPs %s\n",
+ i, rcktpt_io_addr[i], num_aiops, type_string);
+
+ printk(KERN_INFO "Installing %s, creating /dev/ttyR%d - %ld\n",
+ rocketModel[i].modelString,
+ rocketModel[i].startingPortNumber,
+ rocketModel[i].startingPortNumber +
+ rocketModel[i].numPorts - 1);
+
+ return (1);
+}
+
+static struct tty_operations rocket_ops = {
+ .open = rp_open,
+ .close = rp_close,
+ .write = rp_write,
+ .put_char = rp_put_char,
+ .write_room = rp_write_room,
+ .chars_in_buffer = rp_chars_in_buffer,
+ .flush_buffer = rp_flush_buffer,
+ .ioctl = rp_ioctl,
+ .throttle = rp_throttle,
+ .unthrottle = rp_unthrottle,
+ .set_termios = rp_set_termios,
+ .stop = rp_stop,
+ .start = rp_start,
+ .hangup = rp_hangup,
+ .break_ctl = rp_break,
+ .send_xchar = rp_send_xchar,
+ .wait_until_sent = rp_wait_until_sent,
+ .tiocmget = rp_tiocmget,
+ .tiocmset = rp_tiocmset,
+};
+
+/*
+ * The module "startup" routine; it's run when the module is loaded.
+ */
+int __init rp_init(void)
+{
+ int retval, pci_boards_found, isa_boards_found, i;
+
+ printk(KERN_INFO "RocketPort device driver module, version %s, %s\n",
+ ROCKET_VERSION, ROCKET_DATE);
+
+ rocket_driver = alloc_tty_driver(MAX_RP_PORTS);
+ if (!rocket_driver)
+ return -ENOMEM;
+
+ /*
+ * Set up the timer channel.
+ */
+ init_timer(&rocket_timer);
+ rocket_timer.function = rp_do_poll;
+
+ /*
+ * Initialize the array of pointers to our own internal state
+ * structures.
+ */
+ memset(rp_table, 0, sizeof (rp_table));
+ memset(xmit_flags, 0, sizeof (xmit_flags));
+
+ for (i = 0; i < MAX_RP_PORTS; i++)
+ lineNumbers[i] = 0;
+ nextLineNumber = 0;
+ memset(rocketModel, 0, sizeof (rocketModel));
+
+ /*
+ * If board 1 is non-zero, there is at least one ISA configured. If controller is
+ * zero, use the default controller IO address of board1 + 0x40.
+ */
+ if (board1) {
+ if (controller == 0)
+ controller = board1 + 0x40;
+ } else {
+ controller = 0; /* Used as a flag, meaning no ISA boards */
+ }
+
+ /* If an ISA card is configured, reserve the 4 byte IO space for the Mudbac controller */
+ if (controller && (!request_region(controller, 4, "Comtrol RocketPort"))) {
+ printk(KERN_INFO "Unable to reserve IO region for first configured ISA RocketPort controller 0x%lx. Driver exiting \n", controller);
+ return -EBUSY;
+ }
+
+ /* Store ISA variable retrieved from command line or .conf file. */
+ rcktpt_io_addr[0] = board1;
+ rcktpt_io_addr[1] = board2;
+ rcktpt_io_addr[2] = board3;
+ rcktpt_io_addr[3] = board4;
+
+ rcktpt_type[0] = modem1 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+ rcktpt_type[0] = pc104_1[0] ? ROCKET_TYPE_PC104 : rcktpt_type[0];
+ rcktpt_type[1] = modem2 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+ rcktpt_type[1] = pc104_2[0] ? ROCKET_TYPE_PC104 : rcktpt_type[1];
+ rcktpt_type[2] = modem3 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+ rcktpt_type[2] = pc104_3[0] ? ROCKET_TYPE_PC104 : rcktpt_type[2];
+ rcktpt_type[3] = modem4 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+ rcktpt_type[3] = pc104_4[0] ? ROCKET_TYPE_PC104 : rcktpt_type[3];
+
+ /*
+ * Set up the tty driver structure and then register this
+ * driver with the tty layer.
+ */
+
+ rocket_driver->owner = THIS_MODULE;
+ rocket_driver->flags = TTY_DRIVER_NO_DEVFS;
+ rocket_driver->devfs_name = "tts/R";
+ rocket_driver->name = "ttyR";
+ rocket_driver->driver_name = "Comtrol RocketPort";
+ rocket_driver->major = TTY_ROCKET_MAJOR;
+ rocket_driver->minor_start = 0;
+ rocket_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ rocket_driver->subtype = SERIAL_TYPE_NORMAL;
+ rocket_driver->init_termios = tty_std_termios;
+ rocket_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+#ifdef ROCKET_SOFT_FLOW
+ rocket_driver->flags |= TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
+#endif
+ tty_set_operations(rocket_driver, &rocket_ops);
+
+ retval = tty_register_driver(rocket_driver);
+ if (retval < 0) {
+ printk(KERN_INFO "Couldn't install tty RocketPort driver (error %d)\n", -retval);
+ put_tty_driver(rocket_driver);
+ return -1;
+ }
+
+#ifdef ROCKET_DEBUG_OPEN
+ printk(KERN_INFO "RocketPort driver is major %d\n", rocket_driver.major);
+#endif
+
+ /*
+ * OK, let's probe each of the controllers looking for boards. Any boards found
+ * will be initialized here.
+ */
+ isa_boards_found = 0;
+ pci_boards_found = 0;
+
+ for (i = 0; i < NUM_BOARDS; i++) {
+ if (init_ISA(i))
+ isa_boards_found++;
+ }
+
+#ifdef CONFIG_PCI
+ if (isa_boards_found < NUM_BOARDS)
+ pci_boards_found = init_PCI(isa_boards_found);
+#endif
+
+ max_board = pci_boards_found + isa_boards_found;
+
+ if (max_board == 0) {
+ printk(KERN_INFO "No rocketport ports found; unloading driver.\n");
+ del_timer_sync(&rocket_timer);
+ tty_unregister_driver(rocket_driver);
+ put_tty_driver(rocket_driver);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+#ifdef MODULE
+
+static void rp_cleanup_module(void)
+{
+ int retval;
+ int i;
+
+ del_timer_sync(&rocket_timer);
+
+ retval = tty_unregister_driver(rocket_driver);
+ if (retval)
+ printk(KERN_INFO "Error %d while trying to unregister "
+ "rocketport driver\n", -retval);
+ put_tty_driver(rocket_driver);
+
+ for (i = 0; i < MAX_RP_PORTS; i++) {
+ if (rp_table[i])
+ kfree(rp_table[i]);
+ }
+
+ for (i = 0; i < NUM_BOARDS; i++) {
+ if (rcktpt_io_addr[i] <= 0 || is_PCI[i])
+ continue;
+ release_region(rcktpt_io_addr[i], 64);
+ }
+ if (controller)
+ release_region(controller, 4);
+}
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+static Byte_t RData[RDATASIZE] = {
+ 0x00, 0x09, 0xf6, 0x82,
+ 0x02, 0x09, 0x86, 0xfb,
+ 0x04, 0x09, 0x00, 0x0a,
+ 0x06, 0x09, 0x01, 0x0a,
+ 0x08, 0x09, 0x8a, 0x13,
+ 0x0a, 0x09, 0xc5, 0x11,
+ 0x0c, 0x09, 0x86, 0x85,
+ 0x0e, 0x09, 0x20, 0x0a,
+ 0x10, 0x09, 0x21, 0x0a,
+ 0x12, 0x09, 0x41, 0xff,
+ 0x14, 0x09, 0x82, 0x00,
+ 0x16, 0x09, 0x82, 0x7b,
+ 0x18, 0x09, 0x8a, 0x7d,
+ 0x1a, 0x09, 0x88, 0x81,
+ 0x1c, 0x09, 0x86, 0x7a,
+ 0x1e, 0x09, 0x84, 0x81,
+ 0x20, 0x09, 0x82, 0x7c,
+ 0x22, 0x09, 0x0a, 0x0a
+};
+
+static Byte_t RRegData[RREGDATASIZE] = {
+ 0x00, 0x09, 0xf6, 0x82, /* 00: Stop Rx processor */
+ 0x08, 0x09, 0x8a, 0x13, /* 04: Tx software flow control */
+ 0x0a, 0x09, 0xc5, 0x11, /* 08: XON char */
+ 0x0c, 0x09, 0x86, 0x85, /* 0c: XANY */
+ 0x12, 0x09, 0x41, 0xff, /* 10: Rx mask char */
+ 0x14, 0x09, 0x82, 0x00, /* 14: Compare/Ignore #0 */
+ 0x16, 0x09, 0x82, 0x7b, /* 18: Compare #1 */
+ 0x18, 0x09, 0x8a, 0x7d, /* 1c: Compare #2 */
+ 0x1a, 0x09, 0x88, 0x81, /* 20: Interrupt #1 */
+ 0x1c, 0x09, 0x86, 0x7a, /* 24: Ignore/Replace #1 */
+ 0x1e, 0x09, 0x84, 0x81, /* 28: Interrupt #2 */
+ 0x20, 0x09, 0x82, 0x7c, /* 2c: Ignore/Replace #2 */
+ 0x22, 0x09, 0x0a, 0x0a /* 30: Rx FIFO Enable */
+};
+
+CONTROLLER_T sController[CTL_SIZE] = {
+ {-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+ {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+ {-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+ {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+ {-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+ {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+ {-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+ {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}}
+};
+
+Byte_t sBitMapClrTbl[8] = {
+ 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f
+};
+
+Byte_t sBitMapSetTbl[8] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
+};
+
+int sClockPrescale = 0x14;
+
+/***************************************************************************
+Function: sInitController
+Purpose: Initialization of controller global registers and controller
+ structure.
+Call: sInitController(CtlP,CtlNum,MudbacIO,AiopIOList,AiopIOListSize,
+ IRQNum,Frequency,PeriodicOnly)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int CtlNum; Controller number
+ ByteIO_t MudbacIO; Mudbac base I/O address.
+ ByteIO_t *AiopIOList; List of I/O addresses for each AIOP.
+ This list must be in the order the AIOPs will be found on the
+ controller. Once an AIOP in the list is not found, it is
+ assumed that there are no more AIOPs on the controller.
+ int AiopIOListSize; Number of addresses in AiopIOList
+ int IRQNum; Interrupt Request number. Can be any of the following:
+ 0: Disable global interrupts
+ 3: IRQ 3
+ 4: IRQ 4
+ 5: IRQ 5
+ 9: IRQ 9
+ 10: IRQ 10
+ 11: IRQ 11
+ 12: IRQ 12
+ 15: IRQ 15
+ Byte_t Frequency: A flag identifying the frequency
+ of the periodic interrupt, can be any one of the following:
+ FREQ_DIS - periodic interrupt disabled
+ FREQ_137HZ - 137 Hertz
+ FREQ_69HZ - 69 Hertz
+ FREQ_34HZ - 34 Hertz
+ FREQ_17HZ - 17 Hertz
+ FREQ_9HZ - 9 Hertz
+ FREQ_4HZ - 4 Hertz
+ If IRQNum is set to 0 the Frequency parameter is
+ overidden, it is forced to a value of FREQ_DIS.
+ int PeriodicOnly: TRUE if all interrupts except the periodic
+ interrupt are to be blocked.
+ FALSE is both the periodic interrupt and
+ other channel interrupts are allowed.
+ If IRQNum is set to 0 the PeriodicOnly parameter is
+ overidden, it is forced to a value of FALSE.
+Return: int: Number of AIOPs on the controller, or CTLID_NULL if controller
+ initialization failed.
+
+Comments:
+ If periodic interrupts are to be disabled but AIOP interrupts
+ are allowed, set Frequency to FREQ_DIS and PeriodicOnly to FALSE.
+
+ If interrupts are to be completely disabled set IRQNum to 0.
+
+ Setting Frequency to FREQ_DIS and PeriodicOnly to TRUE is an
+ invalid combination.
+
+ This function performs initialization of global interrupt modes,
+ but it does not actually enable global interrupts. To enable
+ and disable global interrupts use functions sEnGlobalInt() and
+ sDisGlobalInt(). Enabling of global interrupts is normally not
+ done until all other initializations are complete.
+
+ Even if interrupts are globally enabled, they must also be
+ individually enabled for each channel that is to generate
+ interrupts.
+
+Warnings: No range checking on any of the parameters is done.
+
+ No context switches are allowed while executing this function.
+
+ After this function all AIOPs on the controller are disabled,
+ they can be enabled with sEnAiop().
+*/
+int sInitController(CONTROLLER_T * CtlP, int CtlNum, ByteIO_t MudbacIO,
+ ByteIO_t * AiopIOList, int AiopIOListSize, int IRQNum,
+ Byte_t Frequency, int PeriodicOnly)
+{
+ int i;
+ ByteIO_t io;
+ int done;
+
+ CtlP->AiopIntrBits = aiop_intr_bits;
+ CtlP->AltChanRingIndicator = 0;
+ CtlP->CtlNum = CtlNum;
+ CtlP->CtlID = CTLID_0001; /* controller release 1 */
+ CtlP->BusType = isISA;
+ CtlP->MBaseIO = MudbacIO;
+ CtlP->MReg1IO = MudbacIO + 1;
+ CtlP->MReg2IO = MudbacIO + 2;
+ CtlP->MReg3IO = MudbacIO + 3;
+#if 1
+ CtlP->MReg2 = 0; /* interrupt disable */
+ CtlP->MReg3 = 0; /* no periodic interrupts */
+#else
+ if (sIRQMap[IRQNum] == 0) { /* interrupts globally disabled */
+ CtlP->MReg2 = 0; /* interrupt disable */
+ CtlP->MReg3 = 0; /* no periodic interrupts */
+ } else {
+ CtlP->MReg2 = sIRQMap[IRQNum]; /* set IRQ number */
+ CtlP->MReg3 = Frequency; /* set frequency */
+ if (PeriodicOnly) { /* periodic interrupt only */
+ CtlP->MReg3 |= PERIODIC_ONLY;
+ }
+ }
+#endif
+ sOutB(CtlP->MReg2IO, CtlP->MReg2);
+ sOutB(CtlP->MReg3IO, CtlP->MReg3);
+ sControllerEOI(CtlP); /* clear EOI if warm init */
+ /* Init AIOPs */
+ CtlP->NumAiop = 0;
+ for (i = done = 0; i < AiopIOListSize; i++) {
+ io = AiopIOList[i];
+ CtlP->AiopIO[i] = (WordIO_t) io;
+ CtlP->AiopIntChanIO[i] = io + _INT_CHAN;
+ sOutB(CtlP->MReg2IO, CtlP->MReg2 | (i & 0x03)); /* AIOP index */
+ sOutB(MudbacIO, (Byte_t) (io >> 6)); /* set up AIOP I/O in MUDBAC */
+ if (done)
+ continue;
+ sEnAiop(CtlP, i); /* enable the AIOP */
+ CtlP->AiopID[i] = sReadAiopID(io); /* read AIOP ID */
+ if (CtlP->AiopID[i] == AIOPID_NULL) /* if AIOP does not exist */
+ done = 1; /* done looking for AIOPs */
+ else {
+ CtlP->AiopNumChan[i] = sReadAiopNumChan((WordIO_t) io); /* num channels in AIOP */
+ sOutW((WordIO_t) io + _INDX_ADDR, _CLK_PRE); /* clock prescaler */
+ sOutB(io + _INDX_DATA, sClockPrescale);
+ CtlP->NumAiop++; /* bump count of AIOPs */
+ }
+ sDisAiop(CtlP, i); /* disable AIOP */
+ }
+
+ if (CtlP->NumAiop == 0)
+ return (-1);
+ else
+ return (CtlP->NumAiop);
+}
+
+/***************************************************************************
+Function: sPCIInitController
+Purpose: Initialization of controller global registers and controller
+ structure.
+Call: sPCIInitController(CtlP,CtlNum,AiopIOList,AiopIOListSize,
+ IRQNum,Frequency,PeriodicOnly)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int CtlNum; Controller number
+ ByteIO_t *AiopIOList; List of I/O addresses for each AIOP.
+ This list must be in the order the AIOPs will be found on the
+ controller. Once an AIOP in the list is not found, it is
+ assumed that there are no more AIOPs on the controller.
+ int AiopIOListSize; Number of addresses in AiopIOList
+ int IRQNum; Interrupt Request number. Can be any of the following:
+ 0: Disable global interrupts
+ 3: IRQ 3
+ 4: IRQ 4
+ 5: IRQ 5
+ 9: IRQ 9
+ 10: IRQ 10
+ 11: IRQ 11
+ 12: IRQ 12
+ 15: IRQ 15
+ Byte_t Frequency: A flag identifying the frequency
+ of the periodic interrupt, can be any one of the following:
+ FREQ_DIS - periodic interrupt disabled
+ FREQ_137HZ - 137 Hertz
+ FREQ_69HZ - 69 Hertz
+ FREQ_34HZ - 34 Hertz
+ FREQ_17HZ - 17 Hertz
+ FREQ_9HZ - 9 Hertz
+ FREQ_4HZ - 4 Hertz
+ If IRQNum is set to 0 the Frequency parameter is
+ overidden, it is forced to a value of FREQ_DIS.
+ int PeriodicOnly: TRUE if all interrupts except the periodic
+ interrupt are to be blocked.
+ FALSE is both the periodic interrupt and
+ other channel interrupts are allowed.
+ If IRQNum is set to 0 the PeriodicOnly parameter is
+ overidden, it is forced to a value of FALSE.
+Return: int: Number of AIOPs on the controller, or CTLID_NULL if controller
+ initialization failed.
+
+Comments:
+ If periodic interrupts are to be disabled but AIOP interrupts
+ are allowed, set Frequency to FREQ_DIS and PeriodicOnly to FALSE.
+
+ If interrupts are to be completely disabled set IRQNum to 0.
+
+ Setting Frequency to FREQ_DIS and PeriodicOnly to TRUE is an
+ invalid combination.
+
+ This function performs initialization of global interrupt modes,
+ but it does not actually enable global interrupts. To enable
+ and disable global interrupts use functions sEnGlobalInt() and
+ sDisGlobalInt(). Enabling of global interrupts is normally not
+ done until all other initializations are complete.
+
+ Even if interrupts are globally enabled, they must also be
+ individually enabled for each channel that is to generate
+ interrupts.
+
+Warnings: No range checking on any of the parameters is done.
+
+ No context switches are allowed while executing this function.
+
+ After this function all AIOPs on the controller are disabled,
+ they can be enabled with sEnAiop().
+*/
+int sPCIInitController(CONTROLLER_T * CtlP, int CtlNum,
+ ByteIO_t * AiopIOList, int AiopIOListSize,
+ WordIO_t ConfigIO, int IRQNum, Byte_t Frequency,
+ int PeriodicOnly, int altChanRingIndicator,
+ int UPCIRingInd)
+{
+ int i;
+ ByteIO_t io;
+
+ CtlP->AltChanRingIndicator = altChanRingIndicator;
+ CtlP->UPCIRingInd = UPCIRingInd;
+ CtlP->CtlNum = CtlNum;
+ CtlP->CtlID = CTLID_0001; /* controller release 1 */
+ CtlP->BusType = isPCI; /* controller release 1 */
+
+ if (ConfigIO) {
+ CtlP->isUPCI = 1;
+ CtlP->PCIIO = ConfigIO + _PCI_9030_INT_CTRL;
+ CtlP->PCIIO2 = ConfigIO + _PCI_9030_GPIO_CTRL;
+ CtlP->AiopIntrBits = upci_aiop_intr_bits;
+ } else {
+ CtlP->isUPCI = 0;
+ CtlP->PCIIO =
+ (WordIO_t) ((ByteIO_t) AiopIOList[0] + _PCI_INT_FUNC);
+ CtlP->AiopIntrBits = aiop_intr_bits;
+ }
+
+ sPCIControllerEOI(CtlP); /* clear EOI if warm init */
+ /* Init AIOPs */
+ CtlP->NumAiop = 0;
+ for (i = 0; i < AiopIOListSize; i++) {
+ io = AiopIOList[i];
+ CtlP->AiopIO[i] = (WordIO_t) io;
+ CtlP->AiopIntChanIO[i] = io + _INT_CHAN;
+
+ CtlP->AiopID[i] = sReadAiopID(io); /* read AIOP ID */
+ if (CtlP->AiopID[i] == AIOPID_NULL) /* if AIOP does not exist */
+ break; /* done looking for AIOPs */
+
+ CtlP->AiopNumChan[i] = sReadAiopNumChan((WordIO_t) io); /* num channels in AIOP */
+ sOutW((WordIO_t) io + _INDX_ADDR, _CLK_PRE); /* clock prescaler */
+ sOutB(io + _INDX_DATA, sClockPrescale);
+ CtlP->NumAiop++; /* bump count of AIOPs */
+ }
+
+ if (CtlP->NumAiop == 0)
+ return (-1);
+ else
+ return (CtlP->NumAiop);
+}
+
+/***************************************************************************
+Function: sReadAiopID
+Purpose: Read the AIOP idenfication number directly from an AIOP.
+Call: sReadAiopID(io)
+ ByteIO_t io: AIOP base I/O address
+Return: int: Flag AIOPID_XXXX if a valid AIOP is found, where X
+ is replace by an identifying number.
+ Flag AIOPID_NULL if no valid AIOP is found
+Warnings: No context switches are allowed while executing this function.
+
+*/
+int sReadAiopID(ByteIO_t io)
+{
+ Byte_t AiopID; /* ID byte from AIOP */
+
+ sOutB(io + _CMD_REG, RESET_ALL); /* reset AIOP */
+ sOutB(io + _CMD_REG, 0x0);
+ AiopID = sInW(io + _CHN_STAT0) & 0x07;
+ if (AiopID == 0x06)
+ return (1);
+ else /* AIOP does not exist */
+ return (-1);
+}
+
+/***************************************************************************
+Function: sReadAiopNumChan
+Purpose: Read the number of channels available in an AIOP directly from
+ an AIOP.
+Call: sReadAiopNumChan(io)
+ WordIO_t io: AIOP base I/O address
+Return: int: The number of channels available
+Comments: The number of channels is determined by write/reads from identical
+ offsets within the SRAM address spaces for channels 0 and 4.
+ If the channel 4 space is mirrored to channel 0 it is a 4 channel
+ AIOP, otherwise it is an 8 channel.
+Warnings: No context switches are allowed while executing this function.
+*/
+int sReadAiopNumChan(WordIO_t io)
+{
+ Word_t x;
+ static Byte_t R[4] = { 0x00, 0x00, 0x34, 0x12 };
+
+ /* write to chan 0 SRAM */
+ sOutDW((DWordIO_t) io + _INDX_ADDR, *((DWord_t *) & R[0]));
+ sOutW(io + _INDX_ADDR, 0); /* read from SRAM, chan 0 */
+ x = sInW(io + _INDX_DATA);
+ sOutW(io + _INDX_ADDR, 0x4000); /* read from SRAM, chan 4 */
+ if (x != sInW(io + _INDX_DATA)) /* if different must be 8 chan */
+ return (8);
+ else
+ return (4);
+}
+
+/***************************************************************************
+Function: sInitChan
+Purpose: Initialization of a channel and channel structure
+Call: sInitChan(CtlP,ChP,AiopNum,ChanNum)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ CHANNEL_T *ChP; Ptr to channel structure
+ int AiopNum; AIOP number within controller
+ int ChanNum; Channel number within AIOP
+Return: int: TRUE if initialization succeeded, FALSE if it fails because channel
+ number exceeds number of channels available in AIOP.
+Comments: This function must be called before a channel can be used.
+Warnings: No range checking on any of the parameters is done.
+
+ No context switches are allowed while executing this function.
+*/
+int sInitChan(CONTROLLER_T * CtlP, CHANNEL_T * ChP, int AiopNum,
+ int ChanNum)
+{
+ int i;
+ WordIO_t AiopIO;
+ WordIO_t ChIOOff;
+ Byte_t *ChR;
+ Word_t ChOff;
+ static Byte_t R[4];
+ int brd9600;
+
+ if (ChanNum >= CtlP->AiopNumChan[AiopNum])
+ return (FALSE); /* exceeds num chans in AIOP */
+
+ /* Channel, AIOP, and controller identifiers */
+ ChP->CtlP = CtlP;
+ ChP->ChanID = CtlP->AiopID[AiopNum];
+ ChP->AiopNum = AiopNum;
+ ChP->ChanNum = ChanNum;
+
+ /* Global direct addresses */
+ AiopIO = CtlP->AiopIO[AiopNum];
+ ChP->Cmd = (ByteIO_t) AiopIO + _CMD_REG;
+ ChP->IntChan = (ByteIO_t) AiopIO + _INT_CHAN;
+ ChP->IntMask = (ByteIO_t) AiopIO + _INT_MASK;
+ ChP->IndexAddr = (DWordIO_t) AiopIO + _INDX_ADDR;
+ ChP->IndexData = AiopIO + _INDX_DATA;
+
+ /* Channel direct addresses */
+ ChIOOff = AiopIO + ChP->ChanNum * 2;
+ ChP->TxRxData = ChIOOff + _TD0;
+ ChP->ChanStat = ChIOOff + _CHN_STAT0;
+ ChP->TxRxCount = ChIOOff + _FIFO_CNT0;
+ ChP->IntID = (ByteIO_t) AiopIO + ChP->ChanNum + _INT_ID0;
+
+ /* Initialize the channel from the RData array */
+ for (i = 0; i < RDATASIZE; i += 4) {
+ R[0] = RData[i];
+ R[1] = RData[i + 1] + 0x10 * ChanNum;
+ R[2] = RData[i + 2];
+ R[3] = RData[i + 3];
+ sOutDW(ChP->IndexAddr, *((DWord_t *) & R[0]));
+ }
+
+ ChR = ChP->R;
+ for (i = 0; i < RREGDATASIZE; i += 4) {
+ ChR[i] = RRegData[i];
+ ChR[i + 1] = RRegData[i + 1] + 0x10 * ChanNum;
+ ChR[i + 2] = RRegData[i + 2];
+ ChR[i + 3] = RRegData[i + 3];
+ }
+
+ /* Indexed registers */
+ ChOff = (Word_t) ChanNum *0x1000;
+
+ if (sClockPrescale == 0x14)
+ brd9600 = 47;
+ else
+ brd9600 = 23;
+
+ ChP->BaudDiv[0] = (Byte_t) (ChOff + _BAUD);
+ ChP->BaudDiv[1] = (Byte_t) ((ChOff + _BAUD) >> 8);
+ ChP->BaudDiv[2] = (Byte_t) brd9600;
+ ChP->BaudDiv[3] = (Byte_t) (brd9600 >> 8);
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->BaudDiv[0]);
+
+ ChP->TxControl[0] = (Byte_t) (ChOff + _TX_CTRL);
+ ChP->TxControl[1] = (Byte_t) ((ChOff + _TX_CTRL) >> 8);
+ ChP->TxControl[2] = 0;
+ ChP->TxControl[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+ ChP->RxControl[0] = (Byte_t) (ChOff + _RX_CTRL);
+ ChP->RxControl[1] = (Byte_t) ((ChOff + _RX_CTRL) >> 8);
+ ChP->RxControl[2] = 0;
+ ChP->RxControl[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+
+ ChP->TxEnables[0] = (Byte_t) (ChOff + _TX_ENBLS);
+ ChP->TxEnables[1] = (Byte_t) ((ChOff + _TX_ENBLS) >> 8);
+ ChP->TxEnables[2] = 0;
+ ChP->TxEnables[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxEnables[0]);
+
+ ChP->TxCompare[0] = (Byte_t) (ChOff + _TXCMP1);
+ ChP->TxCompare[1] = (Byte_t) ((ChOff + _TXCMP1) >> 8);
+ ChP->TxCompare[2] = 0;
+ ChP->TxCompare[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxCompare[0]);
+
+ ChP->TxReplace1[0] = (Byte_t) (ChOff + _TXREP1B1);
+ ChP->TxReplace1[1] = (Byte_t) ((ChOff + _TXREP1B1) >> 8);
+ ChP->TxReplace1[2] = 0;
+ ChP->TxReplace1[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxReplace1[0]);
+
+ ChP->TxReplace2[0] = (Byte_t) (ChOff + _TXREP2);
+ ChP->TxReplace2[1] = (Byte_t) ((ChOff + _TXREP2) >> 8);
+ ChP->TxReplace2[2] = 0;
+ ChP->TxReplace2[3] = 0;
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxReplace2[0]);
+
+ ChP->TxFIFOPtrs = ChOff + _TXF_OUTP;
+ ChP->TxFIFO = ChOff + _TX_FIFO;
+
+ sOutB(ChP->Cmd, (Byte_t) ChanNum | RESTXFCNT); /* apply reset Tx FIFO count */
+ sOutB(ChP->Cmd, (Byte_t) ChanNum); /* remove reset Tx FIFO count */
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */
+ sOutW(ChP->IndexData, 0);
+ ChP->RxFIFOPtrs = ChOff + _RXF_OUTP;
+ ChP->RxFIFO = ChOff + _RX_FIFO;
+
+ sOutB(ChP->Cmd, (Byte_t) ChanNum | RESRXFCNT); /* apply reset Rx FIFO count */
+ sOutB(ChP->Cmd, (Byte_t) ChanNum); /* remove reset Rx FIFO count */
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs); /* clear Rx out ptr */
+ sOutW(ChP->IndexData, 0);
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */
+ sOutW(ChP->IndexData, 0);
+ ChP->TxPrioCnt = ChOff + _TXP_CNT;
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->TxPrioCnt);
+ sOutB(ChP->IndexData, 0);
+ ChP->TxPrioPtr = ChOff + _TXP_PNTR;
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->TxPrioPtr);
+ sOutB(ChP->IndexData, 0);
+ ChP->TxPrioBuf = ChOff + _TXP_BUF;
+ sEnRxProcessor(ChP); /* start the Rx processor */
+
+ return (TRUE);
+}
+
+/***************************************************************************
+Function: sStopRxProcessor
+Purpose: Stop the receive processor from processing a channel.
+Call: sStopRxProcessor(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+
+Comments: The receive processor can be started again with sStartRxProcessor().
+ This function causes the receive processor to skip over the
+ stopped channel. It does not stop it from processing other channels.
+
+Warnings: No context switches are allowed while executing this function.
+
+ Do not leave the receive processor stopped for more than one
+ character time.
+
+ After calling this function a delay of 4 uS is required to ensure
+ that the receive processor is no longer processing this channel.
+*/
+void sStopRxProcessor(CHANNEL_T * ChP)
+{
+ Byte_t R[4];
+
+ R[0] = ChP->R[0];
+ R[1] = ChP->R[1];
+ R[2] = 0x0a;
+ R[3] = ChP->R[3];
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & R[0]);
+}
+
+/***************************************************************************
+Function: sFlushRxFIFO
+Purpose: Flush the Rx FIFO
+Call: sFlushRxFIFO(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: void
+Comments: To prevent data from being enqueued or dequeued in the Tx FIFO
+ while it is being flushed the receive processor is stopped
+ and the transmitter is disabled. After these operations a
+ 4 uS delay is done before clearing the pointers to allow
+ the receive processor to stop. These items are handled inside
+ this function.
+Warnings: No context switches are allowed while executing this function.
+*/
+void sFlushRxFIFO(CHANNEL_T * ChP)
+{
+ int i;
+ Byte_t Ch; /* channel number within AIOP */
+ int RxFIFOEnabled; /* TRUE if Rx FIFO enabled */
+
+ if (sGetRxCnt(ChP) == 0) /* Rx FIFO empty */
+ return; /* don't need to flush */
+
+ RxFIFOEnabled = FALSE;
+ if (ChP->R[0x32] == 0x08) { /* Rx FIFO is enabled */
+ RxFIFOEnabled = TRUE;
+ sDisRxFIFO(ChP); /* disable it */
+ for (i = 0; i < 2000 / 200; i++) /* delay 2 uS to allow proc to disable FIFO */
+ sInB(ChP->IntChan); /* depends on bus i/o timing */
+ }
+ sGetChanStatus(ChP); /* clear any pending Rx errors in chan stat */
+ Ch = (Byte_t) sGetChanNum(ChP);
+ sOutB(ChP->Cmd, Ch | RESRXFCNT); /* apply reset Rx FIFO count */
+ sOutB(ChP->Cmd, Ch); /* remove reset Rx FIFO count */
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs); /* clear Rx out ptr */
+ sOutW(ChP->IndexData, 0);
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs + 2); /* clear Rx in ptr */
+ sOutW(ChP->IndexData, 0);
+ if (RxFIFOEnabled)
+ sEnRxFIFO(ChP); /* enable Rx FIFO */
+}
+
+/***************************************************************************
+Function: sFlushTxFIFO
+Purpose: Flush the Tx FIFO
+Call: sFlushTxFIFO(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: void
+Comments: To prevent data from being enqueued or dequeued in the Tx FIFO
+ while it is being flushed the receive processor is stopped
+ and the transmitter is disabled. After these operations a
+ 4 uS delay is done before clearing the pointers to allow
+ the receive processor to stop. These items are handled inside
+ this function.
+Warnings: No context switches are allowed while executing this function.
+*/
+void sFlushTxFIFO(CHANNEL_T * ChP)
+{
+ int i;
+ Byte_t Ch; /* channel number within AIOP */
+ int TxEnabled; /* TRUE if transmitter enabled */
+
+ if (sGetTxCnt(ChP) == 0) /* Tx FIFO empty */
+ return; /* don't need to flush */
+
+ TxEnabled = FALSE;
+ if (ChP->TxControl[3] & TX_ENABLE) {
+ TxEnabled = TRUE;
+ sDisTransmit(ChP); /* disable transmitter */
+ }
+ sStopRxProcessor(ChP); /* stop Rx processor */
+ for (i = 0; i < 4000 / 200; i++) /* delay 4 uS to allow proc to stop */
+ sInB(ChP->IntChan); /* depends on bus i/o timing */
+ Ch = (Byte_t) sGetChanNum(ChP);
+ sOutB(ChP->Cmd, Ch | RESTXFCNT); /* apply reset Tx FIFO count */
+ sOutB(ChP->Cmd, Ch); /* remove reset Tx FIFO count */
+ sOutW((WordIO_t) ChP->IndexAddr, ChP->TxFIFOPtrs); /* clear Tx in/out ptrs */
+ sOutW(ChP->IndexData, 0);
+ if (TxEnabled)
+ sEnTransmit(ChP); /* enable transmitter */
+ sStartRxProcessor(ChP); /* restart Rx processor */
+}
+
+/***************************************************************************
+Function: sWriteTxPrioByte
+Purpose: Write a byte of priority transmit data to a channel
+Call: sWriteTxPrioByte(ChP,Data)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Byte_t Data; The transmit data byte
+
+Return: int: 1 if the bytes is successfully written, otherwise 0.
+
+Comments: The priority byte is transmitted before any data in the Tx FIFO.
+
+Warnings: No context switches are allowed while executing this function.
+*/
+int sWriteTxPrioByte(CHANNEL_T * ChP, Byte_t Data)
+{
+ Byte_t DWBuf[4]; /* buffer for double word writes */
+ Word_t *WordPtr; /* must be far because Win SS != DS */
+ register DWordIO_t IndexAddr;
+
+ if (sGetTxCnt(ChP) > 1) { /* write it to Tx priority buffer */
+ IndexAddr = ChP->IndexAddr;
+ sOutW((WordIO_t) IndexAddr, ChP->TxPrioCnt); /* get priority buffer status */
+ if (sInB((ByteIO_t) ChP->IndexData) & PRI_PEND) /* priority buffer busy */
+ return (0); /* nothing sent */
+
+ WordPtr = (Word_t *) (&DWBuf[0]);
+ *WordPtr = ChP->TxPrioBuf; /* data byte address */
+
+ DWBuf[2] = Data; /* data byte value */
+ sOutDW(IndexAddr, *((DWord_t *) (&DWBuf[0]))); /* write it out */
+
+ *WordPtr = ChP->TxPrioCnt; /* Tx priority count address */
+
+ DWBuf[2] = PRI_PEND + 1; /* indicate 1 byte pending */
+ DWBuf[3] = 0; /* priority buffer pointer */
+ sOutDW(IndexAddr, *((DWord_t *) (&DWBuf[0]))); /* write it out */
+ } else { /* write it to Tx FIFO */
+
+ sWriteTxByte(sGetTxRxDataIO(ChP), Data);
+ }
+ return (1); /* 1 byte sent */
+}
+
+/***************************************************************************
+Function: sEnInterrupts
+Purpose: Enable one or more interrupts for a channel
+Call: sEnInterrupts(ChP,Flags)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Word_t Flags: Interrupt enable flags, can be any combination
+ of the following flags:
+ TXINT_EN: Interrupt on Tx FIFO empty
+ RXINT_EN: Interrupt on Rx FIFO at trigger level (see
+ sSetRxTrigger())
+ SRCINT_EN: Interrupt on SRC (Special Rx Condition)
+ MCINT_EN: Interrupt on modem input change
+ CHANINT_EN: Allow channel interrupt signal to the AIOP's
+ Interrupt Channel Register.
+Return: void
+Comments: If an interrupt enable flag is set in Flags, that interrupt will be
+ enabled. If an interrupt enable flag is not set in Flags, that
+ interrupt will not be changed. Interrupts can be disabled with
+ function sDisInterrupts().
+
+ This function sets the appropriate bit for the channel in the AIOP's
+ Interrupt Mask Register if the CHANINT_EN flag is set. This allows
+ this channel's bit to be set in the AIOP's Interrupt Channel Register.
+
+ Interrupts must also be globally enabled before channel interrupts
+ will be passed on to the host. This is done with function
+ sEnGlobalInt().
+
+ In some cases it may be desirable to disable interrupts globally but
+ enable channel interrupts. This would allow the global interrupt
+ status register to be used to determine which AIOPs need service.
+*/
+void sEnInterrupts(CHANNEL_T * ChP, Word_t Flags)
+{
+ Byte_t Mask; /* Interrupt Mask Register */
+
+ ChP->RxControl[2] |=
+ ((Byte_t) Flags & (RXINT_EN | SRCINT_EN | MCINT_EN));
+
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+
+ ChP->TxControl[2] |= ((Byte_t) Flags & TXINT_EN);
+
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+ if (Flags & CHANINT_EN) {
+ Mask = sInB(ChP->IntMask) | sBitMapSetTbl[ChP->ChanNum];
+ sOutB(ChP->IntMask, Mask);
+ }
+}
+
+/***************************************************************************
+Function: sDisInterrupts
+Purpose: Disable one or more interrupts for a channel
+Call: sDisInterrupts(ChP,Flags)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Word_t Flags: Interrupt flags, can be any combination
+ of the following flags:
+ TXINT_EN: Interrupt on Tx FIFO empty
+ RXINT_EN: Interrupt on Rx FIFO at trigger level (see
+ sSetRxTrigger())
+ SRCINT_EN: Interrupt on SRC (Special Rx Condition)
+ MCINT_EN: Interrupt on modem input change
+ CHANINT_EN: Disable channel interrupt signal to the
+ AIOP's Interrupt Channel Register.
+Return: void
+Comments: If an interrupt flag is set in Flags, that interrupt will be
+ disabled. If an interrupt flag is not set in Flags, that
+ interrupt will not be changed. Interrupts can be enabled with
+ function sEnInterrupts().
+
+ This function clears the appropriate bit for the channel in the AIOP's
+ Interrupt Mask Register if the CHANINT_EN flag is set. This blocks
+ this channel's bit from being set in the AIOP's Interrupt Channel
+ Register.
+*/
+void sDisInterrupts(CHANNEL_T * ChP, Word_t Flags)
+{
+ Byte_t Mask; /* Interrupt Mask Register */
+
+ ChP->RxControl[2] &=
+ ~((Byte_t) Flags & (RXINT_EN | SRCINT_EN | MCINT_EN));
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+ ChP->TxControl[2] &= ~((Byte_t) Flags & TXINT_EN);
+ sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+ if (Flags & CHANINT_EN) {
+ Mask = sInB(ChP->IntMask) & sBitMapClrTbl[ChP->ChanNum];
+ sOutB(ChP->IntMask, Mask);
+ }
+}
+
+void sSetInterfaceMode(CHANNEL_T * ChP, Byte_t mode)
+{
+ sOutB(ChP->CtlP->AiopIO[2], (mode & 0x18) | ChP->ChanNum);
+}
+
+/*
+ * Not an official SSCI function, but how to reset RocketModems.
+ * ISA bus version
+ */
+void sModemReset(CONTROLLER_T * CtlP, int chan, int on)
+{
+ ByteIO_t addr;
+ Byte_t val;
+
+ addr = CtlP->AiopIO[0] + 0x400;
+ val = sInB(CtlP->MReg3IO);
+ /* if AIOP[1] is not enabled, enable it */
+ if ((val & 2) == 0) {
+ val = sInB(CtlP->MReg2IO);
+ sOutB(CtlP->MReg2IO, (val & 0xfc) | (1 & 0x03));
+ sOutB(CtlP->MBaseIO, (unsigned char) (addr >> 6));
+ }
+
+ sEnAiop(CtlP, 1);
+ if (!on)
+ addr += 8;
+ sOutB(addr + chan, 0); /* apply or remove reset */
+ sDisAiop(CtlP, 1);
+}
+
+/*
+ * Not an official SSCI function, but how to reset RocketModems.
+ * PCI bus version
+ */
+void sPCIModemReset(CONTROLLER_T * CtlP, int chan, int on)
+{
+ ByteIO_t addr;
+
+ addr = CtlP->AiopIO[0] + 0x40; /* 2nd AIOP */
+ if (!on)
+ addr += 8;
+ sOutB(addr + chan, 0); /* apply or remove reset */
+}
+
+/* Resets the speaker controller on RocketModem II and III devices */
+static void rmSpeakerReset(CONTROLLER_T * CtlP, unsigned long model)
+{
+ ByteIO_t addr;
+
+ /* RocketModem II speaker control is at the 8th port location of offset 0x40 */
+ if ((model == MODEL_RP4M) || (model == MODEL_RP6M)) {
+ addr = CtlP->AiopIO[0] + 0x4F;
+ sOutB(addr, 0);
+ }
+
+ /* RocketModem III speaker control is at the 1st port location of offset 0x80 */
+ if ((model == MODEL_UPCI_RM3_8PORT)
+ || (model == MODEL_UPCI_RM3_4PORT)) {
+ addr = CtlP->AiopIO[0] + 0x88;
+ sOutB(addr, 0);
+ }
+}
+
+/* Returns the line number given the controller (board), aiop and channel number */
+static unsigned char GetLineNumber(int ctrl, int aiop, int ch)
+{
+ return lineNumbers[(ctrl << 5) | (aiop << 3) | ch];
+}
+
+/*
+ * Stores the line number associated with a given controller (board), aiop
+ * and channel number.
+ * Returns: The line number assigned
+ */
+static unsigned char SetLineNumber(int ctrl, int aiop, int ch)
+{
+ lineNumbers[(ctrl << 5) | (aiop << 3) | ch] = nextLineNumber++;
+ return (nextLineNumber - 1);
+}
diff --git a/drivers/char/rocket.h b/drivers/char/rocket.h
new file mode 100644
index 000000000000..ae6b04f90c03
--- /dev/null
+++ b/drivers/char/rocket.h
@@ -0,0 +1,111 @@
+/*
+ * rocket.h --- the exported interface of the rocket driver to its configuration program.
+ *
+ * Written by Theodore Ts'o, Copyright 1997.
+ * Copyright 1997 Comtrol Corporation.
+ *
+ */
+
+/* Model Information Struct */
+typedef struct {
+ unsigned long model;
+ char modelString[80];
+ unsigned long numPorts;
+ int loadrm2;
+ int startingPortNumber;
+} rocketModel_t;
+
+struct rocket_config {
+ int line;
+ int flags;
+ int closing_wait;
+ int close_delay;
+ int port;
+ int reserved[32];
+};
+
+struct rocket_ports {
+ int tty_major;
+ int callout_major;
+ rocketModel_t rocketModel[8];
+};
+
+struct rocket_version {
+ char rocket_version[32];
+ char rocket_date[32];
+ char reserved[64];
+};
+
+/*
+ * Rocketport flags
+ */
+#define ROCKET_CALLOUT_NOHUP 0x00000001
+#define ROCKET_FORCE_CD 0x00000002
+#define ROCKET_HUP_NOTIFY 0x00000004
+#define ROCKET_SPLIT_TERMIOS 0x00000008
+#define ROCKET_SPD_MASK 0x00000070
+#define ROCKET_SPD_HI 0x00000010 /* Use 56000 instead of 38400 bps */
+#define ROCKET_SPD_VHI 0x00000020 /* Use 115200 instead of 38400 bps */
+#define ROCKET_SPD_SHI 0x00000030 /* Use 230400 instead of 38400 bps */
+#define ROCKET_SPD_WARP 0x00000040 /* Use 460800 instead of 38400 bps */
+#define ROCKET_SAK 0x00000080
+#define ROCKET_SESSION_LOCKOUT 0x00000100
+#define ROCKET_PGRP_LOCKOUT 0x00000200
+#define ROCKET_RTS_TOGGLE 0x00000400
+#define ROCKET_MODE_MASK 0x00003000
+#define ROCKET_MODE_RS232 0x00000000
+#define ROCKET_MODE_RS485 0x00001000
+#define ROCKET_MODE_RS422 0x00002000
+#define ROCKET_FLAGS 0x00003FFF
+
+#define ROCKET_USR_MASK 0x0071 /* Legal flags that non-privileged
+ * users can set or reset */
+
+/*
+ * For closing_wait and closing_wait2
+ */
+#define ROCKET_CLOSING_WAIT_NONE 65535
+#define ROCKET_CLOSING_WAIT_INF 0
+
+/*
+ * Rocketport ioctls -- "RP"
+ */
+#define RCKP_GET_STRUCT 0x00525001
+#define RCKP_GET_CONFIG 0x00525002
+#define RCKP_SET_CONFIG 0x00525003
+#define RCKP_GET_PORTS 0x00525004
+#define RCKP_RESET_RM2 0x00525005
+#define RCKP_GET_VERSION 0x00525006
+
+/* Rocketport Models */
+#define MODEL_RP32INTF 0x0001 /* RP 32 port w/external I/F */
+#define MODEL_RP8INTF 0x0002 /* RP 8 port w/external I/F */
+#define MODEL_RP16INTF 0x0003 /* RP 16 port w/external I/F */
+#define MODEL_RP8OCTA 0x0005 /* RP 8 port w/octa cable */
+#define MODEL_RP4QUAD 0x0004 /* RP 4 port w/quad cable */
+#define MODEL_RP8J 0x0006 /* RP 8 port w/RJ11 connectors */
+#define MODEL_RP4J 0x0007 /* RP 4 port w/RJ45 connectors */
+#define MODEL_RP8SNI 0x0008 /* RP 8 port w/ DB78 SNI connector */
+#define MODEL_RP16SNI 0x0009 /* RP 16 port w/ DB78 SNI connector */
+#define MODEL_RPP4 0x000A /* RP Plus 4 port */
+#define MODEL_RPP8 0x000B /* RP Plus 8 port */
+#define MODEL_RP2_232 0x000E /* RP Plus 2 port RS232 */
+#define MODEL_RP2_422 0x000F /* RP Plus 2 port RS232 */
+
+/* Rocketmodem II Models */
+#define MODEL_RP6M 0x000C /* RM 6 port */
+#define MODEL_RP4M 0x000D /* RM 4 port */
+
+/* Universal PCI boards */
+#define MODEL_UPCI_RP32INTF 0x0801 /* RP UPCI 32 port w/external I/F */
+#define MODEL_UPCI_RP8INTF 0x0802 /* RP UPCI 8 port w/external I/F */
+#define MODEL_UPCI_RP16INTF 0x0803 /* RP UPCI 16 port w/external I/F */
+#define MODEL_UPCI_RP8OCTA 0x0805 /* RP UPCI 8 port w/octa cable */
+#define MODEL_UPCI_RM3_8PORT 0x080C /* RP UPCI Rocketmodem III 8 port */
+#define MODEL_UPCI_RM3_4PORT 0x080C /* RP UPCI Rocketmodem III 4 port */
+
+/* Compact PCI 16 port */
+#define MODEL_CPCI_RP16INTF 0x0903 /* RP Compact PCI 16 port w/external I/F */
+
+/* All ISA boards */
+#define MODEL_ISA 0x1000
diff --git a/drivers/char/rocket_int.h b/drivers/char/rocket_int.h
new file mode 100644
index 000000000000..802687290ee1
--- /dev/null
+++ b/drivers/char/rocket_int.h
@@ -0,0 +1,1296 @@
+/*
+ * rocket_int.h --- internal header file for rocket.c
+ *
+ * Written by Theodore Ts'o, Copyright 1997.
+ * Copyright 1997 Comtrol Corporation.
+ *
+ */
+
+/*
+ * Definition of the types in rcktpt_type
+ */
+#define ROCKET_TYPE_NORMAL 0
+#define ROCKET_TYPE_MODEM 1
+#define ROCKET_TYPE_MODEMII 2
+#define ROCKET_TYPE_MODEMIII 3
+#define ROCKET_TYPE_PC104 4
+
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+typedef unsigned char Byte_t;
+typedef unsigned int ByteIO_t;
+
+typedef unsigned int Word_t;
+typedef unsigned int WordIO_t;
+
+typedef unsigned long DWord_t;
+typedef unsigned int DWordIO_t;
+
+/*
+ * Note! Normally the Linux I/O macros already take care of
+ * byte-swapping the I/O instructions. However, all accesses using
+ * sOutDW aren't really 32-bit accesses, but should be handled in byte
+ * order. Hence the use of the cpu_to_le32() macro to byte-swap
+ * things to no-op the byte swapping done by the big-endian outl()
+ * instruction.
+ */
+
+#ifdef ROCKET_DEBUG_IO
+static inline void sOutB(unsigned short port, unsigned char value)
+{
+#ifdef ROCKET_DEBUG_IO
+ printk("sOutB(%x, %x)...", port, value);
+#endif
+ outb_p(value, port);
+}
+
+static inline void sOutW(unsigned short port, unsigned short value)
+{
+#ifdef ROCKET_DEBUG_IO
+ printk("sOutW(%x, %x)...", port, value);
+#endif
+ outw_p(value, port);
+}
+
+static inline void sOutDW(unsigned short port, unsigned long value)
+{
+#ifdef ROCKET_DEBUG_IO
+ printk("sOutDW(%x, %lx)...", port, value);
+#endif
+ outl_p(cpu_to_le32(value), port);
+}
+
+static inline unsigned char sInB(unsigned short port)
+{
+ return inb_p(port);
+}
+
+static inline unsigned short sInW(unsigned short port)
+{
+ return inw_p(port);
+}
+
+#else /* !ROCKET_DEBUG_IO */
+#define sOutB(a, b) outb_p(b, a)
+#define sOutW(a, b) outw_p(b, a)
+#define sOutDW(port, value) outl_p(cpu_to_le32(value), port)
+#define sInB(a) (inb_p(a))
+#define sInW(a) (inw_p(a))
+#endif /* ROCKET_DEBUG_IO */
+
+/* This is used to move arrays of bytes so byte swapping isn't appropriate. */
+#define sOutStrW(port, addr, count) if (count) outsw(port, addr, count)
+#define sInStrW(port, addr, count) if (count) insw(port, addr, count)
+
+#define CTL_SIZE 8
+#define AIOP_CTL_SIZE 4
+#define CHAN_AIOP_SIZE 8
+#define MAX_PORTS_PER_AIOP 8
+#define MAX_AIOPS_PER_BOARD 4
+#define MAX_PORTS_PER_BOARD 32
+
+/* Bus type ID */
+#define isISA 0
+#define isPCI 1
+#define isMC 2
+
+/* Controller ID numbers */
+#define CTLID_NULL -1 /* no controller exists */
+#define CTLID_0001 0x0001 /* controller release 1 */
+
+/* AIOP ID numbers, identifies AIOP type implementing channel */
+#define AIOPID_NULL -1 /* no AIOP or channel exists */
+#define AIOPID_0001 0x0001 /* AIOP release 1 */
+
+#define NULLDEV -1 /* identifies non-existant device */
+#define NULLCTL -1 /* identifies non-existant controller */
+#define NULLCTLPTR (CONTROLLER_T *)0 /* identifies non-existant controller */
+#define NULLAIOP -1 /* identifies non-existant AIOP */
+#define NULLCHAN -1 /* identifies non-existant channel */
+
+/************************************************************************
+ Global Register Offsets - Direct Access - Fixed values
+************************************************************************/
+
+#define _CMD_REG 0x38 /* Command Register 8 Write */
+#define _INT_CHAN 0x39 /* Interrupt Channel Register 8 Read */
+#define _INT_MASK 0x3A /* Interrupt Mask Register 8 Read / Write */
+#define _UNUSED 0x3B /* Unused 8 */
+#define _INDX_ADDR 0x3C /* Index Register Address 16 Write */
+#define _INDX_DATA 0x3E /* Index Register Data 8/16 Read / Write */
+
+/************************************************************************
+ Channel Register Offsets for 1st channel in AIOP - Direct Access
+************************************************************************/
+#define _TD0 0x00 /* Transmit Data 16 Write */
+#define _RD0 0x00 /* Receive Data 16 Read */
+#define _CHN_STAT0 0x20 /* Channel Status 8/16 Read / Write */
+#define _FIFO_CNT0 0x10 /* Transmit/Receive FIFO Count 16 Read */
+#define _INT_ID0 0x30 /* Interrupt Identification 8 Read */
+
+/************************************************************************
+ Tx Control Register Offsets - Indexed - External - Fixed
+************************************************************************/
+#define _TX_ENBLS 0x980 /* Tx Processor Enables Register 8 Read / Write */
+#define _TXCMP1 0x988 /* Transmit Compare Value #1 8 Read / Write */
+#define _TXCMP2 0x989 /* Transmit Compare Value #2 8 Read / Write */
+#define _TXREP1B1 0x98A /* Tx Replace Value #1 - Byte 1 8 Read / Write */
+#define _TXREP1B2 0x98B /* Tx Replace Value #1 - Byte 2 8 Read / Write */
+#define _TXREP2 0x98C /* Transmit Replace Value #2 8 Read / Write */
+
+/************************************************************************
+Memory Controller Register Offsets - Indexed - External - Fixed
+************************************************************************/
+#define _RX_FIFO 0x000 /* Rx FIFO */
+#define _TX_FIFO 0x800 /* Tx FIFO */
+#define _RXF_OUTP 0x990 /* Rx FIFO OUT pointer 16 Read / Write */
+#define _RXF_INP 0x992 /* Rx FIFO IN pointer 16 Read / Write */
+#define _TXF_OUTP 0x994 /* Tx FIFO OUT pointer 8 Read / Write */
+#define _TXF_INP 0x995 /* Tx FIFO IN pointer 8 Read / Write */
+#define _TXP_CNT 0x996 /* Tx Priority Count 8 Read / Write */
+#define _TXP_PNTR 0x997 /* Tx Priority Pointer 8 Read / Write */
+
+#define PRI_PEND 0x80 /* Priority data pending (bit7, Tx pri cnt) */
+#define TXFIFO_SIZE 255 /* size of Tx FIFO */
+#define RXFIFO_SIZE 1023 /* size of Rx FIFO */
+
+/************************************************************************
+Tx Priority Buffer - Indexed - External - Fixed
+************************************************************************/
+#define _TXP_BUF 0x9C0 /* Tx Priority Buffer 32 Bytes Read / Write */
+#define TXP_SIZE 0x20 /* 32 bytes */
+
+/************************************************************************
+Channel Register Offsets - Indexed - Internal - Fixed
+************************************************************************/
+
+#define _TX_CTRL 0xFF0 /* Transmit Control 16 Write */
+#define _RX_CTRL 0xFF2 /* Receive Control 8 Write */
+#define _BAUD 0xFF4 /* Baud Rate 16 Write */
+#define _CLK_PRE 0xFF6 /* Clock Prescaler 8 Write */
+
+#define STMBREAK 0x08 /* BREAK */
+#define STMFRAME 0x04 /* framing error */
+#define STMRCVROVR 0x02 /* receiver over run error */
+#define STMPARITY 0x01 /* parity error */
+#define STMERROR (STMBREAK | STMFRAME | STMPARITY)
+#define STMBREAKH 0x800 /* BREAK */
+#define STMFRAMEH 0x400 /* framing error */
+#define STMRCVROVRH 0x200 /* receiver over run error */
+#define STMPARITYH 0x100 /* parity error */
+#define STMERRORH (STMBREAKH | STMFRAMEH | STMPARITYH)
+
+#define CTS_ACT 0x20 /* CTS input asserted */
+#define DSR_ACT 0x10 /* DSR input asserted */
+#define CD_ACT 0x08 /* CD input asserted */
+#define TXFIFOMT 0x04 /* Tx FIFO is empty */
+#define TXSHRMT 0x02 /* Tx shift register is empty */
+#define RDA 0x01 /* Rx data available */
+#define DRAINED (TXFIFOMT | TXSHRMT) /* indicates Tx is drained */
+
+#define STATMODE 0x8000 /* status mode enable bit */
+#define RXFOVERFL 0x2000 /* receive FIFO overflow */
+#define RX2MATCH 0x1000 /* receive compare byte 2 match */
+#define RX1MATCH 0x0800 /* receive compare byte 1 match */
+#define RXBREAK 0x0400 /* received BREAK */
+#define RXFRAME 0x0200 /* received framing error */
+#define RXPARITY 0x0100 /* received parity error */
+#define STATERROR (RXBREAK | RXFRAME | RXPARITY)
+
+#define CTSFC_EN 0x80 /* CTS flow control enable bit */
+#define RTSTOG_EN 0x40 /* RTS toggle enable bit */
+#define TXINT_EN 0x10 /* transmit interrupt enable */
+#define STOP2 0x08 /* enable 2 stop bits (0 = 1 stop) */
+#define PARITY_EN 0x04 /* enable parity (0 = no parity) */
+#define EVEN_PAR 0x02 /* even parity (0 = odd parity) */
+#define DATA8BIT 0x01 /* 8 bit data (0 = 7 bit data) */
+
+#define SETBREAK 0x10 /* send break condition (must clear) */
+#define LOCALLOOP 0x08 /* local loopback set for test */
+#define SET_DTR 0x04 /* assert DTR */
+#define SET_RTS 0x02 /* assert RTS */
+#define TX_ENABLE 0x01 /* enable transmitter */
+
+#define RTSFC_EN 0x40 /* RTS flow control enable */
+#define RXPROC_EN 0x20 /* receive processor enable */
+#define TRIG_NO 0x00 /* Rx FIFO trigger level 0 (no trigger) */
+#define TRIG_1 0x08 /* trigger level 1 char */
+#define TRIG_1_2 0x10 /* trigger level 1/2 */
+#define TRIG_7_8 0x18 /* trigger level 7/8 */
+#define TRIG_MASK 0x18 /* trigger level mask */
+#define SRCINT_EN 0x04 /* special Rx condition interrupt enable */
+#define RXINT_EN 0x02 /* Rx interrupt enable */
+#define MCINT_EN 0x01 /* modem change interrupt enable */
+
+#define RXF_TRIG 0x20 /* Rx FIFO trigger level interrupt */
+#define TXFIFO_MT 0x10 /* Tx FIFO empty interrupt */
+#define SRC_INT 0x08 /* special receive condition interrupt */
+#define DELTA_CD 0x04 /* CD change interrupt */
+#define DELTA_CTS 0x02 /* CTS change interrupt */
+#define DELTA_DSR 0x01 /* DSR change interrupt */
+
+#define REP1W2_EN 0x10 /* replace byte 1 with 2 bytes enable */
+#define IGN2_EN 0x08 /* ignore byte 2 enable */
+#define IGN1_EN 0x04 /* ignore byte 1 enable */
+#define COMP2_EN 0x02 /* compare byte 2 enable */
+#define COMP1_EN 0x01 /* compare byte 1 enable */
+
+#define RESET_ALL 0x80 /* reset AIOP (all channels) */
+#define TXOVERIDE 0x40 /* Transmit software off override */
+#define RESETUART 0x20 /* reset channel's UART */
+#define RESTXFCNT 0x10 /* reset channel's Tx FIFO count register */
+#define RESRXFCNT 0x08 /* reset channel's Rx FIFO count register */
+
+#define INTSTAT0 0x01 /* AIOP 0 interrupt status */
+#define INTSTAT1 0x02 /* AIOP 1 interrupt status */
+#define INTSTAT2 0x04 /* AIOP 2 interrupt status */
+#define INTSTAT3 0x08 /* AIOP 3 interrupt status */
+
+#define INTR_EN 0x08 /* allow interrupts to host */
+#define INT_STROB 0x04 /* strobe and clear interrupt line (EOI) */
+
+/**************************************************************************
+ MUDBAC remapped for PCI
+**************************************************************************/
+
+#define _CFG_INT_PCI 0x40
+#define _PCI_INT_FUNC 0x3A
+
+#define PCI_STROB 0x2000 /* bit 13 of int aiop register */
+#define INTR_EN_PCI 0x0010 /* allow interrupts to host */
+
+/*
+ * Definitions for Universal PCI board registers
+ */
+#define _PCI_9030_INT_CTRL 0x4c /* Offsets from BAR1 */
+#define _PCI_9030_GPIO_CTRL 0x54
+#define PCI_INT_CTRL_AIOP 0x0001
+#define PCI_GPIO_CTRL_8PORT 0x4000
+#define _PCI_9030_RING_IND 0xc0 /* Offsets from BAR1 */
+
+#define CHAN3_EN 0x08 /* enable AIOP 3 */
+#define CHAN2_EN 0x04 /* enable AIOP 2 */
+#define CHAN1_EN 0x02 /* enable AIOP 1 */
+#define CHAN0_EN 0x01 /* enable AIOP 0 */
+#define FREQ_DIS 0x00
+#define FREQ_274HZ 0x60
+#define FREQ_137HZ 0x50
+#define FREQ_69HZ 0x40
+#define FREQ_34HZ 0x30
+#define FREQ_17HZ 0x20
+#define FREQ_9HZ 0x10
+#define PERIODIC_ONLY 0x80 /* only PERIODIC interrupt */
+
+#define CHANINT_EN 0x0100 /* flags to enable/disable channel ints */
+
+#define RDATASIZE 72
+#define RREGDATASIZE 52
+
+/*
+ * AIOP interrupt bits for ISA/PCI boards and UPCI boards.
+ */
+#define AIOP_INTR_BIT_0 0x0001
+#define AIOP_INTR_BIT_1 0x0002
+#define AIOP_INTR_BIT_2 0x0004
+#define AIOP_INTR_BIT_3 0x0008
+
+#define AIOP_INTR_BITS ( \
+ AIOP_INTR_BIT_0 \
+ | AIOP_INTR_BIT_1 \
+ | AIOP_INTR_BIT_2 \
+ | AIOP_INTR_BIT_3)
+
+#define UPCI_AIOP_INTR_BIT_0 0x0004
+#define UPCI_AIOP_INTR_BIT_1 0x0020
+#define UPCI_AIOP_INTR_BIT_2 0x0100
+#define UPCI_AIOP_INTR_BIT_3 0x0800
+
+#define UPCI_AIOP_INTR_BITS ( \
+ UPCI_AIOP_INTR_BIT_0 \
+ | UPCI_AIOP_INTR_BIT_1 \
+ | UPCI_AIOP_INTR_BIT_2 \
+ | UPCI_AIOP_INTR_BIT_3)
+
+/* Controller level information structure */
+typedef struct {
+ int CtlID;
+ int CtlNum;
+ int BusType;
+ int boardType;
+ int isUPCI;
+ WordIO_t PCIIO;
+ WordIO_t PCIIO2;
+ ByteIO_t MBaseIO;
+ ByteIO_t MReg1IO;
+ ByteIO_t MReg2IO;
+ ByteIO_t MReg3IO;
+ Byte_t MReg2;
+ Byte_t MReg3;
+ int NumAiop;
+ int AltChanRingIndicator;
+ ByteIO_t UPCIRingInd;
+ WordIO_t AiopIO[AIOP_CTL_SIZE];
+ ByteIO_t AiopIntChanIO[AIOP_CTL_SIZE];
+ int AiopID[AIOP_CTL_SIZE];
+ int AiopNumChan[AIOP_CTL_SIZE];
+ Word_t *AiopIntrBits;
+} CONTROLLER_T;
+
+typedef CONTROLLER_T CONTROLLER_t;
+
+/* Channel level information structure */
+typedef struct {
+ CONTROLLER_T *CtlP;
+ int AiopNum;
+ int ChanID;
+ int ChanNum;
+ int rtsToggle;
+
+ ByteIO_t Cmd;
+ ByteIO_t IntChan;
+ ByteIO_t IntMask;
+ DWordIO_t IndexAddr;
+ WordIO_t IndexData;
+
+ WordIO_t TxRxData;
+ WordIO_t ChanStat;
+ WordIO_t TxRxCount;
+ ByteIO_t IntID;
+
+ Word_t TxFIFO;
+ Word_t TxFIFOPtrs;
+ Word_t RxFIFO;
+ Word_t RxFIFOPtrs;
+ Word_t TxPrioCnt;
+ Word_t TxPrioPtr;
+ Word_t TxPrioBuf;
+
+ Byte_t R[RREGDATASIZE];
+
+ Byte_t BaudDiv[4];
+ Byte_t TxControl[4];
+ Byte_t RxControl[4];
+ Byte_t TxEnables[4];
+ Byte_t TxCompare[4];
+ Byte_t TxReplace1[4];
+ Byte_t TxReplace2[4];
+} CHANNEL_T;
+
+typedef CHANNEL_T CHANNEL_t;
+typedef CHANNEL_T *CHANPTR_T;
+
+#define InterfaceModeRS232 0x00
+#define InterfaceModeRS422 0x08
+#define InterfaceModeRS485 0x10
+#define InterfaceModeRS232T 0x18
+
+/***************************************************************************
+Function: sClrBreak
+Purpose: Stop sending a transmit BREAK signal
+Call: sClrBreak(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrBreak(ChP) \
+do { \
+ (ChP)->TxControl[3] &= ~SETBREAK; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrDTR
+Purpose: Clr the DTR output
+Call: sClrDTR(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrDTR(ChP) \
+do { \
+ (ChP)->TxControl[3] &= ~SET_DTR; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrRTS
+Purpose: Clr the RTS output
+Call: sClrRTS(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrRTS(ChP) \
+do { \
+ if ((ChP)->rtsToggle) break; \
+ (ChP)->TxControl[3] &= ~SET_RTS; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrTxXOFF
+Purpose: Clear any existing transmit software flow control off condition
+Call: sClrTxXOFF(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrTxXOFF(ChP) \
+do { \
+ sOutB((ChP)->Cmd,TXOVERIDE | (Byte_t)(ChP)->ChanNum); \
+ sOutB((ChP)->Cmd,(Byte_t)(ChP)->ChanNum); \
+} while (0)
+
+/***************************************************************************
+Function: sCtlNumToCtlPtr
+Purpose: Convert a controller number to controller structure pointer
+Call: sCtlNumToCtlPtr(CtlNum)
+ int CtlNum; Controller number
+Return: CONTROLLER_T *: Ptr to controller structure
+*/
+#define sCtlNumToCtlPtr(CTLNUM) &sController[CTLNUM]
+
+/***************************************************************************
+Function: sControllerEOI
+Purpose: Strobe the MUDBAC's End Of Interrupt bit.
+Call: sControllerEOI(CtlP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+*/
+#define sControllerEOI(CTLP) sOutB((CTLP)->MReg2IO,(CTLP)->MReg2 | INT_STROB)
+
+/***************************************************************************
+Function: sPCIControllerEOI
+Purpose: Strobe the PCI End Of Interrupt bit.
+ For the UPCI boards, toggle the AIOP interrupt enable bit
+ (this was taken from the Windows driver).
+Call: sPCIControllerEOI(CtlP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+*/
+#define sPCIControllerEOI(CTLP) \
+do { \
+ if ((CTLP)->isUPCI) { \
+ Word_t w = sInW((CTLP)->PCIIO); \
+ sOutW((CTLP)->PCIIO, (w ^ PCI_INT_CTRL_AIOP)); \
+ sOutW((CTLP)->PCIIO, w); \
+ } \
+ else { \
+ sOutW((CTLP)->PCIIO, PCI_STROB); \
+ } \
+} while (0)
+
+/***************************************************************************
+Function: sDisAiop
+Purpose: Disable I/O access to an AIOP
+Call: sDisAiop(CltP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int AiopNum; Number of AIOP on controller
+*/
+#define sDisAiop(CTLP,AIOPNUM) \
+do { \
+ (CTLP)->MReg3 &= sBitMapClrTbl[AIOPNUM]; \
+ sOutB((CTLP)->MReg3IO,(CTLP)->MReg3); \
+} while (0)
+
+/***************************************************************************
+Function: sDisCTSFlowCtl
+Purpose: Disable output flow control using CTS
+Call: sDisCTSFlowCtl(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisCTSFlowCtl(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~CTSFC_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisIXANY
+Purpose: Disable IXANY Software Flow Control
+Call: sDisIXANY(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisIXANY(ChP) \
+do { \
+ (ChP)->R[0x0e] = 0x86; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x0c]); \
+} while (0)
+
+/***************************************************************************
+Function: DisParity
+Purpose: Disable parity
+Call: sDisParity(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+ sDisParity(), sSetOddParity(), and sSetEvenParity().
+*/
+#define sDisParity(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~PARITY_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisRTSToggle
+Purpose: Disable RTS toggle
+Call: sDisRTSToggle(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisRTSToggle(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~RTSTOG_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+ (ChP)->rtsToggle = 0; \
+} while (0)
+
+/***************************************************************************
+Function: sDisRxFIFO
+Purpose: Disable Rx FIFO
+Call: sDisRxFIFO(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisRxFIFO(ChP) \
+do { \
+ (ChP)->R[0x32] = 0x0a; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x30]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisRxStatusMode
+Purpose: Disable the Rx status mode
+Call: sDisRxStatusMode(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: This takes the channel out of the receive status mode. All
+ subsequent reads of receive data using sReadRxWord() will return
+ two data bytes.
+*/
+#define sDisRxStatusMode(ChP) sOutW((ChP)->ChanStat,0)
+
+/***************************************************************************
+Function: sDisTransmit
+Purpose: Disable transmit
+Call: sDisTransmit(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+ This disables movement of Tx data from the Tx FIFO into the 1 byte
+ Tx buffer. Therefore there could be up to a 2 byte latency
+ between the time sDisTransmit() is called and the transmit buffer
+ and transmit shift register going completely empty.
+*/
+#define sDisTransmit(ChP) \
+do { \
+ (ChP)->TxControl[3] &= ~TX_ENABLE; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisTxSoftFlowCtl
+Purpose: Disable Tx Software Flow Control
+Call: sDisTxSoftFlowCtl(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisTxSoftFlowCtl(ChP) \
+do { \
+ (ChP)->R[0x06] = 0x8a; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnAiop
+Purpose: Enable I/O access to an AIOP
+Call: sEnAiop(CltP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int AiopNum; Number of AIOP on controller
+*/
+#define sEnAiop(CTLP,AIOPNUM) \
+do { \
+ (CTLP)->MReg3 |= sBitMapSetTbl[AIOPNUM]; \
+ sOutB((CTLP)->MReg3IO,(CTLP)->MReg3); \
+} while (0)
+
+/***************************************************************************
+Function: sEnCTSFlowCtl
+Purpose: Enable output flow control using CTS
+Call: sEnCTSFlowCtl(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnCTSFlowCtl(ChP) \
+do { \
+ (ChP)->TxControl[2] |= CTSFC_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnIXANY
+Purpose: Enable IXANY Software Flow Control
+Call: sEnIXANY(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnIXANY(ChP) \
+do { \
+ (ChP)->R[0x0e] = 0x21; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x0c]); \
+} while (0)
+
+/***************************************************************************
+Function: EnParity
+Purpose: Enable parity
+Call: sEnParity(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+ sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: Before enabling parity odd or even parity should be chosen using
+ functions sSetOddParity() or sSetEvenParity().
+*/
+#define sEnParity(ChP) \
+do { \
+ (ChP)->TxControl[2] |= PARITY_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRTSToggle
+Purpose: Enable RTS toggle
+Call: sEnRTSToggle(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function will disable RTS flow control and clear the RTS
+ line to allow operation of RTS toggle.
+*/
+#define sEnRTSToggle(ChP) \
+do { \
+ (ChP)->RxControl[2] &= ~RTSFC_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+ (ChP)->TxControl[2] |= RTSTOG_EN; \
+ (ChP)->TxControl[3] &= ~SET_RTS; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+ (ChP)->rtsToggle = 1; \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxFIFO
+Purpose: Enable Rx FIFO
+Call: sEnRxFIFO(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnRxFIFO(ChP) \
+do { \
+ (ChP)->R[0x32] = 0x08; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x30]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxProcessor
+Purpose: Enable the receive processor
+Call: sEnRxProcessor(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function is used to start the receive processor. When
+ the channel is in the reset state the receive processor is not
+ running. This is done to prevent the receive processor from
+ executing invalid microcode instructions prior to the
+ downloading of the microcode.
+
+Warnings: This function must be called after valid microcode has been
+ downloaded to the AIOP, and it must not be called before the
+ microcode has been downloaded.
+*/
+#define sEnRxProcessor(ChP) \
+do { \
+ (ChP)->RxControl[2] |= RXPROC_EN; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxStatusMode
+Purpose: Enable the Rx status mode
+Call: sEnRxStatusMode(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: This places the channel in the receive status mode. All subsequent
+ reads of receive data using sReadRxWord() will return a data byte
+ in the low word and a status byte in the high word.
+
+*/
+#define sEnRxStatusMode(ChP) sOutW((ChP)->ChanStat,STATMODE)
+
+/***************************************************************************
+Function: sEnTransmit
+Purpose: Enable transmit
+Call: sEnTransmit(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnTransmit(ChP) \
+do { \
+ (ChP)->TxControl[3] |= TX_ENABLE; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnTxSoftFlowCtl
+Purpose: Enable Tx Software Flow Control
+Call: sEnTxSoftFlowCtl(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnTxSoftFlowCtl(ChP) \
+do { \
+ (ChP)->R[0x06] = 0xc5; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sGetAiopIntStatus
+Purpose: Get the AIOP interrupt status
+Call: sGetAiopIntStatus(CtlP,AiopNum)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int AiopNum; AIOP number
+Return: Byte_t: The AIOP interrupt status. Bits 0 through 7
+ represent channels 0 through 7 respectively. If a
+ bit is set that channel is interrupting.
+*/
+#define sGetAiopIntStatus(CTLP,AIOPNUM) sInB((CTLP)->AiopIntChanIO[AIOPNUM])
+
+/***************************************************************************
+Function: sGetAiopNumChan
+Purpose: Get the number of channels supported by an AIOP
+Call: sGetAiopNumChan(CtlP,AiopNum)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+ int AiopNum; AIOP number
+Return: int: The number of channels supported by the AIOP
+*/
+#define sGetAiopNumChan(CTLP,AIOPNUM) (CTLP)->AiopNumChan[AIOPNUM]
+
+/***************************************************************************
+Function: sGetChanIntID
+Purpose: Get a channel's interrupt identification byte
+Call: sGetChanIntID(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: Byte_t: The channel interrupt ID. Can be any
+ combination of the following flags:
+ RXF_TRIG: Rx FIFO trigger level interrupt
+ TXFIFO_MT: Tx FIFO empty interrupt
+ SRC_INT: Special receive condition interrupt
+ DELTA_CD: CD change interrupt
+ DELTA_CTS: CTS change interrupt
+ DELTA_DSR: DSR change interrupt
+*/
+#define sGetChanIntID(ChP) (sInB((ChP)->IntID) & (RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR))
+
+/***************************************************************************
+Function: sGetChanNum
+Purpose: Get the number of a channel within an AIOP
+Call: sGetChanNum(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: int: Channel number within AIOP, or NULLCHAN if channel does
+ not exist.
+*/
+#define sGetChanNum(ChP) (ChP)->ChanNum
+
+/***************************************************************************
+Function: sGetChanStatus
+Purpose: Get the channel status
+Call: sGetChanStatus(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: Word_t: The channel status. Can be any combination of
+ the following flags:
+ LOW BYTE FLAGS
+ CTS_ACT: CTS input asserted
+ DSR_ACT: DSR input asserted
+ CD_ACT: CD input asserted
+ TXFIFOMT: Tx FIFO is empty
+ TXSHRMT: Tx shift register is empty
+ RDA: Rx data available
+
+ HIGH BYTE FLAGS
+ STATMODE: status mode enable bit
+ RXFOVERFL: receive FIFO overflow
+ RX2MATCH: receive compare byte 2 match
+ RX1MATCH: receive compare byte 1 match
+ RXBREAK: received BREAK
+ RXFRAME: received framing error
+ RXPARITY: received parity error
+Warnings: This function will clear the high byte flags in the Channel
+ Status Register.
+*/
+#define sGetChanStatus(ChP) sInW((ChP)->ChanStat)
+
+/***************************************************************************
+Function: sGetChanStatusLo
+Purpose: Get the low byte only of the channel status
+Call: sGetChanStatusLo(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: Byte_t: The channel status low byte. Can be any combination
+ of the following flags:
+ CTS_ACT: CTS input asserted
+ DSR_ACT: DSR input asserted
+ CD_ACT: CD input asserted
+ TXFIFOMT: Tx FIFO is empty
+ TXSHRMT: Tx shift register is empty
+ RDA: Rx data available
+*/
+#define sGetChanStatusLo(ChP) sInB((ByteIO_t)(ChP)->ChanStat)
+
+/**********************************************************************
+ * Get RI status of channel
+ * Defined as a function in rocket.c -aes
+ */
+#if 0
+#define sGetChanRI(ChP) ((ChP)->CtlP->AltChanRingIndicator ? \
+ (sInB((ByteIO_t)((ChP)->ChanStat+8)) & DSR_ACT) : \
+ (((ChP)->CtlP->boardType == ROCKET_TYPE_PC104) ? \
+ (!(sInB((ChP)->CtlP->AiopIO[3]) & sBitMapSetTbl[(ChP)->ChanNum])) : \
+ 0))
+#endif
+
+/***************************************************************************
+Function: sGetControllerIntStatus
+Purpose: Get the controller interrupt status
+Call: sGetControllerIntStatus(CtlP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+Return: Byte_t: The controller interrupt status in the lower 4
+ bits. Bits 0 through 3 represent AIOP's 0
+ through 3 respectively. If a bit is set that
+ AIOP is interrupting. Bits 4 through 7 will
+ always be cleared.
+*/
+#define sGetControllerIntStatus(CTLP) (sInB((CTLP)->MReg1IO) & 0x0f)
+
+/***************************************************************************
+Function: sPCIGetControllerIntStatus
+Purpose: Get the controller interrupt status
+Call: sPCIGetControllerIntStatus(CtlP)
+ CONTROLLER_T *CtlP; Ptr to controller structure
+Return: unsigned char: The controller interrupt status in the lower 4
+ bits and bit 4. Bits 0 through 3 represent AIOP's 0
+ through 3 respectively. Bit 4 is set if the int
+ was generated from periodic. If a bit is set the
+ AIOP is interrupting.
+*/
+#define sPCIGetControllerIntStatus(CTLP) \
+ ((CTLP)->isUPCI ? \
+ (sInW((CTLP)->PCIIO2) & UPCI_AIOP_INTR_BITS) : \
+ ((sInW((CTLP)->PCIIO) >> 8) & AIOP_INTR_BITS))
+
+/***************************************************************************
+
+Function: sGetRxCnt
+Purpose: Get the number of data bytes in the Rx FIFO
+Call: sGetRxCnt(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: int: The number of data bytes in the Rx FIFO.
+Comments: Byte read of count register is required to obtain Rx count.
+
+*/
+#define sGetRxCnt(ChP) sInW((ChP)->TxRxCount)
+
+/***************************************************************************
+Function: sGetTxCnt
+Purpose: Get the number of data bytes in the Tx FIFO
+Call: sGetTxCnt(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: Byte_t: The number of data bytes in the Tx FIFO.
+Comments: Byte read of count register is required to obtain Tx count.
+
+*/
+#define sGetTxCnt(ChP) sInB((ByteIO_t)(ChP)->TxRxCount)
+
+/*****************************************************************************
+Function: sGetTxRxDataIO
+Purpose: Get the I/O address of a channel's TxRx Data register
+Call: sGetTxRxDataIO(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Return: WordIO_t: I/O address of a channel's TxRx Data register
+*/
+#define sGetTxRxDataIO(ChP) (ChP)->TxRxData
+
+/***************************************************************************
+Function: sInitChanDefaults
+Purpose: Initialize a channel structure to it's default state.
+Call: sInitChanDefaults(ChP)
+ CHANNEL_T *ChP; Ptr to the channel structure
+Comments: This function must be called once for every channel structure
+ that exists before any other SSCI calls can be made.
+
+*/
+#define sInitChanDefaults(ChP) \
+do { \
+ (ChP)->CtlP = NULLCTLPTR; \
+ (ChP)->AiopNum = NULLAIOP; \
+ (ChP)->ChanID = AIOPID_NULL; \
+ (ChP)->ChanNum = NULLCHAN; \
+} while (0)
+
+/***************************************************************************
+Function: sResetAiopByNum
+Purpose: Reset the AIOP by number
+Call: sResetAiopByNum(CTLP,AIOPNUM)
+ CONTROLLER_T CTLP; Ptr to controller structure
+ AIOPNUM; AIOP index
+*/
+#define sResetAiopByNum(CTLP,AIOPNUM) \
+do { \
+ sOutB((CTLP)->AiopIO[(AIOPNUM)]+_CMD_REG,RESET_ALL); \
+ sOutB((CTLP)->AiopIO[(AIOPNUM)]+_CMD_REG,0x0); \
+} while (0)
+
+/***************************************************************************
+Function: sSendBreak
+Purpose: Send a transmit BREAK signal
+Call: sSendBreak(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSendBreak(ChP) \
+do { \
+ (ChP)->TxControl[3] |= SETBREAK; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetBaud
+Purpose: Set baud rate
+Call: sSetBaud(ChP,Divisor)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Word_t Divisor; 16 bit baud rate divisor for channel
+*/
+#define sSetBaud(ChP,DIVISOR) \
+do { \
+ (ChP)->BaudDiv[2] = (Byte_t)(DIVISOR); \
+ (ChP)->BaudDiv[3] = (Byte_t)((DIVISOR) >> 8); \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->BaudDiv[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetData7
+Purpose: Set data bits to 7
+Call: sSetData7(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetData7(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~DATA8BIT; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetData8
+Purpose: Set data bits to 8
+Call: sSetData8(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetData8(ChP) \
+do { \
+ (ChP)->TxControl[2] |= DATA8BIT; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetDTR
+Purpose: Set the DTR output
+Call: sSetDTR(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetDTR(ChP) \
+do { \
+ (ChP)->TxControl[3] |= SET_DTR; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetEvenParity
+Purpose: Set even parity
+Call: sSetEvenParity(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+ sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: This function has no effect unless parity is enabled with function
+ sEnParity().
+*/
+#define sSetEvenParity(ChP) \
+do { \
+ (ChP)->TxControl[2] |= EVEN_PAR; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetOddParity
+Purpose: Set odd parity
+Call: sSetOddParity(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+ sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: This function has no effect unless parity is enabled with function
+ sEnParity().
+*/
+#define sSetOddParity(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~EVEN_PAR; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetRTS
+Purpose: Set the RTS output
+Call: sSetRTS(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetRTS(ChP) \
+do { \
+ if ((ChP)->rtsToggle) break; \
+ (ChP)->TxControl[3] |= SET_RTS; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetRxTrigger
+Purpose: Set the Rx FIFO trigger level
+Call: sSetRxProcessor(ChP,Level)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Byte_t Level; Number of characters in Rx FIFO at which the
+ interrupt will be generated. Can be any of the following flags:
+
+ TRIG_NO: no trigger
+ TRIG_1: 1 character in FIFO
+ TRIG_1_2: FIFO 1/2 full
+ TRIG_7_8: FIFO 7/8 full
+Comments: An interrupt will be generated when the trigger level is reached
+ only if function sEnInterrupt() has been called with flag
+ RXINT_EN set. The RXF_TRIG flag in the Interrupt Idenfification
+ register will be set whenever the trigger level is reached
+ regardless of the setting of RXINT_EN.
+
+*/
+#define sSetRxTrigger(ChP,LEVEL) \
+do { \
+ (ChP)->RxControl[2] &= ~TRIG_MASK; \
+ (ChP)->RxControl[2] |= LEVEL; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetStop1
+Purpose: Set stop bits to 1
+Call: sSetStop1(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetStop1(ChP) \
+do { \
+ (ChP)->TxControl[2] &= ~STOP2; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetStop2
+Purpose: Set stop bits to 2
+Call: sSetStop2(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetStop2(ChP) \
+do { \
+ (ChP)->TxControl[2] |= STOP2; \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetTxXOFFChar
+Purpose: Set the Tx XOFF flow control character
+Call: sSetTxXOFFChar(ChP,Ch)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Byte_t Ch; The value to set the Tx XOFF character to
+*/
+#define sSetTxXOFFChar(ChP,CH) \
+do { \
+ (ChP)->R[0x07] = (CH); \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetTxXONChar
+Purpose: Set the Tx XON flow control character
+Call: sSetTxXONChar(ChP,Ch)
+ CHANNEL_T *ChP; Ptr to channel structure
+ Byte_t Ch; The value to set the Tx XON character to
+*/
+#define sSetTxXONChar(ChP,CH) \
+do { \
+ (ChP)->R[0x0b] = (CH); \
+ sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x08]); \
+} while (0)
+
+/***************************************************************************
+Function: sStartRxProcessor
+Purpose: Start a channel's receive processor
+Call: sStartRxProcessor(ChP)
+ CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function is used to start a Rx processor after it was
+ stopped with sStopRxProcessor() or sStopSWInFlowCtl(). It
+ will restart both the Rx processor and software input flow control.
+
+*/
+#define sStartRxProcessor(ChP) sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0])
+
+/***************************************************************************
+Function: sWriteTxByte
+Purpose: Write a transmit data byte to a channel.
+ ByteIO_t io: Channel transmit register I/O address. This can
+ be obtained with sGetTxRxDataIO().
+ Byte_t Data; The transmit data byte.
+Warnings: This function writes the data byte without checking to see if
+ sMaxTxSize is exceeded in the Tx FIFO.
+*/
+#define sWriteTxByte(IO,DATA) sOutB(IO,DATA)
+
+int sInitController(CONTROLLER_T * CtlP,
+ int CtlNum,
+ ByteIO_t MudbacIO,
+ ByteIO_t * AiopIOList,
+ int AiopIOListSize,
+ int IRQNum, Byte_t Frequency, int PeriodicOnly);
+
+int sPCIInitController(CONTROLLER_T * CtlP,
+ int CtlNum,
+ ByteIO_t * AiopIOList,
+ int AiopIOListSize,
+ WordIO_t ConfigIO,
+ int IRQNum,
+ Byte_t Frequency,
+ int PeriodicOnly,
+ int altChanRingIndicator, int UPCIRingInd);
+
+int sReadAiopID(ByteIO_t io);
+int sReadAiopNumChan(WordIO_t io);
+int sInitChan(CONTROLLER_T * CtlP,
+ CHANNEL_T * ChP, int AiopNum, int ChanNum);
+Byte_t sGetRxErrStatus(CHANNEL_T * ChP);
+void sStopRxProcessor(CHANNEL_T * ChP);
+void sStopSWInFlowCtl(CHANNEL_T * ChP);
+void sFlushRxFIFO(CHANNEL_T * ChP);
+void sFlushTxFIFO(CHANNEL_T * ChP);
+int sWriteTxPrioByte(CHANNEL_T * ChP, Byte_t Data);
+void sEnInterrupts(CHANNEL_T * ChP, Word_t Flags);
+void sDisInterrupts(CHANNEL_T * ChP, Word_t Flags);
+void sModemReset(CONTROLLER_T * CtlP, int chan, int on);
+void sPCIModemReset(CONTROLLER_T * CtlP, int chan, int on);
+void sSetInterfaceMode(CHANNEL_T * ChP, Byte_t mode);
+
+extern Byte_t R[RDATASIZE];
+extern CONTROLLER_T sController[CTL_SIZE];
+extern Byte_t sIRQMap[16];
+extern Byte_t sBitMapClrTbl[8];
+extern Byte_t sBitMapSetTbl[8];
+extern int sClockPrescale;
+
+/*
+ * Begin Linux specific definitions for the Rocketport driver
+ *
+ * This code is Copyright Theodore Ts'o, 1995-1997
+ */
+
+struct r_port {
+ int magic;
+ int line;
+ int flags;
+ int count;
+ int blocked_open;
+ struct tty_struct *tty;
+ unsigned int board:3;
+ unsigned int aiop:2;
+ unsigned int chan:3;
+ CONTROLLER_t *ctlp;
+ CHANNEL_t channel;
+ int closing_wait;
+ int close_delay;
+ int intmask;
+ int xmit_fifo_room; /* room in xmit fifo */
+ unsigned char *xmit_buf;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ int session;
+ int pgrp;
+ int cd_status;
+ int ignore_status_mask;
+ int read_status_mask;
+ int cps;
+
+#ifdef DECLARE_WAITQUEUE
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+#else
+ struct wait_queue *open_wait;
+ struct wait_queue *close_wait;
+#endif
+ spinlock_t slock;
+ struct semaphore write_sem;
+};
+
+#define RPORT_MAGIC 0x525001
+
+#define NUM_BOARDS 8
+#define MAX_RP_PORTS (32*NUM_BOARDS)
+
+/*
+ * The size of the xmit buffer is 1 page, or 4096 bytes
+ */
+#define XMIT_BUF_SIZE 4096
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+/* Internal flags used only by the rocketport driver */
+#define ROCKET_INITIALIZED 0x80000000 /* Port is active */
+#define ROCKET_CLOSING 0x40000000 /* Serial port is closing */
+#define ROCKET_NORMAL_ACTIVE 0x20000000 /* Normal port is active */
+
+/* tty subtypes */
+#define SERIAL_TYPE_NORMAL 1
+
+/*
+ * Assigned major numbers for the Comtrol Rocketport
+ */
+#define TTY_ROCKET_MAJOR 46
+#define CUA_ROCKET_MAJOR 47
+
+#ifdef PCI_VENDOR_ID_RP
+#undef PCI_VENDOR_ID_RP
+#undef PCI_DEVICE_ID_RP8OCTA
+#undef PCI_DEVICE_ID_RP8INTF
+#undef PCI_DEVICE_ID_RP16INTF
+#undef PCI_DEVICE_ID_RP32INTF
+#undef PCI_DEVICE_ID_URP8OCTA
+#undef PCI_DEVICE_ID_URP8INTF
+#undef PCI_DEVICE_ID_URP16INTF
+#undef PCI_DEVICE_ID_CRP16INTF
+#undef PCI_DEVICE_ID_URP32INTF
+#endif
+
+/* Comtrol PCI Vendor ID */
+#define PCI_VENDOR_ID_RP 0x11fe
+
+/* Comtrol Device ID's */
+#define PCI_DEVICE_ID_RP32INTF 0x0001 /* Rocketport 32 port w/external I/F */
+#define PCI_DEVICE_ID_RP8INTF 0x0002 /* Rocketport 8 port w/external I/F */
+#define PCI_DEVICE_ID_RP16INTF 0x0003 /* Rocketport 16 port w/external I/F */
+#define PCI_DEVICE_ID_RP4QUAD 0x0004 /* Rocketport 4 port w/quad cable */
+#define PCI_DEVICE_ID_RP8OCTA 0x0005 /* Rocketport 8 port w/octa cable */
+#define PCI_DEVICE_ID_RP8J 0x0006 /* Rocketport 8 port w/RJ11 connectors */
+#define PCI_DEVICE_ID_RP4J 0x0007 /* Rocketport 4 port w/RJ11 connectors */
+#define PCI_DEVICE_ID_RP8SNI 0x0008 /* Rocketport 8 port w/ DB78 SNI (Siemens) connector */
+#define PCI_DEVICE_ID_RP16SNI 0x0009 /* Rocketport 16 port w/ DB78 SNI (Siemens) connector */
+#define PCI_DEVICE_ID_RPP4 0x000A /* Rocketport Plus 4 port */
+#define PCI_DEVICE_ID_RPP8 0x000B /* Rocketport Plus 8 port */
+#define PCI_DEVICE_ID_RP6M 0x000C /* RocketModem 6 port */
+#define PCI_DEVICE_ID_RP4M 0x000D /* RocketModem 4 port */
+#define PCI_DEVICE_ID_RP2_232 0x000E /* Rocketport Plus 2 port RS232 */
+#define PCI_DEVICE_ID_RP2_422 0x000F /* Rocketport Plus 2 port RS422 */
+
+/* Universal PCI boards */
+#define PCI_DEVICE_ID_URP32INTF 0x0801 /* Rocketport UPCI 32 port w/external I/F */
+#define PCI_DEVICE_ID_URP8INTF 0x0802 /* Rocketport UPCI 8 port w/external I/F */
+#define PCI_DEVICE_ID_URP16INTF 0x0803 /* Rocketport UPCI 16 port w/external I/F */
+#define PCI_DEVICE_ID_URP8OCTA 0x0805 /* Rocketport UPCI 8 port w/octa cable */
+#define PCI_DEVICE_ID_UPCI_RM3_8PORT 0x080C /* Rocketmodem III 8 port */
+#define PCI_DEVICE_ID_UPCI_RM3_4PORT 0x080D /* Rocketmodem III 4 port */
+
+/* Compact PCI device */
+#define PCI_DEVICE_ID_CRP16INTF 0x0903 /* Rocketport Compact PCI 16 port w/external I/F */
+
+#define TTY_GET_LINE(t) t->index
+#define TTY_DRIVER_MINOR_START(t) t->driver->minor_start
+#define TTY_DRIVER_SUBTYPE(t) t->driver->subtype
+#define TTY_DRIVER_NAME(t) t->driver->name
+#define TTY_DRIVER_NAME_BASE(t) t->driver->name_base
+#define TTY_DRIVER_FLUSH_BUFFER_EXISTS(t) t->driver->flush_buffer
+#define TTY_DRIVER_FLUSH_BUFFER(t) t->driver->flush_buffer(t)
+
+
diff --git a/drivers/char/rtc.c b/drivers/char/rtc.c
new file mode 100644
index 000000000000..ff4f09804865
--- /dev/null
+++ b/drivers/char/rtc.c
@@ -0,0 +1,1354 @@
+/*
+ * Real Time Clock interface for Linux
+ *
+ * Copyright (C) 1996 Paul Gortmaker
+ *
+ * This driver allows use of the real time clock (built into
+ * nearly all computers) from user space. It exports the /dev/rtc
+ * interface supporting various ioctl() and also the
+ * /proc/driver/rtc pseudo-file for status information.
+ *
+ * The ioctls can be used to set the interrupt behaviour and
+ * generation rate from the RTC via IRQ 8. Then the /dev/rtc
+ * interface can be used to make use of these timer interrupts,
+ * be they interval or alarm based.
+ *
+ * The /dev/rtc interface will block on reads until an interrupt
+ * has been received. If a RTC interrupt has already happened,
+ * it will output an unsigned long and then block. The output value
+ * contains the interrupt status in the low byte and the number of
+ * interrupts since the last read in the remaining high bytes. The
+ * /dev/rtc interface can also be used with the select(2) call.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Based on other minimal char device drivers, like Alan's
+ * watchdog, Ted's random, etc. etc.
+ *
+ * 1.07 Paul Gortmaker.
+ * 1.08 Miquel van Smoorenburg: disallow certain things on the
+ * DEC Alpha as the CMOS clock is also used for other things.
+ * 1.09 Nikita Schmidt: epoch support and some Alpha cleanup.
+ * 1.09a Pete Zaitcev: Sun SPARC
+ * 1.09b Jeff Garzik: Modularize, init cleanup
+ * 1.09c Jeff Garzik: SMP cleanup
+ * 1.10 Paul Barton-Davis: add support for async I/O
+ * 1.10a Andrea Arcangeli: Alpha updates
+ * 1.10b Andrew Morton: SMP lock fix
+ * 1.10c Cesar Barros: SMP locking fixes and cleanup
+ * 1.10d Paul Gortmaker: delete paranoia check in rtc_exit
+ * 1.10e Maciej W. Rozycki: Handle DECstation's year weirdness.
+ * 1.11 Takashi Iwai: Kernel access functions
+ * rtc_register/rtc_unregister/rtc_control
+ * 1.11a Daniele Bellucci: Audit create_proc_read_entry in rtc_init
+ * 1.12 Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer
+ * CONFIG_HPET_EMULATE_RTC
+ *
+ */
+
+#define RTC_VERSION "1.12"
+
+#define RTC_IO_EXTENT 0x8
+
+/*
+ * Note that *all* calls to CMOS_READ and CMOS_WRITE are done with
+ * interrupts disabled. Due to the index-port/data-port (0x70/0x71)
+ * design of the RTC, we don't want two different things trying to
+ * get to it at once. (e.g. the periodic 11 min sync from time.c vs.
+ * this driver.)
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/sysctl.h>
+#include <linux/wait.h>
+#include <linux/bcd.h>
+
+#include <asm/current.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#if defined(__i386__)
+#include <asm/hpet.h>
+#endif
+
+#ifdef __sparc__
+#include <linux/pci.h>
+#include <asm/ebus.h>
+#ifdef __sparc_v9__
+#include <asm/isa.h>
+#endif
+
+static unsigned long rtc_port;
+static int rtc_irq = PCI_IRQ_NONE;
+#endif
+
+#ifdef CONFIG_HPET_RTC_IRQ
+#undef RTC_IRQ
+#endif
+
+#ifdef RTC_IRQ
+static int rtc_has_irq = 1;
+#endif
+
+#ifndef CONFIG_HPET_EMULATE_RTC
+#define is_hpet_enabled() 0
+#define hpet_set_alarm_time(hrs, min, sec) 0
+#define hpet_set_periodic_freq(arg) 0
+#define hpet_mask_rtc_irq_bit(arg) 0
+#define hpet_set_rtc_irq_bit(arg) 0
+#define hpet_rtc_timer_init() do { } while (0)
+#define hpet_rtc_dropped_irq() 0
+static inline irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) {return 0;}
+#else
+extern irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+/*
+ * We sponge a minor off of the misc major. No need slurping
+ * up another valuable major dev number for this. If you add
+ * an ioctl, make sure you don't conflict with SPARC's RTC
+ * ioctls.
+ */
+
+static struct fasync_struct *rtc_async_queue;
+
+static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);
+
+#ifdef RTC_IRQ
+static struct timer_list rtc_irq_timer;
+#endif
+
+static ssize_t rtc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos);
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+#ifdef RTC_IRQ
+static unsigned int rtc_poll(struct file *file, poll_table *wait);
+#endif
+
+static void get_rtc_alm_time (struct rtc_time *alm_tm);
+#ifdef RTC_IRQ
+static void rtc_dropped_irq(unsigned long data);
+
+static void set_rtc_irq_bit(unsigned char bit);
+static void mask_rtc_irq_bit(unsigned char bit);
+#endif
+
+static int rtc_proc_open(struct inode *inode, struct file *file);
+
+/*
+ * Bits in rtc_status. (6 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN 0x01 /* means /dev/rtc is in use */
+#define RTC_TIMER_ON 0x02 /* missed irq timer active */
+
+/*
+ * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is
+ * protected by the big kernel lock. However, ioctl can still disable the timer
+ * in rtc_status and then with del_timer after the interrupt has read
+ * rtc_status but before mod_timer is called, which would then reenable the
+ * timer (but you would need to have an awful timing before you'd trip on it)
+ */
+static unsigned long rtc_status = 0; /* bitmapped status byte. */
+static unsigned long rtc_freq = 0; /* Current periodic IRQ rate */
+static unsigned long rtc_irq_data = 0; /* our output to the world */
+static unsigned long rtc_max_user_freq = 64; /* > this, need CAP_SYS_RESOURCE */
+
+#ifdef RTC_IRQ
+/*
+ * rtc_task_lock nests inside rtc_lock.
+ */
+static DEFINE_SPINLOCK(rtc_task_lock);
+static rtc_task_t *rtc_callback = NULL;
+#endif
+
+/*
+ * If this driver ever becomes modularised, it will be really nice
+ * to make the epoch retain its value across module reload...
+ */
+
+static unsigned long epoch = 1900; /* year corresponding to 0x00 */
+
+static const unsigned char days_in_mo[] =
+{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/*
+ * Returns true if a clock update is in progress
+ */
+static inline unsigned char rtc_is_updating(void)
+{
+ unsigned char uip;
+
+ spin_lock_irq(&rtc_lock);
+ uip = (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP);
+ spin_unlock_irq(&rtc_lock);
+ return uip;
+}
+
+#ifdef RTC_IRQ
+/*
+ * A very tiny interrupt handler. It runs with SA_INTERRUPT set,
+ * but there is possibility of conflicting with the set_rtc_mmss()
+ * call (the rtc irq and the timer irq can easily run at the same
+ * time in two different CPUs). So we need to serialize
+ * accesses to the chip with the rtc_lock spinlock that each
+ * architecture should implement in the timer code.
+ * (See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
+ */
+
+irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /*
+ * Can be an alarm interrupt, update complete interrupt,
+ * or a periodic interrupt. We store the status in the
+ * low byte and the number of interrupts received since
+ * the last read in the remainder of rtc_irq_data.
+ */
+
+ spin_lock (&rtc_lock);
+ rtc_irq_data += 0x100;
+ rtc_irq_data &= ~0xff;
+ if (is_hpet_enabled()) {
+ /*
+ * In this case it is HPET RTC interrupt handler
+ * calling us, with the interrupt information
+ * passed as arg1, instead of irq.
+ */
+ rtc_irq_data |= (unsigned long)irq & 0xF0;
+ } else {
+ rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
+ }
+
+ if (rtc_status & RTC_TIMER_ON)
+ mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
+
+ spin_unlock (&rtc_lock);
+
+ /* Now do the rest of the actions */
+ spin_lock(&rtc_task_lock);
+ if (rtc_callback)
+ rtc_callback->func(rtc_callback->private_data);
+ spin_unlock(&rtc_task_lock);
+ wake_up_interruptible(&rtc_wait);
+
+ kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
+
+ return IRQ_HANDLED;
+}
+#endif
+
+/*
+ * sysctl-tuning infrastructure.
+ */
+static ctl_table rtc_table[] = {
+ {
+ .ctl_name = 1,
+ .procname = "max-user-freq",
+ .data = &rtc_max_user_freq,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ { .ctl_name = 0 }
+};
+
+static ctl_table rtc_root[] = {
+ {
+ .ctl_name = 1,
+ .procname = "rtc",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = rtc_table,
+ },
+ { .ctl_name = 0 }
+};
+
+static ctl_table dev_root[] = {
+ {
+ .ctl_name = CTL_DEV,
+ .procname = "dev",
+ .maxlen = 0,
+ .mode = 0555,
+ .child = rtc_root,
+ },
+ { .ctl_name = 0 }
+};
+
+static struct ctl_table_header *sysctl_header;
+
+static int __init init_sysctl(void)
+{
+ sysctl_header = register_sysctl_table(dev_root, 0);
+ return 0;
+}
+
+static void __exit cleanup_sysctl(void)
+{
+ unregister_sysctl_table(sysctl_header);
+}
+
+/*
+ * Now all the various file operations that we export.
+ */
+
+static ssize_t rtc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+#ifndef RTC_IRQ
+ return -EIO;
+#else
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long data;
+ ssize_t retval;
+
+ if (rtc_has_irq == 0)
+ return -EIO;
+
+ if (count < sizeof(unsigned))
+ return -EINVAL;
+
+ add_wait_queue(&rtc_wait, &wait);
+
+ do {
+ /* First make it right. Then make it fast. Putting this whole
+ * block within the parentheses of a while would be too
+ * confusing. And no, xchg() is not the answer. */
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_irq (&rtc_lock);
+ data = rtc_irq_data;
+ rtc_irq_data = 0;
+ spin_unlock_irq (&rtc_lock);
+
+ if (data != 0)
+ break;
+
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ }
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+ schedule();
+ } while (1);
+
+ if (count < sizeof(unsigned long))
+ retval = put_user(data, (unsigned int __user *)buf) ?: sizeof(int);
+ else
+ retval = put_user(data, (unsigned long __user *)buf) ?: sizeof(long);
+ out:
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&rtc_wait, &wait);
+
+ return retval;
+#endif
+}
+
+static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
+{
+ struct rtc_time wtime;
+
+#ifdef RTC_IRQ
+ if (rtc_has_irq == 0) {
+ switch (cmd) {
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ case RTC_PIE_OFF:
+ case RTC_PIE_ON:
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ case RTC_IRQP_READ:
+ case RTC_IRQP_SET:
+ return -EINVAL;
+ };
+ }
+#endif
+
+ switch (cmd) {
+#ifdef RTC_IRQ
+ case RTC_AIE_OFF: /* Mask alarm int. enab. bit */
+ {
+ mask_rtc_irq_bit(RTC_AIE);
+ return 0;
+ }
+ case RTC_AIE_ON: /* Allow alarm interrupts. */
+ {
+ set_rtc_irq_bit(RTC_AIE);
+ return 0;
+ }
+ case RTC_PIE_OFF: /* Mask periodic int. enab. bit */
+ {
+ mask_rtc_irq_bit(RTC_PIE);
+ if (rtc_status & RTC_TIMER_ON) {
+ spin_lock_irq (&rtc_lock);
+ rtc_status &= ~RTC_TIMER_ON;
+ del_timer(&rtc_irq_timer);
+ spin_unlock_irq (&rtc_lock);
+ }
+ return 0;
+ }
+ case RTC_PIE_ON: /* Allow periodic ints */
+ {
+
+ /*
+ * We don't really want Joe User enabling more
+ * than 64Hz of interrupts on a multi-user machine.
+ */
+ if (!kernel && (rtc_freq > rtc_max_user_freq) &&
+ (!capable(CAP_SYS_RESOURCE)))
+ return -EACCES;
+
+ if (!(rtc_status & RTC_TIMER_ON)) {
+ spin_lock_irq (&rtc_lock);
+ rtc_irq_timer.expires = jiffies + HZ/rtc_freq + 2*HZ/100;
+ add_timer(&rtc_irq_timer);
+ rtc_status |= RTC_TIMER_ON;
+ spin_unlock_irq (&rtc_lock);
+ }
+ set_rtc_irq_bit(RTC_PIE);
+ return 0;
+ }
+ case RTC_UIE_OFF: /* Mask ints from RTC updates. */
+ {
+ mask_rtc_irq_bit(RTC_UIE);
+ return 0;
+ }
+ case RTC_UIE_ON: /* Allow ints for RTC updates. */
+ {
+ set_rtc_irq_bit(RTC_UIE);
+ return 0;
+ }
+#endif
+ case RTC_ALM_READ: /* Read the present alarm time */
+ {
+ /*
+ * This returns a struct rtc_time. Reading >= 0xc0
+ * means "don't care" or "match all". Only the tm_hour,
+ * tm_min, and tm_sec values are filled in.
+ */
+ memset(&wtime, 0, sizeof(struct rtc_time));
+ get_rtc_alm_time(&wtime);
+ break;
+ }
+ case RTC_ALM_SET: /* Store a time into the alarm */
+ {
+ /*
+ * This expects a struct rtc_time. Writing 0xff means
+ * "don't care" or "match all". Only the tm_hour,
+ * tm_min and tm_sec are used.
+ */
+ unsigned char hrs, min, sec;
+ struct rtc_time alm_tm;
+
+ if (copy_from_user(&alm_tm, (struct rtc_time __user *)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ hrs = alm_tm.tm_hour;
+ min = alm_tm.tm_min;
+ sec = alm_tm.tm_sec;
+
+ spin_lock_irq(&rtc_lock);
+ if (hpet_set_alarm_time(hrs, min, sec)) {
+ /*
+ * Fallthru and set alarm time in CMOS too,
+ * so that we will get proper value in RTC_ALM_READ
+ */
+ }
+ if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) ||
+ RTC_ALWAYS_BCD)
+ {
+ if (sec < 60) BIN_TO_BCD(sec);
+ else sec = 0xff;
+
+ if (min < 60) BIN_TO_BCD(min);
+ else min = 0xff;
+
+ if (hrs < 24) BIN_TO_BCD(hrs);
+ else hrs = 0xff;
+ }
+ CMOS_WRITE(hrs, RTC_HOURS_ALARM);
+ CMOS_WRITE(min, RTC_MINUTES_ALARM);
+ CMOS_WRITE(sec, RTC_SECONDS_ALARM);
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+ }
+ case RTC_RD_TIME: /* Read the time/date from RTC */
+ {
+ memset(&wtime, 0, sizeof(struct rtc_time));
+ rtc_get_rtc_time(&wtime);
+ break;
+ }
+ case RTC_SET_TIME: /* Set the RTC */
+ {
+ struct rtc_time rtc_tm;
+ unsigned char mon, day, hrs, min, sec, leap_yr;
+ unsigned char save_control, save_freq_select;
+ unsigned int yrs;
+#ifdef CONFIG_MACH_DECSTATION
+ unsigned int real_yrs;
+#endif
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ yrs = rtc_tm.tm_year + 1900;
+ mon = rtc_tm.tm_mon + 1; /* tm_mon starts at zero */
+ day = rtc_tm.tm_mday;
+ hrs = rtc_tm.tm_hour;
+ min = rtc_tm.tm_min;
+ sec = rtc_tm.tm_sec;
+
+ if (yrs < 1970)
+ return -EINVAL;
+
+ leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+ if ((mon > 12) || (day == 0))
+ return -EINVAL;
+
+ if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+ return -EINVAL;
+
+ if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+ return -EINVAL;
+
+ if ((yrs -= epoch) > 255) /* They are unsigned */
+ return -EINVAL;
+
+ spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_MACH_DECSTATION
+ real_yrs = yrs;
+ yrs = 72;
+
+ /*
+ * We want to keep the year set to 73 until March
+ * for non-leap years, so that Feb, 29th is handled
+ * correctly.
+ */
+ if (!leap_yr && mon < 3) {
+ real_yrs--;
+ yrs = 73;
+ }
+#endif
+ /* These limits and adjustments are independent of
+ * whether the chip is in binary mode or not.
+ */
+ if (yrs > 169) {
+ spin_unlock_irq(&rtc_lock);
+ return -EINVAL;
+ }
+ if (yrs >= 100)
+ yrs -= 100;
+
+ if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY)
+ || RTC_ALWAYS_BCD) {
+ BIN_TO_BCD(sec);
+ BIN_TO_BCD(min);
+ BIN_TO_BCD(hrs);
+ BIN_TO_BCD(day);
+ BIN_TO_BCD(mon);
+ BIN_TO_BCD(yrs);
+ }
+
+ save_control = CMOS_READ(RTC_CONTROL);
+ CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);
+ save_freq_select = CMOS_READ(RTC_FREQ_SELECT);
+ CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);
+
+#ifdef CONFIG_MACH_DECSTATION
+ CMOS_WRITE(real_yrs, RTC_DEC_YEAR);
+#endif
+ CMOS_WRITE(yrs, RTC_YEAR);
+ CMOS_WRITE(mon, RTC_MONTH);
+ CMOS_WRITE(day, RTC_DAY_OF_MONTH);
+ CMOS_WRITE(hrs, RTC_HOURS);
+ CMOS_WRITE(min, RTC_MINUTES);
+ CMOS_WRITE(sec, RTC_SECONDS);
+
+ CMOS_WRITE(save_control, RTC_CONTROL);
+ CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
+
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+ }
+#ifdef RTC_IRQ
+ case RTC_IRQP_READ: /* Read the periodic IRQ rate. */
+ {
+ return put_user(rtc_freq, (unsigned long __user *)arg);
+ }
+ case RTC_IRQP_SET: /* Set periodic IRQ rate. */
+ {
+ int tmp = 0;
+ unsigned char val;
+
+ /*
+ * The max we can do is 8192Hz.
+ */
+ if ((arg < 2) || (arg > 8192))
+ return -EINVAL;
+ /*
+ * We don't really want Joe User generating more
+ * than 64Hz of interrupts on a multi-user machine.
+ */
+ if (!kernel && (arg > rtc_max_user_freq) && (!capable(CAP_SYS_RESOURCE)))
+ return -EACCES;
+
+ while (arg > (1<<tmp))
+ tmp++;
+
+ /*
+ * Check that the input was really a power of 2.
+ */
+ if (arg != (1<<tmp))
+ return -EINVAL;
+
+ spin_lock_irq(&rtc_lock);
+ if (hpet_set_periodic_freq(arg)) {
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+ }
+ rtc_freq = arg;
+
+ val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0;
+ val |= (16 - tmp);
+ CMOS_WRITE(val, RTC_FREQ_SELECT);
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+ }
+#endif
+ case RTC_EPOCH_READ: /* Read the epoch. */
+ {
+ return put_user (epoch, (unsigned long __user *)arg);
+ }
+ case RTC_EPOCH_SET: /* Set the epoch. */
+ {
+ /*
+ * There were no RTC clocks before 1900.
+ */
+ if (arg < 1900)
+ return -EINVAL;
+
+ if (!capable(CAP_SYS_TIME))
+ return -EACCES;
+
+ epoch = arg;
+ return 0;
+ }
+ default:
+ return -ENOTTY;
+ }
+ return copy_to_user((void __user *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return rtc_do_ioctl(cmd, arg, 0);
+}
+
+/*
+ * We enforce only one user at a time here with the open/close.
+ * Also clear the previous interrupt data on an open, and clean
+ * up things on a close.
+ */
+
+/* We use rtc_lock to protect against concurrent opens. So the BKL is not
+ * needed here. Or anywhere else in this driver. */
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ spin_lock_irq (&rtc_lock);
+
+ if(rtc_status & RTC_IS_OPEN)
+ goto out_busy;
+
+ rtc_status |= RTC_IS_OPEN;
+
+ rtc_irq_data = 0;
+ spin_unlock_irq (&rtc_lock);
+ return 0;
+
+out_busy:
+ spin_unlock_irq (&rtc_lock);
+ return -EBUSY;
+}
+
+static int rtc_fasync (int fd, struct file *filp, int on)
+
+{
+ return fasync_helper (fd, filp, on, &rtc_async_queue);
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+#ifdef RTC_IRQ
+ unsigned char tmp;
+
+ if (rtc_has_irq == 0)
+ goto no_irq;
+
+ /*
+ * Turn off all interrupts once the device is no longer
+ * in use, and clear the data.
+ */
+
+ spin_lock_irq(&rtc_lock);
+ if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) {
+ tmp = CMOS_READ(RTC_CONTROL);
+ tmp &= ~RTC_PIE;
+ tmp &= ~RTC_AIE;
+ tmp &= ~RTC_UIE;
+ CMOS_WRITE(tmp, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+ }
+ if (rtc_status & RTC_TIMER_ON) {
+ rtc_status &= ~RTC_TIMER_ON;
+ del_timer(&rtc_irq_timer);
+ }
+ spin_unlock_irq(&rtc_lock);
+
+ if (file->f_flags & FASYNC) {
+ rtc_fasync (-1, file, 0);
+ }
+no_irq:
+#endif
+
+ spin_lock_irq (&rtc_lock);
+ rtc_irq_data = 0;
+ rtc_status &= ~RTC_IS_OPEN;
+ spin_unlock_irq (&rtc_lock);
+ return 0;
+}
+
+#ifdef RTC_IRQ
+/* Called without the kernel lock - fine */
+static unsigned int rtc_poll(struct file *file, poll_table *wait)
+{
+ unsigned long l;
+
+ if (rtc_has_irq == 0)
+ return 0;
+
+ poll_wait(file, &rtc_wait, wait);
+
+ spin_lock_irq (&rtc_lock);
+ l = rtc_irq_data;
+ spin_unlock_irq (&rtc_lock);
+
+ if (l != 0)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+#endif
+
+/*
+ * exported stuffs
+ */
+
+EXPORT_SYMBOL(rtc_register);
+EXPORT_SYMBOL(rtc_unregister);
+EXPORT_SYMBOL(rtc_control);
+
+int rtc_register(rtc_task_t *task)
+{
+#ifndef RTC_IRQ
+ return -EIO;
+#else
+ if (task == NULL || task->func == NULL)
+ return -EINVAL;
+ spin_lock_irq(&rtc_lock);
+ if (rtc_status & RTC_IS_OPEN) {
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+ }
+ spin_lock(&rtc_task_lock);
+ if (rtc_callback) {
+ spin_unlock(&rtc_task_lock);
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+ }
+ rtc_status |= RTC_IS_OPEN;
+ rtc_callback = task;
+ spin_unlock(&rtc_task_lock);
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+#endif
+}
+
+int rtc_unregister(rtc_task_t *task)
+{
+#ifndef RTC_IRQ
+ return -EIO;
+#else
+ unsigned char tmp;
+
+ spin_lock_irq(&rtc_lock);
+ spin_lock(&rtc_task_lock);
+ if (rtc_callback != task) {
+ spin_unlock(&rtc_task_lock);
+ spin_unlock_irq(&rtc_lock);
+ return -ENXIO;
+ }
+ rtc_callback = NULL;
+
+ /* disable controls */
+ if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) {
+ tmp = CMOS_READ(RTC_CONTROL);
+ tmp &= ~RTC_PIE;
+ tmp &= ~RTC_AIE;
+ tmp &= ~RTC_UIE;
+ CMOS_WRITE(tmp, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+ }
+ if (rtc_status & RTC_TIMER_ON) {
+ rtc_status &= ~RTC_TIMER_ON;
+ del_timer(&rtc_irq_timer);
+ }
+ rtc_status &= ~RTC_IS_OPEN;
+ spin_unlock(&rtc_task_lock);
+ spin_unlock_irq(&rtc_lock);
+ return 0;
+#endif
+}
+
+int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg)
+{
+#ifndef RTC_IRQ
+ return -EIO;
+#else
+ spin_lock_irq(&rtc_task_lock);
+ if (rtc_callback != task) {
+ spin_unlock_irq(&rtc_task_lock);
+ return -ENXIO;
+ }
+ spin_unlock_irq(&rtc_task_lock);
+ return rtc_do_ioctl(cmd, arg, 1);
+#endif
+}
+
+
+/*
+ * The various file operations we support.
+ */
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = rtc_read,
+#ifdef RTC_IRQ
+ .poll = rtc_poll,
+#endif
+ .ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+ .fasync = rtc_fasync,
+};
+
+static struct miscdevice rtc_dev = {
+ .minor = RTC_MINOR,
+ .name = "rtc",
+ .fops = &rtc_fops,
+};
+
+static struct file_operations rtc_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = rtc_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#if defined(RTC_IRQ) && !defined(__sparc__)
+static irqreturn_t (*rtc_int_handler_ptr)(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+static int __init rtc_init(void)
+{
+ struct proc_dir_entry *ent;
+#if defined(__alpha__) || defined(__mips__)
+ unsigned int year, ctrl;
+ unsigned long uip_watchdog;
+ char *guess = NULL;
+#endif
+#ifdef __sparc__
+ struct linux_ebus *ebus;
+ struct linux_ebus_device *edev;
+#ifdef __sparc_v9__
+ struct sparc_isa_bridge *isa_br;
+ struct sparc_isa_device *isa_dev;
+#endif
+#endif
+
+#ifdef __sparc__
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if(strcmp(edev->prom_name, "rtc") == 0) {
+ rtc_port = edev->resource[0].start;
+ rtc_irq = edev->irqs[0];
+ goto found;
+ }
+ }
+ }
+#ifdef __sparc_v9__
+ for_each_isa(isa_br) {
+ for_each_isadev(isa_dev, isa_br) {
+ if (strcmp(isa_dev->prom_name, "rtc") == 0) {
+ rtc_port = isa_dev->resource.start;
+ rtc_irq = isa_dev->irq;
+ goto found;
+ }
+ }
+ }
+#endif
+ printk(KERN_ERR "rtc_init: no PC rtc found\n");
+ return -EIO;
+
+found:
+ if (rtc_irq == PCI_IRQ_NONE) {
+ rtc_has_irq = 0;
+ goto no_irq;
+ }
+
+ /*
+ * XXX Interrupt pin #7 in Espresso is shared between RTC and
+ * PCI Slot 2 INTA# (and some INTx# in Slot 1). SA_INTERRUPT here
+ * is asking for trouble with add-on boards. Change to SA_SHIRQ.
+ */
+ if (request_irq(rtc_irq, rtc_interrupt, SA_INTERRUPT, "rtc", (void *)&rtc_port)) {
+ /*
+ * Standard way for sparc to print irq's is to use
+ * __irq_itoa(). I think for EBus it's ok to use %d.
+ */
+ printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
+ return -EIO;
+ }
+no_irq:
+#else
+ if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, "rtc")) {
+ printk(KERN_ERR "rtc: I/O port %d is not free.\n", RTC_PORT (0));
+ return -EIO;
+ }
+
+#ifdef RTC_IRQ
+ if (is_hpet_enabled()) {
+ rtc_int_handler_ptr = hpet_rtc_interrupt;
+ } else {
+ rtc_int_handler_ptr = rtc_interrupt;
+ }
+
+ if(request_irq(RTC_IRQ, rtc_int_handler_ptr, SA_INTERRUPT, "rtc", NULL)) {
+ /* Yeah right, seeing as irq 8 doesn't even hit the bus. */
+ printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ);
+ release_region(RTC_PORT(0), RTC_IO_EXTENT);
+ return -EIO;
+ }
+ hpet_rtc_timer_init();
+
+#endif
+
+#endif /* __sparc__ vs. others */
+
+ if (misc_register(&rtc_dev)) {
+#ifdef RTC_IRQ
+ free_irq(RTC_IRQ, NULL);
+#endif
+ release_region(RTC_PORT(0), RTC_IO_EXTENT);
+ return -ENODEV;
+ }
+
+ ent = create_proc_entry("driver/rtc", 0, NULL);
+ if (!ent) {
+#ifdef RTC_IRQ
+ free_irq(RTC_IRQ, NULL);
+#endif
+ release_region(RTC_PORT(0), RTC_IO_EXTENT);
+ misc_deregister(&rtc_dev);
+ return -ENOMEM;
+ }
+ ent->proc_fops = &rtc_proc_fops;
+
+#if defined(__alpha__) || defined(__mips__)
+ rtc_freq = HZ;
+
+ /* Each operating system on an Alpha uses its own epoch.
+ Let's try to guess which one we are using now. */
+
+ uip_watchdog = jiffies;
+ if (rtc_is_updating() != 0)
+ while (jiffies - uip_watchdog < 2*HZ/100) {
+ barrier();
+ cpu_relax();
+ }
+
+ spin_lock_irq(&rtc_lock);
+ year = CMOS_READ(RTC_YEAR);
+ ctrl = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+ BCD_TO_BIN(year); /* This should never happen... */
+
+ if (year < 20) {
+ epoch = 2000;
+ guess = "SRM (post-2000)";
+ } else if (year >= 20 && year < 48) {
+ epoch = 1980;
+ guess = "ARC console";
+ } else if (year >= 48 && year < 72) {
+ epoch = 1952;
+ guess = "Digital UNIX";
+#if defined(__mips__)
+ } else if (year >= 72 && year < 74) {
+ epoch = 2000;
+ guess = "Digital DECstation";
+#else
+ } else if (year >= 70) {
+ epoch = 1900;
+ guess = "Standard PC (1900)";
+#endif
+ }
+ if (guess)
+ printk(KERN_INFO "rtc: %s epoch (%lu) detected\n", guess, epoch);
+#endif
+#ifdef RTC_IRQ
+ if (rtc_has_irq == 0)
+ goto no_irq2;
+
+ init_timer(&rtc_irq_timer);
+ rtc_irq_timer.function = rtc_dropped_irq;
+ spin_lock_irq(&rtc_lock);
+ rtc_freq = 1024;
+ if (!hpet_set_periodic_freq(rtc_freq)) {
+ /* Initialize periodic freq. to CMOS reset default, which is 1024Hz */
+ CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), RTC_FREQ_SELECT);
+ }
+ spin_unlock_irq(&rtc_lock);
+no_irq2:
+#endif
+
+ (void) init_sysctl();
+
+ printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
+
+ return 0;
+}
+
+static void __exit rtc_exit (void)
+{
+ cleanup_sysctl();
+ remove_proc_entry ("driver/rtc", NULL);
+ misc_deregister(&rtc_dev);
+
+#ifdef __sparc__
+ if (rtc_has_irq)
+ free_irq (rtc_irq, &rtc_port);
+#else
+ release_region (RTC_PORT (0), RTC_IO_EXTENT);
+#ifdef RTC_IRQ
+ if (rtc_has_irq)
+ free_irq (RTC_IRQ, NULL);
+#endif
+#endif /* __sparc__ */
+}
+
+module_init(rtc_init);
+module_exit(rtc_exit);
+
+#ifdef RTC_IRQ
+/*
+ * At IRQ rates >= 4096Hz, an interrupt may get lost altogether.
+ * (usually during an IDE disk interrupt, with IRQ unmasking off)
+ * Since the interrupt handler doesn't get called, the IRQ status
+ * byte doesn't get read, and the RTC stops generating interrupts.
+ * A timer is set, and will call this function if/when that happens.
+ * To get it out of this stalled state, we just read the status.
+ * At least a jiffy of interrupts (rtc_freq/HZ) will have been lost.
+ * (You *really* shouldn't be trying to use a non-realtime system
+ * for something that requires a steady > 1KHz signal anyways.)
+ */
+
+static void rtc_dropped_irq(unsigned long data)
+{
+ unsigned long freq;
+
+ spin_lock_irq (&rtc_lock);
+
+ if (hpet_rtc_dropped_irq()) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+
+ /* Just in case someone disabled the timer from behind our back... */
+ if (rtc_status & RTC_TIMER_ON)
+ mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
+
+ rtc_irq_data += ((rtc_freq/HZ)<<8);
+ rtc_irq_data &= ~0xff;
+ rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); /* restart */
+
+ freq = rtc_freq;
+
+ spin_unlock_irq(&rtc_lock);
+
+ printk(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n", freq);
+
+ /* Now we have new data */
+ wake_up_interruptible(&rtc_wait);
+
+ kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
+}
+#endif
+
+/*
+ * Info exported via "/proc/driver/rtc".
+ */
+
+static int rtc_proc_show(struct seq_file *seq, void *v)
+{
+#define YN(bit) ((ctrl & bit) ? "yes" : "no")
+#define NY(bit) ((ctrl & bit) ? "no" : "yes")
+ struct rtc_time tm;
+ unsigned char batt, ctrl;
+ unsigned long freq;
+
+ spin_lock_irq(&rtc_lock);
+ batt = CMOS_READ(RTC_VALID) & RTC_VRT;
+ ctrl = CMOS_READ(RTC_CONTROL);
+ freq = rtc_freq;
+ spin_unlock_irq(&rtc_lock);
+
+
+ rtc_get_rtc_time(&tm);
+
+ /*
+ * There is no way to tell if the luser has the RTC set for local
+ * time or for Universal Standard Time (GMT). Probably local though.
+ */
+ seq_printf(seq,
+ "rtc_time\t: %02d:%02d:%02d\n"
+ "rtc_date\t: %04d-%02d-%02d\n"
+ "rtc_epoch\t: %04lu\n",
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch);
+
+ get_rtc_alm_time(&tm);
+
+ /*
+ * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will
+ * match any value for that particular field. Values that are
+ * greater than a valid time, but less than 0xc0 shouldn't appear.
+ */
+ seq_puts(seq, "alarm\t\t: ");
+ if (tm.tm_hour <= 24)
+ seq_printf(seq, "%02d:", tm.tm_hour);
+ else
+ seq_puts(seq, "**:");
+
+ if (tm.tm_min <= 59)
+ seq_printf(seq, "%02d:", tm.tm_min);
+ else
+ seq_puts(seq, "**:");
+
+ if (tm.tm_sec <= 59)
+ seq_printf(seq, "%02d\n", tm.tm_sec);
+ else
+ seq_puts(seq, "**\n");
+
+ seq_printf(seq,
+ "DST_enable\t: %s\n"
+ "BCD\t\t: %s\n"
+ "24hr\t\t: %s\n"
+ "square_wave\t: %s\n"
+ "alarm_IRQ\t: %s\n"
+ "update_IRQ\t: %s\n"
+ "periodic_IRQ\t: %s\n"
+ "periodic_freq\t: %ld\n"
+ "batt_status\t: %s\n",
+ YN(RTC_DST_EN),
+ NY(RTC_DM_BINARY),
+ YN(RTC_24H),
+ YN(RTC_SQWE),
+ YN(RTC_AIE),
+ YN(RTC_UIE),
+ YN(RTC_PIE),
+ freq,
+ batt ? "okay" : "dead");
+
+ return 0;
+#undef YN
+#undef NY
+}
+
+static int rtc_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rtc_proc_show, NULL);
+}
+
+void rtc_get_rtc_time(struct rtc_time *rtc_tm)
+{
+ unsigned long uip_watchdog = jiffies;
+ unsigned char ctrl;
+#ifdef CONFIG_MACH_DECSTATION
+ unsigned int real_year;
+#endif
+
+ /*
+ * read RTC once any update in progress is done. The update
+ * can take just over 2ms. We wait 10 to 20ms. There is no need to
+ * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP.
+ * If you need to know *exactly* when a second has started, enable
+ * periodic update complete interrupts, (via ioctl) and then
+ * immediately read /dev/rtc which will block until you get the IRQ.
+ * Once the read clears, read the RTC time (again via ioctl). Easy.
+ */
+
+ if (rtc_is_updating() != 0)
+ while (jiffies - uip_watchdog < 2*HZ/100) {
+ barrier();
+ cpu_relax();
+ }
+
+ /*
+ * Only the values that we read from the RTC are set. We leave
+ * tm_wday, tm_yday and tm_isdst untouched. Even though the
+ * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+ * by the RTC when initially set to a non-zero value.
+ */
+ spin_lock_irq(&rtc_lock);
+ rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
+ rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
+ rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
+ rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
+ rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
+ rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
+#ifdef CONFIG_MACH_DECSTATION
+ real_year = CMOS_READ(RTC_DEC_YEAR);
+#endif
+ ctrl = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+ {
+ BCD_TO_BIN(rtc_tm->tm_sec);
+ BCD_TO_BIN(rtc_tm->tm_min);
+ BCD_TO_BIN(rtc_tm->tm_hour);
+ BCD_TO_BIN(rtc_tm->tm_mday);
+ BCD_TO_BIN(rtc_tm->tm_mon);
+ BCD_TO_BIN(rtc_tm->tm_year);
+ }
+
+#ifdef CONFIG_MACH_DECSTATION
+ rtc_tm->tm_year += real_year - 72;
+#endif
+
+ /*
+ * Account for differences between how the RTC uses the values
+ * and how they are defined in a struct rtc_time;
+ */
+ if ((rtc_tm->tm_year += (epoch - 1900)) <= 69)
+ rtc_tm->tm_year += 100;
+
+ rtc_tm->tm_mon--;
+}
+
+static void get_rtc_alm_time(struct rtc_time *alm_tm)
+{
+ unsigned char ctrl;
+
+ /*
+ * Only the values that we read from the RTC are set. That
+ * means only tm_hour, tm_min, and tm_sec.
+ */
+ spin_lock_irq(&rtc_lock);
+ alm_tm->tm_sec = CMOS_READ(RTC_SECONDS_ALARM);
+ alm_tm->tm_min = CMOS_READ(RTC_MINUTES_ALARM);
+ alm_tm->tm_hour = CMOS_READ(RTC_HOURS_ALARM);
+ ctrl = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+ {
+ BCD_TO_BIN(alm_tm->tm_sec);
+ BCD_TO_BIN(alm_tm->tm_min);
+ BCD_TO_BIN(alm_tm->tm_hour);
+ }
+}
+
+#ifdef RTC_IRQ
+/*
+ * Used to disable/enable interrupts for any one of UIE, AIE, PIE.
+ * Rumour has it that if you frob the interrupt enable/disable
+ * bits in RTC_CONTROL, you should read RTC_INTR_FLAGS, to
+ * ensure you actually start getting interrupts. Probably for
+ * compatibility with older/broken chipset RTC implementations.
+ * We also clear out any old irq data after an ioctl() that
+ * meddles with the interrupt enable/disable bits.
+ */
+
+static void mask_rtc_irq_bit(unsigned char bit)
+{
+ unsigned char val;
+
+ spin_lock_irq(&rtc_lock);
+ if (hpet_mask_rtc_irq_bit(bit)) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+ val = CMOS_READ(RTC_CONTROL);
+ val &= ~bit;
+ CMOS_WRITE(val, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ rtc_irq_data = 0;
+ spin_unlock_irq(&rtc_lock);
+}
+
+static void set_rtc_irq_bit(unsigned char bit)
+{
+ unsigned char val;
+
+ spin_lock_irq(&rtc_lock);
+ if (hpet_set_rtc_irq_bit(bit)) {
+ spin_unlock_irq(&rtc_lock);
+ return;
+ }
+ val = CMOS_READ(RTC_CONTROL);
+ val |= bit;
+ CMOS_WRITE(val, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ rtc_irq_data = 0;
+ spin_unlock_irq(&rtc_lock);
+}
+#endif
+
+MODULE_AUTHOR("Paul Gortmaker");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/s3c2410-rtc.c b/drivers/char/s3c2410-rtc.c
new file mode 100644
index 000000000000..ec666395a26c
--- /dev/null
+++ b/drivers/char/s3c2410-rtc.c
@@ -0,0 +1,588 @@
+/* drivers/char/s3c2410_rtc.c
+ *
+ * Copyright (c) 2004 Simtec Electronics <linux@simtec.co.uk>
+ * http://www.simtec.co.uk/products/SWLINUX/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * S3C2410 Internal RTC Driver
+ *
+ * Changelog:
+ * 08-Nov-2004 BJD Initial creation
+ * 12-Nov-2004 BJD Added periodic IRQ and PM code
+ * 22-Nov-2004 BJD Sign-test on alarm code to check for <0
+ * 10-Mar-2005 LCVR Changed S3C2410_VA_RTC to S3C24XX_VA_RTC
+*/
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/rtc.h>
+
+#include <asm/mach/time.h>
+
+#include <asm/hardware/clock.h>
+#include <asm/arch/regs-rtc.h>
+
+/* need this for the RTC_AF definitions */
+#include <linux/mc146818rtc.h>
+
+#undef S3C24XX_VA_RTC
+#define S3C24XX_VA_RTC s3c2410_rtc_base
+
+static struct resource *s3c2410_rtc_mem;
+
+static void __iomem *s3c2410_rtc_base;
+static int s3c2410_rtc_alarmno = NO_IRQ;
+static int s3c2410_rtc_tickno = NO_IRQ;
+static int s3c2410_rtc_freq = 1;
+
+static DEFINE_SPINLOCK(s3c2410_rtc_pie_lock);
+
+/* IRQ Handlers */
+
+static irqreturn_t s3c2410_rtc_alarmirq(int irq, void *id, struct pt_regs *r)
+{
+ rtc_update(1, RTC_AF | RTC_IRQF);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t s3c2410_rtc_tickirq(int irq, void *id, struct pt_regs *r)
+{
+ rtc_update(1, RTC_PF | RTC_IRQF);
+ return IRQ_HANDLED;
+}
+
+/* Update control registers */
+static void s3c2410_rtc_setaie(int to)
+{
+ unsigned int tmp;
+
+ pr_debug("%s: aie=%d\n", __FUNCTION__, to);
+
+ tmp = readb(S3C2410_RTCALM);
+
+ if (to)
+ tmp |= S3C2410_RTCALM_ALMEN;
+ else
+ tmp &= ~S3C2410_RTCALM_ALMEN;
+
+
+ writeb(tmp, S3C2410_RTCALM);
+}
+
+static void s3c2410_rtc_setpie(int to)
+{
+ unsigned int tmp;
+
+ pr_debug("%s: pie=%d\n", __FUNCTION__, to);
+
+ spin_lock_irq(&s3c2410_rtc_pie_lock);
+ tmp = readb(S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;
+
+ if (to)
+ tmp |= S3C2410_TICNT_ENABLE;
+
+ writeb(tmp, S3C2410_TICNT);
+ spin_unlock_irq(&s3c2410_rtc_pie_lock);
+}
+
+static void s3c2410_rtc_setfreq(int freq)
+{
+ unsigned int tmp;
+
+ spin_lock_irq(&s3c2410_rtc_pie_lock);
+ tmp = readb(S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
+
+ s3c2410_rtc_freq = freq;
+
+ tmp |= (128 / freq)-1;
+
+ writeb(tmp, S3C2410_TICNT);
+ spin_unlock_irq(&s3c2410_rtc_pie_lock);
+}
+
+/* Time read/write */
+
+static void s3c2410_rtc_gettime(struct rtc_time *rtc_tm)
+{
+ unsigned int have_retried = 0;
+
+ retry_get_time:
+ rtc_tm->tm_min = readb(S3C2410_RTCMIN);
+ rtc_tm->tm_hour = readb(S3C2410_RTCHOUR);
+ rtc_tm->tm_mday = readb(S3C2410_RTCDATE);
+ rtc_tm->tm_mon = readb(S3C2410_RTCMON);
+ rtc_tm->tm_year = readb(S3C2410_RTCYEAR);
+ rtc_tm->tm_sec = readb(S3C2410_RTCSEC);
+
+ /* the only way to work out wether the system was mid-update
+ * when we read it is to check the second counter, and if it
+ * is zero, then we re-try the entire read
+ */
+
+ if (rtc_tm->tm_sec == 0 && !have_retried) {
+ have_retried = 1;
+ goto retry_get_time;
+ }
+
+ pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
+ rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
+ rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
+
+ BCD_TO_BIN(rtc_tm->tm_sec);
+ BCD_TO_BIN(rtc_tm->tm_min);
+ BCD_TO_BIN(rtc_tm->tm_hour);
+ BCD_TO_BIN(rtc_tm->tm_mday);
+ BCD_TO_BIN(rtc_tm->tm_mon);
+ BCD_TO_BIN(rtc_tm->tm_year);
+
+ rtc_tm->tm_year += 100;
+ rtc_tm->tm_mon -= 1;
+}
+
+
+static int s3c2410_rtc_settime(struct rtc_time *tm)
+{
+ /* the rtc gets round the y2k problem by just not supporting it */
+
+ if (tm->tm_year < 100)
+ return -EINVAL;
+
+ writeb(BIN2BCD(tm->tm_sec), S3C2410_RTCSEC);
+ writeb(BIN2BCD(tm->tm_min), S3C2410_RTCMIN);
+ writeb(BIN2BCD(tm->tm_hour), S3C2410_RTCHOUR);
+ writeb(BIN2BCD(tm->tm_mday), S3C2410_RTCDATE);
+ writeb(BIN2BCD(tm->tm_mon + 1), S3C2410_RTCMON);
+ writeb(BIN2BCD(tm->tm_year - 100), S3C2410_RTCYEAR);
+
+ return 0;
+}
+
+static void s3c2410_rtc_getalarm(struct rtc_wkalrm *alrm)
+{
+ struct rtc_time *alm_tm = &alrm->time;
+ unsigned int alm_en;
+
+ alm_tm->tm_sec = readb(S3C2410_ALMSEC);
+ alm_tm->tm_min = readb(S3C2410_ALMMIN);
+ alm_tm->tm_hour = readb(S3C2410_ALMHOUR);
+ alm_tm->tm_mon = readb(S3C2410_ALMMON);
+ alm_tm->tm_mday = readb(S3C2410_ALMDATE);
+ alm_tm->tm_year = readb(S3C2410_ALMYEAR);
+
+ alm_en = readb(S3C2410_RTCALM);
+
+ pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
+ alm_en,
+ alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
+ alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);
+
+
+ /* decode the alarm enable field */
+
+ if (alm_en & S3C2410_RTCALM_SECEN) {
+ BCD_TO_BIN(alm_tm->tm_sec);
+ } else {
+ alm_tm->tm_sec = 0xff;
+ }
+
+ if (alm_en & S3C2410_RTCALM_MINEN) {
+ BCD_TO_BIN(alm_tm->tm_min);
+ } else {
+ alm_tm->tm_min = 0xff;
+ }
+
+ if (alm_en & S3C2410_RTCALM_HOUREN) {
+ BCD_TO_BIN(alm_tm->tm_hour);
+ } else {
+ alm_tm->tm_hour = 0xff;
+ }
+
+ if (alm_en & S3C2410_RTCALM_DAYEN) {
+ BCD_TO_BIN(alm_tm->tm_mday);
+ } else {
+ alm_tm->tm_mday = 0xff;
+ }
+
+ if (alm_en & S3C2410_RTCALM_MONEN) {
+ BCD_TO_BIN(alm_tm->tm_mon);
+ alm_tm->tm_mon -= 1;
+ } else {
+ alm_tm->tm_mon = 0xff;
+ }
+
+ if (alm_en & S3C2410_RTCALM_YEAREN) {
+ BCD_TO_BIN(alm_tm->tm_year);
+ } else {
+ alm_tm->tm_year = 0xffff;
+ }
+
+ /* todo - set alrm->enabled ? */
+}
+
+static int s3c2410_rtc_setalarm(struct rtc_wkalrm *alrm)
+{
+ struct rtc_time *tm = &alrm->time;
+ unsigned int alrm_en;
+
+ pr_debug("s3c2410_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
+ alrm->enabled,
+ tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
+ tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);
+
+ if (alrm->enabled || 1) {
+ alrm_en = readb(S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
+ writeb(0x00, S3C2410_RTCALM);
+
+ if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
+ alrm_en |= S3C2410_RTCALM_SECEN;
+ writeb(BIN2BCD(tm->tm_sec), S3C2410_ALMSEC);
+ }
+
+ if (tm->tm_min < 60 && tm->tm_min >= 0) {
+ alrm_en |= S3C2410_RTCALM_MINEN;
+ writeb(BIN2BCD(tm->tm_min), S3C2410_ALMMIN);
+ }
+
+ if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
+ alrm_en |= S3C2410_RTCALM_HOUREN;
+ writeb(BIN2BCD(tm->tm_hour), S3C2410_ALMHOUR);
+ }
+
+ pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
+
+ writeb(alrm_en, S3C2410_RTCALM);
+ enable_irq_wake(s3c2410_rtc_alarmno);
+ } else {
+ alrm_en = readb(S3C2410_RTCALM);
+ alrm_en &= ~S3C2410_RTCALM_ALMEN;
+ writeb(alrm_en, S3C2410_RTCALM);
+ disable_irq_wake(s3c2410_rtc_alarmno);
+ }
+
+ return 0;
+}
+
+static int s3c2410_rtc_ioctl(unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ s3c2410_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
+ return 0;
+
+ case RTC_PIE_OFF:
+ case RTC_PIE_ON:
+ s3c2410_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
+ return 0;
+
+ case RTC_IRQP_READ:
+ return put_user(s3c2410_rtc_freq, (unsigned long __user *)arg);
+
+ case RTC_IRQP_SET:
+ if (arg < 1 || arg > 64)
+ return -EINVAL;
+
+ if (!capable(CAP_SYS_RESOURCE))
+ return -EACCES;
+
+ /* check for power of 2 */
+
+ if ((arg & (arg-1)) != 0)
+ return -EINVAL;
+
+ pr_debug("s3c2410_rtc: setting frequency %ld\n", arg);
+
+ s3c2410_rtc_setfreq(arg);
+ return 0;
+
+ case RTC_UIE_ON:
+ case RTC_UIE_OFF:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int s3c2410_rtc_proc(char *buf)
+{
+ unsigned int rtcalm = readb(S3C2410_RTCALM);
+ unsigned int ticnt = readb (S3C2410_TICNT);
+ char *p = buf;
+
+ p += sprintf(p, "alarm_IRQ\t: %s\n",
+ (rtcalm & S3C2410_RTCALM_ALMEN) ? "yes" : "no" );
+ p += sprintf(p, "periodic_IRQ\t: %s\n",
+ (ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
+ p += sprintf(p, "periodic_freq\t: %d\n", s3c2410_rtc_freq);
+
+ return p - buf;
+}
+
+static int s3c2410_rtc_open(void)
+{
+ int ret;
+
+ ret = request_irq(s3c2410_rtc_alarmno, s3c2410_rtc_alarmirq,
+ SA_INTERRUPT, "s3c2410-rtc alarm", NULL);
+
+ if (ret)
+ printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_alarmno);
+
+ ret = request_irq(s3c2410_rtc_tickno, s3c2410_rtc_tickirq,
+ SA_INTERRUPT, "s3c2410-rtc tick", NULL);
+
+ if (ret) {
+ printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_tickno);
+ goto tick_err;
+ }
+
+ return ret;
+
+ tick_err:
+ free_irq(s3c2410_rtc_alarmno, NULL);
+ return ret;
+}
+
+static void s3c2410_rtc_release(void)
+{
+ /* do not clear AIE here, it may be needed for wake */
+
+ s3c2410_rtc_setpie(0);
+ free_irq(s3c2410_rtc_alarmno, NULL);
+ free_irq(s3c2410_rtc_tickno, NULL);
+}
+
+static struct rtc_ops s3c2410_rtcops = {
+ .owner = THIS_MODULE,
+ .open = s3c2410_rtc_open,
+ .release = s3c2410_rtc_release,
+ .ioctl = s3c2410_rtc_ioctl,
+ .read_time = s3c2410_rtc_gettime,
+ .set_time = s3c2410_rtc_settime,
+ .read_alarm = s3c2410_rtc_getalarm,
+ .set_alarm = s3c2410_rtc_setalarm,
+ .proc = s3c2410_rtc_proc,
+};
+
+static void s3c2410_rtc_enable(struct device *dev, int en)
+{
+ unsigned int tmp;
+
+ if (s3c2410_rtc_base == NULL)
+ return;
+
+ if (!en) {
+ tmp = readb(S3C2410_RTCCON);
+ writeb(tmp & ~S3C2410_RTCCON_RTCEN, S3C2410_RTCCON);
+
+ tmp = readb(S3C2410_TICNT);
+ writeb(tmp & ~S3C2410_TICNT_ENABLE, S3C2410_TICNT);
+ } else {
+ /* re-enable the device, and check it is ok */
+
+ if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
+ dev_info(dev, "rtc disabled, re-enabling\n");
+
+ tmp = readb(S3C2410_RTCCON);
+ writeb(tmp | S3C2410_RTCCON_RTCEN , S3C2410_RTCCON);
+ }
+
+ if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
+ dev_info(dev, "removing S3C2410_RTCCON_CNTSEL\n");
+
+ tmp = readb(S3C2410_RTCCON);
+ writeb(tmp& ~S3C2410_RTCCON_CNTSEL , S3C2410_RTCCON);
+ }
+
+ if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
+ dev_info(dev, "removing S3C2410_RTCCON_CLKRST\n");
+
+ tmp = readb(S3C2410_RTCCON);
+ writeb(tmp & ~S3C2410_RTCCON_CLKRST, S3C2410_RTCCON);
+ }
+ }
+}
+
+static int s3c2410_rtc_remove(struct device *dev)
+{
+ unregister_rtc(&s3c2410_rtcops);
+
+ s3c2410_rtc_setpie(0);
+ s3c2410_rtc_setaie(0);
+
+ if (s3c2410_rtc_mem != NULL) {
+ pr_debug("s3c2410_rtc: releasing s3c2410_rtc_mem\n");
+ iounmap(s3c2410_rtc_base);
+ release_resource(s3c2410_rtc_mem);
+ kfree(s3c2410_rtc_mem);
+ }
+
+ return 0;
+}
+
+static int s3c2410_rtc_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct resource *res;
+ int ret;
+
+ pr_debug("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
+
+ /* find the IRQs */
+
+ s3c2410_rtc_tickno = platform_get_irq(pdev, 1);
+ if (s3c2410_rtc_tickno <= 0) {
+ dev_err(dev, "no irq for rtc tick\n");
+ return -ENOENT;
+ }
+
+ s3c2410_rtc_alarmno = platform_get_irq(pdev, 0);
+ if (s3c2410_rtc_alarmno <= 0) {
+ dev_err(dev, "no irq for alarm\n");
+ return -ENOENT;
+ }
+
+ pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
+ s3c2410_rtc_tickno, s3c2410_rtc_alarmno);
+
+ /* get the memory region */
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ dev_err(dev, "failed to get memory region resource\n");
+ return -ENOENT;
+ }
+
+ s3c2410_rtc_mem = request_mem_region(res->start, res->end-res->start+1,
+ pdev->name);
+
+ if (s3c2410_rtc_mem == NULL) {
+ dev_err(dev, "failed to reserve memory region\n");
+ ret = -ENOENT;
+ goto exit_err;
+ }
+
+ s3c2410_rtc_base = ioremap(res->start, res->end - res->start + 1);
+ if (s3c2410_rtc_base == NULL) {
+ dev_err(dev, "failed ioremap()\n");
+ ret = -EINVAL;
+ goto exit_err;
+ }
+
+ s3c2410_rtc_mem = res;
+ pr_debug("s3c2410_rtc_base=%p\n", s3c2410_rtc_base);
+
+ pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
+
+ /* check to see if everything is setup correctly */
+
+ s3c2410_rtc_enable(dev, 1);
+
+ pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
+
+ s3c2410_rtc_setfreq(s3c2410_rtc_freq);
+
+ /* register RTC and exit */
+
+ register_rtc(&s3c2410_rtcops);
+ return 0;
+
+ exit_err:
+ dev_err(dev, "error %d during initialisation\n", ret);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM
+
+/* S3C2410 RTC Power management control */
+
+static struct timespec s3c2410_rtc_delta;
+
+static int ticnt_save;
+
+static int s3c2410_rtc_suspend(struct device *dev, u32 state, u32 level)
+{
+ struct rtc_time tm;
+ struct timespec time;
+
+ time.tv_nsec = 0;
+
+ if (level == SUSPEND_POWER_DOWN) {
+ /* save TICNT for anyone using periodic interrupts */
+
+ ticnt_save = readb(S3C2410_TICNT);
+
+ /* calculate time delta for suspend */
+
+ s3c2410_rtc_gettime(&tm);
+ rtc_tm_to_time(&tm, &time.tv_sec);
+ save_time_delta(&s3c2410_rtc_delta, &time);
+ s3c2410_rtc_enable(dev, 0);
+ }
+
+ return 0;
+}
+
+static int s3c2410_rtc_resume(struct device *dev, u32 level)
+{
+ struct rtc_time tm;
+ struct timespec time;
+
+ time.tv_nsec = 0;
+
+ s3c2410_rtc_enable(dev, 1);
+ s3c2410_rtc_gettime(&tm);
+ rtc_tm_to_time(&tm, &time.tv_sec);
+ restore_time_delta(&s3c2410_rtc_delta, &time);
+
+ writeb(ticnt_save, S3C2410_TICNT);
+ return 0;
+}
+#else
+#define s3c2410_rtc_suspend NULL
+#define s3c2410_rtc_resume NULL
+#endif
+
+static struct device_driver s3c2410_rtcdrv = {
+ .name = "s3c2410-rtc",
+ .bus = &platform_bus_type,
+ .probe = s3c2410_rtc_probe,
+ .remove = s3c2410_rtc_remove,
+ .suspend = s3c2410_rtc_suspend,
+ .resume = s3c2410_rtc_resume,
+};
+
+static char __initdata banner[] = "S3C2410 RTC, (c) 2004 Simtec Electronics\n";
+
+static int __init s3c2410_rtc_init(void)
+{
+ printk(banner);
+ return driver_register(&s3c2410_rtcdrv);
+}
+
+static void __exit s3c2410_rtc_exit(void)
+{
+ driver_unregister(&s3c2410_rtcdrv);
+}
+
+module_init(s3c2410_rtc_init);
+module_exit(s3c2410_rtc_exit);
+
+MODULE_DESCRIPTION("S3C24XX RTC Driver");
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/scan_keyb.c b/drivers/char/scan_keyb.c
new file mode 100644
index 000000000000..2b5bb4f5754d
--- /dev/null
+++ b/drivers/char/scan_keyb.c
@@ -0,0 +1,149 @@
+/*
+ * $Id: scan_keyb.c,v 1.2 2000/07/04 06:24:42 yaegashi Exp $
+ * Copyright (C) 2000 YAEGASHI Takeshi
+ * Generic scan keyboard driver
+ */
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/timer.h>
+
+#define SCANHZ (HZ/20)
+
+struct scan_keyboard {
+ struct scan_keyboard *next;
+ int (*scan)(unsigned char *buffer);
+ const unsigned char *table;
+ unsigned char *s0, *s1;
+ int length;
+};
+
+static int scan_jiffies=0;
+static struct scan_keyboard *keyboards=NULL;
+struct timer_list scan_timer;
+
+static void check_kbd(const unsigned char *table,
+ unsigned char *new, unsigned char *old, int length)
+{
+ int need_tasklet_schedule=0;
+ unsigned int xor, bit;
+
+ while(length-->0) {
+ if((xor=*new^*old)==0) {
+ table+=8;
+ }
+ else {
+ for(bit=0x01; bit<0x100; bit<<=1) {
+ if(xor&bit) {
+ handle_scancode(*table, !(*new&bit));
+ need_tasklet_schedule=1;
+#if 0
+ printk("0x%x %s\n", *table, (*new&bit)?"released":"pressed");
+#endif
+ }
+ table++;
+ }
+ }
+ new++; old++;
+ }
+
+ if(need_tasklet_schedule)
+ tasklet_schedule(&keyboard_tasklet);
+}
+
+
+static void scan_kbd(unsigned long dummy)
+{
+ struct scan_keyboard *kbd;
+
+ scan_jiffies++;
+
+ for(kbd=keyboards; kbd!=NULL; kbd=kbd->next) {
+ if(scan_jiffies&1) {
+ if(!kbd->scan(kbd->s0))
+ check_kbd(kbd->table,
+ kbd->s0, kbd->s1, kbd->length);
+ else
+ memcpy(kbd->s0, kbd->s1, kbd->length);
+ }
+ else {
+ if(!kbd->scan(kbd->s1))
+ check_kbd(kbd->table,
+ kbd->s1, kbd->s0, kbd->length);
+ else
+ memcpy(kbd->s1, kbd->s0, kbd->length);
+ }
+
+ }
+
+ init_timer(&scan_timer);
+ scan_timer.expires = jiffies + SCANHZ;
+ scan_timer.data = 0;
+ scan_timer.function = scan_kbd;
+ add_timer(&scan_timer);
+}
+
+
+int register_scan_keyboard(int (*scan)(unsigned char *buffer),
+ const unsigned char *table,
+ int length)
+{
+ struct scan_keyboard *kbd;
+
+ kbd = kmalloc(sizeof(struct scan_keyboard), GFP_KERNEL);
+ if (kbd == NULL)
+ goto error_out;
+
+ kbd->scan=scan;
+ kbd->table=table;
+ kbd->length=length;
+
+ kbd->s0 = kmalloc(length, GFP_KERNEL);
+ if (kbd->s0 == NULL)
+ goto error_free_kbd;
+
+ kbd->s1 = kmalloc(length, GFP_KERNEL);
+ if (kbd->s1 == NULL)
+ goto error_free_s0;
+
+ memset(kbd->s0, -1, kbd->length);
+ memset(kbd->s1, -1, kbd->length);
+
+ kbd->next=keyboards;
+ keyboards=kbd;
+
+ return 0;
+
+ error_free_s0:
+ kfree(kbd->s0);
+
+ error_free_kbd:
+ kfree(kbd);
+
+ error_out:
+ return -ENOMEM;
+}
+
+
+void __init scan_kbd_init(void)
+{
+ init_timer(&scan_timer);
+ scan_timer.expires = jiffies + SCANHZ;
+ scan_timer.data = 0;
+ scan_timer.function = scan_kbd;
+ add_timer(&scan_timer);
+
+ printk(KERN_INFO "Generic scan keyboard driver initialized\n");
+}
diff --git a/drivers/char/scan_keyb.h b/drivers/char/scan_keyb.h
new file mode 100644
index 000000000000..b4b611290a00
--- /dev/null
+++ b/drivers/char/scan_keyb.h
@@ -0,0 +1,15 @@
+#ifndef __DRIVER_CHAR_SCAN_KEYB_H
+#define __DRIVER_CHAR_SCAN_KEYB_H
+/*
+ * $Id: scan_keyb.h,v 1.1 2000/06/10 21:45:30 yaegashi Exp $
+ * Copyright (C) 2000 YAEGASHI Takeshi
+ * Generic scan keyboard driver
+ */
+
+int register_scan_keyboard(int (*scan)(unsigned char *buffer),
+ const unsigned char *table,
+ int length);
+
+void __init scan_kbd_init(void);
+
+#endif
diff --git a/drivers/char/scc.h b/drivers/char/scc.h
new file mode 100644
index 000000000000..51810f72f1a9
--- /dev/null
+++ b/drivers/char/scc.h
@@ -0,0 +1,613 @@
+/*
+ * atari_SCC.h: Definitions for the Am8530 Serial Communications Controller
+ *
+ * Copyright 1994 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+
+#ifndef _SCC_H
+#define _SCC_H
+
+#include <linux/delay.h>
+
+/* Special configuration ioctls for the Atari SCC5380 Serial
+ * Communications Controller
+ */
+
+/* ioctl command codes */
+
+#define TIOCGATSCC 0x54c0 /* get SCC configuration */
+#define TIOCSATSCC 0x54c1 /* set SCC configuration */
+#define TIOCDATSCC 0x54c2 /* reset configuration to defaults */
+
+/* Clock sources */
+
+#define CLK_RTxC 0
+#define CLK_TRxC 1
+#define CLK_PCLK 2
+
+/* baud_bases for the common clocks in the Atari. These are the real
+ * frequencies divided by 16.
+ */
+
+#define SCC_BAUD_BASE_TIMC 19200 /* 0.3072 MHz from TT-MFP, Timer C */
+#define SCC_BAUD_BASE_BCLK 153600 /* 2.4576 MHz */
+#define SCC_BAUD_BASE_PCLK4 229500 /* 3.6720 MHz */
+#define SCC_BAUD_BASE_PCLK 503374 /* 8.0539763 MHz */
+#define SCC_BAUD_BASE_NONE 0 /* for not connected or unused
+ * clock sources */
+
+/* The SCC clock configuration structure */
+
+struct scc_clock_config {
+ unsigned RTxC_base; /* base_baud of RTxC */
+ unsigned TRxC_base; /* base_baud of TRxC */
+ unsigned PCLK_base; /* base_baud of PCLK, both channels! */
+ struct {
+ unsigned clksrc; /* CLK_RTxC, CLK_TRxC or CLK_PCLK */
+ unsigned divisor; /* divisor for base baud, valid values:
+ * see below */
+ } baud_table[17]; /* For 50, 75, 110, 135, 150, 200, 300,
+ * 600, 1200, 1800, 2400, 4800, 9600,
+ * 19200, 38400, 57600 and 115200 bps.
+ * The last two could be replaced by
+ * other rates > 38400 if they're not
+ * possible.
+ */
+};
+
+/* The following divisors are valid:
+ *
+ * - CLK_RTxC: 1 or even (1, 2 and 4 are the direct modes, > 4 use
+ * the BRG)
+ *
+ * - CLK_TRxC: 1, 2 or 4 (no BRG, only direct modes possible)
+ *
+ * - CLK_PCLK: >= 4 and even (no direct modes, only BRG)
+ *
+ */
+
+struct scc_port {
+ struct gs_port gs;
+ volatile unsigned char *ctrlp;
+ volatile unsigned char *datap;
+ int x_char; /* xon/xoff character */
+ int c_dcd;
+ int channel;
+ struct scc_port *port_a; /* Reference to port A and B */
+ struct scc_port *port_b; /* structs for reg access */
+};
+
+#define SCC_MAGIC 0x52696368
+
+/***********************************************************************/
+/* */
+/* Register Names */
+/* */
+/***********************************************************************/
+
+/* The SCC documentation gives no explicit names to the registers,
+ * they're just called WR0..15 and RR0..15. To make the source code
+ * better readable and make the transparent write reg read access (see
+ * below) possible, I christen them here with self-invented names.
+ * Note that (real) read registers are assigned numbers 16..31. WR7'
+ * has number 33.
+ */
+
+#define COMMAND_REG 0 /* wo */
+#define INT_AND_DMA_REG 1 /* wo */
+#define INT_VECTOR_REG 2 /* rw, common to both channels */
+#define RX_CTRL_REG 3 /* rw */
+#define AUX1_CTRL_REG 4 /* rw */
+#define TX_CTRL_REG 5 /* rw */
+#define SYNC_ADR_REG 6 /* wo */
+#define SYNC_CHAR_REG 7 /* wo */
+#define SDLC_OPTION_REG 33 /* wo */
+#define TX_DATA_REG 8 /* wo */
+#define MASTER_INT_CTRL 9 /* wo, common to both channels */
+#define AUX2_CTRL_REG 10 /* rw */
+#define CLK_CTRL_REG 11 /* wo */
+#define TIMER_LOW_REG 12 /* rw */
+#define TIMER_HIGH_REG 13 /* rw */
+#define DPLL_CTRL_REG 14 /* wo */
+#define INT_CTRL_REG 15 /* rw */
+
+#define STATUS_REG 16 /* ro */
+#define SPCOND_STATUS_REG 17 /* wo */
+/* RR2 is WR2 for Channel A, Channel B gives vector + current status: */
+#define CURR_VECTOR_REG 18 /* Ch. B only, Ch. A for rw */
+#define INT_PENDING_REG 19 /* Channel A only! */
+/* RR4 is WR4, if b6(MR7') == 1 */
+/* RR5 is WR5, if b6(MR7') == 1 */
+#define FS_FIFO_LOW_REG 22 /* ro */
+#define FS_FIFO_HIGH_REG 23 /* ro */
+#define RX_DATA_REG 24 /* ro */
+/* RR9 is WR3, if b6(MR7') == 1 */
+#define DPLL_STATUS_REG 26 /* ro */
+/* RR11 is WR10, if b6(MR7') == 1 */
+/* RR12 is WR12 */
+/* RR13 is WR13 */
+/* RR14 not present */
+/* RR15 is WR15 */
+
+
+/***********************************************************************/
+/* */
+/* Register Values */
+/* */
+/***********************************************************************/
+
+
+/* WR0: COMMAND_REG "CR" */
+
+#define CR_RX_CRC_RESET 0x40
+#define CR_TX_CRC_RESET 0x80
+#define CR_TX_UNDERRUN_RESET 0xc0
+
+#define CR_EXTSTAT_RESET 0x10
+#define CR_SEND_ABORT 0x18
+#define CR_ENAB_INT_NEXT_RX 0x20
+#define CR_TX_PENDING_RESET 0x28
+#define CR_ERROR_RESET 0x30
+#define CR_HIGHEST_IUS_RESET 0x38
+
+
+/* WR1: INT_AND_DMA_REG "IDR" */
+
+#define IDR_EXTSTAT_INT_ENAB 0x01
+#define IDR_TX_INT_ENAB 0x02
+#define IDR_PARERR_AS_SPCOND 0x04
+
+#define IDR_RX_INT_DISAB 0x00
+#define IDR_RX_INT_FIRST 0x08
+#define IDR_RX_INT_ALL 0x10
+#define IDR_RX_INT_SPCOND 0x18
+#define IDR_RX_INT_MASK 0x18
+
+#define IDR_WAITREQ_RX 0x20
+#define IDR_WAITREQ_IS_REQ 0x40
+#define IDR_WAITREQ_ENAB 0x80
+
+
+/* WR3: RX_CTRL_REG "RCR" */
+
+#define RCR_RX_ENAB 0x01
+#define RCR_DISCARD_SYNC_CHARS 0x02
+#define RCR_ADDR_SEARCH 0x04
+#define RCR_CRC_ENAB 0x08
+#define RCR_SEARCH_MODE 0x10
+#define RCR_AUTO_ENAB_MODE 0x20
+
+#define RCR_CHSIZE_MASK 0xc0
+#define RCR_CHSIZE_5 0x00
+#define RCR_CHSIZE_6 0x40
+#define RCR_CHSIZE_7 0x80
+#define RCR_CHSIZE_8 0xc0
+
+
+/* WR4: AUX1_CTRL_REG "A1CR" */
+
+#define A1CR_PARITY_MASK 0x03
+#define A1CR_PARITY_NONE 0x00
+#define A1CR_PARITY_ODD 0x01
+#define A1CR_PARITY_EVEN 0x03
+
+#define A1CR_MODE_MASK 0x0c
+#define A1CR_MODE_SYNCR 0x00
+#define A1CR_MODE_ASYNC_1 0x04
+#define A1CR_MODE_ASYNC_15 0x08
+#define A1CR_MODE_ASYNC_2 0x0c
+
+#define A1CR_SYNCR_MODE_MASK 0x30
+#define A1CR_SYNCR_MONOSYNC 0x00
+#define A1CR_SYNCR_BISYNC 0x10
+#define A1CR_SYNCR_SDLC 0x20
+#define A1CR_SYNCR_EXTCSYNC 0x30
+
+#define A1CR_CLKMODE_MASK 0xc0
+#define A1CR_CLKMODE_x1 0x00
+#define A1CR_CLKMODE_x16 0x40
+#define A1CR_CLKMODE_x32 0x80
+#define A1CR_CLKMODE_x64 0xc0
+
+
+/* WR5: TX_CTRL_REG "TCR" */
+
+#define TCR_TX_CRC_ENAB 0x01
+#define TCR_RTS 0x02
+#define TCR_USE_CRC_CCITT 0x00
+#define TCR_USE_CRC_16 0x04
+#define TCR_TX_ENAB 0x08
+#define TCR_SEND_BREAK 0x10
+
+#define TCR_CHSIZE_MASK 0x60
+#define TCR_CHSIZE_5 0x00
+#define TCR_CHSIZE_6 0x20
+#define TCR_CHSIZE_7 0x40
+#define TCR_CHSIZE_8 0x60
+
+#define TCR_DTR 0x80
+
+
+/* WR7': SLDC_OPTION_REG "SOR" */
+
+#define SOR_AUTO_TX_ENAB 0x01
+#define SOR_AUTO_EOM_RESET 0x02
+#define SOR_AUTO_RTS_MODE 0x04
+#define SOR_NRZI_DISAB_HIGH 0x08
+#define SOR_ALT_DTRREQ_TIMING 0x10
+#define SOR_READ_CRC_CHARS 0x20
+#define SOR_EXTENDED_REG_ACCESS 0x40
+
+
+/* WR9: MASTER_INT_CTRL "MIC" */
+
+#define MIC_VEC_INCL_STAT 0x01
+#define MIC_NO_VECTOR 0x02
+#define MIC_DISAB_LOWER_CHAIN 0x04
+#define MIC_MASTER_INT_ENAB 0x08
+#define MIC_STATUS_HIGH 0x10
+#define MIC_IGN_INTACK 0x20
+
+#define MIC_NO_RESET 0x00
+#define MIC_CH_A_RESET 0x40
+#define MIC_CH_B_RESET 0x80
+#define MIC_HARD_RESET 0xc0
+
+
+/* WR10: AUX2_CTRL_REG "A2CR" */
+
+#define A2CR_SYNC_6 0x01
+#define A2CR_LOOP_MODE 0x02
+#define A2CR_ABORT_ON_UNDERRUN 0x04
+#define A2CR_MARK_IDLE 0x08
+#define A2CR_GO_ACTIVE_ON_POLL 0x10
+
+#define A2CR_CODING_MASK 0x60
+#define A2CR_CODING_NRZ 0x00
+#define A2CR_CODING_NRZI 0x20
+#define A2CR_CODING_FM1 0x40
+#define A2CR_CODING_FM0 0x60
+
+#define A2CR_PRESET_CRC_1 0x80
+
+
+/* WR11: CLK_CTRL_REG "CCR" */
+
+#define CCR_TRxCOUT_MASK 0x03
+#define CCR_TRxCOUT_XTAL 0x00
+#define CCR_TRxCOUT_TXCLK 0x01
+#define CCR_TRxCOUT_BRG 0x02
+#define CCR_TRxCOUT_DPLL 0x03
+
+#define CCR_TRxC_OUTPUT 0x04
+
+#define CCR_TXCLK_MASK 0x18
+#define CCR_TXCLK_RTxC 0x00
+#define CCR_TXCLK_TRxC 0x08
+#define CCR_TXCLK_BRG 0x10
+#define CCR_TXCLK_DPLL 0x18
+
+#define CCR_RXCLK_MASK 0x60
+#define CCR_RXCLK_RTxC 0x00
+#define CCR_RXCLK_TRxC 0x20
+#define CCR_RXCLK_BRG 0x40
+#define CCR_RXCLK_DPLL 0x60
+
+#define CCR_RTxC_XTAL 0x80
+
+
+/* WR14: DPLL_CTRL_REG "DCR" */
+
+#define DCR_BRG_ENAB 0x01
+#define DCR_BRG_USE_PCLK 0x02
+#define DCR_DTRREQ_IS_REQ 0x04
+#define DCR_AUTO_ECHO 0x08
+#define DCR_LOCAL_LOOPBACK 0x10
+
+#define DCR_DPLL_EDGE_SEARCH 0x20
+#define DCR_DPLL_ERR_RESET 0x40
+#define DCR_DPLL_DISAB 0x60
+#define DCR_DPLL_CLK_BRG 0x80
+#define DCR_DPLL_CLK_RTxC 0xa0
+#define DCR_DPLL_FM 0xc0
+#define DCR_DPLL_NRZI 0xe0
+
+
+/* WR15: INT_CTRL_REG "ICR" */
+
+#define ICR_OPTIONREG_SELECT 0x01
+#define ICR_ENAB_BRG_ZERO_INT 0x02
+#define ICR_USE_FS_FIFO 0x04
+#define ICR_ENAB_DCD_INT 0x08
+#define ICR_ENAB_SYNC_INT 0x10
+#define ICR_ENAB_CTS_INT 0x20
+#define ICR_ENAB_UNDERRUN_INT 0x40
+#define ICR_ENAB_BREAK_INT 0x80
+
+
+/* RR0: STATUS_REG "SR" */
+
+#define SR_CHAR_AVAIL 0x01
+#define SR_BRG_ZERO 0x02
+#define SR_TX_BUF_EMPTY 0x04
+#define SR_DCD 0x08
+#define SR_SYNC_ABORT 0x10
+#define SR_CTS 0x20
+#define SR_TX_UNDERRUN 0x40
+#define SR_BREAK 0x80
+
+
+/* RR1: SPCOND_STATUS_REG "SCSR" */
+
+#define SCSR_ALL_SENT 0x01
+#define SCSR_RESIDUAL_MASK 0x0e
+#define SCSR_PARITY_ERR 0x10
+#define SCSR_RX_OVERRUN 0x20
+#define SCSR_CRC_FRAME_ERR 0x40
+#define SCSR_END_OF_FRAME 0x80
+
+
+/* RR3: INT_PENDING_REG "IPR" */
+
+#define IPR_B_EXTSTAT 0x01
+#define IPR_B_TX 0x02
+#define IPR_B_RX 0x04
+#define IPR_A_EXTSTAT 0x08
+#define IPR_A_TX 0x10
+#define IPR_A_RX 0x20
+
+
+/* RR7: FS_FIFO_HIGH_REG "FFHR" */
+
+#define FFHR_CNT_MASK 0x3f
+#define FFHR_IS_FROM_FIFO 0x40
+#define FFHR_FIFO_OVERRUN 0x80
+
+
+/* RR10: DPLL_STATUS_REG "DSR" */
+
+#define DSR_ON_LOOP 0x02
+#define DSR_ON_LOOP_SENDING 0x10
+#define DSR_TWO_CLK_MISSING 0x40
+#define DSR_ONE_CLK_MISSING 0x80
+
+/***********************************************************************/
+/* */
+/* Register Access */
+/* */
+/***********************************************************************/
+
+
+/* The SCC needs 3.5 PCLK cycles recovery time between to register
+ * accesses. PCLK runs with 8 MHz on an Atari, so this delay is 3.5 *
+ * 125 ns = 437.5 ns. This is too short for udelay().
+ * 10/16/95: A tstb mfp.par_dt_reg takes 600ns (sure?) and thus should be
+ * quite right
+ */
+
+#define scc_reg_delay() \
+ do { \
+ if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147) \
+ __asm__ __volatile__ ( " nop; nop"); \
+ else if (MACH_IS_ATARI) \
+ __asm__ __volatile__ ( "tstb %0" : : "g" (*_scc_del) : "cc" );\
+ } while (0)
+
+extern unsigned char scc_shadow[2][16];
+
+/* The following functions should relax the somehow complicated
+ * register access of the SCC. _SCCwrite() stores all written values
+ * (except for WR0 and WR8) in shadow registers for later recall. This
+ * removes the burden of remembering written values as needed. The
+ * extra work of storing the value doesn't count, since a delay is
+ * needed after a SCC access anyway. Additionally, _SCCwrite() manages
+ * writes to WR0 and WR8 differently, because these can be accessed
+ * directly with less overhead. Another special case are WR7 and WR7'.
+ * _SCCwrite automatically checks what of this registers is selected
+ * and changes b0 of WR15 if needed.
+ *
+ * _SCCread() for standard read registers is straightforward, except
+ * for RR2 (split into two "virtual" registers: one for the value
+ * written to WR2 (from the shadow) and one for the vector including
+ * status from RR2, Ch. B) and RR3. The latter must be read from
+ * Channel A, because it reads as all zeros on Ch. B. RR0 and RR8 can
+ * be accessed directly as before.
+ *
+ * The two inline function contain complicated switch statements. But
+ * I rely on regno and final_delay being constants, so gcc can reduce
+ * the whole stuff to just some assembler statements.
+ *
+ * _SCCwrite and _SCCread aren't intended to be used directly under
+ * normal circumstances. The macros SCCread[_ND] and SCCwrite[_ND] are
+ * for that purpose. They assume that a local variable 'port' is
+ * declared and pointing to the port's scc_struct entry. The
+ * variants with "_NB" appended should be used if no other SCC
+ * accesses follow immediately (within 0.5 usecs). They just skip the
+ * final delay nops.
+ *
+ * Please note that accesses to SCC registers should only take place
+ * when interrupts are turned off (at least if SCC interrupts are
+ * enabled). Otherwise, an interrupt could interfere with the
+ * two-stage accessing process.
+ *
+ */
+
+
+static __inline__ void _SCCwrite(
+ struct scc_port *port,
+ unsigned char *shadow,
+ volatile unsigned char *_scc_del,
+ int regno,
+ unsigned char val, int final_delay )
+{
+ switch( regno ) {
+
+ case COMMAND_REG:
+ /* WR0 can be written directly without pointing */
+ *port->ctrlp = val;
+ break;
+
+ case SYNC_CHAR_REG:
+ /* For WR7, first set b0 of WR15 to 0, if needed */
+ if (shadow[INT_CTRL_REG] & ICR_OPTIONREG_SELECT) {
+ *port->ctrlp = 15;
+ shadow[INT_CTRL_REG] &= ~ICR_OPTIONREG_SELECT;
+ scc_reg_delay();
+ *port->ctrlp = shadow[INT_CTRL_REG];
+ scc_reg_delay();
+ }
+ goto normal_case;
+
+ case SDLC_OPTION_REG:
+ /* For WR7', first set b0 of WR15 to 1, if needed */
+ if (!(shadow[INT_CTRL_REG] & ICR_OPTIONREG_SELECT)) {
+ *port->ctrlp = 15;
+ shadow[INT_CTRL_REG] |= ICR_OPTIONREG_SELECT;
+ scc_reg_delay();
+ *port->ctrlp = shadow[INT_CTRL_REG];
+ scc_reg_delay();
+ }
+ *port->ctrlp = 7;
+ shadow[8] = val; /* WR7' shadowed at WR8 */
+ scc_reg_delay();
+ *port->ctrlp = val;
+ break;
+
+ case TX_DATA_REG: /* WR8 */
+ /* TX_DATA_REG can be accessed directly on some h/w */
+ if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147)
+ {
+ *port->ctrlp = regno;
+ scc_reg_delay();
+ *port->ctrlp = val;
+ }
+ else
+ *port->datap = val;
+ break;
+
+ case MASTER_INT_CTRL:
+ *port->ctrlp = regno;
+ val &= 0x3f; /* bits 6..7 are the reset commands */
+ scc_shadow[0][regno] = val;
+ scc_reg_delay();
+ *port->ctrlp = val;
+ break;
+
+ case DPLL_CTRL_REG:
+ *port->ctrlp = regno;
+ val &= 0x1f; /* bits 5..7 are the DPLL commands */
+ shadow[regno] = val;
+ scc_reg_delay();
+ *port->ctrlp = val;
+ break;
+
+ case 1 ... 6:
+ case 10 ... 13:
+ case 15:
+ normal_case:
+ *port->ctrlp = regno;
+ shadow[regno] = val;
+ scc_reg_delay();
+ *port->ctrlp = val;
+ break;
+
+ default:
+ printk( "Bad SCC write access to WR%d\n", regno );
+ break;
+
+ }
+
+ if (final_delay)
+ scc_reg_delay();
+}
+
+
+static __inline__ unsigned char _SCCread(
+ struct scc_port *port,
+ unsigned char *shadow,
+ volatile unsigned char *_scc_del,
+ int regno, int final_delay )
+{
+ unsigned char rv;
+
+ switch( regno ) {
+
+ /* --- real read registers --- */
+ case STATUS_REG:
+ rv = *port->ctrlp;
+ break;
+
+ case INT_PENDING_REG:
+ /* RR3: read only from Channel A! */
+ port = port->port_a;
+ goto normal_case;
+
+ case RX_DATA_REG:
+ /* RR8 can be accessed directly on some h/w */
+ if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147)
+ {
+ *port->ctrlp = 8;
+ scc_reg_delay();
+ rv = *port->ctrlp;
+ }
+ else
+ rv = *port->datap;
+ break;
+
+ case CURR_VECTOR_REG:
+ /* RR2 (vector including status) from Ch. B */
+ port = port->port_b;
+ goto normal_case;
+
+ /* --- reading write registers: access the shadow --- */
+ case 1 ... 7:
+ case 10 ... 15:
+ return shadow[regno]; /* no final delay! */
+
+ /* WR7' is special, because it is shadowed at the place of WR8 */
+ case SDLC_OPTION_REG:
+ return shadow[8]; /* no final delay! */
+
+ /* WR9 is special too, because it is common for both channels */
+ case MASTER_INT_CTRL:
+ return scc_shadow[0][9]; /* no final delay! */
+
+ default:
+ printk( "Bad SCC read access to %cR%d\n", (regno & 16) ? 'R' : 'W',
+ regno & ~16 );
+ break;
+
+ case SPCOND_STATUS_REG:
+ case FS_FIFO_LOW_REG:
+ case FS_FIFO_HIGH_REG:
+ case DPLL_STATUS_REG:
+ normal_case:
+ *port->ctrlp = regno & 0x0f;
+ scc_reg_delay();
+ rv = *port->ctrlp;
+ break;
+
+ }
+
+ if (final_delay)
+ scc_reg_delay();
+ return rv;
+}
+
+#define SCC_ACCESS_INIT(port) \
+ unsigned char *_scc_shadow = &scc_shadow[port->channel][0]
+
+#define SCCwrite(reg,val) _SCCwrite(port,_scc_shadow,scc_del,(reg),(val),1)
+#define SCCwrite_NB(reg,val) _SCCwrite(port,_scc_shadow,scc_del,(reg),(val),0)
+#define SCCread(reg) _SCCread(port,_scc_shadow,scc_del,(reg),1)
+#define SCCread_NB(reg) _SCCread(port,_scc_shadow,scc_del,(reg),0)
+
+#define SCCmod(reg,and,or) SCCwrite((reg),(SCCread(reg)&(and))|(or))
+
+#endif /* _SCC_H */
diff --git a/drivers/char/scx200_gpio.c b/drivers/char/scx200_gpio.c
new file mode 100644
index 000000000000..664a6e97eb1a
--- /dev/null
+++ b/drivers/char/scx200_gpio.c
@@ -0,0 +1,149 @@
+/* linux/drivers/char/scx200_gpio.c
+
+ National Semiconductor SCx200 GPIO driver. Allows a user space
+ process to play with the GPIO pins.
+
+ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> */
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/scx200_gpio.h>
+
+#define NAME "scx200_gpio"
+
+MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
+MODULE_DESCRIPTION("NatSemi SCx200 GPIO Pin Driver");
+MODULE_LICENSE("GPL");
+
+static int major = 0; /* default to dynamic major */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+static ssize_t scx200_gpio_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ unsigned m = iminor(file->f_dentry->d_inode);
+ size_t i;
+
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data+i))
+ return -EFAULT;
+ switch (c)
+ {
+ case '0':
+ scx200_gpio_set(m, 0);
+ break;
+ case '1':
+ scx200_gpio_set(m, 1);
+ break;
+ case 'O':
+ printk(KERN_INFO NAME ": GPIO%d output enabled\n", m);
+ scx200_gpio_configure(m, ~1, 1);
+ break;
+ case 'o':
+ printk(KERN_INFO NAME ": GPIO%d output disabled\n", m);
+ scx200_gpio_configure(m, ~1, 0);
+ break;
+ case 'T':
+ printk(KERN_INFO NAME ": GPIO%d output is push pull\n", m);
+ scx200_gpio_configure(m, ~2, 2);
+ break;
+ case 't':
+ printk(KERN_INFO NAME ": GPIO%d output is open drain\n", m);
+ scx200_gpio_configure(m, ~2, 0);
+ break;
+ case 'P':
+ printk(KERN_INFO NAME ": GPIO%d pull up enabled\n", m);
+ scx200_gpio_configure(m, ~4, 4);
+ break;
+ case 'p':
+ printk(KERN_INFO NAME ": GPIO%d pull up disabled\n", m);
+ scx200_gpio_configure(m, ~4, 0);
+ break;
+ }
+ }
+
+ return len;
+}
+
+static ssize_t scx200_gpio_read(struct file *file, char __user *buf,
+ size_t len, loff_t *ppos)
+{
+ unsigned m = iminor(file->f_dentry->d_inode);
+ int value;
+
+ value = scx200_gpio_get(m);
+ if (put_user(value ? '1' : '0', buf))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int scx200_gpio_open(struct inode *inode, struct file *file)
+{
+ unsigned m = iminor(inode);
+ if (m > 63)
+ return -EINVAL;
+ return nonseekable_open(inode, file);
+}
+
+static int scx200_gpio_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+
+static struct file_operations scx200_gpio_fops = {
+ .owner = THIS_MODULE,
+ .write = scx200_gpio_write,
+ .read = scx200_gpio_read,
+ .open = scx200_gpio_open,
+ .release = scx200_gpio_release,
+};
+
+static int __init scx200_gpio_init(void)
+{
+ int r;
+
+ printk(KERN_DEBUG NAME ": NatSemi SCx200 GPIO Driver\n");
+
+ if (!scx200_gpio_present()) {
+ printk(KERN_ERR NAME ": no SCx200 gpio pins available\n");
+ return -ENODEV;
+ }
+
+ r = register_chrdev(major, NAME, &scx200_gpio_fops);
+ if (r < 0) {
+ printk(KERN_ERR NAME ": unable to register character device\n");
+ return r;
+ }
+ if (!major) {
+ major = r;
+ printk(KERN_DEBUG NAME ": got dynamic major %d\n", major);
+ }
+
+ return 0;
+}
+
+static void __exit scx200_gpio_cleanup(void)
+{
+ unregister_chrdev(major, NAME);
+}
+
+module_init(scx200_gpio_init);
+module_exit(scx200_gpio_cleanup);
+
+/*
+ Local variables:
+ compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
+ c-basic-offset: 8
+ End:
+*/
diff --git a/drivers/char/selection.c b/drivers/char/selection.c
new file mode 100644
index 000000000000..16d630f58bb4
--- /dev/null
+++ b/drivers/char/selection.c
@@ -0,0 +1,306 @@
+/*
+ * linux/drivers/char/selection.c
+ *
+ * This module exports the functions:
+ *
+ * 'int set_selection(struct tiocl_selection __user *, struct tty_struct *)'
+ * 'void clear_selection(void)'
+ * 'int paste_selection(struct tty_struct *)'
+ * 'int sel_loadlut(char __user *)'
+ *
+ * Now that /dev/vcs exists, most of this can disappear again.
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/uaccess.h>
+
+#include <linux/vt_kern.h>
+#include <linux/consolemap.h>
+#include <linux/selection.h>
+#include <linux/tiocl.h>
+#include <linux/console.h>
+
+/* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
+#define isspace(c) ((c) == ' ')
+
+extern void poke_blanked_console(void);
+
+/* Variables for selection control. */
+/* Use a dynamic buffer, instead of static (Dec 1994) */
+struct vc_data *sel_cons; /* must not be disallocated */
+static volatile int sel_start = -1; /* cleared by clear_selection */
+static int sel_end;
+static int sel_buffer_lth;
+static char *sel_buffer;
+
+/* clear_selection, highlight and highlight_pointer can be called
+ from interrupt (via scrollback/front) */
+
+/* set reverse video on characters s-e of console with selection. */
+static inline void highlight(const int s, const int e)
+{
+ invert_screen(sel_cons, s, e-s+2, 1);
+}
+
+/* use complementary color to show the pointer */
+static inline void highlight_pointer(const int where)
+{
+ complement_pos(sel_cons, where);
+}
+
+static unsigned char
+sel_pos(int n)
+{
+ return inverse_translate(sel_cons, screen_glyph(sel_cons, n));
+}
+
+/* remove the current selection highlight, if any,
+ from the console holding the selection. */
+void
+clear_selection(void) {
+ highlight_pointer(-1); /* hide the pointer */
+ if (sel_start != -1) {
+ highlight(sel_start, sel_end);
+ sel_start = -1;
+ }
+}
+
+/*
+ * User settable table: what characters are to be considered alphabetic?
+ * 256 bits
+ */
+static u32 inwordLut[8]={
+ 0x00000000, /* control chars */
+ 0x03FF0000, /* digits */
+ 0x87FFFFFE, /* uppercase and '_' */
+ 0x07FFFFFE, /* lowercase */
+ 0x00000000,
+ 0x00000000,
+ 0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */
+ 0xFF7FFFFF /* latin-1 accented letters, not division sign */
+};
+
+static inline int inword(const unsigned char c) {
+ return ( inwordLut[c>>5] >> (c & 0x1F) ) & 1;
+}
+
+/* set inwordLut contents. Invoked by ioctl(). */
+int sel_loadlut(char __user *p)
+{
+ return copy_from_user(inwordLut, (u32 __user *)(p+4), 32) ? -EFAULT : 0;
+}
+
+/* does screen address p correspond to character at LH/RH edge of screen? */
+static inline int atedge(const int p, int size_row)
+{
+ return (!(p % size_row) || !((p + 2) % size_row));
+}
+
+/* constrain v such that v <= u */
+static inline unsigned short limit(const unsigned short v, const unsigned short u)
+{
+ return (v > u) ? u : v;
+}
+
+/* set the current selection. Invoked by ioctl() or by kernel code. */
+int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *tty)
+{
+ struct vc_data *vc = vc_cons[fg_console].d;
+ int sel_mode, new_sel_start, new_sel_end, spc;
+ char *bp, *obp;
+ int i, ps, pe;
+
+ poke_blanked_console();
+
+ { unsigned short xs, ys, xe, ye;
+
+ if (!access_ok(VERIFY_READ, sel, sizeof(*sel)))
+ return -EFAULT;
+ __get_user(xs, &sel->xs);
+ __get_user(ys, &sel->ys);
+ __get_user(xe, &sel->xe);
+ __get_user(ye, &sel->ye);
+ __get_user(sel_mode, &sel->sel_mode);
+ xs--; ys--; xe--; ye--;
+ xs = limit(xs, vc->vc_cols - 1);
+ ys = limit(ys, vc->vc_rows - 1);
+ xe = limit(xe, vc->vc_cols - 1);
+ ye = limit(ye, vc->vc_rows - 1);
+ ps = ys * vc->vc_size_row + (xs << 1);
+ pe = ye * vc->vc_size_row + (xe << 1);
+
+ if (sel_mode == TIOCL_SELCLEAR) {
+ /* useful for screendump without selection highlights */
+ clear_selection();
+ return 0;
+ }
+
+ if (mouse_reporting() && (sel_mode & TIOCL_SELMOUSEREPORT)) {
+ mouse_report(tty, sel_mode & TIOCL_SELBUTTONMASK, xs, ys);
+ return 0;
+ }
+ }
+
+ if (ps > pe) /* make sel_start <= sel_end */
+ {
+ int tmp = ps;
+ ps = pe;
+ pe = tmp;
+ }
+
+ if (sel_cons != vc_cons[fg_console].d) {
+ clear_selection();
+ sel_cons = vc_cons[fg_console].d;
+ }
+
+ switch (sel_mode)
+ {
+ case TIOCL_SELCHAR: /* character-by-character selection */
+ new_sel_start = ps;
+ new_sel_end = pe;
+ break;
+ case TIOCL_SELWORD: /* word-by-word selection */
+ spc = isspace(sel_pos(ps));
+ for (new_sel_start = ps; ; ps -= 2)
+ {
+ if ((spc && !isspace(sel_pos(ps))) ||
+ (!spc && !inword(sel_pos(ps))))
+ break;
+ new_sel_start = ps;
+ if (!(ps % vc->vc_size_row))
+ break;
+ }
+ spc = isspace(sel_pos(pe));
+ for (new_sel_end = pe; ; pe += 2)
+ {
+ if ((spc && !isspace(sel_pos(pe))) ||
+ (!spc && !inword(sel_pos(pe))))
+ break;
+ new_sel_end = pe;
+ if (!((pe + 2) % vc->vc_size_row))
+ break;
+ }
+ break;
+ case TIOCL_SELLINE: /* line-by-line selection */
+ new_sel_start = ps - ps % vc->vc_size_row;
+ new_sel_end = pe + vc->vc_size_row
+ - pe % vc->vc_size_row - 2;
+ break;
+ case TIOCL_SELPOINTER:
+ highlight_pointer(pe);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+
+ /* remove the pointer */
+ highlight_pointer(-1);
+
+ /* select to end of line if on trailing space */
+ if (new_sel_end > new_sel_start &&
+ !atedge(new_sel_end, vc->vc_size_row) &&
+ isspace(sel_pos(new_sel_end))) {
+ for (pe = new_sel_end + 2; ; pe += 2)
+ if (!isspace(sel_pos(pe)) ||
+ atedge(pe, vc->vc_size_row))
+ break;
+ if (isspace(sel_pos(pe)))
+ new_sel_end = pe;
+ }
+ if (sel_start == -1) /* no current selection */
+ highlight(new_sel_start, new_sel_end);
+ else if (new_sel_start == sel_start)
+ {
+ if (new_sel_end == sel_end) /* no action required */
+ return 0;
+ else if (new_sel_end > sel_end) /* extend to right */
+ highlight(sel_end + 2, new_sel_end);
+ else /* contract from right */
+ highlight(new_sel_end + 2, sel_end);
+ }
+ else if (new_sel_end == sel_end)
+ {
+ if (new_sel_start < sel_start) /* extend to left */
+ highlight(new_sel_start, sel_start - 2);
+ else /* contract from left */
+ highlight(sel_start, new_sel_start - 2);
+ }
+ else /* some other case; start selection from scratch */
+ {
+ clear_selection();
+ highlight(new_sel_start, new_sel_end);
+ }
+ sel_start = new_sel_start;
+ sel_end = new_sel_end;
+
+ /* Allocate a new buffer before freeing the old one ... */
+ bp = kmalloc((sel_end-sel_start)/2+1, GFP_KERNEL);
+ if (!bp) {
+ printk(KERN_WARNING "selection: kmalloc() failed\n");
+ clear_selection();
+ return -ENOMEM;
+ }
+ if (sel_buffer)
+ kfree(sel_buffer);
+ sel_buffer = bp;
+
+ obp = bp;
+ for (i = sel_start; i <= sel_end; i += 2) {
+ *bp = sel_pos(i);
+ if (!isspace(*bp++))
+ obp = bp;
+ if (! ((i + 2) % vc->vc_size_row)) {
+ /* strip trailing blanks from line and add newline,
+ unless non-space at end of line. */
+ if (obp != bp) {
+ bp = obp;
+ *bp++ = '\r';
+ }
+ obp = bp;
+ }
+ }
+ sel_buffer_lth = bp - sel_buffer;
+ return 0;
+}
+
+/* Insert the contents of the selection buffer into the
+ * queue of the tty associated with the current console.
+ * Invoked by ioctl().
+ */
+int paste_selection(struct tty_struct *tty)
+{
+ struct vc_data *vc = (struct vc_data *)tty->driver_data;
+ int pasted = 0, count;
+ struct tty_ldisc *ld;
+ DECLARE_WAITQUEUE(wait, current);
+
+ acquire_console_sem();
+ poke_blanked_console();
+ release_console_sem();
+
+ ld = tty_ldisc_ref_wait(tty);
+
+ add_wait_queue(&vc->paste_wait, &wait);
+ while (sel_buffer && sel_buffer_lth > pasted) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (test_bit(TTY_THROTTLED, &tty->flags)) {
+ schedule();
+ continue;
+ }
+ count = sel_buffer_lth - pasted;
+ count = min(count, tty->ldisc.receive_room(tty));
+ tty->ldisc.receive_buf(tty, sel_buffer + pasted, NULL, count);
+ pasted += count;
+ }
+ remove_wait_queue(&vc->paste_wait, &wait);
+ current->state = TASK_RUNNING;
+
+ tty_ldisc_deref(ld);
+ return 0;
+}
diff --git a/drivers/char/ser_a2232.c b/drivers/char/ser_a2232.c
new file mode 100644
index 000000000000..6b4e9d155f50
--- /dev/null
+++ b/drivers/char/ser_a2232.c
@@ -0,0 +1,825 @@
+/* drivers/char/ser_a2232.c */
+
+/* $Id: ser_a2232.c,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/* Linux serial driver for the Amiga A2232 board */
+
+/* This driver is MAINTAINED. Before applying any changes, please contact
+ * the author.
+ */
+
+/* Copyright (c) 2000-2001 Enver Haase <ehaase@inf.fu-berlin.de>
+ * alias The A2232 driver project <A2232@gmx.net>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+/***************************** Documentation ************************/
+/*
+ * This driver is in EXPERIMENTAL state. That means I could not find
+ * someone with five A2232 boards with 35 ports running at 19200 bps
+ * at the same time and test the machine's behaviour.
+ * However, I know that you can performance-tweak this driver (see
+ * the source code).
+ * One thing to consider is the time this driver consumes during the
+ * Amiga's vertical blank interrupt. Everything that is to be done
+ * _IS DONE_ when entering the vertical blank interrupt handler of
+ * this driver.
+ * However, it would be more sane to only do the job for only ONE card
+ * instead of ALL cards at a time; or, more generally, to handle only
+ * SOME ports instead of ALL ports at a time.
+ * However, as long as no-one runs into problems I guess I shouldn't
+ * change the driver as it runs fine for me :) .
+ *
+ * Version history of this file:
+ * 0.4 Resolved licensing issues.
+ * 0.3 Inclusion in the Linux/m68k tree, small fixes.
+ * 0.2 Added documentation, minor typo fixes.
+ * 0.1 Initial release.
+ *
+ * TO DO:
+ * - Handle incoming BREAK events. I guess "Stevens: Advanced
+ * Programming in the UNIX(R) Environment" is a good reference
+ * on what is to be done.
+ * - When installing as a module, don't simply 'printk' text, but
+ * send it to the TTY used by the user.
+ *
+ * THANKS TO:
+ * - Jukka Marin (65EC02 code).
+ * - The other NetBSD developers on whose A2232 driver I had a
+ * pretty close look. However, I didn't copy any code so it
+ * is okay to put my code under the GPL and include it into
+ * Linux.
+ */
+/***************************** End of Documentation *****************/
+
+/***************************** Defines ******************************/
+/*
+ * Enables experimental 115200 (normal) 230400 (turbo) baud rate.
+ * The A2232 specification states it can only operate at speeds up to
+ * 19200 bits per second, and I was not able to send a file via
+ * "sz"/"rz" and a null-modem cable from one A2232 port to another
+ * at 115200 bits per second.
+ * However, this might work for you.
+ */
+#undef A2232_SPEEDHACK
+/*
+ * Default is not to use RTS/CTS so you could be talked to death.
+ */
+#define A2232_SUPPRESS_RTSCTS_WARNING
+/************************* End of Defines ***************************/
+
+/***************************** Includes *****************************/
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+
+#include <asm/setup.h>
+#include <asm/amigaints.h>
+#include <asm/amigahw.h>
+#include <linux/zorro.h>
+#include <asm/irq.h>
+#include <asm/semaphore.h>
+
+#include <linux/delay.h>
+
+#include <linux/serial.h>
+#include <linux/generic_serial.h>
+
+#include "ser_a2232.h"
+#include "ser_a2232fw.h"
+/************************* End of Includes **************************/
+
+/***************************** Prototypes ***************************/
+/* The interrupt service routine */
+static irqreturn_t a2232_vbl_inter(int irq, void *data, struct pt_regs *fp);
+/* Initialize the port structures */
+static void a2232_init_portstructs(void);
+/* Initialize and register TTY drivers. */
+/* returns 0 IFF successful */
+static int a2232_init_drivers(void);
+
+/* BEGIN GENERIC_SERIAL PROTOTYPES */
+static void a2232_disable_tx_interrupts(void *ptr);
+static void a2232_enable_tx_interrupts(void *ptr);
+static void a2232_disable_rx_interrupts(void *ptr);
+static void a2232_enable_rx_interrupts(void *ptr);
+static int a2232_get_CD(void *ptr);
+static void a2232_shutdown_port(void *ptr);
+static int a2232_set_real_termios(void *ptr);
+static int a2232_chars_in_buffer(void *ptr);
+static void a2232_close(void *ptr);
+static void a2232_hungup(void *ptr);
+/* static void a2232_getserial (void *ptr, struct serial_struct *sp); */
+/* END GENERIC_SERIAL PROTOTYPES */
+
+/* Functions that the TTY driver struct expects */
+static int a2232_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static void a2232_throttle(struct tty_struct *tty);
+static void a2232_unthrottle(struct tty_struct *tty);
+static int a2232_open(struct tty_struct * tty, struct file * filp);
+/************************* End of Prototypes ************************/
+
+/***************************** Global variables *********************/
+/*---------------------------------------------------------------------------
+ * Interface from generic_serial.c back here
+ *--------------------------------------------------------------------------*/
+static struct real_driver a2232_real_driver = {
+ a2232_disable_tx_interrupts,
+ a2232_enable_tx_interrupts,
+ a2232_disable_rx_interrupts,
+ a2232_enable_rx_interrupts,
+ a2232_get_CD,
+ a2232_shutdown_port,
+ a2232_set_real_termios,
+ a2232_chars_in_buffer,
+ a2232_close,
+ a2232_hungup,
+ NULL /* a2232_getserial */
+};
+
+static void *a2232_driver_ID = &a2232_driver_ID; // Some memory address WE own.
+
+/* Ports structs */
+static struct a2232_port a2232_ports[MAX_A2232_BOARDS*NUMLINES];
+
+/* TTY driver structs */
+static struct tty_driver *a2232_driver;
+
+/* nr of cards completely (all ports) and correctly configured */
+static int nr_a2232;
+
+/* zorro_dev structs for the A2232's */
+static struct zorro_dev *zd_a2232[MAX_A2232_BOARDS];
+/***************************** End of Global variables **************/
+
+/* Helper functions */
+
+static inline volatile struct a2232memory *a2232mem(unsigned int board)
+{
+ return (volatile struct a2232memory *)ZTWO_VADDR(zd_a2232[board]->resource.start);
+}
+
+static inline volatile struct a2232status *a2232stat(unsigned int board,
+ unsigned int portonboard)
+{
+ volatile struct a2232memory *mem = a2232mem(board);
+ return &(mem->Status[portonboard]);
+}
+
+static inline void a2232_receive_char(struct a2232_port *port, int ch, int err)
+{
+/* Mostly stolen from other drivers.
+ Maybe one could implement a more efficient version by not only
+ transferring one character at a time.
+*/
+ struct tty_struct *tty = port->gs.tty;
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ return;
+
+ tty->flip.count++;
+
+#if 0
+ switch(err) {
+ case TTY_BREAK:
+ break;
+ case TTY_PARITY:
+ break;
+ case TTY_OVERRUN:
+ break;
+ case TTY_FRAME:
+ break;
+ }
+#endif
+
+ *tty->flip.flag_buf_ptr++ = err;
+ *tty->flip.char_buf_ptr++ = ch;
+ tty_flip_buffer_push(tty);
+}
+
+/***************************** Functions ****************************/
+/*** BEGIN OF REAL_DRIVER FUNCTIONS ***/
+
+static void a2232_disable_tx_interrupts(void *ptr)
+{
+ struct a2232_port *port;
+ volatile struct a2232status *stat;
+ unsigned long flags;
+
+ port = ptr;
+ stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+ stat->OutDisable = -1;
+
+ /* Does this here really have to be? */
+ local_irq_save(flags);
+ port->gs.flags &= ~GS_TX_INTEN;
+ local_irq_restore(flags);
+}
+
+static void a2232_enable_tx_interrupts(void *ptr)
+{
+ struct a2232_port *port;
+ volatile struct a2232status *stat;
+ unsigned long flags;
+
+ port = ptr;
+ stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+ stat->OutDisable = 0;
+
+ /* Does this here really have to be? */
+ local_irq_save(flags);
+ port->gs.flags |= GS_TX_INTEN;
+ local_irq_restore(flags);
+}
+
+static void a2232_disable_rx_interrupts(void *ptr)
+{
+ struct a2232_port *port;
+ port = ptr;
+ port->disable_rx = -1;
+}
+
+static void a2232_enable_rx_interrupts(void *ptr)
+{
+ struct a2232_port *port;
+ port = ptr;
+ port->disable_rx = 0;
+}
+
+static int a2232_get_CD(void *ptr)
+{
+ return ((struct a2232_port *) ptr)->cd_status;
+}
+
+static void a2232_shutdown_port(void *ptr)
+{
+ struct a2232_port *port;
+ volatile struct a2232status *stat;
+ unsigned long flags;
+
+ port = ptr;
+ stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+
+ local_irq_save(flags);
+
+ port->gs.flags &= ~GS_ACTIVE;
+
+ if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL) {
+ /* Set DTR and RTS to Low, flush output.
+ The NetBSD driver "msc.c" does it this way. */
+ stat->Command = ( (stat->Command & ~A2232CMD_CMask) |
+ A2232CMD_Close );
+ stat->OutFlush = -1;
+ stat->Setup = -1;
+ }
+
+ local_irq_restore(flags);
+
+ /* After analyzing control flow, I think a2232_shutdown_port
+ is actually the last call from the system when at application
+ level someone issues a "echo Hello >>/dev/ttyY0".
+ Therefore I think the MOD_DEC_USE_COUNT should be here and
+ not in "a2232_close()". See the comment in "sx.c", too.
+ If you run into problems, compile this driver into the
+ kernel instead of compiling it as a module. */
+}
+
+static int a2232_set_real_termios(void *ptr)
+{
+ unsigned int cflag, baud, chsize, stopb, parity, softflow;
+ int rate;
+ int a2232_param, a2232_cmd;
+ unsigned long flags;
+ unsigned int i;
+ struct a2232_port *port = ptr;
+ volatile struct a2232status *status;
+ volatile struct a2232memory *mem;
+
+ if (!port->gs.tty || !port->gs.tty->termios) return 0;
+
+ status = a2232stat(port->which_a2232, port->which_port_on_a2232);
+ mem = a2232mem(port->which_a2232);
+
+ a2232_param = a2232_cmd = 0;
+
+ // get baud rate
+ baud = port->gs.baud;
+ if (baud == 0) {
+ /* speed == 0 -> drop DTR, do nothing else */
+ local_irq_save(flags);
+ // Clear DTR (and RTS... mhhh).
+ status->Command = ( (status->Command & ~A2232CMD_CMask) |
+ A2232CMD_Close );
+ status->OutFlush = -1;
+ status->Setup = -1;
+
+ local_irq_restore(flags);
+ return 0;
+ }
+
+ rate = A2232_BAUD_TABLE_NOAVAIL;
+ for (i=0; i < A2232_BAUD_TABLE_NUM_RATES * 3; i += 3){
+ if (a2232_baud_table[i] == baud){
+ if (mem->Common.Crystal == A2232_TURBO) rate = a2232_baud_table[i+2];
+ else rate = a2232_baud_table[i+1];
+ }
+ }
+ if (rate == A2232_BAUD_TABLE_NOAVAIL){
+ printk("a2232: Board %d Port %d unsupported baud rate: %d baud. Using another.\n",port->which_a2232,port->which_port_on_a2232,baud);
+ // This is useful for both (turbo or normal) Crystal versions.
+ rate = A2232PARAM_B9600;
+ }
+ a2232_param |= rate;
+
+ cflag = port->gs.tty->termios->c_cflag;
+
+ // get character size
+ chsize = cflag & CSIZE;
+ switch (chsize){
+ case CS8: a2232_param |= A2232PARAM_8Bit; break;
+ case CS7: a2232_param |= A2232PARAM_7Bit; break;
+ case CS6: a2232_param |= A2232PARAM_6Bit; break;
+ case CS5: a2232_param |= A2232PARAM_5Bit; break;
+ default: printk("a2232: Board %d Port %d unsupported character size: %d. Using 8 data bits.\n",
+ port->which_a2232,port->which_port_on_a2232,chsize);
+ a2232_param |= A2232PARAM_8Bit; break;
+ }
+
+ // get number of stop bits
+ stopb = cflag & CSTOPB;
+ if (stopb){ // two stop bits instead of one
+ printk("a2232: Board %d Port %d 2 stop bits unsupported. Using 1 stop bit.\n",
+ port->which_a2232,port->which_port_on_a2232);
+ }
+
+ // Warn if RTS/CTS not wanted
+ if (!(cflag & CRTSCTS)){
+#ifndef A2232_SUPPRESS_RTSCTS_WARNING
+ printk("a2232: Board %d Port %d cannot switch off firmware-implemented RTS/CTS hardware flow control.\n",
+ port->which_a2232,port->which_port_on_a2232);
+#endif
+ }
+
+ /* I think this is correct.
+ However, IXOFF means _input_ flow control and I wonder
+ if one should care about IXON _output_ flow control,
+ too. If this makes problems, one should turn the A2232
+ firmware XON/XOFF "SoftFlow" flow control off and use
+ the conventional way of inserting START/STOP characters
+ by hand in throttle()/unthrottle().
+ */
+ softflow = !!( port->gs.tty->termios->c_iflag & IXOFF );
+
+ // get Parity (Enabled/Disabled? If Enabled, Odd or Even?)
+ parity = cflag & (PARENB | PARODD);
+ if (parity & PARENB){
+ if (parity & PARODD){
+ a2232_cmd |= A2232CMD_OddParity;
+ }
+ else{
+ a2232_cmd |= A2232CMD_EvenParity;
+ }
+ }
+ else a2232_cmd |= A2232CMD_NoParity;
+
+
+ /* Hmm. Maybe an own a2232_port structure
+ member would be cleaner? */
+ if (cflag & CLOCAL)
+ port->gs.flags &= ~ASYNC_CHECK_CD;
+ else
+ port->gs.flags |= ASYNC_CHECK_CD;
+
+
+ /* Now we have all parameters and can go to set them: */
+ local_irq_save(flags);
+
+ status->Param = a2232_param | A2232PARAM_RcvBaud;
+ status->Command = a2232_cmd | A2232CMD_Open | A2232CMD_Enable;
+ status->SoftFlow = softflow;
+ status->OutDisable = 0;
+ status->Setup = -1;
+
+ local_irq_restore(flags);
+ return 0;
+}
+
+static int a2232_chars_in_buffer(void *ptr)
+{
+ struct a2232_port *port;
+ volatile struct a2232status *status;
+ unsigned char ret; /* we need modulo-256 arithmetics */
+ port = ptr;
+ status = a2232stat(port->which_a2232, port->which_port_on_a2232);
+#if A2232_IOBUFLEN != 256
+#error "Re-Implement a2232_chars_in_buffer()!"
+#endif
+ ret = (status->OutHead - status->OutTail);
+ return ret;
+}
+
+static void a2232_close(void *ptr)
+{
+ a2232_disable_tx_interrupts(ptr);
+ a2232_disable_rx_interrupts(ptr);
+ /* see the comment in a2232_shutdown_port above. */
+}
+
+static void a2232_hungup(void *ptr)
+{
+ a2232_close(ptr);
+}
+/*** END OF REAL_DRIVER FUNCTIONS ***/
+
+/*** BEGIN FUNCTIONS EXPECTED BY TTY DRIVER STRUCTS ***/
+static int a2232_ioctl( struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return -ENOIOCTLCMD;
+}
+
+static void a2232_throttle(struct tty_struct *tty)
+{
+/* Throttle: System cannot take another chars: Drop RTS or
+ send the STOP char or whatever.
+ The A2232 firmware does RTS/CTS anyway, and XON/XOFF
+ if switched on. So the only thing we can do at this
+ layer here is not taking any characters out of the
+ A2232 buffer any more. */
+ struct a2232_port *port = (struct a2232_port *) tty->driver_data;
+ port->throttle_input = -1;
+}
+
+static void a2232_unthrottle(struct tty_struct *tty)
+{
+/* Unthrottle: dual to "throttle()" above. */
+ struct a2232_port *port = (struct a2232_port *) tty->driver_data;
+ port->throttle_input = 0;
+}
+
+static int a2232_open(struct tty_struct * tty, struct file * filp)
+{
+/* More or less stolen from other drivers. */
+ int line;
+ int retval;
+ struct a2232_port *port;
+
+ line = tty->index;
+ port = &a2232_ports[line];
+
+ tty->driver_data = port;
+ port->gs.tty = tty;
+ port->gs.count++;
+ retval = gs_init_port(&port->gs);
+ if (retval) {
+ port->gs.count--;
+ return retval;
+ }
+ port->gs.flags |= GS_ACTIVE;
+ retval = gs_block_til_ready(port, filp);
+
+ if (retval) {
+ port->gs.count--;
+ return retval;
+ }
+
+ a2232_enable_rx_interrupts(port);
+
+ return 0;
+}
+/*** END OF FUNCTIONS EXPECTED BY TTY DRIVER STRUCTS ***/
+
+static irqreturn_t a2232_vbl_inter(int irq, void *data, struct pt_regs *fp)
+{
+#if A2232_IOBUFLEN != 256
+#error "Re-Implement a2232_vbl_inter()!"
+#endif
+
+struct a2232_port *port;
+volatile struct a2232memory *mem;
+volatile struct a2232status *status;
+unsigned char newhead;
+unsigned char bufpos; /* Must be unsigned char. We need the modulo-256 arithmetics */
+unsigned char ncd, ocd, ccd; /* names consistent with the NetBSD driver */
+volatile u_char *ibuf, *cbuf, *obuf;
+int ch, err, n, p;
+ for (n = 0; n < nr_a2232; n++){ /* for every completely initialized A2232 board */
+ mem = a2232mem(n);
+ for (p = 0; p < NUMLINES; p++){ /* for every port on this board */
+ err = 0;
+ port = &a2232_ports[n*NUMLINES+p];
+ if ( port->gs.flags & GS_ACTIVE ){ /* if the port is used */
+
+ status = a2232stat(n,p);
+
+ if (!port->disable_rx && !port->throttle_input){ /* If input is not disabled */
+ newhead = status->InHead; /* 65EC02 write pointer */
+ bufpos = status->InTail;
+
+ /* check for input for this port */
+ if (newhead != bufpos) {
+ /* buffer for input chars/events */
+ ibuf = mem->InBuf[p];
+
+ /* data types of bytes in ibuf */
+ cbuf = mem->InCtl[p];
+
+ /* do for all chars */
+ while (bufpos != newhead) {
+ /* which type of input data? */
+ switch (cbuf[bufpos]) {
+ /* switch on input event (CD, BREAK, etc.) */
+ case A2232INCTL_EVENT:
+ switch (ibuf[bufpos++]) {
+ case A2232EVENT_Break:
+ /* TODO: Handle BREAK signal */
+ break;
+ /* A2232EVENT_CarrierOn and A2232EVENT_CarrierOff are
+ handled in a separate queue and should not occur here. */
+ case A2232EVENT_Sync:
+ printk("A2232: 65EC02 software sent SYNC event, don't know what to do. Ignoring.");
+ break;
+ default:
+ printk("A2232: 65EC02 software broken, unknown event type %d occurred.\n",ibuf[bufpos-1]);
+ } /* event type switch */
+ break;
+ case A2232INCTL_CHAR:
+ /* Receive incoming char */
+ a2232_receive_char(port, ibuf[bufpos], err);
+ bufpos++;
+ break;
+ default:
+ printk("A2232: 65EC02 software broken, unknown data type %d occurred.\n",cbuf[bufpos]);
+ bufpos++;
+ } /* switch on input data type */
+ } /* while there's something in the buffer */
+
+ status->InTail = bufpos; /* tell 65EC02 what we've read */
+
+ } /* if there was something in the buffer */
+ } /* If input is not disabled */
+
+ /* Now check if there's something to output */
+ obuf = mem->OutBuf[p];
+ bufpos = status->OutHead;
+ while ( (port->gs.xmit_cnt > 0) &&
+ (!port->gs.tty->stopped) &&
+ (!port->gs.tty->hw_stopped) ){ /* While there are chars to transmit */
+ if (((bufpos+1) & A2232_IOBUFLENMASK) != status->OutTail) { /* If the A2232 buffer is not full */
+ ch = port->gs.xmit_buf[port->gs.xmit_tail]; /* get the next char to transmit */
+ port->gs.xmit_tail = (port->gs.xmit_tail+1) & (SERIAL_XMIT_SIZE-1); /* modulo-addition for the gs.xmit_buf ring-buffer */
+ obuf[bufpos++] = ch; /* put it into the A2232 buffer */
+ port->gs.xmit_cnt--;
+ }
+ else{ /* If A2232 the buffer is full */
+ break; /* simply stop filling it. */
+ }
+ }
+ status->OutHead = bufpos;
+
+ /* WakeUp if output buffer runs low */
+ if ((port->gs.xmit_cnt <= port->gs.wakeup_chars) && port->gs.tty) {
+ tty_wakeup(port->gs.tty);
+ }
+ } // if the port is used
+ } // for every port on the board
+
+ /* Now check the CD message queue */
+ newhead = mem->Common.CDHead;
+ bufpos = mem->Common.CDTail;
+ if (newhead != bufpos){ /* There are CD events in queue */
+ ocd = mem->Common.CDStatus; /* get old status bits */
+ while (newhead != bufpos){ /* read all events */
+ ncd = mem->CDBuf[bufpos++]; /* get one event */
+ ccd = ncd ^ ocd; /* mask of changed lines */
+ ocd = ncd; /* save new status bits */
+ for(p=0; p < NUMLINES; p++){ /* for all ports */
+ if (ccd & 1){ /* this one changed */
+
+ struct a2232_port *port = &a2232_ports[n*7+p];
+ port->cd_status = !(ncd & 1); /* ncd&1 <=> CD is now off */
+
+ if (!(port->gs.flags & ASYNC_CHECK_CD))
+ ; /* Don't report DCD changes */
+ else if (port->cd_status) { // if DCD on: DCD went UP!
+
+ /* Are we blocking in open?*/
+ wake_up_interruptible(&port->gs.open_wait);
+ }
+ else { // if DCD off: DCD went DOWN!
+ if (port->gs.tty)
+ tty_hangup (port->gs.tty);
+ }
+
+ } // if CD changed for this port
+ ccd >>= 1;
+ ncd >>= 1; /* Shift bits for next line */
+ } // for every port
+ } // while CD events in queue
+ mem->Common.CDStatus = ocd; /* save new status */
+ mem->Common.CDTail = bufpos; /* remove events */
+ } // if events in CD queue
+
+ } // for every completely initialized A2232 board
+ return IRQ_HANDLED;
+}
+
+static void a2232_init_portstructs(void)
+{
+ struct a2232_port *port;
+ int i;
+
+ for (i = 0; i < MAX_A2232_BOARDS*NUMLINES; i++) {
+ port = a2232_ports + i;
+ port->which_a2232 = i/NUMLINES;
+ port->which_port_on_a2232 = i%NUMLINES;
+ port->disable_rx = port->throttle_input = port->cd_status = 0;
+ port->gs.magic = A2232_MAGIC;
+ port->gs.close_delay = HZ/2;
+ port->gs.closing_wait = 30 * HZ;
+ port->gs.rd = &a2232_real_driver;
+#ifdef NEW_WRITE_LOCKING
+ init_MUTEX(&(port->gs.port_write_sem));
+#endif
+ init_waitqueue_head(&port->gs.open_wait);
+ init_waitqueue_head(&port->gs.close_wait);
+ }
+}
+
+static struct tty_operations a2232_ops = {
+ .open = a2232_open,
+ .close = gs_close,
+ .write = gs_write,
+ .put_char = gs_put_char,
+ .flush_chars = gs_flush_chars,
+ .write_room = gs_write_room,
+ .chars_in_buffer = gs_chars_in_buffer,
+ .flush_buffer = gs_flush_buffer,
+ .ioctl = a2232_ioctl,
+ .throttle = a2232_throttle,
+ .unthrottle = a2232_unthrottle,
+ .set_termios = gs_set_termios,
+ .stop = gs_stop,
+ .start = gs_start,
+ .hangup = gs_hangup,
+};
+
+static int a2232_init_drivers(void)
+{
+ int error;
+
+ a2232_driver = alloc_tty_driver(NUMLINES * nr_a2232);
+ if (!a2232_driver)
+ return -ENOMEM;
+ a2232_driver->owner = THIS_MODULE;
+ a2232_driver->driver_name = "commodore_a2232";
+ a2232_driver->name = "ttyY";
+ a2232_driver->major = A2232_NORMAL_MAJOR;
+ a2232_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ a2232_driver->subtype = SERIAL_TYPE_NORMAL;
+ a2232_driver->init_termios = tty_std_termios;
+ a2232_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ a2232_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(a2232_driver, &a2232_ops);
+ if ((error = tty_register_driver(a2232_driver))) {
+ printk(KERN_ERR "A2232: Couldn't register A2232 driver, error = %d\n",
+ error);
+ put_tty_driver(a2232_driver);
+ return 1;
+ }
+ return 0;
+}
+
+static int __init a2232board_init(void)
+{
+ struct zorro_dev *z;
+
+ unsigned int boardaddr;
+ int bcount;
+ short start;
+ u_char *from;
+ volatile u_char *to;
+ volatile struct a2232memory *mem;
+
+#ifdef CONFIG_SMP
+ return -ENODEV; /* This driver is not SMP aware. Is there an SMP ZorroII-bus-machine? */
+#endif
+
+ if (!MACH_IS_AMIGA){
+ return -ENODEV;
+ }
+
+ printk("Commodore A2232 driver initializing.\n"); /* Say that we're alive. */
+
+ z = NULL;
+ nr_a2232 = 0;
+ while ( (z = zorro_find_device(ZORRO_WILDCARD, z)) ){
+ if ( (z->id != ZORRO_PROD_CBM_A2232_PROTOTYPE) &&
+ (z->id != ZORRO_PROD_CBM_A2232) ){
+ continue; // The board found was no A2232
+ }
+ if (!zorro_request_device(z,"A2232 driver"))
+ continue;
+
+ printk("Commodore A2232 found (#%d).\n",nr_a2232);
+
+ zd_a2232[nr_a2232] = z;
+
+ boardaddr = ZTWO_VADDR( z->resource.start );
+ printk("Board is located at address 0x%x, size is 0x%x.\n", boardaddr, (unsigned int) ((z->resource.end+1) - (z->resource.start)));
+
+ mem = (volatile struct a2232memory *) boardaddr;
+
+ (void) mem->Enable6502Reset; /* copy the code across to the board */
+ to = (u_char *)mem; from = a2232_65EC02code; bcount = sizeof(a2232_65EC02code) - 2;
+ start = *(short *)from;
+ from += sizeof(start);
+ to += start;
+ while(bcount--) *to++ = *from++;
+ printk("65EC02 software uploaded to the A2232 memory.\n");
+
+ mem->Common.Crystal = A2232_UNKNOWN; /* use automatic speed check */
+
+ /* start 6502 running */
+ (void) mem->ResetBoard;
+ printk("A2232's 65EC02 CPU up and running.\n");
+
+ /* wait until speed detector has finished */
+ for (bcount = 0; bcount < 2000; bcount++) {
+ udelay(1000);
+ if (mem->Common.Crystal)
+ break;
+ }
+ printk((mem->Common.Crystal?"A2232 oscillator crystal detected by 65EC02 software: ":"65EC02 software could not determine A2232 oscillator crystal: "));
+ switch (mem->Common.Crystal){
+ case A2232_UNKNOWN:
+ printk("Unknown crystal.\n");
+ break;
+ case A2232_NORMAL:
+ printk ("Normal crystal.\n");
+ break;
+ case A2232_TURBO:
+ printk ("Turbo crystal.\n");
+ break;
+ default:
+ printk ("0x%x. Huh?\n",mem->Common.Crystal);
+ }
+
+ nr_a2232++;
+
+ }
+
+ printk("Total: %d A2232 boards initialized.\n.", nr_a2232); /* Some status report if no card was found */
+
+ a2232_init_portstructs();
+
+ /*
+ a2232_init_drivers also registers the drivers. Must be here because all boards
+ have to be detected first.
+ */
+ if (a2232_init_drivers()) return -ENODEV; // maybe we should use a different -Exxx?
+
+ request_irq(IRQ_AMIGA_VERTB, a2232_vbl_inter, 0, "A2232 serial VBL", a2232_driver_ID);
+ return 0;
+}
+
+static void __exit a2232board_exit(void)
+{
+ int i;
+
+ for (i = 0; i < nr_a2232; i++) {
+ zorro_release_device(zd_a2232[i]);
+ }
+
+ tty_unregister_driver(a2232_driver);
+ put_tty_driver(a2232_driver);
+ free_irq(IRQ_AMIGA_VERTB, a2232_driver_ID);
+}
+
+module_init(a2232board_init);
+module_exit(a2232board_exit);
+
+MODULE_AUTHOR("Enver Haase");
+MODULE_DESCRIPTION("Amiga A2232 multi-serial board driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ser_a2232.h b/drivers/char/ser_a2232.h
new file mode 100644
index 000000000000..bc09eb9e118b
--- /dev/null
+++ b/drivers/char/ser_a2232.h
@@ -0,0 +1,202 @@
+/* drivers/char/ser_a2232.h */
+
+/* $Id: ser_a2232.h,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/* Linux serial driver for the Amiga A2232 board */
+
+/* This driver is MAINTAINED. Before applying any changes, please contact
+ * the author.
+ */
+
+/* Copyright (c) 2000-2001 Enver Haase <ehaase@inf.fu-berlin.de>
+ * alias The A2232 driver project <A2232@gmx.net>
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef _SER_A2232_H_
+#define _SER_A2232_H_
+
+/*
+ How many boards are to be supported at maximum;
+ "up to five A2232 Multiport Serial Cards may be installed in a
+ single Amiga 2000" states the A2232 User's Guide. If you have
+ more slots available, you might want to change the value below.
+*/
+#define MAX_A2232_BOARDS 5
+
+#ifndef A2232_NORMAL_MAJOR
+/* This allows overriding on the compiler commandline, or in a "major.h"
+ include or something like that */
+#define A2232_NORMAL_MAJOR 224 /* /dev/ttyY* */
+#define A2232_CALLOUT_MAJOR 225 /* /dev/cuy* */
+#endif
+
+/* Some magic is always good - Who knows :) */
+#define A2232_MAGIC 0x000a2232
+
+/* A2232 port structure to keep track of the
+ status of every single line used */
+struct a2232_port{
+ struct gs_port gs;
+ unsigned int which_a2232;
+ unsigned int which_port_on_a2232;
+ short disable_rx;
+ short throttle_input;
+ short cd_status;
+};
+
+#define NUMLINES 7 /* number of lines per board */
+#define A2232_IOBUFLEN 256 /* number of bytes per buffer */
+#define A2232_IOBUFLENMASK 0xff /* mask for maximum number of bytes */
+
+
+#define A2232_UNKNOWN 0 /* crystal not known */
+#define A2232_NORMAL 1 /* normal A2232 (1.8432 MHz oscillator) */
+#define A2232_TURBO 2 /* turbo A2232 (3.6864 MHz oscillator) */
+
+
+struct a2232common {
+ char Crystal; /* normal (1) or turbo (2) board? */
+ u_char Pad_a;
+ u_char TimerH; /* timer value after speed check */
+ u_char TimerL;
+ u_char CDHead; /* head pointer for CD message queue */
+ u_char CDTail; /* tail pointer for CD message queue */
+ u_char CDStatus;
+ u_char Pad_b;
+};
+
+struct a2232status {
+ u_char InHead; /* input queue head */
+ u_char InTail; /* input queue tail */
+ u_char OutDisable; /* disables output */
+ u_char OutHead; /* output queue head */
+ u_char OutTail; /* output queue tail */
+ u_char OutCtrl; /* soft flow control character to send */
+ u_char OutFlush; /* flushes output buffer */
+ u_char Setup; /* causes reconfiguration */
+ u_char Param; /* parameter byte - see A2232PARAM */
+ u_char Command; /* command byte - see A2232CMD */
+ u_char SoftFlow; /* enables xon/xoff flow control */
+ /* private 65EC02 fields: */
+ u_char XonOff; /* stores XON/XOFF enable/disable */
+};
+
+#define A2232_MEMPAD1 \
+ (0x0200 - NUMLINES * sizeof(struct a2232status) - \
+ sizeof(struct a2232common))
+#define A2232_MEMPAD2 (0x2000 - NUMLINES * A2232_IOBUFLEN - A2232_IOBUFLEN)
+
+struct a2232memory {
+ struct a2232status Status[NUMLINES]; /* 0x0000-0x006f status areas */
+ struct a2232common Common; /* 0x0070-0x0077 common flags */
+ u_char Dummy1[A2232_MEMPAD1]; /* 0x00XX-0x01ff */
+ u_char OutBuf[NUMLINES][A2232_IOBUFLEN];/* 0x0200-0x08ff output bufs */
+ u_char InBuf[NUMLINES][A2232_IOBUFLEN]; /* 0x0900-0x0fff input bufs */
+ u_char InCtl[NUMLINES][A2232_IOBUFLEN]; /* 0x1000-0x16ff control data */
+ u_char CDBuf[A2232_IOBUFLEN]; /* 0x1700-0x17ff CD event buffer */
+ u_char Dummy2[A2232_MEMPAD2]; /* 0x1800-0x2fff */
+ u_char Code[0x1000]; /* 0x3000-0x3fff code area */
+ u_short InterruptAck; /* 0x4000 intr ack */
+ u_char Dummy3[0x3ffe]; /* 0x4002-0x7fff */
+ u_short Enable6502Reset; /* 0x8000 Stop board, */
+ /* 6502 RESET line held low */
+ u_char Dummy4[0x3ffe]; /* 0x8002-0xbfff */
+ u_short ResetBoard; /* 0xc000 reset board & run, */
+ /* 6502 RESET line held high */
+};
+
+#undef A2232_MEMPAD1
+#undef A2232_MEMPAD2
+
+#define A2232INCTL_CHAR 0 /* corresponding byte in InBuf is a character */
+#define A2232INCTL_EVENT 1 /* corresponding byte in InBuf is an event */
+
+#define A2232EVENT_Break 1 /* break set */
+#define A2232EVENT_CarrierOn 2 /* carrier raised */
+#define A2232EVENT_CarrierOff 3 /* carrier dropped */
+#define A2232EVENT_Sync 4 /* don't know, defined in 2232.ax */
+
+#define A2232CMD_Enable 0x1 /* enable/DTR bit */
+#define A2232CMD_Close 0x2 /* close the device */
+#define A2232CMD_Open 0xb /* open the device */
+#define A2232CMD_CMask 0xf /* command mask */
+#define A2232CMD_RTSOff 0x0 /* turn off RTS */
+#define A2232CMD_RTSOn 0x8 /* turn on RTS */
+#define A2232CMD_Break 0xd /* transmit a break */
+#define A2232CMD_RTSMask 0xc /* mask for RTS stuff */
+#define A2232CMD_NoParity 0x00 /* don't use parity */
+#define A2232CMD_OddParity 0x20 /* odd parity */
+#define A2232CMD_EvenParity 0x60 /* even parity */
+#define A2232CMD_ParityMask 0xe0 /* parity mask */
+
+#define A2232PARAM_B115200 0x0 /* baud rates */
+#define A2232PARAM_B50 0x1
+#define A2232PARAM_B75 0x2
+#define A2232PARAM_B110 0x3
+#define A2232PARAM_B134 0x4
+#define A2232PARAM_B150 0x5
+#define A2232PARAM_B300 0x6
+#define A2232PARAM_B600 0x7
+#define A2232PARAM_B1200 0x8
+#define A2232PARAM_B1800 0x9
+#define A2232PARAM_B2400 0xa
+#define A2232PARAM_B3600 0xb
+#define A2232PARAM_B4800 0xc
+#define A2232PARAM_B7200 0xd
+#define A2232PARAM_B9600 0xe
+#define A2232PARAM_B19200 0xf
+#define A2232PARAM_BaudMask 0xf /* baud rate mask */
+#define A2232PARAM_RcvBaud 0x10 /* enable receive baud rate */
+#define A2232PARAM_8Bit 0x00 /* numbers of bits */
+#define A2232PARAM_7Bit 0x20
+#define A2232PARAM_6Bit 0x40
+#define A2232PARAM_5Bit 0x60
+#define A2232PARAM_BitMask 0x60 /* numbers of bits mask */
+
+
+/* Standard speeds tables, -1 means unavailable, -2 means 0 baud: switch off line */
+#define A2232_BAUD_TABLE_NOAVAIL -1
+#define A2232_BAUD_TABLE_NUM_RATES (18)
+static int a2232_baud_table[A2232_BAUD_TABLE_NUM_RATES*3] = {
+ //Baud //Normal //Turbo
+ 50, A2232PARAM_B50, A2232_BAUD_TABLE_NOAVAIL,
+ 75, A2232PARAM_B75, A2232_BAUD_TABLE_NOAVAIL,
+ 110, A2232PARAM_B110, A2232_BAUD_TABLE_NOAVAIL,
+ 134, A2232PARAM_B134, A2232_BAUD_TABLE_NOAVAIL,
+ 150, A2232PARAM_B150, A2232PARAM_B75,
+ 200, A2232_BAUD_TABLE_NOAVAIL, A2232_BAUD_TABLE_NOAVAIL,
+ 300, A2232PARAM_B300, A2232PARAM_B150,
+ 600, A2232PARAM_B600, A2232PARAM_B300,
+ 1200, A2232PARAM_B1200, A2232PARAM_B600,
+ 1800, A2232PARAM_B1800, A2232_BAUD_TABLE_NOAVAIL,
+ 2400, A2232PARAM_B2400, A2232PARAM_B1200,
+ 4800, A2232PARAM_B4800, A2232PARAM_B2400,
+ 9600, A2232PARAM_B9600, A2232PARAM_B4800,
+ 19200, A2232PARAM_B19200, A2232PARAM_B9600,
+ 38400, A2232_BAUD_TABLE_NOAVAIL, A2232PARAM_B19200,
+ 57600, A2232_BAUD_TABLE_NOAVAIL, A2232_BAUD_TABLE_NOAVAIL,
+#ifdef A2232_SPEEDHACK
+ 115200, A2232PARAM_B115200, A2232_BAUD_TABLE_NOAVAIL,
+ 230400, A2232_BAUD_TABLE_NOAVAIL, A2232PARAM_B115200
+#else
+ 115200, A2232_BAUD_TABLE_NOAVAIL, A2232_BAUD_TABLE_NOAVAIL,
+ 230400, A2232_BAUD_TABLE_NOAVAIL, A2232_BAUD_TABLE_NOAVAIL
+#endif
+};
+#endif
diff --git a/drivers/char/ser_a2232fw.ax b/drivers/char/ser_a2232fw.ax
new file mode 100644
index 000000000000..736438032768
--- /dev/null
+++ b/drivers/char/ser_a2232fw.ax
@@ -0,0 +1,529 @@
+;.lib "axm"
+;
+;begin
+;title "A2232 serial board driver"
+;
+;set modules "2232"
+;set executable "2232.bin"
+;
+;;;;set nolink
+;
+;set temporary directory "t:"
+;
+;set assembly options "-m6502 -l60:t:list"
+;set link options "bin"; loadadr"
+;;;bin2c 2232.bin msc6502.h msc6502code
+;end
+;
+;
+; ### Commodore A2232 serial board driver for NetBSD by JM v1.3 ###
+;
+; - Created 950501 by JM -
+;
+;
+; Serial board driver software.
+;
+;
+% Copyright (c) 1995 Jukka Marin <jmarin@jmp.fi>.
+% All rights reserved.
+%
+% Redistribution and use in source and binary forms, with or without
+% modification, are permitted provided that the following conditions
+% are met:
+% 1. Redistributions of source code must retain the above copyright
+% notice, and the entire permission notice in its entirety,
+% including the disclaimer of warranties.
+% 2. Redistributions in binary form must reproduce the above copyright
+% notice, this list of conditions and the following disclaimer in the
+% documentation and/or other materials provided with the distribution.
+% 3. The name of the author may not be used to endorse or promote
+% products derived from this software without specific prior
+% written permission.
+%
+% ALTERNATIVELY, this product may be distributed under the terms of
+% the GNU General Public License, in which case the provisions of the
+% GPL are required INSTEAD OF the above restrictions. (This clause is
+% necessary due to a potential bad interaction between the GPL and
+% the restrictions contained in a BSD-style copyright.)
+%
+% THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+% DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+% INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+% STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+% OF THE POSSIBILITY OF SUCH DAMAGE.
+;
+;
+; Bugs:
+;
+; - Can't send a break yet
+;
+;
+;
+; Edited:
+;
+; - 950501 by JM -> v0.1 - Created this file.
+; - 951029 by JM -> v1.3 - Carrier Detect events now queued in a separate
+; queue.
+;
+;
+
+
+CODE equ $3800 ; start address for program code
+
+
+CTL_CHAR equ $00 ; byte in ibuf is a character
+CTL_EVENT equ $01 ; byte in ibuf is an event
+
+EVENT_BREAK equ $01
+EVENT_CDON equ $02
+EVENT_CDOFF equ $03
+EVENT_SYNC equ $04
+
+XON equ $11
+XOFF equ $13
+
+
+VARBASE macro *starting_address ; was VARINIT
+_varbase set \1
+ endm
+
+VARDEF macro *name space_needs
+\1 equ _varbase
+_varbase set _varbase+\2
+ endm
+
+
+stz macro * address
+ db $64,\1
+ endm
+
+stzax macro * address
+ db $9e,<\1,>\1
+ endm
+
+
+biti macro * immediate value
+ db $89,\1
+ endm
+
+smb0 macro * address
+ db $87,\1
+ endm
+smb1 macro * address
+ db $97,\1
+ endm
+smb2 macro * address
+ db $a7,\1
+ endm
+smb3 macro * address
+ db $b7,\1
+ endm
+smb4 macro * address
+ db $c7,\1
+ endm
+smb5 macro * address
+ db $d7,\1
+ endm
+smb6 macro * address
+ db $e7,\1
+ endm
+smb7 macro * address
+ db $f7,\1
+ endm
+
+
+
+;-----------------------------------------------------------------------;
+; ;
+; stuff common for all ports, non-critical (run once / loop) ;
+; ;
+DO_SLOW macro * port_number ;
+ .local ; ;
+ lda CIA+C_PA ; check all CD inputs ;
+ cmp CommonCDo ; changed from previous accptd? ;
+ beq =over ; nope, do nothing else here ;
+ ; ;
+ cmp CommonCDb ; bouncing? ;
+ beq =nobounce ; nope -> ;
+ ; ;
+ sta CommonCDb ; save current state ;
+ lda #64 ; reinitialize counter ;
+ sta CommonCDc ; ;
+ jmp =over ; skip CD save ;
+ ; ;
+=nobounce dec CommonCDc ; no, decrement bounce counter ;
+ bpl =over ; not done yet, so skip CD save ;
+ ; ;
+=saveCD ldx CDHead ; get write index ;
+ sta cdbuf,x ; save status in buffer ;
+ inx ; ;
+ cpx CDTail ; buffer full? ;
+ .if ne ; no: preserve status: ;
+ stx CDHead ; update index in RAM ;
+ sta CommonCDo ; save state for the next check ;
+ .end ; ;
+=over .end local ;
+ endm ;
+ ;
+;-----------------------------------------------------------------------;
+
+
+; port specific stuff (no data transfer)
+
+DO_PORT macro * port_number
+ .local ; ;
+ lda SetUp\1 ; reconfiguration request? ;
+ .if ne ; yes: ;
+ lda SoftFlow\1 ; get XON/XOFF flag ;
+ sta XonOff\1 ; save it ;
+ lda Param\1 ; get parameter ;
+ ora #%00010000 ; use baud generator for Rx ;
+ sta ACIA\1+A_CTRL ; store in control register ;
+ stz OutDisable\1 ; enable transmit output ;
+ stz SetUp\1 ; no reconfiguration no more ;
+ .end ; ;
+ ; ;
+ lda InHead\1 ; get write index ;
+ sbc InTail\1 ; buffer full soon? ;
+ cmp #200 ; 200 chars or more in buffer? ;
+ lda Command\1 ; get Command reg value ;
+ and #%11110011 ; turn RTS OFF by default ;
+ .if cc ; still room in buffer: ;
+ ora #%00001000 ; turn RTS ON ;
+ .end ; ;
+ sta ACIA\1+A_CMD ; set/clear RTS ;
+ ; ;
+ lda OutFlush\1 ; request to flush output buffer;
+ .if ne ; yessh! ;
+ lda OutHead\1 ; get head ;
+ sta OutTail\1 ; save as tail ;
+ stz OutDisable\1 ; enable transmit output ;
+ stz OutFlush\1 ; clear request ;
+ .end
+ .end local
+ endm
+
+
+DO_DATA macro * port number
+ .local
+ lda ACIA\1+A_SR ; read ACIA status register ;
+ biti [1<<3] ; something received? ;
+ .if ne ; yes: ;
+ biti [1<<1] ; framing error? ;
+ .if ne ; yes: ;
+ lda ACIA\1+A_DATA ; read received character ;
+ bne =SEND ; not break -> ignore it ;
+ ldx InHead\1 ; get write pointer ;
+ lda #CTL_EVENT ; get type of byte ;
+ sta ictl\1,x ; save it in InCtl buffer ;
+ lda #EVENT_BREAK ; event code ;
+ sta ibuf\1,x ; save it as well ;
+ inx ; ;
+ cpx InTail\1 ; still room in buffer? ;
+ .if ne ; absolutely: ;
+ stx InHead\1 ; update index in memory ;
+ .end ; ;
+ jmp =SEND ; go check if anything to send ;
+ .end ; ;
+ ; normal char received: ;
+ ldx InHead\1 ; get write index ;
+ lda ACIA\1+A_DATA ; read received character ;
+ sta ibuf\1,x ; save char in buffer ;
+ stzax ictl\1 ; set type to CTL_CHAR ;
+ inx ; ;
+ cpx InTail\1 ; buffer full? ;
+ .if ne ; no: preserve character: ;
+ stx InHead\1 ; update index in RAM ;
+ .end ; ;
+ and #$7f ; mask off parity if any ;
+ cmp #XOFF ; XOFF from remote host? ;
+ .if eq ; yes: ;
+ lda XonOff\1 ; if XON/XOFF handshaking.. ;
+ sta OutDisable\1 ; ..disable transmitter ;
+ .end ; ;
+ .end ; ;
+ ; ;
+ ; BUFFER FULL CHECK WAS HERE ;
+ ; ;
+=SEND lda ACIA\1+A_SR ; transmit register empty? ;
+ and #[1<<4] ; ;
+ .if ne ; yes: ;
+ ldx OutCtrl\1 ; sending out XON/XOFF? ;
+ .if ne ; yes: ;
+ lda CIA+C_PB ; check CTS signal ;
+ and #[1<<\1] ; (for this port only) ;
+ bne =DONE ; not allowed to send -> done ;
+ stx ACIA\1+A_DATA ; transmit control char ;
+ stz OutCtrl\1 ; clear flag ;
+ jmp =DONE ; and we're done ;
+ .end ; ;
+ ; ;
+ ldx OutTail\1 ; anything to transmit? ;
+ cpx OutHead\1 ; ;
+ .if ne ; yes: ;
+ lda OutDisable\1 ; allowed to transmit? ;
+ .if eq ; yes: ;
+ lda CIA+C_PB ; check CTS signal ;
+ and #[1<<\1] ; (for this port only) ;
+ bne =DONE ; not allowed to send -> done ;
+ lda obuf\1,x ; get a char from buffer ;
+ sta ACIA\1+A_DATA ; send it away ;
+ inc OutTail\1 ; update read index ;
+ .end ; ;
+ .end ; ;
+ .end ; ;
+=DONE .end local
+ endm
+
+
+
+PORTVAR macro * port number
+ VARDEF InHead\1 1
+ VARDEF InTail\1 1
+ VARDEF OutDisable\1 1
+ VARDEF OutHead\1 1
+ VARDEF OutTail\1 1
+ VARDEF OutCtrl\1 1
+ VARDEF OutFlush\1 1
+ VARDEF SetUp\1 1
+ VARDEF Param\1 1
+ VARDEF Command\1 1
+ VARDEF SoftFlow\1 1
+ ; private:
+ VARDEF XonOff\1 1
+ endm
+
+
+ VARBASE 0 ; start variables at address $0000
+ PORTVAR 0 ; define variables for port 0
+ PORTVAR 1 ; define variables for port 1
+ PORTVAR 2 ; define variables for port 2
+ PORTVAR 3 ; define variables for port 3
+ PORTVAR 4 ; define variables for port 4
+ PORTVAR 5 ; define variables for port 5
+ PORTVAR 6 ; define variables for port 6
+
+
+
+ VARDEF Crystal 1 ; 0 = unknown, 1 = normal, 2 = turbo
+ VARDEF Pad_a 1
+ VARDEF TimerH 1
+ VARDEF TimerL 1
+ VARDEF CDHead 1
+ VARDEF CDTail 1
+ VARDEF CDStatus 1
+ VARDEF Pad_b 1
+
+ VARDEF CommonCDo 1 ; for carrier detect optimization
+ VARDEF CommonCDc 1 ; for carrier detect debouncing
+ VARDEF CommonCDb 1 ; for carrier detect debouncing
+
+
+ VARBASE $0200
+ VARDEF obuf0 256 ; output data (characters only)
+ VARDEF obuf1 256
+ VARDEF obuf2 256
+ VARDEF obuf3 256
+ VARDEF obuf4 256
+ VARDEF obuf5 256
+ VARDEF obuf6 256
+
+ VARDEF ibuf0 256 ; input data (characters, events etc - see ictl)
+ VARDEF ibuf1 256
+ VARDEF ibuf2 256
+ VARDEF ibuf3 256
+ VARDEF ibuf4 256
+ VARDEF ibuf5 256
+ VARDEF ibuf6 256
+
+ VARDEF ictl0 256 ; input control information (type of data in ibuf)
+ VARDEF ictl1 256
+ VARDEF ictl2 256
+ VARDEF ictl3 256
+ VARDEF ictl4 256
+ VARDEF ictl5 256
+ VARDEF ictl6 256
+
+ VARDEF cdbuf 256 ; CD event queue
+
+
+ACIA0 equ $4400
+ACIA1 equ $4c00
+ACIA2 equ $5400
+ACIA3 equ $5c00
+ACIA4 equ $6400
+ACIA5 equ $6c00
+ACIA6 equ $7400
+
+A_DATA equ $00
+A_SR equ $02
+A_CMD equ $04
+A_CTRL equ $06
+; 00 write transmit data read received data
+; 02 reset ACIA read status register
+; 04 write command register read command register
+; 06 write control register read control register
+
+CIA equ $7c00 ; 8520 CIA
+C_PA equ $00 ; port A data register
+C_PB equ $02 ; port B data register
+C_DDRA equ $04 ; data direction register for port A
+C_DDRB equ $06 ; data direction register for port B
+C_TAL equ $08 ; timer A
+C_TAH equ $0a
+C_TBL equ $0c ; timer B
+C_TBH equ $0e
+C_TODL equ $10 ; TOD LSB
+C_TODM equ $12 ; TOD middle byte
+C_TODH equ $14 ; TOD MSB
+C_DATA equ $18 ; serial data register
+C_INTCTRL equ $1a ; interrupt control register
+C_CTRLA equ $1c ; control register A
+C_CTRLB equ $1e ; control register B
+
+
+
+
+
+ section main,code,CODE-2
+
+ db >CODE,<CODE
+
+;-----------------------------------------------------------------------;
+; here's the initialization code: ;
+; ;
+R_RESET ldx #$ff ;
+ txs ; initialize stack pointer ;
+ cld ; in case a 6502 is used... ;
+ ldx #0 ; ;
+ lda #0 ; ;
+ ldy #Crystal ; this many bytes to clear ;
+clr_loop sta 0,x ; clear zero page variables ;
+ inx ; ;
+ dey ; ;
+ bne clr_loop ; ;
+ ; ;
+ stz CommonCDo ; force CD test at boot ;
+ stz CommonCDb ; ;
+ stz CDHead ; clear queue ;
+ stz CDTail ; ;
+ ; ;
+ lda #0 ; ;
+ sta Pad_a ; ;
+ lda #170 ; test cmp ;
+ cmp #100 ; ;
+ .if cs ; ;
+ inc Pad_a ; C was set ;
+ .end ; ;
+ ;
+;-----------------------------------------------------------------------;
+; Speed check ;
+;-----------------------------------------------------------------------;
+ ;
+ lda Crystal ; speed already set? ;
+ beq DoSpeedy ; ;
+ jmp LOOP ; yes, skip speed test ;
+ ; ;
+DoSpeedy lda #%10011000 ; 8N1, 1200/2400 bps ;
+ sta ACIA0+A_CTRL ; ;
+ lda #%00001011 ; enable DTR ;
+ sta ACIA0+A_CMD ; ;
+ lda ACIA0+A_SR ; read status register ;
+ ; ;
+ lda #%10000000 ; disable all ints (unnecessary);
+ sta CIA+C_INTCTRL ; ;
+ lda #255 ; program the timer ;
+ sta CIA+C_TAL ; ;
+ sta CIA+C_TAH ; ;
+ ; ;
+ ldx #0 ; ;
+ stx ACIA0+A_DATA ; transmit a zero ;
+ nop ; ;
+ nop ; ;
+ lda ACIA0+A_SR ; read status ;
+ nop ; ;
+ nop ; ;
+ stx ACIA0+A_DATA ; transmit a zero ;
+Speedy1 lda ACIA0+A_SR ; read status ;
+ and #[1<<4] ; transmit data reg empty? ;
+ beq Speedy1 ; not yet, wait more ;
+ ; ;
+ lda #%00010001 ; load & start the timer ;
+ stx ACIA0+A_DATA ; transmit one more zero ;
+ sta CIA+C_CTRLA ; ;
+Speedy2 lda ACIA0+A_SR ; read status ;
+ and #[1<<4] ; transmit data reg empty? ;
+ beq Speedy2 ; not yet, wait more ;
+ stx CIA+C_CTRLA ; stop the timer ;
+ ; ;
+ lda CIA+C_TAL ; copy timer value for 68k ;
+ sta TimerL ; ;
+ lda CIA+C_TAH ; ;
+ sta TimerH ; ;
+ cmp #$d0 ; turbo or normal? ;
+ .if cs ; ;
+ lda #2 ; turbo! :-) ;
+ .else ; ;
+ lda #1 ; normal :-( ;
+ .end ; ;
+ sta Crystal ; ;
+ lda #0 ; ;
+ sta ACIA0+A_SR ; ;
+ sta ACIA0+A_CTRL ; reset UART ;
+ sta ACIA0+A_CMD ; ;
+ ;
+ jmp LOOP ;
+ ;
+; ;
+;-----------------------------------------------------------------------;
+; ;
+; The Real Thing: ;
+; ;
+LOOP DO_SLOW ; do non-critical things ;
+ jsr do_input ; check for received data
+ DO_PORT 0
+ jsr do_input
+ DO_PORT 1
+ jsr do_input
+ DO_PORT 2
+ jsr do_input
+ DO_PORT 3
+ jsr do_input
+ DO_PORT 4
+ jsr do_input
+ DO_PORT 5
+ jsr do_input
+ DO_PORT 6
+ jsr do_input
+ jmp LOOP
+
+
+do_input DO_DATA 0
+ DO_DATA 1
+ DO_DATA 2
+ DO_DATA 3
+ DO_DATA 4
+ DO_DATA 5
+ DO_DATA 6
+ rts
+
+
+;-----------------------------------------------------------------------;
+ section vectors,data,$3ffa
+ dw $d0d0
+ dw R_RESET
+ dw $c0ce
+;-----------------------------------------------------------------------;
+
+
+
+ end
+
+
+
diff --git a/drivers/char/ser_a2232fw.h b/drivers/char/ser_a2232fw.h
new file mode 100644
index 000000000000..e09a30acfe5c
--- /dev/null
+++ b/drivers/char/ser_a2232fw.h
@@ -0,0 +1,306 @@
+/* drivers/char/ser_a2232fw.h */
+
+/* $Id: ser_a2232fw.h,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/*
+ * Copyright (c) 1995 Jukka Marin <jmarin@jmp.fi>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions. (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/* This is the 65EC02 code by Jukka Marin that is executed by
+ the A2232's 65EC02 processor (base address: 0x3800)
+ Source file: ser_a2232fw.ax
+ Version: 1.3 (951029)
+ Known Bugs: Cannot send a break yet
+*/
+static unsigned char a2232_65EC02code[] = {
+ 0x38, 0x00, 0xA2, 0xFF, 0x9A, 0xD8, 0xA2, 0x00,
+ 0xA9, 0x00, 0xA0, 0x54, 0x95, 0x00, 0xE8, 0x88,
+ 0xD0, 0xFA, 0x64, 0x5C, 0x64, 0x5E, 0x64, 0x58,
+ 0x64, 0x59, 0xA9, 0x00, 0x85, 0x55, 0xA9, 0xAA,
+ 0xC9, 0x64, 0x90, 0x02, 0xE6, 0x55, 0xA5, 0x54,
+ 0xF0, 0x03, 0x4C, 0x92, 0x38, 0xA9, 0x98, 0x8D,
+ 0x06, 0x44, 0xA9, 0x0B, 0x8D, 0x04, 0x44, 0xAD,
+ 0x02, 0x44, 0xA9, 0x80, 0x8D, 0x1A, 0x7C, 0xA9,
+ 0xFF, 0x8D, 0x08, 0x7C, 0x8D, 0x0A, 0x7C, 0xA2,
+ 0x00, 0x8E, 0x00, 0x44, 0xEA, 0xEA, 0xAD, 0x02,
+ 0x44, 0xEA, 0xEA, 0x8E, 0x00, 0x44, 0xAD, 0x02,
+ 0x44, 0x29, 0x10, 0xF0, 0xF9, 0xA9, 0x11, 0x8E,
+ 0x00, 0x44, 0x8D, 0x1C, 0x7C, 0xAD, 0x02, 0x44,
+ 0x29, 0x10, 0xF0, 0xF9, 0x8E, 0x1C, 0x7C, 0xAD,
+ 0x08, 0x7C, 0x85, 0x57, 0xAD, 0x0A, 0x7C, 0x85,
+ 0x56, 0xC9, 0xD0, 0x90, 0x05, 0xA9, 0x02, 0x4C,
+ 0x82, 0x38, 0xA9, 0x01, 0x85, 0x54, 0xA9, 0x00,
+ 0x8D, 0x02, 0x44, 0x8D, 0x06, 0x44, 0x8D, 0x04,
+ 0x44, 0x4C, 0x92, 0x38, 0xAD, 0x00, 0x7C, 0xC5,
+ 0x5C, 0xF0, 0x1F, 0xC5, 0x5E, 0xF0, 0x09, 0x85,
+ 0x5E, 0xA9, 0x40, 0x85, 0x5D, 0x4C, 0xB8, 0x38,
+ 0xC6, 0x5D, 0x10, 0x0E, 0xA6, 0x58, 0x9D, 0x00,
+ 0x17, 0xE8, 0xE4, 0x59, 0xF0, 0x04, 0x86, 0x58,
+ 0x85, 0x5C, 0x20, 0x23, 0x3A, 0xA5, 0x07, 0xF0,
+ 0x0F, 0xA5, 0x0A, 0x85, 0x0B, 0xA5, 0x08, 0x09,
+ 0x10, 0x8D, 0x06, 0x44, 0x64, 0x02, 0x64, 0x07,
+ 0xA5, 0x00, 0xE5, 0x01, 0xC9, 0xC8, 0xA5, 0x09,
+ 0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04,
+ 0x44, 0xA5, 0x06, 0xF0, 0x08, 0xA5, 0x03, 0x85,
+ 0x04, 0x64, 0x02, 0x64, 0x06, 0x20, 0x23, 0x3A,
+ 0xA5, 0x13, 0xF0, 0x0F, 0xA5, 0x16, 0x85, 0x17,
+ 0xA5, 0x14, 0x09, 0x10, 0x8D, 0x06, 0x4C, 0x64,
+ 0x0E, 0x64, 0x13, 0xA5, 0x0C, 0xE5, 0x0D, 0xC9,
+ 0xC8, 0xA5, 0x15, 0x29, 0xF3, 0xB0, 0x02, 0x09,
+ 0x08, 0x8D, 0x04, 0x4C, 0xA5, 0x12, 0xF0, 0x08,
+ 0xA5, 0x0F, 0x85, 0x10, 0x64, 0x0E, 0x64, 0x12,
+ 0x20, 0x23, 0x3A, 0xA5, 0x1F, 0xF0, 0x0F, 0xA5,
+ 0x22, 0x85, 0x23, 0xA5, 0x20, 0x09, 0x10, 0x8D,
+ 0x06, 0x54, 0x64, 0x1A, 0x64, 0x1F, 0xA5, 0x18,
+ 0xE5, 0x19, 0xC9, 0xC8, 0xA5, 0x21, 0x29, 0xF3,
+ 0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04, 0x54, 0xA5,
+ 0x1E, 0xF0, 0x08, 0xA5, 0x1B, 0x85, 0x1C, 0x64,
+ 0x1A, 0x64, 0x1E, 0x20, 0x23, 0x3A, 0xA5, 0x2B,
+ 0xF0, 0x0F, 0xA5, 0x2E, 0x85, 0x2F, 0xA5, 0x2C,
+ 0x09, 0x10, 0x8D, 0x06, 0x5C, 0x64, 0x26, 0x64,
+ 0x2B, 0xA5, 0x24, 0xE5, 0x25, 0xC9, 0xC8, 0xA5,
+ 0x2D, 0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D,
+ 0x04, 0x5C, 0xA5, 0x2A, 0xF0, 0x08, 0xA5, 0x27,
+ 0x85, 0x28, 0x64, 0x26, 0x64, 0x2A, 0x20, 0x23,
+ 0x3A, 0xA5, 0x37, 0xF0, 0x0F, 0xA5, 0x3A, 0x85,
+ 0x3B, 0xA5, 0x38, 0x09, 0x10, 0x8D, 0x06, 0x64,
+ 0x64, 0x32, 0x64, 0x37, 0xA5, 0x30, 0xE5, 0x31,
+ 0xC9, 0xC8, 0xA5, 0x39, 0x29, 0xF3, 0xB0, 0x02,
+ 0x09, 0x08, 0x8D, 0x04, 0x64, 0xA5, 0x36, 0xF0,
+ 0x08, 0xA5, 0x33, 0x85, 0x34, 0x64, 0x32, 0x64,
+ 0x36, 0x20, 0x23, 0x3A, 0xA5, 0x43, 0xF0, 0x0F,
+ 0xA5, 0x46, 0x85, 0x47, 0xA5, 0x44, 0x09, 0x10,
+ 0x8D, 0x06, 0x6C, 0x64, 0x3E, 0x64, 0x43, 0xA5,
+ 0x3C, 0xE5, 0x3D, 0xC9, 0xC8, 0xA5, 0x45, 0x29,
+ 0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04, 0x6C,
+ 0xA5, 0x42, 0xF0, 0x08, 0xA5, 0x3F, 0x85, 0x40,
+ 0x64, 0x3E, 0x64, 0x42, 0x20, 0x23, 0x3A, 0xA5,
+ 0x4F, 0xF0, 0x0F, 0xA5, 0x52, 0x85, 0x53, 0xA5,
+ 0x50, 0x09, 0x10, 0x8D, 0x06, 0x74, 0x64, 0x4A,
+ 0x64, 0x4F, 0xA5, 0x48, 0xE5, 0x49, 0xC9, 0xC8,
+ 0xA5, 0x51, 0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08,
+ 0x8D, 0x04, 0x74, 0xA5, 0x4E, 0xF0, 0x08, 0xA5,
+ 0x4B, 0x85, 0x4C, 0x64, 0x4A, 0x64, 0x4E, 0x20,
+ 0x23, 0x3A, 0x4C, 0x92, 0x38, 0xAD, 0x02, 0x44,
+ 0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B,
+ 0xAD, 0x00, 0x44, 0xD0, 0x32, 0xA6, 0x00, 0xA9,
+ 0x01, 0x9D, 0x00, 0x10, 0xA9, 0x01, 0x9D, 0x00,
+ 0x09, 0xE8, 0xE4, 0x01, 0xF0, 0x02, 0x86, 0x00,
+ 0x4C, 0x65, 0x3A, 0xA6, 0x00, 0xAD, 0x00, 0x44,
+ 0x9D, 0x00, 0x09, 0x9E, 0x00, 0x10, 0xE8, 0xE4,
+ 0x01, 0xF0, 0x02, 0x86, 0x00, 0x29, 0x7F, 0xC9,
+ 0x13, 0xD0, 0x04, 0xA5, 0x0B, 0x85, 0x02, 0xAD,
+ 0x02, 0x44, 0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x05,
+ 0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x01, 0xD0,
+ 0x21, 0x8E, 0x00, 0x44, 0x64, 0x05, 0x4C, 0x98,
+ 0x3A, 0xA6, 0x04, 0xE4, 0x03, 0xF0, 0x13, 0xA5,
+ 0x02, 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x01,
+ 0xD0, 0x08, 0xBD, 0x00, 0x02, 0x8D, 0x00, 0x44,
+ 0xE6, 0x04, 0xAD, 0x02, 0x4C, 0x89, 0x08, 0xF0,
+ 0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00, 0x4C,
+ 0xD0, 0x32, 0xA6, 0x0C, 0xA9, 0x01, 0x9D, 0x00,
+ 0x11, 0xA9, 0x01, 0x9D, 0x00, 0x0A, 0xE8, 0xE4,
+ 0x0D, 0xF0, 0x02, 0x86, 0x0C, 0x4C, 0xDA, 0x3A,
+ 0xA6, 0x0C, 0xAD, 0x00, 0x4C, 0x9D, 0x00, 0x0A,
+ 0x9E, 0x00, 0x11, 0xE8, 0xE4, 0x0D, 0xF0, 0x02,
+ 0x86, 0x0C, 0x29, 0x7F, 0xC9, 0x13, 0xD0, 0x04,
+ 0xA5, 0x17, 0x85, 0x0E, 0xAD, 0x02, 0x4C, 0x29,
+ 0x10, 0xF0, 0x2C, 0xA6, 0x11, 0xF0, 0x0F, 0xAD,
+ 0x02, 0x7C, 0x29, 0x02, 0xD0, 0x21, 0x8E, 0x00,
+ 0x4C, 0x64, 0x11, 0x4C, 0x0D, 0x3B, 0xA6, 0x10,
+ 0xE4, 0x0F, 0xF0, 0x13, 0xA5, 0x0E, 0xD0, 0x0F,
+ 0xAD, 0x02, 0x7C, 0x29, 0x02, 0xD0, 0x08, 0xBD,
+ 0x00, 0x03, 0x8D, 0x00, 0x4C, 0xE6, 0x10, 0xAD,
+ 0x02, 0x54, 0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02,
+ 0xF0, 0x1B, 0xAD, 0x00, 0x54, 0xD0, 0x32, 0xA6,
+ 0x18, 0xA9, 0x01, 0x9D, 0x00, 0x12, 0xA9, 0x01,
+ 0x9D, 0x00, 0x0B, 0xE8, 0xE4, 0x19, 0xF0, 0x02,
+ 0x86, 0x18, 0x4C, 0x4F, 0x3B, 0xA6, 0x18, 0xAD,
+ 0x00, 0x54, 0x9D, 0x00, 0x0B, 0x9E, 0x00, 0x12,
+ 0xE8, 0xE4, 0x19, 0xF0, 0x02, 0x86, 0x18, 0x29,
+ 0x7F, 0xC9, 0x13, 0xD0, 0x04, 0xA5, 0x23, 0x85,
+ 0x1A, 0xAD, 0x02, 0x54, 0x29, 0x10, 0xF0, 0x2C,
+ 0xA6, 0x1D, 0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29,
+ 0x04, 0xD0, 0x21, 0x8E, 0x00, 0x54, 0x64, 0x1D,
+ 0x4C, 0x82, 0x3B, 0xA6, 0x1C, 0xE4, 0x1B, 0xF0,
+ 0x13, 0xA5, 0x1A, 0xD0, 0x0F, 0xAD, 0x02, 0x7C,
+ 0x29, 0x04, 0xD0, 0x08, 0xBD, 0x00, 0x04, 0x8D,
+ 0x00, 0x54, 0xE6, 0x1C, 0xAD, 0x02, 0x5C, 0x89,
+ 0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD,
+ 0x00, 0x5C, 0xD0, 0x32, 0xA6, 0x24, 0xA9, 0x01,
+ 0x9D, 0x00, 0x13, 0xA9, 0x01, 0x9D, 0x00, 0x0C,
+ 0xE8, 0xE4, 0x25, 0xF0, 0x02, 0x86, 0x24, 0x4C,
+ 0xC4, 0x3B, 0xA6, 0x24, 0xAD, 0x00, 0x5C, 0x9D,
+ 0x00, 0x0C, 0x9E, 0x00, 0x13, 0xE8, 0xE4, 0x25,
+ 0xF0, 0x02, 0x86, 0x24, 0x29, 0x7F, 0xC9, 0x13,
+ 0xD0, 0x04, 0xA5, 0x2F, 0x85, 0x26, 0xAD, 0x02,
+ 0x5C, 0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x29, 0xF0,
+ 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x08, 0xD0, 0x21,
+ 0x8E, 0x00, 0x5C, 0x64, 0x29, 0x4C, 0xF7, 0x3B,
+ 0xA6, 0x28, 0xE4, 0x27, 0xF0, 0x13, 0xA5, 0x26,
+ 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x08, 0xD0,
+ 0x08, 0xBD, 0x00, 0x05, 0x8D, 0x00, 0x5C, 0xE6,
+ 0x28, 0xAD, 0x02, 0x64, 0x89, 0x08, 0xF0, 0x3B,
+ 0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00, 0x64, 0xD0,
+ 0x32, 0xA6, 0x30, 0xA9, 0x01, 0x9D, 0x00, 0x14,
+ 0xA9, 0x01, 0x9D, 0x00, 0x0D, 0xE8, 0xE4, 0x31,
+ 0xF0, 0x02, 0x86, 0x30, 0x4C, 0x39, 0x3C, 0xA6,
+ 0x30, 0xAD, 0x00, 0x64, 0x9D, 0x00, 0x0D, 0x9E,
+ 0x00, 0x14, 0xE8, 0xE4, 0x31, 0xF0, 0x02, 0x86,
+ 0x30, 0x29, 0x7F, 0xC9, 0x13, 0xD0, 0x04, 0xA5,
+ 0x3B, 0x85, 0x32, 0xAD, 0x02, 0x64, 0x29, 0x10,
+ 0xF0, 0x2C, 0xA6, 0x35, 0xF0, 0x0F, 0xAD, 0x02,
+ 0x7C, 0x29, 0x10, 0xD0, 0x21, 0x8E, 0x00, 0x64,
+ 0x64, 0x35, 0x4C, 0x6C, 0x3C, 0xA6, 0x34, 0xE4,
+ 0x33, 0xF0, 0x13, 0xA5, 0x32, 0xD0, 0x0F, 0xAD,
+ 0x02, 0x7C, 0x29, 0x10, 0xD0, 0x08, 0xBD, 0x00,
+ 0x06, 0x8D, 0x00, 0x64, 0xE6, 0x34, 0xAD, 0x02,
+ 0x6C, 0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0,
+ 0x1B, 0xAD, 0x00, 0x6C, 0xD0, 0x32, 0xA6, 0x3C,
+ 0xA9, 0x01, 0x9D, 0x00, 0x15, 0xA9, 0x01, 0x9D,
+ 0x00, 0x0E, 0xE8, 0xE4, 0x3D, 0xF0, 0x02, 0x86,
+ 0x3C, 0x4C, 0xAE, 0x3C, 0xA6, 0x3C, 0xAD, 0x00,
+ 0x6C, 0x9D, 0x00, 0x0E, 0x9E, 0x00, 0x15, 0xE8,
+ 0xE4, 0x3D, 0xF0, 0x02, 0x86, 0x3C, 0x29, 0x7F,
+ 0xC9, 0x13, 0xD0, 0x04, 0xA5, 0x47, 0x85, 0x3E,
+ 0xAD, 0x02, 0x6C, 0x29, 0x10, 0xF0, 0x2C, 0xA6,
+ 0x41, 0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x20,
+ 0xD0, 0x21, 0x8E, 0x00, 0x6C, 0x64, 0x41, 0x4C,
+ 0xE1, 0x3C, 0xA6, 0x40, 0xE4, 0x3F, 0xF0, 0x13,
+ 0xA5, 0x3E, 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29,
+ 0x20, 0xD0, 0x08, 0xBD, 0x00, 0x07, 0x8D, 0x00,
+ 0x6C, 0xE6, 0x40, 0xAD, 0x02, 0x74, 0x89, 0x08,
+ 0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00,
+ 0x74, 0xD0, 0x32, 0xA6, 0x48, 0xA9, 0x01, 0x9D,
+ 0x00, 0x16, 0xA9, 0x01, 0x9D, 0x00, 0x0F, 0xE8,
+ 0xE4, 0x49, 0xF0, 0x02, 0x86, 0x48, 0x4C, 0x23,
+ 0x3D, 0xA6, 0x48, 0xAD, 0x00, 0x74, 0x9D, 0x00,
+ 0x0F, 0x9E, 0x00, 0x16, 0xE8, 0xE4, 0x49, 0xF0,
+ 0x02, 0x86, 0x48, 0x29, 0x7F, 0xC9, 0x13, 0xD0,
+ 0x04, 0xA5, 0x53, 0x85, 0x4A, 0xAD, 0x02, 0x74,
+ 0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x4D, 0xF0, 0x0F,
+ 0xAD, 0x02, 0x7C, 0x29, 0x40, 0xD0, 0x21, 0x8E,
+ 0x00, 0x74, 0x64, 0x4D, 0x4C, 0x56, 0x3D, 0xA6,
+ 0x4C, 0xE4, 0x4B, 0xF0, 0x13, 0xA5, 0x4A, 0xD0,
+ 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x40, 0xD0, 0x08,
+ 0xBD, 0x00, 0x08, 0x8D, 0x00, 0x74, 0xE6, 0x4C,
+ 0x60, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xD0, 0x00, 0x38,
+ 0xCE, 0xC0,
+};
diff --git a/drivers/char/serial167.c b/drivers/char/serial167.c
new file mode 100644
index 000000000000..c2deac968bdd
--- /dev/null
+++ b/drivers/char/serial167.c
@@ -0,0 +1,2858 @@
+/*
+ * linux/drivers/char/serial167.c
+ *
+ * Driver for MVME166/7 board serial ports, which are via a CD2401.
+ * Based very much on cyclades.c.
+ *
+ * MVME166/7 work by Richard Hirst [richard@sleepie.demon.co.uk]
+ *
+ * ==============================================================
+ *
+ * static char rcsid[] =
+ * "$Revision: 1.36.1.4 $$Date: 1995/03/29 06:14:14 $";
+ *
+ * linux/kernel/cyclades.c
+ *
+ * Maintained by Marcio Saito (cyclades@netcom.com) and
+ * Randolph Bentson (bentson@grieg.seaslug.org)
+ *
+ * Much of the design and some of the code came from serial.c
+ * which was copyright (C) 1991, 1992 Linus Torvalds. It was
+ * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92,
+ * and then fixed as suggested by Michael K. Johnson 12/12/92.
+ *
+ * This version does not support shared irq's.
+ *
+ * $Log: cyclades.c,v $
+ * Revision 1.36.1.4 1995/03/29 06:14:14 bentson
+ * disambiguate between Cyclom-16Y and Cyclom-32Ye;
+ *
+ * Changes:
+ *
+ * 200 lines of changes record removed - RGH 11-10-95, starting work on
+ * converting this to drive serial ports on mvme166 (cd2401).
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 2000/08/25
+ * - get rid of verify_area
+ * - use get_user to access memory from userspace in set_threshold,
+ * set_default_threshold and set_timeout
+ * - don't use the panic function in serial167_init
+ * - do resource release on failure on serial167_init
+ * - include missing restore_flags in mvme167_serial_console_setup
+ *
+ * Kars de Jong <jongk@linux-m68k.org> - 2004/09/06
+ * - replace bottom half handler with task queue handler
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/interrupt.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/serial167.h>
+#include <linux/delay.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/mvme16xhw.h>
+#include <asm/bootinfo.h>
+#include <asm/setup.h>
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <asm/uaccess.h>
+#include <linux/init.h>
+
+#define SERIAL_PARANOIA_CHECK
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_THROTTLE
+#undef SERIAL_DEBUG_OTHER
+#undef SERIAL_DEBUG_IO
+#undef SERIAL_DEBUG_COUNT
+#undef SERIAL_DEBUG_DTR
+#undef CYCLOM_16Y_HACK
+#define CYCLOM_ENABLE_MONITORING
+
+#define WAKEUP_CHARS 256
+
+#define STD_COM_FLAGS (0)
+
+#define SERIAL_TYPE_NORMAL 1
+
+static struct tty_driver *cy_serial_driver;
+extern int serial_console;
+static struct cyclades_port *serial_console_info = NULL;
+static unsigned int serial_console_cflag = 0;
+u_char initial_console_speed;
+
+/* Base address of cd2401 chip on mvme166/7 */
+
+#define BASE_ADDR (0xfff45000)
+#define pcc2chip ((volatile u_char *)0xfff42000)
+#define PccSCCMICR 0x1d
+#define PccSCCTICR 0x1e
+#define PccSCCRICR 0x1f
+#define PccTPIACKR 0x25
+#define PccRPIACKR 0x27
+#define PccIMLR 0x3f
+
+/* This is the per-port data structure */
+struct cyclades_port cy_port[] = {
+ /* CARD# */
+ {-1 }, /* ttyS0 */
+ {-1 }, /* ttyS1 */
+ {-1 }, /* ttyS2 */
+ {-1 }, /* ttyS3 */
+};
+#define NR_PORTS (sizeof(cy_port)/sizeof(struct cyclades_port))
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write. We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf = 0;
+DECLARE_MUTEX(tmp_buf_sem);
+
+/*
+ * This is used to look up the divisor speeds and the timeouts
+ * We're normally limited to 15 distinct baud rates. The extra
+ * are accessed via settings in info->flags.
+ * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ * 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ * HI VHI
+ */
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200,
+ 1800, 2400, 4800, 9600, 19200, 38400, 57600, 76800,115200,150000,
+ 0};
+
+#if 0
+static char baud_co[] = { /* 25 MHz clock option table */
+ /* value => 00 01 02 03 04 */
+ /* divide by 8 32 128 512 2048 */
+ 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x02,
+ 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static char baud_bpr[] = { /* 25 MHz baud rate period table */
+ 0x00, 0xf5, 0xa3, 0x6f, 0x5c, 0x51, 0xf5, 0xa3, 0x51, 0xa3,
+ 0x6d, 0x51, 0xa3, 0x51, 0xa3, 0x51, 0x36, 0x29, 0x1b, 0x15};
+#endif
+
+/* I think 166 brd clocks 2401 at 20MHz.... */
+
+/* These values are written directly to tcor, and >> 5 for writing to rcor */
+static u_char baud_co[] = { /* 20 MHz clock option table */
+ 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x60, 0x60, 0x40,
+ 0x40, 0x40, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* These values written directly to tbpr/rbpr */
+static u_char baud_bpr[] = { /* 20 MHz baud rate period table */
+ 0x00, 0xc0, 0x80, 0x58, 0x6c, 0x40, 0xc0, 0x81, 0x40, 0x81,
+ 0x57, 0x40, 0x81, 0x40, 0x81, 0x40, 0x2b, 0x20, 0x15, 0x10};
+
+static u_char baud_cor4[] = { /* receive threshold */
+ 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+ 0x0a, 0x0a, 0x0a, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x07};
+
+
+
+static void shutdown(struct cyclades_port *);
+static int startup (struct cyclades_port *);
+static void cy_throttle(struct tty_struct *);
+static void cy_unthrottle(struct tty_struct *);
+static void config_setup(struct cyclades_port *);
+extern void console_print(const char *);
+#ifdef CYCLOM_SHOW_STATUS
+static void show_status(int);
+#endif
+
+#ifdef CONFIG_REMOTE_DEBUG
+static void debug_setup(void);
+void queueDebugChar (int c);
+int getDebugChar(void);
+
+#define DEBUG_PORT 1
+#define DEBUG_LEN 256
+
+typedef struct {
+ int in;
+ int out;
+ unsigned char buf[DEBUG_LEN];
+} debugq;
+
+debugq debugiq;
+#endif
+
+/*
+ * I have my own version of udelay(), as it is needed when initialising
+ * the chip, before the delay loop has been calibrated. Should probably
+ * reference one of the vmechip2 or pccchip2 counter for an accurate
+ * delay, but this wild guess will do for now.
+ */
+
+void my_udelay (long us)
+{
+ u_char x;
+ volatile u_char *p = &x;
+ int i;
+
+ while (us--)
+ for (i = 100; i; i--)
+ x |= *p;
+}
+
+static inline int
+serial_paranoia_check(struct cyclades_port *info, char *name,
+ const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for serial struct (%s) in %s\n";
+ static const char *badinfo =
+ "Warning: null cyclades_port for (%s) in %s\n";
+ static const char *badrange =
+ "Warning: cyclades_port out of range for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+
+ if( (long)info < (long)(&cy_port[0])
+ || (long)(&cy_port[NR_PORTS]) < (long)info ){
+ printk(badrange, name, routine);
+ return 1;
+ }
+
+ if (info->magic != CYCLADES_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+} /* serial_paranoia_check */
+
+#if 0
+/* The following diagnostic routines allow the driver to spew
+ information on the screen, even (especially!) during interrupts.
+ */
+void
+SP(char *data){
+ unsigned long flags;
+ local_irq_save(flags);
+ console_print(data);
+ local_irq_restore(flags);
+}
+char scrn[2];
+void
+CP(char data){
+ unsigned long flags;
+ local_irq_save(flags);
+ scrn[0] = data;
+ console_print(scrn);
+ local_irq_restore(flags);
+}/* CP */
+
+void CP1(int data) { (data<10)? CP(data+'0'): CP(data+'A'-10); }/* CP1 */
+void CP2(int data) { CP1((data>>4) & 0x0f); CP1( data & 0x0f); }/* CP2 */
+void CP4(int data) { CP2((data>>8) & 0xff); CP2(data & 0xff); }/* CP4 */
+void CP8(long data) { CP4((data>>16) & 0xffff); CP4(data & 0xffff); }/* CP8 */
+#endif
+
+/* This routine waits up to 1000 micro-seconds for the previous
+ command to the Cirrus chip to complete and then issues the
+ new command. An error is returned if the previous command
+ didn't finish within the time limit.
+ */
+u_short
+write_cy_cmd(volatile u_char *base_addr, u_char cmd)
+{
+ unsigned long flags;
+ volatile int i;
+
+ local_irq_save(flags);
+ /* Check to see that the previous command has completed */
+ for(i = 0 ; i < 100 ; i++){
+ if (base_addr[CyCCR] == 0){
+ break;
+ }
+ my_udelay(10L);
+ }
+ /* if the CCR never cleared, the previous command
+ didn't finish within the "reasonable time" */
+ if ( i == 10 ) {
+ local_irq_restore(flags);
+ return (-1);
+ }
+
+ /* Issue the new command */
+ base_addr[CyCCR] = cmd;
+ local_irq_restore(flags);
+ return(0);
+} /* write_cy_cmd */
+
+
+/* cy_start and cy_stop provide software output flow control as a
+ function of XON/XOFF, software CTS, and other such stuff. */
+
+static void
+cy_stop(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+ unsigned long flags;
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_stop %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_stop"))
+ return;
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)(channel); /* index channel */
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ local_irq_restore(flags);
+
+ return;
+} /* cy_stop */
+
+static void
+cy_start(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+ unsigned long flags;
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_start %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_start"))
+ return;
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)(channel);
+ base_addr[CyIER] |= CyTxMpty;
+ local_irq_restore(flags);
+
+ return;
+} /* cy_start */
+
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver
+ * (also known as the "bottom half"). This can be called any
+ * number of times for any channel without harm.
+ */
+static inline void
+cy_sched_event(struct cyclades_port *info, int event)
+{
+ info->event |= 1 << event; /* remember what kind of event and who */
+ schedule_work(&info->tqueue);
+} /* cy_sched_event */
+
+
+/* The real interrupt service routines are called
+ whenever the card wants its hand held--chars
+ received, out buffer empty, modem change, etc.
+ */
+static irqreturn_t
+cd2401_rxerr_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+ struct tty_struct *tty;
+ struct cyclades_port *info;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ unsigned char err, rfoc;
+ int channel;
+ char data;
+
+ /* determine the channel and change to that context */
+ channel = (u_short ) (base_addr[CyLICR] >> 2);
+ info = &cy_port[channel];
+ info->last_active = jiffies;
+
+ if ((err = base_addr[CyRISR]) & CyTIMEOUT) {
+ /* This is a receive timeout interrupt, ignore it */
+ base_addr[CyREOIR] = CyNOTRANS;
+ return IRQ_HANDLED;
+ }
+
+ /* Read a byte of data if there is any - assume the error
+ * is associated with this character */
+
+ if ((rfoc = base_addr[CyRFOC]) != 0)
+ data = base_addr[CyRDR];
+ else
+ data = 0;
+
+ /* if there is nowhere to put the data, discard it */
+ if(info->tty == 0) {
+ base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+ return IRQ_HANDLED;
+ }
+ else { /* there is an open port for this data */
+ tty = info->tty;
+ if(err & info->ignore_status_mask){
+ base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+ return IRQ_HANDLED;
+ }
+ if (tty->flip.count < TTY_FLIPBUF_SIZE){
+ tty->flip.count++;
+ if (err & info->read_status_mask){
+ if(err & CyBREAK){
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ *tty->flip.char_buf_ptr++ = data;
+ if (info->flags & ASYNC_SAK){
+ do_SAK(tty);
+ }
+ }else if(err & CyFRAME){
+ *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+ *tty->flip.char_buf_ptr++ = data;
+ }else if(err & CyPARITY){
+ *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+ *tty->flip.char_buf_ptr++ = data;
+ }else if(err & CyOVERRUN){
+ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+ *tty->flip.char_buf_ptr++ = 0;
+ /*
+ If the flip buffer itself is
+ overflowing, we still loose
+ the next incoming character.
+ */
+ if(tty->flip.count < TTY_FLIPBUF_SIZE){
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ = data;
+ }
+ /* These two conditions may imply */
+ /* a normal read should be done. */
+ /* else if(data & CyTIMEOUT) */
+ /* else if(data & CySPECHAR) */
+ }else{
+ *tty->flip.flag_buf_ptr++ = 0;
+ *tty->flip.char_buf_ptr++ = 0;
+ }
+ }else{
+ *tty->flip.flag_buf_ptr++ = 0;
+ *tty->flip.char_buf_ptr++ = 0;
+ }
+ }else{
+ /* there was a software buffer overrun
+ and nothing could be done about it!!! */
+ }
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+ /* end of service */
+ base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+ return IRQ_HANDLED;
+} /* cy_rxerr_interrupt */
+
+static irqreturn_t
+cd2401_modem_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+ struct cyclades_port *info;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+ int mdm_change;
+ int mdm_status;
+
+
+ /* determine the channel and change to that context */
+ channel = (u_short ) (base_addr[CyLICR] >> 2);
+ info = &cy_port[channel];
+ info->last_active = jiffies;
+
+ mdm_change = base_addr[CyMISR];
+ mdm_status = base_addr[CyMSVR1];
+
+ if(info->tty == 0){ /* nowhere to put the data, ignore it */
+ ;
+ }else{
+ if((mdm_change & CyDCD)
+ && (info->flags & ASYNC_CHECK_CD)){
+ if(mdm_status & CyDCD){
+/* CP('!'); */
+ cy_sched_event(info, Cy_EVENT_OPEN_WAKEUP);
+ } else {
+/* CP('@'); */
+ cy_sched_event(info, Cy_EVENT_HANGUP);
+ }
+ }
+ if((mdm_change & CyCTS)
+ && (info->flags & ASYNC_CTS_FLOW)){
+ if(info->tty->stopped){
+ if(mdm_status & CyCTS){
+ /* !!! cy_start isn't used because... */
+ info->tty->stopped = 0;
+ base_addr[CyIER] |= CyTxMpty;
+ cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+ }
+ }else{
+ if(!(mdm_status & CyCTS)){
+ /* !!! cy_stop isn't used because... */
+ info->tty->stopped = 1;
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ }
+ }
+ }
+ if(mdm_status & CyDSR){
+ }
+ }
+ base_addr[CyMEOIR] = 0;
+ return IRQ_HANDLED;
+} /* cy_modem_interrupt */
+
+static irqreturn_t
+cd2401_tx_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+ struct cyclades_port *info;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+ int char_count, saved_cnt;
+ int outch;
+
+ /* determine the channel and change to that context */
+ channel = (u_short ) (base_addr[CyLICR] >> 2);
+
+#ifdef CONFIG_REMOTE_DEBUG
+ if (channel == DEBUG_PORT) {
+ panic ("TxInt on debug port!!!");
+ }
+#endif
+
+ info = &cy_port[channel];
+
+ /* validate the port number (as configured and open) */
+ if( (channel < 0) || (NR_PORTS <= channel) ){
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ base_addr[CyTEOIR] = CyNOTRANS;
+ return IRQ_HANDLED;
+ }
+ info->last_active = jiffies;
+ if(info->tty == 0){
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+ }
+ base_addr[CyTEOIR] = CyNOTRANS;
+ return IRQ_HANDLED;
+ }
+
+ /* load the on-chip space available for outbound data */
+ saved_cnt = char_count = base_addr[CyTFTC];
+
+ if(info->x_char) { /* send special char */
+ outch = info->x_char;
+ base_addr[CyTDR] = outch;
+ char_count--;
+ info->x_char = 0;
+ }
+
+ if (info->x_break){
+ /* The Cirrus chip requires the "Embedded Transmit
+ Commands" of start break, delay, and end break
+ sequences to be sent. The duration of the
+ break is given in TICs, which runs at HZ
+ (typically 100) and the PPR runs at 200 Hz,
+ so the delay is duration * 200/HZ, and thus a
+ break can run from 1/100 sec to about 5/4 sec.
+ Need to check these values - RGH 141095.
+ */
+ base_addr[CyTDR] = 0; /* start break */
+ base_addr[CyTDR] = 0x81;
+ base_addr[CyTDR] = 0; /* delay a bit */
+ base_addr[CyTDR] = 0x82;
+ base_addr[CyTDR] = info->x_break*200/HZ;
+ base_addr[CyTDR] = 0; /* terminate break */
+ base_addr[CyTDR] = 0x83;
+ char_count -= 7;
+ info->x_break = 0;
+ }
+
+ while (char_count > 0){
+ if (!info->xmit_cnt){
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ break;
+ }
+ if (info->xmit_buf == 0){
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ break;
+ }
+ if (info->tty->stopped || info->tty->hw_stopped){
+ base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+ break;
+ }
+ /* Because the Embedded Transmit Commands have been
+ enabled, we must check to see if the escape
+ character, NULL, is being sent. If it is, we
+ must ensure that there is room for it to be
+ doubled in the output stream. Therefore we
+ no longer advance the pointer when the character
+ is fetched, but rather wait until after the check
+ for a NULL output character. (This is necessary
+ because there may not be room for the two chars
+ needed to send a NULL.
+ */
+ outch = info->xmit_buf[info->xmit_tail];
+ if( outch ){
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1)
+ & (PAGE_SIZE - 1);
+ base_addr[CyTDR] = outch;
+ char_count--;
+ }else{
+ if(char_count > 1){
+ info->xmit_cnt--;
+ info->xmit_tail = (info->xmit_tail + 1)
+ & (PAGE_SIZE - 1);
+ base_addr[CyTDR] = outch;
+ base_addr[CyTDR] = 0;
+ char_count--;
+ char_count--;
+ }else{
+ break;
+ }
+ }
+ }
+
+ if (info->xmit_cnt < WAKEUP_CHARS) {
+ cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+ }
+ base_addr[CyTEOIR] = (char_count != saved_cnt) ? 0 : CyNOTRANS;
+ return IRQ_HANDLED;
+} /* cy_tx_interrupt */
+
+static irqreturn_t
+cd2401_rx_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+ struct tty_struct *tty;
+ struct cyclades_port *info;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+ char data;
+ int char_count;
+ int save_cnt;
+
+ /* determine the channel and change to that context */
+ channel = (u_short ) (base_addr[CyLICR] >> 2);
+ info = &cy_port[channel];
+ info->last_active = jiffies;
+ save_cnt = char_count = base_addr[CyRFOC];
+
+#ifdef CONFIG_REMOTE_DEBUG
+ if (channel == DEBUG_PORT) {
+ while (char_count--) {
+ data = base_addr[CyRDR];
+ queueDebugChar(data);
+ }
+ }
+ else
+#endif
+ /* if there is nowhere to put the data, discard it */
+ if(info->tty == 0){
+ while(char_count--){
+ data = base_addr[CyRDR];
+ }
+ }else{ /* there is an open port for this data */
+ tty = info->tty;
+ /* load # characters available from the chip */
+
+#ifdef CYCLOM_ENABLE_MONITORING
+ ++info->mon.int_count;
+ info->mon.char_count += char_count;
+ if (char_count > info->mon.char_max)
+ info->mon.char_max = char_count;
+ info->mon.char_last = char_count;
+#endif
+ while(char_count--){
+ data = base_addr[CyRDR];
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE){
+ continue;
+ }
+ tty->flip.count++;
+ *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+ *tty->flip.char_buf_ptr++ = data;
+#ifdef CYCLOM_16Y_HACK
+ udelay(10L);
+#endif
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+ }
+ /* end of service */
+ base_addr[CyREOIR] = save_cnt ? 0 : CyNOTRANS;
+ return IRQ_HANDLED;
+} /* cy_rx_interrupt */
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * cy#/_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON. This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using cy_sched_event(), and they get done here.
+ *
+ * This is done through one level of indirection--the task queue.
+ * When a hardware interrupt service routine wants service by the
+ * driver's bottom half, it enqueues the appropriate tq_struct (one
+ * per port) to the keventd work queue and sets a request flag
+ * that the work queue be processed.
+ *
+ * Although this may seem unwieldy, it gives the system a way to
+ * pass an argument (in this case the pointer to the cyclades_port
+ * structure) to the bottom half of the driver. Previous kernels
+ * had to poll every port to see if that port needed servicing.
+ */
+static void
+do_softint(void *private_)
+{
+ struct cyclades_port *info = (struct cyclades_port *) private_;
+ struct tty_struct *tty;
+
+ tty = info->tty;
+ if (!tty)
+ return;
+
+ if (test_and_clear_bit(Cy_EVENT_HANGUP, &info->event)) {
+ tty_hangup(info->tty);
+ wake_up_interruptible(&info->open_wait);
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ }
+ if (test_and_clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) {
+ wake_up_interruptible(&info->open_wait);
+ }
+ if (test_and_clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) {
+ tty_wakeup(tty);
+ }
+} /* do_softint */
+
+
+/* This is called whenever a port becomes active;
+ interrupts are enabled and DTR & RTS are turned on.
+ */
+static int
+startup(struct cyclades_port * info)
+{
+ unsigned long flags;
+ volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+ int channel;
+
+ if (info->flags & ASYNC_INITIALIZED){
+ return 0;
+ }
+
+ if (!info->type){
+ if (info->tty){
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ return 0;
+ }
+ if (!info->xmit_buf){
+ info->xmit_buf = (unsigned char *) get_zeroed_page (GFP_KERNEL);
+ if (!info->xmit_buf){
+ return -ENOMEM;
+ }
+ }
+
+ config_setup(info);
+
+ channel = info->line;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("startup channel %d\n", channel);
+#endif
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ write_cy_cmd(base_addr,CyENB_RCVR|CyENB_XMTR);
+
+ base_addr[CyCAR] = (u_char)channel; /* !!! Is this needed? */
+ base_addr[CyMSVR1] = CyRTS;
+/* CP('S');CP('1'); */
+ base_addr[CyMSVR2] = CyDTR;
+
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: raising DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+
+ base_addr[CyIER] |= CyRxData;
+ info->flags |= ASYNC_INITIALIZED;
+
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+ local_irq_restore(flags);
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk(" done\n");
+#endif
+ return 0;
+} /* startup */
+
+void
+start_xmit( struct cyclades_port *info )
+{
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+
+ channel = info->line;
+ local_irq_save(flags);
+ base_addr[CyCAR] = channel;
+ base_addr[CyIER] |= CyTxMpty;
+ local_irq_restore(flags);
+} /* start_xmit */
+
+/*
+ * This routine shuts down a serial port; interrupts are disabled,
+ * and DTR is dropped if the hangup on close termio flag is on.
+ */
+static void
+shutdown(struct cyclades_port * info)
+{
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+
+ if (!(info->flags & ASYNC_INITIALIZED)){
+/* CP('$'); */
+ return;
+ }
+
+ channel = info->line;
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("shutdown channel %d\n", channel);
+#endif
+
+ /* !!! REALLY MUST WAIT FOR LAST CHARACTER TO BE
+ SENT BEFORE DROPPING THE LINE !!! (Perhaps
+ set some flag that is read when XMTY happens.)
+ Other choices are to delay some fixed interval
+ or schedule some later processing.
+ */
+ local_irq_save(flags);
+ if (info->xmit_buf){
+ free_page((unsigned long) info->xmit_buf);
+ info->xmit_buf = 0;
+ }
+
+ base_addr[CyCAR] = (u_char)channel;
+ if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+ base_addr[CyMSVR1] = 0;
+/* CP('C');CP('1'); */
+ base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: dropping DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ }
+ write_cy_cmd(base_addr,CyDIS_RCVR);
+ /* it may be appropriate to clear _XMIT at
+ some later date (after testing)!!! */
+
+ if (info->tty){
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+ info->flags &= ~ASYNC_INITIALIZED;
+ local_irq_restore(flags);
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk(" done\n");
+#endif
+ return;
+} /* shutdown */
+
+/*
+ * This routine finds or computes the various line characteristics.
+ */
+static void
+config_setup(struct cyclades_port * info)
+{
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+ unsigned cflag;
+ int i;
+ unsigned char ti, need_init_chan = 0;
+
+ if (!info->tty || !info->tty->termios){
+ return;
+ }
+ if (info->line == -1){
+ return;
+ }
+ cflag = info->tty->termios->c_cflag;
+
+ /* baud rate */
+ i = cflag & CBAUD;
+#ifdef CBAUDEX
+/* Starting with kernel 1.1.65, there is direct support for
+ higher baud rates. The following code supports those
+ changes. The conditional aspect allows this driver to be
+ used for earlier as well as later kernel versions. (The
+ mapping is slightly different from serial.c because there
+ is still the possibility of supporting 75 kbit/sec with
+ the Cyclades board.)
+ */
+ if (i & CBAUDEX) {
+ if (i == B57600)
+ i = 16;
+ else if(i == B115200)
+ i = 18;
+#ifdef B78600
+ else if(i == B78600)
+ i = 17;
+#endif
+ else
+ info->tty->termios->c_cflag &= ~CBAUDEX;
+ }
+#endif
+ if (i == 15) {
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ i += 1;
+ if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ i += 3;
+ }
+ /* Don't ever change the speed of the console port. It will
+ * run at the speed specified in bootinfo, or at 19.2K */
+ /* Actually, it should run at whatever speed 166Bug was using */
+ /* Note info->timeout isn't used at present */
+ if (info != serial_console_info) {
+ info->tbpr = baud_bpr[i]; /* Tx BPR */
+ info->tco = baud_co[i]; /* Tx CO */
+ info->rbpr = baud_bpr[i]; /* Rx BPR */
+ info->rco = baud_co[i] >> 5; /* Rx CO */
+ if (baud_table[i] == 134) {
+ info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+ /* get it right for 134.5 baud */
+ } else if (baud_table[i]) {
+ info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
+ /* this needs to be propagated into the card info */
+ } else {
+ info->timeout = 0;
+ }
+ }
+ /* By tradition (is it a standard?) a baud rate of zero
+ implies the line should be/has been closed. A bit
+ later in this routine such a test is performed. */
+
+ /* byte size and parity */
+ info->cor7 = 0;
+ info->cor6 = 0;
+ info->cor5 = 0;
+ info->cor4 = (info->default_threshold
+ ? info->default_threshold
+ : baud_cor4[i]); /* receive threshold */
+ /* Following two lines added 101295, RGH. */
+ /* It is obviously wrong to access CyCORx, and not info->corx here,
+ * try and remember to fix it later! */
+ channel = info->line;
+ base_addr[CyCAR] = (u_char)channel;
+ if (C_CLOCAL(info->tty)) {
+ if (base_addr[CyIER] & CyMdmCh)
+ base_addr[CyIER] &= ~CyMdmCh; /* without modem intr */
+ /* ignore 1->0 modem transitions */
+ if (base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD))
+ base_addr[CyCOR4] &= ~(CyDSR|CyCTS|CyDCD);
+ /* ignore 0->1 modem transitions */
+ if (base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD))
+ base_addr[CyCOR5] &= ~(CyDSR|CyCTS|CyDCD);
+ } else {
+ if ((base_addr[CyIER] & CyMdmCh) != CyMdmCh)
+ base_addr[CyIER] |= CyMdmCh; /* with modem intr */
+ /* act on 1->0 modem transitions */
+ if ((base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD))
+ base_addr[CyCOR4] |= CyDSR|CyCTS|CyDCD;
+ /* act on 0->1 modem transitions */
+ if ((base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD))
+ base_addr[CyCOR5] |= CyDSR|CyCTS|CyDCD;
+ }
+ info->cor3 = (cflag & CSTOPB) ? Cy_2_STOP : Cy_1_STOP;
+ info->cor2 = CyETC;
+ switch(cflag & CSIZE){
+ case CS5:
+ info->cor1 = Cy_5_BITS;
+ break;
+ case CS6:
+ info->cor1 = Cy_6_BITS;
+ break;
+ case CS7:
+ info->cor1 = Cy_7_BITS;
+ break;
+ case CS8:
+ info->cor1 = Cy_8_BITS;
+ break;
+ }
+ if (cflag & PARENB){
+ if (cflag & PARODD){
+ info->cor1 |= CyPARITY_O;
+ }else{
+ info->cor1 |= CyPARITY_E;
+ }
+ }else{
+ info->cor1 |= CyPARITY_NONE;
+ }
+
+ /* CTS flow control flag */
+#if 0
+ /* Don't complcate matters for now! RGH 141095 */
+ if (cflag & CRTSCTS){
+ info->flags |= ASYNC_CTS_FLOW;
+ info->cor2 |= CyCtsAE;
+ }else{
+ info->flags &= ~ASYNC_CTS_FLOW;
+ info->cor2 &= ~CyCtsAE;
+ }
+#endif
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ /***********************************************
+ The hardware option, CyRtsAO, presents RTS when
+ the chip has characters to send. Since most modems
+ use RTS as reverse (inbound) flow control, this
+ option is not used. If inbound flow control is
+ necessary, DTR can be programmed to provide the
+ appropriate signals for use with a non-standard
+ cable. Contact Marcio Saito for details.
+ ***********************************************/
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+
+ /* CyCMR set once only in mvme167_init_serial() */
+ if (base_addr[CyLICR] != channel << 2)
+ base_addr[CyLICR] = channel << 2;
+ if (base_addr[CyLIVR] != 0x5c)
+ base_addr[CyLIVR] = 0x5c;
+
+ /* tx and rx baud rate */
+
+ if (base_addr[CyCOR1] != info->cor1)
+ need_init_chan = 1;
+ if (base_addr[CyTCOR] != info->tco)
+ base_addr[CyTCOR] = info->tco;
+ if (base_addr[CyTBPR] != info->tbpr)
+ base_addr[CyTBPR] = info->tbpr;
+ if (base_addr[CyRCOR] != info->rco)
+ base_addr[CyRCOR] = info->rco;
+ if (base_addr[CyRBPR] != info->rbpr)
+ base_addr[CyRBPR] = info->rbpr;
+
+ /* set line characteristics according configuration */
+
+ if (base_addr[CySCHR1] != START_CHAR(info->tty))
+ base_addr[CySCHR1] = START_CHAR(info->tty);
+ if (base_addr[CySCHR2] != STOP_CHAR(info->tty))
+ base_addr[CySCHR2] = STOP_CHAR(info->tty);
+ if (base_addr[CySCRL] != START_CHAR(info->tty))
+ base_addr[CySCRL] = START_CHAR(info->tty);
+ if (base_addr[CySCRH] != START_CHAR(info->tty))
+ base_addr[CySCRH] = START_CHAR(info->tty);
+ if (base_addr[CyCOR1] != info->cor1)
+ base_addr[CyCOR1] = info->cor1;
+ if (base_addr[CyCOR2] != info->cor2)
+ base_addr[CyCOR2] = info->cor2;
+ if (base_addr[CyCOR3] != info->cor3)
+ base_addr[CyCOR3] = info->cor3;
+ if (base_addr[CyCOR4] != info->cor4)
+ base_addr[CyCOR4] = info->cor4;
+ if (base_addr[CyCOR5] != info->cor5)
+ base_addr[CyCOR5] = info->cor5;
+ if (base_addr[CyCOR6] != info->cor6)
+ base_addr[CyCOR6] = info->cor6;
+ if (base_addr[CyCOR7] != info->cor7)
+ base_addr[CyCOR7] = info->cor7;
+
+ if (need_init_chan)
+ write_cy_cmd(base_addr,CyINIT_CHAN);
+
+ base_addr[CyCAR] = (u_char)channel; /* !!! Is this needed? */
+
+ /* 2ms default rx timeout */
+ ti = info->default_timeout ? info->default_timeout : 0x02;
+ if (base_addr[CyRTPRL] != ti)
+ base_addr[CyRTPRL] = ti;
+ if (base_addr[CyRTPRH] != 0)
+ base_addr[CyRTPRH] = 0;
+
+ /* Set up RTS here also ????? RGH 141095 */
+ if(i == 0){ /* baud rate is zero, turn off line */
+ if ((base_addr[CyMSVR2] & CyDTR) == CyDTR)
+ base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: dropping DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ }else{
+ if ((base_addr[CyMSVR2] & CyDTR) != CyDTR)
+ base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: raising DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ }
+
+ if (info->tty){
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+ }
+
+ local_irq_restore(flags);
+
+} /* config_setup */
+
+
+static void
+cy_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_put_char %s(0x%02x)\n", tty->name, ch);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_put_char"))
+ return;
+
+ if (!tty || !info->xmit_buf)
+ return;
+
+ local_irq_save(flags);
+ if (info->xmit_cnt >= PAGE_SIZE - 1) {
+ local_irq_restore(flags);
+ return;
+ }
+
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= PAGE_SIZE - 1;
+ info->xmit_cnt++;
+ local_irq_restore(flags);
+} /* cy_put_char */
+
+
+static void
+cy_flush_chars(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_flush_chars %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_flush_chars"))
+ return;
+
+ if (info->xmit_cnt <= 0 || tty->stopped
+ || tty->hw_stopped || !info->xmit_buf)
+ return;
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = channel;
+ base_addr[CyIER] |= CyTxMpty;
+ local_irq_restore(flags);
+} /* cy_flush_chars */
+
+
+/* This routine gets called when tty_write has put something into
+ the write_queue. If the port is not already transmitting stuff,
+ start it off by enabling interrupts. The interrupt service
+ routine will then ensure that the characters are sent. If the
+ port is already active, there is no need to kick it.
+ */
+static int
+cy_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ int c, total = 0;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_write %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_write")){
+ return 0;
+ }
+
+ if (!tty || !info->xmit_buf || !tmp_buf){
+ return 0;
+ }
+
+ while (1) {
+ local_irq_save(flags);
+ c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - info->xmit_head));
+ if (c <= 0) {
+ local_irq_restore(flags);
+ break;
+ }
+
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ info->xmit_cnt += c;
+ local_irq_restore(flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ if (info->xmit_cnt
+ && !tty->stopped
+ && !tty->hw_stopped ) {
+ start_xmit(info);
+ }
+ return total;
+} /* cy_write */
+
+
+static int
+cy_write_room(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ int ret;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_write_room %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_write_room"))
+ return 0;
+ ret = PAGE_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+ return ret;
+} /* cy_write_room */
+
+
+static int
+cy_chars_in_buffer(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_chars_in_buffer %s %d\n", tty->name, info->xmit_cnt); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_chars_in_buffer"))
+ return 0;
+
+ return info->xmit_cnt;
+} /* cy_chars_in_buffer */
+
+
+static void
+cy_flush_buffer(struct tty_struct *tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+
+#ifdef SERIAL_DEBUG_IO
+ printk("cy_flush_buffer %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_flush_buffer"))
+ return;
+ local_irq_save(flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ local_irq_restore(flags);
+ tty_wakeup(tty);
+} /* cy_flush_buffer */
+
+
+/* This routine is called by the upper-layer tty layer to signal
+ that incoming characters should be throttled or that the
+ throttle should be released.
+ */
+static void
+cy_throttle(struct tty_struct * tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("throttle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+ printk("cy_throttle %s\n", tty->name);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_nthrottle")){
+ return;
+ }
+
+ if (I_IXOFF(tty)) {
+ info->x_char = STOP_CHAR(tty);
+ /* Should use the "Send Special Character" feature!!! */
+ }
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ base_addr[CyMSVR1] = 0;
+ local_irq_restore(flags);
+
+ return;
+} /* cy_throttle */
+
+
+static void
+cy_unthrottle(struct tty_struct * tty)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+
+#ifdef SERIAL_DEBUG_THROTTLE
+ char buf[64];
+
+ printk("throttle %s: %d....\n", tty_name(tty, buf),
+ tty->ldisc.chars_in_buffer(tty));
+ printk("cy_unthrottle %s\n", tty->name);
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_nthrottle")){
+ return;
+ }
+
+ if (I_IXOFF(tty)) {
+ info->x_char = START_CHAR(tty);
+ /* Should use the "Send Special Character" feature!!! */
+ }
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ base_addr[CyMSVR1] = CyRTS;
+ local_irq_restore(flags);
+
+ return;
+} /* cy_unthrottle */
+
+static int
+get_serial_info(struct cyclades_port * info,
+ struct serial_struct * retinfo)
+{
+ struct serial_struct tmp;
+
+/* CP('g'); */
+ if (!retinfo)
+ return -EFAULT;
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = info->type;
+ tmp.line = info->line;
+ tmp.port = info->line;
+ tmp.irq = 0;
+ tmp.flags = info->flags;
+ tmp.baud_base = 0; /*!!!*/
+ tmp.close_delay = info->close_delay;
+ tmp.custom_divisor = 0; /*!!!*/
+ tmp.hub6 = 0; /*!!!*/
+ return copy_to_user(retinfo,&tmp,sizeof(*retinfo)) ? -EFAULT : 0;
+} /* get_serial_info */
+
+static int
+set_serial_info(struct cyclades_port * info,
+ struct serial_struct * new_info)
+{
+ struct serial_struct new_serial;
+ struct cyclades_port old_info;
+
+/* CP('s'); */
+ if (!new_info)
+ return -EFAULT;
+ if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+ return -EFAULT;
+ old_info = *info;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((new_serial.close_delay != info->close_delay) ||
+ ((new_serial.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK) !=
+ (info->flags & ASYNC_FLAGS & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ goto check_and_exit;
+ }
+
+
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+
+ info->flags = ((info->flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ info->close_delay = new_serial.close_delay;
+
+
+check_and_exit:
+ if (info->flags & ASYNC_INITIALIZED){
+ config_setup(info);
+ return 0;
+ }else{
+ return startup(info);
+ }
+} /* set_serial_info */
+
+static int
+cy_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ int channel;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long flags;
+ unsigned char status;
+ unsigned int result;
+
+ channel = info->line;
+
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ status = base_addr[CyMSVR1] | base_addr[CyMSVR2];
+ local_irq_restore(flags);
+
+ return ((status & CyRTS) ? TIOCM_RTS : 0)
+ | ((status & CyDTR) ? TIOCM_DTR : 0)
+ | ((status & CyDCD) ? TIOCM_CAR : 0)
+ | ((status & CyDSR) ? TIOCM_DSR : 0)
+ | ((status & CyCTS) ? TIOCM_CTS : 0);
+} /* cy_tiocmget */
+
+static int
+cy_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ int channel;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long flags;
+ unsigned int arg;
+
+ channel = info->line;
+
+ if (set & TIOCM_RTS){
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ base_addr[CyMSVR1] = CyRTS;
+ local_irq_restore(flags);
+ }
+ if (set & TIOCM_DTR){
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+/* CP('S');CP('2'); */
+ base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: raising DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ local_irq_restore(flags);
+ }
+
+ if (clear & TIOCM_RTS){
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ base_addr[CyMSVR1] = 0;
+ local_irq_restore(flags);
+ }
+ if (clear & TIOCM_DTR){
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+/* CP('C');CP('2'); */
+ base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: dropping DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ local_irq_restore(flags);
+ }
+
+ return 0;
+} /* set_modem_info */
+
+static void
+send_break( struct cyclades_port * info, int duration)
+{ /* Let the transmit ISR take care of this (since it
+ requires stuffing characters into the output stream).
+ */
+ info->x_break = duration;
+ if (!info->xmit_cnt ) {
+ start_xmit(info);
+ }
+} /* send_break */
+
+static int
+get_mon_info(struct cyclades_port * info, struct cyclades_monitor * mon)
+{
+
+ if (copy_to_user(mon, &info->mon, sizeof(struct cyclades_monitor)))
+ return -EFAULT;
+ info->mon.int_count = 0;
+ info->mon.char_count = 0;
+ info->mon.char_max = 0;
+ info->mon.char_last = 0;
+ return 0;
+}
+
+static int
+set_threshold(struct cyclades_port * info, unsigned long *arg)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long value;
+ int channel;
+
+ if (get_user(value, arg))
+ return -EFAULT;
+
+ channel = info->line;
+ info->cor4 &= ~CyREC_FIFO;
+ info->cor4 |= value & CyREC_FIFO;
+ base_addr[CyCOR4] = info->cor4;
+ return 0;
+}
+
+static int
+get_threshold(struct cyclades_port * info, unsigned long *value)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+ unsigned long tmp;
+
+ channel = info->line;
+
+ tmp = base_addr[CyCOR4] & CyREC_FIFO;
+ return put_user(tmp,value);
+}
+
+static int
+set_default_threshold(struct cyclades_port * info, unsigned long *arg)
+{
+ unsigned long value;
+
+ if (get_user(value, arg))
+ return -EFAULT;
+
+ info->default_threshold = value & 0x0f;
+ return 0;
+}
+
+static int
+get_default_threshold(struct cyclades_port * info, unsigned long *value)
+{
+ return put_user(info->default_threshold,value);
+}
+
+static int
+set_timeout(struct cyclades_port * info, unsigned long *arg)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+ unsigned long value;
+
+ if (get_user(value, arg))
+ return -EFAULT;
+
+ channel = info->line;
+
+ base_addr[CyRTPRL] = value & 0xff;
+ base_addr[CyRTPRH] = (value >> 8) & 0xff;
+ return 0;
+}
+
+static int
+get_timeout(struct cyclades_port * info, unsigned long *value)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+ unsigned long tmp;
+
+ channel = info->line;
+
+ tmp = base_addr[CyRTPRL];
+ return put_user(tmp,value);
+}
+
+static int
+set_default_timeout(struct cyclades_port * info, unsigned long value)
+{
+ info->default_timeout = value & 0xff;
+ return 0;
+}
+
+static int
+get_default_timeout(struct cyclades_port * info, unsigned long *value)
+{
+ return put_user(info->default_timeout,value);
+}
+
+static int
+cy_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned long val;
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+ int ret_val = 0;
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_ioctl %s, cmd = %x arg = %lx\n", tty->name, cmd, arg); /* */
+#endif
+
+ switch (cmd) {
+ case CYGETMON:
+ ret_val = get_mon_info(info, (struct cyclades_monitor *)arg);
+ break;
+ case CYGETTHRESH:
+ ret_val = get_threshold(info, (unsigned long *)arg);
+ break;
+ case CYSETTHRESH:
+ ret_val = set_threshold(info, (unsigned long *)arg);
+ break;
+ case CYGETDEFTHRESH:
+ ret_val = get_default_threshold(info, (unsigned long *)arg);
+ break;
+ case CYSETDEFTHRESH:
+ ret_val = set_default_threshold(info, (unsigned long *)arg);
+ break;
+ case CYGETTIMEOUT:
+ ret_val = get_timeout(info, (unsigned long *)arg);
+ break;
+ case CYSETTIMEOUT:
+ ret_val = set_timeout(info, (unsigned long *)arg);
+ break;
+ case CYGETDEFTIMEOUT:
+ ret_val = get_default_timeout(info, (unsigned long *)arg);
+ break;
+ case CYSETDEFTIMEOUT:
+ ret_val = set_default_timeout(info, (unsigned long)arg);
+ break;
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ ret_val = tty_check_change(tty);
+ if (ret_val)
+ break;
+ tty_wait_until_sent(tty,0);
+ if (!arg)
+ send_break(info, HZ/4); /* 1/4 second */
+ break;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ ret_val = tty_check_change(tty);
+ if (ret_val)
+ break;
+ tty_wait_until_sent(tty,0);
+ send_break(info, arg ? arg*(HZ/10) : HZ/4);
+ break;
+
+/* The following commands are incompletely implemented!!! */
+ case TIOCGSOFTCAR:
+ ret_val = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg);
+ break;
+ case TIOCSSOFTCAR:
+ ret_val = get_user(val, (unsigned long *) arg);
+ if (ret_val)
+ break;
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) | (val ? CLOCAL : 0));
+ break;
+ case TIOCGSERIAL:
+ ret_val = get_serial_info(info, (struct serial_struct *) arg);
+ break;
+ case TIOCSSERIAL:
+ ret_val = set_serial_info(info,
+ (struct serial_struct *) arg);
+ break;
+ default:
+ ret_val = -ENOIOCTLCMD;
+ }
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_ioctl done\n");
+#endif
+
+ return ret_val;
+} /* cy_ioctl */
+
+
+
+
+static void
+cy_set_termios(struct tty_struct *tty, struct termios * old_termios)
+{
+ struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_set_termios %s\n", tty->name);
+#endif
+
+ if (tty->termios->c_cflag == old_termios->c_cflag)
+ return;
+ config_setup(info);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->stopped = 0;
+ cy_start(tty);
+ }
+#ifdef tytso_patch_94Nov25_1726
+ if (!(old_termios->c_cflag & CLOCAL) &&
+ (tty->termios->c_cflag & CLOCAL))
+ wake_up_interruptible(&info->open_wait);
+#endif
+
+ return;
+} /* cy_set_termios */
+
+
+static void
+cy_close(struct tty_struct * tty, struct file * filp)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+
+/* CP('C'); */
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_close %s\n", tty->name);
+#endif
+
+ if (!info
+ || serial_paranoia_check(info, tty->name, "cy_close")){
+ return;
+ }
+#ifdef SERIAL_DEBUG_OPEN
+ printk("cy_close %s, count = %d\n", tty->name, info->count);
+#endif
+
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. Info->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ printk("cy_close: bad serial port count; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: decrementing count to %d\n", __LINE__, info->count - 1);
+#endif
+ if (--info->count < 0) {
+ printk("cy_close: bad serial port count for ttys%d: %d\n",
+ info->line, info->count);
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+ info->count = 0;
+ }
+ if (info->count)
+ return;
+ info->flags |= ASYNC_CLOSING;
+ if (info->flags & ASYNC_INITIALIZED)
+ tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+ shutdown(info);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ info->event = 0;
+ info->tty = 0;
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&info->close_wait);
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_close done\n");
+#endif
+
+ return;
+} /* cy_close */
+
+/*
+ * cy_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+void
+cy_hangup(struct tty_struct *tty)
+{
+ struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_hangup %s\n", tty->name); /* */
+#endif
+
+ if (serial_paranoia_check(info, tty->name, "cy_hangup"))
+ return;
+
+ shutdown(info);
+#if 0
+ info->event = 0;
+ info->count = 0;
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+ info->tty = 0;
+#endif
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ wake_up_interruptible(&info->open_wait);
+} /* cy_hangup */
+
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_open() and friends
+ * ------------------------------------------------------------
+ */
+
+static int
+block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct cyclades_port *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ int channel;
+ int retval;
+ volatile u_char *base_addr = (u_char *)BASE_ADDR;
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (info->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&info->close_wait);
+ if (info->flags & ASYNC_HUP_NOTIFY){
+ return -EAGAIN;
+ }else{
+ return -ERESTARTSYS;
+ }
+ }
+
+ /*
+ * If non-blocking mode is set, then make the check up front
+ * and then exit.
+ */
+ if (filp->f_flags & O_NONBLOCK) {
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * cy_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready before block: %s, count = %d\n",
+ tty->name, info->count);/**/
+#endif
+ info->count--;
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: decrementing count to %d\n", __LINE__, info->count);
+#endif
+ info->blocked_open++;
+
+ channel = info->line;
+
+ while (1) {
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+ base_addr[CyMSVR1] = CyRTS;
+/* CP('S');CP('4'); */
+ base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+ printk("cyc: %d: raising DTR\n", __LINE__);
+ printk(" status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+ local_irq_restore(flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp)
+ || !(info->flags & ASYNC_INITIALIZED) ){
+ if (info->flags & ASYNC_HUP_NOTIFY) {
+ retval = -EAGAIN;
+ }else{
+ retval = -ERESTARTSYS;
+ }
+ break;
+ }
+ local_irq_save(flags);
+ base_addr[CyCAR] = (u_char)channel;
+/* CP('L');CP1(1 && C_CLOCAL(tty)); CP1(1 && (base_addr[CyMSVR1] & CyDCD) ); */
+ if (!(info->flags & ASYNC_CLOSING)
+ && (C_CLOCAL(tty)
+ || (base_addr[CyMSVR1] & CyDCD))) {
+ local_irq_restore(flags);
+ break;
+ }
+ local_irq_restore(flags);
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready blocking: %s, count = %d\n",
+ tty->name, info->count);/**/
+#endif
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&info->open_wait, &wait);
+ if (!tty_hung_up_p(filp)){
+ info->count++;
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: incrementing count to %d\n", __LINE__, info->count);
+#endif
+ }
+ info->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+ printk("block_til_ready after blocking: %s, count = %d\n",
+ tty->name, info->count);/**/
+#endif
+ if (retval)
+ return retval;
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+} /* block_til_ready */
+
+/*
+ * This routine is called whenever a serial port is opened. It
+ * performs the serial-specific initialization for the tty structure.
+ */
+int
+cy_open(struct tty_struct *tty, struct file * filp)
+{
+ struct cyclades_port *info;
+ int retval, line;
+
+/* CP('O'); */
+ line = tty->index;
+ if ((line < 0) || (NR_PORTS <= line)){
+ return -ENODEV;
+ }
+ info = &cy_port[line];
+ if (info->line < 0){
+ return -ENODEV;
+ }
+#ifdef SERIAL_DEBUG_OTHER
+ printk("cy_open %s\n", tty->name); /* */
+#endif
+ if (serial_paranoia_check(info, tty->name, "cy_open")){
+ return -ENODEV;
+ }
+#ifdef SERIAL_DEBUG_OPEN
+ printk("cy_open %s, count = %d\n", tty->name, info->count);/**/
+#endif
+ info->count++;
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: incrementing count to %d\n", __LINE__, info->count);
+#endif
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (!tmp_buf) {
+ tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+ if (!tmp_buf){
+ return -ENOMEM;
+ }
+ }
+
+ /*
+ * Start up serial port
+ */
+ retval = startup(info);
+ if (retval){
+ return retval;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+ printk("cy_open returning after block_til_ready with %d\n",
+ retval);
+#endif
+ return retval;
+ }
+
+#ifdef SERIAL_DEBUG_OPEN
+ printk("cy_open done\n");/**/
+#endif
+ return 0;
+} /* cy_open */
+
+
+
+/*
+ * ---------------------------------------------------------------------
+ * serial167_init() and friends
+ *
+ * serial167_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+static void
+show_version(void)
+{
+ printk("MVME166/167 cd2401 driver\n");
+} /* show_version */
+
+/* initialize chips on card -- return number of valid
+ chips (which is number of ports/4) */
+
+/*
+ * This initialises the hardware to a reasonable state. It should
+ * probe the chip first so as to copy 166-Bug setup as a default for
+ * port 0. It initialises CMR to CyASYNC; that is never done again, so
+ * as to limit the number of CyINIT_CHAN commands in normal running.
+ *
+ * ... I wonder what I should do if this fails ...
+ */
+
+void
+mvme167_serial_console_setup(int cflag)
+{
+ volatile unsigned char* base_addr = (u_char *)BASE_ADDR;
+ int ch;
+ u_char spd;
+ u_char rcor, rbpr, badspeed = 0;
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ /*
+ * First probe channel zero of the chip, to see what speed has
+ * been selected.
+ */
+
+ base_addr[CyCAR] = 0;
+
+ rcor = base_addr[CyRCOR] << 5;
+ rbpr = base_addr[CyRBPR];
+
+ for (spd = 0; spd < sizeof(baud_bpr); spd++)
+ if (rbpr == baud_bpr[spd] && rcor == baud_co[spd])
+ break;
+ if (spd >= sizeof(baud_bpr)) {
+ spd = 14; /* 19200 */
+ badspeed = 1; /* Failed to identify speed */
+ }
+ initial_console_speed = spd;
+
+ /* OK, we have chosen a speed, now reset and reinitialise */
+
+ my_udelay(20000L); /* Allow time for any active o/p to complete */
+ if(base_addr[CyCCR] != 0x00){
+ local_irq_restore(flags);
+ /* printk(" chip is never idle (CCR != 0)\n"); */
+ return;
+ }
+
+ base_addr[CyCCR] = CyCHIP_RESET; /* Reset the chip */
+ my_udelay(1000L);
+
+ if(base_addr[CyGFRCR] == 0x00){
+ local_irq_restore(flags);
+ /* printk(" chip is not responding (GFRCR stayed 0)\n"); */
+ return;
+ }
+
+ /*
+ * System clock is 20Mhz, divided by 2048, so divide by 10 for a 1.0ms
+ * tick
+ */
+
+ base_addr[CyTPR] = 10;
+
+ base_addr[CyPILR1] = 0x01; /* Interrupt level for modem change */
+ base_addr[CyPILR2] = 0x02; /* Interrupt level for tx ints */
+ base_addr[CyPILR3] = 0x03; /* Interrupt level for rx ints */
+
+ /*
+ * Attempt to set up all channels to something reasonable, and
+ * bang out a INIT_CHAN command. We should then be able to limit
+ * the ammount of fiddling we have to do in normal running.
+ */
+
+ for (ch = 3; ch >= 0 ; ch--) {
+ base_addr[CyCAR] = (u_char)ch;
+ base_addr[CyIER] = 0;
+ base_addr[CyCMR] = CyASYNC;
+ base_addr[CyLICR] = (u_char)ch << 2;
+ base_addr[CyLIVR] = 0x5c;
+ base_addr[CyTCOR] = baud_co[spd];
+ base_addr[CyTBPR] = baud_bpr[spd];
+ base_addr[CyRCOR] = baud_co[spd] >> 5;
+ base_addr[CyRBPR] = baud_bpr[spd];
+ base_addr[CySCHR1] = 'Q' & 0x1f;
+ base_addr[CySCHR2] = 'X' & 0x1f;
+ base_addr[CySCRL] = 0;
+ base_addr[CySCRH] = 0;
+ base_addr[CyCOR1] = Cy_8_BITS | CyPARITY_NONE;
+ base_addr[CyCOR2] = 0;
+ base_addr[CyCOR3] = Cy_1_STOP;
+ base_addr[CyCOR4] = baud_cor4[spd];
+ base_addr[CyCOR5] = 0;
+ base_addr[CyCOR6] = 0;
+ base_addr[CyCOR7] = 0;
+ base_addr[CyRTPRL] = 2;
+ base_addr[CyRTPRH] = 0;
+ base_addr[CyMSVR1] = 0;
+ base_addr[CyMSVR2] = 0;
+ write_cy_cmd(base_addr,CyINIT_CHAN|CyDIS_RCVR|CyDIS_XMTR);
+ }
+
+ /*
+ * Now do specials for channel zero....
+ */
+
+ base_addr[CyMSVR1] = CyRTS;
+ base_addr[CyMSVR2] = CyDTR;
+ base_addr[CyIER] = CyRxData;
+ write_cy_cmd(base_addr,CyENB_RCVR|CyENB_XMTR);
+
+ local_irq_restore(flags);
+
+ my_udelay(20000L); /* Let it all settle down */
+
+ printk("CD2401 initialised, chip is rev 0x%02x\n", base_addr[CyGFRCR]);
+ if (badspeed)
+ printk(" WARNING: Failed to identify line speed, rcor=%02x,rbpr=%02x\n",
+ rcor >> 5, rbpr);
+} /* serial_console_init */
+
+static struct tty_operations cy_ops = {
+ .open = cy_open,
+ .close = cy_close,
+ .write = cy_write,
+ .put_char = cy_put_char,
+ .flush_chars = cy_flush_chars,
+ .write_room = cy_write_room,
+ .chars_in_buffer = cy_chars_in_buffer,
+ .flush_buffer = cy_flush_buffer,
+ .ioctl = cy_ioctl,
+ .throttle = cy_throttle,
+ .unthrottle = cy_unthrottle,
+ .set_termios = cy_set_termios,
+ .stop = cy_stop,
+ .start = cy_start,
+ .hangup = cy_hangup,
+ .tiocmget = cy_tiocmget,
+ .tiocmset = cy_tiocmset,
+};
+/* The serial driver boot-time initialization code!
+ Hardware I/O ports are mapped to character special devices on a
+ first found, first allocated manner. That is, this code searches
+ for Cyclom cards in the system. As each is found, it is probed
+ to discover how many chips (and thus how many ports) are present.
+ These ports are mapped to the tty ports 64 and upward in monotonic
+ fashion. If an 8-port card is replaced with a 16-port card, the
+ port mapping on a following card will shift.
+
+ This approach is different from what is used in the other serial
+ device driver because the Cyclom is more properly a multiplexer,
+ not just an aggregation of serial ports on one card.
+
+ If there are more cards with more ports than have been statically
+ allocated above, a warning is printed and the extra ports are ignored.
+ */
+static int __init
+serial167_init(void)
+{
+ struct cyclades_port *info;
+ int ret = 0;
+ int good_ports = 0;
+ int port_num = 0;
+ int index;
+ int DefSpeed;
+#ifdef notyet
+ struct sigaction sa;
+#endif
+
+ if (!(mvme16x_config &MVME16x_CONFIG_GOT_CD2401))
+ return 0;
+
+ cy_serial_driver = alloc_tty_driver(NR_PORTS);
+ if (!cy_serial_driver)
+ return -ENOMEM;
+
+#if 0
+scrn[1] = '\0';
+#endif
+
+ show_version();
+
+ /* Has "console=0,9600n8" been used in bootinfo to change speed? */
+ if (serial_console_cflag)
+ DefSpeed = serial_console_cflag & 0017;
+ else {
+ DefSpeed = initial_console_speed;
+ serial_console_info = &cy_port[0];
+ serial_console_cflag = DefSpeed | CS8;
+#if 0
+ serial_console = 64; /*callout_driver.minor_start*/
+#endif
+ }
+
+ /* Initialize the tty_driver structure */
+
+ cy_serial_driver->owner = THIS_MODULE;
+ cy_serial_driver->devfs_name = "tts/";
+ cy_serial_driver->name = "ttyS";
+ cy_serial_driver->major = TTY_MAJOR;
+ cy_serial_driver->minor_start = 64;
+ cy_serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ cy_serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ cy_serial_driver->init_termios = tty_std_termios;
+ cy_serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ cy_serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(cy_serial_driver, &cy_ops);
+
+ ret = tty_register_driver(cy_serial_driver);
+ if (ret) {
+ printk(KERN_ERR "Couldn't register MVME166/7 serial driver\n");
+ put_tty_driver(cy_serial_driver);
+ return ret;
+ }
+
+ port_num = 0;
+ info = cy_port;
+ for (index = 0; index < 1; index++) {
+
+ good_ports = 4;
+
+ if(port_num < NR_PORTS){
+ while( good_ports-- && port_num < NR_PORTS){
+ /*** initialize port ***/
+ info->magic = CYCLADES_MAGIC;
+ info->type = PORT_CIRRUS;
+ info->card = index;
+ info->line = port_num;
+ info->flags = STD_COM_FLAGS;
+ info->tty = 0;
+ info->xmit_fifo_size = 12;
+ info->cor1 = CyPARITY_NONE|Cy_8_BITS;
+ info->cor2 = CyETC;
+ info->cor3 = Cy_1_STOP;
+ info->cor4 = 0x08; /* _very_ small receive threshold */
+ info->cor5 = 0;
+ info->cor6 = 0;
+ info->cor7 = 0;
+ info->tbpr = baud_bpr[DefSpeed]; /* Tx BPR */
+ info->tco = baud_co[DefSpeed]; /* Tx CO */
+ info->rbpr = baud_bpr[DefSpeed]; /* Rx BPR */
+ info->rco = baud_co[DefSpeed] >> 5; /* Rx CO */
+ info->close_delay = 0;
+ info->x_char = 0;
+ info->event = 0;
+ info->count = 0;
+#ifdef SERIAL_DEBUG_COUNT
+ printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+ info->blocked_open = 0;
+ info->default_threshold = 0;
+ info->default_timeout = 0;
+ INIT_WORK(&info->tqueue, do_softint, info);
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ /* info->session */
+ /* info->pgrp */
+/*** !!!!!!!! this may expose new bugs !!!!!!!!! *********/
+ info->read_status_mask = CyTIMEOUT| CySPECHAR| CyBREAK
+ | CyPARITY| CyFRAME| CyOVERRUN;
+ /* info->timeout */
+
+ printk("ttyS%d ", info->line);
+ port_num++;info++;
+ if(!(port_num & 7)){
+ printk("\n ");
+ }
+ }
+ }
+ printk("\n");
+ }
+ while( port_num < NR_PORTS){
+ info->line = -1;
+ port_num++;info++;
+ }
+#ifdef CONFIG_REMOTE_DEBUG
+ debug_setup();
+#endif
+ ret = request_irq(MVME167_IRQ_SER_ERR, cd2401_rxerr_interrupt, 0,
+ "cd2401_errors", cd2401_rxerr_interrupt);
+ if (ret) {
+ printk(KERN_ERR "Could't get cd2401_errors IRQ");
+ goto cleanup_serial_driver;
+ }
+
+ ret = request_irq(MVME167_IRQ_SER_MODEM, cd2401_modem_interrupt, 0,
+ "cd2401_modem", cd2401_modem_interrupt);
+ if (ret) {
+ printk(KERN_ERR "Could't get cd2401_modem IRQ");
+ goto cleanup_irq_cd2401_errors;
+ }
+
+ ret = request_irq(MVME167_IRQ_SER_TX, cd2401_tx_interrupt, 0,
+ "cd2401_txints", cd2401_tx_interrupt);
+ if (ret) {
+ printk(KERN_ERR "Could't get cd2401_txints IRQ");
+ goto cleanup_irq_cd2401_modem;
+ }
+
+ ret = request_irq(MVME167_IRQ_SER_RX, cd2401_rx_interrupt, 0,
+ "cd2401_rxints", cd2401_rx_interrupt);
+ if (ret) {
+ printk(KERN_ERR "Could't get cd2401_rxints IRQ");
+ goto cleanup_irq_cd2401_txints;
+ }
+
+ /* Now we have registered the interrupt handlers, allow the interrupts */
+
+ pcc2chip[PccSCCMICR] = 0x15; /* Serial ints are level 5 */
+ pcc2chip[PccSCCTICR] = 0x15;
+ pcc2chip[PccSCCRICR] = 0x15;
+
+ pcc2chip[PccIMLR] = 3; /* Allow PCC2 ints above 3!? */
+
+ return 0;
+cleanup_irq_cd2401_txints:
+ free_irq(MVME167_IRQ_SER_TX, cd2401_tx_interrupt);
+cleanup_irq_cd2401_modem:
+ free_irq(MVME167_IRQ_SER_MODEM, cd2401_modem_interrupt);
+cleanup_irq_cd2401_errors:
+ free_irq(MVME167_IRQ_SER_ERR, cd2401_rxerr_interrupt);
+cleanup_serial_driver:
+ if (tty_unregister_driver(cy_serial_driver))
+ printk(KERN_ERR "Couldn't unregister MVME166/7 serial driver\n");
+ put_tty_driver(cy_serial_driver);
+ return ret;
+} /* serial167_init */
+
+module_init(serial167_init);
+
+
+#ifdef CYCLOM_SHOW_STATUS
+static void
+show_status(int line_num)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int channel;
+ struct cyclades_port * info;
+ unsigned long flags;
+
+ info = &cy_port[line_num];
+ channel = info->line;
+ printk(" channel %d\n", channel);/**/
+
+ printk(" cy_port\n");
+ printk(" card line flags = %d %d %x\n",
+ info->card, info->line, info->flags);
+ printk(" *tty read_status_mask timeout xmit_fifo_size = %lx %x %x %x\n",
+ (long)info->tty, info->read_status_mask,
+ info->timeout, info->xmit_fifo_size);
+ printk(" cor1,cor2,cor3,cor4,cor5,cor6,cor7 = %x %x %x %x %x %x %x\n",
+ info->cor1, info->cor2, info->cor3, info->cor4, info->cor5,
+ info->cor6, info->cor7);
+ printk(" tbpr,tco,rbpr,rco = %d %d %d %d\n",
+ info->tbpr, info->tco, info->rbpr, info->rco);
+ printk(" close_delay event count = %d %d %d\n",
+ info->close_delay, info->event, info->count);
+ printk(" x_char blocked_open = %x %x\n",
+ info->x_char, info->blocked_open);
+ printk(" open_wait = %lx %lx %lx\n",
+ (long)info->open_wait);
+
+
+ local_irq_save(flags);
+
+/* Global Registers */
+
+ printk(" CyGFRCR %x\n", base_addr[CyGFRCR]);
+ printk(" CyCAR %x\n", base_addr[CyCAR]);
+ printk(" CyRISR %x\n", base_addr[CyRISR]);
+ printk(" CyTISR %x\n", base_addr[CyTISR]);
+ printk(" CyMISR %x\n", base_addr[CyMISR]);
+ printk(" CyRIR %x\n", base_addr[CyRIR]);
+ printk(" CyTIR %x\n", base_addr[CyTIR]);
+ printk(" CyMIR %x\n", base_addr[CyMIR]);
+ printk(" CyTPR %x\n", base_addr[CyTPR]);
+
+ base_addr[CyCAR] = (u_char)channel;
+
+/* Virtual Registers */
+
+#if 0
+ printk(" CyRIVR %x\n", base_addr[CyRIVR]);
+ printk(" CyTIVR %x\n", base_addr[CyTIVR]);
+ printk(" CyMIVR %x\n", base_addr[CyMIVR]);
+ printk(" CyMISR %x\n", base_addr[CyMISR]);
+#endif
+
+/* Channel Registers */
+
+ printk(" CyCCR %x\n", base_addr[CyCCR]);
+ printk(" CyIER %x\n", base_addr[CyIER]);
+ printk(" CyCOR1 %x\n", base_addr[CyCOR1]);
+ printk(" CyCOR2 %x\n", base_addr[CyCOR2]);
+ printk(" CyCOR3 %x\n", base_addr[CyCOR3]);
+ printk(" CyCOR4 %x\n", base_addr[CyCOR4]);
+ printk(" CyCOR5 %x\n", base_addr[CyCOR5]);
+#if 0
+ printk(" CyCCSR %x\n", base_addr[CyCCSR]);
+ printk(" CyRDCR %x\n", base_addr[CyRDCR]);
+#endif
+ printk(" CySCHR1 %x\n", base_addr[CySCHR1]);
+ printk(" CySCHR2 %x\n", base_addr[CySCHR2]);
+#if 0
+ printk(" CySCHR3 %x\n", base_addr[CySCHR3]);
+ printk(" CySCHR4 %x\n", base_addr[CySCHR4]);
+ printk(" CySCRL %x\n", base_addr[CySCRL]);
+ printk(" CySCRH %x\n", base_addr[CySCRH]);
+ printk(" CyLNC %x\n", base_addr[CyLNC]);
+ printk(" CyMCOR1 %x\n", base_addr[CyMCOR1]);
+ printk(" CyMCOR2 %x\n", base_addr[CyMCOR2]);
+#endif
+ printk(" CyRTPRL %x\n", base_addr[CyRTPRL]);
+ printk(" CyRTPRH %x\n", base_addr[CyRTPRH]);
+ printk(" CyMSVR1 %x\n", base_addr[CyMSVR1]);
+ printk(" CyMSVR2 %x\n", base_addr[CyMSVR2]);
+ printk(" CyRBPR %x\n", base_addr[CyRBPR]);
+ printk(" CyRCOR %x\n", base_addr[CyRCOR]);
+ printk(" CyTBPR %x\n", base_addr[CyTBPR]);
+ printk(" CyTCOR %x\n", base_addr[CyTCOR]);
+
+ local_irq_restore(flags);
+} /* show_status */
+#endif
+
+
+#if 0
+/* Dummy routine in mvme16x/config.c for now */
+
+/* Serial console setup. Called from linux/init/main.c */
+
+void console_setup(char *str, int *ints)
+{
+ char *s;
+ int baud, bits, parity;
+ int cflag = 0;
+
+ /* Sanity check. */
+ if (ints[0] > 3 || ints[1] > 3) return;
+
+ /* Get baud, bits and parity */
+ baud = 2400;
+ bits = 8;
+ parity = 'n';
+ if (ints[2]) baud = ints[2];
+ if ((s = strchr(str, ','))) {
+ do {
+ s++;
+ } while(*s >= '0' && *s <= '9');
+ if (*s) parity = *s++;
+ if (*s) bits = *s - '0';
+ }
+
+ /* Now construct a cflag setting. */
+ switch(baud) {
+ case 1200:
+ cflag |= B1200;
+ break;
+ case 9600:
+ cflag |= B9600;
+ break;
+ case 19200:
+ cflag |= B19200;
+ break;
+ case 38400:
+ cflag |= B38400;
+ break;
+ case 2400:
+ default:
+ cflag |= B2400;
+ break;
+ }
+ switch(bits) {
+ case 7:
+ cflag |= CS7;
+ break;
+ default:
+ case 8:
+ cflag |= CS8;
+ break;
+ }
+ switch(parity) {
+ case 'o': case 'O':
+ cflag |= PARODD;
+ break;
+ case 'e': case 'E':
+ cflag |= PARENB;
+ break;
+ }
+
+ serial_console_info = &cy_port[ints[1]];
+ serial_console_cflag = cflag;
+ serial_console = ints[1] + 64; /*callout_driver.minor_start*/
+}
+#endif
+
+/*
+ * The following is probably out of date for 2.1.x serial console stuff.
+ *
+ * The console is registered early on from arch/m68k/kernel/setup.c, and
+ * it therefore relies on the chip being setup correctly by 166-Bug. This
+ * seems reasonable, as the serial port has been used to invoke the system
+ * boot. It also means that this function must not rely on any data
+ * initialisation performed by serial167_init() etc.
+ *
+ * Of course, once the console has been registered, we had better ensure
+ * that serial167_init() doesn't leave the chip non-functional.
+ *
+ * The console must be locked when we get here.
+ */
+
+void serial167_console_write(struct console *co, const char *str, unsigned count)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long flags;
+ volatile u_char sink;
+ u_char ier;
+ int port;
+ u_char do_lf = 0;
+ int i = 0;
+
+ local_irq_save(flags);
+
+ /* Ensure transmitter is enabled! */
+
+ port = 0;
+ base_addr[CyCAR] = (u_char)port;
+ while (base_addr[CyCCR])
+ ;
+ base_addr[CyCCR] = CyENB_XMTR;
+
+ ier = base_addr[CyIER];
+ base_addr[CyIER] = CyTxMpty;
+
+ while (1) {
+ if (pcc2chip[PccSCCTICR] & 0x20)
+ {
+ /* We have a Tx int. Acknowledge it */
+ sink = pcc2chip[PccTPIACKR];
+ if ((base_addr[CyLICR] >> 2) == port) {
+ if (i == count) {
+ /* Last char of string is now output */
+ base_addr[CyTEOIR] = CyNOTRANS;
+ break;
+ }
+ if (do_lf) {
+ base_addr[CyTDR] = '\n';
+ str++;
+ i++;
+ do_lf = 0;
+ }
+ else if (*str == '\n') {
+ base_addr[CyTDR] = '\r';
+ do_lf = 1;
+ }
+ else {
+ base_addr[CyTDR] = *str++;
+ i++;
+ }
+ base_addr[CyTEOIR] = 0;
+ }
+ else
+ base_addr[CyTEOIR] = CyNOTRANS;
+ }
+ }
+
+ base_addr[CyIER] = ier;
+
+ local_irq_restore(flags);
+}
+
+static struct tty_driver *serial167_console_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return cy_serial_driver;
+}
+
+
+static int __init serial167_console_setup(struct console *co, char *options)
+{
+ return 0;
+}
+
+
+static struct console sercons = {
+ .name = "ttyS",
+ .write = serial167_console_write,
+ .device = serial167_console_device,
+ .setup = serial167_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+
+static int __init serial167_console_init(void)
+{
+ if (vme_brdtype == VME_TYPE_MVME166 ||
+ vme_brdtype == VME_TYPE_MVME167 ||
+ vme_brdtype == VME_TYPE_MVME177) {
+ mvme167_serial_console_setup(0);
+ register_console(&sercons);
+ }
+ return 0;
+}
+console_initcall(serial167_console_init);
+
+#ifdef CONFIG_REMOTE_DEBUG
+void putDebugChar (int c)
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long flags;
+ volatile u_char sink;
+ u_char ier;
+ int port;
+
+ local_irq_save(flags);
+
+ /* Ensure transmitter is enabled! */
+
+ port = DEBUG_PORT;
+ base_addr[CyCAR] = (u_char)port;
+ while (base_addr[CyCCR])
+ ;
+ base_addr[CyCCR] = CyENB_XMTR;
+
+ ier = base_addr[CyIER];
+ base_addr[CyIER] = CyTxMpty;
+
+ while (1) {
+ if (pcc2chip[PccSCCTICR] & 0x20)
+ {
+ /* We have a Tx int. Acknowledge it */
+ sink = pcc2chip[PccTPIACKR];
+ if ((base_addr[CyLICR] >> 2) == port) {
+ base_addr[CyTDR] = c;
+ base_addr[CyTEOIR] = 0;
+ break;
+ }
+ else
+ base_addr[CyTEOIR] = CyNOTRANS;
+ }
+ }
+
+ base_addr[CyIER] = ier;
+
+ local_irq_restore(flags);
+}
+
+int getDebugChar()
+{
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ unsigned long flags;
+ volatile u_char sink;
+ u_char ier;
+ int port;
+ int i, c;
+
+ i = debugiq.out;
+ if (i != debugiq.in) {
+ c = debugiq.buf[i];
+ if (++i == DEBUG_LEN)
+ i = 0;
+ debugiq.out = i;
+ return c;
+ }
+ /* OK, nothing in queue, wait in poll loop */
+
+ local_irq_save(flags);
+
+ /* Ensure receiver is enabled! */
+
+ port = DEBUG_PORT;
+ base_addr[CyCAR] = (u_char)port;
+#if 0
+ while (base_addr[CyCCR])
+ ;
+ base_addr[CyCCR] = CyENB_RCVR;
+#endif
+ ier = base_addr[CyIER];
+ base_addr[CyIER] = CyRxData;
+
+ while (1) {
+ if (pcc2chip[PccSCCRICR] & 0x20)
+ {
+ /* We have a Rx int. Acknowledge it */
+ sink = pcc2chip[PccRPIACKR];
+ if ((base_addr[CyLICR] >> 2) == port) {
+ int cnt = base_addr[CyRFOC];
+ while (cnt-- > 0)
+ {
+ c = base_addr[CyRDR];
+ if (c == 0)
+ printk ("!! debug char is null (cnt=%d) !!", cnt);
+ else
+ queueDebugChar (c);
+ }
+ base_addr[CyREOIR] = 0;
+ i = debugiq.out;
+ if (i == debugiq.in)
+ panic ("Debug input queue empty!");
+ c = debugiq.buf[i];
+ if (++i == DEBUG_LEN)
+ i = 0;
+ debugiq.out = i;
+ break;
+ }
+ else
+ base_addr[CyREOIR] = CyNOTRANS;
+ }
+ }
+
+ base_addr[CyIER] = ier;
+
+ local_irq_restore(flags);
+
+ return (c);
+}
+
+void queueDebugChar (int c)
+{
+ int i;
+
+ i = debugiq.in;
+ debugiq.buf[i] = c;
+ if (++i == DEBUG_LEN)
+ i = 0;
+ if (i != debugiq.out)
+ debugiq.in = i;
+}
+
+static void
+debug_setup()
+{
+ unsigned long flags;
+ volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+ int i, cflag;
+
+ cflag = B19200;
+
+ local_irq_save(flags);
+
+ for (i = 0; i < 4; i++)
+ {
+ base_addr[CyCAR] = i;
+ base_addr[CyLICR] = i << 2;
+ }
+
+ debugiq.in = debugiq.out = 0;
+
+ base_addr[CyCAR] = DEBUG_PORT;
+
+ /* baud rate */
+ i = cflag & CBAUD;
+
+ base_addr[CyIER] = 0;
+
+ base_addr[CyCMR] = CyASYNC;
+ base_addr[CyLICR] = DEBUG_PORT << 2;
+ base_addr[CyLIVR] = 0x5c;
+
+ /* tx and rx baud rate */
+
+ base_addr[CyTCOR] = baud_co[i];
+ base_addr[CyTBPR] = baud_bpr[i];
+ base_addr[CyRCOR] = baud_co[i] >> 5;
+ base_addr[CyRBPR] = baud_bpr[i];
+
+ /* set line characteristics according configuration */
+
+ base_addr[CySCHR1] = 0;
+ base_addr[CySCHR2] = 0;
+ base_addr[CySCRL] = 0;
+ base_addr[CySCRH] = 0;
+ base_addr[CyCOR1] = Cy_8_BITS | CyPARITY_NONE;
+ base_addr[CyCOR2] = 0;
+ base_addr[CyCOR3] = Cy_1_STOP;
+ base_addr[CyCOR4] = baud_cor4[i];
+ base_addr[CyCOR5] = 0;
+ base_addr[CyCOR6] = 0;
+ base_addr[CyCOR7] = 0;
+
+ write_cy_cmd(base_addr,CyINIT_CHAN);
+ write_cy_cmd(base_addr,CyENB_RCVR);
+
+ base_addr[CyCAR] = DEBUG_PORT; /* !!! Is this needed? */
+
+ base_addr[CyRTPRL] = 2;
+ base_addr[CyRTPRH] = 0;
+
+ base_addr[CyMSVR1] = CyRTS;
+ base_addr[CyMSVR2] = CyDTR;
+
+ base_addr[CyIER] = CyRxData;
+
+ local_irq_restore(flags);
+
+} /* debug_setup */
+
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/snsc.c b/drivers/char/snsc.c
new file mode 100644
index 000000000000..ffb9143376bb
--- /dev/null
+++ b/drivers/char/snsc.c
@@ -0,0 +1,448 @@
+/*
+ * SN Platform system controller communication support
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2004 Silicon Graphics, Inc. All rights reserved.
+ */
+
+/*
+ * System controller communication driver
+ *
+ * This driver allows a user process to communicate with the system
+ * controller (a.k.a. "IRouter") network in an SGI SN system.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/sn/io.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/module.h>
+#include <asm/sn/geo.h>
+#include <asm/sn/nodepda.h>
+#include "snsc.h"
+
+#define SYSCTL_BASENAME "snsc"
+
+#define SCDRV_BUFSZ 2048
+#define SCDRV_TIMEOUT 1000
+
+static irqreturn_t
+scdrv_interrupt(int irq, void *subch_data, struct pt_regs *regs)
+{
+ struct subch_data_s *sd = subch_data;
+ unsigned long flags;
+ int status;
+
+ spin_lock_irqsave(&sd->sd_rlock, flags);
+ spin_lock(&sd->sd_wlock);
+ status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);
+
+ if (status > 0) {
+ if (status & SAL_IROUTER_INTR_RECV) {
+ wake_up(&sd->sd_rq);
+ }
+ if (status & SAL_IROUTER_INTR_XMIT) {
+ ia64_sn_irtr_intr_disable
+ (sd->sd_nasid, sd->sd_subch,
+ SAL_IROUTER_INTR_XMIT);
+ wake_up(&sd->sd_wq);
+ }
+ }
+ spin_unlock(&sd->sd_wlock);
+ spin_unlock_irqrestore(&sd->sd_rlock, flags);
+ return IRQ_HANDLED;
+}
+
+/*
+ * scdrv_open
+ *
+ * Reserve a subchannel for system controller communication.
+ */
+
+static int
+scdrv_open(struct inode *inode, struct file *file)
+{
+ struct sysctl_data_s *scd;
+ struct subch_data_s *sd;
+ int rv;
+
+ /* look up device info for this device file */
+ scd = container_of(inode->i_cdev, struct sysctl_data_s, scd_cdev);
+
+ /* allocate memory for subchannel data */
+ sd = kmalloc(sizeof (struct subch_data_s), GFP_KERNEL);
+ if (sd == NULL) {
+ printk("%s: couldn't allocate subchannel data\n",
+ __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ /* initialize subch_data_s fields */
+ memset(sd, 0, sizeof (struct subch_data_s));
+ sd->sd_nasid = scd->scd_nasid;
+ sd->sd_subch = ia64_sn_irtr_open(scd->scd_nasid);
+
+ if (sd->sd_subch < 0) {
+ kfree(sd);
+ printk("%s: couldn't allocate subchannel\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ spin_lock_init(&sd->sd_rlock);
+ spin_lock_init(&sd->sd_wlock);
+ init_waitqueue_head(&sd->sd_rq);
+ init_waitqueue_head(&sd->sd_wq);
+ sema_init(&sd->sd_rbs, 1);
+ sema_init(&sd->sd_wbs, 1);
+
+ file->private_data = sd;
+
+ /* hook this subchannel up to the system controller interrupt */
+ rv = request_irq(SGI_UART_VECTOR, scdrv_interrupt,
+ SA_SHIRQ | SA_INTERRUPT,
+ SYSCTL_BASENAME, sd);
+ if (rv) {
+ ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch);
+ kfree(sd);
+ printk("%s: irq request failed (%d)\n", __FUNCTION__, rv);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/*
+ * scdrv_release
+ *
+ * Release a previously-reserved subchannel.
+ */
+
+static int
+scdrv_release(struct inode *inode, struct file *file)
+{
+ struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+ int rv;
+
+ /* free the interrupt */
+ free_irq(SGI_UART_VECTOR, sd);
+
+ /* ask SAL to close the subchannel */
+ rv = ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch);
+
+ kfree(sd);
+ return rv;
+}
+
+/*
+ * scdrv_read
+ *
+ * Called to read bytes from the open IRouter pipe.
+ *
+ */
+
+static inline int
+read_status_check(struct subch_data_s *sd, int *len)
+{
+ return ia64_sn_irtr_recv(sd->sd_nasid, sd->sd_subch, sd->sd_rb, len);
+}
+
+static ssize_t
+scdrv_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+ int status;
+ int len;
+ unsigned long flags;
+ struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+
+ /* try to get control of the read buffer */
+ if (down_trylock(&sd->sd_rbs)) {
+ /* somebody else has it now;
+ * if we're non-blocking, then exit...
+ */
+ if (file->f_flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+ /* ...or if we want to block, then do so here */
+ if (down_interruptible(&sd->sd_rbs)) {
+ /* something went wrong with wait */
+ return -ERESTARTSYS;
+ }
+ }
+
+ /* anything to read? */
+ len = CHUNKSIZE;
+ spin_lock_irqsave(&sd->sd_rlock, flags);
+ status = read_status_check(sd, &len);
+
+ /* if not, and we're blocking I/O, loop */
+ while (status < 0) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock_irqrestore(&sd->sd_rlock, flags);
+ up(&sd->sd_rbs);
+ return -EAGAIN;
+ }
+
+ len = CHUNKSIZE;
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&sd->sd_rq, &wait);
+ spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+ schedule_timeout(SCDRV_TIMEOUT);
+
+ remove_wait_queue(&sd->sd_rq, &wait);
+ if (signal_pending(current)) {
+ /* wait was interrupted */
+ up(&sd->sd_rbs);
+ return -ERESTARTSYS;
+ }
+
+ spin_lock_irqsave(&sd->sd_rlock, flags);
+ status = read_status_check(sd, &len);
+ }
+ spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+ if (len > 0) {
+ /* we read something in the last read_status_check(); copy
+ * it out to user space
+ */
+ if (count < len) {
+ pr_debug("%s: only accepting %d of %d bytes\n",
+ __FUNCTION__, (int) count, len);
+ }
+ len = min((int) count, len);
+ if (copy_to_user(buf, sd->sd_rb, len))
+ len = -EFAULT;
+ }
+
+ /* release the read buffer and wake anyone who might be
+ * waiting for it
+ */
+ up(&sd->sd_rbs);
+
+ /* return the number of characters read in */
+ return len;
+}
+
+/*
+ * scdrv_write
+ *
+ * Writes a chunk of an IRouter packet (or other system controller data)
+ * to the system controller.
+ *
+ */
+static inline int
+write_status_check(struct subch_data_s *sd, int count)
+{
+ return ia64_sn_irtr_send(sd->sd_nasid, sd->sd_subch, sd->sd_wb, count);
+}
+
+static ssize_t
+scdrv_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ unsigned long flags;
+ int status;
+ struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+
+ /* try to get control of the write buffer */
+ if (down_trylock(&sd->sd_wbs)) {
+ /* somebody else has it now;
+ * if we're non-blocking, then exit...
+ */
+ if (file->f_flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+ /* ...or if we want to block, then do so here */
+ if (down_interruptible(&sd->sd_wbs)) {
+ /* something went wrong with wait */
+ return -ERESTARTSYS;
+ }
+ }
+
+ count = min((int) count, CHUNKSIZE);
+ if (copy_from_user(sd->sd_wb, buf, count)) {
+ up(&sd->sd_wbs);
+ return -EFAULT;
+ }
+
+ /* try to send the buffer */
+ spin_lock_irqsave(&sd->sd_wlock, flags);
+ status = write_status_check(sd, count);
+
+ /* if we failed, and we want to block, then loop */
+ while (status <= 0) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (file->f_flags & O_NONBLOCK) {
+ spin_unlock(&sd->sd_wlock);
+ up(&sd->sd_wbs);
+ return -EAGAIN;
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&sd->sd_wq, &wait);
+ spin_unlock_irqrestore(&sd->sd_wlock, flags);
+
+ schedule_timeout(SCDRV_TIMEOUT);
+
+ remove_wait_queue(&sd->sd_wq, &wait);
+ if (signal_pending(current)) {
+ /* wait was interrupted */
+ up(&sd->sd_wbs);
+ return -ERESTARTSYS;
+ }
+
+ spin_lock_irqsave(&sd->sd_wlock, flags);
+ status = write_status_check(sd, count);
+ }
+ spin_unlock_irqrestore(&sd->sd_wlock, flags);
+
+ /* release the write buffer and wake anyone who's waiting for it */
+ up(&sd->sd_wbs);
+
+ /* return the number of characters accepted (should be the complete
+ * "chunk" as requested)
+ */
+ if ((status >= 0) && (status < count)) {
+ pr_debug("Didn't accept the full chunk; %d of %d\n",
+ status, (int) count);
+ }
+ return status;
+}
+
+static unsigned int
+scdrv_poll(struct file *file, struct poll_table_struct *wait)
+{
+ unsigned int mask = 0;
+ int status = 0;
+ struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+ unsigned long flags;
+
+ poll_wait(file, &sd->sd_rq, wait);
+ poll_wait(file, &sd->sd_wq, wait);
+
+ spin_lock_irqsave(&sd->sd_rlock, flags);
+ spin_lock(&sd->sd_wlock);
+ status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);
+ spin_unlock(&sd->sd_wlock);
+ spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+ if (status > 0) {
+ if (status & SAL_IROUTER_INTR_RECV) {
+ mask |= POLLIN | POLLRDNORM;
+ }
+ if (status & SAL_IROUTER_INTR_XMIT) {
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ }
+
+ return mask;
+}
+
+static struct file_operations scdrv_fops = {
+ .owner = THIS_MODULE,
+ .read = scdrv_read,
+ .write = scdrv_write,
+ .poll = scdrv_poll,
+ .open = scdrv_open,
+ .release = scdrv_release,
+};
+
+/*
+ * scdrv_init
+ *
+ * Called at boot time to initialize the system controller communication
+ * facility.
+ */
+int __init
+scdrv_init(void)
+{
+ geoid_t geoid;
+ cnodeid_t cnode;
+ char devname[32];
+ char *devnamep;
+ struct sysctl_data_s *scd;
+ void *salbuf;
+ struct class_simple *snsc_class;
+ dev_t first_dev, dev;
+
+ if (alloc_chrdev_region(&first_dev, 0, numionodes,
+ SYSCTL_BASENAME) < 0) {
+ printk("%s: failed to register SN system controller device\n",
+ __FUNCTION__);
+ return -ENODEV;
+ }
+ snsc_class = class_simple_create(THIS_MODULE, SYSCTL_BASENAME);
+
+ for (cnode = 0; cnode < numionodes; cnode++) {
+ geoid = cnodeid_get_geoid(cnode);
+ devnamep = devname;
+ format_module_id(devnamep, geo_module(geoid),
+ MODULE_FORMAT_BRIEF);
+ devnamep = devname + strlen(devname);
+ sprintf(devnamep, "#%d", geo_slab(geoid));
+
+ /* allocate sysctl device data */
+ scd = kmalloc(sizeof (struct sysctl_data_s),
+ GFP_KERNEL);
+ if (!scd) {
+ printk("%s: failed to allocate device info"
+ "for %s/%s\n", __FUNCTION__,
+ SYSCTL_BASENAME, devname);
+ continue;
+ }
+ memset(scd, 0, sizeof (struct sysctl_data_s));
+
+ /* initialize sysctl device data fields */
+ scd->scd_nasid = cnodeid_to_nasid(cnode);
+ if (!(salbuf = kmalloc(SCDRV_BUFSZ, GFP_KERNEL))) {
+ printk("%s: failed to allocate driver buffer"
+ "(%s%s)\n", __FUNCTION__,
+ SYSCTL_BASENAME, devname);
+ kfree(scd);
+ continue;
+ }
+
+ if (ia64_sn_irtr_init(scd->scd_nasid, salbuf,
+ SCDRV_BUFSZ) < 0) {
+ printk
+ ("%s: failed to initialize SAL for"
+ " system controller communication"
+ " (%s/%s): outdated PROM?\n",
+ __FUNCTION__, SYSCTL_BASENAME, devname);
+ kfree(scd);
+ kfree(salbuf);
+ continue;
+ }
+
+ dev = first_dev + cnode;
+ cdev_init(&scd->scd_cdev, &scdrv_fops);
+ if (cdev_add(&scd->scd_cdev, dev, 1)) {
+ printk("%s: failed to register system"
+ " controller device (%s%s)\n",
+ __FUNCTION__, SYSCTL_BASENAME, devname);
+ kfree(scd);
+ kfree(salbuf);
+ continue;
+ }
+
+ class_simple_device_add(snsc_class, dev, NULL,
+ "%s", devname);
+
+ ia64_sn_irtr_intr_enable(scd->scd_nasid,
+ 0 /*ignored */ ,
+ SAL_IROUTER_INTR_RECV);
+ }
+ return 0;
+}
+
+module_init(scdrv_init);
diff --git a/drivers/char/snsc.h b/drivers/char/snsc.h
new file mode 100644
index 000000000000..c22c6c55e254
--- /dev/null
+++ b/drivers/char/snsc.h
@@ -0,0 +1,50 @@
+/*
+ * SN Platform system controller communication support
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2004 Silicon Graphics, Inc. All rights reserved.
+ */
+
+/*
+ * This file contains macros and data types for communication with the
+ * system controllers in SGI SN systems.
+ */
+
+#ifndef _SN_SYSCTL_H_
+#define _SN_SYSCTL_H_
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <asm/sn/types.h>
+#include <asm/semaphore.h>
+
+#define CHUNKSIZE 127
+
+/* This structure is used to track an open subchannel. */
+struct subch_data_s {
+ nasid_t sd_nasid; /* node on which the subchannel was opened */
+ int sd_subch; /* subchannel number */
+ spinlock_t sd_rlock; /* monitor lock for rsv */
+ spinlock_t sd_wlock; /* monitor lock for wsv */
+ wait_queue_head_t sd_rq; /* wait queue for readers */
+ wait_queue_head_t sd_wq; /* wait queue for writers */
+ struct semaphore sd_rbs; /* semaphore for read buffer */
+ struct semaphore sd_wbs; /* semaphore for write buffer */
+
+ char sd_rb[CHUNKSIZE]; /* read buffer */
+ char sd_wb[CHUNKSIZE]; /* write buffer */
+};
+
+struct sysctl_data_s {
+ struct cdev scd_cdev; /* Character device info */
+ nasid_t scd_nasid; /* Node on which subchannels are opened. */
+};
+
+#endif /* _SN_SYSCTL_H_ */
diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c
new file mode 100644
index 000000000000..f97a8a9751a0
--- /dev/null
+++ b/drivers/char/sonypi.c
@@ -0,0 +1,1403 @@
+/*
+ * Sony Programmable I/O Control Device driver for VAIO
+ *
+ * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
+ *
+ * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
+ *
+ * Copyright (C) 2001-2002 Alcôve <www.alcove.com>
+ *
+ * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au>
+ *
+ * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp>
+ *
+ * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp>
+ *
+ * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com>
+ *
+ * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/kfifo.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/sonypi.h>
+
+#define SONYPI_DRIVER_VERSION "1.26"
+
+MODULE_AUTHOR("Stelian Pop <stelian@popies.net>");
+MODULE_DESCRIPTION("Sony Programmable I/O Control Device driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SONYPI_DRIVER_VERSION);
+
+static int minor = -1;
+module_param(minor, int, 0);
+MODULE_PARM_DESC(minor,
+ "minor number of the misc device, default is -1 (automatic)");
+
+static int verbose; /* = 0 */
+module_param(verbose, int, 0644);
+MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
+
+static int fnkeyinit; /* = 0 */
+module_param(fnkeyinit, int, 0444);
+MODULE_PARM_DESC(fnkeyinit,
+ "set this if your Fn keys do not generate any event");
+
+static int camera; /* = 0 */
+module_param(camera, int, 0444);
+MODULE_PARM_DESC(camera,
+ "set this if you have a MotionEye camera (PictureBook series)");
+
+static int compat; /* = 0 */
+module_param(compat, int, 0444);
+MODULE_PARM_DESC(compat,
+ "set this if you want to enable backward compatibility mode");
+
+static unsigned long mask = 0xffffffff;
+module_param(mask, ulong, 0644);
+MODULE_PARM_DESC(mask,
+ "set this to the mask of event you want to enable (see doc)");
+
+static int useinput = 1;
+module_param(useinput, int, 0444);
+MODULE_PARM_DESC(useinput,
+ "set this if you would like sonypi to feed events to the input subsystem");
+
+#define SONYPI_DEVICE_MODEL_TYPE1 1
+#define SONYPI_DEVICE_MODEL_TYPE2 2
+
+/* type1 models use those */
+#define SONYPI_IRQ_PORT 0x8034
+#define SONYPI_IRQ_SHIFT 22
+#define SONYPI_BASE 0x50
+#define SONYPI_G10A (SONYPI_BASE+0x14)
+#define SONYPI_TYPE1_REGION_SIZE 0x08
+#define SONYPI_TYPE1_EVTYPE_OFFSET 0x04
+
+/* type2 series specifics */
+#define SONYPI_SIRQ 0x9b
+#define SONYPI_SLOB 0x9c
+#define SONYPI_SHIB 0x9d
+#define SONYPI_TYPE2_REGION_SIZE 0x20
+#define SONYPI_TYPE2_EVTYPE_OFFSET 0x12
+
+/* battery / brightness addresses */
+#define SONYPI_BAT_FLAGS 0x81
+#define SONYPI_LCD_LIGHT 0x96
+#define SONYPI_BAT1_PCTRM 0xa0
+#define SONYPI_BAT1_LEFT 0xa2
+#define SONYPI_BAT1_MAXRT 0xa4
+#define SONYPI_BAT2_PCTRM 0xa8
+#define SONYPI_BAT2_LEFT 0xaa
+#define SONYPI_BAT2_MAXRT 0xac
+#define SONYPI_BAT1_MAXTK 0xb0
+#define SONYPI_BAT1_FULL 0xb2
+#define SONYPI_BAT2_MAXTK 0xb8
+#define SONYPI_BAT2_FULL 0xba
+
+/* FAN0 information (reverse engineered from ACPI tables) */
+#define SONYPI_FAN0_STATUS 0x93
+#define SONYPI_TEMP_STATUS 0xC1
+
+/* ioports used for brightness and type2 events */
+#define SONYPI_DATA_IOPORT 0x62
+#define SONYPI_CST_IOPORT 0x66
+
+/* The set of possible ioports */
+struct sonypi_ioport_list {
+ u16 port1;
+ u16 port2;
+};
+
+static struct sonypi_ioport_list sonypi_type1_ioport_list[] = {
+ { 0x10c0, 0x10c4 }, /* looks like the default on C1Vx */
+ { 0x1080, 0x1084 },
+ { 0x1090, 0x1094 },
+ { 0x10a0, 0x10a4 },
+ { 0x10b0, 0x10b4 },
+ { 0x0, 0x0 }
+};
+
+static struct sonypi_ioport_list sonypi_type2_ioport_list[] = {
+ { 0x1080, 0x1084 },
+ { 0x10a0, 0x10a4 },
+ { 0x10c0, 0x10c4 },
+ { 0x10e0, 0x10e4 },
+ { 0x0, 0x0 }
+};
+
+/* The set of possible interrupts */
+struct sonypi_irq_list {
+ u16 irq;
+ u16 bits;
+};
+
+static struct sonypi_irq_list sonypi_type1_irq_list[] = {
+ { 11, 0x2 }, /* IRQ 11, GO22=0,GO23=1 in AML */
+ { 10, 0x1 }, /* IRQ 10, GO22=1,GO23=0 in AML */
+ { 5, 0x0 }, /* IRQ 5, GO22=0,GO23=0 in AML */
+ { 0, 0x3 } /* no IRQ, GO22=1,GO23=1 in AML */
+};
+
+static struct sonypi_irq_list sonypi_type2_irq_list[] = {
+ { 11, 0x80 }, /* IRQ 11, 0x80 in SIRQ in AML */
+ { 10, 0x40 }, /* IRQ 10, 0x40 in SIRQ in AML */
+ { 9, 0x20 }, /* IRQ 9, 0x20 in SIRQ in AML */
+ { 6, 0x10 }, /* IRQ 6, 0x10 in SIRQ in AML */
+ { 0, 0x00 } /* no IRQ, 0x00 in SIRQ in AML */
+};
+
+#define SONYPI_CAMERA_BRIGHTNESS 0
+#define SONYPI_CAMERA_CONTRAST 1
+#define SONYPI_CAMERA_HUE 2
+#define SONYPI_CAMERA_COLOR 3
+#define SONYPI_CAMERA_SHARPNESS 4
+
+#define SONYPI_CAMERA_PICTURE 5
+#define SONYPI_CAMERA_EXPOSURE_MASK 0xC
+#define SONYPI_CAMERA_WHITE_BALANCE_MASK 0x3
+#define SONYPI_CAMERA_PICTURE_MODE_MASK 0x30
+#define SONYPI_CAMERA_MUTE_MASK 0x40
+
+/* the rest don't need a loop until not 0xff */
+#define SONYPI_CAMERA_AGC 6
+#define SONYPI_CAMERA_AGC_MASK 0x30
+#define SONYPI_CAMERA_SHUTTER_MASK 0x7
+
+#define SONYPI_CAMERA_SHUTDOWN_REQUEST 7
+#define SONYPI_CAMERA_CONTROL 0x10
+
+#define SONYPI_CAMERA_STATUS 7
+#define SONYPI_CAMERA_STATUS_READY 0x2
+#define SONYPI_CAMERA_STATUS_POSITION 0x4
+
+#define SONYPI_DIRECTION_BACKWARDS 0x4
+
+#define SONYPI_CAMERA_REVISION 8
+#define SONYPI_CAMERA_ROMVERSION 9
+
+/* Event masks */
+#define SONYPI_JOGGER_MASK 0x00000001
+#define SONYPI_CAPTURE_MASK 0x00000002
+#define SONYPI_FNKEY_MASK 0x00000004
+#define SONYPI_BLUETOOTH_MASK 0x00000008
+#define SONYPI_PKEY_MASK 0x00000010
+#define SONYPI_BACK_MASK 0x00000020
+#define SONYPI_HELP_MASK 0x00000040
+#define SONYPI_LID_MASK 0x00000080
+#define SONYPI_ZOOM_MASK 0x00000100
+#define SONYPI_THUMBPHRASE_MASK 0x00000200
+#define SONYPI_MEYE_MASK 0x00000400
+#define SONYPI_MEMORYSTICK_MASK 0x00000800
+#define SONYPI_BATTERY_MASK 0x00001000
+
+struct sonypi_event {
+ u8 data;
+ u8 event;
+};
+
+/* The set of possible button release events */
+static struct sonypi_event sonypi_releaseev[] = {
+ { 0x00, SONYPI_EVENT_ANYBUTTON_RELEASED },
+ { 0, 0 }
+};
+
+/* The set of possible jogger events */
+static struct sonypi_event sonypi_joggerev[] = {
+ { 0x1f, SONYPI_EVENT_JOGDIAL_UP },
+ { 0x01, SONYPI_EVENT_JOGDIAL_DOWN },
+ { 0x5f, SONYPI_EVENT_JOGDIAL_UP_PRESSED },
+ { 0x41, SONYPI_EVENT_JOGDIAL_DOWN_PRESSED },
+ { 0x1e, SONYPI_EVENT_JOGDIAL_FAST_UP },
+ { 0x02, SONYPI_EVENT_JOGDIAL_FAST_DOWN },
+ { 0x5e, SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED },
+ { 0x42, SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED },
+ { 0x1d, SONYPI_EVENT_JOGDIAL_VFAST_UP },
+ { 0x03, SONYPI_EVENT_JOGDIAL_VFAST_DOWN },
+ { 0x5d, SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED },
+ { 0x43, SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED },
+ { 0x40, SONYPI_EVENT_JOGDIAL_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible capture button events */
+static struct sonypi_event sonypi_captureev[] = {
+ { 0x05, SONYPI_EVENT_CAPTURE_PARTIALPRESSED },
+ { 0x07, SONYPI_EVENT_CAPTURE_PRESSED },
+ { 0x01, SONYPI_EVENT_CAPTURE_PARTIALRELEASED },
+ { 0, 0 }
+};
+
+/* The set of possible fnkeys events */
+static struct sonypi_event sonypi_fnkeyev[] = {
+ { 0x10, SONYPI_EVENT_FNKEY_ESC },
+ { 0x11, SONYPI_EVENT_FNKEY_F1 },
+ { 0x12, SONYPI_EVENT_FNKEY_F2 },
+ { 0x13, SONYPI_EVENT_FNKEY_F3 },
+ { 0x14, SONYPI_EVENT_FNKEY_F4 },
+ { 0x15, SONYPI_EVENT_FNKEY_F5 },
+ { 0x16, SONYPI_EVENT_FNKEY_F6 },
+ { 0x17, SONYPI_EVENT_FNKEY_F7 },
+ { 0x18, SONYPI_EVENT_FNKEY_F8 },
+ { 0x19, SONYPI_EVENT_FNKEY_F9 },
+ { 0x1a, SONYPI_EVENT_FNKEY_F10 },
+ { 0x1b, SONYPI_EVENT_FNKEY_F11 },
+ { 0x1c, SONYPI_EVENT_FNKEY_F12 },
+ { 0x1f, SONYPI_EVENT_FNKEY_RELEASED },
+ { 0x21, SONYPI_EVENT_FNKEY_1 },
+ { 0x22, SONYPI_EVENT_FNKEY_2 },
+ { 0x31, SONYPI_EVENT_FNKEY_D },
+ { 0x32, SONYPI_EVENT_FNKEY_E },
+ { 0x33, SONYPI_EVENT_FNKEY_F },
+ { 0x34, SONYPI_EVENT_FNKEY_S },
+ { 0x35, SONYPI_EVENT_FNKEY_B },
+ { 0x36, SONYPI_EVENT_FNKEY_ONLY },
+ { 0, 0 }
+};
+
+/* The set of possible program key events */
+static struct sonypi_event sonypi_pkeyev[] = {
+ { 0x01, SONYPI_EVENT_PKEY_P1 },
+ { 0x02, SONYPI_EVENT_PKEY_P2 },
+ { 0x04, SONYPI_EVENT_PKEY_P3 },
+ { 0x5c, SONYPI_EVENT_PKEY_P1 },
+ { 0, 0 }
+};
+
+/* The set of possible bluetooth events */
+static struct sonypi_event sonypi_blueev[] = {
+ { 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED },
+ { 0x59, SONYPI_EVENT_BLUETOOTH_ON },
+ { 0x5a, SONYPI_EVENT_BLUETOOTH_OFF },
+ { 0, 0 }
+};
+
+/* The set of possible back button events */
+static struct sonypi_event sonypi_backev[] = {
+ { 0x20, SONYPI_EVENT_BACK_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible help button events */
+static struct sonypi_event sonypi_helpev[] = {
+ { 0x3b, SONYPI_EVENT_HELP_PRESSED },
+ { 0, 0 }
+};
+
+
+/* The set of possible lid events */
+static struct sonypi_event sonypi_lidev[] = {
+ { 0x51, SONYPI_EVENT_LID_CLOSED },
+ { 0x50, SONYPI_EVENT_LID_OPENED },
+ { 0, 0 }
+};
+
+/* The set of possible zoom events */
+static struct sonypi_event sonypi_zoomev[] = {
+ { 0x39, SONYPI_EVENT_ZOOM_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible thumbphrase events */
+static struct sonypi_event sonypi_thumbphraseev[] = {
+ { 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED },
+ { 0, 0 }
+};
+
+/* The set of possible motioneye camera events */
+static struct sonypi_event sonypi_meyeev[] = {
+ { 0x00, SONYPI_EVENT_MEYE_FACE },
+ { 0x01, SONYPI_EVENT_MEYE_OPPOSITE },
+ { 0, 0 }
+};
+
+/* The set of possible memorystick events */
+static struct sonypi_event sonypi_memorystickev[] = {
+ { 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT },
+ { 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT },
+ { 0, 0 }
+};
+
+/* The set of possible battery events */
+static struct sonypi_event sonypi_batteryev[] = {
+ { 0x20, SONYPI_EVENT_BATTERY_INSERT },
+ { 0x30, SONYPI_EVENT_BATTERY_REMOVE },
+ { 0, 0 }
+};
+
+static struct sonypi_eventtypes {
+ int model;
+ u8 data;
+ unsigned long mask;
+ struct sonypi_event * events;
+} sonypi_eventtypes[] = {
+ { SONYPI_DEVICE_MODEL_TYPE1, 0, 0xffffffff, sonypi_releaseev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x70, SONYPI_MEYE_MASK, sonypi_meyeev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_LID_MASK, sonypi_lidev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+ { SONYPI_DEVICE_MODEL_TYPE1, 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev },
+
+ { SONYPI_DEVICE_MODEL_TYPE2, 0, 0xffffffff, sonypi_releaseev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x38, SONYPI_LID_MASK, sonypi_lidev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x11, SONYPI_BACK_MASK, sonypi_backev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x08, SONYPI_HELP_MASK, sonypi_helpev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_HELP_MASK, sonypi_helpev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+ { SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+
+ { 0 }
+};
+
+#define SONYPI_BUF_SIZE 128
+
+/* The name of the devices for the input device drivers */
+#define SONYPI_JOG_INPUTNAME "Sony Vaio Jogdial"
+#define SONYPI_KEY_INPUTNAME "Sony Vaio Keys"
+
+/* Correspondance table between sonypi events and input layer events */
+static struct {
+ int sonypiev;
+ int inputev;
+} sonypi_inputkeys[] = {
+ { SONYPI_EVENT_CAPTURE_PRESSED, KEY_CAMERA },
+ { SONYPI_EVENT_FNKEY_ONLY, KEY_FN },
+ { SONYPI_EVENT_FNKEY_ESC, KEY_FN_ESC },
+ { SONYPI_EVENT_FNKEY_F1, KEY_FN_F1 },
+ { SONYPI_EVENT_FNKEY_F2, KEY_FN_F2 },
+ { SONYPI_EVENT_FNKEY_F3, KEY_FN_F3 },
+ { SONYPI_EVENT_FNKEY_F4, KEY_FN_F4 },
+ { SONYPI_EVENT_FNKEY_F5, KEY_FN_F5 },
+ { SONYPI_EVENT_FNKEY_F6, KEY_FN_F6 },
+ { SONYPI_EVENT_FNKEY_F7, KEY_FN_F7 },
+ { SONYPI_EVENT_FNKEY_F8, KEY_FN_F8 },
+ { SONYPI_EVENT_FNKEY_F9, KEY_FN_F9 },
+ { SONYPI_EVENT_FNKEY_F10, KEY_FN_F10 },
+ { SONYPI_EVENT_FNKEY_F11, KEY_FN_F11 },
+ { SONYPI_EVENT_FNKEY_F12, KEY_FN_F12 },
+ { SONYPI_EVENT_FNKEY_1, KEY_FN_1 },
+ { SONYPI_EVENT_FNKEY_2, KEY_FN_2 },
+ { SONYPI_EVENT_FNKEY_D, KEY_FN_D },
+ { SONYPI_EVENT_FNKEY_E, KEY_FN_E },
+ { SONYPI_EVENT_FNKEY_F, KEY_FN_F },
+ { SONYPI_EVENT_FNKEY_S, KEY_FN_S },
+ { SONYPI_EVENT_FNKEY_B, KEY_FN_B },
+ { SONYPI_EVENT_BLUETOOTH_PRESSED, KEY_BLUE },
+ { SONYPI_EVENT_BLUETOOTH_ON, KEY_BLUE },
+ { SONYPI_EVENT_PKEY_P1, KEY_PROG1 },
+ { SONYPI_EVENT_PKEY_P2, KEY_PROG2 },
+ { SONYPI_EVENT_PKEY_P3, KEY_PROG3 },
+ { SONYPI_EVENT_BACK_PRESSED, KEY_BACK },
+ { SONYPI_EVENT_HELP_PRESSED, KEY_HELP },
+ { SONYPI_EVENT_ZOOM_PRESSED, KEY_ZOOM },
+ { SONYPI_EVENT_THUMBPHRASE_PRESSED, BTN_THUMB },
+ { 0, 0 },
+};
+
+static struct sonypi_device {
+ struct pci_dev *dev;
+ struct platform_device *pdev;
+ u16 irq;
+ u16 bits;
+ u16 ioport1;
+ u16 ioport2;
+ u16 region_size;
+ u16 evtype_offset;
+ int camera_power;
+ int bluetooth_power;
+ struct semaphore lock;
+ struct kfifo *fifo;
+ spinlock_t fifo_lock;
+ wait_queue_head_t fifo_proc_list;
+ struct fasync_struct *fifo_async;
+ int open_count;
+ int model;
+ struct input_dev input_jog_dev;
+ struct input_dev input_key_dev;
+ struct work_struct input_work;
+ struct kfifo *input_fifo;
+ spinlock_t input_fifo_lock;
+} sonypi_device;
+
+#define ITERATIONS_LONG 10000
+#define ITERATIONS_SHORT 10
+
+#define wait_on_command(quiet, command, iterations) { \
+ unsigned int n = iterations; \
+ while (--n && (command)) \
+ udelay(1); \
+ if (!n && (verbose || !quiet)) \
+ printk(KERN_WARNING "sonypi command failed at %s : %s (line %d)\n", __FILE__, __FUNCTION__, __LINE__); \
+}
+
+#ifdef CONFIG_ACPI
+#define SONYPI_ACPI_ACTIVE (!acpi_disabled)
+#else
+#define SONYPI_ACPI_ACTIVE 0
+#endif /* CONFIG_ACPI */
+
+static int sonypi_ec_write(u8 addr, u8 value)
+{
+#ifdef CONFIG_ACPI_EC
+ if (SONYPI_ACPI_ACTIVE)
+ return ec_write(addr, value);
+#endif
+ wait_on_command(1, inb_p(SONYPI_CST_IOPORT) & 3, ITERATIONS_LONG);
+ outb_p(0x81, SONYPI_CST_IOPORT);
+ wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+ outb_p(addr, SONYPI_DATA_IOPORT);
+ wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+ outb_p(value, SONYPI_DATA_IOPORT);
+ wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+ return 0;
+}
+
+static int sonypi_ec_read(u8 addr, u8 *value)
+{
+#ifdef CONFIG_ACPI_EC
+ if (SONYPI_ACPI_ACTIVE)
+ return ec_read(addr, value);
+#endif
+ wait_on_command(1, inb_p(SONYPI_CST_IOPORT) & 3, ITERATIONS_LONG);
+ outb_p(0x80, SONYPI_CST_IOPORT);
+ wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+ outb_p(addr, SONYPI_DATA_IOPORT);
+ wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+ *value = inb_p(SONYPI_DATA_IOPORT);
+ return 0;
+}
+
+static int ec_read16(u8 addr, u16 *value)
+{
+ u8 val_lb, val_hb;
+ if (sonypi_ec_read(addr, &val_lb))
+ return -1;
+ if (sonypi_ec_read(addr + 1, &val_hb))
+ return -1;
+ *value = val_lb | (val_hb << 8);
+ return 0;
+}
+
+/* Initializes the device - this comes from the AML code in the ACPI bios */
+static void sonypi_type1_srs(void)
+{
+ u32 v;
+
+ pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+ v = (v & 0xFFFF0000) | ((u32) sonypi_device.ioport1);
+ pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+ pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+ v = (v & 0xFFF0FFFF) |
+ (((u32) sonypi_device.ioport1 ^ sonypi_device.ioport2) << 16);
+ pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+ v = inl(SONYPI_IRQ_PORT);
+ v &= ~(((u32) 0x3) << SONYPI_IRQ_SHIFT);
+ v |= (((u32) sonypi_device.bits) << SONYPI_IRQ_SHIFT);
+ outl(v, SONYPI_IRQ_PORT);
+
+ pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+ v = (v & 0xFF1FFFFF) | 0x00C00000;
+ pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+}
+
+static void sonypi_type2_srs(void)
+{
+ if (sonypi_ec_write(SONYPI_SHIB, (sonypi_device.ioport1 & 0xFF00) >> 8))
+ printk(KERN_WARNING "ec_write failed\n");
+ if (sonypi_ec_write(SONYPI_SLOB, sonypi_device.ioport1 & 0x00FF))
+ printk(KERN_WARNING "ec_write failed\n");
+ if (sonypi_ec_write(SONYPI_SIRQ, sonypi_device.bits))
+ printk(KERN_WARNING "ec_write failed\n");
+ udelay(10);
+}
+
+/* Disables the device - this comes from the AML code in the ACPI bios */
+static void sonypi_type1_dis(void)
+{
+ u32 v;
+
+ pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+ v = v & 0xFF3FFFFF;
+ pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+ v = inl(SONYPI_IRQ_PORT);
+ v |= (0x3 << SONYPI_IRQ_SHIFT);
+ outl(v, SONYPI_IRQ_PORT);
+}
+
+static void sonypi_type2_dis(void)
+{
+ if (sonypi_ec_write(SONYPI_SHIB, 0))
+ printk(KERN_WARNING "ec_write failed\n");
+ if (sonypi_ec_write(SONYPI_SLOB, 0))
+ printk(KERN_WARNING "ec_write failed\n");
+ if (sonypi_ec_write(SONYPI_SIRQ, 0))
+ printk(KERN_WARNING "ec_write failed\n");
+}
+
+static u8 sonypi_call1(u8 dev)
+{
+ u8 v1, v2;
+
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(dev, sonypi_device.ioport2);
+ v1 = inb_p(sonypi_device.ioport2);
+ v2 = inb_p(sonypi_device.ioport1);
+ return v2;
+}
+
+static u8 sonypi_call2(u8 dev, u8 fn)
+{
+ u8 v1;
+
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(dev, sonypi_device.ioport2);
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(fn, sonypi_device.ioport1);
+ v1 = inb_p(sonypi_device.ioport1);
+ return v1;
+}
+
+static u8 sonypi_call3(u8 dev, u8 fn, u8 v)
+{
+ u8 v1;
+
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(dev, sonypi_device.ioport2);
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(fn, sonypi_device.ioport1);
+ wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+ outb(v, sonypi_device.ioport1);
+ v1 = inb_p(sonypi_device.ioport1);
+ return v1;
+}
+
+#if 0
+/* Get brightness, hue etc. Unreliable... */
+static u8 sonypi_read(u8 fn)
+{
+ u8 v1, v2;
+ int n = 100;
+
+ while (n--) {
+ v1 = sonypi_call2(0x8f, fn);
+ v2 = sonypi_call2(0x8f, fn);
+ if (v1 == v2 && v1 != 0xff)
+ return v1;
+ }
+ return 0xff;
+}
+#endif
+
+/* Set brightness, hue etc */
+static void sonypi_set(u8 fn, u8 v)
+{
+ wait_on_command(0, sonypi_call3(0x90, fn, v), ITERATIONS_SHORT);
+}
+
+/* Tests if the camera is ready */
+static int sonypi_camera_ready(void)
+{
+ u8 v;
+
+ v = sonypi_call2(0x8f, SONYPI_CAMERA_STATUS);
+ return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
+}
+
+/* Turns the camera off */
+static void sonypi_camera_off(void)
+{
+ sonypi_set(SONYPI_CAMERA_PICTURE, SONYPI_CAMERA_MUTE_MASK);
+
+ if (!sonypi_device.camera_power)
+ return;
+
+ sonypi_call2(0x91, 0);
+ sonypi_device.camera_power = 0;
+}
+
+/* Turns the camera on */
+static void sonypi_camera_on(void)
+{
+ int i, j;
+
+ if (sonypi_device.camera_power)
+ return;
+
+ for (j = 5; j > 0; j--) {
+
+ while (sonypi_call2(0x91, 0x1))
+ msleep(10);
+ sonypi_call1(0x93);
+
+ for (i = 400; i > 0; i--) {
+ if (sonypi_camera_ready())
+ break;
+ msleep(10);
+ }
+ if (i)
+ break;
+ }
+
+ if (j == 0) {
+ printk(KERN_WARNING "sonypi: failed to power on camera\n");
+ return;
+ }
+
+ sonypi_set(0x10, 0x5a);
+ sonypi_device.camera_power = 1;
+}
+
+/* sets the bluetooth subsystem power state */
+static void sonypi_setbluetoothpower(u8 state)
+{
+ state = !!state;
+
+ if (sonypi_device.bluetooth_power == state)
+ return;
+
+ sonypi_call2(0x96, state);
+ sonypi_call1(0x82);
+ sonypi_device.bluetooth_power = state;
+}
+
+static void input_keyrelease(void *data)
+{
+ struct input_dev *input_dev;
+ int key;
+
+ while (1) {
+ if (kfifo_get(sonypi_device.input_fifo,
+ (unsigned char *)&input_dev,
+ sizeof(input_dev)) != sizeof(input_dev))
+ return;
+ if (kfifo_get(sonypi_device.input_fifo,
+ (unsigned char *)&key,
+ sizeof(key)) != sizeof(key))
+ return;
+
+ msleep(10);
+ input_report_key(input_dev, key, 0);
+ input_sync(input_dev);
+ }
+}
+
+/* Interrupt handler: some event is available */
+static irqreturn_t sonypi_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ u8 v1, v2, event = 0;
+ int i, j;
+
+ v1 = inb_p(sonypi_device.ioport1);
+ v2 = inb_p(sonypi_device.ioport1 + sonypi_device.evtype_offset);
+
+ for (i = 0; sonypi_eventtypes[i].model; i++) {
+ if (sonypi_device.model != sonypi_eventtypes[i].model)
+ continue;
+ if ((v2 & sonypi_eventtypes[i].data) !=
+ sonypi_eventtypes[i].data)
+ continue;
+ if (!(mask & sonypi_eventtypes[i].mask))
+ continue;
+ for (j = 0; sonypi_eventtypes[i].events[j].event; j++) {
+ if (v1 == sonypi_eventtypes[i].events[j].data) {
+ event = sonypi_eventtypes[i].events[j].event;
+ goto found;
+ }
+ }
+ }
+
+ if (verbose)
+ printk(KERN_WARNING
+ "sonypi: unknown event port1=0x%02x,port2=0x%02x\n",
+ v1, v2);
+ /* We need to return IRQ_HANDLED here because there *are*
+ * events belonging to the sonypi device we don't know about,
+ * but we still don't want those to pollute the logs... */
+ return IRQ_HANDLED;
+
+found:
+ if (verbose > 1)
+ printk(KERN_INFO
+ "sonypi: event port1=0x%02x,port2=0x%02x\n", v1, v2);
+
+ if (useinput) {
+ struct input_dev *input_jog_dev = &sonypi_device.input_jog_dev;
+ struct input_dev *input_key_dev = &sonypi_device.input_key_dev;
+ switch (event) {
+ case SONYPI_EVENT_JOGDIAL_UP:
+ case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
+ input_report_rel(input_jog_dev, REL_WHEEL, 1);
+ break;
+ case SONYPI_EVENT_JOGDIAL_DOWN:
+ case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
+ input_report_rel(input_jog_dev, REL_WHEEL, -1);
+ break;
+ case SONYPI_EVENT_JOGDIAL_PRESSED: {
+ int key = BTN_MIDDLE;
+ input_report_key(input_jog_dev, key, 1);
+ kfifo_put(sonypi_device.input_fifo,
+ (unsigned char *)&input_jog_dev,
+ sizeof(input_jog_dev));
+ kfifo_put(sonypi_device.input_fifo,
+ (unsigned char *)&key, sizeof(key));
+ break;
+ }
+ case SONYPI_EVENT_FNKEY_RELEASED:
+ /* Nothing, not all VAIOs generate this event */
+ break;
+ }
+ input_sync(input_jog_dev);
+
+ for (i = 0; sonypi_inputkeys[i].sonypiev; i++) {
+ int key;
+
+ if (event != sonypi_inputkeys[i].sonypiev)
+ continue;
+
+ key = sonypi_inputkeys[i].inputev;
+ input_report_key(input_key_dev, key, 1);
+ kfifo_put(sonypi_device.input_fifo,
+ (unsigned char *)&input_key_dev,
+ sizeof(input_key_dev));
+ kfifo_put(sonypi_device.input_fifo,
+ (unsigned char *)&key, sizeof(key));
+ }
+ input_sync(input_key_dev);
+ schedule_work(&sonypi_device.input_work);
+ }
+
+ kfifo_put(sonypi_device.fifo, (unsigned char *)&event, sizeof(event));
+ kill_fasync(&sonypi_device.fifo_async, SIGIO, POLL_IN);
+ wake_up_interruptible(&sonypi_device.fifo_proc_list);
+
+ return IRQ_HANDLED;
+}
+
+/* External camera command (exported to the motion eye v4l driver) */
+int sonypi_camera_command(int command, u8 value)
+{
+ if (!camera)
+ return -EIO;
+
+ down(&sonypi_device.lock);
+
+ switch (command) {
+ case SONYPI_COMMAND_SETCAMERA:
+ if (value)
+ sonypi_camera_on();
+ else
+ sonypi_camera_off();
+ break;
+ case SONYPI_COMMAND_SETCAMERABRIGHTNESS:
+ sonypi_set(SONYPI_CAMERA_BRIGHTNESS, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERACONTRAST:
+ sonypi_set(SONYPI_CAMERA_CONTRAST, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERAHUE:
+ sonypi_set(SONYPI_CAMERA_HUE, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERACOLOR:
+ sonypi_set(SONYPI_CAMERA_COLOR, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERASHARPNESS:
+ sonypi_set(SONYPI_CAMERA_SHARPNESS, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERAPICTURE:
+ sonypi_set(SONYPI_CAMERA_PICTURE, value);
+ break;
+ case SONYPI_COMMAND_SETCAMERAAGC:
+ sonypi_set(SONYPI_CAMERA_AGC, value);
+ break;
+ default:
+ printk(KERN_ERR "sonypi: sonypi_camera_command invalid: %d\n",
+ command);
+ break;
+ }
+ up(&sonypi_device.lock);
+ return 0;
+}
+
+EXPORT_SYMBOL(sonypi_camera_command);
+
+static int sonypi_misc_fasync(int fd, struct file *filp, int on)
+{
+ int retval;
+
+ retval = fasync_helper(fd, filp, on, &sonypi_device.fifo_async);
+ if (retval < 0)
+ return retval;
+ return 0;
+}
+
+static int sonypi_misc_release(struct inode *inode, struct file *file)
+{
+ sonypi_misc_fasync(-1, file, 0);
+ down(&sonypi_device.lock);
+ sonypi_device.open_count--;
+ up(&sonypi_device.lock);
+ return 0;
+}
+
+static int sonypi_misc_open(struct inode *inode, struct file *file)
+{
+ down(&sonypi_device.lock);
+ /* Flush input queue on first open */
+ if (!sonypi_device.open_count)
+ kfifo_reset(sonypi_device.fifo);
+ sonypi_device.open_count++;
+ up(&sonypi_device.lock);
+ return 0;
+}
+
+static ssize_t sonypi_misc_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ ssize_t ret;
+ unsigned char c;
+
+ if ((kfifo_len(sonypi_device.fifo) == 0) &&
+ (file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(sonypi_device.fifo_proc_list,
+ kfifo_len(sonypi_device.fifo) != 0);
+ if (ret)
+ return ret;
+
+ while (ret < count &&
+ (kfifo_get(sonypi_device.fifo, &c, sizeof(c)) == sizeof(c))) {
+ if (put_user(c, buf++))
+ return -EFAULT;
+ ret++;
+ }
+
+ if (ret > 0) {
+ struct inode *inode = file->f_dentry->d_inode;
+ inode->i_atime = current_fs_time(inode->i_sb);
+ }
+
+ return ret;
+}
+
+static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait)
+{
+ poll_wait(file, &sonypi_device.fifo_proc_list, wait);
+ if (kfifo_len(sonypi_device.fifo))
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+static int sonypi_misc_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ void __user *argp = (void __user *)arg;
+ u8 val8;
+ u16 val16;
+
+ down(&sonypi_device.lock);
+ switch (cmd) {
+ case SONYPI_IOCGBRT:
+ if (sonypi_ec_read(SONYPI_LCD_LIGHT, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSBRT:
+ if (copy_from_user(&val8, argp, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (sonypi_ec_write(SONYPI_LCD_LIGHT, val8))
+ ret = -EIO;
+ break;
+ case SONYPI_IOCGBAT1CAP:
+ if (ec_read16(SONYPI_BAT1_FULL, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT1REM:
+ if (ec_read16(SONYPI_BAT1_LEFT, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT2CAP:
+ if (ec_read16(SONYPI_BAT2_FULL, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBAT2REM:
+ if (ec_read16(SONYPI_BAT2_LEFT, &val16)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user(argp, &val16, sizeof(val16)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBATFLAGS:
+ if (sonypi_ec_read(SONYPI_BAT_FLAGS, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ val8 &= 0x07;
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCGBLUE:
+ val8 = sonypi_device.bluetooth_power;
+ if (copy_to_user(argp, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSBLUE:
+ if (copy_from_user(&val8, argp, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ sonypi_setbluetoothpower(val8);
+ break;
+ /* FAN Controls */
+ case SONYPI_IOCGFAN:
+ if (sonypi_ec_read(SONYPI_FAN0_STATUS, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user((u8 *)arg, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ case SONYPI_IOCSFAN:
+ if (copy_from_user(&val8, (u8 *)arg, sizeof(val8))) {
+ ret = -EFAULT;
+ break;
+ }
+ if (sonypi_ec_write(SONYPI_FAN0_STATUS, val8))
+ ret = -EIO;
+ break;
+ /* GET Temperature (useful under APM) */
+ case SONYPI_IOCGTEMP:
+ if (sonypi_ec_read(SONYPI_TEMP_STATUS, &val8)) {
+ ret = -EIO;
+ break;
+ }
+ if (copy_to_user((u8 *)arg, &val8, sizeof(val8)))
+ ret = -EFAULT;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ up(&sonypi_device.lock);
+ return ret;
+}
+
+static struct file_operations sonypi_misc_fops = {
+ .owner = THIS_MODULE,
+ .read = sonypi_misc_read,
+ .poll = sonypi_misc_poll,
+ .open = sonypi_misc_open,
+ .release = sonypi_misc_release,
+ .fasync = sonypi_misc_fasync,
+ .ioctl = sonypi_misc_ioctl,
+};
+
+static struct miscdevice sonypi_misc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "sonypi",
+ .fops = &sonypi_misc_fops,
+};
+
+static void sonypi_enable(unsigned int camera_on)
+{
+ if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2)
+ sonypi_type2_srs();
+ else
+ sonypi_type1_srs();
+
+ sonypi_call1(0x82);
+ sonypi_call2(0x81, 0xff);
+ sonypi_call1(compat ? 0x92 : 0x82);
+
+ /* Enable ACPI mode to get Fn key events */
+ if (!SONYPI_ACPI_ACTIVE && fnkeyinit)
+ outb(0xf0, 0xb2);
+
+ if (camera && camera_on)
+ sonypi_camera_on();
+}
+
+static int sonypi_disable(void)
+{
+ sonypi_call2(0x81, 0); /* make sure we don't get any more events */
+ if (camera)
+ sonypi_camera_off();
+
+ /* disable ACPI mode */
+ if (!SONYPI_ACPI_ACTIVE && fnkeyinit)
+ outb(0xf1, 0xb2);
+
+ if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2)
+ sonypi_type2_dis();
+ else
+ sonypi_type1_dis();
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int old_camera_power;
+
+static int sonypi_suspend(struct device *dev, u32 state, u32 level)
+{
+ if (level == SUSPEND_DISABLE) {
+ old_camera_power = sonypi_device.camera_power;
+ sonypi_disable();
+ }
+ return 0;
+}
+
+static int sonypi_resume(struct device *dev, u32 level)
+{
+ if (level == RESUME_ENABLE)
+ sonypi_enable(old_camera_power);
+ return 0;
+}
+#endif
+
+static void sonypi_shutdown(struct device *dev)
+{
+ sonypi_disable();
+}
+
+static struct device_driver sonypi_driver = {
+ .name = "sonypi",
+ .bus = &platform_bus_type,
+#ifdef CONFIG_PM
+ .suspend = sonypi_suspend,
+ .resume = sonypi_resume,
+#endif
+ .shutdown = sonypi_shutdown,
+};
+
+static int __devinit sonypi_probe(void)
+{
+ int i, ret;
+ struct sonypi_ioport_list *ioport_list;
+ struct sonypi_irq_list *irq_list;
+ struct pci_dev *pcidev;
+
+ pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+
+ sonypi_device.dev = pcidev;
+ sonypi_device.model = pcidev ?
+ SONYPI_DEVICE_MODEL_TYPE1 : SONYPI_DEVICE_MODEL_TYPE2;
+
+ spin_lock_init(&sonypi_device.fifo_lock);
+ sonypi_device.fifo = kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
+ &sonypi_device.fifo_lock);
+ if (IS_ERR(sonypi_device.fifo)) {
+ printk(KERN_ERR "sonypi: kfifo_alloc failed\n");
+ ret = PTR_ERR(sonypi_device.fifo);
+ goto out_fifo;
+ }
+
+ init_waitqueue_head(&sonypi_device.fifo_proc_list);
+ init_MUTEX(&sonypi_device.lock);
+ sonypi_device.bluetooth_power = -1;
+
+ if (pcidev && pci_enable_device(pcidev)) {
+ printk(KERN_ERR "sonypi: pci_enable_device failed\n");
+ ret = -EIO;
+ goto out_pcienable;
+ }
+
+ if (minor != -1)
+ sonypi_misc_device.minor = minor;
+ if ((ret = misc_register(&sonypi_misc_device))) {
+ printk(KERN_ERR "sonypi: misc_register failed\n");
+ goto out_miscreg;
+ }
+
+ if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2) {
+ ioport_list = sonypi_type2_ioport_list;
+ sonypi_device.region_size = SONYPI_TYPE2_REGION_SIZE;
+ sonypi_device.evtype_offset = SONYPI_TYPE2_EVTYPE_OFFSET;
+ irq_list = sonypi_type2_irq_list;
+ } else {
+ ioport_list = sonypi_type1_ioport_list;
+ sonypi_device.region_size = SONYPI_TYPE1_REGION_SIZE;
+ sonypi_device.evtype_offset = SONYPI_TYPE1_EVTYPE_OFFSET;
+ irq_list = sonypi_type1_irq_list;
+ }
+
+ for (i = 0; ioport_list[i].port1; i++) {
+ if (request_region(ioport_list[i].port1,
+ sonypi_device.region_size,
+ "Sony Programable I/O Device")) {
+ /* get the ioport */
+ sonypi_device.ioport1 = ioport_list[i].port1;
+ sonypi_device.ioport2 = ioport_list[i].port2;
+ break;
+ }
+ }
+ if (!sonypi_device.ioport1) {
+ printk(KERN_ERR "sonypi: request_region failed\n");
+ ret = -ENODEV;
+ goto out_reqreg;
+ }
+
+ for (i = 0; irq_list[i].irq; i++) {
+
+ sonypi_device.irq = irq_list[i].irq;
+ sonypi_device.bits = irq_list[i].bits;
+
+ if (!request_irq(sonypi_device.irq, sonypi_irq,
+ SA_SHIRQ, "sonypi", sonypi_irq))
+ break;
+ }
+
+ if (!irq_list[i].irq) {
+ printk(KERN_ERR "sonypi: request_irq failed\n");
+ ret = -ENODEV;
+ goto out_reqirq;
+ }
+
+ if (useinput) {
+ /* Initialize the Input Drivers: jogdial */
+ int i;
+ sonypi_device.input_jog_dev.evbit[0] =
+ BIT(EV_KEY) | BIT(EV_REL);
+ sonypi_device.input_jog_dev.keybit[LONG(BTN_MOUSE)] =
+ BIT(BTN_MIDDLE);
+ sonypi_device.input_jog_dev.relbit[0] = BIT(REL_WHEEL);
+ sonypi_device.input_jog_dev.name =
+ kmalloc(sizeof(SONYPI_JOG_INPUTNAME), GFP_KERNEL);
+ if (!sonypi_device.input_jog_dev.name) {
+ printk(KERN_ERR "sonypi: kmalloc failed\n");
+ ret = -ENOMEM;
+ goto out_inkmallocinput1;
+ }
+ sprintf(sonypi_device.input_jog_dev.name, SONYPI_JOG_INPUTNAME);
+ sonypi_device.input_jog_dev.id.bustype = BUS_ISA;
+ sonypi_device.input_jog_dev.id.vendor = PCI_VENDOR_ID_SONY;
+
+ input_register_device(&sonypi_device.input_jog_dev);
+ printk(KERN_INFO "%s input method installed.\n",
+ sonypi_device.input_jog_dev.name);
+
+ /* Initialize the Input Drivers: special keys */
+ sonypi_device.input_key_dev.evbit[0] = BIT(EV_KEY);
+ for (i = 0; sonypi_inputkeys[i].sonypiev; i++)
+ if (sonypi_inputkeys[i].inputev)
+ set_bit(sonypi_inputkeys[i].inputev,
+ sonypi_device.input_key_dev.keybit);
+ sonypi_device.input_key_dev.name =
+ kmalloc(sizeof(SONYPI_KEY_INPUTNAME), GFP_KERNEL);
+ if (!sonypi_device.input_key_dev.name) {
+ printk(KERN_ERR "sonypi: kmalloc failed\n");
+ ret = -ENOMEM;
+ goto out_inkmallocinput2;
+ }
+ sprintf(sonypi_device.input_key_dev.name, SONYPI_KEY_INPUTNAME);
+ sonypi_device.input_key_dev.id.bustype = BUS_ISA;
+ sonypi_device.input_key_dev.id.vendor = PCI_VENDOR_ID_SONY;
+
+ input_register_device(&sonypi_device.input_key_dev);
+ printk(KERN_INFO "%s input method installed.\n",
+ sonypi_device.input_key_dev.name);
+
+ spin_lock_init(&sonypi_device.input_fifo_lock);
+ sonypi_device.input_fifo =
+ kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
+ &sonypi_device.input_fifo_lock);
+ if (IS_ERR(sonypi_device.input_fifo)) {
+ printk(KERN_ERR "sonypi: kfifo_alloc failed\n");
+ ret = PTR_ERR(sonypi_device.input_fifo);
+ goto out_infifo;
+ }
+
+ INIT_WORK(&sonypi_device.input_work, input_keyrelease, NULL);
+ }
+
+ sonypi_device.pdev = platform_device_register_simple("sonypi", -1,
+ NULL, 0);
+ if (IS_ERR(sonypi_device.pdev)) {
+ ret = PTR_ERR(sonypi_device.pdev);
+ goto out_platformdev;
+ }
+
+ sonypi_enable(0);
+
+ printk(KERN_INFO "sonypi: Sony Programmable I/O Controller Driver"
+ "v%s.\n", SONYPI_DRIVER_VERSION);
+ printk(KERN_INFO "sonypi: detected %s model, "
+ "verbose = %d, fnkeyinit = %s, camera = %s, "
+ "compat = %s, mask = 0x%08lx, useinput = %s, acpi = %s\n",
+ (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE1) ?
+ "type1" : "type2",
+ verbose,
+ fnkeyinit ? "on" : "off",
+ camera ? "on" : "off",
+ compat ? "on" : "off",
+ mask,
+ useinput ? "on" : "off",
+ SONYPI_ACPI_ACTIVE ? "on" : "off");
+ printk(KERN_INFO "sonypi: enabled at irq=%d, port1=0x%x, port2=0x%x\n",
+ sonypi_device.irq,
+ sonypi_device.ioport1, sonypi_device.ioport2);
+
+ if (minor == -1)
+ printk(KERN_INFO "sonypi: device allocated minor is %d\n",
+ sonypi_misc_device.minor);
+
+ return 0;
+
+out_platformdev:
+ kfifo_free(sonypi_device.input_fifo);
+out_infifo:
+ input_unregister_device(&sonypi_device.input_key_dev);
+ kfree(sonypi_device.input_key_dev.name);
+out_inkmallocinput2:
+ input_unregister_device(&sonypi_device.input_jog_dev);
+ kfree(sonypi_device.input_jog_dev.name);
+out_inkmallocinput1:
+ free_irq(sonypi_device.irq, sonypi_irq);
+out_reqirq:
+ release_region(sonypi_device.ioport1, sonypi_device.region_size);
+out_reqreg:
+ misc_deregister(&sonypi_misc_device);
+out_miscreg:
+ if (pcidev)
+ pci_disable_device(pcidev);
+out_pcienable:
+ kfifo_free(sonypi_device.fifo);
+out_fifo:
+ pci_dev_put(sonypi_device.dev);
+ return ret;
+}
+
+static void __devexit sonypi_remove(void)
+{
+ sonypi_disable();
+
+ platform_device_unregister(sonypi_device.pdev);
+
+ if (useinput) {
+ input_unregister_device(&sonypi_device.input_key_dev);
+ kfree(sonypi_device.input_key_dev.name);
+ input_unregister_device(&sonypi_device.input_jog_dev);
+ kfree(sonypi_device.input_jog_dev.name);
+ kfifo_free(sonypi_device.input_fifo);
+ }
+
+ free_irq(sonypi_device.irq, sonypi_irq);
+ release_region(sonypi_device.ioport1, sonypi_device.region_size);
+ misc_deregister(&sonypi_misc_device);
+ if (sonypi_device.dev)
+ pci_disable_device(sonypi_device.dev);
+ kfifo_free(sonypi_device.fifo);
+ pci_dev_put(sonypi_device.dev);
+ printk(KERN_INFO "sonypi: removed.\n");
+}
+
+static struct dmi_system_id __initdata sonypi_dmi_table[] = {
+ {
+ .ident = "Sony Vaio",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "PCG-"),
+ },
+ },
+ {
+ .ident = "Sony Vaio",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "VGN-"),
+ },
+ },
+ { }
+};
+
+static int __init sonypi_init(void)
+{
+ int ret;
+
+ if (!dmi_check_system(sonypi_dmi_table))
+ return -ENODEV;
+
+ ret = driver_register(&sonypi_driver);
+ if (ret)
+ return ret;
+
+ ret = sonypi_probe();
+ if (ret)
+ driver_unregister(&sonypi_driver);
+
+ return ret;
+}
+
+static void __exit sonypi_exit(void)
+{
+ driver_unregister(&sonypi_driver);
+ sonypi_remove();
+}
+
+module_init(sonypi_init);
+module_exit(sonypi_exit);
diff --git a/drivers/char/specialix.c b/drivers/char/specialix.c
new file mode 100644
index 000000000000..c789d5ceac76
--- /dev/null
+++ b/drivers/char/specialix.c
@@ -0,0 +1,2610 @@
+/*
+ * specialix.c -- specialix IO8+ multiport serial driver.
+ *
+ * Copyright (C) 1997 Roger Wolff (R.E.Wolff@BitWizard.nl)
+ * Copyright (C) 1994-1996 Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact io8-linux@specialix.co.uk if you require
+ * support. But please read the documentation (specialix.txt)
+ * first.
+ *
+ * This driver was developped in the BitWizard linux device
+ * driver service. If you require a linux device driver for your
+ * product, please contact devices@BitWizard.nl for a quote.
+ *
+ * This code is firmly based on the riscom/8 serial driver,
+ * written by Dmitry Gorodchanin. The specialix IO8+ card
+ * programming information was obtained from the CL-CD1865 Data
+ * Book, and Specialix document number 6200059: IO8+ Hardware
+ * Functional Specification.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Revision history:
+ *
+ * Revision 1.0: April 1st 1997.
+ * Initial release for alpha testing.
+ * Revision 1.1: April 14th 1997.
+ * Incorporated Richard Hudsons suggestions,
+ * removed some debugging printk's.
+ * Revision 1.2: April 15th 1997.
+ * Ported to 2.1.x kernels.
+ * Revision 1.3: April 17th 1997
+ * Backported to 2.0. (Compatibility macros).
+ * Revision 1.4: April 18th 1997
+ * Fixed DTR/RTS bug that caused the card to indicate
+ * "don't send data" to a modem after the password prompt.
+ * Fixed bug for premature (fake) interrupts.
+ * Revision 1.5: April 19th 1997
+ * fixed a minor typo in the header file, cleanup a little.
+ * performance warnings are now MAXed at once per minute.
+ * Revision 1.6: May 23 1997
+ * Changed the specialix=... format to include interrupt.
+ * Revision 1.7: May 27 1997
+ * Made many more debug printk's a compile time option.
+ * Revision 1.8: Jul 1 1997
+ * port to linux-2.1.43 kernel.
+ * Revision 1.9: Oct 9 1998
+ * Added stuff for the IO8+/PCI version.
+ * Revision 1.10: Oct 22 1999 / Jan 21 2000.
+ * Added stuff for setserial.
+ * Nicolas Mailhot (Nicolas.Mailhot@email.enst.fr)
+ *
+ */
+
+#define VERSION "1.11"
+
+
+/*
+ * There is a bunch of documentation about the card, jumpers, config
+ * settings, restrictions, cables, device names and numbers in
+ * Documentation/specialix.txt
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#include "specialix_io8.h"
+#include "cd1865.h"
+
+
+/*
+ This driver can spew a whole lot of debugging output at you. If you
+ need maximum performance, you should disable the DEBUG define. To
+ aid in debugging in the field, I'm leaving the compile-time debug
+ features enabled, and disable them "runtime". That allows me to
+ instruct people with problems to enable debugging without requiring
+ them to recompile...
+*/
+#define DEBUG
+
+static int sx_debug;
+static int sx_rxfifo = SPECIALIX_RXFIFO;
+
+#ifdef DEBUG
+#define dprintk(f, str...) if (sx_debug & f) printk (str)
+#else
+#define dprintk(f, str...) /* nothing */
+#endif
+
+#define SX_DEBUG_FLOW 0x0001
+#define SX_DEBUG_DATA 0x0002
+#define SX_DEBUG_PROBE 0x0004
+#define SX_DEBUG_CHAN 0x0008
+#define SX_DEBUG_INIT 0x0010
+#define SX_DEBUG_RX 0x0020
+#define SX_DEBUG_TX 0x0040
+#define SX_DEBUG_IRQ 0x0080
+#define SX_DEBUG_OPEN 0x0100
+#define SX_DEBUG_TERMIOS 0x0200
+#define SX_DEBUG_SIGNALS 0x0400
+#define SX_DEBUG_FIFO 0x0800
+
+
+#define func_enter() dprintk (SX_DEBUG_FLOW, "io8: enter %s\n",__FUNCTION__)
+#define func_exit() dprintk (SX_DEBUG_FLOW, "io8: exit %s\n", __FUNCTION__)
+
+#define jiffies_from_ms(a) ((((a) * HZ)/1000)+1)
+
+
+/* Configurable options: */
+
+/* Am I paranoid or not ? ;-) */
+#define SPECIALIX_PARANOIA_CHECK
+
+/* Do I trust the IRQ from the card? (enabeling it doesn't seem to help)
+ When the IRQ routine leaves the chip in a state that is keeps on
+ requiring attention, the timer doesn't help either. */
+#undef SPECIALIX_TIMER
+
+#ifdef SPECIALIX_TIMER
+static int sx_poll = HZ;
+#endif
+
+
+
+/*
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#undef SX_REPORT_FIFO
+#undef SX_REPORT_OVERRUN
+
+
+
+#ifdef CONFIG_SPECIALIX_RTSCTS
+#define SX_CRTSCTS(bla) 1
+#else
+#define SX_CRTSCTS(tty) C_CRTSCTS(tty)
+#endif
+
+
+/* Used to be outb (0xff, 0x80); */
+#define short_pause() udelay (1)
+
+
+#define SPECIALIX_LEGAL_FLAGS \
+ (ASYNC_HUP_NOTIFY | ASYNC_SAK | ASYNC_SPLIT_TERMIOS | \
+ ASYNC_SPD_HI | ASYNC_SPEED_VHI | ASYNC_SESSION_LOCKOUT | \
+ ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP)
+
+#undef RS_EVENT_WRITE_WAKEUP
+#define RS_EVENT_WRITE_WAKEUP 0
+
+static struct tty_driver *specialix_driver;
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static unsigned long baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 0,
+};
+
+static struct specialix_board sx_board[SX_NBOARD] = {
+ { 0, SX_IOBASE1, 9, },
+ { 0, SX_IOBASE2, 11, },
+ { 0, SX_IOBASE3, 12, },
+ { 0, SX_IOBASE4, 15, },
+};
+
+static struct specialix_port sx_port[SX_NBOARD * SX_NPORT];
+
+
+#ifdef SPECIALIX_TIMER
+static struct timer_list missed_irq_timer;
+static irqreturn_t sx_interrupt(int irq, void * dev_id, struct pt_regs * regs);
+#endif
+
+
+
+static inline int sx_paranoia_check(struct specialix_port const * port,
+ char *name, const char *routine)
+{
+#ifdef SPECIALIX_PARANOIA_CHECK
+ static const char *badmagic =
+ KERN_ERR "sx: Warning: bad specialix port magic number for device %s in %s\n";
+ static const char *badinfo =
+ KERN_ERR "sx: Warning: null specialix port for device %s in %s\n";
+
+ if (!port) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (port->magic != SPECIALIX_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+
+/*
+ *
+ * Service functions for specialix IO8+ driver.
+ *
+ */
+
+/* Get board number from pointer */
+static inline int board_No (struct specialix_board * bp)
+{
+ return bp - sx_board;
+}
+
+
+/* Get port number from pointer */
+static inline int port_No (struct specialix_port const * port)
+{
+ return SX_PORT(port - sx_port);
+}
+
+
+/* Get pointer to board from pointer to port */
+static inline struct specialix_board * port_Board(struct specialix_port const * port)
+{
+ return &sx_board[SX_BOARD(port - sx_port)];
+}
+
+
+/* Input Byte from CL CD186x register */
+static inline unsigned char sx_in(struct specialix_board * bp, unsigned short reg)
+{
+ bp->reg = reg | 0x80;
+ outb (reg | 0x80, bp->base + SX_ADDR_REG);
+ return inb (bp->base + SX_DATA_REG);
+}
+
+
+/* Output Byte to CL CD186x register */
+static inline void sx_out(struct specialix_board * bp, unsigned short reg,
+ unsigned char val)
+{
+ bp->reg = reg | 0x80;
+ outb (reg | 0x80, bp->base + SX_ADDR_REG);
+ outb (val, bp->base + SX_DATA_REG);
+}
+
+
+/* Input Byte from CL CD186x register */
+static inline unsigned char sx_in_off(struct specialix_board * bp, unsigned short reg)
+{
+ bp->reg = reg;
+ outb (reg, bp->base + SX_ADDR_REG);
+ return inb (bp->base + SX_DATA_REG);
+}
+
+
+/* Output Byte to CL CD186x register */
+static inline void sx_out_off(struct specialix_board * bp, unsigned short reg,
+ unsigned char val)
+{
+ bp->reg = reg;
+ outb (reg, bp->base + SX_ADDR_REG);
+ outb (val, bp->base + SX_DATA_REG);
+}
+
+
+/* Wait for Channel Command Register ready */
+static inline void sx_wait_CCR(struct specialix_board * bp)
+{
+ unsigned long delay, flags;
+ unsigned char ccr;
+
+ for (delay = SX_CCR_TIMEOUT; delay; delay--) {
+ spin_lock_irqsave(&bp->lock, flags);
+ ccr = sx_in(bp, CD186x_CCR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (!ccr)
+ return;
+ udelay (1);
+ }
+
+ printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+
+/* Wait for Channel Command Register ready */
+static inline void sx_wait_CCR_off(struct specialix_board * bp)
+{
+ unsigned long delay;
+ unsigned char crr;
+ unsigned long flags;
+
+ for (delay = SX_CCR_TIMEOUT; delay; delay--) {
+ spin_lock_irqsave(&bp->lock, flags);
+ crr = sx_in_off(bp, CD186x_CCR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (!crr)
+ return;
+ udelay (1);
+ }
+
+ printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+
+/*
+ * specialix IO8+ IO range functions.
+ */
+
+static inline int sx_check_io_range(struct specialix_board * bp)
+{
+ return check_region (bp->base, SX_IO_SPACE);
+}
+
+
+static inline void sx_request_io_range(struct specialix_board * bp)
+{
+ request_region(bp->base,
+ bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE,
+ "specialix IO8+" );
+}
+
+
+static inline void sx_release_io_range(struct specialix_board * bp)
+{
+ release_region(bp->base,
+ bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE);
+}
+
+
+/* Must be called with enabled interrupts */
+/* Ugly. Very ugly. Don't use this for anything else than initialization
+ code */
+static inline void sx_long_delay(unsigned long delay)
+{
+ unsigned long i;
+
+ for (i = jiffies + delay; time_after(i, jiffies); ) ;
+}
+
+
+
+/* Set the IRQ using the RTS lines that run to the PAL on the board.... */
+static int sx_set_irq ( struct specialix_board *bp)
+{
+ int virq;
+ int i;
+ unsigned long flags;
+
+ if (bp->flags & SX_BOARD_IS_PCI)
+ return 1;
+ switch (bp->irq) {
+ /* In the same order as in the docs... */
+ case 15: virq = 0;break;
+ case 12: virq = 1;break;
+ case 11: virq = 2;break;
+ case 9: virq = 3;break;
+ default: printk (KERN_ERR "Speclialix: cannot set irq to %d.\n", bp->irq);
+ return 0;
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ for (i=0;i<2;i++) {
+ sx_out(bp, CD186x_CAR, i);
+ sx_out(bp, CD186x_MSVRTS, ((virq >> i) & 0x1)? MSVR_RTS:0);
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+ return 1;
+}
+
+
+/* Reset and setup CD186x chip */
+static int sx_init_CD186x(struct specialix_board * bp)
+{
+ unsigned long flags;
+ int scaler;
+ int rv = 1;
+
+ func_enter();
+ sx_wait_CCR_off(bp); /* Wait for CCR ready */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out_off(bp, CD186x_CCR, CCR_HARDRESET); /* Reset CD186x chip */
+ spin_unlock_irqrestore(&bp->lock, flags);
+ sx_long_delay(HZ/20); /* Delay 0.05 sec */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out_off(bp, CD186x_GIVR, SX_ID); /* Set ID for this chip */
+ sx_out_off(bp, CD186x_GICR, 0); /* Clear all bits */
+ sx_out_off(bp, CD186x_PILR1, SX_ACK_MINT); /* Prio for modem intr */
+ sx_out_off(bp, CD186x_PILR2, SX_ACK_TINT); /* Prio for transmitter intr */
+ sx_out_off(bp, CD186x_PILR3, SX_ACK_RINT); /* Prio for receiver intr */
+ /* Set RegAckEn */
+ sx_out_off(bp, CD186x_SRCR, sx_in (bp, CD186x_SRCR) | SRCR_REGACKEN);
+
+ /* Setting up prescaler. We need 4 ticks per 1 ms */
+ scaler = SX_OSCFREQ/SPECIALIX_TPS;
+
+ sx_out_off(bp, CD186x_PPRH, scaler >> 8);
+ sx_out_off(bp, CD186x_PPRL, scaler & 0xff);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ if (!sx_set_irq (bp)) {
+ /* Figure out how to pass this along... */
+ printk (KERN_ERR "Cannot set irq to %d.\n", bp->irq);
+ rv = 0;
+ }
+
+ func_exit();
+ return rv;
+}
+
+
+static int read_cross_byte (struct specialix_board *bp, int reg, int bit)
+{
+ int i;
+ int t;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ for (i=0, t=0;i<8;i++) {
+ sx_out_off (bp, CD186x_CAR, i);
+ if (sx_in_off (bp, reg) & bit)
+ t |= 1 << i;
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ return t;
+}
+
+
+#ifdef SPECIALIX_TIMER
+void missed_irq (unsigned long data)
+{
+ unsigned char irq;
+ unsigned long flags;
+ struct specialix_board *bp = (struct specialix_board *)data;
+
+ spin_lock_irqsave(&bp->lock, flags);
+ irq = sx_in ((struct specialix_board *)data, CD186x_SRSR) &
+ (SRSR_RREQint |
+ SRSR_TREQint |
+ SRSR_MREQint);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (irq) {
+ printk (KERN_INFO "Missed interrupt... Calling int from timer. \n");
+ sx_interrupt (((struct specialix_board *)data)->irq,
+ (void*)data, NULL);
+ }
+ missed_irq_timer.expires = jiffies + sx_poll;
+ add_timer (&missed_irq_timer);
+}
+#endif
+
+
+
+/* Main probing routine, also sets irq. */
+static int sx_probe(struct specialix_board *bp)
+{
+ unsigned char val1, val2;
+#if 0
+ int irqs = 0;
+ int retries;
+#endif
+ int rev;
+ int chip;
+
+ func_enter();
+
+ if (sx_check_io_range(bp)) {
+ func_exit();
+ return 1;
+ }
+
+ /* Are the I/O ports here ? */
+ sx_out_off(bp, CD186x_PPRL, 0x5a);
+ short_pause ();
+ val1 = sx_in_off(bp, CD186x_PPRL);
+
+ sx_out_off(bp, CD186x_PPRL, 0xa5);
+ short_pause ();
+ val2 = sx_in_off(bp, CD186x_PPRL);
+
+
+ if ((val1 != 0x5a) || (val2 != 0xa5)) {
+ printk(KERN_INFO "sx%d: specialix IO8+ Board at 0x%03x not found.\n",
+ board_No(bp), bp->base);
+ func_exit();
+ return 1;
+ }
+
+ /* Check the DSR lines that Specialix uses as board
+ identification */
+ val1 = read_cross_byte (bp, CD186x_MSVR, MSVR_DSR);
+ val2 = read_cross_byte (bp, CD186x_MSVR, MSVR_RTS);
+ dprintk (SX_DEBUG_INIT, "sx%d: DSR lines are: %02x, rts lines are: %02x\n",
+ board_No(bp), val1, val2);
+
+ /* They managed to switch the bit order between the docs and
+ the IO8+ card. The new PCI card now conforms to old docs.
+ They changed the PCI docs to reflect the situation on the
+ old card. */
+ val2 = (bp->flags & SX_BOARD_IS_PCI)?0x4d : 0xb2;
+ if (val1 != val2) {
+ printk(KERN_INFO "sx%d: specialix IO8+ ID %02x at 0x%03x not found (%02x).\n",
+ board_No(bp), val2, bp->base, val1);
+ func_exit();
+ return 1;
+ }
+
+
+#if 0
+ /* It's time to find IRQ for this board */
+ for (retries = 0; retries < 5 && irqs <= 0; retries++) {
+ irqs = probe_irq_on();
+ sx_init_CD186x(bp); /* Reset CD186x chip */
+ sx_out(bp, CD186x_CAR, 2); /* Select port 2 */
+ sx_wait_CCR(bp);
+ sx_out(bp, CD186x_CCR, CCR_TXEN); /* Enable transmitter */
+ sx_out(bp, CD186x_IER, IER_TXRDY); /* Enable tx empty intr */
+ sx_long_delay(HZ/20);
+ irqs = probe_irq_off(irqs);
+
+ dprintk (SX_DEBUG_INIT, "SRSR = %02x, ", sx_in(bp, CD186x_SRSR));
+ dprintk (SX_DEBUG_INIT, "TRAR = %02x, ", sx_in(bp, CD186x_TRAR));
+ dprintk (SX_DEBUG_INIT, "GIVR = %02x, ", sx_in(bp, CD186x_GIVR));
+ dprintk (SX_DEBUG_INIT, "GICR = %02x, ", sx_in(bp, CD186x_GICR));
+ dprintk (SX_DEBUG_INIT, "\n");
+
+ /* Reset CD186x again */
+ if (!sx_init_CD186x(bp)) {
+ /* Hmmm. This is dead code anyway. */
+ }
+
+ dprintk (SX_DEBUG_INIT "val1 = %02x, val2 = %02x, val3 = %02x.\n",
+ val1, val2, val3);
+
+ }
+
+#if 0
+ if (irqs <= 0) {
+ printk(KERN_ERR "sx%d: Can't find IRQ for specialix IO8+ board at 0x%03x.\n",
+ board_No(bp), bp->base);
+ func_exit();
+ return 1;
+ }
+#endif
+ printk (KERN_INFO "Started with irq=%d, but now have irq=%d.\n", bp->irq, irqs);
+ if (irqs > 0)
+ bp->irq = irqs;
+#endif
+ /* Reset CD186x again */
+ if (!sx_init_CD186x(bp)) {
+ func_exit();
+ return -EIO;
+ }
+
+ sx_request_io_range(bp);
+ bp->flags |= SX_BOARD_PRESENT;
+
+ /* Chip revcode pkgtype
+ GFRCR SRCR bit 7
+ CD180 rev B 0x81 0
+ CD180 rev C 0x82 0
+ CD1864 rev A 0x82 1
+ CD1865 rev A 0x83 1 -- Do not use!!! Does not work.
+ CD1865 rev B 0x84 1
+ -- Thanks to Gwen Wang, Cirrus Logic.
+ */
+
+ switch (sx_in_off(bp, CD186x_GFRCR)) {
+ case 0x82:chip = 1864;rev='A';break;
+ case 0x83:chip = 1865;rev='A';break;
+ case 0x84:chip = 1865;rev='B';break;
+ case 0x85:chip = 1865;rev='C';break; /* Does not exist at this time */
+ default:chip=-1;rev='x';
+ }
+
+ dprintk (SX_DEBUG_INIT, " GFCR = 0x%02x\n", sx_in_off(bp, CD186x_GFRCR) );
+
+#ifdef SPECIALIX_TIMER
+ init_timer (&missed_irq_timer);
+ missed_irq_timer.function = missed_irq;
+ missed_irq_timer.data = (unsigned long) bp;
+ missed_irq_timer.expires = jiffies + sx_poll;
+ add_timer (&missed_irq_timer);
+#endif
+
+ printk(KERN_INFO"sx%d: specialix IO8+ board detected at 0x%03x, IRQ %d, CD%d Rev. %c.\n",
+ board_No(bp),
+ bp->base, bp->irq,
+ chip, rev);
+
+ func_exit();
+ return 0;
+}
+
+/*
+ *
+ * Interrupt processing routines.
+ * */
+
+static inline void sx_mark_event(struct specialix_port * port, int event)
+{
+ func_enter();
+
+ set_bit(event, &port->event);
+ schedule_work(&port->tqueue);
+
+ func_exit();
+}
+
+
+static inline struct specialix_port * sx_get_port(struct specialix_board * bp,
+ unsigned char const * what)
+{
+ unsigned char channel;
+ struct specialix_port * port = NULL;
+
+ channel = sx_in(bp, CD186x_GICR) >> GICR_CHAN_OFF;
+ dprintk (SX_DEBUG_CHAN, "channel: %d\n", channel);
+ if (channel < CD186x_NCH) {
+ port = &sx_port[board_No(bp) * SX_NPORT + channel];
+ dprintk (SX_DEBUG_CHAN, "port: %d %p flags: 0x%x\n",board_No(bp) * SX_NPORT + channel, port, port->flags & ASYNC_INITIALIZED);
+
+ if (port->flags & ASYNC_INITIALIZED) {
+ dprintk (SX_DEBUG_CHAN, "port: %d %p\n", channel, port);
+ func_exit();
+ return port;
+ }
+ }
+ printk(KERN_INFO "sx%d: %s interrupt from invalid port %d\n",
+ board_No(bp), what, channel);
+ return NULL;
+}
+
+
+static inline void sx_receive_exc(struct specialix_board * bp)
+{
+ struct specialix_port *port;
+ struct tty_struct *tty;
+ unsigned char status;
+ unsigned char ch;
+
+ func_enter();
+
+ port = sx_get_port(bp, "Receive");
+ if (!port) {
+ dprintk (SX_DEBUG_RX, "Hmm, couldn't find port.\n");
+ func_exit();
+ return;
+ }
+ tty = port->tty;
+ dprintk (SX_DEBUG_RX, "port: %p count: %d BUFF_SIZE: %d\n",
+ port, tty->flip.count, TTY_FLIPBUF_SIZE);
+
+ status = sx_in(bp, CD186x_RCSR);
+
+ dprintk (SX_DEBUG_RX, "status: 0x%x\n", status);
+ if (status & RCSR_OE) {
+ port->overrun++;
+ dprintk(SX_DEBUG_FIFO, "sx%d: port %d: Overrun. Total %ld overruns.\n",
+ board_No(bp), port_No(port), port->overrun);
+ }
+ status &= port->mark_mask;
+
+ /* This flip buffer check needs to be below the reading of the
+ status register to reset the chip's IRQ.... */
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ dprintk(SX_DEBUG_FIFO, "sx%d: port %d: Working around flip buffer overflow.\n",
+ board_No(bp), port_No(port));
+ func_exit();
+ return;
+ }
+
+ ch = sx_in(bp, CD186x_RDR);
+ if (!status) {
+ func_exit();
+ return;
+ }
+ if (status & RCSR_TOUT) {
+ printk(KERN_INFO "sx%d: port %d: Receiver timeout. Hardware problems ?\n",
+ board_No(bp), port_No(port));
+ func_exit();
+ return;
+
+ } else if (status & RCSR_BREAK) {
+ dprintk(SX_DEBUG_RX, "sx%d: port %d: Handling break...\n",
+ board_No(bp), port_No(port));
+ *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+ if (port->flags & ASYNC_SAK)
+ do_SAK(tty);
+
+ } else if (status & RCSR_PE)
+ *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+
+ else if (status & RCSR_FE)
+ *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+
+ else if (status & RCSR_OE)
+ *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+
+ else
+ *tty->flip.flag_buf_ptr++ = 0;
+
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ schedule_delayed_work(&tty->flip.work, 1);
+
+ func_exit();
+}
+
+
+static inline void sx_receive(struct specialix_board * bp)
+{
+ struct specialix_port *port;
+ struct tty_struct *tty;
+ unsigned char count;
+
+ func_enter();
+
+ if (!(port = sx_get_port(bp, "Receive"))) {
+ dprintk (SX_DEBUG_RX, "Hmm, couldn't find port.\n");
+ func_exit();
+ return;
+ }
+ tty = port->tty;
+
+ count = sx_in(bp, CD186x_RDCR);
+ dprintk (SX_DEBUG_RX, "port: %p: count: %d\n", port, count);
+ port->hits[count > 8 ? 9 : count]++;
+
+ while (count--) {
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+ printk(KERN_INFO "sx%d: port %d: Working around flip buffer overflow.\n",
+ board_No(bp), port_No(port));
+ break;
+ }
+ *tty->flip.char_buf_ptr++ = sx_in(bp, CD186x_RDR);
+ *tty->flip.flag_buf_ptr++ = 0;
+ tty->flip.count++;
+ }
+ schedule_delayed_work(&tty->flip.work, 1);
+
+ func_exit();
+}
+
+
+static inline void sx_transmit(struct specialix_board * bp)
+{
+ struct specialix_port *port;
+ struct tty_struct *tty;
+ unsigned char count;
+
+ func_enter();
+ if (!(port = sx_get_port(bp, "Transmit"))) {
+ func_exit();
+ return;
+ }
+ dprintk (SX_DEBUG_TX, "port: %p\n", port);
+ tty = port->tty;
+
+ if (port->IER & IER_TXEMPTY) {
+ /* FIFO drained */
+ sx_out(bp, CD186x_CAR, port_No(port));
+ port->IER &= ~IER_TXEMPTY;
+ sx_out(bp, CD186x_IER, port->IER);
+ func_exit();
+ return;
+ }
+
+ if ((port->xmit_cnt <= 0 && !port->break_length)
+ || tty->stopped || tty->hw_stopped) {
+ sx_out(bp, CD186x_CAR, port_No(port));
+ port->IER &= ~IER_TXRDY;
+ sx_out(bp, CD186x_IER, port->IER);
+ func_exit();
+ return;
+ }
+
+ if (port->break_length) {
+ if (port->break_length > 0) {
+ if (port->COR2 & COR2_ETC) {
+ sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+ sx_out(bp, CD186x_TDR, CD186x_C_SBRK);
+ port->COR2 &= ~COR2_ETC;
+ }
+ count = min_t(int, port->break_length, 0xff);
+ sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+ sx_out(bp, CD186x_TDR, CD186x_C_DELAY);
+ sx_out(bp, CD186x_TDR, count);
+ if (!(port->break_length -= count))
+ port->break_length--;
+ } else {
+ sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+ sx_out(bp, CD186x_TDR, CD186x_C_EBRK);
+ sx_out(bp, CD186x_COR2, port->COR2);
+ sx_wait_CCR(bp);
+ sx_out(bp, CD186x_CCR, CCR_CORCHG2);
+ port->break_length = 0;
+ }
+
+ func_exit();
+ return;
+ }
+
+ count = CD186x_NFIFO;
+ do {
+ sx_out(bp, CD186x_TDR, port->xmit_buf[port->xmit_tail++]);
+ port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ if (--port->xmit_cnt <= 0)
+ break;
+ } while (--count > 0);
+
+ if (port->xmit_cnt <= 0) {
+ sx_out(bp, CD186x_CAR, port_No(port));
+ port->IER &= ~IER_TXRDY;
+ sx_out(bp, CD186x_IER, port->IER);
+ }
+ if (port->xmit_cnt <= port->wakeup_chars)
+ sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+
+ func_exit();
+}
+
+
+static inline void sx_check_modem(struct specialix_board * bp)
+{
+ struct specialix_port *port;
+ struct tty_struct *tty;
+ unsigned char mcr;
+ int msvr_cd;
+
+ dprintk (SX_DEBUG_SIGNALS, "Modem intr. ");
+ if (!(port = sx_get_port(bp, "Modem")))
+ return;
+
+ tty = port->tty;
+
+ mcr = sx_in(bp, CD186x_MCR);
+ printk ("mcr = %02x.\n", mcr);
+
+ if ((mcr & MCR_CDCHG)) {
+ dprintk (SX_DEBUG_SIGNALS, "CD just changed... ");
+ msvr_cd = sx_in(bp, CD186x_MSVR) & MSVR_CD;
+ if (msvr_cd) {
+ dprintk (SX_DEBUG_SIGNALS, "Waking up guys in open.\n");
+ wake_up_interruptible(&port->open_wait);
+ } else {
+ dprintk (SX_DEBUG_SIGNALS, "Sending HUP.\n");
+ schedule_work(&port->tqueue_hangup);
+ }
+ }
+
+#ifdef SPECIALIX_BRAIN_DAMAGED_CTS
+ if (mcr & MCR_CTSCHG) {
+ if (sx_in(bp, CD186x_MSVR) & MSVR_CTS) {
+ tty->hw_stopped = 0;
+ port->IER |= IER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->IER &= ~IER_TXRDY;
+ }
+ sx_out(bp, CD186x_IER, port->IER);
+ }
+ if (mcr & MCR_DSSXHG) {
+ if (sx_in(bp, CD186x_MSVR) & MSVR_DSR) {
+ tty->hw_stopped = 0;
+ port->IER |= IER_TXRDY;
+ if (port->xmit_cnt <= port->wakeup_chars)
+ sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+ } else {
+ tty->hw_stopped = 1;
+ port->IER &= ~IER_TXRDY;
+ }
+ sx_out(bp, CD186x_IER, port->IER);
+ }
+#endif /* SPECIALIX_BRAIN_DAMAGED_CTS */
+
+ /* Clear change bits */
+ sx_out(bp, CD186x_MCR, 0);
+}
+
+
+/* The main interrupt processing routine */
+static irqreturn_t sx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ unsigned char status;
+ unsigned char ack;
+ struct specialix_board *bp;
+ unsigned long loop = 0;
+ int saved_reg;
+ unsigned long flags;
+
+ func_enter();
+
+ bp = dev_id;
+ spin_lock_irqsave(&bp->lock, flags);
+
+ dprintk (SX_DEBUG_FLOW, "enter %s port %d room: %ld\n", __FUNCTION__, port_No(sx_get_port(bp, "INT")), SERIAL_XMIT_SIZE - sx_get_port(bp, "ITN")->xmit_cnt - 1);
+ if (!bp || !(bp->flags & SX_BOARD_ACTIVE)) {
+ dprintk (SX_DEBUG_IRQ, "sx: False interrupt. irq %d.\n", irq);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ func_exit();
+ return IRQ_NONE;
+ }
+
+ saved_reg = bp->reg;
+
+ while ((++loop < 16) && (status = (sx_in(bp, CD186x_SRSR) &
+ (SRSR_RREQint |
+ SRSR_TREQint |
+ SRSR_MREQint)))) {
+ if (status & SRSR_RREQint) {
+ ack = sx_in(bp, CD186x_RRAR);
+
+ if (ack == (SX_ID | GIVR_IT_RCV))
+ sx_receive(bp);
+ else if (ack == (SX_ID | GIVR_IT_REXC))
+ sx_receive_exc(bp);
+ else
+ printk(KERN_ERR "sx%d: status: 0x%x Bad receive ack 0x%02x.\n",
+ board_No(bp), status, ack);
+
+ } else if (status & SRSR_TREQint) {
+ ack = sx_in(bp, CD186x_TRAR);
+
+ if (ack == (SX_ID | GIVR_IT_TX))
+ sx_transmit(bp);
+ else
+ printk(KERN_ERR "sx%d: status: 0x%x Bad transmit ack 0x%02x. port: %d\n",
+ board_No(bp), status, ack, port_No (sx_get_port (bp, "Int")));
+ } else if (status & SRSR_MREQint) {
+ ack = sx_in(bp, CD186x_MRAR);
+
+ if (ack == (SX_ID | GIVR_IT_MODEM))
+ sx_check_modem(bp);
+ else
+ printk(KERN_ERR "sx%d: status: 0x%x Bad modem ack 0x%02x.\n",
+ board_No(bp), status, ack);
+
+ }
+
+ sx_out(bp, CD186x_EOIR, 0); /* Mark end of interrupt */
+ }
+ bp->reg = saved_reg;
+ outb (bp->reg, bp->base + SX_ADDR_REG);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ func_exit();
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * Routines for open & close processing.
+ */
+
+static void turn_ints_off (struct specialix_board *bp)
+{
+ unsigned long flags;
+
+ func_enter();
+ if (bp->flags & SX_BOARD_IS_PCI) {
+ /* This was intended for enabeling the interrupt on the
+ * PCI card. However it seems that it's already enabled
+ * and as PCI interrupts can be shared, there is no real
+ * reason to have to turn it off. */
+ }
+
+ spin_lock_irqsave(&bp->lock, flags);
+ (void) sx_in_off (bp, 0); /* Turn off interrupts. */
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ func_exit();
+}
+
+static void turn_ints_on (struct specialix_board *bp)
+{
+ unsigned long flags;
+
+ func_enter();
+
+ if (bp->flags & SX_BOARD_IS_PCI) {
+ /* play with the PCI chip. See comment above. */
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ (void) sx_in (bp, 0); /* Turn ON interrupts. */
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ func_exit();
+}
+
+
+/* Called with disabled interrupts */
+static inline int sx_setup_board(struct specialix_board * bp)
+{
+ int error;
+
+ if (bp->flags & SX_BOARD_ACTIVE)
+ return 0;
+
+ if (bp->flags & SX_BOARD_IS_PCI)
+ error = request_irq(bp->irq, sx_interrupt, SA_INTERRUPT | SA_SHIRQ, "specialix IO8+", bp);
+ else
+ error = request_irq(bp->irq, sx_interrupt, SA_INTERRUPT, "specialix IO8+", bp);
+
+ if (error)
+ return error;
+
+ turn_ints_on (bp);
+ bp->flags |= SX_BOARD_ACTIVE;
+
+ return 0;
+}
+
+
+/* Called with disabled interrupts */
+static inline void sx_shutdown_board(struct specialix_board *bp)
+{
+ func_enter();
+
+ if (!(bp->flags & SX_BOARD_ACTIVE)) {
+ func_exit();
+ return;
+ }
+
+ bp->flags &= ~SX_BOARD_ACTIVE;
+
+ dprintk (SX_DEBUG_IRQ, "Freeing IRQ%d for board %d.\n",
+ bp->irq, board_No (bp));
+ free_irq(bp->irq, bp);
+
+ turn_ints_off (bp);
+
+
+ func_exit();
+}
+
+
+/*
+ * Setting up port characteristics.
+ * Must be called with disabled interrupts
+ */
+static void sx_change_speed(struct specialix_board *bp, struct specialix_port *port)
+{
+ struct tty_struct *tty;
+ unsigned long baud;
+ long tmp;
+ unsigned char cor1 = 0, cor3 = 0;
+ unsigned char mcor1 = 0, mcor2 = 0;
+ static unsigned long again;
+ unsigned long flags;
+
+ func_enter();
+
+ if (!(tty = port->tty) || !tty->termios) {
+ func_exit();
+ return;
+ }
+
+ port->IER = 0;
+ port->COR2 = 0;
+ /* Select port on the board */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+
+ /* The Specialix board doens't implement the RTS lines.
+ They are used to set the IRQ level. Don't touch them. */
+ if (SX_CRTSCTS(tty))
+ port->MSVR = MSVR_DTR | (sx_in(bp, CD186x_MSVR) & MSVR_RTS);
+ else
+ port->MSVR = (sx_in(bp, CD186x_MSVR) & MSVR_RTS);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ dprintk (SX_DEBUG_TERMIOS, "sx: got MSVR=%02x.\n", port->MSVR);
+ baud = C_BAUD(tty);
+
+ if (baud & CBAUDEX) {
+ baud &= ~CBAUDEX;
+ if (baud < 1 || baud > 2)
+ port->tty->termios->c_cflag &= ~CBAUDEX;
+ else
+ baud += 15;
+ }
+ if (baud == 15) {
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baud ++;
+ if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baud += 2;
+ }
+
+
+ if (!baud_table[baud]) {
+ /* Drop DTR & exit */
+ dprintk (SX_DEBUG_TERMIOS, "Dropping DTR... Hmm....\n");
+ if (!SX_CRTSCTS (tty)) {
+ port -> MSVR &= ~ MSVR_DTR;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_MSVR, port->MSVR );
+ spin_unlock_irqrestore(&bp->lock, flags);
+ }
+ else
+ dprintk (SX_DEBUG_TERMIOS, "Can't drop DTR: no DTR.\n");
+ return;
+ } else {
+ /* Set DTR on */
+ if (!SX_CRTSCTS (tty)) {
+ port ->MSVR |= MSVR_DTR;
+ }
+ }
+
+ /*
+ * Now we must calculate some speed depended things
+ */
+
+ /* Set baud rate for port */
+ tmp = port->custom_divisor ;
+ if ( tmp )
+ printk (KERN_INFO "sx%d: Using custom baud rate divisor %ld. \n"
+ "This is an untested option, please be carefull.\n",
+ port_No (port), tmp);
+ else
+ tmp = (((SX_OSCFREQ + baud_table[baud]/2) / baud_table[baud] +
+ CD186x_TPC/2) / CD186x_TPC);
+
+ if ((tmp < 0x10) && time_before(again, jiffies)) {
+ again = jiffies + HZ * 60;
+ /* Page 48 of version 2.0 of the CL-CD1865 databook */
+ if (tmp >= 12) {
+ printk (KERN_INFO "sx%d: Baud rate divisor is %ld. \n"
+ "Performance degradation is possible.\n"
+ "Read specialix.txt for more info.\n",
+ port_No (port), tmp);
+ } else {
+ printk (KERN_INFO "sx%d: Baud rate divisor is %ld. \n"
+ "Warning: overstressing Cirrus chip. "
+ "This might not work.\n"
+ "Read specialix.txt for more info.\n",
+ port_No (port), tmp);
+ }
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_RBPRH, (tmp >> 8) & 0xff);
+ sx_out(bp, CD186x_TBPRH, (tmp >> 8) & 0xff);
+ sx_out(bp, CD186x_RBPRL, tmp & 0xff);
+ sx_out(bp, CD186x_TBPRL, tmp & 0xff);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (port->custom_divisor) {
+ baud = (SX_OSCFREQ + port->custom_divisor/2) / port->custom_divisor;
+ baud = ( baud + 5 ) / 10;
+ } else
+ baud = (baud_table[baud] + 5) / 10; /* Estimated CPS */
+
+ /* Two timer ticks seems enough to wakeup something like SLIP driver */
+ tmp = ((baud + HZ/2) / HZ) * 2 - CD186x_NFIFO;
+ port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ?
+ SERIAL_XMIT_SIZE - 1 : tmp);
+
+ /* Receiver timeout will be transmission time for 1.5 chars */
+ tmp = (SPECIALIX_TPS + SPECIALIX_TPS/2 + baud/2) / baud;
+ tmp = (tmp > 0xff) ? 0xff : tmp;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_RTPR, tmp);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ switch (C_CSIZE(tty)) {
+ case CS5:
+ cor1 |= COR1_5BITS;
+ break;
+ case CS6:
+ cor1 |= COR1_6BITS;
+ break;
+ case CS7:
+ cor1 |= COR1_7BITS;
+ break;
+ case CS8:
+ cor1 |= COR1_8BITS;
+ break;
+ }
+
+ if (C_CSTOPB(tty))
+ cor1 |= COR1_2SB;
+
+ cor1 |= COR1_IGNORE;
+ if (C_PARENB(tty)) {
+ cor1 |= COR1_NORMPAR;
+ if (C_PARODD(tty))
+ cor1 |= COR1_ODDP;
+ if (I_INPCK(tty))
+ cor1 &= ~COR1_IGNORE;
+ }
+ /* Set marking of some errors */
+ port->mark_mask = RCSR_OE | RCSR_TOUT;
+ if (I_INPCK(tty))
+ port->mark_mask |= RCSR_FE | RCSR_PE;
+ if (I_BRKINT(tty) || I_PARMRK(tty))
+ port->mark_mask |= RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ port->mark_mask &= ~(RCSR_FE | RCSR_PE);
+ if (I_IGNBRK(tty)) {
+ port->mark_mask &= ~RCSR_BREAK;
+ if (I_IGNPAR(tty))
+ /* Real raw mode. Ignore all */
+ port->mark_mask &= ~RCSR_OE;
+ }
+ /* Enable Hardware Flow Control */
+ if (C_CRTSCTS(tty)) {
+#ifdef SPECIALIX_BRAIN_DAMAGED_CTS
+ port->IER |= IER_DSR | IER_CTS;
+ mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+ mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+ spin_lock_irqsave(&bp->lock, flags);
+ tty->hw_stopped = !(sx_in(bp, CD186x_MSVR) & (MSVR_CTS|MSVR_DSR));
+ spin_unlock_irqrestore(&bp->lock, flags);
+#else
+ port->COR2 |= COR2_CTSAE;
+#endif
+ }
+ /* Enable Software Flow Control. FIXME: I'm not sure about this */
+ /* Some people reported that it works, but I still doubt it */
+ if (I_IXON(tty)) {
+ port->COR2 |= COR2_TXIBE;
+ cor3 |= (COR3_FCT | COR3_SCDE);
+ if (I_IXANY(tty))
+ port->COR2 |= COR2_IXM;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_SCHR1, START_CHAR(tty));
+ sx_out(bp, CD186x_SCHR2, STOP_CHAR(tty));
+ sx_out(bp, CD186x_SCHR3, START_CHAR(tty));
+ sx_out(bp, CD186x_SCHR4, STOP_CHAR(tty));
+ spin_unlock_irqrestore(&bp->lock, flags);
+ }
+ if (!C_CLOCAL(tty)) {
+ /* Enable CD check */
+ port->IER |= IER_CD;
+ mcor1 |= MCOR1_CDZD;
+ mcor2 |= MCOR2_CDOD;
+ }
+
+ if (C_CREAD(tty))
+ /* Enable receiver */
+ port->IER |= IER_RXD;
+
+ /* Set input FIFO size (1-8 bytes) */
+ cor3 |= sx_rxfifo;
+ /* Setting up CD186x channel registers */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_COR1, cor1);
+ sx_out(bp, CD186x_COR2, port->COR2);
+ sx_out(bp, CD186x_COR3, cor3);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ /* Make CD186x know about registers change */
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3);
+ /* Setting up modem option registers */
+ dprintk (SX_DEBUG_TERMIOS, "Mcor1 = %02x, mcor2 = %02x.\n", mcor1, mcor2);
+ sx_out(bp, CD186x_MCOR1, mcor1);
+ sx_out(bp, CD186x_MCOR2, mcor2);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ /* Enable CD186x transmitter & receiver */
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_TXEN | CCR_RXEN);
+ /* Enable interrupts */
+ sx_out(bp, CD186x_IER, port->IER);
+ /* And finally set the modem lines... */
+ sx_out(bp, CD186x_MSVR, port->MSVR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ func_exit();
+}
+
+
+/* Must be called with interrupts enabled */
+static int sx_setup_port(struct specialix_board *bp, struct specialix_port *port)
+{
+ unsigned long flags;
+
+ func_enter();
+
+ if (port->flags & ASYNC_INITIALIZED) {
+ func_exit();
+ return 0;
+ }
+
+ if (!port->xmit_buf) {
+ /* We may sleep in get_zeroed_page() */
+ unsigned long tmp;
+
+ if (!(tmp = get_zeroed_page(GFP_KERNEL))) {
+ func_exit();
+ return -ENOMEM;
+ }
+
+ if (port->xmit_buf) {
+ free_page(tmp);
+ func_exit();
+ return -ERESTARTSYS;
+ }
+ port->xmit_buf = (unsigned char *) tmp;
+ }
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (port->tty)
+ clear_bit(TTY_IO_ERROR, &port->tty->flags);
+
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ sx_change_speed(bp, port);
+ port->flags |= ASYNC_INITIALIZED;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+
+ func_exit();
+ return 0;
+}
+
+
+/* Must be called with interrupts disabled */
+static void sx_shutdown_port(struct specialix_board *bp, struct specialix_port *port)
+{
+ struct tty_struct *tty;
+ int i;
+ unsigned long flags;
+
+ func_enter();
+
+ if (!(port->flags & ASYNC_INITIALIZED)) {
+ func_exit();
+ return;
+ }
+
+ if (sx_debug & SX_DEBUG_FIFO) {
+ dprintk(SX_DEBUG_FIFO, "sx%d: port %d: %ld overruns, FIFO hits [ ",
+ board_No(bp), port_No(port), port->overrun);
+ for (i = 0; i < 10; i++) {
+ dprintk(SX_DEBUG_FIFO, "%ld ", port->hits[i]);
+ }
+ dprintk(SX_DEBUG_FIFO, "].\n");
+ }
+
+ if (port->xmit_buf) {
+ free_page((unsigned long) port->xmit_buf);
+ port->xmit_buf = NULL;
+ }
+
+ /* Select port */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+
+ if (!(tty = port->tty) || C_HUPCL(tty)) {
+ /* Drop DTR */
+ sx_out(bp, CD186x_MSVDTR, 0);
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+ /* Reset port */
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_SOFTRESET);
+ /* Disable all interrupts from this port */
+ port->IER = 0;
+ sx_out(bp, CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (tty)
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ port->flags &= ~ASYNC_INITIALIZED;
+
+ if (!bp->count)
+ sx_shutdown_board(bp);
+ func_exit();
+}
+
+
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct specialix_port *port)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct specialix_board *bp = port_Board(port);
+ int retval;
+ int do_clocal = 0;
+ int CD;
+ unsigned long flags;
+
+ func_enter();
+
+ /*
+ * If the device is in the middle of being closed, then block
+ * until it's done, and then try again.
+ */
+ if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&port->close_wait);
+ if (port->flags & ASYNC_HUP_NOTIFY) {
+ func_exit();
+ return -EAGAIN;
+ } else {
+ func_exit();
+ return -ERESTARTSYS;
+ }
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if ((filp->f_flags & O_NONBLOCK) ||
+ (tty->flags & (1 << TTY_IO_ERROR))) {
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ func_exit();
+ return 0;
+ }
+
+ if (C_CLOCAL(tty))
+ do_clocal = 1;
+
+ /*
+ * Block waiting for the carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * rs_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+ retval = 0;
+ add_wait_queue(&port->open_wait, &wait);
+ spin_lock_irqsave(&port->lock, flags);
+ if (!tty_hung_up_p(filp)) {
+ port->count--;
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+ port->blocked_open++;
+ while (1) {
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ CD = sx_in(bp, CD186x_MSVR) & MSVR_CD;
+ if (SX_CRTSCTS (tty)) {
+ /* Activate RTS */
+ port->MSVR |= MSVR_DTR; /* WTF? */
+ sx_out (bp, CD186x_MSVR, port->MSVR);
+ } else {
+ /* Activate DTR */
+ port->MSVR |= MSVR_DTR;
+ sx_out (bp, CD186x_MSVR, port->MSVR);
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (tty_hung_up_p(filp) ||
+ !(port->flags & ASYNC_INITIALIZED)) {
+ if (port->flags & ASYNC_HUP_NOTIFY)
+ retval = -EAGAIN;
+ else
+ retval = -ERESTARTSYS;
+ break;
+ }
+ if (!(port->flags & ASYNC_CLOSING) &&
+ (do_clocal || CD))
+ break;
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&port->open_wait, &wait);
+ spin_lock_irqsave(&port->lock, flags);
+ if (!tty_hung_up_p(filp)) {
+ port->count++;
+ }
+ port->blocked_open--;
+ spin_unlock_irqrestore(&port->lock, flags);
+ if (retval) {
+ func_exit();
+ return retval;
+ }
+
+ port->flags |= ASYNC_NORMAL_ACTIVE;
+ func_exit();
+ return 0;
+}
+
+
+static int sx_open(struct tty_struct * tty, struct file * filp)
+{
+ int board;
+ int error;
+ struct specialix_port * port;
+ struct specialix_board * bp;
+ int i;
+ unsigned long flags;
+
+ func_enter();
+
+ board = SX_BOARD(tty->index);
+
+ if (board >= SX_NBOARD || !(sx_board[board].flags & SX_BOARD_PRESENT)) {
+ func_exit();
+ return -ENODEV;
+ }
+
+ bp = &sx_board[board];
+ port = sx_port + board * SX_NPORT + SX_PORT(tty->index);
+ port->overrun = 0;
+ for (i = 0; i < 10; i++)
+ port->hits[i]=0;
+
+ dprintk (SX_DEBUG_OPEN, "Board = %d, bp = %p, port = %p, portno = %d.\n",
+ board, bp, port, SX_PORT(tty->index));
+
+ if (sx_paranoia_check(port, tty->name, "sx_open")) {
+ func_enter();
+ return -ENODEV;
+ }
+
+ if ((error = sx_setup_board(bp))) {
+ func_exit();
+ return error;
+ }
+
+ spin_lock_irqsave(&bp->lock, flags);
+ port->count++;
+ bp->count++;
+ tty->driver_data = port;
+ port->tty = tty;
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ if ((error = sx_setup_port(bp, port))) {
+ func_enter();
+ return error;
+ }
+
+ if ((error = block_til_ready(tty, filp, port))) {
+ func_enter();
+ return error;
+ }
+
+ func_exit();
+ return 0;
+}
+
+
+static void sx_close(struct tty_struct * tty, struct file * filp)
+{
+ struct specialix_port *port = (struct specialix_port *) tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+ unsigned long timeout;
+
+ func_enter();
+ if (!port || sx_paranoia_check(port, tty->name, "close")) {
+ func_exit();
+ return;
+ }
+ spin_lock_irqsave(&port->lock, flags);
+
+ if (tty_hung_up_p(filp)) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+ if ((tty->count == 1) && (port->count != 1)) {
+ printk(KERN_ERR "sx%d: sx_close: bad port count;"
+ " tty->count is 1, port count is %d\n",
+ board_No(bp), port->count);
+ port->count = 1;
+ }
+
+ if (port->count > 1) {
+ port->count--;
+ bp->count--;
+
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ func_exit();
+ return;
+ }
+ port->flags |= ASYNC_CLOSING;
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ spin_unlock_irqrestore(&port->lock, flags);
+ dprintk (SX_DEBUG_OPEN, "Closing\n");
+ if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ tty_wait_until_sent(tty, port->closing_wait);
+ }
+ /*
+ * At this point we stop accepting input. To do this, we
+ * disable the receive line status interrupts, and tell the
+ * interrupt driver to stop checking the data ready bit in the
+ * line status register.
+ */
+ dprintk (SX_DEBUG_OPEN, "Closed\n");
+ port->IER &= ~IER_RXD;
+ if (port->flags & ASYNC_INITIALIZED) {
+ port->IER &= ~IER_TXRDY;
+ port->IER |= IER_TXEMPTY;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ /*
+ * Before we drop DTR, make sure the UART transmitter
+ * has completely drained; this is especially
+ * important if there is a transmit FIFO!
+ */
+ timeout = jiffies+HZ;
+ while(port->IER & IER_TXEMPTY) {
+ set_current_state (TASK_INTERRUPTIBLE);
+ msleep_interruptible(jiffies_to_msecs(port->timeout));
+ if (time_after(jiffies, timeout)) {
+ printk (KERN_INFO "Timeout waiting for close\n");
+ break;
+ }
+ }
+
+ }
+
+ if (--bp->count < 0) {
+ printk(KERN_ERR "sx%d: sx_shutdown_port: bad board count: %d port: %d\n",
+ board_No(bp), bp->count, tty->index);
+ bp->count = 0;
+ }
+ if (--port->count < 0) {
+ printk(KERN_ERR "sx%d: sx_close: bad port count for tty%d: %d\n",
+ board_No(bp), port_No(port), port->count);
+ port->count = 0;
+ }
+
+ sx_shutdown_port(bp, port);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ tty_ldisc_flush(tty);
+ spin_lock_irqsave(&port->lock, flags);
+ tty->closing = 0;
+ port->event = 0;
+ port->tty = NULL;
+ spin_unlock_irqrestore(&port->lock, flags);
+ if (port->blocked_open) {
+ if (port->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(port->close_delay));
+ }
+ wake_up_interruptible(&port->open_wait);
+ }
+ port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&port->close_wait);
+
+ func_exit();
+}
+
+
+static int sx_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ int c, total = 0;
+ unsigned long flags;
+
+ func_enter();
+ if (sx_paranoia_check(port, tty->name, "sx_write")) {
+ func_exit();
+ return 0;
+ }
+
+ bp = port_Board(port);
+
+ if (!tty || !port->xmit_buf || !tmp_buf) {
+ func_exit();
+ return 0;
+ }
+
+ while (1) {
+ spin_lock_irqsave(&port->lock, flags);
+ c = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - port->xmit_head));
+ if (c <= 0) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ break;
+ }
+ memcpy(port->xmit_buf + port->xmit_head, buf, c);
+ port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+ port->xmit_cnt += c;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ buf += c;
+ count -= c;
+ total += c;
+ }
+
+ spin_lock_irqsave(&bp->lock, flags);
+ if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
+ !(port->IER & IER_TXRDY)) {
+ port->IER |= IER_TXRDY;
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_IER, port->IER);
+ }
+ spin_unlock_irqrestore(&bp->lock, flags);
+ func_exit();
+
+ return total;
+}
+
+
+static void sx_put_char(struct tty_struct * tty, unsigned char ch)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ unsigned long flags;
+ struct specialix_board * bp;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_put_char")) {
+ func_exit();
+ return;
+ }
+ dprintk (SX_DEBUG_TX, "check tty: %p %p\n", tty, port->xmit_buf);
+ if (!tty || !port->xmit_buf) {
+ func_exit();
+ return;
+ }
+ bp = port_Board(port);
+ spin_lock_irqsave(&port->lock, flags);
+
+ dprintk (SX_DEBUG_TX, "xmit_cnt: %d xmit_buf: %p\n", port->xmit_cnt, port->xmit_buf);
+ if ((port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) || (!port->xmit_buf)) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ dprintk (SX_DEBUG_TX, "Exit size\n");
+ func_exit();
+ return;
+ }
+ dprintk (SX_DEBUG_TX, "Handle xmit: %p %p\n", port, port->xmit_buf);
+ port->xmit_buf[port->xmit_head++] = ch;
+ port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+ port->xmit_cnt++;
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ func_exit();
+}
+
+
+static void sx_flush_chars(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ unsigned long flags;
+ struct specialix_board * bp = port_Board(port);
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_flush_chars")) {
+ func_exit();
+ return;
+ }
+ if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+ !port->xmit_buf) {
+ func_exit();
+ return;
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ port->IER |= IER_TXRDY;
+ sx_out(port_Board(port), CD186x_CAR, port_No(port));
+ sx_out(port_Board(port), CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ func_exit();
+}
+
+
+static int sx_write_room(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ int ret;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_write_room")) {
+ func_exit();
+ return 0;
+ }
+
+ ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+
+ func_exit();
+ return ret;
+}
+
+
+static int sx_chars_in_buffer(struct tty_struct *tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_chars_in_buffer")) {
+ func_exit();
+ return 0;
+ }
+ func_exit();
+ return port->xmit_cnt;
+}
+
+
+static void sx_flush_buffer(struct tty_struct *tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ unsigned long flags;
+ struct specialix_board * bp;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_flush_buffer")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+ spin_lock_irqsave(&port->lock, flags);
+ port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+ spin_unlock_irqrestore(&port->lock, flags);
+ tty_wakeup(tty);
+
+ func_exit();
+}
+
+
+static int sx_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board * bp;
+ unsigned char status;
+ unsigned int result;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, __FUNCTION__)) {
+ func_exit();
+ return -ENODEV;
+ }
+
+ bp = port_Board(port);
+ spin_lock_irqsave (&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ status = sx_in(bp, CD186x_MSVR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ dprintk (SX_DEBUG_INIT, "Got msvr[%d] = %02x, car = %d.\n",
+ port_No(port), status, sx_in (bp, CD186x_CAR));
+ dprintk (SX_DEBUG_INIT, "sx_port = %p, port = %p\n", sx_port, port);
+ if (SX_CRTSCTS(port->tty)) {
+ result = /* (status & MSVR_RTS) ? */ TIOCM_DTR /* : 0) */
+ | ((status & MSVR_DTR) ? TIOCM_RTS : 0)
+ | ((status & MSVR_CD) ? TIOCM_CAR : 0)
+ |/* ((status & MSVR_DSR) ? */ TIOCM_DSR /* : 0) */
+ | ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+ } else {
+ result = /* (status & MSVR_RTS) ? */ TIOCM_RTS /* : 0) */
+ | ((status & MSVR_DTR) ? TIOCM_DTR : 0)
+ | ((status & MSVR_CD) ? TIOCM_CAR : 0)
+ |/* ((status & MSVR_DSR) ? */ TIOCM_DSR /* : 0) */
+ | ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+ }
+
+ func_exit();
+
+ return result;
+}
+
+
+static int sx_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ unsigned long flags;
+ struct specialix_board *bp;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, __FUNCTION__)) {
+ func_exit();
+ return -ENODEV;
+ }
+
+ bp = port_Board(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+ /* if (set & TIOCM_RTS)
+ port->MSVR |= MSVR_RTS; */
+ /* if (set & TIOCM_DTR)
+ port->MSVR |= MSVR_DTR; */
+
+ if (SX_CRTSCTS(port->tty)) {
+ if (set & TIOCM_RTS)
+ port->MSVR |= MSVR_DTR;
+ } else {
+ if (set & TIOCM_DTR)
+ port->MSVR |= MSVR_DTR;
+ }
+
+ /* if (clear & TIOCM_RTS)
+ port->MSVR &= ~MSVR_RTS; */
+ /* if (clear & TIOCM_DTR)
+ port->MSVR &= ~MSVR_DTR; */
+ if (SX_CRTSCTS(port->tty)) {
+ if (clear & TIOCM_RTS)
+ port->MSVR &= ~MSVR_DTR;
+ } else {
+ if (clear & TIOCM_DTR)
+ port->MSVR &= ~MSVR_DTR;
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_MSVR, port->MSVR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ spin_unlock_irqrestore(&port->lock, flags);
+ func_exit();
+ return 0;
+}
+
+
+static inline void sx_send_break(struct specialix_port * port, unsigned long length)
+{
+ struct specialix_board *bp = port_Board(port);
+ unsigned long flags;
+
+ func_enter();
+
+ spin_lock_irqsave (&port->lock, flags);
+ port->break_length = SPECIALIX_TPS / HZ * length;
+ port->COR2 |= COR2_ETC;
+ port->IER |= IER_TXRDY;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_COR2, port->COR2);
+ sx_out(bp, CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ spin_unlock_irqrestore (&port->lock, flags);
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_CORCHG2);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ sx_wait_CCR(bp);
+
+ func_exit();
+}
+
+
+static inline int sx_set_serial_info(struct specialix_port * port,
+ struct serial_struct __user * newinfo)
+{
+ struct serial_struct tmp;
+ struct specialix_board *bp = port_Board(port);
+ int change_speed;
+
+ func_enter();
+ /*
+ error = verify_area(VERIFY_READ, (void *) newinfo, sizeof(tmp));
+ if (error) {
+ func_exit();
+ return error;
+ }
+ */
+ if (copy_from_user(&tmp, newinfo, sizeof(tmp))) {
+ func_enter();
+ return -EFAULT;
+ }
+
+#if 0
+ if ((tmp.irq != bp->irq) ||
+ (tmp.port != bp->base) ||
+ (tmp.type != PORT_CIRRUS) ||
+ (tmp.baud_base != (SX_OSCFREQ + CD186x_TPC/2) / CD186x_TPC) ||
+ (tmp.custom_divisor != 0) ||
+ (tmp.xmit_fifo_size != CD186x_NFIFO) ||
+ (tmp.flags & ~SPECIALIX_LEGAL_FLAGS)) {
+ func_exit();
+ return -EINVAL;
+ }
+#endif
+
+ change_speed = ((port->flags & ASYNC_SPD_MASK) !=
+ (tmp.flags & ASYNC_SPD_MASK));
+ change_speed |= (tmp.custom_divisor != port->custom_divisor);
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((tmp.close_delay != port->close_delay) ||
+ (tmp.closing_wait != port->closing_wait) ||
+ ((tmp.flags & ~ASYNC_USR_MASK) !=
+ (port->flags & ~ASYNC_USR_MASK))) {
+ func_exit();
+ return -EPERM;
+ }
+ port->flags = ((port->flags & ~ASYNC_USR_MASK) |
+ (tmp.flags & ASYNC_USR_MASK));
+ port->custom_divisor = tmp.custom_divisor;
+ } else {
+ port->flags = ((port->flags & ~ASYNC_FLAGS) |
+ (tmp.flags & ASYNC_FLAGS));
+ port->close_delay = tmp.close_delay;
+ port->closing_wait = tmp.closing_wait;
+ port->custom_divisor = tmp.custom_divisor;
+ }
+ if (change_speed) {
+ sx_change_speed(bp, port);
+ }
+ func_exit();
+ return 0;
+}
+
+
+static inline int sx_get_serial_info(struct specialix_port * port,
+ struct serial_struct __user *retinfo)
+{
+ struct serial_struct tmp;
+ struct specialix_board *bp = port_Board(port);
+ // int error;
+
+ func_enter();
+
+ /*
+ error = verify_area(VERIFY_WRITE, (void *) retinfo, sizeof(tmp));
+ if (error)
+ return error;
+ */
+
+ memset(&tmp, 0, sizeof(tmp));
+ tmp.type = PORT_CIRRUS;
+ tmp.line = port - sx_port;
+ tmp.port = bp->base;
+ tmp.irq = bp->irq;
+ tmp.flags = port->flags;
+ tmp.baud_base = (SX_OSCFREQ + CD186x_TPC/2) / CD186x_TPC;
+ tmp.close_delay = port->close_delay * HZ/100;
+ tmp.closing_wait = port->closing_wait * HZ/100;
+ tmp.custom_divisor = port->custom_divisor;
+ tmp.xmit_fifo_size = CD186x_NFIFO;
+ if (copy_to_user(retinfo, &tmp, sizeof(tmp))) {
+ func_exit();
+ return -EFAULT;
+ }
+
+ func_exit();
+ return 0;
+}
+
+
+static int sx_ioctl(struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ int retval;
+ void __user *argp = (void __user *)arg;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_ioctl")) {
+ func_exit();
+ return -ENODEV;
+ }
+
+ switch (cmd) {
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ retval = tty_check_change(tty);
+ if (retval) {
+ func_exit();
+ return retval;
+ }
+ tty_wait_until_sent(tty, 0);
+ if (!arg)
+ sx_send_break(port, HZ/4); /* 1/4 second */
+ return 0;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ retval = tty_check_change(tty);
+ if (retval) {
+ func_exit();
+ return retval;
+ }
+ tty_wait_until_sent(tty, 0);
+ sx_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+ func_exit();
+ return 0;
+ case TIOCGSOFTCAR:
+ if (put_user(C_CLOCAL(tty)?1:0, (unsigned long __user *)argp)) {
+ func_exit();
+ return -EFAULT;
+ }
+ func_exit();
+ return 0;
+ case TIOCSSOFTCAR:
+ if (get_user(arg, (unsigned long __user *) argp)) {
+ func_exit();
+ return -EFAULT;
+ }
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ func_exit();
+ return 0;
+ case TIOCGSERIAL:
+ func_exit();
+ return sx_get_serial_info(port, argp);
+ case TIOCSSERIAL:
+ func_exit();
+ return sx_set_serial_info(port, argp);
+ default:
+ func_exit();
+ return -ENOIOCTLCMD;
+ }
+ func_exit();
+ return 0;
+}
+
+
+static void sx_throttle(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_throttle")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+
+ /* Use DTR instead of RTS ! */
+ if (SX_CRTSCTS (tty))
+ port->MSVR &= ~MSVR_DTR;
+ else {
+ /* Auch!!! I think the system shouldn't call this then. */
+ /* Or maybe we're supposed (allowed?) to do our side of hw
+ handshake anyway, even when hardware handshake is off.
+ When you see this in your logs, please report.... */
+ printk (KERN_ERR "sx%d: Need to throttle, but can't (hardware hs is off)\n",
+ port_No (port));
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (I_IXOFF(tty)) {
+ spin_unlock_irqrestore(&bp->lock, flags);
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_SSCH2);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ sx_wait_CCR(bp);
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_MSVR, port->MSVR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+
+ func_exit();
+}
+
+
+static void sx_unthrottle(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_unthrottle")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+ /* XXXX Use DTR INSTEAD???? */
+ if (SX_CRTSCTS(tty)) {
+ port->MSVR |= MSVR_DTR;
+ } /* Else clause: see remark in "sx_throttle"... */
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ spin_unlock_irqrestore(&bp->lock, flags);
+ if (I_IXOFF(tty)) {
+ spin_unlock_irqrestore(&port->lock, flags);
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CCR, CCR_SSCH1);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ sx_wait_CCR(bp);
+ spin_lock_irqsave(&port->lock, flags);
+ }
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_MSVR, port->MSVR);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ func_exit();
+}
+
+
+static void sx_stop(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_stop")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+ port->IER &= ~IER_TXRDY;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ func_exit();
+}
+
+
+static void sx_start(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_start")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+
+ spin_lock_irqsave(&port->lock, flags);
+ if (port->xmit_cnt && port->xmit_buf && !(port->IER & IER_TXRDY)) {
+ port->IER |= IER_TXRDY;
+ spin_lock_irqsave(&bp->lock, flags);
+ sx_out(bp, CD186x_CAR, port_No(port));
+ sx_out(bp, CD186x_IER, port->IER);
+ spin_unlock_irqrestore(&bp->lock, flags);
+ }
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ func_exit();
+}
+
+
+/*
+ * This routine is called from the work-queue when the interrupt
+ * routine has signalled that a hangup has occurred. The path of
+ * hangup processing is:
+ *
+ * serial interrupt routine -> (workqueue) ->
+ * do_sx_hangup() -> tty->hangup() -> sx_hangup()
+ *
+ */
+static void do_sx_hangup(void *private_)
+{
+ struct specialix_port *port = (struct specialix_port *) private_;
+ struct tty_struct *tty;
+
+ func_enter();
+
+ tty = port->tty;
+ if (tty)
+ tty_hangup(tty); /* FIXME: module removal race here */
+
+ func_exit();
+}
+
+
+static void sx_hangup(struct tty_struct * tty)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ struct specialix_board *bp;
+ unsigned long flags;
+
+ func_enter();
+
+ if (sx_paranoia_check(port, tty->name, "sx_hangup")) {
+ func_exit();
+ return;
+ }
+
+ bp = port_Board(port);
+
+ sx_shutdown_port(bp, port);
+ spin_lock_irqsave(&port->lock, flags);
+ port->event = 0;
+ bp->count -= port->count;
+ if (bp->count < 0) {
+ printk(KERN_ERR "sx%d: sx_hangup: bad board count: %d port: %d\n",
+ board_No(bp), bp->count, tty->index);
+ bp->count = 0;
+ }
+ port->count = 0;
+ port->flags &= ~ASYNC_NORMAL_ACTIVE;
+ port->tty = NULL;
+ spin_unlock_irqrestore(&port->lock, flags);
+ wake_up_interruptible(&port->open_wait);
+
+ func_exit();
+}
+
+
+static void sx_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+ struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+ unsigned long flags;
+ struct specialix_board * bp;
+
+ if (sx_paranoia_check(port, tty->name, "sx_set_termios"))
+ return;
+
+ if (tty->termios->c_cflag == old_termios->c_cflag &&
+ tty->termios->c_iflag == old_termios->c_iflag)
+ return;
+
+ bp = port_Board(port);
+ spin_lock_irqsave(&port->lock, flags);
+ sx_change_speed(port_Board(port), port);
+ spin_unlock_irqrestore(&port->lock, flags);
+
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ sx_start(tty);
+ }
+}
+
+
+static void do_softint(void *private_)
+{
+ struct specialix_port *port = (struct specialix_port *) private_;
+ struct tty_struct *tty;
+
+ func_enter();
+
+ if(!(tty = port->tty)) {
+ func_exit();
+ return;
+ }
+
+ if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+ tty_wakeup(tty);
+ //wake_up_interruptible(&tty->write_wait);
+ }
+
+ func_exit();
+}
+
+static struct tty_operations sx_ops = {
+ .open = sx_open,
+ .close = sx_close,
+ .write = sx_write,
+ .put_char = sx_put_char,
+ .flush_chars = sx_flush_chars,
+ .write_room = sx_write_room,
+ .chars_in_buffer = sx_chars_in_buffer,
+ .flush_buffer = sx_flush_buffer,
+ .ioctl = sx_ioctl,
+ .throttle = sx_throttle,
+ .unthrottle = sx_unthrottle,
+ .set_termios = sx_set_termios,
+ .stop = sx_stop,
+ .start = sx_start,
+ .hangup = sx_hangup,
+ .tiocmget = sx_tiocmget,
+ .tiocmset = sx_tiocmset,
+};
+
+static int sx_init_drivers(void)
+{
+ int error;
+ int i;
+
+ func_enter();
+
+ specialix_driver = alloc_tty_driver(SX_NBOARD * SX_NPORT);
+ if (!specialix_driver) {
+ printk(KERN_ERR "sx: Couldn't allocate tty_driver.\n");
+ func_exit();
+ return 1;
+ }
+
+ if (!(tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL))) {
+ printk(KERN_ERR "sx: Couldn't get free page.\n");
+ put_tty_driver(specialix_driver);
+ func_exit();
+ return 1;
+ }
+ specialix_driver->owner = THIS_MODULE;
+ specialix_driver->name = "ttyW";
+ specialix_driver->major = SPECIALIX_NORMAL_MAJOR;
+ specialix_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ specialix_driver->subtype = SERIAL_TYPE_NORMAL;
+ specialix_driver->init_termios = tty_std_termios;
+ specialix_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ specialix_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(specialix_driver, &sx_ops);
+
+ if ((error = tty_register_driver(specialix_driver))) {
+ put_tty_driver(specialix_driver);
+ free_page((unsigned long)tmp_buf);
+ printk(KERN_ERR "sx: Couldn't register specialix IO8+ driver, error = %d\n",
+ error);
+ func_exit();
+ return 1;
+ }
+ memset(sx_port, 0, sizeof(sx_port));
+ for (i = 0; i < SX_NPORT * SX_NBOARD; i++) {
+ sx_port[i].magic = SPECIALIX_MAGIC;
+ INIT_WORK(&sx_port[i].tqueue, do_softint, &sx_port[i]);
+ INIT_WORK(&sx_port[i].tqueue_hangup, do_sx_hangup, &sx_port[i]);
+ sx_port[i].close_delay = 50 * HZ/100;
+ sx_port[i].closing_wait = 3000 * HZ/100;
+ init_waitqueue_head(&sx_port[i].open_wait);
+ init_waitqueue_head(&sx_port[i].close_wait);
+ spin_lock_init(&sx_port[i].lock);
+ }
+
+ func_exit();
+ return 0;
+}
+
+static void sx_release_drivers(void)
+{
+ func_enter();
+
+ free_page((unsigned long)tmp_buf);
+ tty_unregister_driver(specialix_driver);
+ put_tty_driver(specialix_driver);
+ func_exit();
+}
+
+/*
+ * This routine must be called by kernel at boot time
+ */
+static int __init specialix_init(void)
+{
+ int i;
+ int found = 0;
+
+ func_enter();
+
+ printk(KERN_INFO "sx: Specialix IO8+ driver v" VERSION ", (c) R.E.Wolff 1997/1998.\n");
+ printk(KERN_INFO "sx: derived from work (c) D.Gorodchanin 1994-1996.\n");
+#ifdef CONFIG_SPECIALIX_RTSCTS
+ printk (KERN_INFO "sx: DTR/RTS pin is always RTS.\n");
+#else
+ printk (KERN_INFO "sx: DTR/RTS pin is RTS when CRTSCTS is on.\n");
+#endif
+
+ for (i = 0; i < SX_NBOARD; i++)
+ sx_board[i].lock = SPIN_LOCK_UNLOCKED;
+
+ if (sx_init_drivers()) {
+ func_exit();
+ return -EIO;
+ }
+
+ for (i = 0; i < SX_NBOARD; i++)
+ if (sx_board[i].base && !sx_probe(&sx_board[i]))
+ found++;
+
+#ifdef CONFIG_PCI
+ {
+ struct pci_dev *pdev = NULL;
+
+ i=0;
+ while (i < SX_NBOARD) {
+ if (sx_board[i].flags & SX_BOARD_PRESENT) {
+ i++;
+ continue;
+ }
+ pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX,
+ PCI_DEVICE_ID_SPECIALIX_IO8,
+ pdev);
+ if (!pdev) break;
+
+ if (pci_enable_device(pdev))
+ continue;
+
+ sx_board[i].irq = pdev->irq;
+
+ sx_board[i].base = pci_resource_start (pdev, 2);
+
+ sx_board[i].flags |= SX_BOARD_IS_PCI;
+ if (!sx_probe(&sx_board[i]))
+ found ++;
+ }
+ }
+#endif
+
+ if (!found) {
+ sx_release_drivers();
+ printk(KERN_INFO "sx: No specialix IO8+ boards detected.\n");
+ func_exit();
+ return -EIO;
+ }
+
+ func_exit();
+ return 0;
+}
+
+static int iobase[SX_NBOARD] = {0,};
+
+static int irq [SX_NBOARD] = {0,};
+
+module_param_array(iobase, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param(sx_debug, int, 0);
+module_param(sx_rxfifo, int, 0);
+#ifdef SPECIALIX_TIMER
+module_param(sx_poll, int, 0);
+#endif
+
+/*
+ * You can setup up to 4 boards.
+ * by specifying "iobase=0xXXX,0xXXX ..." as insmod parameter.
+ * You should specify the IRQs too in that case "irq=....,...".
+ *
+ * More than 4 boards in one computer is not possible, as the card can
+ * only use 4 different interrupts.
+ *
+ */
+static int __init specialix_init_module(void)
+{
+ int i;
+
+ func_enter();
+
+ init_MUTEX(&tmp_buf_sem); /* Init de the semaphore - pvdl */
+
+ if (iobase[0] || iobase[1] || iobase[2] || iobase[3]) {
+ for(i = 0; i < SX_NBOARD; i++) {
+ sx_board[i].base = iobase[i];
+ sx_board[i].irq = irq[i];
+ sx_board[i].count= 0;
+ }
+ }
+
+ func_exit();
+
+ return specialix_init();
+}
+
+static void __exit specialix_exit_module(void)
+{
+ int i;
+
+ func_enter();
+
+ sx_release_drivers();
+ for (i = 0; i < SX_NBOARD; i++)
+ if (sx_board[i].flags & SX_BOARD_PRESENT)
+ sx_release_io_range(&sx_board[i]);
+#ifdef SPECIALIX_TIMER
+ del_timer (&missed_irq_timer);
+#endif
+
+ func_exit();
+}
+
+module_init(specialix_init_module);
+module_exit(specialix_exit_module);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/specialix_io8.h b/drivers/char/specialix_io8.h
new file mode 100644
index 000000000000..895bd90de363
--- /dev/null
+++ b/drivers/char/specialix_io8.h
@@ -0,0 +1,149 @@
+/*
+ * linux/drivers/char/specialix_io8.h --
+ * Specialix IO8+ multiport serial driver.
+ *
+ * Copyright (C) 1997 Roger Wolff (R.E.Wolff@BitWizard.nl)
+ * Copyright (C) 1994-1996 Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact io8-linux@specialix.co.uk if you require
+ * support.
+ *
+ * This driver was developped in the BitWizard linux device
+ * driver service. If you require a linux device driver for your
+ * product, please contact devices@BitWizard.nl for a quote.
+ *
+ * This code is firmly based on the riscom/8 serial driver,
+ * written by Dmitry Gorodchanin. The specialix IO8+ card
+ * programming information was obtained from the CL-CD1865 Data
+ * Book, and Specialix document number 6200059: IO8+ Hardware
+ * Functional Specification.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ * */
+
+#ifndef __LINUX_SPECIALIX_H
+#define __LINUX_SPECIALIX_H
+
+#include <linux/serial.h>
+
+#ifdef __KERNEL__
+
+/* You can have max 4 ISA cards in one PC, and I recommend not much
+more than a few PCI versions of the card. */
+
+#define SX_NBOARD 8
+
+/* NOTE: Specialix decoder recognizes 4 addresses, but only two are used.... */
+#define SX_IO_SPACE 4
+/* The PCI version decodes 8 addresses, but still only 2 are used. */
+#define SX_PCI_IO_SPACE 8
+
+/* eight ports per board. */
+#define SX_NPORT 8
+#define SX_BOARD(line) ((line) / SX_NPORT)
+#define SX_PORT(line) ((line) & (SX_NPORT - 1))
+
+
+#define SX_DATA_REG 0 /* Base+0 : Data register */
+#define SX_ADDR_REG 1 /* base+1 : Address register. */
+
+#define MHz *1000000 /* I'm ashamed of myself. */
+
+/* On-board oscillator frequency */
+#define SX_OSCFREQ (25 MHz/2)
+/* There is a 25MHz crystal on the board, but the chip is in /2 mode */
+
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define SPECIALIX_TPS 4000
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define SPECIALIX_RXFIFO 6 /* Max. receiver FIFO size (1-8) */
+
+#define SPECIALIX_MAGIC 0x0907
+
+#define SX_CCR_TIMEOUT 10000 /* CCR timeout. You may need to wait upto
+ 10 milliseconds before the internal
+ processor is available again after
+ you give it a command */
+
+#define SX_IOBASE1 0x100
+#define SX_IOBASE2 0x180
+#define SX_IOBASE3 0x250
+#define SX_IOBASE4 0x260
+
+struct specialix_board {
+ unsigned long flags;
+ unsigned short base;
+ unsigned char irq;
+ //signed char count;
+ int count;
+ unsigned char DTR;
+ int reg;
+ spinlock_t lock;
+};
+
+#define SX_BOARD_PRESENT 0x00000001
+#define SX_BOARD_ACTIVE 0x00000002
+#define SX_BOARD_IS_PCI 0x00000004
+
+
+struct specialix_port {
+ int magic;
+ int baud_base;
+ int flags;
+ struct tty_struct * tty;
+ int count;
+ int blocked_open;
+ ulong event;
+ int timeout;
+ int close_delay;
+ unsigned char * xmit_buf;
+ int custom_divisor;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+ struct work_struct tqueue;
+ struct work_struct tqueue_hangup;
+ short wakeup_chars;
+ short break_length;
+ unsigned short closing_wait;
+ unsigned char mark_mask;
+ unsigned char IER;
+ unsigned char MSVR;
+ unsigned char COR2;
+ unsigned long overrun;
+ unsigned long hits[10];
+ spinlock_t lock;
+};
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_SPECIALIX_H */
+
+
+
+
+
+
+
+
+
diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c
new file mode 100644
index 000000000000..de166608c59e
--- /dev/null
+++ b/drivers/char/stallion.c
@@ -0,0 +1,5197 @@
+/*****************************************************************************/
+
+/*
+ * stallion.c -- stallion multiport serial driver.
+ *
+ * Copyright (C) 1996-1999 Stallion Technologies
+ * Copyright (C) 1994-1996 Greg Ungerer.
+ *
+ * This code is loosely based on the Linux serial driver, written by
+ * Linus Torvalds, Theodore T'so and others.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/cd1400.h>
+#include <linux/sc26198.h>
+#include <linux/comstats.h>
+#include <linux/stallion.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define different board types. Use the standard Stallion "assigned"
+ * board numbers. Boards supported in this driver are abbreviated as
+ * EIO = EasyIO and ECH = EasyConnection 8/32.
+ */
+#define BRD_EASYIO 20
+#define BRD_ECH 21
+#define BRD_ECHMC 22
+#define BRD_ECHPCI 26
+#define BRD_ECH64PCI 27
+#define BRD_EASYIOPCI 28
+
+/*
+ * Define a configuration structure to hold the board configuration.
+ * Need to set this up in the code (for now) with the boards that are
+ * to be configured into the system. This is what needs to be modified
+ * when adding/removing/modifying boards. Each line entry in the
+ * stl_brdconf[] array is a board. Each line contains io/irq/memory
+ * ranges for that board (as well as what type of board it is).
+ * Some examples:
+ * { BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },
+ * This line would configure an EasyIO board (4 or 8, no difference),
+ * at io address 2a0 and irq 10.
+ * Another example:
+ * { BRD_ECH, 0x2a8, 0x280, 0, 12, 0 },
+ * This line will configure an EasyConnection 8/32 board at primary io
+ * address 2a8, secondary io address 280 and irq 12.
+ * Enter as many lines into this array as you want (only the first 4
+ * will actually be used!). Any combination of EasyIO and EasyConnection
+ * boards can be specified. EasyConnection 8/32 boards can share their
+ * secondary io addresses between each other.
+ *
+ * NOTE: there is no need to put any entries in this table for PCI
+ * boards. They will be found automatically by the driver - provided
+ * PCI BIOS32 support is compiled into the kernel.
+ */
+
+typedef struct {
+ int brdtype;
+ int ioaddr1;
+ int ioaddr2;
+ unsigned long memaddr;
+ int irq;
+ int irqtype;
+} stlconf_t;
+
+static stlconf_t stl_brdconf[] = {
+ /*{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },*/
+};
+
+static int stl_nrbrds = sizeof(stl_brdconf) / sizeof(stlconf_t);
+
+/*****************************************************************************/
+
+/*
+ * Define some important driver characteristics. Device major numbers
+ * allocated as per Linux Device Registry.
+ */
+#ifndef STL_SIOMEMMAJOR
+#define STL_SIOMEMMAJOR 28
+#endif
+#ifndef STL_SERIALMAJOR
+#define STL_SERIALMAJOR 24
+#endif
+#ifndef STL_CALLOUTMAJOR
+#define STL_CALLOUTMAJOR 25
+#endif
+
+/*
+ * Set the TX buffer size. Bigger is better, but we don't want
+ * to chew too much memory with buffers!
+ */
+#define STL_TXBUFLOW 512
+#define STL_TXBUFSIZE 4096
+
+/*****************************************************************************/
+
+/*
+ * Define our local driver identity first. Set up stuff to deal with
+ * all the local structures required by a serial tty driver.
+ */
+static char *stl_drvtitle = "Stallion Multiport Serial Driver";
+static char *stl_drvname = "stallion";
+static char *stl_drvversion = "5.6.0";
+
+static struct tty_driver *stl_serial;
+
+/*
+ * We will need to allocate a temporary write buffer for chars that
+ * come direct from user space. The problem is that a copy from user
+ * space might cause a page fault (typically on a system that is
+ * swapping!). All ports will share one buffer - since if the system
+ * is already swapping a shared buffer won't make things any worse.
+ */
+static char *stl_tmpwritebuf;
+static DECLARE_MUTEX(stl_tmpwritesem);
+
+/*
+ * Define a local default termios struct. All ports will be created
+ * with this termios initially. Basically all it defines is a raw port
+ * at 9600, 8 data bits, 1 stop bit.
+ */
+static struct termios stl_deftermios = {
+ .c_cflag = (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+ .c_cc = INIT_C_CC,
+};
+
+/*
+ * Define global stats structures. Not used often, and can be
+ * re-used for each stats call.
+ */
+static comstats_t stl_comstats;
+static combrd_t stl_brdstats;
+static stlbrd_t stl_dummybrd;
+static stlport_t stl_dummyport;
+
+/*
+ * Define global place to put buffer overflow characters.
+ */
+static char stl_unwanted[SC26198_RXFIFOSIZE];
+
+/*****************************************************************************/
+
+static stlbrd_t *stl_brds[STL_MAXBRDS];
+
+/*
+ * Per board state flags. Used with the state field of the board struct.
+ * Not really much here!
+ */
+#define BRD_FOUND 0x1
+
+/*
+ * Define the port structure istate flags. These set of flags are
+ * modified at interrupt time - so setting and reseting them needs
+ * to be atomic. Use the bit clear/setting routines for this.
+ */
+#define ASYI_TXBUSY 1
+#define ASYI_TXLOW 2
+#define ASYI_DCDCHANGE 3
+#define ASYI_TXFLOWED 4
+
+/*
+ * Define an array of board names as printable strings. Handy for
+ * referencing boards when printing trace and stuff.
+ */
+static char *stl_brdnames[] = {
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ "EasyIO",
+ "EC8/32-AT",
+ "EC8/32-MC",
+ (char *) NULL,
+ (char *) NULL,
+ (char *) NULL,
+ "EC8/32-PCI",
+ "EC8/64-PCI",
+ "EasyIO-PCI",
+};
+
+/*****************************************************************************/
+
+/*
+ * Define some string labels for arguments passed from the module
+ * load line. These allow for easy board definitions, and easy
+ * modification of the io, memory and irq resoucres.
+ */
+static int stl_nargs = 0;
+static char *board0[4];
+static char *board1[4];
+static char *board2[4];
+static char *board3[4];
+
+static char **stl_brdsp[] = {
+ (char **) &board0,
+ (char **) &board1,
+ (char **) &board2,
+ (char **) &board3
+};
+
+/*
+ * Define a set of common board names, and types. This is used to
+ * parse any module arguments.
+ */
+
+typedef struct stlbrdtype {
+ char *name;
+ int type;
+} stlbrdtype_t;
+
+static stlbrdtype_t stl_brdstr[] = {
+ { "easyio", BRD_EASYIO },
+ { "eio", BRD_EASYIO },
+ { "20", BRD_EASYIO },
+ { "ec8/32", BRD_ECH },
+ { "ec8/32-at", BRD_ECH },
+ { "ec8/32-isa", BRD_ECH },
+ { "ech", BRD_ECH },
+ { "echat", BRD_ECH },
+ { "21", BRD_ECH },
+ { "ec8/32-mc", BRD_ECHMC },
+ { "ec8/32-mca", BRD_ECHMC },
+ { "echmc", BRD_ECHMC },
+ { "echmca", BRD_ECHMC },
+ { "22", BRD_ECHMC },
+ { "ec8/32-pc", BRD_ECHPCI },
+ { "ec8/32-pci", BRD_ECHPCI },
+ { "26", BRD_ECHPCI },
+ { "ec8/64-pc", BRD_ECH64PCI },
+ { "ec8/64-pci", BRD_ECH64PCI },
+ { "ech-pci", BRD_ECH64PCI },
+ { "echpci", BRD_ECH64PCI },
+ { "echpc", BRD_ECH64PCI },
+ { "27", BRD_ECH64PCI },
+ { "easyio-pc", BRD_EASYIOPCI },
+ { "easyio-pci", BRD_EASYIOPCI },
+ { "eio-pci", BRD_EASYIOPCI },
+ { "eiopci", BRD_EASYIOPCI },
+ { "28", BRD_EASYIOPCI },
+};
+
+/*
+ * Define the module agruments.
+ */
+MODULE_AUTHOR("Greg Ungerer");
+MODULE_DESCRIPTION("Stallion Multiport Serial Driver");
+MODULE_LICENSE("GPL");
+
+module_param_array(board0, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board1, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board2, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board3, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,ioaddr2][,irq]]");
+
+/*****************************************************************************/
+
+/*
+ * Hardware ID bits for the EasyIO and ECH boards. These defines apply
+ * to the directly accessible io ports of these boards (not the uarts -
+ * they are in cd1400.h and sc26198.h).
+ */
+#define EIO_8PORTRS 0x04
+#define EIO_4PORTRS 0x05
+#define EIO_8PORTDI 0x00
+#define EIO_8PORTM 0x06
+#define EIO_MK3 0x03
+#define EIO_IDBITMASK 0x07
+
+#define EIO_BRDMASK 0xf0
+#define ID_BRD4 0x10
+#define ID_BRD8 0x20
+#define ID_BRD16 0x30
+
+#define EIO_INTRPEND 0x08
+#define EIO_INTEDGE 0x00
+#define EIO_INTLEVEL 0x08
+#define EIO_0WS 0x10
+
+#define ECH_ID 0xa0
+#define ECH_IDBITMASK 0xe0
+#define ECH_BRDENABLE 0x08
+#define ECH_BRDDISABLE 0x00
+#define ECH_INTENABLE 0x01
+#define ECH_INTDISABLE 0x00
+#define ECH_INTLEVEL 0x02
+#define ECH_INTEDGE 0x00
+#define ECH_INTRPEND 0x01
+#define ECH_BRDRESET 0x01
+
+#define ECHMC_INTENABLE 0x01
+#define ECHMC_BRDRESET 0x02
+
+#define ECH_PNLSTATUS 2
+#define ECH_PNL16PORT 0x20
+#define ECH_PNLIDMASK 0x07
+#define ECH_PNLXPID 0x40
+#define ECH_PNLINTRPEND 0x80
+
+#define ECH_ADDR2MASK 0x1e0
+
+/*
+ * Define the vector mapping bits for the programmable interrupt board
+ * hardware. These bits encode the interrupt for the board to use - it
+ * is software selectable (except the EIO-8M).
+ */
+static unsigned char stl_vecmap[] = {
+ 0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07,
+ 0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03
+};
+
+/*
+ * Set up enable and disable macros for the ECH boards. They require
+ * the secondary io address space to be activated and deactivated.
+ * This way all ECH boards can share their secondary io region.
+ * If this is an ECH-PCI board then also need to set the page pointer
+ * to point to the correct page.
+ */
+#define BRDENABLE(brdnr,pagenr) \
+ if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
+ outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE), \
+ stl_brds[(brdnr)]->ioctrl); \
+ else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI) \
+ outb((pagenr), stl_brds[(brdnr)]->ioctrl);
+
+#define BRDDISABLE(brdnr) \
+ if (stl_brds[(brdnr)]->brdtype == BRD_ECH) \
+ outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE), \
+ stl_brds[(brdnr)]->ioctrl);
+
+#define STL_CD1400MAXBAUD 230400
+#define STL_SC26198MAXBAUD 460800
+
+#define STL_BAUDBASE 115200
+#define STL_CLOSEDELAY (5 * HZ / 10)
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ * Define the Stallion PCI vendor and device IDs.
+ */
+#ifndef PCI_VENDOR_ID_STALLION
+#define PCI_VENDOR_ID_STALLION 0x124d
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI832
+#define PCI_DEVICE_ID_ECHPCI832 0x0000
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI864
+#define PCI_DEVICE_ID_ECHPCI864 0x0002
+#endif
+#ifndef PCI_DEVICE_ID_EIOPCI
+#define PCI_DEVICE_ID_EIOPCI 0x0003
+#endif
+
+/*
+ * Define structure to hold all Stallion PCI boards.
+ */
+typedef struct stlpcibrd {
+ unsigned short vendid;
+ unsigned short devid;
+ int brdtype;
+} stlpcibrd_t;
+
+static stlpcibrd_t stl_pcibrds[] = {
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI864, BRD_ECH64PCI },
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_EIOPCI, BRD_EASYIOPCI },
+ { PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI832, BRD_ECHPCI },
+ { PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87410, BRD_ECHPCI },
+};
+
+static int stl_nrpcibrds = sizeof(stl_pcibrds) / sizeof(stlpcibrd_t);
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Define macros to extract a brd/port number from a minor number.
+ */
+#define MINOR2BRD(min) (((min) & 0xc0) >> 6)
+#define MINOR2PORT(min) ((min) & 0x3f)
+
+/*
+ * Define a baud rate table that converts termios baud rate selector
+ * into the actual baud rate value. All baud rate calculations are
+ * based on the actual baud rate required.
+ */
+static unsigned int stl_baudrates[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+/*
+ * Define some handy local macros...
+ */
+#undef MIN
+#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
+
+#undef TOLOWER
+#define TOLOWER(x) ((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x))
+
+/*****************************************************************************/
+
+/*
+ * Declare all those functions in this driver!
+ */
+
+static void stl_argbrds(void);
+static int stl_parsebrd(stlconf_t *confp, char **argp);
+
+static unsigned long stl_atol(char *str);
+
+int stl_init(void);
+static int stl_open(struct tty_struct *tty, struct file *filp);
+static void stl_close(struct tty_struct *tty, struct file *filp);
+static int stl_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void stl_putchar(struct tty_struct *tty, unsigned char ch);
+static void stl_flushchars(struct tty_struct *tty);
+static int stl_writeroom(struct tty_struct *tty);
+static int stl_charsinbuffer(struct tty_struct *tty);
+static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void stl_settermios(struct tty_struct *tty, struct termios *old);
+static void stl_throttle(struct tty_struct *tty);
+static void stl_unthrottle(struct tty_struct *tty);
+static void stl_stop(struct tty_struct *tty);
+static void stl_start(struct tty_struct *tty);
+static void stl_flushbuffer(struct tty_struct *tty);
+static void stl_breakctl(struct tty_struct *tty, int state);
+static void stl_waituntilsent(struct tty_struct *tty, int timeout);
+static void stl_sendxchar(struct tty_struct *tty, char ch);
+static void stl_hangup(struct tty_struct *tty);
+static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static int stl_portinfo(stlport_t *portp, int portnr, char *pos);
+static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data);
+
+static int stl_brdinit(stlbrd_t *brdp);
+static int stl_initports(stlbrd_t *brdp, stlpanel_t *panelp);
+static int stl_getserial(stlport_t *portp, struct serial_struct __user *sp);
+static int stl_setserial(stlport_t *portp, struct serial_struct __user *sp);
+static int stl_getbrdstats(combrd_t __user *bp);
+static int stl_getportstats(stlport_t *portp, comstats_t __user *cp);
+static int stl_clrportstats(stlport_t *portp, comstats_t __user *cp);
+static int stl_getportstruct(stlport_t __user *arg);
+static int stl_getbrdstruct(stlbrd_t __user *arg);
+static int stl_waitcarrier(stlport_t *portp, struct file *filp);
+static int stl_eiointr(stlbrd_t *brdp);
+static int stl_echatintr(stlbrd_t *brdp);
+static int stl_echmcaintr(stlbrd_t *brdp);
+static int stl_echpciintr(stlbrd_t *brdp);
+static int stl_echpci64intr(stlbrd_t *brdp);
+static void stl_offintr(void *private);
+static void *stl_memalloc(int len);
+static stlbrd_t *stl_allocbrd(void);
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr);
+
+static inline int stl_initbrds(void);
+static inline int stl_initeio(stlbrd_t *brdp);
+static inline int stl_initech(stlbrd_t *brdp);
+static inline int stl_getbrdnr(void);
+
+#ifdef CONFIG_PCI
+static inline int stl_findpcibrds(void);
+static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp);
+#endif
+
+/*
+ * CD1400 uart specific handling functions.
+ */
+static void stl_cd1400setreg(stlport_t *portp, int regnr, int value);
+static int stl_cd1400getreg(stlport_t *portp, int regnr);
+static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value);
+static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp);
+static int stl_cd1400getsignals(stlport_t *portp);
+static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts);
+static void stl_cd1400ccrwait(stlport_t *portp);
+static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx);
+static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx);
+static void stl_cd1400disableintrs(stlport_t *portp);
+static void stl_cd1400sendbreak(stlport_t *portp, int len);
+static void stl_cd1400flowctrl(stlport_t *portp, int state);
+static void stl_cd1400sendflow(stlport_t *portp, int state);
+static void stl_cd1400flush(stlport_t *portp);
+static int stl_cd1400datastate(stlport_t *portp);
+static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr);
+static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr);
+static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr);
+
+static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr);
+
+/*
+ * SC26198 uart specific handling functions.
+ */
+static void stl_sc26198setreg(stlport_t *portp, int regnr, int value);
+static int stl_sc26198getreg(stlport_t *portp, int regnr);
+static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value);
+static int stl_sc26198getglobreg(stlport_t *portp, int regnr);
+static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp);
+static int stl_sc26198getsignals(stlport_t *portp);
+static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts);
+static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx);
+static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx);
+static void stl_sc26198disableintrs(stlport_t *portp);
+static void stl_sc26198sendbreak(stlport_t *portp, int len);
+static void stl_sc26198flowctrl(stlport_t *portp, int state);
+static void stl_sc26198sendflow(stlport_t *portp, int state);
+static void stl_sc26198flush(stlport_t *portp);
+static int stl_sc26198datastate(stlport_t *portp);
+static void stl_sc26198wait(stlport_t *portp);
+static void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty);
+static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase);
+static void stl_sc26198txisr(stlport_t *port);
+static void stl_sc26198rxisr(stlport_t *port, unsigned int iack);
+static void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch);
+static void stl_sc26198rxbadchars(stlport_t *portp);
+static void stl_sc26198otherisr(stlport_t *port, unsigned int iack);
+
+/*****************************************************************************/
+
+/*
+ * Generic UART support structure.
+ */
+typedef struct uart {
+ int (*panelinit)(stlbrd_t *brdp, stlpanel_t *panelp);
+ void (*portinit)(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+ void (*setport)(stlport_t *portp, struct termios *tiosp);
+ int (*getsignals)(stlport_t *portp);
+ void (*setsignals)(stlport_t *portp, int dtr, int rts);
+ void (*enablerxtx)(stlport_t *portp, int rx, int tx);
+ void (*startrxtx)(stlport_t *portp, int rx, int tx);
+ void (*disableintrs)(stlport_t *portp);
+ void (*sendbreak)(stlport_t *portp, int len);
+ void (*flowctrl)(stlport_t *portp, int state);
+ void (*sendflow)(stlport_t *portp, int state);
+ void (*flush)(stlport_t *portp);
+ int (*datastate)(stlport_t *portp);
+ void (*intr)(stlpanel_t *panelp, unsigned int iobase);
+} uart_t;
+
+/*
+ * Define some macros to make calling these functions nice and clean.
+ */
+#define stl_panelinit (* ((uart_t *) panelp->uartp)->panelinit)
+#define stl_portinit (* ((uart_t *) portp->uartp)->portinit)
+#define stl_setport (* ((uart_t *) portp->uartp)->setport)
+#define stl_getsignals (* ((uart_t *) portp->uartp)->getsignals)
+#define stl_setsignals (* ((uart_t *) portp->uartp)->setsignals)
+#define stl_enablerxtx (* ((uart_t *) portp->uartp)->enablerxtx)
+#define stl_startrxtx (* ((uart_t *) portp->uartp)->startrxtx)
+#define stl_disableintrs (* ((uart_t *) portp->uartp)->disableintrs)
+#define stl_sendbreak (* ((uart_t *) portp->uartp)->sendbreak)
+#define stl_flowctrl (* ((uart_t *) portp->uartp)->flowctrl)
+#define stl_sendflow (* ((uart_t *) portp->uartp)->sendflow)
+#define stl_flush (* ((uart_t *) portp->uartp)->flush)
+#define stl_datastate (* ((uart_t *) portp->uartp)->datastate)
+
+/*****************************************************************************/
+
+/*
+ * CD1400 UART specific data initialization.
+ */
+static uart_t stl_cd1400uart = {
+ stl_cd1400panelinit,
+ stl_cd1400portinit,
+ stl_cd1400setport,
+ stl_cd1400getsignals,
+ stl_cd1400setsignals,
+ stl_cd1400enablerxtx,
+ stl_cd1400startrxtx,
+ stl_cd1400disableintrs,
+ stl_cd1400sendbreak,
+ stl_cd1400flowctrl,
+ stl_cd1400sendflow,
+ stl_cd1400flush,
+ stl_cd1400datastate,
+ stl_cd1400eiointr
+};
+
+/*
+ * Define the offsets within the register bank of a cd1400 based panel.
+ * These io address offsets are common to the EasyIO board as well.
+ */
+#define EREG_ADDR 0
+#define EREG_DATA 4
+#define EREG_RXACK 5
+#define EREG_TXACK 6
+#define EREG_MDACK 7
+
+#define EREG_BANKSIZE 8
+
+#define CD1400_CLK 25000000
+#define CD1400_CLK8M 20000000
+
+/*
+ * Define the cd1400 baud rate clocks. These are used when calculating
+ * what clock and divisor to use for the required baud rate. Also
+ * define the maximum baud rate allowed, and the default base baud.
+ */
+static int stl_cd1400clkdivs[] = {
+ CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4
+};
+
+/*****************************************************************************/
+
+/*
+ * SC26198 UART specific data initization.
+ */
+static uart_t stl_sc26198uart = {
+ stl_sc26198panelinit,
+ stl_sc26198portinit,
+ stl_sc26198setport,
+ stl_sc26198getsignals,
+ stl_sc26198setsignals,
+ stl_sc26198enablerxtx,
+ stl_sc26198startrxtx,
+ stl_sc26198disableintrs,
+ stl_sc26198sendbreak,
+ stl_sc26198flowctrl,
+ stl_sc26198sendflow,
+ stl_sc26198flush,
+ stl_sc26198datastate,
+ stl_sc26198intr
+};
+
+/*
+ * Define the offsets within the register bank of a sc26198 based panel.
+ */
+#define XP_DATA 0
+#define XP_ADDR 1
+#define XP_MODID 2
+#define XP_STATUS 2
+#define XP_IACK 3
+
+#define XP_BANKSIZE 4
+
+/*
+ * Define the sc26198 baud rate table. Offsets within the table
+ * represent the actual baud rate selector of sc26198 registers.
+ */
+static unsigned int sc26198_baudtable[] = {
+ 50, 75, 150, 200, 300, 450, 600, 900, 1200, 1800, 2400, 3600,
+ 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200,
+ 230400, 460800, 921600
+};
+
+#define SC26198_NRBAUDS (sizeof(sc26198_baudtable) / sizeof(unsigned int))
+
+/*****************************************************************************/
+
+/*
+ * Define the driver info for a user level control device. Used mainly
+ * to get at port stats - only not using the port device itself.
+ */
+static struct file_operations stl_fsiomem = {
+ .owner = THIS_MODULE,
+ .ioctl = stl_memioctl,
+};
+
+/*****************************************************************************/
+
+static struct class_simple *stallion_class;
+
+/*
+ * Loadable module initialization stuff.
+ */
+
+static int __init stallion_module_init(void)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("init_module()\n");
+#endif
+
+ save_flags(flags);
+ cli();
+ stl_init();
+ restore_flags(flags);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+static void __exit stallion_module_exit(void)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ stlport_t *portp;
+ unsigned long flags;
+ int i, j, k;
+
+#ifdef DEBUG
+ printk("cleanup_module()\n");
+#endif
+
+ printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle,
+ stl_drvversion);
+
+ save_flags(flags);
+ cli();
+
+/*
+ * Free up all allocated resources used by the ports. This includes
+ * memory and interrupts. As part of this process we will also do
+ * a hangup on every open port - to try to flush out any processes
+ * hanging onto ports.
+ */
+ i = tty_unregister_driver(stl_serial);
+ put_tty_driver(stl_serial);
+ if (i) {
+ printk("STALLION: failed to un-register tty driver, "
+ "errno=%d\n", -i);
+ restore_flags(flags);
+ return;
+ }
+ for (i = 0; i < 4; i++) {
+ devfs_remove("staliomem/%d", i);
+ class_simple_device_remove(MKDEV(STL_SIOMEMMAJOR, i));
+ }
+ devfs_remove("staliomem");
+ if ((i = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem")))
+ printk("STALLION: failed to un-register serial memory device, "
+ "errno=%d\n", -i);
+ class_simple_destroy(stallion_class);
+
+ if (stl_tmpwritebuf != (char *) NULL)
+ kfree(stl_tmpwritebuf);
+
+ for (i = 0; (i < stl_nrbrds); i++) {
+ if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL)
+ continue;
+
+ free_irq(brdp->irq, brdp);
+
+ for (j = 0; (j < STL_MAXPANELS); j++) {
+ panelp = brdp->panels[j];
+ if (panelp == (stlpanel_t *) NULL)
+ continue;
+ for (k = 0; (k < STL_PORTSPERPANEL); k++) {
+ portp = panelp->ports[k];
+ if (portp == (stlport_t *) NULL)
+ continue;
+ if (portp->tty != (struct tty_struct *) NULL)
+ stl_hangup(portp->tty);
+ if (portp->tx.buf != (char *) NULL)
+ kfree(portp->tx.buf);
+ kfree(portp);
+ }
+ kfree(panelp);
+ }
+
+ release_region(brdp->ioaddr1, brdp->iosize1);
+ if (brdp->iosize2 > 0)
+ release_region(brdp->ioaddr2, brdp->iosize2);
+
+ kfree(brdp);
+ stl_brds[i] = (stlbrd_t *) NULL;
+ }
+
+ restore_flags(flags);
+}
+
+module_init(stallion_module_init);
+module_exit(stallion_module_exit);
+
+/*****************************************************************************/
+
+/*
+ * Check for any arguments passed in on the module load command line.
+ */
+
+static void stl_argbrds(void)
+{
+ stlconf_t conf;
+ stlbrd_t *brdp;
+ int i;
+
+#ifdef DEBUG
+ printk("stl_argbrds()\n");
+#endif
+
+ for (i = stl_nrbrds; (i < stl_nargs); i++) {
+ memset(&conf, 0, sizeof(conf));
+ if (stl_parsebrd(&conf, stl_brdsp[i]) == 0)
+ continue;
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ continue;
+ stl_nrbrds = i + 1;
+ brdp->brdnr = i;
+ brdp->brdtype = conf.brdtype;
+ brdp->ioaddr1 = conf.ioaddr1;
+ brdp->ioaddr2 = conf.ioaddr2;
+ brdp->irq = conf.irq;
+ brdp->irqtype = conf.irqtype;
+ stl_brdinit(brdp);
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Convert an ascii string number into an unsigned long.
+ */
+
+static unsigned long stl_atol(char *str)
+{
+ unsigned long val;
+ int base, c;
+ char *sp;
+
+ val = 0;
+ sp = str;
+ if ((*sp == '0') && (*(sp+1) == 'x')) {
+ base = 16;
+ sp += 2;
+ } else if (*sp == '0') {
+ base = 8;
+ sp++;
+ } else {
+ base = 10;
+ }
+
+ for (; (*sp != 0); sp++) {
+ c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0');
+ if ((c < 0) || (c >= base)) {
+ printk("STALLION: invalid argument %s\n", str);
+ val = 0;
+ break;
+ }
+ val = (val * base) + c;
+ }
+ return(val);
+}
+
+/*****************************************************************************/
+
+/*
+ * Parse the supplied argument string, into the board conf struct.
+ */
+
+static int stl_parsebrd(stlconf_t *confp, char **argp)
+{
+ char *sp;
+ int nrbrdnames, i;
+
+#ifdef DEBUG
+ printk("stl_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp);
+#endif
+
+ if ((argp[0] == (char *) NULL) || (*argp[0] == 0))
+ return(0);
+
+ for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++)
+ *sp = TOLOWER(*sp);
+
+ nrbrdnames = sizeof(stl_brdstr) / sizeof(stlbrdtype_t);
+ for (i = 0; (i < nrbrdnames); i++) {
+ if (strcmp(stl_brdstr[i].name, argp[0]) == 0)
+ break;
+ }
+ if (i >= nrbrdnames) {
+ printk("STALLION: unknown board name, %s?\n", argp[0]);
+ return(0);
+ }
+
+ confp->brdtype = stl_brdstr[i].type;
+
+ i = 1;
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->ioaddr1 = stl_atol(argp[i]);
+ i++;
+ if (confp->brdtype == BRD_ECH) {
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->ioaddr2 = stl_atol(argp[i]);
+ i++;
+ }
+ if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+ confp->irq = stl_atol(argp[i]);
+ return(1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Local driver kernel memory allocation routine.
+ */
+
+static void *stl_memalloc(int len)
+{
+ return((void *) kmalloc(len, GFP_KERNEL));
+}
+
+/*****************************************************************************/
+
+/*
+ * Allocate a new board structure. Fill out the basic info in it.
+ */
+
+static stlbrd_t *stl_allocbrd(void)
+{
+ stlbrd_t *brdp;
+
+ brdp = (stlbrd_t *) stl_memalloc(sizeof(stlbrd_t));
+ if (brdp == (stlbrd_t *) NULL) {
+ printk("STALLION: failed to allocate memory (size=%d)\n",
+ sizeof(stlbrd_t));
+ return((stlbrd_t *) NULL);
+ }
+
+ memset(brdp, 0, sizeof(stlbrd_t));
+ brdp->magic = STL_BOARDMAGIC;
+ return(brdp);
+}
+
+/*****************************************************************************/
+
+static int stl_open(struct tty_struct *tty, struct file *filp)
+{
+ stlport_t *portp;
+ stlbrd_t *brdp;
+ unsigned int minordev;
+ int brdnr, panelnr, portnr, rc;
+
+#ifdef DEBUG
+ printk("stl_open(tty=%x,filp=%x): device=%s\n", (int) tty,
+ (int) filp, tty->name);
+#endif
+
+ minordev = tty->index;
+ brdnr = MINOR2BRD(minordev);
+ if (brdnr >= stl_nrbrds)
+ return(-ENODEV);
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return(-ENODEV);
+ minordev = MINOR2PORT(minordev);
+ for (portnr = -1, panelnr = 0; (panelnr < STL_MAXPANELS); panelnr++) {
+ if (brdp->panels[panelnr] == (stlpanel_t *) NULL)
+ break;
+ if (minordev < brdp->panels[panelnr]->nrports) {
+ portnr = minordev;
+ break;
+ }
+ minordev -= brdp->panels[panelnr]->nrports;
+ }
+ if (portnr < 0)
+ return(-ENODEV);
+
+ portp = brdp->panels[panelnr]->ports[portnr];
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+
+/*
+ * On the first open of the device setup the port hardware, and
+ * initialize the per port data structure.
+ */
+ portp->tty = tty;
+ tty->driver_data = portp;
+ portp->refcount++;
+
+ if ((portp->flags & ASYNC_INITIALIZED) == 0) {
+ if (portp->tx.buf == (char *) NULL) {
+ portp->tx.buf = (char *) stl_memalloc(STL_TXBUFSIZE);
+ if (portp->tx.buf == (char *) NULL)
+ return(-ENOMEM);
+ portp->tx.head = portp->tx.buf;
+ portp->tx.tail = portp->tx.buf;
+ }
+ stl_setport(portp, tty->termios);
+ portp->sigs = stl_getsignals(portp);
+ stl_setsignals(portp, 1, 1);
+ stl_enablerxtx(portp, 1, 1);
+ stl_startrxtx(portp, 1, 0);
+ clear_bit(TTY_IO_ERROR, &tty->flags);
+ portp->flags |= ASYNC_INITIALIZED;
+ }
+
+/*
+ * Check if this port is in the middle of closing. If so then wait
+ * until it is closed then return error status, based on flag settings.
+ * The sleep here does not need interrupt protection since the wakeup
+ * for it is done with the same context.
+ */
+ if (portp->flags & ASYNC_CLOSING) {
+ interruptible_sleep_on(&portp->close_wait);
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ return(-EAGAIN);
+ return(-ERESTARTSYS);
+ }
+
+/*
+ * Based on type of open being done check if it can overlap with any
+ * previous opens still in effect. If we are a normal serial device
+ * then also we might have to wait for carrier.
+ */
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ if ((rc = stl_waitcarrier(portp, filp)) != 0)
+ return(rc);
+ }
+ portp->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Possibly need to wait for carrier (DCD signal) to come high. Say
+ * maybe because if we are clocal then we don't need to wait...
+ */
+
+static int stl_waitcarrier(stlport_t *portp, struct file *filp)
+{
+ unsigned long flags;
+ int rc, doclocal;
+
+#ifdef DEBUG
+ printk("stl_waitcarrier(portp=%x,filp=%x)\n", (int) portp, (int) filp);
+#endif
+
+ rc = 0;
+ doclocal = 0;
+
+ if (portp->tty->termios->c_cflag & CLOCAL)
+ doclocal++;
+
+ save_flags(flags);
+ cli();
+ portp->openwaitcnt++;
+ if (! tty_hung_up_p(filp))
+ portp->refcount--;
+
+ for (;;) {
+ stl_setsignals(portp, 1, 1);
+ if (tty_hung_up_p(filp) ||
+ ((portp->flags & ASYNC_INITIALIZED) == 0)) {
+ if (portp->flags & ASYNC_HUP_NOTIFY)
+ rc = -EBUSY;
+ else
+ rc = -ERESTARTSYS;
+ break;
+ }
+ if (((portp->flags & ASYNC_CLOSING) == 0) &&
+ (doclocal || (portp->sigs & TIOCM_CD))) {
+ break;
+ }
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+ interruptible_sleep_on(&portp->open_wait);
+ }
+
+ if (! tty_hung_up_p(filp))
+ portp->refcount++;
+ portp->openwaitcnt--;
+ restore_flags(flags);
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_close(struct tty_struct *tty, struct file *filp)
+{
+ stlport_t *portp;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_close(tty=%x,filp=%x)\n", (int) tty, (int) filp);
+#endif
+
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ if (tty_hung_up_p(filp)) {
+ restore_flags(flags);
+ return;
+ }
+ if ((tty->count == 1) && (portp->refcount != 1))
+ portp->refcount = 1;
+ if (portp->refcount-- > 1) {
+ restore_flags(flags);
+ return;
+ }
+
+ portp->refcount = 0;
+ portp->flags |= ASYNC_CLOSING;
+
+/*
+ * May want to wait for any data to drain before closing. The BUSY
+ * flag keeps track of whether we are still sending or not - it is
+ * very accurate for the cd1400, not quite so for the sc26198.
+ * (The sc26198 has no "end-of-data" interrupt only empty FIFO)
+ */
+ tty->closing = 1;
+ if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, portp->closing_wait);
+ stl_waituntilsent(tty, (HZ / 2));
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+ stl_disableintrs(portp);
+ if (tty->termios->c_cflag & HUPCL)
+ stl_setsignals(portp, 0, 0);
+ stl_enablerxtx(portp, 0, 0);
+ stl_flushbuffer(tty);
+ portp->istate = 0;
+ if (portp->tx.buf != (char *) NULL) {
+ kfree(portp->tx.buf);
+ portp->tx.buf = (char *) NULL;
+ portp->tx.head = (char *) NULL;
+ portp->tx.tail = (char *) NULL;
+ }
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ tty_ldisc_flush(tty);
+
+ tty->closing = 0;
+ portp->tty = (struct tty_struct *) NULL;
+
+ if (portp->openwaitcnt) {
+ if (portp->close_delay)
+ msleep_interruptible(jiffies_to_msecs(portp->close_delay));
+ wake_up_interruptible(&portp->open_wait);
+ }
+
+ portp->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+ wake_up_interruptible(&portp->close_wait);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Write routine. Take data and stuff it in to the TX ring queue.
+ * If transmit interrupts are not running then start them.
+ */
+
+static int stl_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ stlport_t *portp;
+ unsigned int len, stlen;
+ unsigned char *chbuf;
+ char *head, *tail;
+
+#ifdef DEBUG
+ printk("stl_write(tty=%x,buf=%x,count=%d)\n",
+ (int) tty, (int) buf, count);
+#endif
+
+ if ((tty == (struct tty_struct *) NULL) ||
+ (stl_tmpwritebuf == (char *) NULL))
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+/*
+ * If copying direct from user space we must cater for page faults,
+ * causing us to "sleep" here for a while. To handle this copy in all
+ * the data we need now, into a local buffer. Then when we got it all
+ * copy it into the TX buffer.
+ */
+ chbuf = (unsigned char *) buf;
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ if (head >= tail) {
+ len = STL_TXBUFSIZE - (head - tail) - 1;
+ stlen = STL_TXBUFSIZE - (head - portp->tx.buf);
+ } else {
+ len = tail - head - 1;
+ stlen = len;
+ }
+
+ len = MIN(len, count);
+ count = 0;
+ while (len > 0) {
+ stlen = MIN(len, stlen);
+ memcpy(head, chbuf, stlen);
+ len -= stlen;
+ chbuf += stlen;
+ count += stlen;
+ head += stlen;
+ if (head >= (portp->tx.buf + STL_TXBUFSIZE)) {
+ head = portp->tx.buf;
+ stlen = tail - head;
+ }
+ }
+ portp->tx.head = head;
+
+ clear_bit(ASYI_TXLOW, &portp->istate);
+ stl_startrxtx(portp, -1, 1);
+
+ return(count);
+}
+
+/*****************************************************************************/
+
+static void stl_putchar(struct tty_struct *tty, unsigned char ch)
+{
+ stlport_t *portp;
+ unsigned int len;
+ char *head, *tail;
+
+#ifdef DEBUG
+ printk("stl_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ if (portp->tx.buf == (char *) NULL)
+ return;
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+
+ len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail)) : (tail - head);
+ len--;
+
+ if (len > 0) {
+ *head++ = ch;
+ if (head >= (portp->tx.buf + STL_TXBUFSIZE))
+ head = portp->tx.buf;
+ }
+ portp->tx.head = head;
+}
+
+/*****************************************************************************/
+
+/*
+ * If there are any characters in the buffer then make sure that TX
+ * interrupts are on and get'em out. Normally used after the putchar
+ * routine has been called.
+ */
+
+static void stl_flushchars(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_flushchars(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ if (portp->tx.buf == (char *) NULL)
+ return;
+
+#if 0
+ if (tty->stopped || tty->hw_stopped ||
+ (portp->tx.head == portp->tx.tail))
+ return;
+#endif
+ stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+static int stl_writeroom(struct tty_struct *tty)
+{
+ stlport_t *portp;
+ char *head, *tail;
+
+#ifdef DEBUG
+ printk("stl_writeroom(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ return((head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) : (tail - head - 1));
+}
+
+/*****************************************************************************/
+
+/*
+ * Return number of chars in the TX buffer. Normally we would just
+ * calculate the number of chars in the buffer and return that, but if
+ * the buffer is empty and TX interrupts are still on then we return
+ * that the buffer still has 1 char in it. This way whoever called us
+ * will not think that ALL chars have drained - since the UART still
+ * must have some chars in it (we are busy after all).
+ */
+
+static int stl_charsinbuffer(struct tty_struct *tty)
+{
+ stlport_t *portp;
+ unsigned int size;
+ char *head, *tail;
+
+#ifdef DEBUG
+ printk("stl_charsinbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(0);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (portp->tx.buf == (char *) NULL)
+ return(0);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ size = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((size == 0) && test_bit(ASYI_TXBUSY, &portp->istate))
+ size = 1;
+ return(size);
+}
+
+/*****************************************************************************/
+
+/*
+ * Generate the serial struct info.
+ */
+
+static int stl_getserial(stlport_t *portp, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+ stlbrd_t *brdp;
+
+#ifdef DEBUG
+ printk("stl_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+ memset(&sio, 0, sizeof(struct serial_struct));
+ sio.line = portp->portnr;
+ sio.port = portp->ioaddr;
+ sio.flags = portp->flags;
+ sio.baud_base = portp->baud_base;
+ sio.close_delay = portp->close_delay;
+ sio.closing_wait = portp->closing_wait;
+ sio.custom_divisor = portp->custom_divisor;
+ sio.hub6 = 0;
+ if (portp->uartp == &stl_cd1400uart) {
+ sio.type = PORT_CIRRUS;
+ sio.xmit_fifo_size = CD1400_TXFIFOSIZE;
+ } else {
+ sio.type = PORT_UNKNOWN;
+ sio.xmit_fifo_size = SC26198_TXFIFOSIZE;
+ }
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp != (stlbrd_t *) NULL)
+ sio.irq = brdp->irq;
+
+ return copy_to_user(sp, &sio, sizeof(struct serial_struct)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Set port according to the serial struct info.
+ * At this point we do not do any auto-configure stuff, so we will
+ * just quietly ignore any requests to change irq, etc.
+ */
+
+static int stl_setserial(stlport_t *portp, struct serial_struct __user *sp)
+{
+ struct serial_struct sio;
+
+#ifdef DEBUG
+ printk("stl_setserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+ if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+ return -EFAULT;
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((sio.baud_base != portp->baud_base) ||
+ (sio.close_delay != portp->close_delay) ||
+ ((sio.flags & ~ASYNC_USR_MASK) !=
+ (portp->flags & ~ASYNC_USR_MASK)))
+ return(-EPERM);
+ }
+
+ portp->flags = (portp->flags & ~ASYNC_USR_MASK) |
+ (sio.flags & ASYNC_USR_MASK);
+ portp->baud_base = sio.baud_base;
+ portp->close_delay = sio.close_delay;
+ portp->closing_wait = sio.closing_wait;
+ portp->custom_divisor = sio.custom_divisor;
+ stl_setport(portp, portp->tty->termios);
+ return(0);
+}
+
+/*****************************************************************************/
+
+static int stl_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ stlport_t *portp;
+
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+
+ return stl_getsignals(portp);
+}
+
+static int stl_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ stlport_t *portp;
+ int rts = -1, dtr = -1;
+
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+
+ if (set & TIOCM_RTS)
+ rts = 1;
+ if (set & TIOCM_DTR)
+ dtr = 1;
+ if (clear & TIOCM_RTS)
+ rts = 0;
+ if (clear & TIOCM_DTR)
+ dtr = 0;
+
+ stl_setsignals(portp, dtr, rts);
+ return 0;
+}
+
+static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ stlport_t *portp;
+ unsigned int ival;
+ int rc;
+ void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+ printk("stl_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n",
+ (int) tty, (int) file, cmd, (int) arg);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return(-ENODEV);
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return(-EIO);
+ }
+
+ rc = 0;
+
+ switch (cmd) {
+ case TIOCGSOFTCAR:
+ rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+ (unsigned __user *) argp);
+ break;
+ case TIOCSSOFTCAR:
+ if (get_user(ival, (unsigned int __user *) arg))
+ return -EFAULT;
+ tty->termios->c_cflag =
+ (tty->termios->c_cflag & ~CLOCAL) |
+ (ival ? CLOCAL : 0);
+ break;
+ case TIOCGSERIAL:
+ rc = stl_getserial(portp, argp);
+ break;
+ case TIOCSSERIAL:
+ rc = stl_setserial(portp, argp);
+ break;
+ case COM_GETPORTSTATS:
+ rc = stl_getportstats(portp, argp);
+ break;
+ case COM_CLRPORTSTATS:
+ rc = stl_clrportstats(portp, argp);
+ break;
+ case TIOCSERCONFIG:
+ case TIOCSERGWILD:
+ case TIOCSERSWILD:
+ case TIOCSERGETLSR:
+ case TIOCSERGSTRUCT:
+ case TIOCSERGETMULTI:
+ case TIOCSERSETMULTI:
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_settermios(struct tty_struct *tty, struct termios *old)
+{
+ stlport_t *portp;
+ struct termios *tiosp;
+
+#ifdef DEBUG
+ printk("stl_settermios(tty=%x,old=%x)\n", (int) tty, (int) old);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ tiosp = tty->termios;
+ if ((tiosp->c_cflag == old->c_cflag) &&
+ (tiosp->c_iflag == old->c_iflag))
+ return;
+
+ stl_setport(portp, tiosp);
+ stl_setsignals(portp, ((tiosp->c_cflag & (CBAUD & ~CBAUDEX)) ? 1 : 0),
+ -1);
+ if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0)) {
+ tty->hw_stopped = 0;
+ stl_start(tty);
+ }
+ if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL))
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ * Attempt to flow control who ever is sending us data. Based on termios
+ * settings use software or/and hardware flow control.
+ */
+
+static void stl_throttle(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_throttle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_flowctrl(portp, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Unflow control the device sending us data...
+ */
+
+static void stl_unthrottle(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_unthrottle(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_flowctrl(portp, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Stop the transmitter. Basically to do this we will just turn TX
+ * interrupts off.
+ */
+
+static void stl_stop(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_stop(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_startrxtx(portp, -1, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start the transmitter again. Just turn TX interrupts back on.
+ */
+
+static void stl_start(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_start(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+ stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Hangup this port. This is pretty much like closing the port, only
+ * a little more brutal. No waiting for data to drain. Shutdown the
+ * port and maybe drop signals.
+ */
+
+static void stl_hangup(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_hangup(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ portp->flags &= ~ASYNC_INITIALIZED;
+ stl_disableintrs(portp);
+ if (tty->termios->c_cflag & HUPCL)
+ stl_setsignals(portp, 0, 0);
+ stl_enablerxtx(portp, 0, 0);
+ stl_flushbuffer(tty);
+ portp->istate = 0;
+ set_bit(TTY_IO_ERROR, &tty->flags);
+ if (portp->tx.buf != (char *) NULL) {
+ kfree(portp->tx.buf);
+ portp->tx.buf = (char *) NULL;
+ portp->tx.head = (char *) NULL;
+ portp->tx.tail = (char *) NULL;
+ }
+ portp->tty = (struct tty_struct *) NULL;
+ portp->flags &= ~ASYNC_NORMAL_ACTIVE;
+ portp->refcount = 0;
+ wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+static void stl_flushbuffer(struct tty_struct *tty)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_flushbuffer(tty=%x)\n", (int) tty);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ stl_flush(portp);
+ tty_wakeup(tty);
+}
+
+/*****************************************************************************/
+
+static void stl_breakctl(struct tty_struct *tty, int state)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_breakctl(tty=%x,state=%d)\n", (int) tty, state);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ stl_sendbreak(portp, ((state == -1) ? 1 : 2));
+}
+
+/*****************************************************************************/
+
+static void stl_waituntilsent(struct tty_struct *tty, int timeout)
+{
+ stlport_t *portp;
+ unsigned long tend;
+
+#ifdef DEBUG
+ printk("stl_waituntilsent(tty=%x,timeout=%d)\n", (int) tty, timeout);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ if (timeout == 0)
+ timeout = HZ;
+ tend = jiffies + timeout;
+
+ while (stl_datastate(portp)) {
+ if (signal_pending(current))
+ break;
+ msleep_interruptible(20);
+ if (time_after_eq(jiffies, tend))
+ break;
+ }
+}
+
+/*****************************************************************************/
+
+static void stl_sendxchar(struct tty_struct *tty, char ch)
+{
+ stlport_t *portp;
+
+#ifdef DEBUG
+ printk("stl_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch);
+#endif
+
+ if (tty == (struct tty_struct *) NULL)
+ return;
+ portp = tty->driver_data;
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ if (ch == STOP_CHAR(tty))
+ stl_sendflow(portp, 0);
+ else if (ch == START_CHAR(tty))
+ stl_sendflow(portp, 1);
+ else
+ stl_putchar(tty, ch);
+}
+
+/*****************************************************************************/
+
+#define MAXLINE 80
+
+/*
+ * Format info for a specified port. The line is deliberately limited
+ * to 80 characters. (If it is too long it will be truncated, if too
+ * short then padded with spaces).
+ */
+
+static int stl_portinfo(stlport_t *portp, int portnr, char *pos)
+{
+ char *sp;
+ int sigs, cnt;
+
+ sp = pos;
+ sp += sprintf(sp, "%d: uart:%s tx:%d rx:%d",
+ portnr, (portp->hwid == 1) ? "SC26198" : "CD1400",
+ (int) portp->stats.txtotal, (int) portp->stats.rxtotal);
+
+ if (portp->stats.rxframing)
+ sp += sprintf(sp, " fe:%d", (int) portp->stats.rxframing);
+ if (portp->stats.rxparity)
+ sp += sprintf(sp, " pe:%d", (int) portp->stats.rxparity);
+ if (portp->stats.rxbreaks)
+ sp += sprintf(sp, " brk:%d", (int) portp->stats.rxbreaks);
+ if (portp->stats.rxoverrun)
+ sp += sprintf(sp, " oe:%d", (int) portp->stats.rxoverrun);
+
+ sigs = stl_getsignals(portp);
+ cnt = sprintf(sp, "%s%s%s%s%s ",
+ (sigs & TIOCM_RTS) ? "|RTS" : "",
+ (sigs & TIOCM_CTS) ? "|CTS" : "",
+ (sigs & TIOCM_DTR) ? "|DTR" : "",
+ (sigs & TIOCM_CD) ? "|DCD" : "",
+ (sigs & TIOCM_DSR) ? "|DSR" : "");
+ *sp = ' ';
+ sp += cnt;
+
+ for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++)
+ *sp++ = ' ';
+ if (cnt >= MAXLINE)
+ pos[(MAXLINE - 2)] = '+';
+ pos[(MAXLINE - 1)] = '\n';
+
+ return(MAXLINE);
+}
+
+/*****************************************************************************/
+
+/*
+ * Port info, read from the /proc file system.
+ */
+
+static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ stlport_t *portp;
+ int brdnr, panelnr, portnr, totalport;
+ int curoff, maxoff;
+ char *pos;
+
+#ifdef DEBUG
+ printk("stl_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x,"
+ "data=%x\n", (int) page, (int) start, (int) off, count,
+ (int) eof, (int) data);
+#endif
+
+ pos = page;
+ totalport = 0;
+ curoff = 0;
+
+ if (off == 0) {
+ pos += sprintf(pos, "%s: version %s", stl_drvtitle,
+ stl_drvversion);
+ while (pos < (page + MAXLINE - 1))
+ *pos++ = ' ';
+ *pos++ = '\n';
+ }
+ curoff = MAXLINE;
+
+/*
+ * We scan through for each board, panel and port. The offset is
+ * calculated on the fly, and irrelevant ports are skipped.
+ */
+ for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) {
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ continue;
+ if (brdp->state == 0)
+ continue;
+
+ maxoff = curoff + (brdp->nrports * MAXLINE);
+ if (off >= maxoff) {
+ curoff = maxoff;
+ continue;
+ }
+
+ totalport = brdnr * STL_MAXPORTS;
+ for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+ panelp = brdp->panels[panelnr];
+ if (panelp == (stlpanel_t *) NULL)
+ continue;
+
+ maxoff = curoff + (panelp->nrports * MAXLINE);
+ if (off >= maxoff) {
+ curoff = maxoff;
+ totalport += panelp->nrports;
+ continue;
+ }
+
+ for (portnr = 0; (portnr < panelp->nrports); portnr++,
+ totalport++) {
+ portp = panelp->ports[portnr];
+ if (portp == (stlport_t *) NULL)
+ continue;
+ if (off >= (curoff += MAXLINE))
+ continue;
+ if ((pos - page + MAXLINE) > count)
+ goto stl_readdone;
+ pos += stl_portinfo(portp, totalport, pos);
+ }
+ }
+ }
+
+ *eof = 1;
+
+stl_readdone:
+ *start = page;
+ return(pos - page);
+}
+
+/*****************************************************************************/
+
+/*
+ * All board interrupts are vectored through here first. This code then
+ * calls off to the approrpriate board interrupt handlers.
+ */
+
+static irqreturn_t stl_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+ stlbrd_t *brdp = (stlbrd_t *) dev_id;
+
+#ifdef DEBUG
+ printk("stl_intr(brdp=%x,irq=%d,regs=%x)\n", (int) brdp, irq,
+ (int) regs);
+#endif
+
+ return IRQ_RETVAL((* brdp->isr)(brdp));
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for EasyIO board types.
+ */
+
+static int stl_eiointr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int iobase;
+ int handled = 0;
+
+ panelp = brdp->panels[0];
+ iobase = panelp->iobase;
+ while (inb(brdp->iostatus) & EIO_INTRPEND) {
+ handled = 1;
+ (* panelp->isr)(panelp, iobase);
+ }
+ return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-AT board types.
+ */
+
+static int stl_echatintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+ int handled = 0;
+
+ outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+
+ while (inb(brdp->iostatus) & ECH_INTRPEND) {
+ handled = 1;
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+
+ outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+
+ return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-MCA board types.
+ */
+
+static int stl_echmcaintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+ int handled = 0;
+
+ while (inb(brdp->iostatus) & ECH_INTRPEND) {
+ handled = 1;
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+ return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-PCI board types.
+ */
+
+static int stl_echpciintr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr, recheck;
+ int handled = 0;
+
+ while (1) {
+ recheck = 0;
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ outb(brdp->bnkpageaddr[bnknr], brdp->ioctrl);
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ recheck++;
+ handled = 1;
+ }
+ }
+ if (! recheck)
+ break;
+ }
+ return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for ECH-8/64-PCI board types.
+ */
+
+static int stl_echpci64intr(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int ioaddr;
+ int bnknr;
+ int handled = 0;
+
+ while (inb(brdp->ioctrl) & 0x1) {
+ handled = 1;
+ for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+ ioaddr = brdp->bnkstataddr[bnknr];
+ if (inb(ioaddr) & ECH_PNLINTRPEND) {
+ panelp = brdp->bnk2panel[bnknr];
+ (* panelp->isr)(panelp, (ioaddr & 0xfffc));
+ }
+ }
+ }
+
+ return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ * Service an off-level request for some channel.
+ */
+static void stl_offintr(void *private)
+{
+ stlport_t *portp;
+ struct tty_struct *tty;
+ unsigned int oldsigs;
+
+ portp = private;
+
+#ifdef DEBUG
+ printk("stl_offintr(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ lock_kernel();
+ if (test_bit(ASYI_TXLOW, &portp->istate)) {
+ tty_wakeup(tty);
+ }
+ if (test_bit(ASYI_DCDCHANGE, &portp->istate)) {
+ clear_bit(ASYI_DCDCHANGE, &portp->istate);
+ oldsigs = portp->sigs;
+ portp->sigs = stl_getsignals(portp);
+ if ((portp->sigs & TIOCM_CD) && ((oldsigs & TIOCM_CD) == 0))
+ wake_up_interruptible(&portp->open_wait);
+ if ((oldsigs & TIOCM_CD) && ((portp->sigs & TIOCM_CD) == 0)) {
+ if (portp->flags & ASYNC_CHECK_CD)
+ tty_hangup(tty); /* FIXME: module removal race here - AKPM */
+ }
+ }
+ unlock_kernel();
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize all the ports on a panel.
+ */
+
+static int __init stl_initports(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ stlport_t *portp;
+ int chipmask, i;
+
+#ifdef DEBUG
+ printk("stl_initports(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+ chipmask = stl_panelinit(brdp, panelp);
+
+/*
+ * All UART's are initialized (if found!). Now go through and setup
+ * each ports data structures.
+ */
+ for (i = 0; (i < panelp->nrports); i++) {
+ portp = (stlport_t *) stl_memalloc(sizeof(stlport_t));
+ if (portp == (stlport_t *) NULL) {
+ printk("STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlport_t));
+ break;
+ }
+ memset(portp, 0, sizeof(stlport_t));
+
+ portp->magic = STL_PORTMAGIC;
+ portp->portnr = i;
+ portp->brdnr = panelp->brdnr;
+ portp->panelnr = panelp->panelnr;
+ portp->uartp = panelp->uartp;
+ portp->clk = brdp->clk;
+ portp->baud_base = STL_BAUDBASE;
+ portp->close_delay = STL_CLOSEDELAY;
+ portp->closing_wait = 30 * HZ;
+ INIT_WORK(&portp->tqueue, stl_offintr, portp);
+ init_waitqueue_head(&portp->open_wait);
+ init_waitqueue_head(&portp->close_wait);
+ portp->stats.brd = portp->brdnr;
+ portp->stats.panel = portp->panelnr;
+ portp->stats.port = portp->portnr;
+ panelp->ports[i] = portp;
+ stl_portinit(brdp, panelp, portp);
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find and initialize an EasyIO board.
+ */
+
+static inline int stl_initeio(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int status;
+ char *name;
+ int rc;
+
+#ifdef DEBUG
+ printk("stl_initeio(brdp=%x)\n", (int) brdp);
+#endif
+
+ brdp->ioctrl = brdp->ioaddr1 + 1;
+ brdp->iostatus = brdp->ioaddr1 + 2;
+
+ status = inb(brdp->iostatus);
+ if ((status & EIO_IDBITMASK) == EIO_MK3)
+ brdp->ioctrl++;
+
+/*
+ * Handle board specific stuff now. The real difference is PCI
+ * or not PCI.
+ */
+ if (brdp->brdtype == BRD_EASYIOPCI) {
+ brdp->iosize1 = 0x80;
+ brdp->iosize2 = 0x80;
+ name = "serial(EIO-PCI)";
+ outb(0x41, (brdp->ioaddr2 + 0x4c));
+ } else {
+ brdp->iosize1 = 8;
+ name = "serial(EIO)";
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ outb((stl_vecmap[brdp->irq] | EIO_0WS |
+ ((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)),
+ brdp->ioctrl);
+ }
+
+ if (!request_region(brdp->ioaddr1, brdp->iosize1, name)) {
+ printk(KERN_WARNING "STALLION: Warning, board %d I/O address "
+ "%x conflicts with another device\n", brdp->brdnr,
+ brdp->ioaddr1);
+ return(-EBUSY);
+ }
+
+ if (brdp->iosize2 > 0)
+ if (!request_region(brdp->ioaddr2, brdp->iosize2, name)) {
+ printk(KERN_WARNING "STALLION: Warning, board %d I/O "
+ "address %x conflicts with another device\n",
+ brdp->brdnr, brdp->ioaddr2);
+ printk(KERN_WARNING "STALLION: Warning, also "
+ "releasing board %d I/O address %x \n",
+ brdp->brdnr, brdp->ioaddr1);
+ release_region(brdp->ioaddr1, brdp->iosize1);
+ return(-EBUSY);
+ }
+
+/*
+ * Everything looks OK, so let's go ahead and probe for the hardware.
+ */
+ brdp->clk = CD1400_CLK;
+ brdp->isr = stl_eiointr;
+
+ switch (status & EIO_IDBITMASK) {
+ case EIO_8PORTM:
+ brdp->clk = CD1400_CLK8M;
+ /* fall thru */
+ case EIO_8PORTRS:
+ case EIO_8PORTDI:
+ brdp->nrports = 8;
+ break;
+ case EIO_4PORTRS:
+ brdp->nrports = 4;
+ break;
+ case EIO_MK3:
+ switch (status & EIO_BRDMASK) {
+ case ID_BRD4:
+ brdp->nrports = 4;
+ break;
+ case ID_BRD8:
+ brdp->nrports = 8;
+ break;
+ case ID_BRD16:
+ brdp->nrports = 16;
+ break;
+ default:
+ return(-ENODEV);
+ }
+ break;
+ default:
+ return(-ENODEV);
+ }
+
+/*
+ * We have verified that the board is actually present, so now we
+ * can complete the setup.
+ */
+
+ panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+ if (panelp == (stlpanel_t *) NULL) {
+ printk(KERN_WARNING "STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlpanel_t));
+ return(-ENOMEM);
+ }
+ memset(panelp, 0, sizeof(stlpanel_t));
+
+ panelp->magic = STL_PANELMAGIC;
+ panelp->brdnr = brdp->brdnr;
+ panelp->panelnr = 0;
+ panelp->nrports = brdp->nrports;
+ panelp->iobase = brdp->ioaddr1;
+ panelp->hwid = status;
+ if ((status & EIO_IDBITMASK) == EIO_MK3) {
+ panelp->uartp = (void *) &stl_sc26198uart;
+ panelp->isr = stl_sc26198intr;
+ } else {
+ panelp->uartp = (void *) &stl_cd1400uart;
+ panelp->isr = stl_cd1400eiointr;
+ }
+
+ brdp->panels[0] = panelp;
+ brdp->nrpanels = 1;
+ brdp->state |= BRD_FOUND;
+ brdp->hwid = status;
+ if (request_irq(brdp->irq, stl_intr, SA_SHIRQ, name, brdp) != 0) {
+ printk("STALLION: failed to register interrupt "
+ "routine for %s irq=%d\n", name, brdp->irq);
+ rc = -ENODEV;
+ } else {
+ rc = 0;
+ }
+ return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ * Try to find an ECH board and initialize it. This code is capable of
+ * dealing with all types of ECH board.
+ */
+
+static inline int stl_initech(stlbrd_t *brdp)
+{
+ stlpanel_t *panelp;
+ unsigned int status, nxtid, ioaddr, conflict;
+ int panelnr, banknr, i;
+ char *name;
+
+#ifdef DEBUG
+ printk("stl_initech(brdp=%x)\n", (int) brdp);
+#endif
+
+ status = 0;
+ conflict = 0;
+
+/*
+ * Set up the initial board register contents for boards. This varies a
+ * bit between the different board types. So we need to handle each
+ * separately. Also do a check that the supplied IRQ is good.
+ */
+ switch (brdp->brdtype) {
+
+ case BRD_ECH:
+ brdp->isr = stl_echatintr;
+ brdp->ioctrl = brdp->ioaddr1 + 1;
+ brdp->iostatus = brdp->ioaddr1 + 1;
+ status = inb(brdp->iostatus);
+ if ((status & ECH_IDBITMASK) != ECH_ID)
+ return(-ENODEV);
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1);
+ status |= (stl_vecmap[brdp->irq] << 1);
+ outb((status | ECH_BRDRESET), brdp->ioaddr1);
+ brdp->ioctrlval = ECH_INTENABLE |
+ ((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE);
+ for (i = 0; (i < 10); i++)
+ outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+ brdp->iosize1 = 2;
+ brdp->iosize2 = 32;
+ name = "serial(EC8/32)";
+ outb(status, brdp->ioaddr1);
+ break;
+
+ case BRD_ECHMC:
+ brdp->isr = stl_echmcaintr;
+ brdp->ioctrl = brdp->ioaddr1 + 0x20;
+ brdp->iostatus = brdp->ioctrl;
+ status = inb(brdp->iostatus);
+ if ((status & ECH_IDBITMASK) != ECH_ID)
+ return(-ENODEV);
+ if ((brdp->irq < 0) || (brdp->irq > 15) ||
+ (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+ printk("STALLION: invalid irq=%d for brd=%d\n",
+ brdp->irq, brdp->brdnr);
+ return(-EINVAL);
+ }
+ outb(ECHMC_BRDRESET, brdp->ioctrl);
+ outb(ECHMC_INTENABLE, brdp->ioctrl);
+ brdp->iosize1 = 64;
+ name = "serial(EC8/32-MC)";
+ break;
+
+ case BRD_ECHPCI:
+ brdp->isr = stl_echpciintr;
+ brdp->ioctrl = brdp->ioaddr1 + 2;
+ brdp->iosize1 = 4;
+ brdp->iosize2 = 8;
+ name = "serial(EC8/32-PCI)";
+ break;
+
+ case BRD_ECH64PCI:
+ brdp->isr = stl_echpci64intr;
+ brdp->ioctrl = brdp->ioaddr2 + 0x40;
+ outb(0x43, (brdp->ioaddr1 + 0x4c));
+ brdp->iosize1 = 0x80;
+ brdp->iosize2 = 0x80;
+ name = "serial(EC8/64-PCI)";
+ break;
+
+ default:
+ printk("STALLION: unknown board type=%d\n", brdp->brdtype);
+ return(-EINVAL);
+ break;
+ }
+
+/*
+ * Check boards for possible IO address conflicts and return fail status
+ * if an IO conflict found.
+ */
+ if (!request_region(brdp->ioaddr1, brdp->iosize1, name)) {
+ printk(KERN_WARNING "STALLION: Warning, board %d I/O address "
+ "%x conflicts with another device\n", brdp->brdnr,
+ brdp->ioaddr1);
+ return(-EBUSY);
+ }
+
+ if (brdp->iosize2 > 0)
+ if (!request_region(brdp->ioaddr2, brdp->iosize2, name)) {
+ printk(KERN_WARNING "STALLION: Warning, board %d I/O "
+ "address %x conflicts with another device\n",
+ brdp->brdnr, brdp->ioaddr2);
+ printk(KERN_WARNING "STALLION: Warning, also "
+ "releasing board %d I/O address %x \n",
+ brdp->brdnr, brdp->ioaddr1);
+ release_region(brdp->ioaddr1, brdp->iosize1);
+ return(-EBUSY);
+ }
+
+/*
+ * Scan through the secondary io address space looking for panels.
+ * As we find'em allocate and initialize panel structures for each.
+ */
+ brdp->clk = CD1400_CLK;
+ brdp->hwid = status;
+
+ ioaddr = brdp->ioaddr2;
+ banknr = 0;
+ panelnr = 0;
+ nxtid = 0;
+
+ for (i = 0; (i < STL_MAXPANELS); i++) {
+ if (brdp->brdtype == BRD_ECHPCI) {
+ outb(nxtid, brdp->ioctrl);
+ ioaddr = brdp->ioaddr2;
+ }
+ status = inb(ioaddr + ECH_PNLSTATUS);
+ if ((status & ECH_PNLIDMASK) != nxtid)
+ break;
+ panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+ if (panelp == (stlpanel_t *) NULL) {
+ printk("STALLION: failed to allocate memory "
+ "(size=%d)\n", sizeof(stlpanel_t));
+ break;
+ }
+ memset(panelp, 0, sizeof(stlpanel_t));
+ panelp->magic = STL_PANELMAGIC;
+ panelp->brdnr = brdp->brdnr;
+ panelp->panelnr = panelnr;
+ panelp->iobase = ioaddr;
+ panelp->pagenr = nxtid;
+ panelp->hwid = status;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr + ECH_PNLSTATUS;
+
+ if (status & ECH_PNLXPID) {
+ panelp->uartp = (void *) &stl_sc26198uart;
+ panelp->isr = stl_sc26198intr;
+ if (status & ECH_PNL16PORT) {
+ panelp->nrports = 16;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr + 4 +
+ ECH_PNLSTATUS;
+ } else {
+ panelp->nrports = 8;
+ }
+ } else {
+ panelp->uartp = (void *) &stl_cd1400uart;
+ panelp->isr = stl_cd1400echintr;
+ if (status & ECH_PNL16PORT) {
+ panelp->nrports = 16;
+ panelp->ackmask = 0x80;
+ if (brdp->brdtype != BRD_ECHPCI)
+ ioaddr += EREG_BANKSIZE;
+ brdp->bnk2panel[banknr] = panelp;
+ brdp->bnkpageaddr[banknr] = ++nxtid;
+ brdp->bnkstataddr[banknr++] = ioaddr +
+ ECH_PNLSTATUS;
+ } else {
+ panelp->nrports = 8;
+ panelp->ackmask = 0xc0;
+ }
+ }
+
+ nxtid++;
+ ioaddr += EREG_BANKSIZE;
+ brdp->nrports += panelp->nrports;
+ brdp->panels[panelnr++] = panelp;
+ if ((brdp->brdtype != BRD_ECHPCI) &&
+ (ioaddr >= (brdp->ioaddr2 + brdp->iosize2)))
+ break;
+ }
+
+ brdp->nrpanels = panelnr;
+ brdp->nrbnks = banknr;
+ if (brdp->brdtype == BRD_ECH)
+ outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+
+ brdp->state |= BRD_FOUND;
+ if (request_irq(brdp->irq, stl_intr, SA_SHIRQ, name, brdp) != 0) {
+ printk("STALLION: failed to register interrupt "
+ "routine for %s irq=%d\n", name, brdp->irq);
+ i = -ENODEV;
+ } else {
+ i = 0;
+ }
+
+ return(i);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize and configure the specified board.
+ * Scan through all the boards in the configuration and see what we
+ * can find. Handle EIO and the ECH boards a little differently here
+ * since the initial search and setup is very different.
+ */
+
+static int __init stl_brdinit(stlbrd_t *brdp)
+{
+ int i;
+
+#ifdef DEBUG
+ printk("stl_brdinit(brdp=%x)\n", (int) brdp);
+#endif
+
+ switch (brdp->brdtype) {
+ case BRD_EASYIO:
+ case BRD_EASYIOPCI:
+ stl_initeio(brdp);
+ break;
+ case BRD_ECH:
+ case BRD_ECHMC:
+ case BRD_ECHPCI:
+ case BRD_ECH64PCI:
+ stl_initech(brdp);
+ break;
+ default:
+ printk("STALLION: board=%d is unknown board type=%d\n",
+ brdp->brdnr, brdp->brdtype);
+ return(ENODEV);
+ }
+
+ stl_brds[brdp->brdnr] = brdp;
+ if ((brdp->state & BRD_FOUND) == 0) {
+ printk("STALLION: %s board not found, board=%d io=%x irq=%d\n",
+ stl_brdnames[brdp->brdtype], brdp->brdnr,
+ brdp->ioaddr1, brdp->irq);
+ return(ENODEV);
+ }
+
+ for (i = 0; (i < STL_MAXPANELS); i++)
+ if (brdp->panels[i] != (stlpanel_t *) NULL)
+ stl_initports(brdp, brdp->panels[i]);
+
+ printk("STALLION: %s found, board=%d io=%x irq=%d "
+ "nrpanels=%d nrports=%d\n", stl_brdnames[brdp->brdtype],
+ brdp->brdnr, brdp->ioaddr1, brdp->irq, brdp->nrpanels,
+ brdp->nrports);
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find the next available board number that is free.
+ */
+
+static inline int stl_getbrdnr(void)
+{
+ int i;
+
+ for (i = 0; (i < STL_MAXBRDS); i++) {
+ if (stl_brds[i] == (stlbrd_t *) NULL) {
+ if (i >= stl_nrbrds)
+ stl_nrbrds = i + 1;
+ return(i);
+ }
+ }
+ return(-1);
+}
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ * We have a Stallion board. Allocate a board structure and
+ * initialize it. Read its IO and IRQ resources from PCI
+ * configuration space.
+ */
+
+static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp)
+{
+ stlbrd_t *brdp;
+
+#ifdef DEBUG
+ printk("stl_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n", brdtype,
+ devp->bus->number, devp->devfn);
+#endif
+
+ if (pci_enable_device(devp))
+ return(-EIO);
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ return(-ENOMEM);
+ if ((brdp->brdnr = stl_getbrdnr()) < 0) {
+ printk("STALLION: too many boards found, "
+ "maximum supported %d\n", STL_MAXBRDS);
+ return(0);
+ }
+ brdp->brdtype = brdtype;
+
+/*
+ * Different Stallion boards use the BAR registers in different ways,
+ * so set up io addresses based on board type.
+ */
+#ifdef DEBUG
+ printk("%s(%d): BAR[]=%x,%x,%x,%x IRQ=%x\n", __FILE__, __LINE__,
+ pci_resource_start(devp, 0), pci_resource_start(devp, 1),
+ pci_resource_start(devp, 2), pci_resource_start(devp, 3), devp->irq);
+#endif
+
+/*
+ * We have all resources from the board, so let's setup the actual
+ * board structure now.
+ */
+ switch (brdtype) {
+ case BRD_ECHPCI:
+ brdp->ioaddr2 = pci_resource_start(devp, 0);
+ brdp->ioaddr1 = pci_resource_start(devp, 1);
+ break;
+ case BRD_ECH64PCI:
+ brdp->ioaddr2 = pci_resource_start(devp, 2);
+ brdp->ioaddr1 = pci_resource_start(devp, 1);
+ break;
+ case BRD_EASYIOPCI:
+ brdp->ioaddr1 = pci_resource_start(devp, 2);
+ brdp->ioaddr2 = pci_resource_start(devp, 1);
+ break;
+ default:
+ printk("STALLION: unknown PCI board type=%d\n", brdtype);
+ break;
+ }
+
+ brdp->irq = devp->irq;
+ stl_brdinit(brdp);
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Find all Stallion PCI boards that might be installed. Initialize each
+ * one as it is found.
+ */
+
+
+static inline int stl_findpcibrds(void)
+{
+ struct pci_dev *dev = NULL;
+ int i, rc;
+
+#ifdef DEBUG
+ printk("stl_findpcibrds()\n");
+#endif
+
+ for (i = 0; (i < stl_nrpcibrds); i++)
+ while ((dev = pci_find_device(stl_pcibrds[i].vendid,
+ stl_pcibrds[i].devid, dev))) {
+
+/*
+ * Found a device on the PCI bus that has our vendor and
+ * device ID. Need to check now that it is really us.
+ */
+ if ((dev->class >> 8) == PCI_CLASS_STORAGE_IDE)
+ continue;
+
+ rc = stl_initpcibrd(stl_pcibrds[i].brdtype, dev);
+ if (rc)
+ return(rc);
+ }
+
+ return(0);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Scan through all the boards in the configuration and see what we
+ * can find. Handle EIO and the ECH boards a little differently here
+ * since the initial search and setup is too different.
+ */
+
+static inline int stl_initbrds(void)
+{
+ stlbrd_t *brdp;
+ stlconf_t *confp;
+ int i;
+
+#ifdef DEBUG
+ printk("stl_initbrds()\n");
+#endif
+
+ if (stl_nrbrds > STL_MAXBRDS) {
+ printk("STALLION: too many boards in configuration table, "
+ "truncating to %d\n", STL_MAXBRDS);
+ stl_nrbrds = STL_MAXBRDS;
+ }
+
+/*
+ * Firstly scan the list of static boards configured. Allocate
+ * resources and initialize the boards as found.
+ */
+ for (i = 0; (i < stl_nrbrds); i++) {
+ confp = &stl_brdconf[i];
+ stl_parsebrd(confp, stl_brdsp[i]);
+ if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+ return(-ENOMEM);
+ brdp->brdnr = i;
+ brdp->brdtype = confp->brdtype;
+ brdp->ioaddr1 = confp->ioaddr1;
+ brdp->ioaddr2 = confp->ioaddr2;
+ brdp->irq = confp->irq;
+ brdp->irqtype = confp->irqtype;
+ stl_brdinit(brdp);
+ }
+
+/*
+ * Find any dynamically supported boards. That is via module load
+ * line options or auto-detected on the PCI bus.
+ */
+ stl_argbrds();
+#ifdef CONFIG_PCI
+ stl_findpcibrds();
+#endif
+
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the board stats structure to user app.
+ */
+
+static int stl_getbrdstats(combrd_t __user *bp)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+ int i;
+
+ if (copy_from_user(&stl_brdstats, bp, sizeof(combrd_t)))
+ return -EFAULT;
+ if (stl_brdstats.brd >= STL_MAXBRDS)
+ return(-ENODEV);
+ brdp = stl_brds[stl_brdstats.brd];
+ if (brdp == (stlbrd_t *) NULL)
+ return(-ENODEV);
+
+ memset(&stl_brdstats, 0, sizeof(combrd_t));
+ stl_brdstats.brd = brdp->brdnr;
+ stl_brdstats.type = brdp->brdtype;
+ stl_brdstats.hwid = brdp->hwid;
+ stl_brdstats.state = brdp->state;
+ stl_brdstats.ioaddr = brdp->ioaddr1;
+ stl_brdstats.ioaddr2 = brdp->ioaddr2;
+ stl_brdstats.irq = brdp->irq;
+ stl_brdstats.nrpanels = brdp->nrpanels;
+ stl_brdstats.nrports = brdp->nrports;
+ for (i = 0; (i < brdp->nrpanels); i++) {
+ panelp = brdp->panels[i];
+ stl_brdstats.panels[i].panel = i;
+ stl_brdstats.panels[i].hwid = panelp->hwid;
+ stl_brdstats.panels[i].nrports = panelp->nrports;
+ }
+
+ return copy_to_user(bp, &stl_brdstats, sizeof(combrd_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Resolve the referenced port number into a port struct pointer.
+ */
+
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr)
+{
+ stlbrd_t *brdp;
+ stlpanel_t *panelp;
+
+ if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+ return((stlport_t *) NULL);
+ brdp = stl_brds[brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return((stlport_t *) NULL);
+ if ((panelnr < 0) || (panelnr >= brdp->nrpanels))
+ return((stlport_t *) NULL);
+ panelp = brdp->panels[panelnr];
+ if (panelp == (stlpanel_t *) NULL)
+ return((stlport_t *) NULL);
+ if ((portnr < 0) || (portnr >= panelp->nrports))
+ return((stlport_t *) NULL);
+ return(panelp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the port stats structure to user app. A NULL port struct
+ * pointer passed in means that we need to find out from the app
+ * what port to get stats for (used through board control device).
+ */
+
+static int stl_getportstats(stlport_t *portp, comstats_t __user *cp)
+{
+ unsigned char *head, *tail;
+ unsigned long flags;
+
+ if (!portp) {
+ if (copy_from_user(&stl_comstats, cp, sizeof(comstats_t)))
+ return -EFAULT;
+ portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+ stl_comstats.port);
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ }
+
+ portp->stats.state = portp->istate;
+ portp->stats.flags = portp->flags;
+ portp->stats.hwid = portp->hwid;
+
+ portp->stats.ttystate = 0;
+ portp->stats.cflags = 0;
+ portp->stats.iflags = 0;
+ portp->stats.oflags = 0;
+ portp->stats.lflags = 0;
+ portp->stats.rxbuffered = 0;
+
+ save_flags(flags);
+ cli();
+ if (portp->tty != (struct tty_struct *) NULL) {
+ if (portp->tty->driver_data == portp) {
+ portp->stats.ttystate = portp->tty->flags;
+ portp->stats.rxbuffered = portp->tty->flip.count;
+ if (portp->tty->termios != (struct termios *) NULL) {
+ portp->stats.cflags = portp->tty->termios->c_cflag;
+ portp->stats.iflags = portp->tty->termios->c_iflag;
+ portp->stats.oflags = portp->tty->termios->c_oflag;
+ portp->stats.lflags = portp->tty->termios->c_lflag;
+ }
+ }
+ }
+ restore_flags(flags);
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ portp->stats.txbuffered = ((head >= tail) ? (head - tail) :
+ (STL_TXBUFSIZE - (tail - head)));
+
+ portp->stats.signals = (unsigned long) stl_getsignals(portp);
+
+ return copy_to_user(cp, &portp->stats,
+ sizeof(comstats_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Clear the port stats structure. We also return it zeroed out...
+ */
+
+static int stl_clrportstats(stlport_t *portp, comstats_t __user *cp)
+{
+ if (!portp) {
+ if (copy_from_user(&stl_comstats, cp, sizeof(comstats_t)))
+ return -EFAULT;
+ portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+ stl_comstats.port);
+ if (portp == (stlport_t *) NULL)
+ return(-ENODEV);
+ }
+
+ memset(&portp->stats, 0, sizeof(comstats_t));
+ portp->stats.brd = portp->brdnr;
+ portp->stats.panel = portp->panelnr;
+ portp->stats.port = portp->portnr;
+ return copy_to_user(cp, &portp->stats,
+ sizeof(comstats_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver ports structure to a user app.
+ */
+
+static int stl_getportstruct(stlport_t __user *arg)
+{
+ stlport_t *portp;
+
+ if (copy_from_user(&stl_dummyport, arg, sizeof(stlport_t)))
+ return -EFAULT;
+ portp = stl_getport(stl_dummyport.brdnr, stl_dummyport.panelnr,
+ stl_dummyport.portnr);
+ if (!portp)
+ return -ENODEV;
+ return copy_to_user(arg, portp, sizeof(stlport_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the entire driver board structure to a user app.
+ */
+
+static int stl_getbrdstruct(stlbrd_t __user *arg)
+{
+ stlbrd_t *brdp;
+
+ if (copy_from_user(&stl_dummybrd, arg, sizeof(stlbrd_t)))
+ return -EFAULT;
+ if ((stl_dummybrd.brdnr < 0) || (stl_dummybrd.brdnr >= STL_MAXBRDS))
+ return -ENODEV;
+ brdp = stl_brds[stl_dummybrd.brdnr];
+ if (!brdp)
+ return(-ENODEV);
+ return copy_to_user(arg, brdp, sizeof(stlbrd_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ * The "staliomem" device is also required to do some special operations
+ * on the board and/or ports. In this driver it is mostly used for stats
+ * collection.
+ */
+
+static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ int brdnr, rc;
+ void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+ printk("stl_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n", (int) ip,
+ (int) fp, cmd, (int) arg);
+#endif
+
+ brdnr = iminor(ip);
+ if (brdnr >= STL_MAXBRDS)
+ return(-ENODEV);
+ rc = 0;
+
+ switch (cmd) {
+ case COM_GETPORTSTATS:
+ rc = stl_getportstats(NULL, argp);
+ break;
+ case COM_CLRPORTSTATS:
+ rc = stl_clrportstats(NULL, argp);
+ break;
+ case COM_GETBRDSTATS:
+ rc = stl_getbrdstats(argp);
+ break;
+ case COM_READPORT:
+ rc = stl_getportstruct(argp);
+ break;
+ case COM_READBOARD:
+ rc = stl_getbrdstruct(argp);
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ return(rc);
+}
+
+static struct tty_operations stl_ops = {
+ .open = stl_open,
+ .close = stl_close,
+ .write = stl_write,
+ .put_char = stl_putchar,
+ .flush_chars = stl_flushchars,
+ .write_room = stl_writeroom,
+ .chars_in_buffer = stl_charsinbuffer,
+ .ioctl = stl_ioctl,
+ .set_termios = stl_settermios,
+ .throttle = stl_throttle,
+ .unthrottle = stl_unthrottle,
+ .stop = stl_stop,
+ .start = stl_start,
+ .hangup = stl_hangup,
+ .flush_buffer = stl_flushbuffer,
+ .break_ctl = stl_breakctl,
+ .wait_until_sent = stl_waituntilsent,
+ .send_xchar = stl_sendxchar,
+ .read_proc = stl_readproc,
+ .tiocmget = stl_tiocmget,
+ .tiocmset = stl_tiocmset,
+};
+
+/*****************************************************************************/
+
+int __init stl_init(void)
+{
+ int i;
+ printk(KERN_INFO "%s: version %s\n", stl_drvtitle, stl_drvversion);
+
+ stl_initbrds();
+
+ stl_serial = alloc_tty_driver(STL_MAXBRDS * STL_MAXPORTS);
+ if (!stl_serial)
+ return -1;
+
+/*
+ * Allocate a temporary write buffer.
+ */
+ stl_tmpwritebuf = (char *) stl_memalloc(STL_TXBUFSIZE);
+ if (stl_tmpwritebuf == (char *) NULL)
+ printk("STALLION: failed to allocate memory (size=%d)\n",
+ STL_TXBUFSIZE);
+
+/*
+ * Set up a character driver for per board stuff. This is mainly used
+ * to do stats ioctls on the ports.
+ */
+ if (register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stl_fsiomem))
+ printk("STALLION: failed to register serial board device\n");
+ devfs_mk_dir("staliomem");
+
+ stallion_class = class_simple_create(THIS_MODULE, "staliomem");
+ for (i = 0; i < 4; i++) {
+ devfs_mk_cdev(MKDEV(STL_SIOMEMMAJOR, i),
+ S_IFCHR|S_IRUSR|S_IWUSR,
+ "staliomem/%d", i);
+ class_simple_device_add(stallion_class, MKDEV(STL_SIOMEMMAJOR, i), NULL, "staliomem%d", i);
+ }
+
+ stl_serial->owner = THIS_MODULE;
+ stl_serial->driver_name = stl_drvname;
+ stl_serial->name = "ttyE";
+ stl_serial->devfs_name = "tts/E";
+ stl_serial->major = STL_SERIALMAJOR;
+ stl_serial->minor_start = 0;
+ stl_serial->type = TTY_DRIVER_TYPE_SERIAL;
+ stl_serial->subtype = SERIAL_TYPE_NORMAL;
+ stl_serial->init_termios = stl_deftermios;
+ stl_serial->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(stl_serial, &stl_ops);
+
+ if (tty_register_driver(stl_serial)) {
+ put_tty_driver(stl_serial);
+ printk("STALLION: failed to register serial driver\n");
+ return -1;
+ }
+
+ return(0);
+}
+
+/*****************************************************************************/
+/* CD1400 HARDWARE FUNCTIONS */
+/*****************************************************************************/
+
+/*
+ * These functions get/set/update the registers of the cd1400 UARTs.
+ * Access to the cd1400 registers is via an address/data io port pair.
+ * (Maybe should make this inline...)
+ */
+
+static int stl_cd1400getreg(stlport_t *portp, int regnr)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ return(inb(portp->ioaddr + EREG_DATA));
+}
+
+static void stl_cd1400setreg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ outb(value, portp->ioaddr + EREG_DATA);
+}
+
+static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr + portp->uartaddr), portp->ioaddr);
+ if (inb(portp->ioaddr + EREG_DATA) != value) {
+ outb(value, portp->ioaddr + EREG_DATA);
+ return(1);
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Inbitialize the UARTs in a panel. We don't care what sort of board
+ * these ports are on - since the port io registers are almost
+ * identical when dealing with ports.
+ */
+
+static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ unsigned int gfrcr;
+ int chipmask, i, j;
+ int nrchips, uartaddr, ioaddr;
+
+#ifdef DEBUG
+ printk("stl_panelinit(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+ BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ * Check that each chip is present and started up OK.
+ */
+ chipmask = 0;
+ nrchips = panelp->nrports / CD1400_PORTS;
+ for (i = 0; (i < nrchips); i++) {
+ if (brdp->brdtype == BRD_ECHPCI) {
+ outb((panelp->pagenr + (i >> 1)), brdp->ioctrl);
+ ioaddr = panelp->iobase;
+ } else {
+ ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1));
+ }
+ uartaddr = (i & 0x01) ? 0x080 : 0;
+ outb((GFRCR + uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+ outb((CCR + uartaddr), ioaddr);
+ outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+ outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+ outb((GFRCR + uartaddr), ioaddr);
+ for (j = 0; (j < CCR_MAXWAIT); j++) {
+ if ((gfrcr = inb(ioaddr + EREG_DATA)) != 0)
+ break;
+ }
+ if ((j >= CCR_MAXWAIT) || (gfrcr < 0x40) || (gfrcr > 0x60)) {
+ printk("STALLION: cd1400 not responding, "
+ "brd=%d panel=%d chip=%d\n",
+ panelp->brdnr, panelp->panelnr, i);
+ continue;
+ }
+ chipmask |= (0x1 << i);
+ outb((PPR + uartaddr), ioaddr);
+ outb(PPR_SCALAR, (ioaddr + EREG_DATA));
+ }
+
+ BRDDISABLE(panelp->brdnr);
+ return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize hardware specific port registers.
+ */
+
+static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#ifdef DEBUG
+ printk("stl_cd1400portinit(brdp=%x,panelp=%x,portp=%x)\n",
+ (int) brdp, (int) panelp, (int) portp);
+#endif
+
+ if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+ (portp == (stlport_t *) NULL))
+ return;
+
+ portp->ioaddr = panelp->iobase + (((brdp->brdtype == BRD_ECHPCI) ||
+ (portp->portnr < 8)) ? 0 : EREG_BANKSIZE);
+ portp->uartaddr = (portp->portnr & 0x04) << 5;
+ portp->pagenr = panelp->pagenr + (portp->portnr >> 3);
+
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, LIVR, (portp->portnr << 3));
+ portp->hwid = stl_cd1400getreg(portp, GFRCR);
+ BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Wait for the command register to be ready. We will poll this,
+ * since it won't usually take too long to be ready.
+ */
+
+static void stl_cd1400ccrwait(stlport_t *portp)
+{
+ int i;
+
+ for (i = 0; (i < CCR_MAXWAIT); i++) {
+ if (stl_cd1400getreg(portp, CCR) == 0) {
+ return;
+ }
+ }
+
+ printk("STALLION: cd1400 not responding, port=%d panel=%d brd=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set up the cd1400 registers for a port based on the termios port
+ * settings.
+ */
+
+static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp)
+{
+ stlbrd_t *brdp;
+ unsigned long flags;
+ unsigned int clkdiv, baudrate;
+ unsigned char cor1, cor2, cor3;
+ unsigned char cor4, cor5, ccr;
+ unsigned char srer, sreron, sreroff;
+ unsigned char mcor1, mcor2, rtpr;
+ unsigned char clk, div;
+
+ cor1 = 0;
+ cor2 = 0;
+ cor3 = 0;
+ cor4 = 0;
+ cor5 = 0;
+ ccr = 0;
+ rtpr = 0;
+ clk = 0;
+ div = 0;
+ mcor1 = 0;
+ mcor2 = 0;
+ sreron = 0;
+ sreroff = 0;
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return;
+
+/*
+ * Set up the RX char ignore mask with those RX error types we
+ * can ignore. We can get the cd1400 to help us out a little here,
+ * it will ignore parity errors and breaks for us.
+ */
+ portp->rxignoremsk = 0;
+ if (tiosp->c_iflag & IGNPAR) {
+ portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN);
+ cor1 |= COR1_PARIGNORE;
+ }
+ if (tiosp->c_iflag & IGNBRK) {
+ portp->rxignoremsk |= ST_BREAK;
+ cor4 |= COR4_IGNBRK;
+ }
+
+ portp->rxmarkmsk = ST_OVERRUN;
+ if (tiosp->c_iflag & (INPCK | PARMRK))
+ portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING);
+ if (tiosp->c_iflag & BRKINT)
+ portp->rxmarkmsk |= ST_BREAK;
+
+/*
+ * Go through the char size, parity and stop bits and set all the
+ * option register appropriately.
+ */
+ switch (tiosp->c_cflag & CSIZE) {
+ case CS5:
+ cor1 |= COR1_CHL5;
+ break;
+ case CS6:
+ cor1 |= COR1_CHL6;
+ break;
+ case CS7:
+ cor1 |= COR1_CHL7;
+ break;
+ default:
+ cor1 |= COR1_CHL8;
+ break;
+ }
+
+ if (tiosp->c_cflag & CSTOPB)
+ cor1 |= COR1_STOP2;
+ else
+ cor1 |= COR1_STOP1;
+
+ if (tiosp->c_cflag & PARENB) {
+ if (tiosp->c_cflag & PARODD)
+ cor1 |= (COR1_PARENB | COR1_PARODD);
+ else
+ cor1 |= (COR1_PARENB | COR1_PAREVEN);
+ } else {
+ cor1 |= COR1_PARNONE;
+ }
+
+/*
+ * Set the RX FIFO threshold at 6 chars. This gives a bit of breathing
+ * space for hardware flow control and the like. This should be set to
+ * VMIN. Also here we will set the RX data timeout to 10ms - this should
+ * really be based on VTIME.
+ */
+ cor3 |= FIFO_RXTHRESHOLD;
+ rtpr = 2;
+
+/*
+ * Calculate the baud rate timers. For now we will just assume that
+ * the input and output baud are the same. Could have used a baud
+ * table here, but this way we can generate virtually any baud rate
+ * we like!
+ */
+ baudrate = tiosp->c_cflag & CBAUD;
+ if (baudrate & CBAUDEX) {
+ baudrate &= ~CBAUDEX;
+ if ((baudrate < 1) || (baudrate > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ baudrate += 15;
+ }
+ baudrate = stl_baudrates[baudrate];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baudrate = 57600;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baudrate = 115200;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ baudrate = 230400;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ baudrate = 460800;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudrate = (portp->baud_base / portp->custom_divisor);
+ }
+ if (baudrate > STL_CD1400MAXBAUD)
+ baudrate = STL_CD1400MAXBAUD;
+
+ if (baudrate > 0) {
+ for (clk = 0; (clk < CD1400_NUMCLKS); clk++) {
+ clkdiv = ((portp->clk / stl_cd1400clkdivs[clk]) / baudrate);
+ if (clkdiv < 0x100)
+ break;
+ }
+ div = (unsigned char) clkdiv;
+ }
+
+/*
+ * Check what form of modem signaling is required and set it up.
+ */
+ if ((tiosp->c_cflag & CLOCAL) == 0) {
+ mcor1 |= MCOR1_DCD;
+ mcor2 |= MCOR2_DCD;
+ sreron |= SRER_MODEM;
+ portp->flags |= ASYNC_CHECK_CD;
+ } else {
+ portp->flags &= ~ASYNC_CHECK_CD;
+ }
+
+/*
+ * Setup cd1400 enhanced modes if we can. In particular we want to
+ * handle as much of the flow control as possible automatically. As
+ * well as saving a few CPU cycles it will also greatly improve flow
+ * control reliability.
+ */
+ if (tiosp->c_iflag & IXON) {
+ cor2 |= COR2_TXIBE;
+ cor3 |= COR3_SCD12;
+ if (tiosp->c_iflag & IXANY)
+ cor2 |= COR2_IXM;
+ }
+
+ if (tiosp->c_cflag & CRTSCTS) {
+ cor2 |= COR2_CTSAE;
+ mcor1 |= FIFO_RTSTHRESHOLD;
+ }
+
+/*
+ * All cd1400 register values calculated so go through and set
+ * them all up.
+ */
+
+#ifdef DEBUG
+ printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+ printk(" cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n",
+ cor1, cor2, cor3, cor4, cor5);
+ printk(" mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n",
+ mcor1, mcor2, rtpr, sreron, sreroff);
+ printk(" tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div);
+ printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n",
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x3));
+ srer = stl_cd1400getreg(portp, SRER);
+ stl_cd1400setreg(portp, SRER, 0);
+ if (stl_cd1400updatereg(portp, COR1, cor1))
+ ccr = 1;
+ if (stl_cd1400updatereg(portp, COR2, cor2))
+ ccr = 1;
+ if (stl_cd1400updatereg(portp, COR3, cor3))
+ ccr = 1;
+ if (ccr) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_CORCHANGE);
+ }
+ stl_cd1400setreg(portp, COR4, cor4);
+ stl_cd1400setreg(portp, COR5, cor5);
+ stl_cd1400setreg(portp, MCOR1, mcor1);
+ stl_cd1400setreg(portp, MCOR2, mcor2);
+ if (baudrate > 0) {
+ stl_cd1400setreg(portp, TCOR, clk);
+ stl_cd1400setreg(portp, TBPR, div);
+ stl_cd1400setreg(portp, RCOR, clk);
+ stl_cd1400setreg(portp, RBPR, div);
+ }
+ stl_cd1400setreg(portp, SCHR1, tiosp->c_cc[VSTART]);
+ stl_cd1400setreg(portp, SCHR2, tiosp->c_cc[VSTOP]);
+ stl_cd1400setreg(portp, SCHR3, tiosp->c_cc[VSTART]);
+ stl_cd1400setreg(portp, SCHR4, tiosp->c_cc[VSTOP]);
+ stl_cd1400setreg(portp, RTPR, rtpr);
+ mcor1 = stl_cd1400getreg(portp, MSVR1);
+ if (mcor1 & MSVR1_DCD)
+ portp->sigs |= TIOCM_CD;
+ else
+ portp->sigs &= ~TIOCM_CD;
+ stl_cd1400setreg(portp, SRER, ((srer & ~sreroff) | sreron));
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set the state of the DTR and RTS signals.
+ */
+
+static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts)
+{
+ unsigned char msvr1, msvr2;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400setsignals(portp=%x,dtr=%d,rts=%d)\n",
+ (int) portp, dtr, rts);
+#endif
+
+ msvr1 = 0;
+ msvr2 = 0;
+ if (dtr > 0)
+ msvr1 = MSVR1_DTR;
+ if (rts > 0)
+ msvr2 = MSVR2_RTS;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ if (rts >= 0)
+ stl_cd1400setreg(portp, MSVR2, msvr2);
+ if (dtr >= 0)
+ stl_cd1400setreg(portp, MSVR1, msvr1);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the state of the signals.
+ */
+
+static int stl_cd1400getsignals(stlport_t *portp)
+{
+ unsigned char msvr1, msvr2;
+ unsigned long flags;
+ int sigs;
+
+#ifdef DEBUG
+ printk("stl_cd1400getsignals(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ msvr1 = stl_cd1400getreg(portp, MSVR1);
+ msvr2 = stl_cd1400getreg(portp, MSVR2);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ sigs = 0;
+ sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
+ sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
+ sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
+ sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
+#if 0
+ sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
+ sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
+#else
+ sigs |= TIOCM_DSR;
+#endif
+ return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ * Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char ccr;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+ ccr = 0;
+
+ if (tx == 0)
+ ccr |= CCR_TXDISABLE;
+ else if (tx > 0)
+ ccr |= CCR_TXENABLE;
+ if (rx == 0)
+ ccr |= CCR_RXDISABLE;
+ else if (rx > 0)
+ ccr |= CCR_RXENABLE;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, ccr);
+ stl_cd1400ccrwait(portp);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char sreron, sreroff;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400startrxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ sreron = 0;
+ sreroff = 0;
+ if (tx == 0)
+ sreroff |= (SRER_TXDATA | SRER_TXEMPTY);
+ else if (tx == 1)
+ sreron |= SRER_TXDATA;
+ else if (tx >= 2)
+ sreron |= SRER_TXEMPTY;
+ if (rx == 0)
+ sreroff |= SRER_RXDATA;
+ else if (rx > 0)
+ sreron |= SRER_RXDATA;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER,
+ ((stl_cd1400getreg(portp, SRER) & ~sreroff) | sreron));
+ BRDDISABLE(portp->brdnr);
+ if (tx > 0)
+ set_bit(ASYI_TXBUSY, &portp->istate);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Disable all interrupts from this port.
+ */
+
+static void stl_cd1400disableintrs(stlport_t *portp)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400disableintrs(portp=%x)\n", (int) portp);
+#endif
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER, 0);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400sendbreak(stlport_t *portp, int len)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400setreg(portp, SRER,
+ ((stl_cd1400getreg(portp, SRER) & ~SRER_TXDATA) |
+ SRER_TXEMPTY));
+ BRDDISABLE(portp->brdnr);
+ portp->brklen = len;
+ if (len == 1)
+ portp->stats.txbreaks++;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Take flow control actions...
+ */
+
+static void stl_cd1400flowctrl(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+
+ if (state) {
+ if (tty->termios->c_iflag & IXOFF) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+ portp->stats.rxxon++;
+ stl_cd1400ccrwait(portp);
+ }
+/*
+ * Question: should we return RTS to what it was before? It may
+ * have been set by an ioctl... Suppose not, since if you have
+ * hardware flow control set then it is pretty silly to go and
+ * set the RTS line by hand.
+ */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_cd1400setreg(portp, MCOR1,
+ (stl_cd1400getreg(portp, MCOR1) |
+ FIFO_RTSTHRESHOLD));
+ stl_cd1400setreg(portp, MSVR2, MSVR2_RTS);
+ portp->stats.rxrtson++;
+ }
+ } else {
+ if (tty->termios->c_iflag & IXOFF) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+ portp->stats.rxxoff++;
+ stl_cd1400ccrwait(portp);
+ }
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_cd1400setreg(portp, MCOR1,
+ (stl_cd1400getreg(portp, MCOR1) & 0xf0));
+ stl_cd1400setreg(portp, MSVR2, 0);
+ portp->stats.rxrtsoff++;
+ }
+ }
+
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a flow control character...
+ */
+
+static void stl_cd1400sendflow(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ if (state) {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+ portp->stats.rxxon++;
+ stl_cd1400ccrwait(portp);
+ } else {
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+ portp->stats.rxxoff++;
+ stl_cd1400ccrwait(portp);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400flush(stlport_t *portp)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_cd1400flush(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+ stl_cd1400ccrwait(portp);
+ stl_cd1400setreg(portp, CCR, CCR_TXFLUSHFIFO);
+ stl_cd1400ccrwait(portp);
+ portp->tx.tail = portp->tx.head;
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the current state of data flow on this port. This is only
+ * really interresting when determining if data has fully completed
+ * transmission or not... This is easy for the cd1400, it accurately
+ * maintains the busy port flag.
+ */
+
+static int stl_cd1400datastate(stlport_t *portp)
+{
+#ifdef DEBUG
+ printk("stl_cd1400datastate(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return(0);
+
+ return(test_bit(ASYI_TXBUSY, &portp->istate) ? 1 : 0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for cd1400 EasyIO boards.
+ */
+
+static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase)
+{
+ unsigned char svrtype;
+
+#ifdef DEBUG
+ printk("stl_cd1400eiointr(panelp=%x,iobase=%x)\n",
+ (int) panelp, iobase);
+#endif
+
+ outb(SVRR, iobase);
+ svrtype = inb(iobase + EREG_DATA);
+ if (panelp->nrports > 4) {
+ outb((SVRR + 0x80), iobase);
+ svrtype |= inb(iobase + EREG_DATA);
+ }
+
+ if (svrtype & SVRR_RX)
+ stl_cd1400rxisr(panelp, iobase);
+ else if (svrtype & SVRR_TX)
+ stl_cd1400txisr(panelp, iobase);
+ else if (svrtype & SVRR_MDM)
+ stl_cd1400mdmisr(panelp, iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for cd1400 panels.
+ */
+
+static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase)
+{
+ unsigned char svrtype;
+
+#ifdef DEBUG
+ printk("stl_cd1400echintr(panelp=%x,iobase=%x)\n", (int) panelp,
+ iobase);
+#endif
+
+ outb(SVRR, iobase);
+ svrtype = inb(iobase + EREG_DATA);
+ outb((SVRR + 0x80), iobase);
+ svrtype |= inb(iobase + EREG_DATA);
+ if (svrtype & SVRR_RX)
+ stl_cd1400rxisr(panelp, iobase);
+ else if (svrtype & SVRR_TX)
+ stl_cd1400txisr(panelp, iobase);
+ else if (svrtype & SVRR_MDM)
+ stl_cd1400mdmisr(panelp, iobase);
+}
+
+
+/*****************************************************************************/
+
+/*
+ * Unfortunately we need to handle breaks in the TX data stream, since
+ * this is the only way to generate them on the cd1400.
+ */
+
+static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr)
+{
+ if (portp->brklen == 1) {
+ outb((COR2 + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) | COR2_ETC),
+ (ioaddr + EREG_DATA));
+ outb((TDR + portp->uartaddr), ioaddr);
+ outb(ETC_CMD, (ioaddr + EREG_DATA));
+ outb(ETC_STARTBREAK, (ioaddr + EREG_DATA));
+ outb((SRER + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) & ~(SRER_TXDATA | SRER_TXEMPTY)),
+ (ioaddr + EREG_DATA));
+ return(1);
+ } else if (portp->brklen > 1) {
+ outb((TDR + portp->uartaddr), ioaddr);
+ outb(ETC_CMD, (ioaddr + EREG_DATA));
+ outb(ETC_STOPBREAK, (ioaddr + EREG_DATA));
+ portp->brklen = -1;
+ return(1);
+ } else {
+ outb((COR2 + portp->uartaddr), ioaddr);
+ outb((inb(ioaddr + EREG_DATA) & ~COR2_ETC),
+ (ioaddr + EREG_DATA));
+ portp->brklen = 0;
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Transmit interrupt handler. This has gotta be fast! Handling TX
+ * chars is pretty simple, stuff as many as possible from the TX buffer
+ * into the cd1400 FIFO. Must also handle TX breaks here, since they
+ * are embedded as commands in the data stream. Oh no, had to use a goto!
+ * This could be optimized more, will do when I get time...
+ * In practice it is possible that interrupts are enabled but that the
+ * port has been hung up. Need to handle not having any TX buffer here,
+ * this is done by using the side effect that head and tail will also
+ * be NULL if the buffer has been freed.
+ */
+
+static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ int len, stlen;
+ char *head, *tail;
+ unsigned char ioack, srer;
+
+#ifdef DEBUG
+ printk("stl_cd1400txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+ ioack = inb(ioaddr + EREG_TXACK);
+ if (((ioack & panelp->ackmask) != 0) ||
+ ((ioack & ACK_TYPMASK) != ACK_TYPTX)) {
+ printk("STALLION: bad TX interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+
+/*
+ * Unfortunately we need to handle breaks in the data stream, since
+ * this is the only way to generate them on the cd1400. Do it now if
+ * a break is to be sent.
+ */
+ if (portp->brklen != 0)
+ if (stl_cd1400breakisr(portp, ioaddr))
+ goto stl_txalldone;
+
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((len == 0) || ((len < STL_TXBUFLOW) &&
+ (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+ set_bit(ASYI_TXLOW, &portp->istate);
+ schedule_work(&portp->tqueue);
+ }
+
+ if (len == 0) {
+ outb((SRER + portp->uartaddr), ioaddr);
+ srer = inb(ioaddr + EREG_DATA);
+ if (srer & SRER_TXDATA) {
+ srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY;
+ } else {
+ srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
+ clear_bit(ASYI_TXBUSY, &portp->istate);
+ }
+ outb(srer, (ioaddr + EREG_DATA));
+ } else {
+ len = MIN(len, CD1400_TXFIFOSIZE);
+ portp->stats.txtotal += len;
+ stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+ outb((TDR + portp->uartaddr), ioaddr);
+ outsb((ioaddr + EREG_DATA), tail, stlen);
+ len -= stlen;
+ tail += stlen;
+ if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+ tail = portp->tx.buf;
+ if (len > 0) {
+ outsb((ioaddr + EREG_DATA), tail, len);
+ tail += len;
+ }
+ portp->tx.tail = tail;
+ }
+
+stl_txalldone:
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ * Receive character interrupt handler. Determine if we have good chars
+ * or bad chars and then process appropriately. Good chars are easy
+ * just shove the lot into the RX buffer and set all status byte to 0.
+ * If a bad RX char then process as required. This routine needs to be
+ * fast! In practice it is possible that we get an interrupt on a port
+ * that is closed. This can happen on hangups - since they completely
+ * shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ struct tty_struct *tty;
+ unsigned int ioack, len, buflen;
+ unsigned char status;
+ char ch;
+
+#ifdef DEBUG
+ printk("stl_cd1400rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+ ioack = inb(ioaddr + EREG_RXACK);
+ if ((ioack & panelp->ackmask) != 0) {
+ printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+ tty = portp->tty;
+
+ if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) {
+ outb((RDCR + portp->uartaddr), ioaddr);
+ len = inb(ioaddr + EREG_DATA);
+ if ((tty == (struct tty_struct *) NULL) ||
+ (tty->flip.char_buf_ptr == (char *) NULL) ||
+ ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+ len = MIN(len, sizeof(stl_unwanted));
+ outb((RDSR + portp->uartaddr), ioaddr);
+ insb((ioaddr + EREG_DATA), &stl_unwanted[0], len);
+ portp->stats.rxlost += len;
+ portp->stats.rxtotal += len;
+ } else {
+ len = MIN(len, buflen);
+ if (len > 0) {
+ outb((RDSR + portp->uartaddr), ioaddr);
+ insb((ioaddr + EREG_DATA), tty->flip.char_buf_ptr, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.flag_buf_ptr += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.count += len;
+ tty_schedule_flip(tty);
+ portp->stats.rxtotal += len;
+ }
+ }
+ } else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) {
+ outb((RDSR + portp->uartaddr), ioaddr);
+ status = inb(ioaddr + EREG_DATA);
+ ch = inb(ioaddr + EREG_DATA);
+ if (status & ST_PARITY)
+ portp->stats.rxparity++;
+ if (status & ST_FRAMING)
+ portp->stats.rxframing++;
+ if (status & ST_OVERRUN)
+ portp->stats.rxoverrun++;
+ if (status & ST_BREAK)
+ portp->stats.rxbreaks++;
+ if (status & ST_SCHARMASK) {
+ if ((status & ST_SCHARMASK) == ST_SCHAR1)
+ portp->stats.txxon++;
+ if ((status & ST_SCHARMASK) == ST_SCHAR2)
+ portp->stats.txxoff++;
+ goto stl_rxalldone;
+ }
+ if ((tty != (struct tty_struct *) NULL) &&
+ ((portp->rxignoremsk & status) == 0)) {
+ if (portp->rxmarkmsk & status) {
+ if (status & ST_BREAK) {
+ status = TTY_BREAK;
+ if (portp->flags & ASYNC_SAK) {
+ do_SAK(tty);
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ }
+ } else if (status & ST_PARITY) {
+ status = TTY_PARITY;
+ } else if (status & ST_FRAMING) {
+ status = TTY_FRAME;
+ } else if(status & ST_OVERRUN) {
+ status = TTY_OVERRUN;
+ } else {
+ status = 0;
+ }
+ } else {
+ status = 0;
+ }
+ if (tty->flip.char_buf_ptr != (char *) NULL) {
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.flag_buf_ptr++ = status;
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ }
+ tty_schedule_flip(tty);
+ }
+ }
+ } else {
+ printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+ return;
+ }
+
+stl_rxalldone:
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ * Modem interrupt handler. The is called when the modem signal line
+ * (DCD) has changed state. Leave most of the work to the off-level
+ * processing routine.
+ */
+
+static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr)
+{
+ stlport_t *portp;
+ unsigned int ioack;
+ unsigned char misr;
+
+#ifdef DEBUG
+ printk("stl_cd1400mdmisr(panelp=%x)\n", (int) panelp);
+#endif
+
+ ioack = inb(ioaddr + EREG_MDACK);
+ if (((ioack & panelp->ackmask) != 0) ||
+ ((ioack & ACK_TYPMASK) != ACK_TYPMDM)) {
+ printk("STALLION: bad MODEM interrupt ack value=%x\n", ioack);
+ return;
+ }
+ portp = panelp->ports[(ioack >> 3)];
+
+ outb((MISR + portp->uartaddr), ioaddr);
+ misr = inb(ioaddr + EREG_DATA);
+ if (misr & MISR_DCD) {
+ set_bit(ASYI_DCDCHANGE, &portp->istate);
+ schedule_work(&portp->tqueue);
+ portp->stats.modem++;
+ }
+
+ outb((EOSRR + portp->uartaddr), ioaddr);
+ outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+/* SC26198 HARDWARE FUNCTIONS */
+/*****************************************************************************/
+
+/*
+ * These functions get/set/update the registers of the sc26198 UARTs.
+ * Access to the sc26198 registers is via an address/data io port pair.
+ * (Maybe should make this inline...)
+ */
+
+static int stl_sc26198getreg(stlport_t *portp, int regnr)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ return(inb(portp->ioaddr + XP_DATA));
+}
+
+static void stl_sc26198setreg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ outb(value, (portp->ioaddr + XP_DATA));
+}
+
+static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value)
+{
+ outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+ if (inb(portp->ioaddr + XP_DATA) != value) {
+ outb(value, (portp->ioaddr + XP_DATA));
+ return(1);
+ }
+ return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ * Functions to get and set the sc26198 global registers.
+ */
+
+static int stl_sc26198getglobreg(stlport_t *portp, int regnr)
+{
+ outb(regnr, (portp->ioaddr + XP_ADDR));
+ return(inb(portp->ioaddr + XP_DATA));
+}
+
+#if 0
+static void stl_sc26198setglobreg(stlport_t *portp, int regnr, int value)
+{
+ outb(regnr, (portp->ioaddr + XP_ADDR));
+ outb(value, (portp->ioaddr + XP_DATA));
+}
+#endif
+
+/*****************************************************************************/
+
+/*
+ * Inbitialize the UARTs in a panel. We don't care what sort of board
+ * these ports are on - since the port io registers are almost
+ * identical when dealing with ports.
+ */
+
+static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+ int chipmask, i;
+ int nrchips, ioaddr;
+
+#ifdef DEBUG
+ printk("stl_sc26198panelinit(brdp=%x,panelp=%x)\n",
+ (int) brdp, (int) panelp);
+#endif
+
+ BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ * Check that each chip is present and started up OK.
+ */
+ chipmask = 0;
+ nrchips = (panelp->nrports + 4) / SC26198_PORTS;
+ if (brdp->brdtype == BRD_ECHPCI)
+ outb(panelp->pagenr, brdp->ioctrl);
+
+ for (i = 0; (i < nrchips); i++) {
+ ioaddr = panelp->iobase + (i * 4);
+ outb(SCCR, (ioaddr + XP_ADDR));
+ outb(CR_RESETALL, (ioaddr + XP_DATA));
+ outb(TSTR, (ioaddr + XP_ADDR));
+ if (inb(ioaddr + XP_DATA) != 0) {
+ printk("STALLION: sc26198 not responding, "
+ "brd=%d panel=%d chip=%d\n",
+ panelp->brdnr, panelp->panelnr, i);
+ continue;
+ }
+ chipmask |= (0x1 << i);
+ outb(GCCR, (ioaddr + XP_ADDR));
+ outb(GCCR_IVRTYPCHANACK, (ioaddr + XP_DATA));
+ outb(WDTRCR, (ioaddr + XP_ADDR));
+ outb(0xff, (ioaddr + XP_DATA));
+ }
+
+ BRDDISABLE(panelp->brdnr);
+ return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ * Initialize hardware specific port registers.
+ */
+
+static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#ifdef DEBUG
+ printk("stl_sc26198portinit(brdp=%x,panelp=%x,portp=%x)\n",
+ (int) brdp, (int) panelp, (int) portp);
+#endif
+
+ if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+ (portp == (stlport_t *) NULL))
+ return;
+
+ portp->ioaddr = panelp->iobase + ((portp->portnr < 8) ? 0 : 4);
+ portp->uartaddr = (portp->portnr & 0x07) << 4;
+ portp->pagenr = panelp->pagenr;
+ portp->hwid = 0x1;
+
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IOPCR, IOPCR_SETSIGS);
+ BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set up the sc26198 registers for a port based on the termios port
+ * settings.
+ */
+
+static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp)
+{
+ stlbrd_t *brdp;
+ unsigned long flags;
+ unsigned int baudrate;
+ unsigned char mr0, mr1, mr2, clk;
+ unsigned char imron, imroff, iopr, ipr;
+
+ mr0 = 0;
+ mr1 = 0;
+ mr2 = 0;
+ clk = 0;
+ iopr = 0;
+ imron = 0;
+ imroff = 0;
+
+ brdp = stl_brds[portp->brdnr];
+ if (brdp == (stlbrd_t *) NULL)
+ return;
+
+/*
+ * Set up the RX char ignore mask with those RX error types we
+ * can ignore.
+ */
+ portp->rxignoremsk = 0;
+ if (tiosp->c_iflag & IGNPAR)
+ portp->rxignoremsk |= (SR_RXPARITY | SR_RXFRAMING |
+ SR_RXOVERRUN);
+ if (tiosp->c_iflag & IGNBRK)
+ portp->rxignoremsk |= SR_RXBREAK;
+
+ portp->rxmarkmsk = SR_RXOVERRUN;
+ if (tiosp->c_iflag & (INPCK | PARMRK))
+ portp->rxmarkmsk |= (SR_RXPARITY | SR_RXFRAMING);
+ if (tiosp->c_iflag & BRKINT)
+ portp->rxmarkmsk |= SR_RXBREAK;
+
+/*
+ * Go through the char size, parity and stop bits and set all the
+ * option register appropriately.
+ */
+ switch (tiosp->c_cflag & CSIZE) {
+ case CS5:
+ mr1 |= MR1_CS5;
+ break;
+ case CS6:
+ mr1 |= MR1_CS6;
+ break;
+ case CS7:
+ mr1 |= MR1_CS7;
+ break;
+ default:
+ mr1 |= MR1_CS8;
+ break;
+ }
+
+ if (tiosp->c_cflag & CSTOPB)
+ mr2 |= MR2_STOP2;
+ else
+ mr2 |= MR2_STOP1;
+
+ if (tiosp->c_cflag & PARENB) {
+ if (tiosp->c_cflag & PARODD)
+ mr1 |= (MR1_PARENB | MR1_PARODD);
+ else
+ mr1 |= (MR1_PARENB | MR1_PAREVEN);
+ } else {
+ mr1 |= MR1_PARNONE;
+ }
+
+ mr1 |= MR1_ERRBLOCK;
+
+/*
+ * Set the RX FIFO threshold at 8 chars. This gives a bit of breathing
+ * space for hardware flow control and the like. This should be set to
+ * VMIN.
+ */
+ mr2 |= MR2_RXFIFOHALF;
+
+/*
+ * Calculate the baud rate timers. For now we will just assume that
+ * the input and output baud are the same. The sc26198 has a fixed
+ * baud rate table, so only discrete baud rates possible.
+ */
+ baudrate = tiosp->c_cflag & CBAUD;
+ if (baudrate & CBAUDEX) {
+ baudrate &= ~CBAUDEX;
+ if ((baudrate < 1) || (baudrate > 4))
+ tiosp->c_cflag &= ~CBAUDEX;
+ else
+ baudrate += 15;
+ }
+ baudrate = stl_baudrates[baudrate];
+ if ((tiosp->c_cflag & CBAUD) == B38400) {
+ if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ baudrate = 57600;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ baudrate = 115200;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ baudrate = 230400;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ baudrate = 460800;
+ else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+ baudrate = (portp->baud_base / portp->custom_divisor);
+ }
+ if (baudrate > STL_SC26198MAXBAUD)
+ baudrate = STL_SC26198MAXBAUD;
+
+ if (baudrate > 0) {
+ for (clk = 0; (clk < SC26198_NRBAUDS); clk++) {
+ if (baudrate <= sc26198_baudtable[clk])
+ break;
+ }
+ }
+
+/*
+ * Check what form of modem signaling is required and set it up.
+ */
+ if (tiosp->c_cflag & CLOCAL) {
+ portp->flags &= ~ASYNC_CHECK_CD;
+ } else {
+ iopr |= IOPR_DCDCOS;
+ imron |= IR_IOPORT;
+ portp->flags |= ASYNC_CHECK_CD;
+ }
+
+/*
+ * Setup sc26198 enhanced modes if we can. In particular we want to
+ * handle as much of the flow control as possible automatically. As
+ * well as saving a few CPU cycles it will also greatly improve flow
+ * control reliability.
+ */
+ if (tiosp->c_iflag & IXON) {
+ mr0 |= MR0_SWFTX | MR0_SWFT;
+ imron |= IR_XONXOFF;
+ } else {
+ imroff |= IR_XONXOFF;
+ }
+ if (tiosp->c_iflag & IXOFF)
+ mr0 |= MR0_SWFRX;
+
+ if (tiosp->c_cflag & CRTSCTS) {
+ mr2 |= MR2_AUTOCTS;
+ mr1 |= MR1_AUTORTS;
+ }
+
+/*
+ * All sc26198 register values calculated so go through and set
+ * them all up.
+ */
+
+#ifdef DEBUG
+ printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+ portp->portnr, portp->panelnr, portp->brdnr);
+ printk(" mr0=%x mr1=%x mr2=%x clk=%x\n", mr0, mr1, mr2, clk);
+ printk(" iopr=%x imron=%x imroff=%x\n", iopr, imron, imroff);
+ printk(" schr1=%x schr2=%x schr3=%x schr4=%x\n",
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+ tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IMR, 0);
+ stl_sc26198updatereg(portp, MR0, mr0);
+ stl_sc26198updatereg(portp, MR1, mr1);
+ stl_sc26198setreg(portp, SCCR, CR_RXERRBLOCK);
+ stl_sc26198updatereg(portp, MR2, mr2);
+ stl_sc26198updatereg(portp, IOPIOR,
+ ((stl_sc26198getreg(portp, IOPIOR) & ~IPR_CHANGEMASK) | iopr));
+
+ if (baudrate > 0) {
+ stl_sc26198setreg(portp, TXCSR, clk);
+ stl_sc26198setreg(portp, RXCSR, clk);
+ }
+
+ stl_sc26198setreg(portp, XONCR, tiosp->c_cc[VSTART]);
+ stl_sc26198setreg(portp, XOFFCR, tiosp->c_cc[VSTOP]);
+
+ ipr = stl_sc26198getreg(portp, IPR);
+ if (ipr & IPR_DCD)
+ portp->sigs &= ~TIOCM_CD;
+ else
+ portp->sigs |= TIOCM_CD;
+
+ portp->imr = (portp->imr & ~imroff) | imron;
+ stl_sc26198setreg(portp, IMR, portp->imr);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Set the state of the DTR and RTS signals.
+ */
+
+static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts)
+{
+ unsigned char iopioron, iopioroff;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198setsignals(portp=%x,dtr=%d,rts=%d)\n",
+ (int) portp, dtr, rts);
+#endif
+
+ iopioron = 0;
+ iopioroff = 0;
+ if (dtr == 0)
+ iopioroff |= IPR_DTR;
+ else if (dtr > 0)
+ iopioron |= IPR_DTR;
+ if (rts == 0)
+ iopioroff |= IPR_RTS;
+ else if (rts > 0)
+ iopioron |= IPR_RTS;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IOPIOR,
+ ((stl_sc26198getreg(portp, IOPIOR) & ~iopioroff) | iopioron));
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the state of the signals.
+ */
+
+static int stl_sc26198getsignals(stlport_t *portp)
+{
+ unsigned char ipr;
+ unsigned long flags;
+ int sigs;
+
+#ifdef DEBUG
+ printk("stl_sc26198getsignals(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ ipr = stl_sc26198getreg(portp, IPR);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ sigs = 0;
+ sigs |= (ipr & IPR_DCD) ? 0 : TIOCM_CD;
+ sigs |= (ipr & IPR_CTS) ? 0 : TIOCM_CTS;
+ sigs |= (ipr & IPR_DTR) ? 0: TIOCM_DTR;
+ sigs |= (ipr & IPR_RTS) ? 0: TIOCM_RTS;
+ sigs |= TIOCM_DSR;
+ return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ * Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char ccr;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ ccr = portp->crenable;
+ if (tx == 0)
+ ccr &= ~CR_TXENABLE;
+ else if (tx > 0)
+ ccr |= CR_TXENABLE;
+ if (rx == 0)
+ ccr &= ~CR_RXENABLE;
+ else if (rx > 0)
+ ccr |= CR_RXENABLE;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, SCCR, ccr);
+ BRDDISABLE(portp->brdnr);
+ portp->crenable = ccr;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx)
+{
+ unsigned char imr;
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198startrxtx(portp=%x,rx=%d,tx=%d)\n",
+ (int) portp, rx, tx);
+#endif
+
+ imr = portp->imr;
+ if (tx == 0)
+ imr &= ~IR_TXRDY;
+ else if (tx == 1)
+ imr |= IR_TXRDY;
+ if (rx == 0)
+ imr &= ~(IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG);
+ else if (rx > 0)
+ imr |= IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, IMR, imr);
+ BRDDISABLE(portp->brdnr);
+ portp->imr = imr;
+ if (tx > 0)
+ set_bit(ASYI_TXBUSY, &portp->istate);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Disable all interrupts from this port.
+ */
+
+static void stl_sc26198disableintrs(stlport_t *portp)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198disableintrs(portp=%x)\n", (int) portp);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ portp->imr = 0;
+ stl_sc26198setreg(portp, IMR, 0);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198sendbreak(stlport_t *portp, int len)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ if (len == 1) {
+ stl_sc26198setreg(portp, SCCR, CR_TXSTARTBREAK);
+ portp->stats.txbreaks++;
+ } else {
+ stl_sc26198setreg(portp, SCCR, CR_TXSTOPBREAK);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Take flow control actions...
+ */
+
+static void stl_sc26198flowctrl(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ unsigned char mr0;
+
+#ifdef DEBUG
+ printk("stl_sc26198flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+
+ if (state) {
+ if (tty->termios->c_iflag & IXOFF) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+ mr0 |= MR0_SWFRX;
+ portp->stats.rxxon++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+/*
+ * Question: should we return RTS to what it was before? It may
+ * have been set by an ioctl... Suppose not, since if you have
+ * hardware flow control set then it is pretty silly to go and
+ * set the RTS line by hand.
+ */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_sc26198setreg(portp, MR1,
+ (stl_sc26198getreg(portp, MR1) | MR1_AUTORTS));
+ stl_sc26198setreg(portp, IOPIOR,
+ (stl_sc26198getreg(portp, IOPIOR) | IOPR_RTS));
+ portp->stats.rxrtson++;
+ }
+ } else {
+ if (tty->termios->c_iflag & IXOFF) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+ mr0 &= ~MR0_SWFRX;
+ portp->stats.rxxoff++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+ if (tty->termios->c_cflag & CRTSCTS) {
+ stl_sc26198setreg(portp, MR1,
+ (stl_sc26198getreg(portp, MR1) & ~MR1_AUTORTS));
+ stl_sc26198setreg(portp, IOPIOR,
+ (stl_sc26198getreg(portp, IOPIOR) & ~IOPR_RTS));
+ portp->stats.rxrtsoff++;
+ }
+ }
+
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Send a flow control character.
+ */
+
+static void stl_sc26198sendflow(stlport_t *portp, int state)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ unsigned char mr0;
+
+#ifdef DEBUG
+ printk("stl_sc26198sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+ tty = portp->tty;
+ if (tty == (struct tty_struct *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ if (state) {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+ mr0 |= MR0_SWFRX;
+ portp->stats.rxxon++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ } else {
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+ mr0 &= ~MR0_SWFRX;
+ portp->stats.rxxoff++;
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ }
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198flush(stlport_t *portp)
+{
+ unsigned long flags;
+
+#ifdef DEBUG
+ printk("stl_sc26198flush(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ stl_sc26198setreg(portp, SCCR, CR_TXRESET);
+ stl_sc26198setreg(portp, SCCR, portp->crenable);
+ BRDDISABLE(portp->brdnr);
+ portp->tx.tail = portp->tx.head;
+ restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ * Return the current state of data flow on this port. This is only
+ * really interresting when determining if data has fully completed
+ * transmission or not... The sc26198 interrupt scheme cannot
+ * determine when all data has actually drained, so we need to
+ * check the port statusy register to be sure.
+ */
+
+static int stl_sc26198datastate(stlport_t *portp)
+{
+ unsigned long flags;
+ unsigned char sr;
+
+#ifdef DEBUG
+ printk("stl_sc26198datastate(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return(0);
+ if (test_bit(ASYI_TXBUSY, &portp->istate))
+ return(1);
+
+ save_flags(flags);
+ cli();
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ sr = stl_sc26198getreg(portp, SR);
+ BRDDISABLE(portp->brdnr);
+ restore_flags(flags);
+
+ return((sr & SR_TXEMPTY) ? 0 : 1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Delay for a small amount of time, to give the sc26198 a chance
+ * to process a command...
+ */
+
+static void stl_sc26198wait(stlport_t *portp)
+{
+ int i;
+
+#ifdef DEBUG
+ printk("stl_sc26198wait(portp=%x)\n", (int) portp);
+#endif
+
+ if (portp == (stlport_t *) NULL)
+ return;
+
+ for (i = 0; (i < 20); i++)
+ stl_sc26198getglobreg(portp, TSTR);
+}
+
+/*****************************************************************************/
+
+/*
+ * If we are TX flow controlled and in IXANY mode then we may
+ * need to unflow control here. We gotta do this because of the
+ * automatic flow control modes of the sc26198.
+ */
+
+static inline void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty)
+{
+ unsigned char mr0;
+
+ mr0 = stl_sc26198getreg(portp, MR0);
+ stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+ stl_sc26198setreg(portp, SCCR, CR_HOSTXON);
+ stl_sc26198wait(portp);
+ stl_sc26198setreg(portp, MR0, mr0);
+ clear_bit(ASYI_TXFLOWED, &portp->istate);
+}
+
+/*****************************************************************************/
+
+/*
+ * Interrupt service routine for sc26198 panels.
+ */
+
+static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase)
+{
+ stlport_t *portp;
+ unsigned int iack;
+
+/*
+ * Work around bug in sc26198 chip... Cannot have A6 address
+ * line of UART high, else iack will be returned as 0.
+ */
+ outb(0, (iobase + 1));
+
+ iack = inb(iobase + XP_IACK);
+ portp = panelp->ports[(iack & IVR_CHANMASK) + ((iobase & 0x4) << 1)];
+
+ if (iack & IVR_RXDATA)
+ stl_sc26198rxisr(portp, iack);
+ else if (iack & IVR_TXDATA)
+ stl_sc26198txisr(portp);
+ else
+ stl_sc26198otherisr(portp, iack);
+}
+
+/*****************************************************************************/
+
+/*
+ * Transmit interrupt handler. This has gotta be fast! Handling TX
+ * chars is pretty simple, stuff as many as possible from the TX buffer
+ * into the sc26198 FIFO.
+ * In practice it is possible that interrupts are enabled but that the
+ * port has been hung up. Need to handle not having any TX buffer here,
+ * this is done by using the side effect that head and tail will also
+ * be NULL if the buffer has been freed.
+ */
+
+static void stl_sc26198txisr(stlport_t *portp)
+{
+ unsigned int ioaddr;
+ unsigned char mr0;
+ int len, stlen;
+ char *head, *tail;
+
+#ifdef DEBUG
+ printk("stl_sc26198txisr(portp=%x)\n", (int) portp);
+#endif
+
+ ioaddr = portp->ioaddr;
+ head = portp->tx.head;
+ tail = portp->tx.tail;
+ len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+ if ((len == 0) || ((len < STL_TXBUFLOW) &&
+ (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+ set_bit(ASYI_TXLOW, &portp->istate);
+ schedule_work(&portp->tqueue);
+ }
+
+ if (len == 0) {
+ outb((MR0 | portp->uartaddr), (ioaddr + XP_ADDR));
+ mr0 = inb(ioaddr + XP_DATA);
+ if ((mr0 & MR0_TXMASK) == MR0_TXEMPTY) {
+ portp->imr &= ~IR_TXRDY;
+ outb((IMR | portp->uartaddr), (ioaddr + XP_ADDR));
+ outb(portp->imr, (ioaddr + XP_DATA));
+ clear_bit(ASYI_TXBUSY, &portp->istate);
+ } else {
+ mr0 |= ((mr0 & ~MR0_TXMASK) | MR0_TXEMPTY);
+ outb(mr0, (ioaddr + XP_DATA));
+ }
+ } else {
+ len = MIN(len, SC26198_TXFIFOSIZE);
+ portp->stats.txtotal += len;
+ stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+ outb(GTXFIFO, (ioaddr + XP_ADDR));
+ outsb((ioaddr + XP_DATA), tail, stlen);
+ len -= stlen;
+ tail += stlen;
+ if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+ tail = portp->tx.buf;
+ if (len > 0) {
+ outsb((ioaddr + XP_DATA), tail, len);
+ tail += len;
+ }
+ portp->tx.tail = tail;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Receive character interrupt handler. Determine if we have good chars
+ * or bad chars and then process appropriately. Good chars are easy
+ * just shove the lot into the RX buffer and set all status byte to 0.
+ * If a bad RX char then process as required. This routine needs to be
+ * fast! In practice it is possible that we get an interrupt on a port
+ * that is closed. This can happen on hangups - since they completely
+ * shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_sc26198rxisr(stlport_t *portp, unsigned int iack)
+{
+ struct tty_struct *tty;
+ unsigned int len, buflen, ioaddr;
+
+#ifdef DEBUG
+ printk("stl_sc26198rxisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+ tty = portp->tty;
+ ioaddr = portp->ioaddr;
+ outb(GIBCR, (ioaddr + XP_ADDR));
+ len = inb(ioaddr + XP_DATA) + 1;
+
+ if ((iack & IVR_TYPEMASK) == IVR_RXDATA) {
+ if ((tty == (struct tty_struct *) NULL) ||
+ (tty->flip.char_buf_ptr == (char *) NULL) ||
+ ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+ len = MIN(len, sizeof(stl_unwanted));
+ outb(GRXFIFO, (ioaddr + XP_ADDR));
+ insb((ioaddr + XP_DATA), &stl_unwanted[0], len);
+ portp->stats.rxlost += len;
+ portp->stats.rxtotal += len;
+ } else {
+ len = MIN(len, buflen);
+ if (len > 0) {
+ outb(GRXFIFO, (ioaddr + XP_ADDR));
+ insb((ioaddr + XP_DATA), tty->flip.char_buf_ptr, len);
+ memset(tty->flip.flag_buf_ptr, 0, len);
+ tty->flip.flag_buf_ptr += len;
+ tty->flip.char_buf_ptr += len;
+ tty->flip.count += len;
+ tty_schedule_flip(tty);
+ portp->stats.rxtotal += len;
+ }
+ }
+ } else {
+ stl_sc26198rxbadchars(portp);
+ }
+
+/*
+ * If we are TX flow controlled and in IXANY mode then we may need
+ * to unflow control here. We gotta do this because of the automatic
+ * flow control modes of the sc26198.
+ */
+ if (test_bit(ASYI_TXFLOWED, &portp->istate)) {
+ if ((tty != (struct tty_struct *) NULL) &&
+ (tty->termios != (struct termios *) NULL) &&
+ (tty->termios->c_iflag & IXANY)) {
+ stl_sc26198txunflow(portp, tty);
+ }
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Process an RX bad character.
+ */
+
+static inline void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch)
+{
+ struct tty_struct *tty;
+ unsigned int ioaddr;
+
+ tty = portp->tty;
+ ioaddr = portp->ioaddr;
+
+ if (status & SR_RXPARITY)
+ portp->stats.rxparity++;
+ if (status & SR_RXFRAMING)
+ portp->stats.rxframing++;
+ if (status & SR_RXOVERRUN)
+ portp->stats.rxoverrun++;
+ if (status & SR_RXBREAK)
+ portp->stats.rxbreaks++;
+
+ if ((tty != (struct tty_struct *) NULL) &&
+ ((portp->rxignoremsk & status) == 0)) {
+ if (portp->rxmarkmsk & status) {
+ if (status & SR_RXBREAK) {
+ status = TTY_BREAK;
+ if (portp->flags & ASYNC_SAK) {
+ do_SAK(tty);
+ BRDENABLE(portp->brdnr, portp->pagenr);
+ }
+ } else if (status & SR_RXPARITY) {
+ status = TTY_PARITY;
+ } else if (status & SR_RXFRAMING) {
+ status = TTY_FRAME;
+ } else if(status & SR_RXOVERRUN) {
+ status = TTY_OVERRUN;
+ } else {
+ status = 0;
+ }
+ } else {
+ status = 0;
+ }
+
+ if (tty->flip.char_buf_ptr != (char *) NULL) {
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.flag_buf_ptr++ = status;
+ *tty->flip.char_buf_ptr++ = ch;
+ tty->flip.count++;
+ }
+ tty_schedule_flip(tty);
+ }
+
+ if (status == 0)
+ portp->stats.rxtotal++;
+ }
+}
+
+/*****************************************************************************/
+
+/*
+ * Process all characters in the RX FIFO of the UART. Check all char
+ * status bytes as well, and process as required. We need to check
+ * all bytes in the FIFO, in case some more enter the FIFO while we
+ * are here. To get the exact character error type we need to switch
+ * into CHAR error mode (that is why we need to make sure we empty
+ * the FIFO).
+ */
+
+static void stl_sc26198rxbadchars(stlport_t *portp)
+{
+ unsigned char status, mr1;
+ char ch;
+
+/*
+ * To get the precise error type for each character we must switch
+ * back into CHAR error mode.
+ */
+ mr1 = stl_sc26198getreg(portp, MR1);
+ stl_sc26198setreg(portp, MR1, (mr1 & ~MR1_ERRBLOCK));
+
+ while ((status = stl_sc26198getreg(portp, SR)) & SR_RXRDY) {
+ stl_sc26198setreg(portp, SCCR, CR_CLEARRXERR);
+ ch = stl_sc26198getreg(portp, RXFIFO);
+ stl_sc26198rxbadch(portp, status, ch);
+ }
+
+/*
+ * To get correct interrupt class we must switch back into BLOCK
+ * error mode.
+ */
+ stl_sc26198setreg(portp, MR1, mr1);
+}
+
+/*****************************************************************************/
+
+/*
+ * Other interrupt handler. This includes modem signals, flow
+ * control actions, etc. Most stuff is left to off-level interrupt
+ * processing time.
+ */
+
+static void stl_sc26198otherisr(stlport_t *portp, unsigned int iack)
+{
+ unsigned char cir, ipr, xisr;
+
+#ifdef DEBUG
+ printk("stl_sc26198otherisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+ cir = stl_sc26198getglobreg(portp, CIR);
+
+ switch (cir & CIR_SUBTYPEMASK) {
+ case CIR_SUBCOS:
+ ipr = stl_sc26198getreg(portp, IPR);
+ if (ipr & IPR_DCDCHANGE) {
+ set_bit(ASYI_DCDCHANGE, &portp->istate);
+ schedule_work(&portp->tqueue);
+ portp->stats.modem++;
+ }
+ break;
+ case CIR_SUBXONXOFF:
+ xisr = stl_sc26198getreg(portp, XISR);
+ if (xisr & XISR_RXXONGOT) {
+ set_bit(ASYI_TXFLOWED, &portp->istate);
+ portp->stats.txxoff++;
+ }
+ if (xisr & XISR_RXXOFFGOT) {
+ clear_bit(ASYI_TXFLOWED, &portp->istate);
+ portp->stats.txxon++;
+ }
+ break;
+ case CIR_SUBBREAK:
+ stl_sc26198setreg(portp, SCCR, CR_BREAKRESET);
+ stl_sc26198rxbadchars(portp);
+ break;
+ default:
+ break;
+ }
+}
+
+/*****************************************************************************/
diff --git a/drivers/char/sx.c b/drivers/char/sx.c
new file mode 100644
index 000000000000..3ad758a9a1dc
--- /dev/null
+++ b/drivers/char/sx.c
@@ -0,0 +1,2621 @@
+
+/* sx.c -- driver for the Specialix SX series cards.
+ *
+ * This driver will also support the older SI, and XIO cards.
+ *
+ *
+ * (C) 1998 - 2004 R.E.Wolff@BitWizard.nl
+ *
+ * Simon Allen (simonallen@cix.compulink.co.uk) wrote a previous
+ * version of this driver. Some fragments may have been copied. (none
+ * yet :-)
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact support@specialix.co.uk if you require
+ * support. But please read the documentation (sx.txt) first.
+ *
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ * Revision history:
+ * $Log: sx.c,v $
+ * Revision 1.33 2000/03/09 10:00:00 pvdl,wolff
+ * - Fixed module and port counting
+ * - Fixed signal handling
+ * - Fixed an Ooops
+ *
+ * Revision 1.32 2000/03/07 09:00:00 wolff,pvdl
+ * - Fixed some sx_dprintk typos
+ * - added detection for an invalid board/module configuration
+ *
+ * Revision 1.31 2000/03/06 12:00:00 wolff,pvdl
+ * - Added support for EISA
+ *
+ * Revision 1.30 2000/01/21 17:43:06 wolff
+ * - Added support for SX+
+ *
+ * Revision 1.26 1999/08/05 15:22:14 wolff
+ * - Port to 2.3.x
+ * - Reformatted to Linus' liking.
+ *
+ * Revision 1.25 1999/07/30 14:24:08 wolff
+ * Had accidentally left "gs_debug" set to "-1" instead of "off" (=0).
+ *
+ * Revision 1.24 1999/07/28 09:41:52 wolff
+ * - I noticed the remark about use-count straying in sx.txt. I checked
+ * sx_open, and found a few places where that could happen. I hope it's
+ * fixed now.
+ *
+ * Revision 1.23 1999/07/28 08:56:06 wolff
+ * - Fixed crash when sx_firmware run twice.
+ * - Added sx_slowpoll as a module parameter (I guess nobody really wanted
+ * to change it from the default... )
+ * - Fixed a stupid editing problem I introduced in 1.22.
+ * - Fixed dropping characters on a termios change.
+ *
+ * Revision 1.22 1999/07/26 21:01:43 wolff
+ * Russell Brown noticed that I had overlooked 4 out of six modem control
+ * signals in sx_getsignals. Ooops.
+ *
+ * Revision 1.21 1999/07/23 09:11:33 wolff
+ * I forgot to free dynamically allocated memory when the driver is unloaded.
+ *
+ * Revision 1.20 1999/07/20 06:25:26 wolff
+ * The "closing wait" wasn't honoured. Thanks to James Griffiths for
+ * reporting this.
+ *
+ * Revision 1.19 1999/07/11 08:59:59 wolff
+ * Fixed an oops in close, when an open was pending. Changed the memtest
+ * a bit. Should also test the board in word-mode, however my card fails the
+ * memtest then. I still have to figure out what is wrong...
+ *
+ * Revision 1.18 1999/06/10 09:38:42 wolff
+ * Changed the format of the firmware revision from %04x to %x.%02x .
+ *
+ * Revision 1.17 1999/06/04 09:44:35 wolff
+ * fixed problem: reference to pci stuff when config_pci was off...
+ * Thanks to Jorge Novo for noticing this.
+ *
+ * Revision 1.16 1999/06/02 08:30:15 wolff
+ * added/removed the workaround for the DCD bug in the Firmware.
+ * A bit more debugging code to locate that...
+ *
+ * Revision 1.15 1999/06/01 11:35:30 wolff
+ * when DCD is left low (floating?), on TA's the firmware first tells us
+ * that DCD is high, but after a short while suddenly comes to the
+ * conclusion that it is low. All this would be fine, if it weren't that
+ * Unix requires us to send a "hangup" signal in that case. This usually
+ * all happens BEFORE the program has had a chance to ioctl the device
+ * into clocal mode..
+ *
+ * Revision 1.14 1999/05/25 11:18:59 wolff
+ * Added PCI-fix.
+ * Added checks for return code of sx_sendcommand.
+ * Don't issue "reconfig" if port isn't open yet. (bit us on TA modules...)
+ *
+ * Revision 1.13 1999/04/29 15:18:01 wolff
+ * Fixed an "oops" that showed on SuSE 6.0 systems.
+ * Activate DTR again after stty 0.
+ *
+ * Revision 1.12 1999/04/29 07:49:52 wolff
+ * Improved "stty 0" handling a bit. (used to change baud to 9600 assuming
+ * the connection would be dropped anyway. That is not always the case,
+ * and confuses people).
+ * Told the card to always monitor the modem signals.
+ * Added support for dynamic gs_debug adjustments.
+ * Now tells the rest of the system the number of ports.
+ *
+ * Revision 1.11 1999/04/24 11:11:30 wolff
+ * Fixed two stupid typos in the memory test.
+ *
+ * Revision 1.10 1999/04/24 10:53:39 wolff
+ * Added some of Christian's suggestions.
+ * Fixed an HW_COOK_IN bug (ISIG was not in I_OTHER. We used to trust the
+ * card to send the signal to the process.....)
+ *
+ * Revision 1.9 1999/04/23 07:26:38 wolff
+ * Included Christian Lademann's 2.0 compile-warning fixes and interrupt
+ * assignment redesign.
+ * Cleanup of some other stuff.
+ *
+ * Revision 1.8 1999/04/16 13:05:30 wolff
+ * fixed a DCD change unnoticed bug.
+ *
+ * Revision 1.7 1999/04/14 22:19:51 wolff
+ * Fixed typo that showed up in 2.0.x builds (get_user instead of Get_user!)
+ *
+ * Revision 1.6 1999/04/13 18:40:20 wolff
+ * changed misc-minor to 161, as assigned by HPA.
+ *
+ * Revision 1.5 1999/04/13 15:12:25 wolff
+ * Fixed use-count leak when "hangup" occurred.
+ * Added workaround for a stupid-PCIBIOS bug.
+ *
+ *
+ * Revision 1.4 1999/04/01 22:47:40 wolff
+ * Fixed < 1M linux-2.0 problem.
+ * (vremap isn't compatible with ioremap in that case)
+ *
+ * Revision 1.3 1999/03/31 13:45:45 wolff
+ * Firmware loading is now done through a separate IOCTL.
+ *
+ * Revision 1.2 1999/03/28 12:22:29 wolff
+ * rcs cleanup
+ *
+ * Revision 1.1 1999/03/28 12:10:34 wolff
+ * Readying for release on 2.0.x (sorry David, 1.01 becomes 1.1 for RCS).
+ *
+ * Revision 0.12 1999/03/28 09:20:10 wolff
+ * Fixed problem in 0.11, continueing cleanup.
+ *
+ * Revision 0.11 1999/03/28 08:46:44 wolff
+ * cleanup. Not good.
+ *
+ * Revision 0.10 1999/03/28 08:09:43 wolff
+ * Fixed loosing characters on close.
+ *
+ * Revision 0.9 1999/03/21 22:52:01 wolff
+ * Ported back to 2.2.... (minor things)
+ *
+ * Revision 0.8 1999/03/21 22:40:33 wolff
+ * Port to 2.0
+ *
+ * Revision 0.7 1999/03/21 19:06:34 wolff
+ * Fixed hangup processing.
+ *
+ * Revision 0.6 1999/02/05 08:45:14 wolff
+ * fixed real_raw problems. Inclusion into kernel imminent.
+ *
+ * Revision 0.5 1998/12/21 23:51:06 wolff
+ * Snatched a nasty bug: sx_transmit_chars was getting re-entered, and it
+ * shouldn't have. THATs why I want to have transmit interrupts even when
+ * the buffer is empty.
+ *
+ * Revision 0.4 1998/12/17 09:34:46 wolff
+ * PPP works. ioctl works. Basically works!
+ *
+ * Revision 0.3 1998/12/15 13:05:18 wolff
+ * It works! Wow! Gotta start implementing IOCTL and stuff....
+ *
+ * Revision 0.2 1998/12/01 08:33:53 wolff
+ * moved over to 2.1.130
+ *
+ * Revision 0.1 1998/11/03 21:23:51 wolff
+ * Initial revision. Detects SX card.
+ *
+ * */
+
+
+#define RCS_ID "$Id: sx.c,v 1.33 2000/03/08 10:01:02 wolff, pvdl Exp $"
+#define RCS_REV "$Revision: 1.33 $"
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+/* The 3.0.0 version of sxboards/sxwindow.h uses BYTE and WORD.... */
+#define BYTE u8
+#define WORD u16
+
+/* .... but the 3.0.4 version uses _u8 and _u16. */
+#define _u8 u8
+#define _u16 u16
+
+#include "sxboards.h"
+#include "sxwindow.h"
+
+#include <linux/generic_serial.h>
+#include "sx.h"
+
+
+/* I don't think that this driver can handle more than 256 ports on
+ one machine. You'll have to increase the number of boards in sx.h
+ if you want more than 4 boards. */
+
+#ifndef PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8
+#define PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8 0x2000
+#endif
+
+#ifdef CONFIG_PCI
+static struct pci_device_id sx_pci_tbl[] = {
+ { PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8, PCI_ANY_ID, PCI_ANY_ID },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, sx_pci_tbl);
+#endif /* CONFIG_PCI */
+
+/* Configurable options:
+ (Don't be too sure that it'll work if you toggle them) */
+
+/* Am I paranoid or not ? ;-) */
+#undef SX_PARANOIA_CHECK
+
+
+/* 20 -> 2000 per second. The card should rate-limit interrupts at 100
+ Hz, but it is user configurable. I don't recommend going above 1000
+ Hz. The interrupt ratelimit might trigger if the interrupt is
+ shared with a very active other device. */
+#define IRQ_RATE_LIMIT 20
+
+/* Sharing interrupts is possible now. If the other device wants more
+ than 2000 interrupts per second, we'd gracefully decline further
+ interrupts. That's not what we want. On the other hand, if the
+ other device interrupts 2000 times a second, don't use the SX
+ interrupt. Use polling. */
+#undef IRQ_RATE_LIMIT
+
+
+#if 0
+/* Not implemented */
+/*
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#define SX_REPORT_FIFO
+#define SX_REPORT_OVERRUN
+#endif
+
+
+/* Function prototypes */
+static void sx_disable_tx_interrupts (void * ptr);
+static void sx_enable_tx_interrupts (void * ptr);
+static void sx_disable_rx_interrupts (void * ptr);
+static void sx_enable_rx_interrupts (void * ptr);
+static int sx_get_CD (void * ptr);
+static void sx_shutdown_port (void * ptr);
+static int sx_set_real_termios (void *ptr);
+static void sx_close (void *ptr);
+static int sx_chars_in_buffer (void * ptr);
+static int sx_init_board (struct sx_board *board);
+static int sx_init_portstructs (int nboards, int nports);
+static int sx_fw_ioctl (struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg);
+static int sx_init_drivers(void);
+
+
+static struct tty_driver *sx_driver;
+
+static struct sx_board boards[SX_NBOARDS];
+static struct sx_port *sx_ports;
+static int sx_initialized;
+static int sx_nports;
+static int sx_debug;
+
+
+/* You can have the driver poll your card.
+ - Set sx_poll to 1 to poll every timer tick (10ms on Intel).
+ This is used when the card cannot use an interrupt for some reason.
+
+ - set sx_slowpoll to 100 to do an extra poll once a second (on Intel). If
+ the driver misses an interrupt (report this if it DOES happen to you!)
+ everything will continue to work....
+ */
+static int sx_poll = 1;
+static int sx_slowpoll;
+
+/* The card limits the number of interrupts per second.
+ At 115k2 "100" should be sufficient.
+ If you're using higher baudrates, you can increase this...
+ */
+
+static int sx_maxints = 100;
+
+/* These are the only open spaces in my computer. Yours may have more
+ or less.... -- REW
+ duh: Card at 0xa0000 is possible on HP Netserver?? -- pvdl
+*/
+static int sx_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000,
+ 0xc8000, 0xd8000, 0xe8000};
+static int si_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000,
+ 0xc8000, 0xd8000, 0xe8000, 0xa0000};
+static int si1_probe_addrs[]= { 0xd0000};
+
+#define NR_SX_ADDRS (sizeof(sx_probe_addrs)/sizeof (int))
+#define NR_SI_ADDRS (sizeof(si_probe_addrs)/sizeof (int))
+#define NR_SI1_ADDRS (sizeof(si1_probe_addrs)/sizeof (int))
+
+
+/* Set the mask to all-ones. This alas, only supports 32 interrupts.
+ Some architectures may need more. */
+static int sx_irqmask = -1;
+
+module_param_array(sx_probe_addrs, int, NULL, 0);
+module_param_array(si_probe_addrs, int, NULL, 0);
+module_param(sx_poll, int, 0);
+module_param(sx_slowpoll, int, 0);
+module_param(sx_maxints, int, 0);
+module_param(sx_debug, int, 0);
+module_param(sx_irqmask, int, 0);
+
+MODULE_LICENSE("GPL");
+
+static struct real_driver sx_real_driver = {
+ sx_disable_tx_interrupts,
+ sx_enable_tx_interrupts,
+ sx_disable_rx_interrupts,
+ sx_enable_rx_interrupts,
+ sx_get_CD,
+ sx_shutdown_port,
+ sx_set_real_termios,
+ sx_chars_in_buffer,
+ sx_close,
+};
+
+
+/*
+ This driver can spew a whole lot of debugging output at you. If you
+ need maximum performance, you should disable the DEBUG define. To
+ aid in debugging in the field, I'm leaving the compile-time debug
+ features enabled, and disable them "runtime". That allows me to
+ instruct people with problems to enable debugging without requiring
+ them to recompile...
+*/
+#define DEBUG
+
+
+#ifdef DEBUG
+#define sx_dprintk(f, str...) if (sx_debug & f) printk (str)
+#else
+#define sx_dprintk(f, str...) /* nothing */
+#endif
+
+
+
+#define func_enter() sx_dprintk (SX_DEBUG_FLOW, "sx: enter %s\n",__FUNCTION__)
+#define func_exit() sx_dprintk (SX_DEBUG_FLOW, "sx: exit %s\n", __FUNCTION__)
+
+#define func_enter2() sx_dprintk (SX_DEBUG_FLOW, "sx: enter %s (port %d)\n", \
+ __FUNCTION__, port->line)
+
+
+
+
+/*
+ * Firmware loader driver specific routines
+ *
+ */
+
+static struct file_operations sx_fw_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = sx_fw_ioctl,
+};
+
+static struct miscdevice sx_fw_device = {
+ SXCTL_MISC_MINOR, "sxctl", &sx_fw_fops
+};
+
+
+
+
+
+#ifdef SX_PARANOIA_CHECK
+
+/* This doesn't work. Who's paranoid around here? Not me! */
+
+static inline int sx_paranoia_check(struct sx_port const * port,
+ char *name, const char *routine)
+{
+
+ static const char *badmagic =
+ KERN_ERR "sx: Warning: bad sx port magic number for device %s in %s\n";
+ static const char *badinfo =
+ KERN_ERR "sx: Warning: null sx port for device %s in %s\n";
+
+ if (!port) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (port->magic != SX_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+
+ return 0;
+}
+#else
+#define sx_paranoia_check(a,b,c) 0
+#endif
+
+/* The timeouts. First try 30 times as fast as possible. Then give
+ the card some time to breathe between accesses. (Otherwise the
+ processor on the card might not be able to access its OWN bus... */
+
+#define TIMEOUT_1 30
+#define TIMEOUT_2 1000000
+
+
+#ifdef DEBUG
+static void my_hd_io(void __iomem *p, int len)
+{
+ int i, j, ch;
+ unsigned char __iomem *addr = p;
+
+ for (i=0;i<len;i+=16) {
+ printk ("%p ", addr+i);
+ for (j=0;j<16;j++) {
+ printk ("%02x %s", readb(addr+j+i), (j==7)?" ":"");
+ }
+ for (j=0;j<16;j++) {
+ ch = readb(addr+j+i);
+ printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+ }
+ printk ("\n");
+ }
+}
+static void my_hd(void *p, int len)
+{
+ int i, j, ch;
+ unsigned char *addr = p;
+
+ for (i=0;i<len;i+=16) {
+ printk ("%p ", addr+i);
+ for (j=0;j<16;j++) {
+ printk ("%02x %s", addr[j+i], (j==7)?" ":"");
+ }
+ for (j=0;j<16;j++) {
+ ch = addr[j+i];
+ printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+ }
+ printk ("\n");
+ }
+}
+#endif
+
+
+
+/* This needs redoing for Alpha -- REW -- Done. */
+
+static inline void write_sx_byte (struct sx_board *board, int offset, u8 byte)
+{
+ writeb (byte, board->base+offset);
+}
+
+static inline u8 read_sx_byte (struct sx_board *board, int offset)
+{
+ return readb (board->base+offset);
+}
+
+
+static inline void write_sx_word (struct sx_board *board, int offset, u16 word)
+{
+ writew (word, board->base+offset);
+}
+
+static inline u16 read_sx_word (struct sx_board *board, int offset)
+{
+ return readw (board->base + offset);
+}
+
+
+static int sx_busy_wait_eq (struct sx_board *board,
+ int offset, int mask, int correctval)
+{
+ int i;
+
+ func_enter ();
+
+ for (i=0; i < TIMEOUT_1 ;i++)
+ if ((read_sx_byte (board, offset) & mask) == correctval) {
+ func_exit ();
+ return 1;
+ }
+
+ for (i=0; i < TIMEOUT_2 ;i++) {
+ if ((read_sx_byte (board, offset) & mask) == correctval) {
+ func_exit ();
+ return 1;
+ }
+ udelay (1);
+ }
+
+ func_exit ();
+ return 0;
+}
+
+
+static int sx_busy_wait_neq (struct sx_board *board,
+ int offset, int mask, int badval)
+{
+ int i;
+
+ func_enter ();
+
+ for (i=0; i < TIMEOUT_1 ;i++)
+ if ((read_sx_byte (board, offset) & mask) != badval) {
+ func_exit ();
+ return 1;
+ }
+
+ for (i=0; i < TIMEOUT_2 ;i++) {
+ if ((read_sx_byte (board, offset) & mask) != badval) {
+ func_exit ();
+ return 1;
+ }
+ udelay (1);
+ }
+
+ func_exit ();
+ return 0;
+}
+
+
+
+/* 5.6.4 of 6210028 r2.3 */
+static int sx_reset (struct sx_board *board)
+{
+ func_enter ();
+
+ if (IS_SX_BOARD (board)) {
+
+ write_sx_byte (board, SX_CONFIG, 0);
+ write_sx_byte (board, SX_RESET, 1); /* Value doesn't matter */
+
+ if (!sx_busy_wait_eq (board, SX_RESET_STATUS, 1, 0)) {
+ printk (KERN_INFO "sx: Card doesn't respond to reset....\n");
+ return 0;
+ }
+ } else if (IS_EISA_BOARD(board)) {
+ outb(board->irq<<4, board->eisa_base+0xc02);
+ } else if (IS_SI1_BOARD(board)) {
+ write_sx_byte (board, SI1_ISA_RESET, 0); // value does not matter
+ } else {
+ /* Gory details of the SI/ISA board */
+ write_sx_byte (board, SI2_ISA_RESET, SI2_ISA_RESET_SET);
+ write_sx_byte (board, SI2_ISA_IRQ11, SI2_ISA_IRQ11_CLEAR);
+ write_sx_byte (board, SI2_ISA_IRQ12, SI2_ISA_IRQ12_CLEAR);
+ write_sx_byte (board, SI2_ISA_IRQ15, SI2_ISA_IRQ15_CLEAR);
+ write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_CLEAR);
+ write_sx_byte (board, SI2_ISA_IRQSET, SI2_ISA_IRQSET_CLEAR);
+ }
+
+ func_exit ();
+ return 1;
+}
+
+
+/* This doesn't work on machines where "NULL" isn't 0 */
+/* If you have one of those, someone will need to write
+ the equivalent of this, which will amount to about 3 lines. I don't
+ want to complicate this right now. -- REW
+ (See, I do write comments every now and then :-) */
+#define OFFSETOF(strct, elem) ((long)&(((struct strct *)NULL)->elem))
+
+
+#define CHAN_OFFSET(port,elem) (port->ch_base + OFFSETOF (_SXCHANNEL, elem))
+#define MODU_OFFSET(board,addr,elem) (addr + OFFSETOF (_SXMODULE, elem))
+#define BRD_OFFSET(board,elem) (OFFSETOF (_SXCARD, elem))
+
+
+#define sx_write_channel_byte(port, elem, val) \
+ write_sx_byte (port->board, CHAN_OFFSET (port, elem), val)
+
+#define sx_read_channel_byte(port, elem) \
+ read_sx_byte (port->board, CHAN_OFFSET (port, elem))
+
+#define sx_write_channel_word(port, elem, val) \
+ write_sx_word (port->board, CHAN_OFFSET (port, elem), val)
+
+#define sx_read_channel_word(port, elem) \
+ read_sx_word (port->board, CHAN_OFFSET (port, elem))
+
+
+#define sx_write_module_byte(board, addr, elem, val) \
+ write_sx_byte (board, MODU_OFFSET (board, addr, elem), val)
+
+#define sx_read_module_byte(board, addr, elem) \
+ read_sx_byte (board, MODU_OFFSET (board, addr, elem))
+
+#define sx_write_module_word(board, addr, elem, val) \
+ write_sx_word (board, MODU_OFFSET (board, addr, elem), val)
+
+#define sx_read_module_word(board, addr, elem) \
+ read_sx_word (board, MODU_OFFSET (board, addr, elem))
+
+
+#define sx_write_board_byte(board, elem, val) \
+ write_sx_byte (board, BRD_OFFSET (board, elem), val)
+
+#define sx_read_board_byte(board, elem) \
+ read_sx_byte (board, BRD_OFFSET (board, elem))
+
+#define sx_write_board_word(board, elem, val) \
+ write_sx_word (board, BRD_OFFSET (board, elem), val)
+
+#define sx_read_board_word(board, elem) \
+ read_sx_word (board, BRD_OFFSET (board, elem))
+
+
+static int sx_start_board (struct sx_board *board)
+{
+ if (IS_SX_BOARD (board)) {
+ write_sx_byte (board, SX_CONFIG, SX_CONF_BUSEN);
+ } else if (IS_EISA_BOARD(board)) {
+ write_sx_byte(board, SI2_EISA_OFF, SI2_EISA_VAL);
+ outb((board->irq<<4)|4, board->eisa_base+0xc02);
+ } else if (IS_SI1_BOARD(board)) {
+ write_sx_byte (board, SI1_ISA_RESET_CLEAR, 0);
+ write_sx_byte (board, SI1_ISA_INTCL, 0);
+ } else {
+ /* Don't bug me about the clear_set.
+ I haven't the foggiest idea what it's about -- REW */
+ write_sx_byte (board, SI2_ISA_RESET, SI2_ISA_RESET_CLEAR);
+ write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+ }
+ return 1;
+}
+
+#define SX_IRQ_REG_VAL(board) \
+ ((board->flags & SX_ISA_BOARD)?(board->irq << 4):0)
+
+/* Note. The SX register is write-only. Therefore, we have to enable the
+ bus too. This is a no-op, if you don't mess with this driver... */
+static int sx_start_interrupts (struct sx_board *board)
+{
+
+ /* Don't call this with board->irq == 0 */
+
+ if (IS_SX_BOARD(board)) {
+ write_sx_byte (board, SX_CONFIG, SX_IRQ_REG_VAL (board) |
+ SX_CONF_BUSEN |
+ SX_CONF_HOSTIRQ);
+ } else if (IS_EISA_BOARD(board)) {
+ inb(board->eisa_base+0xc03);
+ } else if (IS_SI1_BOARD(board)) {
+ write_sx_byte (board, SI1_ISA_INTCL,0);
+ write_sx_byte (board, SI1_ISA_INTCL_CLEAR,0);
+ } else {
+ switch (board->irq) {
+ case 11:write_sx_byte (board, SI2_ISA_IRQ11, SI2_ISA_IRQ11_SET);break;
+ case 12:write_sx_byte (board, SI2_ISA_IRQ12, SI2_ISA_IRQ12_SET);break;
+ case 15:write_sx_byte (board, SI2_ISA_IRQ15, SI2_ISA_IRQ15_SET);break;
+ default:printk (KERN_INFO "sx: SI/XIO card doesn't support interrupt %d.\n",
+ board->irq);
+ return 0;
+ }
+ write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+ }
+
+ return 1;
+}
+
+
+static int sx_send_command (struct sx_port *port,
+ int command, int mask, int newstat)
+{
+ func_enter2 ();
+ write_sx_byte (port->board, CHAN_OFFSET (port, hi_hstat), command);
+ func_exit ();
+ return sx_busy_wait_eq (port->board, CHAN_OFFSET (port, hi_hstat), mask, newstat);
+}
+
+
+static char *mod_type_s (int module_type)
+{
+ switch (module_type) {
+ case TA4: return "TA4";
+ case TA8: return "TA8";
+ case TA4_ASIC: return "TA4_ASIC";
+ case TA8_ASIC: return "TA8_ASIC";
+ case MTA_CD1400:return "MTA_CD1400";
+ case SXDC: return "SXDC";
+ default:return "Unknown/invalid";
+ }
+}
+
+
+static char *pan_type_s (int pan_type)
+{
+ switch (pan_type) {
+ case MOD_RS232DB25: return "MOD_RS232DB25";
+ case MOD_RS232RJ45: return "MOD_RS232RJ45";
+ case MOD_RS422DB25: return "MOD_RS422DB25";
+ case MOD_PARALLEL: return "MOD_PARALLEL";
+ case MOD_2_RS232DB25: return "MOD_2_RS232DB25";
+ case MOD_2_RS232RJ45: return "MOD_2_RS232RJ45";
+ case MOD_2_RS422DB25: return "MOD_2_RS422DB25";
+ case MOD_RS232DB25MALE: return "MOD_RS232DB25MALE";
+ case MOD_2_PARALLEL: return "MOD_2_PARALLEL";
+ case MOD_BLANK: return "empty";
+ default:return "invalid";
+ }
+}
+
+
+static int mod_compat_type (int module_type)
+{
+ return module_type >> 4;
+}
+
+static void sx_reconfigure_port(struct sx_port *port)
+{
+ if (sx_read_channel_byte (port, hi_hstat) == HS_IDLE_OPEN) {
+ if (sx_send_command (port, HS_CONFIG, -1, HS_IDLE_OPEN) != 1) {
+ printk (KERN_WARNING "sx: Sent reconfigure command, but card didn't react.\n");
+ }
+ } else {
+ sx_dprintk (SX_DEBUG_TERMIOS,
+ "sx: Not sending reconfigure: port isn't open (%02x).\n",
+ sx_read_channel_byte (port, hi_hstat));
+ }
+}
+
+static void sx_setsignals (struct sx_port *port, int dtr, int rts)
+{
+ int t;
+ func_enter2 ();
+
+ t = sx_read_channel_byte (port, hi_op);
+ if (dtr >= 0) t = dtr? (t | OP_DTR): (t & ~OP_DTR);
+ if (rts >= 0) t = rts? (t | OP_RTS): (t & ~OP_RTS);
+ sx_write_channel_byte (port, hi_op, t);
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "setsignals: %d/%d\n", dtr, rts);
+
+ func_exit ();
+}
+
+
+
+static int sx_getsignals (struct sx_port *port)
+{
+ int i_stat,o_stat;
+
+ o_stat = sx_read_channel_byte (port, hi_op);
+ i_stat = sx_read_channel_byte (port, hi_ip);
+
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "getsignals: %d/%d (%d/%d) %02x/%02x\n",
+ (o_stat & OP_DTR) != 0, (o_stat & OP_RTS) != 0,
+ port->c_dcd, sx_get_CD (port),
+ sx_read_channel_byte (port, hi_ip),
+ sx_read_channel_byte (port, hi_state));
+
+ return (((o_stat & OP_DTR)?TIOCM_DTR:0) |
+ ((o_stat & OP_RTS)?TIOCM_RTS:0) |
+ ((i_stat & IP_CTS)?TIOCM_CTS:0) |
+ ((i_stat & IP_DCD)?TIOCM_CAR:0) |
+ ((i_stat & IP_DSR)?TIOCM_DSR:0) |
+ ((i_stat & IP_RI)?TIOCM_RNG:0)
+ );
+}
+
+
+static void sx_set_baud (struct sx_port *port)
+{
+ int t;
+
+ if (port->board->ta_type == MOD_SXDC) {
+ switch (port->gs.baud) {
+ /* Save some typing work... */
+#define e(x) case x:t= BAUD_ ## x ; break
+ e(50);e(75);e(110);e(150);e(200);e(300);e(600);
+ e(1200);e(1800);e(2000);e(2400);e(4800);e(7200);
+ e(9600);e(14400);e(19200);e(28800);e(38400);
+ e(56000);e(57600);e(64000);e(76800);e(115200);
+ e(128000);e(150000);e(230400);e(256000);e(460800);
+ e(921600);
+ case 134 :t = BAUD_134_5; break;
+ case 0 :t = -1;
+ break;
+ default:
+ /* Can I return "invalid"? */
+ t = BAUD_9600;
+ printk (KERN_INFO "sx: unsupported baud rate: %d.\n", port->gs.baud);
+ break;
+ }
+#undef e
+ if (t > 0) {
+ /* The baud rate is not set to 0, so we're enabeling DTR... -- REW */
+ sx_setsignals (port, 1, -1);
+ /* XXX This is not TA & MTA compatible */
+ sx_write_channel_byte (port, hi_csr, 0xff);
+
+ sx_write_channel_byte (port, hi_txbaud, t);
+ sx_write_channel_byte (port, hi_rxbaud, t);
+ } else {
+ sx_setsignals (port, 0, -1);
+ }
+ } else {
+ switch (port->gs.baud) {
+#define e(x) case x:t= CSR_ ## x ; break
+ e(75);e(150);e(300);e(600);e(1200);e(2400);e(4800);
+ e(1800);e(9600);
+ e(19200);e(57600);e(38400);
+ /* TA supports 110, but not 115200, MTA supports 115200, but not 110 */
+ case 110:
+ if (port->board->ta_type == MOD_TA) {
+ t = CSR_110;
+ break;
+ } else {
+ t = CSR_9600;
+ printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+ break;
+ }
+ case 115200:
+ if (port->board->ta_type == MOD_TA) {
+ t = CSR_9600;
+ printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+ break;
+ } else {
+ t = CSR_110;
+ break;
+ }
+ case 0 :t = -1;
+ break;
+ default:
+ t = CSR_9600;
+ printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+ break;
+ }
+#undef e
+ if (t >= 0) {
+ sx_setsignals (port, 1, -1);
+ sx_write_channel_byte (port, hi_csr, t * 0x11);
+ } else {
+ sx_setsignals (port, 0, -1);
+ }
+ }
+}
+
+
+/* Simon Allen's version of this routine was 225 lines long. 85 is a lot
+ better. -- REW */
+
+static int sx_set_real_termios (void *ptr)
+{
+ struct sx_port *port = ptr;
+
+ func_enter2();
+
+ if (!port->gs.tty)
+ return 0;
+
+ /* What is this doing here? -- REW
+ Ha! figured it out. It is to allow you to get DTR active again
+ if you've dropped it with stty 0. Moved to set_baud, where it
+ belongs (next to the drop dtr if baud == 0) -- REW */
+ /* sx_setsignals (port, 1, -1); */
+
+ sx_set_baud (port);
+
+#define CFLAG port->gs.tty->termios->c_cflag
+ sx_write_channel_byte (port, hi_mr1,
+ (C_PARENB (port->gs.tty)? MR1_WITH:MR1_NONE) |
+ (C_PARODD (port->gs.tty)? MR1_ODD:MR1_EVEN) |
+ (C_CRTSCTS(port->gs.tty)? MR1_RTS_RXFLOW:0) |
+ (((CFLAG & CSIZE)==CS8) ? MR1_8_BITS:0) |
+ (((CFLAG & CSIZE)==CS7) ? MR1_7_BITS:0) |
+ (((CFLAG & CSIZE)==CS6) ? MR1_6_BITS:0) |
+ (((CFLAG & CSIZE)==CS5) ? MR1_5_BITS:0) );
+
+ sx_write_channel_byte (port, hi_mr2,
+ (C_CRTSCTS(port->gs.tty)?MR2_CTS_TXFLOW:0) |
+ (C_CSTOPB (port->gs.tty)?MR2_2_STOP:MR2_1_STOP));
+
+ switch (CFLAG & CSIZE) {
+ case CS8:sx_write_channel_byte (port, hi_mask, 0xff);break;
+ case CS7:sx_write_channel_byte (port, hi_mask, 0x7f);break;
+ case CS6:sx_write_channel_byte (port, hi_mask, 0x3f);break;
+ case CS5:sx_write_channel_byte (port, hi_mask, 0x1f);break;
+ default:
+ printk (KERN_INFO "sx: Invalid wordsize: %d\n", CFLAG & CSIZE);
+ break;
+ }
+
+ sx_write_channel_byte (port, hi_prtcl,
+ (I_IXON (port->gs.tty)?SP_TXEN:0) |
+ (I_IXOFF (port->gs.tty)?SP_RXEN:0) |
+ (I_IXANY (port->gs.tty)?SP_TANY:0) |
+ SP_DCEN);
+
+ sx_write_channel_byte (port, hi_break,
+ (I_IGNBRK(port->gs.tty)?BR_IGN:0 |
+ I_BRKINT(port->gs.tty)?BR_INT:0));
+
+ sx_write_channel_byte (port, hi_txon, START_CHAR (port->gs.tty));
+ sx_write_channel_byte (port, hi_rxon, START_CHAR (port->gs.tty));
+ sx_write_channel_byte (port, hi_txoff, STOP_CHAR (port->gs.tty));
+ sx_write_channel_byte (port, hi_rxoff, STOP_CHAR (port->gs.tty));
+
+ sx_reconfigure_port(port);
+
+ /* Tell line discipline whether we will do input cooking */
+ if(I_OTHER(port->gs.tty)) {
+ clear_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+ } else {
+ set_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+ }
+ sx_dprintk (SX_DEBUG_TERMIOS, "iflags: %x(%d) ",
+ port->gs.tty->termios->c_iflag,
+ I_OTHER(port->gs.tty));
+
+
+/* Tell line discipline whether we will do output cooking.
+ * If OPOST is set and no other output flags are set then we can do output
+ * processing. Even if only *one* other flag in the O_OTHER group is set
+ * we do cooking in software.
+ */
+ if(O_OPOST(port->gs.tty) && !O_OTHER(port->gs.tty)) {
+ set_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+ } else {
+ clear_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+ }
+ sx_dprintk (SX_DEBUG_TERMIOS, "oflags: %x(%d)\n",
+ port->gs.tty->termios->c_oflag,
+ O_OTHER(port->gs.tty));
+ /* port->c_dcd = sx_get_CD (port); */
+ func_exit ();
+ return 0;
+}
+
+
+
+/* ********************************************************************** *
+ * the interrupt related routines *
+ * ********************************************************************** */
+
+/* Note:
+ Other drivers use the macro "MIN" to calculate how much to copy.
+ This has the disadvantage that it will evaluate parts twice. That's
+ expensive when it's IO (and the compiler cannot optimize those away!).
+ Moreover, I'm not sure that you're race-free.
+
+ I assign a value, and then only allow the value to decrease. This
+ is always safe. This makes the code a few lines longer, and you
+ know I'm dead against that, but I think it is required in this
+ case. */
+
+
+static void sx_transmit_chars (struct sx_port *port)
+{
+ int c;
+ int tx_ip;
+ int txroom;
+
+ func_enter2 ();
+ sx_dprintk (SX_DEBUG_TRANSMIT, "Port %p: transmit %d chars\n",
+ port, port->gs.xmit_cnt);
+
+ if (test_and_set_bit (SX_PORT_TRANSMIT_LOCK, &port->locks)) {
+ return;
+ }
+
+ while (1) {
+ c = port->gs.xmit_cnt;
+
+ sx_dprintk (SX_DEBUG_TRANSMIT, "Copying %d ", c);
+ tx_ip = sx_read_channel_byte (port, hi_txipos);
+
+ /* Took me 5 minutes to deduce this formula.
+ Luckily it is literally in the manual in section 6.5.4.3.5 */
+ txroom = (sx_read_channel_byte (port, hi_txopos) - tx_ip - 1) & 0xff;
+
+ /* Don't copy more bytes than there is room for in the buffer */
+ if (c > txroom)
+ c = txroom;
+ sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%d) ", c, txroom );
+
+ /* Don't copy past the end of the hardware transmit buffer */
+ if (c > 0x100 - tx_ip)
+ c = 0x100 - tx_ip;
+
+ sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%d) ", c, 0x100-tx_ip );
+
+ /* Don't copy pas the end of the source buffer */
+ if (c > SERIAL_XMIT_SIZE - port->gs.xmit_tail)
+ c = SERIAL_XMIT_SIZE - port->gs.xmit_tail;
+
+ sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%ld) \n",
+ c, SERIAL_XMIT_SIZE- port->gs.xmit_tail);
+
+ /* If for one reason or another, we can't copy more data, we're done! */
+ if (c == 0) break;
+
+
+ memcpy_toio (port->board->base + CHAN_OFFSET(port,hi_txbuf) + tx_ip,
+ port->gs.xmit_buf + port->gs.xmit_tail, c);
+
+ /* Update the pointer in the card */
+ sx_write_channel_byte (port, hi_txipos, (tx_ip+c) & 0xff);
+
+ /* Update the kernel buffer end */
+ port->gs.xmit_tail = (port->gs.xmit_tail + c) & (SERIAL_XMIT_SIZE-1);
+
+ /* This one last. (this is essential)
+ It would allow others to start putting more data into the buffer! */
+ port->gs.xmit_cnt -= c;
+ }
+
+ if (port->gs.xmit_cnt == 0) {
+ sx_disable_tx_interrupts (port);
+ }
+
+ if ((port->gs.xmit_cnt <= port->gs.wakeup_chars) && port->gs.tty) {
+ tty_wakeup(port->gs.tty);
+ sx_dprintk (SX_DEBUG_TRANSMIT, "Waking up.... ldisc (%d)....\n",
+ port->gs.wakeup_chars);
+ }
+
+ clear_bit (SX_PORT_TRANSMIT_LOCK, &port->locks);
+ func_exit ();
+}
+
+
+/* Note the symmetry between receiving chars and transmitting them!
+ Note: The kernel should have implemented both a receive buffer and
+ a transmit buffer. */
+
+/* Inlined: Called only once. Remove the inline when you add another call */
+static inline void sx_receive_chars (struct sx_port *port)
+{
+ int c;
+ int rx_op;
+ struct tty_struct *tty;
+ int copied=0;
+
+ func_enter2 ();
+ tty = port->gs.tty;
+ while (1) {
+ rx_op = sx_read_channel_byte (port, hi_rxopos);
+ c = (sx_read_channel_byte (port, hi_rxipos) - rx_op) & 0xff;
+
+ sx_dprintk (SX_DEBUG_RECEIVE, "rxop=%d, c = %d.\n", rx_op, c);
+
+ /* Don't copy more bytes than there is room for in the buffer */
+ if (tty->flip.count + c > TTY_FLIPBUF_SIZE)
+ c = TTY_FLIPBUF_SIZE - tty->flip.count;
+
+ sx_dprintk (SX_DEBUG_RECEIVE, "c = %d.\n", c);
+
+ /* Don't copy past the end of the hardware receive buffer */
+ if (rx_op + c > 0x100) c = 0x100 - rx_op;
+
+ sx_dprintk (SX_DEBUG_RECEIVE, "c = %d.\n", c);
+
+ /* If for one reason or another, we can't copy more data, we're done! */
+ if (c == 0) break;
+
+ sx_dprintk (SX_DEBUG_RECEIVE , "Copying over %d chars. First is %d at %lx\n", c,
+ read_sx_byte (port->board, CHAN_OFFSET(port,hi_rxbuf) + rx_op),
+ CHAN_OFFSET(port, hi_rxbuf));
+ memcpy_fromio (tty->flip.char_buf_ptr,
+ port->board->base + CHAN_OFFSET(port,hi_rxbuf) + rx_op, c);
+ memset(tty->flip.flag_buf_ptr, TTY_NORMAL, c);
+
+ /* Update the kernel buffer end */
+ tty->flip.count += c;
+ tty->flip.char_buf_ptr += c;
+ tty->flip.flag_buf_ptr += c;
+
+ /* This one last. ( Not essential.)
+ It allows the card to start putting more data into the buffer!
+ Update the pointer in the card */
+ sx_write_channel_byte (port, hi_rxopos, (rx_op + c) & 0xff);
+
+ copied += c;
+ }
+ if (copied) {
+ struct timeval tv;
+
+ do_gettimeofday (&tv);
+ sx_dprintk (SX_DEBUG_RECEIVE,
+ "pushing flipq port %d (%3d chars): %d.%06d (%d/%d)\n",
+ port->line, copied,
+ (int) (tv.tv_sec % 60), (int)tv.tv_usec, tty->raw, tty->real_raw);
+
+ /* Tell the rest of the system the news. Great news. New characters! */
+ tty_flip_buffer_push (tty);
+ /* tty_schedule_flip (tty); */
+ }
+
+ func_exit ();
+}
+
+/* Inlined: it is called only once. Remove the inline if you add another
+ call */
+static inline void sx_check_modem_signals (struct sx_port *port)
+{
+ int hi_state;
+ int c_dcd;
+
+ hi_state = sx_read_channel_byte (port, hi_state);
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "Checking modem signals (%d/%d)\n",
+ port->c_dcd, sx_get_CD (port));
+
+ if (hi_state & ST_BREAK) {
+ hi_state &= ~ST_BREAK;
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "got a break.\n");
+ sx_write_channel_byte (port, hi_state, hi_state);
+ gs_got_break (&port->gs);
+ }
+ if (hi_state & ST_DCD) {
+ hi_state &= ~ST_DCD;
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "got a DCD change.\n");
+ sx_write_channel_byte (port, hi_state, hi_state);
+ c_dcd = sx_get_CD (port);
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD is now %d\n", c_dcd);
+ if (c_dcd != port->c_dcd) {
+ port->c_dcd = c_dcd;
+ if (sx_get_CD (port)) {
+ /* DCD went UP */
+ if ((sx_read_channel_byte(port, hi_hstat) != HS_IDLE_CLOSED) &&
+ !(port->gs.tty->termios->c_cflag & CLOCAL) ) {
+ /* Are we blocking in open?*/
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD active, unblocking open\n");
+ wake_up_interruptible(&port->gs.open_wait);
+ } else {
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD raised. Ignoring.\n");
+ }
+ } else {
+ /* DCD went down! */
+ if (!(port->gs.tty->termios->c_cflag & CLOCAL) ) {
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD dropped. hanging up....\n");
+ tty_hangup (port->gs.tty);
+ } else {
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD dropped. ignoring.\n");
+ }
+ }
+ } else {
+ sx_dprintk (SX_DEBUG_MODEMSIGNALS, "Hmmm. card told us DCD changed, but it didn't.\n");
+ }
+ }
+}
+
+
+/* This is what an interrupt routine should look like.
+ * Small, elegant, clear.
+ */
+
+static irqreturn_t sx_interrupt (int irq, void *ptr, struct pt_regs *regs)
+{
+ struct sx_board *board = ptr;
+ struct sx_port *port;
+ int i;
+
+ func_enter ();
+ sx_dprintk (SX_DEBUG_FLOW, "sx: enter sx_interrupt (%d/%d)\n", irq, board->irq);
+
+ /* AAargh! The order in which to do these things is essential and
+ not trivial.
+
+ - Rate limit goes before "recursive". Otherwise a series of
+ recursive calls will hang the machine in the interrupt routine.
+
+ - hardware twiddling goes before "recursive". Otherwise when we
+ poll the card, and a recursive interrupt happens, we won't
+ ack the card, so it might keep on interrupting us. (especially
+ level sensitive interrupt systems like PCI).
+
+ - Rate limit goes before hardware twiddling. Otherwise we won't
+ catch a card that has gone bonkers.
+
+ - The "initialized" test goes after the hardware twiddling. Otherwise
+ the card will stick us in the interrupt routine again.
+
+ - The initialized test goes before recursive.
+ */
+
+
+
+#ifdef IRQ_RATE_LIMIT
+ /* Aaargh! I'm ashamed. This costs more lines-of-code than the
+ actual interrupt routine!. (Well, used to when I wrote that comment) */
+ {
+ static int lastjif;
+ static int nintr=0;
+
+ if (lastjif == jiffies) {
+ if (++nintr > IRQ_RATE_LIMIT) {
+ free_irq (board->irq, board);
+ printk (KERN_ERR "sx: Too many interrupts. Turning off interrupt %d.\n",
+ board->irq);
+ }
+ } else {
+ lastjif = jiffies;
+ nintr = 0;
+ }
+ }
+#endif
+
+
+ if (board->irq == irq) {
+ /* Tell the card we've noticed the interrupt. */
+
+ sx_write_board_word (board, cc_int_pending, 0);
+ if (IS_SX_BOARD (board)) {
+ write_sx_byte (board, SX_RESET_IRQ, 1);
+ } else if (IS_EISA_BOARD(board)) {
+ inb(board->eisa_base+0xc03);
+ write_sx_word(board, 8, 0);
+ } else {
+ write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_CLEAR);
+ write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+ }
+ }
+
+ if (!sx_initialized)
+ return IRQ_HANDLED;
+ if (!(board->flags & SX_BOARD_INITIALIZED))
+ return IRQ_HANDLED;
+
+ if (test_and_set_bit (SX_BOARD_INTR_LOCK, &board->locks)) {
+ printk (KERN_ERR "Recursive interrupt! (%d)\n", board->irq);
+ return IRQ_HANDLED;
+ }
+
+ for (i=0;i<board->nports;i++) {
+ port = &board->ports[i];
+ if (port->gs.flags & GS_ACTIVE) {
+ if (sx_read_channel_byte (port, hi_state)) {
+ sx_dprintk (SX_DEBUG_INTERRUPTS,
+ "Port %d: modem signal change?... \n", i);
+ sx_check_modem_signals (port);
+ }
+ if (port->gs.xmit_cnt) {
+ sx_transmit_chars (port);
+ }
+ if (!(port->gs.flags & SX_RX_THROTTLE)) {
+ sx_receive_chars (port);
+ }
+ }
+ }
+
+ clear_bit (SX_BOARD_INTR_LOCK, &board->locks);
+
+ sx_dprintk (SX_DEBUG_FLOW, "sx: exit sx_interrupt (%d/%d)\n", irq, board->irq);
+ func_exit ();
+ return IRQ_HANDLED;
+}
+
+
+static void sx_pollfunc (unsigned long data)
+{
+ struct sx_board *board = (struct sx_board *) data;
+
+ func_enter ();
+
+ sx_interrupt (0, board, NULL);
+
+ init_timer(&board->timer);
+
+ board->timer.expires = jiffies + sx_poll;
+ add_timer (&board->timer);
+ func_exit ();
+}
+
+
+
+/* ********************************************************************** *
+ * Here are the routines that actually *
+ * interface with the generic_serial driver *
+ * ********************************************************************** */
+
+/* Ehhm. I don't know how to fiddle with interrupts on the SX card. --REW */
+/* Hmm. Ok I figured it out. You don't. */
+
+static void sx_disable_tx_interrupts (void * ptr)
+{
+ struct sx_port *port = ptr;
+ func_enter2();
+
+ port->gs.flags &= ~GS_TX_INTEN;
+
+ func_exit();
+}
+
+
+static void sx_enable_tx_interrupts (void * ptr)
+{
+ struct sx_port *port = ptr;
+ int data_in_buffer;
+ func_enter2();
+
+ /* First transmit the characters that we're supposed to */
+ sx_transmit_chars (port);
+
+ /* The sx card will never interrupt us if we don't fill the buffer
+ past 25%. So we keep considering interrupts off if that's the case. */
+ data_in_buffer = (sx_read_channel_byte (port, hi_txipos) -
+ sx_read_channel_byte (port, hi_txopos)) & 0xff;
+
+ /* XXX Must be "HIGH_WATER" for SI card according to doc. */
+ if (data_in_buffer < LOW_WATER)
+ port->gs.flags &= ~GS_TX_INTEN;
+
+ func_exit();
+}
+
+
+static void sx_disable_rx_interrupts (void * ptr)
+{
+ /* struct sx_port *port = ptr; */
+ func_enter();
+
+ func_exit();
+}
+
+static void sx_enable_rx_interrupts (void * ptr)
+{
+ /* struct sx_port *port = ptr; */
+ func_enter();
+
+ func_exit();
+}
+
+
+/* Jeez. Isn't this simple? */
+static int sx_get_CD (void * ptr)
+{
+ struct sx_port *port = ptr;
+ func_enter2();
+
+ func_exit();
+ return ((sx_read_channel_byte (port, hi_ip) & IP_DCD) != 0);
+}
+
+
+/* Jeez. Isn't this simple? */
+static int sx_chars_in_buffer (void * ptr)
+{
+ struct sx_port *port = ptr;
+ func_enter2();
+
+ func_exit();
+ return ((sx_read_channel_byte (port, hi_txipos) -
+ sx_read_channel_byte (port, hi_txopos)) & 0xff);
+}
+
+
+static void sx_shutdown_port (void * ptr)
+{
+ struct sx_port *port = ptr;
+
+ func_enter();
+
+ port->gs.flags &= ~ GS_ACTIVE;
+ if (port->gs.tty && (port->gs.tty->termios->c_cflag & HUPCL)) {
+ sx_setsignals (port, 0, 0);
+ sx_reconfigure_port(port);
+ }
+
+ func_exit();
+}
+
+
+
+
+
+/* ********************************************************************** *
+ * Here are the routines that actually *
+ * interface with the rest of the system *
+ * ********************************************************************** */
+
+static int sx_open (struct tty_struct * tty, struct file * filp)
+{
+ struct sx_port *port;
+ int retval, line;
+ unsigned long flags;
+
+ func_enter();
+
+ if (!sx_initialized) {
+ return -EIO;
+ }
+
+ line = tty->index;
+ sx_dprintk (SX_DEBUG_OPEN, "%d: opening line %d. tty=%p ctty=%p, np=%d)\n",
+ current->pid, line, tty, current->signal->tty, sx_nports);
+
+ if ((line < 0) || (line >= SX_NPORTS) || (line >= sx_nports))
+ return -ENODEV;
+
+ port = & sx_ports[line];
+ port->c_dcd = 0; /* Make sure that the first interrupt doesn't detect a
+ 1 -> 0 transition. */
+
+
+ sx_dprintk (SX_DEBUG_OPEN, "port = %p c_dcd = %d\n", port, port->c_dcd);
+
+ spin_lock_irqsave(&port->gs.driver_lock, flags);
+
+ tty->driver_data = port;
+ port->gs.tty = tty;
+ port->gs.count++;
+ spin_unlock_irqrestore(&port->gs.driver_lock, flags);
+
+ sx_dprintk (SX_DEBUG_OPEN, "starting port\n");
+
+ /*
+ * Start up serial port
+ */
+ retval = gs_init_port(&port->gs);
+ sx_dprintk (SX_DEBUG_OPEN, "done gs_init\n");
+ if (retval) {
+ port->gs.count--;
+ return retval;
+ }
+
+ port->gs.flags |= GS_ACTIVE;
+ if (port->gs.count <= 1)
+ sx_setsignals (port, 1,1);
+
+#if 0
+ if (sx_debug & SX_DEBUG_OPEN)
+ my_hd (port, sizeof (*port));
+#else
+ if (sx_debug & SX_DEBUG_OPEN)
+ my_hd_io (port->board->base + port->ch_base, sizeof (*port));
+#endif
+
+ if (port->gs.count <= 1) {
+ if (sx_send_command (port, HS_LOPEN, -1, HS_IDLE_OPEN) != 1) {
+ printk (KERN_ERR "sx: Card didn't respond to LOPEN command.\n");
+ spin_lock_irqsave(&port->gs.driver_lock, flags);
+ port->gs.count--;
+ spin_unlock_irqrestore(&port->gs.driver_lock, flags);
+ return -EIO;
+ }
+ }
+
+ retval = gs_block_til_ready(port, filp);
+ sx_dprintk (SX_DEBUG_OPEN, "Block til ready returned %d. Count=%d\n",
+ retval, port->gs.count);
+
+ if (retval) {
+ /*
+ * Don't lower gs.count here because sx_close() will be called later
+ */
+
+ return retval;
+ }
+ /* tty->low_latency = 1; */
+
+ port->c_dcd = sx_get_CD (port);
+ sx_dprintk (SX_DEBUG_OPEN, "at open: cd=%d\n", port->c_dcd);
+
+ func_exit();
+ return 0;
+
+}
+
+
+static void sx_close (void *ptr)
+{
+ struct sx_port *port = ptr;
+ /* Give the port 5 seconds to close down. */
+ int to = 5 * HZ;
+
+ func_enter ();
+
+ sx_setsignals (port, 0, 0);
+ sx_reconfigure_port(port);
+ sx_send_command (port, HS_CLOSE, 0, 0);
+
+ while (to-- && (sx_read_channel_byte (port, hi_hstat) != HS_IDLE_CLOSED))
+ if (msleep_interruptible(10))
+ break;
+ if (sx_read_channel_byte (port, hi_hstat) != HS_IDLE_CLOSED) {
+ if (sx_send_command (port, HS_FORCE_CLOSED, -1, HS_IDLE_CLOSED) != 1) {
+ printk (KERN_ERR
+ "sx: sent the force_close command, but card didn't react\n");
+ } else
+ sx_dprintk (SX_DEBUG_CLOSE, "sent the force_close command.\n");
+ }
+
+ sx_dprintk (SX_DEBUG_CLOSE, "waited %d jiffies for close. count=%d\n",
+ 5 * HZ - to - 1, port->gs.count);
+
+ if(port->gs.count) {
+ sx_dprintk(SX_DEBUG_CLOSE, "WARNING port count:%d\n", port->gs.count);
+ //printk ("%s SETTING port count to zero: %p count: %d\n", __FUNCTION__, port, port->gs.count);
+ //port->gs.count = 0;
+ }
+
+ func_exit ();
+}
+
+
+
+/* This is relatively thorough. But then again it is only 20 lines. */
+#define MARCHUP for (i=min;i<max;i++)
+#define MARCHDOWN for (i=max-1;i>=min;i--)
+#define W0 write_sx_byte (board, i, 0x55)
+#define W1 write_sx_byte (board, i, 0xaa)
+#define R0 if (read_sx_byte (board, i) != 0x55) return 1
+#define R1 if (read_sx_byte (board, i) != 0xaa) return 1
+
+/* This memtest takes a human-noticable time. You normally only do it
+ once a boot, so I guess that it is worth it. */
+static int do_memtest (struct sx_board *board, int min, int max)
+{
+ int i;
+
+ /* This is a marchb. Theoretically, marchb catches much more than
+ simpler tests. In practise, the longer test just catches more
+ intermittent errors. -- REW
+ (For the theory behind memory testing see:
+ Testing Semiconductor Memories by A.J. van de Goor.) */
+ MARCHUP {W0;}
+ MARCHUP {R0;W1;R1;W0;R0;W1;}
+ MARCHUP {R1;W0;W1;}
+ MARCHDOWN {R1;W0;W1;W0;}
+ MARCHDOWN {R0;W1;W0;}
+
+ return 0;
+}
+
+
+#undef MARCHUP
+#undef MARCHDOWN
+#undef W0
+#undef W1
+#undef R0
+#undef R1
+
+#define MARCHUP for (i=min;i<max;i+=2)
+#define MARCHDOWN for (i=max-1;i>=min;i-=2)
+#define W0 write_sx_word (board, i, 0x55aa)
+#define W1 write_sx_word (board, i, 0xaa55)
+#define R0 if (read_sx_word (board, i) != 0x55aa) return 1
+#define R1 if (read_sx_word (board, i) != 0xaa55) return 1
+
+#if 0
+/* This memtest takes a human-noticable time. You normally only do it
+ once a boot, so I guess that it is worth it. */
+static int do_memtest_w (struct sx_board *board, int min, int max)
+{
+ int i;
+
+ MARCHUP {W0;}
+ MARCHUP {R0;W1;R1;W0;R0;W1;}
+ MARCHUP {R1;W0;W1;}
+ MARCHDOWN {R1;W0;W1;W0;}
+ MARCHDOWN {R0;W1;W0;}
+
+ return 0;
+}
+#endif
+
+
+static int sx_fw_ioctl (struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc = 0;
+ int __user *descr = (int __user *)arg;
+ int i;
+ static struct sx_board *board = NULL;
+ int nbytes, offset;
+ unsigned long data;
+ char *tmp;
+
+ func_enter();
+
+#if 0
+ /* Removed superuser check: Sysops can use the permissions on the device
+ file to restrict access. Recommendation: Root only. (root.root 600) */
+ if (!capable(CAP_SYS_ADMIN)) {
+ return -EPERM;
+ }
+#endif
+
+ sx_dprintk (SX_DEBUG_FIRMWARE, "IOCTL %x: %lx\n", cmd, arg);
+
+ if (!board) board = &boards[0];
+ if (board->flags & SX_BOARD_PRESENT) {
+ sx_dprintk (SX_DEBUG_FIRMWARE, "Board present! (%x)\n",
+ board->flags);
+ } else {
+ sx_dprintk (SX_DEBUG_FIRMWARE, "Board not present! (%x) all:",
+ board->flags);
+ for (i=0;i< SX_NBOARDS;i++)
+ sx_dprintk (SX_DEBUG_FIRMWARE, "<%x> ", boards[i].flags);
+ sx_dprintk (SX_DEBUG_FIRMWARE, "\n");
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case SXIO_SET_BOARD:
+ sx_dprintk (SX_DEBUG_FIRMWARE, "set board to %ld\n", arg);
+ if (arg >= SX_NBOARDS) return -EIO;
+ sx_dprintk (SX_DEBUG_FIRMWARE, "not out of range\n");
+ if (!(boards[arg].flags & SX_BOARD_PRESENT)) return -EIO;
+ sx_dprintk (SX_DEBUG_FIRMWARE, ".. and present!\n");
+ board = &boards[arg];
+ break;
+ case SXIO_GET_TYPE:
+ rc = -ENOENT; /* If we manage to miss one, return error. */
+ if (IS_SX_BOARD (board)) rc = SX_TYPE_SX;
+ if (IS_CF_BOARD (board)) rc = SX_TYPE_CF;
+ if (IS_SI_BOARD (board)) rc = SX_TYPE_SI;
+ if (IS_SI1_BOARD (board)) rc = SX_TYPE_SI;
+ if (IS_EISA_BOARD (board)) rc = SX_TYPE_SI;
+ sx_dprintk (SX_DEBUG_FIRMWARE, "returning type= %d\n", rc);
+ break;
+ case SXIO_DO_RAMTEST:
+ if (sx_initialized) /* Already initialized: better not ramtest the board. */
+ return -EPERM;
+ if (IS_SX_BOARD (board)) {
+ rc = do_memtest (board, 0, 0x7000);
+ if (!rc) rc = do_memtest (board, 0, 0x7000);
+ /*if (!rc) rc = do_memtest_w (board, 0, 0x7000);*/
+ } else {
+ rc = do_memtest (board, 0, 0x7ff8);
+ /* if (!rc) rc = do_memtest_w (board, 0, 0x7ff8); */
+ }
+ sx_dprintk (SX_DEBUG_FIRMWARE, "returning memtest result= %d\n", rc);
+ break;
+ case SXIO_DOWNLOAD:
+ if (sx_initialized) /* Already initialized */
+ return -EEXIST;
+ if (!sx_reset (board))
+ return -EIO;
+ sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+ tmp = kmalloc (SX_CHUNK_SIZE, GFP_USER);
+ if (!tmp) return -ENOMEM;
+ get_user (nbytes, descr++);
+ get_user (offset, descr++);
+ get_user (data, descr++);
+ while (nbytes && data) {
+ for (i=0;i<nbytes;i += SX_CHUNK_SIZE) {
+ if (copy_from_user(tmp, (char __user *)data+i,
+ (i + SX_CHUNK_SIZE >
+ nbytes) ? nbytes - i :
+ SX_CHUNK_SIZE)) {
+ kfree (tmp);
+ return -EFAULT;
+ }
+ memcpy_toio(board->base2 + offset + i, tmp,
+ (i+SX_CHUNK_SIZE>nbytes)?nbytes-i:SX_CHUNK_SIZE);
+ }
+
+ get_user (nbytes, descr++);
+ get_user (offset, descr++);
+ get_user (data, descr++);
+ }
+ kfree (tmp);
+ sx_nports += sx_init_board (board);
+ rc = sx_nports;
+ break;
+ case SXIO_INIT:
+ if (sx_initialized) /* Already initialized */
+ return -EEXIST;
+ /* This is not allowed until all boards are initialized... */
+ for (i=0;i<SX_NBOARDS;i++) {
+ if ( (boards[i].flags & SX_BOARD_PRESENT) &&
+ !(boards[i].flags & SX_BOARD_INITIALIZED))
+ return -EIO;
+ }
+ for (i=0;i<SX_NBOARDS;i++)
+ if (!(boards[i].flags & SX_BOARD_PRESENT)) break;
+
+ sx_dprintk (SX_DEBUG_FIRMWARE, "initing portstructs, %d boards, "
+ "%d channels, first board: %d ports\n",
+ i, sx_nports, boards[0].nports);
+ rc = sx_init_portstructs (i, sx_nports);
+ sx_init_drivers ();
+ if (rc >= 0)
+ sx_initialized++;
+ break;
+ case SXIO_SETDEBUG:
+ sx_debug = arg;
+ break;
+ case SXIO_GETDEBUG:
+ rc = sx_debug;
+ break;
+ case SXIO_GETGSDEBUG:
+ case SXIO_SETGSDEBUG:
+ rc = -EINVAL;
+ break;
+ case SXIO_GETNPORTS:
+ rc = sx_nports;
+ break;
+ default:
+ printk (KERN_WARNING "Unknown ioctl on firmware device (%x).\n", cmd);
+ break;
+ }
+ func_exit ();
+ return rc;
+}
+
+
+static void sx_break (struct tty_struct * tty, int flag)
+{
+ struct sx_port *port = tty->driver_data;
+ int rv;
+
+ func_enter ();
+
+ if (flag)
+ rv = sx_send_command (port, HS_START, -1, HS_IDLE_BREAK);
+ else
+ rv = sx_send_command (port, HS_STOP, -1, HS_IDLE_OPEN);
+ if (rv != 1) printk (KERN_ERR "sx: couldn't send break (%x).\n",
+ read_sx_byte (port->board, CHAN_OFFSET (port, hi_hstat)));
+
+ func_exit ();
+}
+
+
+static int sx_tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct sx_port *port = tty->driver_data;
+ return sx_getsignals(port);
+}
+
+static int sx_tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct sx_port *port = tty->driver_data;
+ int rts = -1, dtr = -1;
+
+ if (set & TIOCM_RTS)
+ rts = 1;
+ if (set & TIOCM_DTR)
+ dtr = 1;
+ if (clear & TIOCM_RTS)
+ rts = 0;
+ if (clear & TIOCM_DTR)
+ dtr = 0;
+
+ sx_setsignals(port, dtr, rts);
+ sx_reconfigure_port(port);
+ return 0;
+}
+
+static int sx_ioctl (struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg)
+{
+ int rc;
+ struct sx_port *port = tty->driver_data;
+ void __user *argp = (void __user *)arg;
+ int ival;
+
+ /* func_enter2(); */
+
+ rc = 0;
+ switch (cmd) {
+ case TIOCGSOFTCAR:
+ rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+ (unsigned __user *) argp);
+ break;
+ case TIOCSSOFTCAR:
+ if ((rc = get_user(ival, (unsigned __user *) argp)) == 0) {
+ tty->termios->c_cflag =
+ (tty->termios->c_cflag & ~CLOCAL) |
+ (ival ? CLOCAL : 0);
+ }
+ break;
+ case TIOCGSERIAL:
+ rc = gs_getserial(&port->gs, argp);
+ break;
+ case TIOCSSERIAL:
+ rc = gs_setserial(&port->gs, argp);
+ break;
+ default:
+ rc = -ENOIOCTLCMD;
+ break;
+ }
+
+ /* func_exit(); */
+ return rc;
+}
+
+
+/* The throttle/unthrottle scheme for the Specialix card is different
+ * from other drivers and deserves some explanation.
+ * The Specialix hardware takes care of XON/XOFF
+ * and CTS/RTS flow control itself. This means that all we have to
+ * do when signalled by the upper tty layer to throttle/unthrottle is
+ * to make a note of it here. When we come to read characters from the
+ * rx buffers on the card (sx_receive_chars()) we look to see if the
+ * upper layer can accept more (as noted here in sx_rx_throt[]).
+ * If it can't we simply don't remove chars from the cards buffer.
+ * When the tty layer can accept chars, we again note that here and when
+ * sx_receive_chars() is called it will remove them from the cards buffer.
+ * The card will notice that a ports buffer has drained below some low
+ * water mark and will unflow control the line itself, using whatever
+ * flow control scheme is in use for that port. -- Simon Allen
+ */
+
+static void sx_throttle (struct tty_struct * tty)
+{
+ struct sx_port *port = (struct sx_port *)tty->driver_data;
+
+ func_enter2();
+ /* If the port is using any type of input flow
+ * control then throttle the port.
+ */
+ if((tty->termios->c_cflag & CRTSCTS) || (I_IXOFF(tty)) ) {
+ port->gs.flags |= SX_RX_THROTTLE;
+ }
+ func_exit();
+}
+
+
+static void sx_unthrottle (struct tty_struct * tty)
+{
+ struct sx_port *port = (struct sx_port *)tty->driver_data;
+
+ func_enter2();
+ /* Always unthrottle even if flow control is not enabled on
+ * this port in case we disabled flow control while the port
+ * was throttled
+ */
+ port->gs.flags &= ~SX_RX_THROTTLE;
+ func_exit();
+ return;
+}
+
+
+/* ********************************************************************** *
+ * Here are the initialization routines. *
+ * ********************************************************************** */
+
+
+
+
+static int sx_init_board (struct sx_board *board)
+{
+ int addr;
+ int chans;
+ int type;
+
+ func_enter();
+
+ /* This is preceded by downloading the download code. */
+
+ board->flags |= SX_BOARD_INITIALIZED;
+
+ if (read_sx_byte (board, 0))
+ /* CF boards may need this. */
+ write_sx_byte(board,0, 0);
+
+ /* This resets the processor again, to make sure it didn't do any
+ foolish things while we were downloading the image */
+ if (!sx_reset (board))
+ return 0;
+
+ sx_start_board (board);
+ udelay (10);
+ if (!sx_busy_wait_neq (board, 0, 0xff, 0)) {
+ printk (KERN_ERR "sx: Ooops. Board won't initialize.\n");
+ return 0;
+ }
+
+ /* Ok. So now the processor on the card is running. It gathered
+ some info for us... */
+ sx_dprintk (SX_DEBUG_INIT, "The sxcard structure:\n");
+ if (sx_debug & SX_DEBUG_INIT) my_hd_io (board->base, 0x10);
+ sx_dprintk (SX_DEBUG_INIT, "the first sx_module structure:\n");
+ if (sx_debug & SX_DEBUG_INIT) my_hd_io (board->base + 0x80, 0x30);
+
+ sx_dprintk (SX_DEBUG_INIT,
+ "init_status: %x, %dk memory, firmware V%x.%02x,\n",
+ read_sx_byte (board, 0), read_sx_byte(board, 1),
+ read_sx_byte (board, 5), read_sx_byte(board, 4));
+
+ if (read_sx_byte (board, 0) == 0xff) {
+ printk (KERN_INFO "sx: No modules found. Sorry.\n");
+ board->nports = 0;
+ return 0;
+ }
+
+ chans = 0;
+
+ if (IS_SX_BOARD(board)) {
+ sx_write_board_word (board, cc_int_count, sx_maxints);
+ } else {
+ if (sx_maxints)
+ sx_write_board_word (board, cc_int_count, SI_PROCESSOR_CLOCK/8/sx_maxints);
+ }
+
+ /* grab the first module type... */
+ /* board->ta_type = mod_compat_type (read_sx_byte (board, 0x80 + 0x08)); */
+ board->ta_type = mod_compat_type (sx_read_module_byte (board, 0x80, mc_chip));
+
+ /* XXX byteorder */
+ for (addr = 0x80;addr != 0;addr = read_sx_word (board, addr) & 0x7fff) {
+ type = sx_read_module_byte (board, addr, mc_chip);
+ sx_dprintk (SX_DEBUG_INIT, "Module at %x: %d channels\n",
+ addr, read_sx_byte (board, addr + 2));
+
+ chans += sx_read_module_byte (board, addr, mc_type);
+
+ sx_dprintk (SX_DEBUG_INIT, "module is an %s, which has %s/%s panels\n",
+ mod_type_s (type),
+ pan_type_s (sx_read_module_byte (board, addr, mc_mods) & 0xf),
+ pan_type_s (sx_read_module_byte (board, addr, mc_mods) >> 4));
+
+ sx_dprintk (SX_DEBUG_INIT, "CD1400 versions: %x/%x, ASIC version: %x\n",
+ sx_read_module_byte (board, addr, mc_rev1),
+ sx_read_module_byte (board, addr, mc_rev2),
+ sx_read_module_byte (board, addr, mc_mtaasic_rev));
+
+ /* The following combinations are illegal: It should theoretically
+ work, but timing problems make the bus HANG. */
+
+ if (mod_compat_type (type) != board->ta_type) {
+ printk (KERN_ERR "sx: This is an invalid configuration.\n"
+ "Don't mix TA/MTA/SXDC on the same hostadapter.\n");
+ chans=0;
+ break;
+ }
+ if ((IS_EISA_BOARD(board) ||
+ IS_SI_BOARD(board)) && (mod_compat_type(type) == 4)) {
+ printk (KERN_ERR "sx: This is an invalid configuration.\n"
+ "Don't use SXDCs on an SI/XIO adapter.\n");
+ chans=0;
+ break;
+ }
+#if 0 /* Problem fixed: firmware 3.05 */
+ if (IS_SX_BOARD(board) && (type == TA8)) {
+ /* There are some issues with the firmware and the DCD/RTS
+ lines. It might work if you tie them together or something.
+ It might also work if you get a newer sx_firmware. Therefore
+ this is just a warning. */
+ printk (KERN_WARNING "sx: The SX host doesn't work too well "
+ "with the TA8 adapters.\nSpecialix is working on it.\n");
+ }
+#endif
+ }
+
+ if (chans) {
+ /* board->flags |= SX_BOARD_PRESENT; */
+ if(board->irq > 0) {
+ /* fixed irq, probably PCI */
+ if(sx_irqmask & (1 << board->irq)) { /* may we use this irq? */
+ if(request_irq(board->irq, sx_interrupt, SA_SHIRQ | SA_INTERRUPT, "sx", board)) {
+ printk(KERN_ERR "sx: Cannot allocate irq %d.\n", board->irq);
+ board->irq = 0;
+ }
+ } else
+ board->irq = 0;
+ } else if(board->irq < 0 && sx_irqmask) {
+ /* auto-allocate irq */
+ int irqnr;
+ int irqmask = sx_irqmask & (IS_SX_BOARD(board) ? SX_ISA_IRQ_MASK : SI2_ISA_IRQ_MASK);
+ for(irqnr = 15; irqnr > 0; irqnr--)
+ if(irqmask & (1 << irqnr))
+ if(! request_irq(irqnr, sx_interrupt, SA_SHIRQ | SA_INTERRUPT, "sx", board))
+ break;
+ if(! irqnr)
+ printk(KERN_ERR "sx: Cannot allocate IRQ.\n");
+ board->irq = irqnr;
+ } else
+ board->irq = 0;
+
+ if (board->irq) {
+ /* Found a valid interrupt, start up interrupts! */
+ sx_dprintk (SX_DEBUG_INIT, "Using irq %d.\n", board->irq);
+ sx_start_interrupts (board);
+ board->poll = sx_slowpoll;
+ board->flags |= SX_IRQ_ALLOCATED;
+ } else {
+ /* no irq: setup board for polled operation */
+ board->poll = sx_poll;
+ sx_dprintk (SX_DEBUG_INIT, "Using poll-interval %d.\n", board->poll);
+ }
+
+ /* The timer should be initialized anyway: That way we can safely
+ del_timer it when the module is unloaded. */
+ init_timer (&board->timer);
+
+ if (board->poll) {
+ board->timer.data = (unsigned long) board;
+ board->timer.function = sx_pollfunc;
+ board->timer.expires = jiffies + board->poll;
+ add_timer (&board->timer);
+ }
+ } else {
+ board->irq = 0;
+ }
+
+ board->nports = chans;
+ sx_dprintk (SX_DEBUG_INIT, "returning %d ports.", board->nports);
+
+ func_exit();
+ return chans;
+}
+
+
+static void printheader(void)
+{
+ static int header_printed;
+
+ if (!header_printed) {
+ printk (KERN_INFO "Specialix SX driver "
+ "(C) 1998/1999 R.E.Wolff@BitWizard.nl \n");
+ printk (KERN_INFO "sx: version %s\n", RCS_ID);
+ header_printed = 1;
+ }
+}
+
+
+static int probe_sx (struct sx_board *board)
+{
+ struct vpd_prom vpdp;
+ char *p;
+ int i;
+
+ func_enter();
+
+ if (!IS_CF_BOARD (board)) {
+ sx_dprintk (SX_DEBUG_PROBE, "Going to verify vpd prom at %p.\n",
+ board->base + SX_VPD_ROM);
+
+ if (sx_debug & SX_DEBUG_PROBE)
+ my_hd_io(board->base + SX_VPD_ROM, 0x40);
+
+ p = (char *) &vpdp;
+ for (i=0;i< sizeof (struct vpd_prom);i++)
+ *p++ = read_sx_byte (board, SX_VPD_ROM + i*2);
+
+ if (sx_debug & SX_DEBUG_PROBE)
+ my_hd (&vpdp, 0x20);
+
+ sx_dprintk (SX_DEBUG_PROBE, "checking identifier...\n");
+
+ if (strncmp (vpdp.identifier, SX_VPD_IDENT_STRING, 16) != 0) {
+ sx_dprintk (SX_DEBUG_PROBE, "Got non-SX identifier: '%s'\n",
+ vpdp.identifier);
+ return 0;
+ }
+ }
+
+ printheader ();
+
+ if (!IS_CF_BOARD (board)) {
+ printk (KERN_DEBUG "sx: Found an SX board at %lx\n", board->hw_base);
+ printk (KERN_DEBUG "sx: hw_rev: %d, assembly level: %d, uniq ID:%08x, ",
+ vpdp.hwrev, vpdp.hwass, vpdp.uniqid);
+ printk ( "Manufactured: %d/%d\n",
+ 1970 + vpdp.myear, vpdp.mweek);
+
+
+ if ((((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) != SX_PCI_UNIQUEID1) &&
+ (((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) != SX_ISA_UNIQUEID1)) {
+ /* This might be a bit harsh. This was the primary reason the
+ SX/ISA card didn't work at first... */
+ printk (KERN_ERR "sx: Hmm. Not an SX/PCI or SX/ISA card. Sorry: giving up.\n");
+ return (0);
+ }
+
+ if (((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) == SX_ISA_UNIQUEID1) {
+ if (((unsigned long)board->hw_base) & 0x8000) {
+ printk (KERN_WARNING "sx: Warning: There may be hardware problems with the card at %lx.\n", board->hw_base);
+ printk (KERN_WARNING "sx: Read sx.txt for more info.\n");
+ }
+ }
+ }
+
+ board->nports = -1;
+
+ /* This resets the processor, and keeps it off the bus. */
+ if (!sx_reset (board))
+ return 0;
+ sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+ board->flags |= SX_BOARD_PRESENT;
+
+ func_exit();
+ return 1;
+}
+
+
+
+/* Specialix probes for this card at 32k increments from 640k to 16M.
+ I consider machines with less than 16M unlikely nowadays, so I'm
+ not probing above 1Mb. Also, 0xa0000, 0xb0000, are taken by the VGA
+ card. 0xe0000 and 0xf0000 are taken by the BIOS. That only leaves
+ 0xc0000, 0xc8000, 0xd0000 and 0xd8000 . */
+
+static int probe_si (struct sx_board *board)
+{
+ int i;
+
+ func_enter();
+ sx_dprintk (SX_DEBUG_PROBE, "Going to verify SI signature hw %lx at %p.\n", board->hw_base,
+ board->base + SI2_ISA_ID_BASE);
+
+ if (sx_debug & SX_DEBUG_PROBE)
+ my_hd_io(board->base + SI2_ISA_ID_BASE, 0x8);
+
+ if (!IS_EISA_BOARD(board)) {
+ if( IS_SI1_BOARD(board) )
+ {
+ for (i=0;i<8;i++) {
+ write_sx_byte (board, SI2_ISA_ID_BASE+7-i,i);
+
+ }
+ }
+ for (i=0;i<8;i++) {
+ if ((read_sx_byte (board, SI2_ISA_ID_BASE+7-i) & 7) != i) {
+ func_exit ();
+ return 0;
+ }
+ }
+ }
+
+ /* Now we're pretty much convinced that there is an SI board here,
+ but to prevent trouble, we'd better double check that we don't
+ have an SI1 board when we're probing for an SI2 board.... */
+
+ write_sx_byte (board, SI2_ISA_ID_BASE,0x10);
+ if ( IS_SI1_BOARD(board)) {
+ /* This should be an SI1 board, which has this
+ location writable... */
+ if (read_sx_byte (board, SI2_ISA_ID_BASE) != 0x10)
+ func_exit ();
+ return 0;
+ } else {
+ /* This should be an SI2 board, which has the bottom
+ 3 bits non-writable... */
+ if (read_sx_byte (board, SI2_ISA_ID_BASE) == 0x10)
+ func_exit ();
+ return 0;
+ }
+
+ /* Now we're pretty much convinced that there is an SI board here,
+ but to prevent trouble, we'd better double check that we don't
+ have an SI1 board when we're probing for an SI2 board.... */
+
+ write_sx_byte (board, SI2_ISA_ID_BASE,0x10);
+ if ( IS_SI1_BOARD(board)) {
+ /* This should be an SI1 board, which has this
+ location writable... */
+ if (read_sx_byte (board, SI2_ISA_ID_BASE) != 0x10)
+ func_exit();
+ return 0;
+ } else {
+ /* This should be an SI2 board, which has the bottom
+ 3 bits non-writable... */
+ if (read_sx_byte (board, SI2_ISA_ID_BASE) == 0x10)
+ func_exit ();
+ return 0;
+ }
+
+ printheader ();
+
+ printk (KERN_DEBUG "sx: Found an SI board at %lx\n", board->hw_base);
+ /* Compared to the SX boards, it is a complete guess as to what
+ this card is up to... */
+
+ board->nports = -1;
+
+ /* This resets the processor, and keeps it off the bus. */
+ if (!sx_reset (board))
+ return 0;
+ sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+ board->flags |= SX_BOARD_PRESENT;
+
+ func_exit();
+ return 1;
+}
+
+static struct tty_operations sx_ops = {
+ .break_ctl = sx_break,
+ .open = sx_open,
+ .close = gs_close,
+ .write = gs_write,
+ .put_char = gs_put_char,
+ .flush_chars = gs_flush_chars,
+ .write_room = gs_write_room,
+ .chars_in_buffer = gs_chars_in_buffer,
+ .flush_buffer = gs_flush_buffer,
+ .ioctl = sx_ioctl,
+ .throttle = sx_throttle,
+ .unthrottle = sx_unthrottle,
+ .set_termios = gs_set_termios,
+ .stop = gs_stop,
+ .start = gs_start,
+ .hangup = gs_hangup,
+ .tiocmget = sx_tiocmget,
+ .tiocmset = sx_tiocmset,
+};
+
+static int sx_init_drivers(void)
+{
+ int error;
+
+ func_enter();
+
+ sx_driver = alloc_tty_driver(sx_nports);
+ if (!sx_driver)
+ return 1;
+ sx_driver->owner = THIS_MODULE;
+ sx_driver->driver_name = "specialix_sx";
+ sx_driver->name = "ttyX";
+ sx_driver->major = SX_NORMAL_MAJOR;
+ sx_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ sx_driver->subtype = SERIAL_TYPE_NORMAL;
+ sx_driver->init_termios = tty_std_termios;
+ sx_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ sx_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(sx_driver, &sx_ops);
+
+ if ((error = tty_register_driver(sx_driver))) {
+ put_tty_driver(sx_driver);
+ printk(KERN_ERR "sx: Couldn't register sx driver, error = %d\n",
+ error);
+ return 1;
+ }
+ func_exit();
+ return 0;
+}
+
+
+static void * ckmalloc (int size)
+{
+ void *p;
+
+ p = kmalloc(size, GFP_KERNEL);
+ if (p)
+ memset(p, 0, size);
+ return p;
+}
+
+
+static int sx_init_portstructs (int nboards, int nports)
+{
+ struct sx_board *board;
+ struct sx_port *port;
+ int i, j;
+ int addr, chans;
+ int portno;
+
+ func_enter();
+
+ /* Many drivers statically allocate the maximum number of ports
+ There is no reason not to allocate them dynamically. Is there? -- REW */
+ sx_ports = ckmalloc(nports * sizeof (struct sx_port));
+ if (!sx_ports)
+ return -ENOMEM;
+
+ port = sx_ports;
+ for (i = 0; i < nboards; i++) {
+ board = &boards[i];
+ board->ports = port;
+ for (j=0; j < boards[i].nports;j++) {
+ sx_dprintk (SX_DEBUG_INIT, "initing port %d\n", j);
+ port->gs.magic = SX_MAGIC;
+ port->gs.close_delay = HZ/2;
+ port->gs.closing_wait = 30 * HZ;
+ port->board = board;
+ port->gs.rd = &sx_real_driver;
+#ifdef NEW_WRITE_LOCKING
+ port->gs.port_write_sem = MUTEX;
+#endif
+ port->gs.driver_lock = SPIN_LOCK_UNLOCKED;
+ /*
+ * Initializing wait queue
+ */
+ init_waitqueue_head(&port->gs.open_wait);
+ init_waitqueue_head(&port->gs.close_wait);
+
+ port++;
+ }
+ }
+
+ port = sx_ports;
+ portno = 0;
+ for (i = 0; i < nboards; i++) {
+ board = &boards[i];
+ board->port_base = portno;
+ /* Possibly the configuration was rejected. */
+ sx_dprintk (SX_DEBUG_PROBE, "Board has %d channels\n", board->nports);
+ if (board->nports <= 0) continue;
+ /* XXX byteorder ?? */
+ for (addr = 0x80;addr != 0;addr = read_sx_word (board, addr) & 0x7fff) {
+ chans = sx_read_module_byte (board, addr, mc_type);
+ sx_dprintk (SX_DEBUG_PROBE, "Module at %x: %d channels\n", addr, chans);
+ sx_dprintk (SX_DEBUG_PROBE, "Port at");
+ for (j=0;j<chans;j++) {
+ /* The "sx-way" is the way it SHOULD be done. That way in the
+ future, the firmware may for example pack the structures a bit
+ more efficient. Neil tells me it isn't going to happen anytime
+ soon though. */
+ if (IS_SX_BOARD(board))
+ port->ch_base = sx_read_module_word (board, addr+j*2, mc_chan_pointer);
+ else
+ port->ch_base = addr + 0x100 + 0x300*j;
+
+ sx_dprintk (SX_DEBUG_PROBE, " %x", port->ch_base);
+ port->line = portno++;
+ port++;
+ }
+ sx_dprintk (SX_DEBUG_PROBE, "\n");
+ }
+ /* This has to be done earlier. */
+ /* board->flags |= SX_BOARD_INITIALIZED; */
+ }
+
+ func_exit();
+ return 0;
+}
+
+static void __exit sx_release_drivers(void)
+{
+ func_enter();
+ tty_unregister_driver(sx_driver);
+ put_tty_driver(sx_driver);
+ func_exit();
+}
+
+#ifdef CONFIG_PCI
+ /********************************************************
+ * Setting bit 17 in the CNTRL register of the PLX 9050 *
+ * chip forces a retry on writes while a read is pending.*
+ * This is to prevent the card locking up on Intel Xeon *
+ * multiprocessor systems with the NX chipset. -- NV *
+ ********************************************************/
+
+/* Newer cards are produced with this bit set from the configuration
+ EEprom. As the bit is read/write for the CPU, we can fix it here,
+ if we detect that it isn't set correctly. -- REW */
+
+static void fix_sx_pci (struct pci_dev *pdev, struct sx_board *board)
+{
+ unsigned int hwbase;
+ void __iomem *rebase;
+ unsigned int t;
+
+#define CNTRL_REG_OFFSET 0x50
+#define CNTRL_REG_GOODVALUE 0x18260000
+
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &hwbase);
+ hwbase &= PCI_BASE_ADDRESS_MEM_MASK;
+ rebase = ioremap(hwbase, 0x80);
+ t = readl (rebase + CNTRL_REG_OFFSET);
+ if (t != CNTRL_REG_GOODVALUE) {
+ printk (KERN_DEBUG "sx: performing cntrl reg fix: %08x -> %08x\n", t, CNTRL_REG_GOODVALUE);
+ writel (CNTRL_REG_GOODVALUE, rebase + CNTRL_REG_OFFSET);
+ }
+ iounmap(rebase);
+}
+#endif
+
+
+static int __init sx_init(void)
+{
+ int i;
+ int found = 0;
+ int eisa_slot;
+ struct sx_board *board;
+
+#ifdef CONFIG_PCI
+ struct pci_dev *pdev = NULL;
+ unsigned int tint;
+ unsigned short tshort;
+#endif
+
+ func_enter();
+ sx_dprintk (SX_DEBUG_INIT, "Initing sx module... (sx_debug=%d)\n", sx_debug);
+ if (abs ((long) (&sx_debug) - sx_debug) < 0x10000) {
+ printk (KERN_WARNING "sx: sx_debug is an address, instead of a value. "
+ "Assuming -1.\n");
+ printk ("(%p)\n", &sx_debug);
+ sx_debug=-1;
+ }
+
+ if (misc_register(&sx_fw_device) < 0) {
+ printk(KERN_ERR "SX: Unable to register firmware loader driver.\n");
+ return -EIO;
+ }
+
+#ifdef CONFIG_PCI
+ while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX,
+ PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8,
+ pdev))) {
+ if (pci_enable_device(pdev))
+ continue;
+
+ /* Specialix has a whole bunch of cards with
+ 0x2000 as the device ID. They say its because
+ the standard requires it. Stupid standard. */
+ /* It seems that reading a word doesn't work reliably on 2.0.
+ Also, reading a non-aligned dword doesn't work. So we read the
+ whole dword at 0x2c and extract the word at 0x2e (SUBSYSTEM_ID)
+ ourselves */
+ /* I don't know why the define doesn't work, constant 0x2c does --REW */
+ pci_read_config_dword (pdev, 0x2c, &tint);
+ tshort = (tint >> 16) & 0xffff;
+ sx_dprintk (SX_DEBUG_PROBE, "Got a specialix card: %x.\n", tint);
+ /* sx_dprintk (SX_DEBUG_PROBE, "pdev = %d/%d (%x)\n", pdev, tint); */
+ if ((tshort != 0x0200) && (tshort != 0x0300)) {
+ sx_dprintk (SX_DEBUG_PROBE, "But it's not an SX card (%d)...\n",
+ tshort);
+ continue;
+ }
+ board = &boards[found];
+
+ board->flags &= ~SX_BOARD_TYPE;
+ board->flags |= (tshort == 0x200)?SX_PCI_BOARD:
+ SX_CFPCI_BOARD;
+
+ /* CF boards use base address 3.... */
+ if (IS_CF_BOARD (board))
+ board->hw_base = pci_resource_start (pdev, 3);
+ else
+ board->hw_base = pci_resource_start (pdev, 2);
+ board->base2 =
+ board->base = ioremap(board->hw_base, WINDOW_LEN (board));
+ if (!board->base) {
+ printk(KERN_ERR "ioremap failed\n");
+ /* XXX handle error */
+ }
+
+ /* Most of the stuff on the CF board is offset by
+ 0x18000 .... */
+ if (IS_CF_BOARD (board)) board->base += 0x18000;
+
+ board->irq = pdev->irq;
+
+ sx_dprintk (SX_DEBUG_PROBE, "Got a specialix card: %x/%p(%d) %x.\n",
+ tint, boards[found].base, board->irq, board->flags);
+
+ if (probe_sx (board)) {
+ found++;
+ fix_sx_pci (pdev, board);
+ } else
+ iounmap(board->base2);
+ }
+#endif
+
+ for (i=0;i<NR_SX_ADDRS;i++) {
+ board = &boards[found];
+ board->hw_base = sx_probe_addrs[i];
+ board->base2 =
+ board->base = ioremap(board->hw_base, SX_WINDOW_LEN);
+ board->flags &= ~SX_BOARD_TYPE;
+ board->flags |= SX_ISA_BOARD;
+ board->irq = sx_irqmask?-1:0;
+
+ if (probe_sx (board)) {
+ found++;
+ } else {
+ iounmap(board->base);
+ }
+ }
+
+ for (i=0;i<NR_SI_ADDRS;i++) {
+ board = &boards[found];
+ board->hw_base = si_probe_addrs[i];
+ board->base2 =
+ board->base = ioremap(board->hw_base, SI2_ISA_WINDOW_LEN);
+ board->flags &= ~SX_BOARD_TYPE;
+ board->flags |= SI_ISA_BOARD;
+ board->irq = sx_irqmask ?-1:0;
+
+ if (probe_si (board)) {
+ found++;
+ } else {
+ iounmap (board->base);
+ }
+ }
+ for (i=0;i<NR_SI1_ADDRS;i++) {
+ board = &boards[found];
+ board->hw_base = si1_probe_addrs[i];
+ board->base2 =
+ board->base = ioremap(board->hw_base, SI1_ISA_WINDOW_LEN);
+ board->flags &= ~SX_BOARD_TYPE;
+ board->flags |= SI1_ISA_BOARD;
+ board->irq = sx_irqmask ?-1:0;
+
+ if (probe_si (board)) {
+ found++;
+ } else {
+ iounmap (board->base);
+ }
+ }
+
+ sx_dprintk(SX_DEBUG_PROBE, "Probing for EISA cards\n");
+ for(eisa_slot=0x1000; eisa_slot<0x10000; eisa_slot+=0x1000)
+ {
+ if((inb(eisa_slot+0xc80)==0x4d) &&
+ (inb(eisa_slot+0xc81)==0x98))
+ {
+ sx_dprintk(SX_DEBUG_PROBE, "%s : Signature found in EISA slot %d, Product %d Rev %d\n",
+ "XIO", (eisa_slot>>12), inb(eisa_slot+0xc82), inb(eisa_slot+0xc83));
+
+ board = &boards[found];
+ board->eisa_base = eisa_slot;
+ board->flags &= ~SX_BOARD_TYPE;
+ board->flags |= SI_EISA_BOARD;
+
+ board->hw_base = (((inb(0xc01+eisa_slot) << 8) + inb(0xc00+eisa_slot)) << 16);
+ board->base2 =
+ board->base = ioremap(board->hw_base, SI2_EISA_WINDOW_LEN);
+
+ sx_dprintk(SX_DEBUG_PROBE, "IO hw_base address: %lx\n", board->hw_base);
+ sx_dprintk(SX_DEBUG_PROBE, "base: %p\n", board->base);
+ board->irq = inb(board->eisa_base+0xc02)>>4;
+ sx_dprintk(SX_DEBUG_PROBE, "IRQ: %d\n", board->irq);
+
+ probe_si(board);
+
+ found++;
+ }
+ }
+ if (found) {
+ printk (KERN_INFO "sx: total of %d boards detected.\n", found);
+ } else {
+ misc_deregister(&sx_fw_device);
+ }
+
+ func_exit();
+ return found?0:-EIO;
+}
+
+
+static void __exit sx_exit (void)
+{
+ int i;
+ struct sx_board *board;
+
+ func_enter();
+ for (i = 0; i < SX_NBOARDS; i++) {
+ board = &boards[i];
+ if (board->flags & SX_BOARD_INITIALIZED) {
+ sx_dprintk (SX_DEBUG_CLEANUP, "Cleaning up board at %p\n", board->base);
+ /* The board should stop messing with us.
+ (actually I mean the interrupt) */
+ sx_reset (board);
+ if ((board->irq) && (board->flags & SX_IRQ_ALLOCATED))
+ free_irq (board->irq, board);
+
+ /* It is safe/allowed to del_timer a non-active timer */
+ del_timer (& board->timer);
+ iounmap(board->base);
+ }
+ }
+ if (misc_deregister(&sx_fw_device) < 0) {
+ printk (KERN_INFO "sx: couldn't deregister firmware loader devic\n");
+ }
+ sx_dprintk (SX_DEBUG_CLEANUP, "Cleaning up drivers (%d)\n", sx_initialized);
+ if (sx_initialized)
+ sx_release_drivers ();
+
+ kfree (sx_ports);
+ func_exit();
+}
+
+module_init(sx_init);
+module_exit(sx_exit);
+
+
diff --git a/drivers/char/sx.h b/drivers/char/sx.h
new file mode 100644
index 000000000000..e01f83cbe299
--- /dev/null
+++ b/drivers/char/sx.h
@@ -0,0 +1,202 @@
+
+/*
+ * sx.h
+ *
+ * Copyright (C) 1998/1999 R.E.Wolff@BitWizard.nl
+ *
+ * SX serial driver.
+ * -- Supports SI, XIO and SX host cards.
+ * -- Supports TAs, MTAs and SXDCs.
+ *
+ * Version 1.3 -- March, 1999.
+ *
+ */
+
+#define SX_NBOARDS 4
+#define SX_PORTSPERBOARD 32
+#define SX_NPORTS (SX_NBOARDS * SX_PORTSPERBOARD)
+
+#ifdef __KERNEL__
+
+#define SX_MAGIC 0x12345678
+
+struct sx_port {
+ struct gs_port gs;
+ struct wait_queue *shutdown_wait;
+ int ch_base;
+ int c_dcd;
+ struct sx_board *board;
+ int line;
+ long locks;
+};
+
+struct sx_board {
+ int magic;
+ void __iomem *base;
+ void __iomem *base2;
+ unsigned long hw_base;
+ int eisa_base;
+ int port_base; /* Number of the first port */
+ struct sx_port *ports;
+ int nports;
+ int flags;
+ int irq;
+ int poll;
+ int ta_type;
+ struct timer_list timer;
+ long locks;
+};
+
+struct vpd_prom {
+ unsigned short id;
+ char hwrev;
+ char hwass;
+ int uniqid;
+ char myear;
+ char mweek;
+ char hw_feature[5];
+ char oem_id;
+ char identifier[16];
+};
+
+#ifndef MOD_RS232DB25MALE
+#define MOD_RS232DB25MALE 0x0a
+#endif
+
+#define SI_ISA_BOARD 0x00000001
+#define SX_ISA_BOARD 0x00000002
+#define SX_PCI_BOARD 0x00000004
+#define SX_CFPCI_BOARD 0x00000008
+#define SX_CFISA_BOARD 0x00000010
+#define SI_EISA_BOARD 0x00000020
+#define SI1_ISA_BOARD 0x00000040
+
+#define SX_BOARD_PRESENT 0x00001000
+#define SX_BOARD_INITIALIZED 0x00002000
+#define SX_IRQ_ALLOCATED 0x00004000
+
+#define SX_BOARD_TYPE 0x000000ff
+
+#define IS_SX_BOARD(board) (board->flags & (SX_PCI_BOARD | SX_CFPCI_BOARD | \
+ SX_ISA_BOARD | SX_CFISA_BOARD))
+
+#define IS_SI_BOARD(board) (board->flags & SI_ISA_BOARD)
+#define IS_SI1_BOARD(board) (board->flags & SI1_ISA_BOARD)
+
+#define IS_EISA_BOARD(board) (board->flags & SI_EISA_BOARD)
+
+#define IS_CF_BOARD(board) (board->flags & (SX_CFISA_BOARD | SX_CFPCI_BOARD))
+
+#define SERIAL_TYPE_NORMAL 1
+
+/* The SI processor clock is required to calculate the cc_int_count register
+ value for the SI cards. */
+#define SI_PROCESSOR_CLOCK 25000000
+
+
+/* port flags */
+/* Make sure these don't clash with gs flags or async flags */
+#define SX_RX_THROTTLE 0x0000001
+
+
+
+#define SX_PORT_TRANSMIT_LOCK 0
+#define SX_BOARD_INTR_LOCK 0
+
+
+
+/* Debug flags. Add these together to get more debug info. */
+
+#define SX_DEBUG_OPEN 0x00000001
+#define SX_DEBUG_SETTING 0x00000002
+#define SX_DEBUG_FLOW 0x00000004
+#define SX_DEBUG_MODEMSIGNALS 0x00000008
+#define SX_DEBUG_TERMIOS 0x00000010
+#define SX_DEBUG_TRANSMIT 0x00000020
+#define SX_DEBUG_RECEIVE 0x00000040
+#define SX_DEBUG_INTERRUPTS 0x00000080
+#define SX_DEBUG_PROBE 0x00000100
+#define SX_DEBUG_INIT 0x00000200
+#define SX_DEBUG_CLEANUP 0x00000400
+#define SX_DEBUG_CLOSE 0x00000800
+#define SX_DEBUG_FIRMWARE 0x00001000
+#define SX_DEBUG_MEMTEST 0x00002000
+
+#define SX_DEBUG_ALL 0xffffffff
+
+
+#define O_OTHER(tty) \
+ ((O_OLCUC(tty)) ||\
+ (O_ONLCR(tty)) ||\
+ (O_OCRNL(tty)) ||\
+ (O_ONOCR(tty)) ||\
+ (O_ONLRET(tty)) ||\
+ (O_OFILL(tty)) ||\
+ (O_OFDEL(tty)) ||\
+ (O_NLDLY(tty)) ||\
+ (O_CRDLY(tty)) ||\
+ (O_TABDLY(tty)) ||\
+ (O_BSDLY(tty)) ||\
+ (O_VTDLY(tty)) ||\
+ (O_FFDLY(tty)))
+
+/* Same for input. */
+#define I_OTHER(tty) \
+ ((I_INLCR(tty)) ||\
+ (I_IGNCR(tty)) ||\
+ (I_ICRNL(tty)) ||\
+ (I_IUCLC(tty)) ||\
+ (L_ISIG(tty)))
+
+#define MOD_TA ( TA>>4)
+#define MOD_MTA (MTA_CD1400>>4)
+#define MOD_SXDC ( SXDC>>4)
+
+
+/* We copy the download code over to the card in chunks of ... bytes */
+#define SX_CHUNK_SIZE 128
+
+#endif /* __KERNEL__ */
+
+
+
+/* Specialix document 6210046-11 page 3 */
+#define SPX(X) (('S'<<24) | ('P' << 16) | (X))
+
+/* Specialix-Linux specific IOCTLS. */
+#define SPXL(X) (SPX(('L' << 8) | (X)))
+
+
+#define SXIO_SET_BOARD SPXL(0x01)
+#define SXIO_GET_TYPE SPXL(0x02)
+#define SXIO_DOWNLOAD SPXL(0x03)
+#define SXIO_INIT SPXL(0x04)
+#define SXIO_SETDEBUG SPXL(0x05)
+#define SXIO_GETDEBUG SPXL(0x06)
+#define SXIO_DO_RAMTEST SPXL(0x07)
+#define SXIO_SETGSDEBUG SPXL(0x08)
+#define SXIO_GETGSDEBUG SPXL(0x09)
+#define SXIO_GETNPORTS SPXL(0x0a)
+
+
+#ifndef SXCTL_MISC_MINOR
+/* Allow others to gather this into "major.h" or something like that */
+#define SXCTL_MISC_MINOR 167
+#endif
+
+#ifndef SX_NORMAL_MAJOR
+/* This allows overriding on the compiler commandline, or in a "major.h"
+ include or something like that */
+#define SX_NORMAL_MAJOR 32
+#define SX_CALLOUT_MAJOR 33
+#endif
+
+
+#define SX_TYPE_SX 0x01
+#define SX_TYPE_SI 0x02
+#define SX_TYPE_CF 0x03
+
+
+#define WINDOW_LEN(board) (IS_CF_BOARD(board)?0x20000:SX_WINDOW_LEN)
+/* Need a #define for ^^^^^^^ !!! */
+
diff --git a/drivers/char/sxboards.h b/drivers/char/sxboards.h
new file mode 100644
index 000000000000..427927dc7dbf
--- /dev/null
+++ b/drivers/char/sxboards.h
@@ -0,0 +1,206 @@
+/************************************************************************/
+/* */
+/* Title : SX/SI/XIO Board Hardware Definitions */
+/* */
+/* Author : N.P.Vassallo */
+/* */
+/* Creation : 16th March 1998 */
+/* */
+/* Version : 3.0.0 */
+/* */
+/* Copyright : (c) Specialix International Ltd. 1998 */
+/* */
+/* Description : Prototypes, structures and definitions */
+/* describing the SX/SI/XIO board hardware */
+/* */
+/************************************************************************/
+
+/* History...
+
+3.0.0 16/03/98 NPV Creation.
+
+*/
+
+#ifndef _sxboards_h /* If SXBOARDS.H not already defined */
+#define _sxboards_h 1
+
+/*****************************************************************************
+******************************* ******************************
+******************************* Board Types ******************************
+******************************* ******************************
+*****************************************************************************/
+
+/* BUS types... */
+#define BUS_ISA 0
+#define BUS_MCA 1
+#define BUS_EISA 2
+#define BUS_PCI 3
+
+/* Board phases... */
+#define SI1_Z280 1
+#define SI2_Z280 2
+#define SI3_T225 3
+
+/* Board types... */
+#define CARD_TYPE(bus,phase) (bus<<4|phase)
+#define CARD_BUS(type) ((type>>4)&0xF)
+#define CARD_PHASE(type) (type&0xF)
+
+#define TYPE_SI1_ISA CARD_TYPE(BUS_ISA,SI1_Z280)
+#define TYPE_SI2_ISA CARD_TYPE(BUS_ISA,SI2_Z280)
+#define TYPE_SI2_EISA CARD_TYPE(BUS_EISA,SI2_Z280)
+#define TYPE_SI2_PCI CARD_TYPE(BUS_PCI,SI2_Z280)
+
+#define TYPE_SX_ISA CARD_TYPE(BUS_ISA,SI3_T225)
+#define TYPE_SX_PCI CARD_TYPE(BUS_PCI,SI3_T225)
+/*****************************************************************************
+****************************** ******************************
+****************************** Phase 1 Z280 ******************************
+****************************** ******************************
+*****************************************************************************/
+
+/* ISA board details... */
+#define SI1_ISA_WINDOW_LEN 0x10000 /* 64 Kbyte shared memory window */
+//#define SI1_ISA_MEMORY_LEN 0x8000 /* Usable memory - unused define*/
+//#define SI1_ISA_ADDR_LOW 0x0A0000 /* Lowest address = 640 Kbyte */
+//#define SI1_ISA_ADDR_HIGH 0xFF8000 /* Highest address = 16Mbyte - 32Kbyte */
+//#define SI2_ISA_ADDR_STEP SI2_ISA_WINDOW_LEN/* ISA board address step */
+//#define SI2_ISA_IRQ_MASK 0x9800 /* IRQs 15,12,11 */
+
+/* ISA board, register definitions... */
+//#define SI2_ISA_ID_BASE 0x7FF8 /* READ: Board ID string */
+#define SI1_ISA_RESET 0x8000 /* WRITE: Host Reset */
+#define SI1_ISA_RESET_CLEAR 0xc000 /* WRITE: Host Reset clear*/
+#define SI1_ISA_WAIT 0x9000 /* WRITE: Host wait */
+#define SI1_ISA_WAIT_CLEAR 0xd000 /* WRITE: Host wait clear */
+#define SI1_ISA_INTCL 0xa000 /* WRITE: Host Reset */
+#define SI1_ISA_INTCL_CLEAR 0xe000 /* WRITE: Host Reset */
+
+
+/*****************************************************************************
+****************************** ******************************
+****************************** Phase 2 Z280 ******************************
+****************************** ******************************
+*****************************************************************************/
+
+/* ISA board details... */
+#define SI2_ISA_WINDOW_LEN 0x8000 /* 32 Kbyte shared memory window */
+#define SI2_ISA_MEMORY_LEN 0x7FF8 /* Usable memory */
+#define SI2_ISA_ADDR_LOW 0x0A0000 /* Lowest address = 640 Kbyte */
+#define SI2_ISA_ADDR_HIGH 0xFF8000 /* Highest address = 16Mbyte - 32Kbyte */
+#define SI2_ISA_ADDR_STEP SI2_ISA_WINDOW_LEN/* ISA board address step */
+#define SI2_ISA_IRQ_MASK 0x9800 /* IRQs 15,12,11 */
+
+/* ISA board, register definitions... */
+#define SI2_ISA_ID_BASE 0x7FF8 /* READ: Board ID string */
+#define SI2_ISA_RESET SI2_ISA_ID_BASE /* WRITE: Host Reset */
+#define SI2_ISA_IRQ11 (SI2_ISA_ID_BASE+1) /* WRITE: Set IRQ11 */
+#define SI2_ISA_IRQ12 (SI2_ISA_ID_BASE+2) /* WRITE: Set IRQ12 */
+#define SI2_ISA_IRQ15 (SI2_ISA_ID_BASE+3) /* WRITE: Set IRQ15 */
+#define SI2_ISA_IRQSET (SI2_ISA_ID_BASE+4) /* WRITE: Set Host Interrupt */
+#define SI2_ISA_INTCLEAR (SI2_ISA_ID_BASE+5) /* WRITE: Enable Host Interrupt */
+
+#define SI2_ISA_IRQ11_SET 0x10
+#define SI2_ISA_IRQ11_CLEAR 0x00
+#define SI2_ISA_IRQ12_SET 0x10
+#define SI2_ISA_IRQ12_CLEAR 0x00
+#define SI2_ISA_IRQ15_SET 0x10
+#define SI2_ISA_IRQ15_CLEAR 0x00
+#define SI2_ISA_INTCLEAR_SET 0x10
+#define SI2_ISA_INTCLEAR_CLEAR 0x00
+#define SI2_ISA_IRQSET_CLEAR 0x10
+#define SI2_ISA_IRQSET_SET 0x00
+#define SI2_ISA_RESET_SET 0x00
+#define SI2_ISA_RESET_CLEAR 0x10
+
+/* PCI board details... */
+#define SI2_PCI_WINDOW_LEN 0x100000 /* 1 Mbyte memory window */
+
+/* PCI board register definitions... */
+#define SI2_PCI_SET_IRQ 0x40001 /* Set Host Interrupt */
+#define SI2_PCI_RESET 0xC0001 /* Host Reset */
+
+/*****************************************************************************
+****************************** ******************************
+****************************** Phase 3 T225 ******************************
+****************************** ******************************
+*****************************************************************************/
+
+/* General board details... */
+#define SX_WINDOW_LEN 64*1024 /* 64 Kbyte memory window */
+
+/* ISA board details... */
+#define SX_ISA_ADDR_LOW 0x0A0000 /* Lowest address = 640 Kbyte */
+#define SX_ISA_ADDR_HIGH 0xFF8000 /* Highest address = 16Mbyte - 32Kbyte */
+#define SX_ISA_ADDR_STEP SX_WINDOW_LEN /* ISA board address step */
+#define SX_ISA_IRQ_MASK 0x9E00 /* IRQs 15,12,11,10,9 */
+
+/* Hardware register definitions... */
+#define SX_EVENT_STATUS 0x7800 /* READ: T225 Event Status */
+#define SX_EVENT_STROBE 0x7800 /* WRITE: T225 Event Strobe */
+#define SX_EVENT_ENABLE 0x7880 /* WRITE: T225 Event Enable */
+#define SX_VPD_ROM 0x7C00 /* READ: Vital Product Data ROM */
+#define SX_CONFIG 0x7C00 /* WRITE: Host Configuration Register */
+#define SX_IRQ_STATUS 0x7C80 /* READ: Host Interrupt Status */
+#define SX_SET_IRQ 0x7C80 /* WRITE: Set Host Interrupt */
+#define SX_RESET_STATUS 0x7D00 /* READ: Host Reset Status */
+#define SX_RESET 0x7D00 /* WRITE: Host Reset */
+#define SX_RESET_IRQ 0x7D80 /* WRITE: Reset Host Interrupt */
+
+/* SX_VPD_ROM definitions... */
+#define SX_VPD_SLX_ID1 0x00
+#define SX_VPD_SLX_ID2 0x01
+#define SX_VPD_HW_REV 0x02
+#define SX_VPD_HW_ASSEM 0x03
+#define SX_VPD_UNIQUEID4 0x04
+#define SX_VPD_UNIQUEID3 0x05
+#define SX_VPD_UNIQUEID2 0x06
+#define SX_VPD_UNIQUEID1 0x07
+#define SX_VPD_MANU_YEAR 0x08
+#define SX_VPD_MANU_WEEK 0x09
+#define SX_VPD_IDENT 0x10
+#define SX_VPD_IDENT_STRING "JET HOST BY KEV#"
+
+/* SX unique identifiers... */
+#define SX_UNIQUEID_MASK 0xF0
+#define SX_ISA_UNIQUEID1 0x20
+#define SX_PCI_UNIQUEID1 0x50
+
+/* SX_CONFIG definitions... */
+#define SX_CONF_BUSEN 0x02 /* Enable T225 memory and I/O */
+#define SX_CONF_HOSTIRQ 0x04 /* Enable board to host interrupt */
+
+/* SX bootstrap... */
+#define SX_BOOTSTRAP "\x28\x20\x21\x02\x60\x0a"
+#define SX_BOOTSTRAP_SIZE 6
+#define SX_BOOTSTRAP_ADDR (0x8000-SX_BOOTSTRAP_SIZE)
+
+/*****************************************************************************
+********************************** **********************************
+********************************** EISA **********************************
+********************************** **********************************
+*****************************************************************************/
+
+#define SI2_EISA_OFF 0x42
+#define SI2_EISA_VAL 0x01
+#define SI2_EISA_WINDOW_LEN 0x10000
+
+/*****************************************************************************
+*********************************** **********************************
+*********************************** PCI **********************************
+*********************************** **********************************
+*****************************************************************************/
+
+/* General definitions... */
+
+#define SPX_VENDOR_ID 0x11CB /* Assigned by the PCI SIG */
+#define SPX_DEVICE_ID 0x4000 /* SI/XIO boards */
+#define SPX_PLXDEVICE_ID 0x2000 /* SX boards */
+
+#define SPX_SUB_VENDOR_ID SPX_VENDOR_ID /* Same as vendor id */
+#define SI2_SUB_SYS_ID 0x400 /* Phase 2 (Z280) board */
+#define SX_SUB_SYS_ID 0x200 /* Phase 3 (t225) board */
+
+#endif /*_sxboards_h */
+
+/* End of SXBOARDS.H */
diff --git a/drivers/char/sxwindow.h b/drivers/char/sxwindow.h
new file mode 100644
index 000000000000..cf01b662aefc
--- /dev/null
+++ b/drivers/char/sxwindow.h
@@ -0,0 +1,393 @@
+/************************************************************************/
+/* */
+/* Title : SX Shared Memory Window Structure */
+/* */
+/* Author : N.P.Vassallo */
+/* */
+/* Creation : 16th March 1998 */
+/* */
+/* Version : 3.0.0 */
+/* */
+/* Copyright : (c) Specialix International Ltd. 1998 */
+/* */
+/* Description : Prototypes, structures and definitions */
+/* describing the SX/SI/XIO cards shared */
+/* memory window structure: */
+/* SXCARD */
+/* SXMODULE */
+/* SXCHANNEL */
+/* */
+/************************************************************************/
+
+/* History...
+
+3.0.0 16/03/98 NPV Creation. (based on STRUCT.H)
+
+*/
+
+#ifndef _sxwindow_h /* If SXWINDOW.H not already defined */
+#define _sxwindow_h 1
+
+/*****************************************************************************
+*************************** ***************************
+*************************** Common Definitions ***************************
+*************************** ***************************
+*****************************************************************************/
+
+typedef struct _SXCARD *PSXCARD; /* SXCARD structure pointer */
+typedef struct _SXMODULE *PMOD; /* SXMODULE structure pointer */
+typedef struct _SXCHANNEL *PCHAN; /* SXCHANNEL structure pointer */
+
+/*****************************************************************************
+********************************* *********************************
+********************************* SXCARD *********************************
+********************************* *********************************
+*****************************************************************************/
+
+typedef struct _SXCARD
+{
+ BYTE cc_init_status; /* 0x00 Initialisation status */
+ BYTE cc_mem_size; /* 0x01 Size of memory on card */
+ WORD cc_int_count; /* 0x02 Interrupt count */
+ WORD cc_revision; /* 0x04 Download code revision */
+ BYTE cc_isr_count; /* 0x06 Count when ISR is run */
+ BYTE cc_main_count; /* 0x07 Count when main loop is run */
+ WORD cc_int_pending; /* 0x08 Interrupt pending */
+ WORD cc_poll_count; /* 0x0A Count when poll is run */
+ BYTE cc_int_set_count; /* 0x0C Count when host interrupt is set */
+ BYTE cc_rfu[0x80 - 0x0D]; /* 0x0D Pad structure to 128 bytes (0x80) */
+
+} SXCARD;
+
+/* SXCARD.cc_init_status definitions... */
+#define ADAPTERS_FOUND (BYTE)0x01
+#define NO_ADAPTERS_FOUND (BYTE)0xFF
+
+/* SXCARD.cc_mem_size definitions... */
+#define SX_MEMORY_SIZE (BYTE)0x40
+
+/* SXCARD.cc_int_count definitions... */
+#define INT_COUNT_DEFAULT 100 /* Hz */
+
+/*****************************************************************************
+******************************** ********************************
+******************************** SXMODULE ********************************
+******************************** ********************************
+*****************************************************************************/
+
+#define TOP_POINTER(a) ((a)|0x8000) /* Sets top bit of word */
+#define UNTOP_POINTER(a) ((a)&~0x8000) /* Clears top bit of word */
+
+typedef struct _SXMODULE
+{
+ WORD mc_next; /* 0x00 Next module "pointer" (ORed with 0x8000) */
+ BYTE mc_type; /* 0x02 Type of TA in terms of number of channels */
+ BYTE mc_mod_no; /* 0x03 Module number on SI bus cable (0 closest to card) */
+ BYTE mc_dtr; /* 0x04 Private DTR copy (TA only) */
+ BYTE mc_rfu1; /* 0x05 Reserved */
+ WORD mc_uart; /* 0x06 UART base address for this module */
+ BYTE mc_chip; /* 0x08 Chip type / number of ports */
+ BYTE mc_current_uart; /* 0x09 Current uart selected for this module */
+#ifdef DOWNLOAD
+ PCHAN mc_chan_pointer[8]; /* 0x0A Pointer to each channel structure */
+#else
+ WORD mc_chan_pointer[8]; /* 0x0A Define as WORD if not compiling into download */
+#endif
+ WORD mc_rfu2; /* 0x1A Reserved */
+ BYTE mc_opens1; /* 0x1C Number of open ports on first four ports on MTA/SXDC */
+ BYTE mc_opens2; /* 0x1D Number of open ports on second four ports on MTA/SXDC */
+ BYTE mc_mods; /* 0x1E Types of connector module attached to MTA/SXDC */
+ BYTE mc_rev1; /* 0x1F Revision of first CD1400 on MTA/SXDC */
+ BYTE mc_rev2; /* 0x20 Revision of second CD1400 on MTA/SXDC */
+ BYTE mc_mtaasic_rev; /* 0x21 Revision of MTA ASIC 1..4 -> A, B, C, D */
+ BYTE mc_rfu3[0x100 - 0x22]; /* 0x22 Pad structure to 256 bytes (0x100) */
+
+} SXMODULE;
+
+/* SXMODULE.mc_type definitions... */
+#define FOUR_PORTS (BYTE)4
+#define EIGHT_PORTS (BYTE)8
+
+/* SXMODULE.mc_chip definitions... */
+#define CHIP_MASK 0xF0
+#define TA (BYTE)0
+#define TA4 (TA | FOUR_PORTS)
+#define TA8 (TA | EIGHT_PORTS)
+#define TA4_ASIC (BYTE)0x0A
+#define TA8_ASIC (BYTE)0x0B
+#define MTA_CD1400 (BYTE)0x28
+#define SXDC (BYTE)0x48
+
+/* SXMODULE.mc_mods definitions... */
+#define MOD_RS232DB25 0x00 /* RS232 DB25 (socket/plug) */
+#define MOD_RS232RJ45 0x01 /* RS232 RJ45 (shielded/opto-isolated) */
+#define MOD_RESERVED_2 0x02 /* Reserved (RS485) */
+#define MOD_RS422DB25 0x03 /* RS422 DB25 Socket */
+#define MOD_RESERVED_4 0x04 /* Reserved */
+#define MOD_PARALLEL 0x05 /* Parallel */
+#define MOD_RESERVED_6 0x06 /* Reserved (RS423) */
+#define MOD_RESERVED_7 0x07 /* Reserved */
+#define MOD_2_RS232DB25 0x08 /* Rev 2.0 RS232 DB25 (socket/plug) */
+#define MOD_2_RS232RJ45 0x09 /* Rev 2.0 RS232 RJ45 */
+#define MOD_RESERVED_A 0x0A /* Rev 2.0 Reserved */
+#define MOD_2_RS422DB25 0x0B /* Rev 2.0 RS422 DB25 */
+#define MOD_RESERVED_C 0x0C /* Rev 2.0 Reserved */
+#define MOD_2_PARALLEL 0x0D /* Rev 2.0 Parallel */
+#define MOD_RESERVED_E 0x0E /* Rev 2.0 Reserved */
+#define MOD_BLANK 0x0F /* Blank Panel */
+
+/*****************************************************************************
+******************************** *******************************
+******************************** SXCHANNEL *******************************
+******************************** *******************************
+*****************************************************************************/
+
+#define TX_BUFF_OFFSET 0x60 /* Transmit buffer offset in channel structure */
+#define BUFF_POINTER(a) (((a)+TX_BUFF_OFFSET)|0x8000)
+#define UNBUFF_POINTER(a) (jet_channel*)(((a)&~0x8000)-TX_BUFF_OFFSET)
+#define BUFFER_SIZE 256
+#define HIGH_WATER ((BUFFER_SIZE / 4) * 3)
+#define LOW_WATER (BUFFER_SIZE / 4)
+
+typedef struct _SXCHANNEL
+{
+ WORD next_item; /* 0x00 Offset from window base of next channels hi_txbuf (ORred with 0x8000) */
+ WORD addr_uart; /* 0x02 INTERNAL pointer to uart address. Includes FASTPATH bit */
+ WORD module; /* 0x04 Offset from window base of parent SXMODULE structure */
+ BYTE type; /* 0x06 Chip type / number of ports (copy of mc_chip) */
+ BYTE chan_number; /* 0x07 Channel number on the TA/MTA/SXDC */
+ WORD xc_status; /* 0x08 Flow control and I/O status */
+ BYTE hi_rxipos; /* 0x0A Receive buffer input index */
+ BYTE hi_rxopos; /* 0x0B Receive buffer output index */
+ BYTE hi_txopos; /* 0x0C Transmit buffer output index */
+ BYTE hi_txipos; /* 0x0D Transmit buffer input index */
+ BYTE hi_hstat; /* 0x0E Command register */
+ BYTE dtr_bit; /* 0x0F INTERNAL DTR control byte (TA only) */
+ BYTE txon; /* 0x10 INTERNAL copy of hi_txon */
+ BYTE txoff; /* 0x11 INTERNAL copy of hi_txoff */
+ BYTE rxon; /* 0x12 INTERNAL copy of hi_rxon */
+ BYTE rxoff; /* 0x13 INTERNAL copy of hi_rxoff */
+ BYTE hi_mr1; /* 0x14 Mode Register 1 (databits,parity,RTS rx flow)*/
+ BYTE hi_mr2; /* 0x15 Mode Register 2 (stopbits,local,CTS tx flow)*/
+ BYTE hi_csr; /* 0x16 Clock Select Register (baud rate) */
+ BYTE hi_op; /* 0x17 Modem Output Signal */
+ BYTE hi_ip; /* 0x18 Modem Input Signal */
+ BYTE hi_state; /* 0x19 Channel status */
+ BYTE hi_prtcl; /* 0x1A Channel protocol (flow control) */
+ BYTE hi_txon; /* 0x1B Transmit XON character */
+ BYTE hi_txoff; /* 0x1C Transmit XOFF character */
+ BYTE hi_rxon; /* 0x1D Receive XON character */
+ BYTE hi_rxoff; /* 0x1E Receive XOFF character */
+ BYTE close_prev; /* 0x1F INTERNAL channel previously closed flag */
+ BYTE hi_break; /* 0x20 Break and error control */
+ BYTE break_state; /* 0x21 INTERNAL copy of hi_break */
+ BYTE hi_mask; /* 0x22 Mask for received data */
+ BYTE mask; /* 0x23 INTERNAL copy of hi_mask */
+ BYTE mod_type; /* 0x24 MTA/SXDC hardware module type */
+ BYTE ccr_state; /* 0x25 INTERNAL MTA/SXDC state of CCR register */
+ BYTE ip_mask; /* 0x26 Input handshake mask */
+ BYTE hi_parallel; /* 0x27 Parallel port flag */
+ BYTE par_error; /* 0x28 Error code for parallel loopback test */
+ BYTE any_sent; /* 0x29 INTERNAL data sent flag */
+ BYTE asic_txfifo_size; /* 0x2A INTERNAL SXDC transmit FIFO size */
+ BYTE rfu1[2]; /* 0x2B Reserved */
+ BYTE csr; /* 0x2D INTERNAL copy of hi_csr */
+#ifdef DOWNLOAD
+ PCHAN nextp; /* 0x2E Offset from window base of next channel structure */
+#else
+ WORD nextp; /* 0x2E Define as WORD if not compiling into download */
+#endif
+ BYTE prtcl; /* 0x30 INTERNAL copy of hi_prtcl */
+ BYTE mr1; /* 0x31 INTERNAL copy of hi_mr1 */
+ BYTE mr2; /* 0x32 INTERNAL copy of hi_mr2 */
+ BYTE hi_txbaud; /* 0x33 Extended transmit baud rate (SXDC only if((hi_csr&0x0F)==0x0F) */
+ BYTE hi_rxbaud; /* 0x34 Extended receive baud rate (SXDC only if((hi_csr&0xF0)==0xF0) */
+ BYTE txbreak_state; /* 0x35 INTERNAL MTA/SXDC transmit break state */
+ BYTE txbaud; /* 0x36 INTERNAL copy of hi_txbaud */
+ BYTE rxbaud; /* 0x37 INTERNAL copy of hi_rxbaud */
+ WORD err_framing; /* 0x38 Count of receive framing errors */
+ WORD err_parity; /* 0x3A Count of receive parity errors */
+ WORD err_overrun; /* 0x3C Count of receive overrun errors */
+ WORD err_overflow; /* 0x3E Count of receive buffer overflow errors */
+ BYTE rfu2[TX_BUFF_OFFSET - 0x40]; /* 0x40 Reserved until hi_txbuf */
+ BYTE hi_txbuf[BUFFER_SIZE]; /* 0x060 Transmit buffer */
+ BYTE hi_rxbuf[BUFFER_SIZE]; /* 0x160 Receive buffer */
+ BYTE rfu3[0x300 - 0x260]; /* 0x260 Reserved until 768 bytes (0x300) */
+
+} SXCHANNEL;
+
+/* SXCHANNEL.addr_uart definitions... */
+#define FASTPATH 0x1000 /* Set to indicate fast rx/tx processing (TA only) */
+
+/* SXCHANNEL.xc_status definitions... */
+#define X_TANY 0x0001 /* XON is any character (TA only) */
+#define X_TION 0x0001 /* Tx interrupts on (MTA only) */
+#define X_TXEN 0x0002 /* Tx XON/XOFF enabled (TA only) */
+#define X_RTSEN 0x0002 /* RTS FLOW enabled (MTA only) */
+#define X_TXRC 0x0004 /* XOFF received (TA only) */
+#define X_RTSLOW 0x0004 /* RTS dropped (MTA only) */
+#define X_RXEN 0x0008 /* Rx XON/XOFF enabled */
+#define X_ANYXO 0x0010 /* XOFF pending/sent or RTS dropped */
+#define X_RXSE 0x0020 /* Rx XOFF sent */
+#define X_NPEND 0x0040 /* Rx XON pending or XOFF pending */
+#define X_FPEND 0x0080 /* Rx XOFF pending */
+#define C_CRSE 0x0100 /* Carriage return sent (TA only) */
+#define C_TEMR 0x0100 /* Tx empty requested (MTA only) */
+#define C_TEMA 0x0200 /* Tx empty acked (MTA only) */
+#define C_ANYP 0x0200 /* Any protocol bar tx XON/XOFF (TA only) */
+#define C_EN 0x0400 /* Cooking enabled (on MTA means port is also || */
+#define C_HIGH 0x0800 /* Buffer previously hit high water */
+#define C_CTSEN 0x1000 /* CTS automatic flow-control enabled */
+#define C_DCDEN 0x2000 /* DCD/DTR checking enabled */
+#define C_BREAK 0x4000 /* Break detected */
+#define C_RTSEN 0x8000 /* RTS automatic flow control enabled (MTA only) */
+#define C_PARITY 0x8000 /* Parity checking enabled (TA only) */
+
+/* SXCHANNEL.hi_hstat definitions... */
+#define HS_IDLE_OPEN 0x00 /* Channel open state */
+#define HS_LOPEN 0x02 /* Local open command (no modem monitoring) */
+#define HS_MOPEN 0x04 /* Modem open command (wait for DCD signal) */
+#define HS_IDLE_MPEND 0x06 /* Waiting for DCD signal state */
+#define HS_CONFIG 0x08 /* Configuration command */
+#define HS_CLOSE 0x0A /* Close command */
+#define HS_START 0x0C /* Start transmit break command */
+#define HS_STOP 0x0E /* Stop transmit break command */
+#define HS_IDLE_CLOSED 0x10 /* Closed channel state */
+#define HS_IDLE_BREAK 0x12 /* Transmit break state */
+#define HS_FORCE_CLOSED 0x14 /* Force close command */
+#define HS_RESUME 0x16 /* Clear pending XOFF command */
+#define HS_WFLUSH 0x18 /* Flush transmit buffer command */
+#define HS_RFLUSH 0x1A /* Flush receive buffer command */
+#define HS_SUSPEND 0x1C /* Suspend output command (like XOFF received) */
+#define PARALLEL 0x1E /* Parallel port loopback test command (Diagnostics Only) */
+#define ENABLE_RX_INTS 0x20 /* Enable receive interrupts command (Diagnostics Only) */
+#define ENABLE_TX_INTS 0x22 /* Enable transmit interrupts command (Diagnostics Only) */
+#define ENABLE_MDM_INTS 0x24 /* Enable modem interrupts command (Diagnostics Only) */
+#define DISABLE_INTS 0x26 /* Disable interrupts command (Diagnostics Only) */
+
+/* SXCHANNEL.hi_mr1 definitions... */
+#define MR1_BITS 0x03 /* Data bits mask */
+#define MR1_5_BITS 0x00 /* 5 data bits */
+#define MR1_6_BITS 0x01 /* 6 data bits */
+#define MR1_7_BITS 0x02 /* 7 data bits */
+#define MR1_8_BITS 0x03 /* 8 data bits */
+#define MR1_PARITY 0x1C /* Parity mask */
+#define MR1_ODD 0x04 /* Odd parity */
+#define MR1_EVEN 0x00 /* Even parity */
+#define MR1_WITH 0x00 /* Parity enabled */
+#define MR1_FORCE 0x08 /* Force parity */
+#define MR1_NONE 0x10 /* No parity */
+#define MR1_NOPARITY MR1_NONE /* No parity */
+#define MR1_ODDPARITY (MR1_WITH|MR1_ODD) /* Odd parity */
+#define MR1_EVENPARITY (MR1_WITH|MR1_EVEN) /* Even parity */
+#define MR1_MARKPARITY (MR1_FORCE|MR1_ODD) /* Mark parity */
+#define MR1_SPACEPARITY (MR1_FORCE|MR1_EVEN) /* Space parity */
+#define MR1_RTS_RXFLOW 0x80 /* RTS receive flow control */
+
+/* SXCHANNEL.hi_mr2 definitions... */
+#define MR2_STOP 0x0F /* Stop bits mask */
+#define MR2_1_STOP 0x07 /* 1 stop bit */
+#define MR2_2_STOP 0x0F /* 2 stop bits */
+#define MR2_CTS_TXFLOW 0x10 /* CTS transmit flow control */
+#define MR2_RTS_TOGGLE 0x20 /* RTS toggle on transmit */
+#define MR2_NORMAL 0x00 /* Normal mode */
+#define MR2_AUTO 0x40 /* Auto-echo mode (TA only) */
+#define MR2_LOCAL 0x80 /* Local echo mode */
+#define MR2_REMOTE 0xC0 /* Remote echo mode (TA only) */
+
+/* SXCHANNEL.hi_csr definitions... */
+#define CSR_75 0x0 /* 75 baud */
+#define CSR_110 0x1 /* 110 baud (TA), 115200 (MTA/SXDC) */
+#define CSR_38400 0x2 /* 38400 baud */
+#define CSR_150 0x3 /* 150 baud */
+#define CSR_300 0x4 /* 300 baud */
+#define CSR_600 0x5 /* 600 baud */
+#define CSR_1200 0x6 /* 1200 baud */
+#define CSR_2000 0x7 /* 2000 baud */
+#define CSR_2400 0x8 /* 2400 baud */
+#define CSR_4800 0x9 /* 4800 baud */
+#define CSR_1800 0xA /* 1800 baud */
+#define CSR_9600 0xB /* 9600 baud */
+#define CSR_19200 0xC /* 19200 baud */
+#define CSR_57600 0xD /* 57600 baud */
+#define CSR_EXTBAUD 0xF /* Extended baud rate (hi_txbaud/hi_rxbaud) */
+
+/* SXCHANNEL.hi_op definitions... */
+#define OP_RTS 0x01 /* RTS modem output signal */
+#define OP_DTR 0x02 /* DTR modem output signal */
+
+/* SXCHANNEL.hi_ip definitions... */
+#define IP_CTS 0x02 /* CTS modem input signal */
+#define IP_DCD 0x04 /* DCD modem input signal */
+#define IP_DSR 0x20 /* DTR modem input signal */
+#define IP_RI 0x40 /* RI modem input signal */
+
+/* SXCHANNEL.hi_state definitions... */
+#define ST_BREAK 0x01 /* Break received (clear with config) */
+#define ST_DCD 0x02 /* DCD signal changed state */
+
+/* SXCHANNEL.hi_prtcl definitions... */
+#define SP_TANY 0x01 /* Transmit XON/XANY (if SP_TXEN enabled) */
+#define SP_TXEN 0x02 /* Transmit XON/XOFF flow control */
+#define SP_CEN 0x04 /* Cooking enabled */
+#define SP_RXEN 0x08 /* Rx XON/XOFF enabled */
+#define SP_DCEN 0x20 /* DCD / DTR check */
+#define SP_DTR_RXFLOW 0x40 /* DTR receive flow control */
+#define SP_PAEN 0x80 /* Parity checking enabled */
+
+/* SXCHANNEL.hi_break definitions... */
+#define BR_IGN 0x01 /* Ignore any received breaks */
+#define BR_INT 0x02 /* Interrupt on received break */
+#define BR_PARMRK 0x04 /* Enable parmrk parity error processing */
+#define BR_PARIGN 0x08 /* Ignore chars with parity errors */
+#define BR_ERRINT 0x80 /* Treat parity/framing/overrun errors as exceptions */
+
+/* SXCHANNEL.par_error definitions.. */
+#define DIAG_IRQ_RX 0x01 /* Indicate serial receive interrupt (diags only) */
+#define DIAG_IRQ_TX 0x02 /* Indicate serial transmit interrupt (diags only) */
+#define DIAG_IRQ_MD 0x04 /* Indicate serial modem interrupt (diags only) */
+
+/* SXCHANNEL.hi_txbaud/hi_rxbaud definitions... (SXDC only) */
+#define BAUD_75 0x00 /* 75 baud */
+#define BAUD_115200 0x01 /* 115200 baud */
+#define BAUD_38400 0x02 /* 38400 baud */
+#define BAUD_150 0x03 /* 150 baud */
+#define BAUD_300 0x04 /* 300 baud */
+#define BAUD_600 0x05 /* 600 baud */
+#define BAUD_1200 0x06 /* 1200 baud */
+#define BAUD_2000 0x07 /* 2000 baud */
+#define BAUD_2400 0x08 /* 2400 baud */
+#define BAUD_4800 0x09 /* 4800 baud */
+#define BAUD_1800 0x0A /* 1800 baud */
+#define BAUD_9600 0x0B /* 9600 baud */
+#define BAUD_19200 0x0C /* 19200 baud */
+#define BAUD_57600 0x0D /* 57600 baud */
+#define BAUD_230400 0x0E /* 230400 baud */
+#define BAUD_460800 0x0F /* 460800 baud */
+#define BAUD_921600 0x10 /* 921600 baud */
+#define BAUD_50 0x11 /* 50 baud */
+#define BAUD_110 0x12 /* 110 baud */
+#define BAUD_134_5 0x13 /* 134.5 baud */
+#define BAUD_200 0x14 /* 200 baud */
+#define BAUD_7200 0x15 /* 7200 baud */
+#define BAUD_56000 0x16 /* 56000 baud */
+#define BAUD_64000 0x17 /* 64000 baud */
+#define BAUD_76800 0x18 /* 76800 baud */
+#define BAUD_128000 0x19 /* 128000 baud */
+#define BAUD_150000 0x1A /* 150000 baud */
+#define BAUD_14400 0x1B /* 14400 baud */
+#define BAUD_256000 0x1C /* 256000 baud */
+#define BAUD_28800 0x1D /* 28800 baud */
+
+/* SXCHANNEL.txbreak_state definiions... */
+#define TXBREAK_OFF 0 /* Not sending break */
+#define TXBREAK_START 1 /* Begin sending break */
+#define TXBREAK_START1 2 /* Begin sending break, part 1 */
+#define TXBREAK_ON 3 /* Sending break */
+#define TXBREAK_STOP 4 /* Stop sending break */
+#define TXBREAK_STOP1 5 /* Stop sending break, part 1 */
+
+#endif /* _sxwindow_h */
+
+/* End of SXWINDOW.H */
+
diff --git a/drivers/char/synclink.c b/drivers/char/synclink.c
new file mode 100644
index 000000000000..37c8bea8e2b0
--- /dev/null
+++ b/drivers/char/synclink.c
@@ -0,0 +1,8214 @@
+/*
+ * linux/drivers/char/synclink.c
+ *
+ * $Id: synclink.c,v 4.28 2004/08/11 19:30:01 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink ISA and PCI
+ * high speed multiprotocol serial adapters.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * Derived from serial.c written by Theodore Ts'o and Linus Torvalds
+ *
+ * Original release 01/11/99
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * This driver is primarily intended for use in synchronous
+ * HDLC mode. Asynchronous mode is also provided.
+ *
+ * When operating in synchronous mode, each call to mgsl_write()
+ * contains exactly one complete HDLC frame. Calling mgsl_put_char
+ * will start assembling an HDLC frame that will not be sent until
+ * mgsl_flush_chars or mgsl_write is called.
+ *
+ * Synchronous receive data is reported as complete frames. To accomplish
+ * this, the TTY flip buffer is bypassed (too small to hold largest
+ * frame and may fragment frames) and the line discipline
+ * receive entry point is called directly.
+ *
+ * This driver has been tested with a slightly modified ppp.c driver
+ * for synchronous PPP.
+ *
+ * 2000/02/16
+ * Added interface for syncppp.c driver (an alternate synchronous PPP
+ * implementation that also supports Cisco HDLC). Each device instance
+ * registers as a tty device AND a network device (if dosyncppp option
+ * is set for the device). The functionality is determined by which
+ * device interface is opened.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__i386__)
+# define BREAKPOINT() asm(" int $3");
+#else
+# define BREAKPOINT() { }
+#endif
+
+#define MAX_ISA_DEVICES 10
+#define MAX_PCI_DEVICES 10
+#define MAX_TOTAL_DEVICES 20
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/netdevice.h>
+
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+#define RCLRVALUE 0xffff
+
+static MGSL_PARAMS default_params = {
+ MGSL_MODE_HDLC, /* unsigned long mode */
+ 0, /* unsigned char loopback; */
+ HDLC_FLAG_UNDERRUN_ABORT15, /* unsigned short flags; */
+ HDLC_ENCODING_NRZI_SPACE, /* unsigned char encoding; */
+ 0, /* unsigned long clock_speed; */
+ 0xff, /* unsigned char addr_filter; */
+ HDLC_CRC_16_CCITT, /* unsigned short crc_type; */
+ HDLC_PREAMBLE_LENGTH_8BITS, /* unsigned char preamble_length; */
+ HDLC_PREAMBLE_PATTERN_NONE, /* unsigned char preamble; */
+ 9600, /* unsigned long data_rate; */
+ 8, /* unsigned char data_bits; */
+ 1, /* unsigned char stop_bits; */
+ ASYNC_PARITY_NONE /* unsigned char parity; */
+};
+
+#define SHARED_MEM_ADDRESS_SIZE 0x40000
+#define BUFFERLISTSIZE (PAGE_SIZE)
+#define DMABUFFERSIZE (PAGE_SIZE)
+#define MAXRXFRAMES 7
+
+typedef struct _DMABUFFERENTRY
+{
+ u32 phys_addr; /* 32-bit flat physical address of data buffer */
+ u16 count; /* buffer size/data count */
+ u16 status; /* Control/status field */
+ u16 rcc; /* character count field */
+ u16 reserved; /* padding required by 16C32 */
+ u32 link; /* 32-bit flat link to next buffer entry */
+ char *virt_addr; /* virtual address of data buffer */
+ u32 phys_entry; /* physical address of this buffer entry */
+} DMABUFFERENTRY, *DMAPBUFFERENTRY;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE 1
+#define BH_TRANSMIT 2
+#define BH_STATUS 4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct _input_signal_events {
+ int ri_up;
+ int ri_down;
+ int dsr_up;
+ int dsr_down;
+ int dcd_up;
+ int dcd_down;
+ int cts_up;
+ int cts_down;
+};
+
+/* transmit holding buffer definitions*/
+#define MAX_TX_HOLDING_BUFFERS 5
+struct tx_holding_buffer {
+ int buffer_size;
+ unsigned char * buffer;
+};
+
+
+/*
+ * Device instance data structure
+ */
+
+struct mgsl_struct {
+ int magic;
+ int flags;
+ int count; /* count of opens */
+ int line;
+ int hw_version;
+ unsigned short close_delay;
+ unsigned short closing_wait; /* time to wait before closing */
+
+ struct mgsl_icount icount;
+
+ struct tty_struct *tty;
+ int timeout;
+ int x_char; /* xon/xoff character */
+ int blocked_open; /* # of blocked opens */
+ u16 read_status_mask;
+ u16 ignore_status_mask;
+ unsigned char *xmit_buf;
+ int xmit_head;
+ int xmit_tail;
+ int xmit_cnt;
+
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+
+ wait_queue_head_t status_event_wait_q;
+ wait_queue_head_t event_wait_q;
+ struct timer_list tx_timer; /* HDLC transmit timeout timer */
+ struct mgsl_struct *next_device; /* device list link */
+
+ spinlock_t irq_spinlock; /* spinlock for synchronizing with ISR */
+ struct work_struct task; /* task structure for scheduling bh */
+
+ u32 EventMask; /* event trigger mask */
+ u32 RecordedEvents; /* pending events */
+
+ u32 max_frame_size; /* as set by device config */
+
+ u32 pending_bh;
+
+ int bh_running; /* Protection from multiple */
+ int isr_overflow;
+ int bh_requested;
+
+ int dcd_chkcount; /* check counts to prevent */
+ int cts_chkcount; /* too many IRQs if a signal */
+ int dsr_chkcount; /* is floating */
+ int ri_chkcount;
+
+ char *buffer_list; /* virtual address of Rx & Tx buffer lists */
+ unsigned long buffer_list_phys;
+
+ unsigned int rx_buffer_count; /* count of total allocated Rx buffers */
+ DMABUFFERENTRY *rx_buffer_list; /* list of receive buffer entries */
+ unsigned int current_rx_buffer;
+
+ int num_tx_dma_buffers; /* number of tx dma frames required */
+ int tx_dma_buffers_used;
+ unsigned int tx_buffer_count; /* count of total allocated Tx buffers */
+ DMABUFFERENTRY *tx_buffer_list; /* list of transmit buffer entries */
+ int start_tx_dma_buffer; /* tx dma buffer to start tx dma operation */
+ int current_tx_buffer; /* next tx dma buffer to be loaded */
+
+ unsigned char *intermediate_rxbuffer;
+
+ int num_tx_holding_buffers; /* number of tx holding buffer allocated */
+ int get_tx_holding_index; /* next tx holding buffer for adapter to load */
+ int put_tx_holding_index; /* next tx holding buffer to store user request */
+ int tx_holding_count; /* number of tx holding buffers waiting */
+ struct tx_holding_buffer tx_holding_buffers[MAX_TX_HOLDING_BUFFERS];
+
+ int rx_enabled;
+ int rx_overflow;
+ int rx_rcc_underrun;
+
+ int tx_enabled;
+ int tx_active;
+ u32 idle_mode;
+
+ u16 cmr_value;
+ u16 tcsr_value;
+
+ char device_name[25]; /* device instance name */
+
+ unsigned int bus_type; /* expansion bus type (ISA,EISA,PCI) */
+ unsigned char bus; /* expansion bus number (zero based) */
+ unsigned char function; /* PCI device number */
+
+ unsigned int io_base; /* base I/O address of adapter */
+ unsigned int io_addr_size; /* size of the I/O address range */
+ int io_addr_requested; /* nonzero if I/O address requested */
+
+ unsigned int irq_level; /* interrupt level */
+ unsigned long irq_flags;
+ int irq_requested; /* nonzero if IRQ requested */
+
+ unsigned int dma_level; /* DMA channel */
+ int dma_requested; /* nonzero if dma channel requested */
+
+ u16 mbre_bit;
+ u16 loopback_bits;
+ u16 usc_idle_mode;
+
+ MGSL_PARAMS params; /* communications parameters */
+
+ unsigned char serial_signals; /* current serial signal states */
+
+ int irq_occurred; /* for diagnostics use */
+ unsigned int init_error; /* Initialization startup error (DIAGS) */
+ int fDiagnosticsmode; /* Driver in Diagnostic mode? (DIAGS) */
+
+ u32 last_mem_alloc;
+ unsigned char* memory_base; /* shared memory address (PCI only) */
+ u32 phys_memory_base;
+ int shared_mem_requested;
+
+ unsigned char* lcr_base; /* local config registers (PCI only) */
+ u32 phys_lcr_base;
+ u32 lcr_offset;
+ int lcr_mem_requested;
+
+ u32 misc_ctrl_value;
+ char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+ char char_buf[MAX_ASYNC_BUFFER_SIZE];
+ BOOLEAN drop_rts_on_tx_done;
+
+ BOOLEAN loopmode_insert_requested;
+ BOOLEAN loopmode_send_done_requested;
+
+ struct _input_signal_events input_signal_events;
+
+ /* generic HDLC device parts */
+ int netcount;
+ int dosyncppp;
+ spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+ struct net_device *netdev;
+#endif
+};
+
+#define MGSL_MAGIC 0x5401
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+#ifndef SERIAL_XMIT_SIZE
+#define SERIAL_XMIT_SIZE 4096
+#endif
+
+/*
+ * These macros define the offsets used in calculating the
+ * I/O address of the specified USC registers.
+ */
+
+
+#define DCPIN 2 /* Bit 1 of I/O address */
+#define SDPIN 4 /* Bit 2 of I/O address */
+
+#define DCAR 0 /* DMA command/address register */
+#define CCAR SDPIN /* channel command/address register */
+#define DATAREG DCPIN + SDPIN /* serial data register */
+#define MSBONLY 0x41
+#define LSBONLY 0x40
+
+/*
+ * These macros define the register address (ordinal number)
+ * used for writing address/value pairs to the USC.
+ */
+
+#define CMR 0x02 /* Channel mode Register */
+#define CCSR 0x04 /* Channel Command/status Register */
+#define CCR 0x06 /* Channel Control Register */
+#define PSR 0x08 /* Port status Register */
+#define PCR 0x0a /* Port Control Register */
+#define TMDR 0x0c /* Test mode Data Register */
+#define TMCR 0x0e /* Test mode Control Register */
+#define CMCR 0x10 /* Clock mode Control Register */
+#define HCR 0x12 /* Hardware Configuration Register */
+#define IVR 0x14 /* Interrupt Vector Register */
+#define IOCR 0x16 /* Input/Output Control Register */
+#define ICR 0x18 /* Interrupt Control Register */
+#define DCCR 0x1a /* Daisy Chain Control Register */
+#define MISR 0x1c /* Misc Interrupt status Register */
+#define SICR 0x1e /* status Interrupt Control Register */
+#define RDR 0x20 /* Receive Data Register */
+#define RMR 0x22 /* Receive mode Register */
+#define RCSR 0x24 /* Receive Command/status Register */
+#define RICR 0x26 /* Receive Interrupt Control Register */
+#define RSR 0x28 /* Receive Sync Register */
+#define RCLR 0x2a /* Receive count Limit Register */
+#define RCCR 0x2c /* Receive Character count Register */
+#define TC0R 0x2e /* Time Constant 0 Register */
+#define TDR 0x30 /* Transmit Data Register */
+#define TMR 0x32 /* Transmit mode Register */
+#define TCSR 0x34 /* Transmit Command/status Register */
+#define TICR 0x36 /* Transmit Interrupt Control Register */
+#define TSR 0x38 /* Transmit Sync Register */
+#define TCLR 0x3a /* Transmit count Limit Register */
+#define TCCR 0x3c /* Transmit Character count Register */
+#define TC1R 0x3e /* Time Constant 1 Register */
+
+
+/*
+ * MACRO DEFINITIONS FOR DMA REGISTERS
+ */
+
+#define DCR 0x06 /* DMA Control Register (shared) */
+#define DACR 0x08 /* DMA Array count Register (shared) */
+#define BDCR 0x12 /* Burst/Dwell Control Register (shared) */
+#define DIVR 0x14 /* DMA Interrupt Vector Register (shared) */
+#define DICR 0x18 /* DMA Interrupt Control Register (shared) */
+#define CDIR 0x1a /* Clear DMA Interrupt Register (shared) */
+#define SDIR 0x1c /* Set DMA Interrupt Register (shared) */
+
+#define TDMR 0x02 /* Transmit DMA mode Register */
+#define TDIAR 0x1e /* Transmit DMA Interrupt Arm Register */
+#define TBCR 0x2a /* Transmit Byte count Register */
+#define TARL 0x2c /* Transmit Address Register (low) */
+#define TARU 0x2e /* Transmit Address Register (high) */
+#define NTBCR 0x3a /* Next Transmit Byte count Register */
+#define NTARL 0x3c /* Next Transmit Address Register (low) */
+#define NTARU 0x3e /* Next Transmit Address Register (high) */
+
+#define RDMR 0x82 /* Receive DMA mode Register (non-shared) */
+#define RDIAR 0x9e /* Receive DMA Interrupt Arm Register */
+#define RBCR 0xaa /* Receive Byte count Register */
+#define RARL 0xac /* Receive Address Register (low) */
+#define RARU 0xae /* Receive Address Register (high) */
+#define NRBCR 0xba /* Next Receive Byte count Register */
+#define NRARL 0xbc /* Next Receive Address Register (low) */
+#define NRARU 0xbe /* Next Receive Address Register (high) */
+
+
+/*
+ * MACRO DEFINITIONS FOR MODEM STATUS BITS
+ */
+
+#define MODEMSTATUS_DTR 0x80
+#define MODEMSTATUS_DSR 0x40
+#define MODEMSTATUS_RTS 0x20
+#define MODEMSTATUS_CTS 0x10
+#define MODEMSTATUS_RI 0x04
+#define MODEMSTATUS_DCD 0x01
+
+
+/*
+ * Channel Command/Address Register (CCAR) Command Codes
+ */
+
+#define RTCmd_Null 0x0000
+#define RTCmd_ResetHighestIus 0x1000
+#define RTCmd_TriggerChannelLoadDma 0x2000
+#define RTCmd_TriggerRxDma 0x2800
+#define RTCmd_TriggerTxDma 0x3000
+#define RTCmd_TriggerRxAndTxDma 0x3800
+#define RTCmd_PurgeRxFifo 0x4800
+#define RTCmd_PurgeTxFifo 0x5000
+#define RTCmd_PurgeRxAndTxFifo 0x5800
+#define RTCmd_LoadRcc 0x6800
+#define RTCmd_LoadTcc 0x7000
+#define RTCmd_LoadRccAndTcc 0x7800
+#define RTCmd_LoadTC0 0x8800
+#define RTCmd_LoadTC1 0x9000
+#define RTCmd_LoadTC0AndTC1 0x9800
+#define RTCmd_SerialDataLSBFirst 0xa000
+#define RTCmd_SerialDataMSBFirst 0xa800
+#define RTCmd_SelectBigEndian 0xb000
+#define RTCmd_SelectLittleEndian 0xb800
+
+
+/*
+ * DMA Command/Address Register (DCAR) Command Codes
+ */
+
+#define DmaCmd_Null 0x0000
+#define DmaCmd_ResetTxChannel 0x1000
+#define DmaCmd_ResetRxChannel 0x1200
+#define DmaCmd_StartTxChannel 0x2000
+#define DmaCmd_StartRxChannel 0x2200
+#define DmaCmd_ContinueTxChannel 0x3000
+#define DmaCmd_ContinueRxChannel 0x3200
+#define DmaCmd_PauseTxChannel 0x4000
+#define DmaCmd_PauseRxChannel 0x4200
+#define DmaCmd_AbortTxChannel 0x5000
+#define DmaCmd_AbortRxChannel 0x5200
+#define DmaCmd_InitTxChannel 0x7000
+#define DmaCmd_InitRxChannel 0x7200
+#define DmaCmd_ResetHighestDmaIus 0x8000
+#define DmaCmd_ResetAllChannels 0x9000
+#define DmaCmd_StartAllChannels 0xa000
+#define DmaCmd_ContinueAllChannels 0xb000
+#define DmaCmd_PauseAllChannels 0xc000
+#define DmaCmd_AbortAllChannels 0xd000
+#define DmaCmd_InitAllChannels 0xf000
+
+#define TCmd_Null 0x0000
+#define TCmd_ClearTxCRC 0x2000
+#define TCmd_SelectTicrTtsaData 0x4000
+#define TCmd_SelectTicrTxFifostatus 0x5000
+#define TCmd_SelectTicrIntLevel 0x6000
+#define TCmd_SelectTicrdma_level 0x7000
+#define TCmd_SendFrame 0x8000
+#define TCmd_SendAbort 0x9000
+#define TCmd_EnableDleInsertion 0xc000
+#define TCmd_DisableDleInsertion 0xd000
+#define TCmd_ClearEofEom 0xe000
+#define TCmd_SetEofEom 0xf000
+
+#define RCmd_Null 0x0000
+#define RCmd_ClearRxCRC 0x2000
+#define RCmd_EnterHuntmode 0x3000
+#define RCmd_SelectRicrRtsaData 0x4000
+#define RCmd_SelectRicrRxFifostatus 0x5000
+#define RCmd_SelectRicrIntLevel 0x6000
+#define RCmd_SelectRicrdma_level 0x7000
+
+/*
+ * Bits for enabling and disabling IRQs in Interrupt Control Register (ICR)
+ */
+
+#define RECEIVE_STATUS BIT5
+#define RECEIVE_DATA BIT4
+#define TRANSMIT_STATUS BIT3
+#define TRANSMIT_DATA BIT2
+#define IO_PIN BIT1
+#define MISC BIT0
+
+
+/*
+ * Receive status Bits in Receive Command/status Register RCSR
+ */
+
+#define RXSTATUS_SHORT_FRAME BIT8
+#define RXSTATUS_CODE_VIOLATION BIT8
+#define RXSTATUS_EXITED_HUNT BIT7
+#define RXSTATUS_IDLE_RECEIVED BIT6
+#define RXSTATUS_BREAK_RECEIVED BIT5
+#define RXSTATUS_ABORT_RECEIVED BIT5
+#define RXSTATUS_RXBOUND BIT4
+#define RXSTATUS_CRC_ERROR BIT3
+#define RXSTATUS_FRAMING_ERROR BIT3
+#define RXSTATUS_ABORT BIT2
+#define RXSTATUS_PARITY_ERROR BIT2
+#define RXSTATUS_OVERRUN BIT1
+#define RXSTATUS_DATA_AVAILABLE BIT0
+#define RXSTATUS_ALL 0x01f6
+#define usc_UnlatchRxstatusBits(a,b) usc_OutReg( (a), RCSR, (u16)((b) & RXSTATUS_ALL) )
+
+/*
+ * Values for setting transmit idle mode in
+ * Transmit Control/status Register (TCSR)
+ */
+#define IDLEMODE_FLAGS 0x0000
+#define IDLEMODE_ALT_ONE_ZERO 0x0100
+#define IDLEMODE_ZERO 0x0200
+#define IDLEMODE_ONE 0x0300
+#define IDLEMODE_ALT_MARK_SPACE 0x0500
+#define IDLEMODE_SPACE 0x0600
+#define IDLEMODE_MARK 0x0700
+#define IDLEMODE_MASK 0x0700
+
+/*
+ * IUSC revision identifiers
+ */
+#define IUSC_SL1660 0x4d44
+#define IUSC_PRE_SL1660 0x4553
+
+/*
+ * Transmit status Bits in Transmit Command/status Register (TCSR)
+ */
+
+#define TCSR_PRESERVE 0x0F00
+
+#define TCSR_UNDERWAIT BIT11
+#define TXSTATUS_PREAMBLE_SENT BIT7
+#define TXSTATUS_IDLE_SENT BIT6
+#define TXSTATUS_ABORT_SENT BIT5
+#define TXSTATUS_EOF_SENT BIT4
+#define TXSTATUS_EOM_SENT BIT4
+#define TXSTATUS_CRC_SENT BIT3
+#define TXSTATUS_ALL_SENT BIT2
+#define TXSTATUS_UNDERRUN BIT1
+#define TXSTATUS_FIFO_EMPTY BIT0
+#define TXSTATUS_ALL 0x00fa
+#define usc_UnlatchTxstatusBits(a,b) usc_OutReg( (a), TCSR, (u16)((a)->tcsr_value + ((b) & 0x00FF)) )
+
+
+#define MISCSTATUS_RXC_LATCHED BIT15
+#define MISCSTATUS_RXC BIT14
+#define MISCSTATUS_TXC_LATCHED BIT13
+#define MISCSTATUS_TXC BIT12
+#define MISCSTATUS_RI_LATCHED BIT11
+#define MISCSTATUS_RI BIT10
+#define MISCSTATUS_DSR_LATCHED BIT9
+#define MISCSTATUS_DSR BIT8
+#define MISCSTATUS_DCD_LATCHED BIT7
+#define MISCSTATUS_DCD BIT6
+#define MISCSTATUS_CTS_LATCHED BIT5
+#define MISCSTATUS_CTS BIT4
+#define MISCSTATUS_RCC_UNDERRUN BIT3
+#define MISCSTATUS_DPLL_NO_SYNC BIT2
+#define MISCSTATUS_BRG1_ZERO BIT1
+#define MISCSTATUS_BRG0_ZERO BIT0
+
+#define usc_UnlatchIostatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0xaaa0))
+#define usc_UnlatchMiscstatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0x000f))
+
+#define SICR_RXC_ACTIVE BIT15
+#define SICR_RXC_INACTIVE BIT14
+#define SICR_RXC (BIT15+BIT14)
+#define SICR_TXC_ACTIVE BIT13
+#define SICR_TXC_INACTIVE BIT12
+#define SICR_TXC (BIT13+BIT12)
+#define SICR_RI_ACTIVE BIT11
+#define SICR_RI_INACTIVE BIT10
+#define SICR_RI (BIT11+BIT10)
+#define SICR_DSR_ACTIVE BIT9
+#define SICR_DSR_INACTIVE BIT8
+#define SICR_DSR (BIT9+BIT8)
+#define SICR_DCD_ACTIVE BIT7
+#define SICR_DCD_INACTIVE BIT6
+#define SICR_DCD (BIT7+BIT6)
+#define SICR_CTS_ACTIVE BIT5
+#define SICR_CTS_INACTIVE BIT4
+#define SICR_CTS (BIT5+BIT4)
+#define SICR_RCC_UNDERFLOW BIT3
+#define SICR_DPLL_NO_SYNC BIT2
+#define SICR_BRG1_ZERO BIT1
+#define SICR_BRG0_ZERO BIT0
+
+void usc_DisableMasterIrqBit( struct mgsl_struct *info );
+void usc_EnableMasterIrqBit( struct mgsl_struct *info );
+void usc_EnableInterrupts( struct mgsl_struct *info, u16 IrqMask );
+void usc_DisableInterrupts( struct mgsl_struct *info, u16 IrqMask );
+void usc_ClearIrqPendingBits( struct mgsl_struct *info, u16 IrqMask );
+
+#define usc_EnableInterrupts( a, b ) \
+ usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0xc0 + (b)) )
+
+#define usc_DisableInterrupts( a, b ) \
+ usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0x80 + (b)) )
+
+#define usc_EnableMasterIrqBit(a) \
+ usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0x0f00) + 0xb000) )
+
+#define usc_DisableMasterIrqBit(a) \
+ usc_OutReg( (a), ICR, (u16)(usc_InReg((a),ICR) & 0x7f00) )
+
+#define usc_ClearIrqPendingBits( a, b ) usc_OutReg( (a), DCCR, 0x40 + (b) )
+
+/*
+ * Transmit status Bits in Transmit Control status Register (TCSR)
+ * and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0)
+ */
+
+#define TXSTATUS_PREAMBLE_SENT BIT7
+#define TXSTATUS_IDLE_SENT BIT6
+#define TXSTATUS_ABORT_SENT BIT5
+#define TXSTATUS_EOF BIT4
+#define TXSTATUS_CRC_SENT BIT3
+#define TXSTATUS_ALL_SENT BIT2
+#define TXSTATUS_UNDERRUN BIT1
+#define TXSTATUS_FIFO_EMPTY BIT0
+
+#define DICR_MASTER BIT15
+#define DICR_TRANSMIT BIT0
+#define DICR_RECEIVE BIT1
+
+#define usc_EnableDmaInterrupts(a,b) \
+ usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) | (b)) )
+
+#define usc_DisableDmaInterrupts(a,b) \
+ usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) & ~(b)) )
+
+#define usc_EnableStatusIrqs(a,b) \
+ usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) | (b)) )
+
+#define usc_DisablestatusIrqs(a,b) \
+ usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) & ~(b)) )
+
+/* Transmit status Bits in Transmit Control status Register (TCSR) */
+/* and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0) */
+
+
+#define DISABLE_UNCONDITIONAL 0
+#define DISABLE_END_OF_FRAME 1
+#define ENABLE_UNCONDITIONAL 2
+#define ENABLE_AUTO_CTS 3
+#define ENABLE_AUTO_DCD 3
+#define usc_EnableTransmitter(a,b) \
+ usc_OutReg( (a), TMR, (u16)((usc_InReg((a),TMR) & 0xfffc) | (b)) )
+#define usc_EnableReceiver(a,b) \
+ usc_OutReg( (a), RMR, (u16)((usc_InReg((a),RMR) & 0xfffc) | (b)) )
+
+static u16 usc_InDmaReg( struct mgsl_struct *info, u16 Port );
+static void usc_OutDmaReg( struct mgsl_struct *info, u16 Port, u16 Value );
+static void usc_DmaCmd( struct mgsl_struct *info, u16 Cmd );
+
+static u16 usc_InReg( struct mgsl_struct *info, u16 Port );
+static void usc_OutReg( struct mgsl_struct *info, u16 Port, u16 Value );
+static void usc_RTCmd( struct mgsl_struct *info, u16 Cmd );
+void usc_RCmd( struct mgsl_struct *info, u16 Cmd );
+void usc_TCmd( struct mgsl_struct *info, u16 Cmd );
+
+#define usc_TCmd(a,b) usc_OutReg((a), TCSR, (u16)((a)->tcsr_value + (b)))
+#define usc_RCmd(a,b) usc_OutReg((a), RCSR, (b))
+
+#define usc_SetTransmitSyncChars(a,s0,s1) usc_OutReg((a), TSR, (u16)(((u16)s0<<8)|(u16)s1))
+
+static void usc_process_rxoverrun_sync( struct mgsl_struct *info );
+static void usc_start_receiver( struct mgsl_struct *info );
+static void usc_stop_receiver( struct mgsl_struct *info );
+
+static void usc_start_transmitter( struct mgsl_struct *info );
+static void usc_stop_transmitter( struct mgsl_struct *info );
+static void usc_set_txidle( struct mgsl_struct *info );
+static void usc_load_txfifo( struct mgsl_struct *info );
+
+static void usc_enable_aux_clock( struct mgsl_struct *info, u32 DataRate );
+static void usc_enable_loopback( struct mgsl_struct *info, int enable );
+
+static void usc_get_serial_signals( struct mgsl_struct *info );
+static void usc_set_serial_signals( struct mgsl_struct *info );
+
+static void usc_reset( struct mgsl_struct *info );
+
+static void usc_set_sync_mode( struct mgsl_struct *info );
+static void usc_set_sdlc_mode( struct mgsl_struct *info );
+static void usc_set_async_mode( struct mgsl_struct *info );
+static void usc_enable_async_clock( struct mgsl_struct *info, u32 DataRate );
+
+static void usc_loopback_frame( struct mgsl_struct *info );
+
+static void mgsl_tx_timeout(unsigned long context);
+
+
+static void usc_loopmode_cancel_transmit( struct mgsl_struct * info );
+static void usc_loopmode_insert_request( struct mgsl_struct * info );
+static int usc_loopmode_active( struct mgsl_struct * info);
+static void usc_loopmode_send_done( struct mgsl_struct * info );
+
+static int mgsl_ioctl_common(struct mgsl_struct *info, unsigned int cmd, unsigned long arg);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(struct mgsl_struct *info);
+static void hdlcdev_rx(struct mgsl_struct *info, char *buf, int size);
+static int hdlcdev_init(struct mgsl_struct *info);
+static void hdlcdev_exit(struct mgsl_struct *info);
+#endif
+
+/*
+ * Defines a BUS descriptor value for the PCI adapter
+ * local bus address ranges.
+ */
+
+#define BUS_DESCRIPTOR( WrHold, WrDly, RdDly, Nwdd, Nwad, Nxda, Nrdd, Nrad ) \
+(0x00400020 + \
+((WrHold) << 30) + \
+((WrDly) << 28) + \
+((RdDly) << 26) + \
+((Nwdd) << 20) + \
+((Nwad) << 15) + \
+((Nxda) << 13) + \
+((Nrdd) << 11) + \
+((Nrad) << 6) )
+
+static void mgsl_trace_block(struct mgsl_struct *info,const char* data, int count, int xmit);
+
+/*
+ * Adapter diagnostic routines
+ */
+static BOOLEAN mgsl_register_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_irq_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_dma_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_memory_test( struct mgsl_struct *info );
+static int mgsl_adapter_test( struct mgsl_struct *info );
+
+/*
+ * device and resource management routines
+ */
+static int mgsl_claim_resources(struct mgsl_struct *info);
+static void mgsl_release_resources(struct mgsl_struct *info);
+static void mgsl_add_device(struct mgsl_struct *info);
+static struct mgsl_struct* mgsl_allocate_device(void);
+
+/*
+ * DMA buffer manupulation functions.
+ */
+static void mgsl_free_rx_frame_buffers( struct mgsl_struct *info, unsigned int StartIndex, unsigned int EndIndex );
+static int mgsl_get_rx_frame( struct mgsl_struct *info );
+static int mgsl_get_raw_rx_frame( struct mgsl_struct *info );
+static void mgsl_reset_rx_dma_buffers( struct mgsl_struct *info );
+static void mgsl_reset_tx_dma_buffers( struct mgsl_struct *info );
+static int num_free_tx_dma_buffers(struct mgsl_struct *info);
+static void mgsl_load_tx_dma_buffer( struct mgsl_struct *info, const char *Buffer, unsigned int BufferSize);
+static void mgsl_load_pci_memory(char* TargetPtr, const char* SourcePtr, unsigned short count);
+
+/*
+ * DMA and Shared Memory buffer allocation and formatting
+ */
+static int mgsl_allocate_dma_buffers(struct mgsl_struct *info);
+static void mgsl_free_dma_buffers(struct mgsl_struct *info);
+static int mgsl_alloc_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
+static void mgsl_free_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
+static int mgsl_alloc_buffer_list_memory(struct mgsl_struct *info);
+static void mgsl_free_buffer_list_memory(struct mgsl_struct *info);
+static int mgsl_alloc_intermediate_rxbuffer_memory(struct mgsl_struct *info);
+static void mgsl_free_intermediate_rxbuffer_memory(struct mgsl_struct *info);
+static int mgsl_alloc_intermediate_txbuffer_memory(struct mgsl_struct *info);
+static void mgsl_free_intermediate_txbuffer_memory(struct mgsl_struct *info);
+static int load_next_tx_holding_buffer(struct mgsl_struct *info);
+static int save_tx_buffer_request(struct mgsl_struct *info,const char *Buffer, unsigned int BufferSize);
+
+/*
+ * Bottom half interrupt handlers
+ */
+static void mgsl_bh_handler(void* Context);
+static void mgsl_bh_receive(struct mgsl_struct *info);
+static void mgsl_bh_transmit(struct mgsl_struct *info);
+static void mgsl_bh_status(struct mgsl_struct *info);
+
+/*
+ * Interrupt handler routines and dispatch table.
+ */
+static void mgsl_isr_null( struct mgsl_struct *info );
+static void mgsl_isr_transmit_data( struct mgsl_struct *info );
+static void mgsl_isr_receive_data( struct mgsl_struct *info );
+static void mgsl_isr_receive_status( struct mgsl_struct *info );
+static void mgsl_isr_transmit_status( struct mgsl_struct *info );
+static void mgsl_isr_io_pin( struct mgsl_struct *info );
+static void mgsl_isr_misc( struct mgsl_struct *info );
+static void mgsl_isr_receive_dma( struct mgsl_struct *info );
+static void mgsl_isr_transmit_dma( struct mgsl_struct *info );
+
+typedef void (*isr_dispatch_func)(struct mgsl_struct *);
+
+static isr_dispatch_func UscIsrTable[7] =
+{
+ mgsl_isr_null,
+ mgsl_isr_misc,
+ mgsl_isr_io_pin,
+ mgsl_isr_transmit_data,
+ mgsl_isr_transmit_status,
+ mgsl_isr_receive_data,
+ mgsl_isr_receive_status
+};
+
+/*
+ * ioctl call handlers
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file);
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount
+ __user *user_icount);
+static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS __user *user_params);
+static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS __user *new_params);
+static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode);
+static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode);
+static int mgsl_txenable(struct mgsl_struct * info, int enable);
+static int mgsl_txabort(struct mgsl_struct * info);
+static int mgsl_rxenable(struct mgsl_struct * info, int enable);
+static int mgsl_wait_event(struct mgsl_struct * info, int __user *mask);
+static int mgsl_loopmode_send_done( struct mgsl_struct * info );
+
+/* set non-zero on successful registration with PCI subsystem */
+static int pci_registered;
+
+/*
+ * Global linked list of SyncLink devices
+ */
+static struct mgsl_struct *mgsl_device_list;
+static int mgsl_device_count;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor;
+
+/*
+ * Array of user specified options for ISA adapters.
+ */
+static int io[MAX_ISA_DEVICES];
+static int irq[MAX_ISA_DEVICES];
+static int dma[MAX_ISA_DEVICES];
+static int debug_level;
+static int maxframe[MAX_TOTAL_DEVICES];
+static int dosyncppp[MAX_TOTAL_DEVICES];
+static int txdmabufs[MAX_TOTAL_DEVICES];
+static int txholdbufs[MAX_TOTAL_DEVICES];
+
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param_array(io, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param_array(dma, int, NULL, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+module_param_array(txdmabufs, int, NULL, 0);
+module_param_array(txholdbufs, int, NULL, 0);
+
+static char *driver_name = "SyncLink serial driver";
+static char *driver_version = "$Revision: 4.28 $";
+
+static int synclink_init_one (struct pci_dev *dev,
+ const struct pci_device_id *ent);
+static void synclink_remove_one (struct pci_dev *dev);
+
+static struct pci_device_id synclink_pci_tbl[] = {
+ { PCI_VENDOR_ID_MICROGATE, PCI_DEVICE_ID_MICROGATE_USC, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_MICROGATE, 0x0210, PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, synclink_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+static struct pci_driver synclink_pci_driver = {
+ .name = "synclink",
+ .id_table = synclink_pci_tbl,
+ .probe = synclink_init_one,
+ .remove = __devexit_p(synclink_remove_one),
+};
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+
+static void mgsl_change_params(struct mgsl_struct *info);
+static void mgsl_wait_until_sent(struct tty_struct *tty, int timeout);
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* mgsl_get_text_ptr(void)
+{
+ return mgsl_get_text_ptr;
+}
+
+/*
+ * tmp_buf is used as a temporary buffer by mgsl_write. We need to
+ * lock it in case the COPY_FROM_USER blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ioports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static inline int mgsl_paranoia_check(struct mgsl_struct *info,
+ char *name, const char *routine)
+{
+#ifdef MGSL_PARANOIA_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for mgsl struct (%s) in %s\n";
+ static const char *badinfo =
+ "Warning: null mgsl_struct for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (info->magic != MGSL_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#else
+ if (!info)
+ return 1;
+#endif
+ return 0;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_receive_buf - pass receive data to line discipline
+ */
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+ const __u8 *data, char *flags, int count)
+{
+ struct tty_ldisc *ld;
+ if (!tty)
+ return;
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->receive_buf)
+ ld->receive_buf(tty, data, flags, count);
+ tty_ldisc_deref(ld);
+ }
+}
+
+/* mgsl_stop() throttle (stop) transmitter
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static void mgsl_stop(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_stop"))
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("mgsl_stop(%s)\n",info->device_name);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (info->tx_enabled)
+ usc_stop_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+} /* end of mgsl_stop() */
+
+/* mgsl_start() release (start) transmitter
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static void mgsl_start(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_start"))
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("mgsl_start(%s)\n",info->device_name);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (!info->tx_enabled)
+ usc_start_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+} /* end of mgsl_start() */
+
+/*
+ * Bottom half work queue access functions
+ */
+
+/* mgsl_bh_action() Return next bottom half action to perform.
+ * Return Value: BH action code or 0 if nothing to do.
+ */
+static int mgsl_bh_action(struct mgsl_struct *info)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ if (info->pending_bh & BH_RECEIVE) {
+ info->pending_bh &= ~BH_RECEIVE;
+ rc = BH_RECEIVE;
+ } else if (info->pending_bh & BH_TRANSMIT) {
+ info->pending_bh &= ~BH_TRANSMIT;
+ rc = BH_TRANSMIT;
+ } else if (info->pending_bh & BH_STATUS) {
+ info->pending_bh &= ~BH_STATUS;
+ rc = BH_STATUS;
+ }
+
+ if (!rc) {
+ /* Mark BH routine as complete */
+ info->bh_running = 0;
+ info->bh_requested = 0;
+ }
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return rc;
+}
+
+/*
+ * Perform bottom half processing of work items queued by ISR.
+ */
+static void mgsl_bh_handler(void* Context)
+{
+ struct mgsl_struct *info = (struct mgsl_struct*)Context;
+ int action;
+
+ if (!info)
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_handler(%s) entry\n",
+ __FILE__,__LINE__,info->device_name);
+
+ info->bh_running = 1;
+
+ while((action = mgsl_bh_action(info)) != 0) {
+
+ /* Process work item */
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_handler() work item action=%d\n",
+ __FILE__,__LINE__,action);
+
+ switch (action) {
+
+ case BH_RECEIVE:
+ mgsl_bh_receive(info);
+ break;
+ case BH_TRANSMIT:
+ mgsl_bh_transmit(info);
+ break;
+ case BH_STATUS:
+ mgsl_bh_status(info);
+ break;
+ default:
+ /* unknown work item ID */
+ printk("Unknown work item ID=%08X!\n", action);
+ break;
+ }
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_handler(%s) exit\n",
+ __FILE__,__LINE__,info->device_name);
+}
+
+static void mgsl_bh_receive(struct mgsl_struct *info)
+{
+ int (*get_rx_frame)(struct mgsl_struct *info) =
+ (info->params.mode == MGSL_MODE_HDLC ? mgsl_get_rx_frame : mgsl_get_raw_rx_frame);
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_receive(%s)\n",
+ __FILE__,__LINE__,info->device_name);
+
+ do
+ {
+ if (info->rx_rcc_underrun) {
+ unsigned long flags;
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_start_receiver(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ return;
+ }
+ } while(get_rx_frame(info));
+}
+
+static void mgsl_bh_transmit(struct mgsl_struct *info)
+{
+ struct tty_struct *tty = info->tty;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_transmit() entry on %s\n",
+ __FILE__,__LINE__,info->device_name);
+
+ if (tty) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+
+ /* if transmitter idle and loopmode_send_done_requested
+ * then start echoing RxD to TxD
+ */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if ( !info->tx_active && info->loopmode_send_done_requested )
+ usc_loopmode_send_done( info );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+}
+
+static void mgsl_bh_status(struct mgsl_struct *info)
+{
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):mgsl_bh_status() entry on %s\n",
+ __FILE__,__LINE__,info->device_name);
+
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+}
+
+/* mgsl_isr_receive_status()
+ *
+ * Service a receive status interrupt. The type of status
+ * interrupt is indicated by the state of the RCSR.
+ * This is only used for HDLC mode.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_receive_status( struct mgsl_struct *info )
+{
+ u16 status = usc_InReg( info, RCSR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_receive_status status=%04X\n",
+ __FILE__,__LINE__,status);
+
+ if ( (status & RXSTATUS_ABORT_RECEIVED) &&
+ info->loopmode_insert_requested &&
+ usc_loopmode_active(info) )
+ {
+ ++info->icount.rxabort;
+ info->loopmode_insert_requested = FALSE;
+
+ /* clear CMR:13 to start echoing RxD to TxD */
+ info->cmr_value &= ~BIT13;
+ usc_OutReg(info, CMR, info->cmr_value);
+
+ /* disable received abort irq (no longer required) */
+ usc_OutReg(info, RICR,
+ (usc_InReg(info, RICR) & ~RXSTATUS_ABORT_RECEIVED));
+ }
+
+ if (status & (RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED)) {
+ if (status & RXSTATUS_EXITED_HUNT)
+ info->icount.exithunt++;
+ if (status & RXSTATUS_IDLE_RECEIVED)
+ info->icount.rxidle++;
+ wake_up_interruptible(&info->event_wait_q);
+ }
+
+ if (status & RXSTATUS_OVERRUN){
+ info->icount.rxover++;
+ usc_process_rxoverrun_sync( info );
+ }
+
+ usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+ usc_UnlatchRxstatusBits( info, status );
+
+} /* end of mgsl_isr_receive_status() */
+
+/* mgsl_isr_transmit_status()
+ *
+ * Service a transmit status interrupt
+ * HDLC mode :end of transmit frame
+ * Async mode:all data is sent
+ * transmit status is indicated by bits in the TCSR.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_transmit_status( struct mgsl_struct *info )
+{
+ u16 status = usc_InReg( info, TCSR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_transmit_status status=%04X\n",
+ __FILE__,__LINE__,status);
+
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+ usc_UnlatchTxstatusBits( info, status );
+
+ if ( status & (TXSTATUS_UNDERRUN | TXSTATUS_ABORT_SENT) )
+ {
+ /* finished sending HDLC abort. This may leave */
+ /* the TxFifo with data from the aborted frame */
+ /* so purge the TxFifo. Also shutdown the DMA */
+ /* channel in case there is data remaining in */
+ /* the DMA buffer */
+ usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+ }
+
+ if ( status & TXSTATUS_EOF_SENT )
+ info->icount.txok++;
+ else if ( status & TXSTATUS_UNDERRUN )
+ info->icount.txunder++;
+ else if ( status & TXSTATUS_ABORT_SENT )
+ info->icount.txabort++;
+ else
+ info->icount.txunder++;
+
+ info->tx_active = 0;
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ del_timer(&info->tx_timer);
+
+ if ( info->drop_rts_on_tx_done ) {
+ usc_get_serial_signals( info );
+ if ( info->serial_signals & SerialSignal_RTS ) {
+ info->serial_signals &= ~SerialSignal_RTS;
+ usc_set_serial_signals( info );
+ }
+ info->drop_rts_on_tx_done = 0;
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ {
+ if (info->tty->stopped || info->tty->hw_stopped) {
+ usc_stop_transmitter(info);
+ return;
+ }
+ info->pending_bh |= BH_TRANSMIT;
+ }
+
+} /* end of mgsl_isr_transmit_status() */
+
+/* mgsl_isr_io_pin()
+ *
+ * Service an Input/Output pin interrupt. The type of
+ * interrupt is indicated by bits in the MISR
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_io_pin( struct mgsl_struct *info )
+{
+ struct mgsl_icount *icount;
+ u16 status = usc_InReg( info, MISR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_io_pin status=%04X\n",
+ __FILE__,__LINE__,status);
+
+ usc_ClearIrqPendingBits( info, IO_PIN );
+ usc_UnlatchIostatusBits( info, status );
+
+ if (status & (MISCSTATUS_CTS_LATCHED | MISCSTATUS_DCD_LATCHED |
+ MISCSTATUS_DSR_LATCHED | MISCSTATUS_RI_LATCHED) ) {
+ icount = &info->icount;
+ /* update input line counters */
+ if (status & MISCSTATUS_RI_LATCHED) {
+ if ((info->ri_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ usc_DisablestatusIrqs(info,SICR_RI);
+ icount->rng++;
+ if ( status & MISCSTATUS_RI )
+ info->input_signal_events.ri_up++;
+ else
+ info->input_signal_events.ri_down++;
+ }
+ if (status & MISCSTATUS_DSR_LATCHED) {
+ if ((info->dsr_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ usc_DisablestatusIrqs(info,SICR_DSR);
+ icount->dsr++;
+ if ( status & MISCSTATUS_DSR )
+ info->input_signal_events.dsr_up++;
+ else
+ info->input_signal_events.dsr_down++;
+ }
+ if (status & MISCSTATUS_DCD_LATCHED) {
+ if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ usc_DisablestatusIrqs(info,SICR_DCD);
+ icount->dcd++;
+ if (status & MISCSTATUS_DCD) {
+ info->input_signal_events.dcd_up++;
+ } else
+ info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlc_set_carrier(status & MISCSTATUS_DCD, info->netdev);
+#endif
+ }
+ if (status & MISCSTATUS_CTS_LATCHED)
+ {
+ if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+ usc_DisablestatusIrqs(info,SICR_CTS);
+ icount->cts++;
+ if ( status & MISCSTATUS_CTS )
+ info->input_signal_events.cts_up++;
+ else
+ info->input_signal_events.cts_down++;
+ }
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ if ( (info->flags & ASYNC_CHECK_CD) &&
+ (status & MISCSTATUS_DCD_LATCHED) ) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s CD now %s...", info->device_name,
+ (status & MISCSTATUS_DCD) ? "on" : "off");
+ if (status & MISCSTATUS_DCD)
+ wake_up_interruptible(&info->open_wait);
+ else {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("doing serial hangup...");
+ if (info->tty)
+ tty_hangup(info->tty);
+ }
+ }
+
+ if ( (info->flags & ASYNC_CTS_FLOW) &&
+ (status & MISCSTATUS_CTS_LATCHED) ) {
+ if (info->tty->hw_stopped) {
+ if (status & MISCSTATUS_CTS) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("CTS tx start...");
+ if (info->tty)
+ info->tty->hw_stopped = 0;
+ usc_start_transmitter(info);
+ info->pending_bh |= BH_TRANSMIT;
+ return;
+ }
+ } else {
+ if (!(status & MISCSTATUS_CTS)) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("CTS tx stop...");
+ if (info->tty)
+ info->tty->hw_stopped = 1;
+ usc_stop_transmitter(info);
+ }
+ }
+ }
+ }
+
+ info->pending_bh |= BH_STATUS;
+
+ /* for diagnostics set IRQ flag */
+ if ( status & MISCSTATUS_TXC_LATCHED ){
+ usc_OutReg( info, SICR,
+ (unsigned short)(usc_InReg(info,SICR) & ~(SICR_TXC_ACTIVE+SICR_TXC_INACTIVE)) );
+ usc_UnlatchIostatusBits( info, MISCSTATUS_TXC_LATCHED );
+ info->irq_occurred = 1;
+ }
+
+} /* end of mgsl_isr_io_pin() */
+
+/* mgsl_isr_transmit_data()
+ *
+ * Service a transmit data interrupt (async mode only).
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_transmit_data( struct mgsl_struct *info )
+{
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_transmit_data xmit_cnt=%d\n",
+ __FILE__,__LINE__,info->xmit_cnt);
+
+ usc_ClearIrqPendingBits( info, TRANSMIT_DATA );
+
+ if (info->tty->stopped || info->tty->hw_stopped) {
+ usc_stop_transmitter(info);
+ return;
+ }
+
+ if ( info->xmit_cnt )
+ usc_load_txfifo( info );
+ else
+ info->tx_active = 0;
+
+ if (info->xmit_cnt < WAKEUP_CHARS)
+ info->pending_bh |= BH_TRANSMIT;
+
+} /* end of mgsl_isr_transmit_data() */
+
+/* mgsl_isr_receive_data()
+ *
+ * Service a receive data interrupt. This occurs
+ * when operating in asynchronous interrupt transfer mode.
+ * The receive data FIFO is flushed to the receive data buffers.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_receive_data( struct mgsl_struct *info )
+{
+ int Fifocount;
+ u16 status;
+ unsigned char DataByte;
+ struct tty_struct *tty = info->tty;
+ struct mgsl_icount *icount = &info->icount;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_receive_data\n",
+ __FILE__,__LINE__);
+
+ usc_ClearIrqPendingBits( info, RECEIVE_DATA );
+
+ /* select FIFO status for RICR readback */
+ usc_RCmd( info, RCmd_SelectRicrRxFifostatus );
+
+ /* clear the Wordstatus bit so that status readback */
+ /* only reflects the status of this byte */
+ usc_OutReg( info, RICR+LSBONLY, (u16)(usc_InReg(info, RICR+LSBONLY) & ~BIT3 ));
+
+ /* flush the receive FIFO */
+
+ while( (Fifocount = (usc_InReg(info,RICR) >> 8)) ) {
+ /* read one byte from RxFIFO */
+ outw( (inw(info->io_base + CCAR) & 0x0780) | (RDR+LSBONLY),
+ info->io_base + CCAR );
+ DataByte = inb( info->io_base + CCAR );
+
+ /* get the status of the received byte */
+ status = usc_InReg(info, RCSR);
+ if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
+ RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) )
+ usc_UnlatchRxstatusBits(info,RXSTATUS_ALL);
+
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ continue;
+
+ *tty->flip.char_buf_ptr = DataByte;
+ icount->rx++;
+
+ *tty->flip.flag_buf_ptr = 0;
+ if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
+ RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) ) {
+ printk("rxerr=%04X\n",status);
+ /* update error statistics */
+ if ( status & RXSTATUS_BREAK_RECEIVED ) {
+ status &= ~(RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR);
+ icount->brk++;
+ } else if (status & RXSTATUS_PARITY_ERROR)
+ icount->parity++;
+ else if (status & RXSTATUS_FRAMING_ERROR)
+ icount->frame++;
+ else if (status & RXSTATUS_OVERRUN) {
+ /* must issue purge fifo cmd before */
+ /* 16C32 accepts more receive chars */
+ usc_RTCmd(info,RTCmd_PurgeRxFifo);
+ icount->overrun++;
+ }
+
+ /* discard char if tty control flags say so */
+ if (status & info->ignore_status_mask)
+ continue;
+
+ status &= info->read_status_mask;
+
+ if (status & RXSTATUS_BREAK_RECEIVED) {
+ *tty->flip.flag_buf_ptr = TTY_BREAK;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ } else if (status & RXSTATUS_PARITY_ERROR)
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ else if (status & RXSTATUS_FRAMING_ERROR)
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ if (status & RXSTATUS_OVERRUN) {
+ /* Overrun is special, since it's
+ * reported immediately, and doesn't
+ * affect the current character
+ */
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ tty->flip.count++;
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ *tty->flip.flag_buf_ptr = TTY_OVERRUN;
+ }
+ }
+ } /* end of if (error) */
+
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_ISR ) {
+ printk("%s(%d):mgsl_isr_receive_data flip count=%d\n",
+ __FILE__,__LINE__,tty->flip.count);
+ printk("%s(%d):rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+ __FILE__,__LINE__,icount->rx,icount->brk,
+ icount->parity,icount->frame,icount->overrun);
+ }
+
+ if ( tty->flip.count )
+ tty_flip_buffer_push(tty);
+}
+
+/* mgsl_isr_misc()
+ *
+ * Service a miscellaneos interrupt source.
+ *
+ * Arguments: info pointer to device extension (instance data)
+ * Return Value: None
+ */
+static void mgsl_isr_misc( struct mgsl_struct *info )
+{
+ u16 status = usc_InReg( info, MISR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_misc status=%04X\n",
+ __FILE__,__LINE__,status);
+
+ if ((status & MISCSTATUS_RCC_UNDERRUN) &&
+ (info->params.mode == MGSL_MODE_HDLC)) {
+
+ /* turn off receiver and rx DMA */
+ usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+ usc_DmaCmd(info, DmaCmd_ResetRxChannel);
+ usc_UnlatchRxstatusBits(info, RXSTATUS_ALL);
+ usc_ClearIrqPendingBits(info, RECEIVE_DATA + RECEIVE_STATUS);
+ usc_DisableInterrupts(info, RECEIVE_DATA + RECEIVE_STATUS);
+
+ /* schedule BH handler to restart receiver */
+ info->pending_bh |= BH_RECEIVE;
+ info->rx_rcc_underrun = 1;
+ }
+
+ usc_ClearIrqPendingBits( info, MISC );
+ usc_UnlatchMiscstatusBits( info, status );
+
+} /* end of mgsl_isr_misc() */
+
+/* mgsl_isr_null()
+ *
+ * Services undefined interrupt vectors from the
+ * USC. (hence this function SHOULD never be called)
+ *
+ * Arguments: info pointer to device extension (instance data)
+ * Return Value: None
+ */
+static void mgsl_isr_null( struct mgsl_struct *info )
+{
+
+} /* end of mgsl_isr_null() */
+
+/* mgsl_isr_receive_dma()
+ *
+ * Service a receive DMA channel interrupt.
+ * For this driver there are two sources of receive DMA interrupts
+ * as identified in the Receive DMA mode Register (RDMR):
+ *
+ * BIT3 EOA/EOL End of List, all receive buffers in receive
+ * buffer list have been filled (no more free buffers
+ * available). The DMA controller has shut down.
+ *
+ * BIT2 EOB End of Buffer. This interrupt occurs when a receive
+ * DMA buffer is terminated in response to completion
+ * of a good frame or a frame with errors. The status
+ * of the frame is stored in the buffer entry in the
+ * list of receive buffer entries.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_receive_dma( struct mgsl_struct *info )
+{
+ u16 status;
+
+ /* clear interrupt pending and IUS bit for Rx DMA IRQ */
+ usc_OutDmaReg( info, CDIR, BIT9+BIT1 );
+
+ /* Read the receive DMA status to identify interrupt type. */
+ /* This also clears the status bits. */
+ status = usc_InDmaReg( info, RDMR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_receive_dma(%s) status=%04X\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ info->pending_bh |= BH_RECEIVE;
+
+ if ( status & BIT3 ) {
+ info->rx_overflow = 1;
+ info->icount.buf_overrun++;
+ }
+
+} /* end of mgsl_isr_receive_dma() */
+
+/* mgsl_isr_transmit_dma()
+ *
+ * This function services a transmit DMA channel interrupt.
+ *
+ * For this driver there is one source of transmit DMA interrupts
+ * as identified in the Transmit DMA Mode Register (TDMR):
+ *
+ * BIT2 EOB End of Buffer. This interrupt occurs when a
+ * transmit DMA buffer has been emptied.
+ *
+ * The driver maintains enough transmit DMA buffers to hold at least
+ * one max frame size transmit frame. When operating in a buffered
+ * transmit mode, there may be enough transmit DMA buffers to hold at
+ * least two or more max frame size frames. On an EOB condition,
+ * determine if there are any queued transmit buffers and copy into
+ * transmit DMA buffers if we have room.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_isr_transmit_dma( struct mgsl_struct *info )
+{
+ u16 status;
+
+ /* clear interrupt pending and IUS bit for Tx DMA IRQ */
+ usc_OutDmaReg(info, CDIR, BIT8+BIT0 );
+
+ /* Read the transmit DMA status to identify interrupt type. */
+ /* This also clears the status bits. */
+
+ status = usc_InDmaReg( info, TDMR );
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_isr_transmit_dma(%s) status=%04X\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ if ( status & BIT2 ) {
+ --info->tx_dma_buffers_used;
+
+ /* if there are transmit frames queued,
+ * try to load the next one
+ */
+ if ( load_next_tx_holding_buffer(info) ) {
+ /* if call returns non-zero value, we have
+ * at least one free tx holding buffer
+ */
+ info->pending_bh |= BH_TRANSMIT;
+ }
+ }
+
+} /* end of mgsl_isr_transmit_dma() */
+
+/* mgsl_interrupt()
+ *
+ * Interrupt service routine entry point.
+ *
+ * Arguments:
+ *
+ * irq interrupt number that caused interrupt
+ * dev_id device ID supplied during interrupt registration
+ * regs interrupted processor context
+ *
+ * Return Value: None
+ */
+static irqreturn_t mgsl_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+ struct mgsl_struct * info;
+ u16 UscVector;
+ u16 DmaVector;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_interrupt(%d)entry.\n",
+ __FILE__,__LINE__,irq);
+
+ info = (struct mgsl_struct *)dev_id;
+ if (!info)
+ return IRQ_NONE;
+
+ spin_lock(&info->irq_spinlock);
+
+ for(;;) {
+ /* Read the interrupt vectors from hardware. */
+ UscVector = usc_InReg(info, IVR) >> 9;
+ DmaVector = usc_InDmaReg(info, DIVR);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s UscVector=%08X DmaVector=%08X\n",
+ __FILE__,__LINE__,info->device_name,UscVector,DmaVector);
+
+ if ( !UscVector && !DmaVector )
+ break;
+
+ /* Dispatch interrupt vector */
+ if ( UscVector )
+ (*UscIsrTable[UscVector])(info);
+ else if ( (DmaVector&(BIT10|BIT9)) == BIT10)
+ mgsl_isr_transmit_dma(info);
+ else
+ mgsl_isr_receive_dma(info);
+
+ if ( info->isr_overflow ) {
+ printk(KERN_ERR"%s(%d):%s isr overflow irq=%d\n",
+ __FILE__,__LINE__,info->device_name, irq);
+ usc_DisableMasterIrqBit(info);
+ usc_DisableDmaInterrupts(info,DICR_MASTER);
+ break;
+ }
+ }
+
+ /* Request bottom half processing if there's something
+ * for it to do and the bh is not already running
+ */
+
+ if ( info->pending_bh && !info->bh_running && !info->bh_requested ) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s queueing bh task.\n",
+ __FILE__,__LINE__,info->device_name);
+ schedule_work(&info->task);
+ info->bh_requested = 1;
+ }
+
+ spin_unlock(&info->irq_spinlock);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):mgsl_interrupt(%d)exit.\n",
+ __FILE__,__LINE__,irq);
+ return IRQ_HANDLED;
+} /* end of mgsl_interrupt() */
+
+/* startup()
+ *
+ * Initialize and start device.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: 0 if success, otherwise error code
+ */
+static int startup(struct mgsl_struct * info)
+{
+ int retval = 0;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):mgsl_startup(%s)\n",__FILE__,__LINE__,info->device_name);
+
+ if (info->flags & ASYNC_INITIALIZED)
+ return 0;
+
+ if (!info->xmit_buf) {
+ /* allocate a page of memory for a transmit buffer */
+ info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
+ if (!info->xmit_buf) {
+ printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+ __FILE__,__LINE__,info->device_name);
+ return -ENOMEM;
+ }
+ }
+
+ info->pending_bh = 0;
+
+ init_timer(&info->tx_timer);
+ info->tx_timer.data = (unsigned long)info;
+ info->tx_timer.function = mgsl_tx_timeout;
+
+ /* Allocate and claim adapter resources */
+ retval = mgsl_claim_resources(info);
+
+ /* perform existence check and diagnostics */
+ if ( !retval )
+ retval = mgsl_adapter_test(info);
+
+ if ( retval ) {
+ if (capable(CAP_SYS_ADMIN) && info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+ mgsl_release_resources(info);
+ return retval;
+ }
+
+ /* program hardware for current parameters */
+ mgsl_change_params(info);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags |= ASYNC_INITIALIZED;
+
+ return 0;
+
+} /* end of startup() */
+
+/* shutdown()
+ *
+ * Called by mgsl_close() and mgsl_hangup() to shutdown hardware
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void shutdown(struct mgsl_struct * info)
+{
+ unsigned long flags;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_shutdown(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ /* clear status wait queue because status changes */
+ /* can't happen after shutting down the hardware */
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ del_timer(&info->tx_timer);
+
+ if (info->xmit_buf) {
+ free_page((unsigned long) info->xmit_buf);
+ info->xmit_buf = NULL;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_DisableMasterIrqBit(info);
+ usc_stop_receiver(info);
+ usc_stop_transmitter(info);
+ usc_DisableInterrupts(info,RECEIVE_DATA + RECEIVE_STATUS +
+ TRANSMIT_DATA + TRANSMIT_STATUS + IO_PIN + MISC );
+ usc_DisableDmaInterrupts(info,DICR_MASTER + DICR_TRANSMIT + DICR_RECEIVE);
+
+ /* Disable DMAEN (Port 7, Bit 14) */
+ /* This disconnects the DMA request signal from the ISA bus */
+ /* on the ISA adapter. This has no effect for the PCI adapter */
+ usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT15) | BIT14));
+
+ /* Disable INTEN (Port 6, Bit12) */
+ /* This disconnects the IRQ request signal to the ISA bus */
+ /* on the ISA adapter. This has no effect for the PCI adapter */
+ usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) | BIT12));
+
+ if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+ usc_set_serial_signals(info);
+ }
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ mgsl_release_resources(info);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+
+} /* end of shutdown() */
+
+static void mgsl_program_hw(struct mgsl_struct *info)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ usc_stop_receiver(info);
+ usc_stop_transmitter(info);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ||
+ info->netcount)
+ usc_set_sync_mode(info);
+ else
+ usc_set_async_mode(info);
+
+ usc_set_serial_signals(info);
+
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+
+ usc_EnableStatusIrqs(info,SICR_CTS+SICR_DSR+SICR_DCD+SICR_RI);
+ usc_EnableInterrupts(info, IO_PIN);
+ usc_get_serial_signals(info);
+
+ if (info->netcount || info->tty->termios->c_cflag & CREAD)
+ usc_start_receiver(info);
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void mgsl_change_params(struct mgsl_struct *info)
+{
+ unsigned cflag;
+ int bits_per_char;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_change_params(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ cflag = info->tty->termios->c_cflag;
+
+ /* if B0 rate (hangup) specified then negate DTR and RTS */
+ /* otherwise assert DTR and RTS */
+ if (cflag & CBAUD)
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ else
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+
+ /* byte size and parity */
+
+ switch (cflag & CSIZE) {
+ case CS5: info->params.data_bits = 5; break;
+ case CS6: info->params.data_bits = 6; break;
+ case CS7: info->params.data_bits = 7; break;
+ case CS8: info->params.data_bits = 8; break;
+ /* Never happens, but GCC is too dumb to figure it out */
+ default: info->params.data_bits = 7; break;
+ }
+
+ if (cflag & CSTOPB)
+ info->params.stop_bits = 2;
+ else
+ info->params.stop_bits = 1;
+
+ info->params.parity = ASYNC_PARITY_NONE;
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ info->params.parity = ASYNC_PARITY_ODD;
+ else
+ info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+ if (cflag & CMSPAR)
+ info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+ }
+
+ /* calculate number of jiffies to transmit a full
+ * FIFO (32 bytes) at specified data rate
+ */
+ bits_per_char = info->params.data_bits +
+ info->params.stop_bits + 1;
+
+ /* if port data rate is set to 460800 or less then
+ * allow tty settings to override, otherwise keep the
+ * current data rate.
+ */
+ if (info->params.data_rate <= 460800)
+ info->params.data_rate = tty_get_baud_rate(info->tty);
+
+ if ( info->params.data_rate ) {
+ info->timeout = (32*HZ*bits_per_char) /
+ info->params.data_rate;
+ }
+ info->timeout += HZ/50; /* Add .02 seconds of slop */
+
+ if (cflag & CRTSCTS)
+ info->flags |= ASYNC_CTS_FLOW;
+ else
+ info->flags &= ~ASYNC_CTS_FLOW;
+
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ /* process tty input control flags */
+
+ info->read_status_mask = RXSTATUS_OVERRUN;
+ if (I_INPCK(info->tty))
+ info->read_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask |= RXSTATUS_BREAK_RECEIVED;
+
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask |= RXSTATUS_BREAK_RECEIVED;
+ /* If ignoring parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask |= RXSTATUS_OVERRUN;
+ }
+
+ mgsl_program_hw(info);
+
+} /* end of mgsl_change_params() */
+
+/* mgsl_put_char()
+ *
+ * Add a character to the transmit buffer.
+ *
+ * Arguments: tty pointer to tty information structure
+ * ch character to add to transmit buffer
+ *
+ * Return Value: None
+ */
+static void mgsl_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO ) {
+ printk( "%s(%d):mgsl_put_char(%d) on %s\n",
+ __FILE__,__LINE__,ch,info->device_name);
+ }
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_put_char"))
+ return;
+
+ if (!tty || !info->xmit_buf)
+ return;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ if ( (info->params.mode == MGSL_MODE_ASYNC ) || !info->tx_active ) {
+
+ if (info->xmit_cnt < SERIAL_XMIT_SIZE - 1) {
+ info->xmit_buf[info->xmit_head++] = ch;
+ info->xmit_head &= SERIAL_XMIT_SIZE-1;
+ info->xmit_cnt++;
+ }
+ }
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+} /* end of mgsl_put_char() */
+
+/* mgsl_flush_chars()
+ *
+ * Enable transmitter so remaining characters in the
+ * transmit buffer are sent.
+ *
+ * Arguments: tty pointer to tty information structure
+ * Return Value: None
+ */
+static void mgsl_flush_chars(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_flush_chars() entry on %s xmit_cnt=%d\n",
+ __FILE__,__LINE__,info->device_name,info->xmit_cnt);
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_chars"))
+ return;
+
+ if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+ !info->xmit_buf)
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_flush_chars() entry on %s starting transmitter\n",
+ __FILE__,__LINE__,info->device_name );
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ if (!info->tx_active) {
+ if ( (info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW) && info->xmit_cnt ) {
+ /* operating in synchronous (frame oriented) mode */
+ /* copy data from circular xmit_buf to */
+ /* transmit DMA buffer. */
+ mgsl_load_tx_dma_buffer(info,
+ info->xmit_buf,info->xmit_cnt);
+ }
+ usc_start_transmitter(info);
+ }
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+} /* end of mgsl_flush_chars() */
+
+/* mgsl_write()
+ *
+ * Send a block of data
+ *
+ * Arguments:
+ *
+ * tty pointer to tty information structure
+ * buf pointer to buffer containing send data
+ * count size of send data in bytes
+ *
+ * Return Value: number of characters written
+ */
+static int mgsl_write(struct tty_struct * tty,
+ const unsigned char *buf, int count)
+{
+ int c, ret = 0;
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_write(%s) count=%d\n",
+ __FILE__,__LINE__,info->device_name,count);
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_write"))
+ goto cleanup;
+
+ if (!tty || !info->xmit_buf || !tmp_buf)
+ goto cleanup;
+
+ if ( info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ /* operating in synchronous (frame oriented) mode */
+ /* operating in synchronous (frame oriented) mode */
+ if (info->tx_active) {
+
+ if ( info->params.mode == MGSL_MODE_HDLC ) {
+ ret = 0;
+ goto cleanup;
+ }
+ /* transmitter is actively sending data -
+ * if we have multiple transmit dma and
+ * holding buffers, attempt to queue this
+ * frame for transmission at a later time.
+ */
+ if (info->tx_holding_count >= info->num_tx_holding_buffers ) {
+ /* no tx holding buffers available */
+ ret = 0;
+ goto cleanup;
+ }
+
+ /* queue transmit frame request */
+ ret = count;
+ save_tx_buffer_request(info,buf,count);
+
+ /* if we have sufficient tx dma buffers,
+ * load the next buffered tx request
+ */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ load_next_tx_holding_buffer(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ goto cleanup;
+ }
+
+ /* if operating in HDLC LoopMode and the adapter */
+ /* has yet to be inserted into the loop, we can't */
+ /* transmit */
+
+ if ( (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) &&
+ !usc_loopmode_active(info) )
+ {
+ ret = 0;
+ goto cleanup;
+ }
+
+ if ( info->xmit_cnt ) {
+ /* Send accumulated from send_char() calls */
+ /* as frame and wait before accepting more data. */
+ ret = 0;
+
+ /* copy data from circular xmit_buf to */
+ /* transmit DMA buffer. */
+ mgsl_load_tx_dma_buffer(info,
+ info->xmit_buf,info->xmit_cnt);
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_write(%s) sync xmit_cnt flushing\n",
+ __FILE__,__LINE__,info->device_name);
+ } else {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_write(%s) sync transmit accepted\n",
+ __FILE__,__LINE__,info->device_name);
+ ret = count;
+ info->xmit_cnt = count;
+ mgsl_load_tx_dma_buffer(info,buf,count);
+ }
+ } else {
+ while (1) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ c = min_t(int, count,
+ min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+ SERIAL_XMIT_SIZE - info->xmit_head));
+ if (c <= 0) {
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ break;
+ }
+ memcpy(info->xmit_buf + info->xmit_head, buf, c);
+ info->xmit_head = ((info->xmit_head + c) &
+ (SERIAL_XMIT_SIZE-1));
+ info->xmit_cnt += c;
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+ }
+
+ if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (!info->tx_active)
+ usc_start_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+cleanup:
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_write(%s) returning=%d\n",
+ __FILE__,__LINE__,info->device_name,ret);
+
+ return ret;
+
+} /* end of mgsl_write() */
+
+/* mgsl_write_room()
+ *
+ * Return the count of free bytes in transmit buffer
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static int mgsl_write_room(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ int ret;
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_write_room"))
+ return 0;
+ ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+ if (ret < 0)
+ ret = 0;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_write_room(%s)=%d\n",
+ __FILE__,__LINE__, info->device_name,ret );
+
+ if ( info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ /* operating in synchronous (frame oriented) mode */
+ if ( info->tx_active )
+ return 0;
+ else
+ return HDLC_MAX_FRAME_SIZE;
+ }
+
+ return ret;
+
+} /* end of mgsl_write_room() */
+
+/* mgsl_chars_in_buffer()
+ *
+ * Return the count of bytes in transmit buffer
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static int mgsl_chars_in_buffer(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_chars_in_buffer(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_chars_in_buffer"))
+ return 0;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_chars_in_buffer(%s)=%d\n",
+ __FILE__,__LINE__, info->device_name,info->xmit_cnt );
+
+ if ( info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ /* operating in synchronous (frame oriented) mode */
+ if ( info->tx_active )
+ return info->max_frame_size;
+ else
+ return 0;
+ }
+
+ return info->xmit_cnt;
+} /* end of mgsl_chars_in_buffer() */
+
+/* mgsl_flush_buffer()
+ *
+ * Discard all data in the send buffer
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static void mgsl_flush_buffer(struct tty_struct *tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_flush_buffer(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_buffer"))
+ return;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+ del_timer(&info->tx_timer);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+/* mgsl_send_xchar()
+ *
+ * Send a high-priority XON/XOFF character
+ *
+ * Arguments: tty pointer to tty info structure
+ * ch character to send
+ * Return Value: None
+ */
+static void mgsl_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_send_xchar(%s,%d)\n",
+ __FILE__,__LINE__, info->device_name, ch );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_send_xchar"))
+ return;
+
+ info->x_char = ch;
+ if (ch) {
+ /* Make sure transmit interrupts are on */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (!info->tx_enabled)
+ usc_start_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+} /* end of mgsl_send_xchar() */
+
+/* mgsl_throttle()
+ *
+ * Signal remote device to throttle send data (our receive data)
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static void mgsl_throttle(struct tty_struct * tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_throttle(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_throttle"))
+ return;
+
+ if (I_IXOFF(tty))
+ mgsl_send_xchar(tty, STOP_CHAR(tty));
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->serial_signals &= ~SerialSignal_RTS;
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+} /* end of mgsl_throttle() */
+
+/* mgsl_unthrottle()
+ *
+ * Signal remote device to stop throttling send data (our receive data)
+ *
+ * Arguments: tty pointer to tty info structure
+ * Return Value: None
+ */
+static void mgsl_unthrottle(struct tty_struct * tty)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_unthrottle(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_unthrottle"))
+ return;
+
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else
+ mgsl_send_xchar(tty, START_CHAR(tty));
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->serial_signals |= SerialSignal_RTS;
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+
+} /* end of mgsl_unthrottle() */
+
+/* mgsl_get_stats()
+ *
+ * get the current serial parameters information
+ *
+ * Arguments: info pointer to device instance data
+ * user_icount pointer to buffer to hold returned stats
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount __user *user_icount)
+{
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_get_params(%s)\n",
+ __FILE__,__LINE__, info->device_name);
+
+ COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_get_stats(%s) user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+
+} /* end of mgsl_get_stats() */
+
+/* mgsl_get_params()
+ *
+ * get the current serial parameters information
+ *
+ * Arguments: info pointer to device instance data
+ * user_params pointer to buffer to hold returned params
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS __user *user_params)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_get_params(%s)\n",
+ __FILE__,__LINE__, info->device_name);
+
+ COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_get_params(%s) user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+
+} /* end of mgsl_get_params() */
+
+/* mgsl_set_params()
+ *
+ * set the serial parameters
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * new_params user buffer containing new serial params
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS __user *new_params)
+{
+ unsigned long flags;
+ MGSL_PARAMS tmp_params;
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_set_params %s\n", __FILE__,__LINE__,
+ info->device_name );
+ COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_set_params(%s) user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ mgsl_change_params(info);
+
+ return 0;
+
+} /* end of mgsl_set_params() */
+
+/* mgsl_get_txidle()
+ *
+ * get the current transmit idle mode
+ *
+ * Arguments: info pointer to device instance data
+ * idle_mode pointer to buffer to hold returned idle mode
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode)
+{
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_get_txidle(%s)=%d\n",
+ __FILE__,__LINE__, info->device_name, info->idle_mode);
+
+ COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_get_txidle(%s) user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+
+} /* end of mgsl_get_txidle() */
+
+/* mgsl_set_txidle() service ioctl to set transmit idle mode
+ *
+ * Arguments: info pointer to device instance data
+ * idle_mode new idle mode
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_set_txidle(%s,%d)\n", __FILE__,__LINE__,
+ info->device_name, idle_mode );
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->idle_mode = idle_mode;
+ usc_set_txidle( info );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ return 0;
+
+} /* end of mgsl_set_txidle() */
+
+/* mgsl_txenable()
+ *
+ * enable or disable the transmitter
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * enable 1 = enable, 0 = disable
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_txenable(struct mgsl_struct * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_txenable(%s,%d)\n", __FILE__,__LINE__,
+ info->device_name, enable);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if ( enable ) {
+ if ( !info->tx_enabled ) {
+
+ usc_start_transmitter(info);
+ /*--------------------------------------------------
+ * if HDLC/SDLC Loop mode, attempt to insert the
+ * station in the 'loop' by setting CMR:13. Upon
+ * receipt of the next GoAhead (RxAbort) sequence,
+ * the OnLoop indicator (CCSR:7) should go active
+ * to indicate that we are on the loop
+ *--------------------------------------------------*/
+ if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+ usc_loopmode_insert_request( info );
+ }
+ } else {
+ if ( info->tx_enabled )
+ usc_stop_transmitter(info);
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ return 0;
+
+} /* end of mgsl_txenable() */
+
+/* mgsl_txabort() abort send HDLC frame
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_txabort(struct mgsl_struct * info)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_txabort(%s)\n", __FILE__,__LINE__,
+ info->device_name);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if ( info->tx_active && info->params.mode == MGSL_MODE_HDLC )
+ {
+ if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+ usc_loopmode_cancel_transmit( info );
+ else
+ usc_TCmd(info,TCmd_SendAbort);
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ return 0;
+
+} /* end of mgsl_txabort() */
+
+/* mgsl_rxenable() enable or disable the receiver
+ *
+ * Arguments: info pointer to device instance data
+ * enable 1 = enable, 0 = disable
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_rxenable(struct mgsl_struct * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_rxenable(%s,%d)\n", __FILE__,__LINE__,
+ info->device_name, enable);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if ( enable ) {
+ if ( !info->rx_enabled )
+ usc_start_receiver(info);
+ } else {
+ if ( info->rx_enabled )
+ usc_stop_receiver(info);
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ return 0;
+
+} /* end of mgsl_rxenable() */
+
+/* mgsl_wait_event() wait for specified event to occur
+ *
+ * Arguments: info pointer to device instance data
+ * mask pointer to bitmask of events to wait for
+ * Return Value: 0 if successful and bit mask updated with
+ * of events triggerred,
+ * otherwise error code
+ */
+static int mgsl_wait_event(struct mgsl_struct * info, int __user * mask_ptr)
+{
+ unsigned long flags;
+ int s;
+ int rc=0;
+ struct mgsl_icount cprev, cnow;
+ int events;
+ int mask;
+ struct _input_signal_events oldsigs, newsigs;
+ DECLARE_WAITQUEUE(wait, current);
+
+ COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+ if (rc) {
+ return -EFAULT;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_wait_event(%s,%d)\n", __FILE__,__LINE__,
+ info->device_name, mask);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ /* return immediately if state matches requested events */
+ usc_get_serial_signals(info);
+ s = info->serial_signals;
+ events = mask &
+ ( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+ ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+ ((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+ if (events) {
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ goto exit;
+ }
+
+ /* save current irq counts */
+ cprev = info->icount;
+ oldsigs = info->input_signal_events;
+
+ /* enable hunt and idle irqs if needed */
+ if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+ u16 oldreg = usc_InReg(info,RICR);
+ u16 newreg = oldreg +
+ (mask & MgslEvent_ExitHuntMode ? RXSTATUS_EXITED_HUNT:0) +
+ (mask & MgslEvent_IdleReceived ? RXSTATUS_IDLE_RECEIVED:0);
+ if (oldreg != newreg)
+ usc_OutReg(info, RICR, newreg);
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&info->event_wait_q, &wait);
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get current irq counts */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ cnow = info->icount;
+ newsigs = info->input_signal_events;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (newsigs.dsr_up == oldsigs.dsr_up &&
+ newsigs.dsr_down == oldsigs.dsr_down &&
+ newsigs.dcd_up == oldsigs.dcd_up &&
+ newsigs.dcd_down == oldsigs.dcd_down &&
+ newsigs.cts_up == oldsigs.cts_up &&
+ newsigs.cts_down == oldsigs.cts_down &&
+ newsigs.ri_up == oldsigs.ri_up &&
+ newsigs.ri_down == oldsigs.ri_down &&
+ cnow.exithunt == cprev.exithunt &&
+ cnow.rxidle == cprev.rxidle) {
+ rc = -EIO;
+ break;
+ }
+
+ events = mask &
+ ( (newsigs.dsr_up != oldsigs.dsr_up ? MgslEvent_DsrActive:0) +
+ (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+ (newsigs.dcd_up != oldsigs.dcd_up ? MgslEvent_DcdActive:0) +
+ (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+ (newsigs.cts_up != oldsigs.cts_up ? MgslEvent_CtsActive:0) +
+ (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+ (newsigs.ri_up != oldsigs.ri_up ? MgslEvent_RiActive:0) +
+ (newsigs.ri_down != oldsigs.ri_down ? MgslEvent_RiInactive:0) +
+ (cnow.exithunt != cprev.exithunt ? MgslEvent_ExitHuntMode:0) +
+ (cnow.rxidle != cprev.rxidle ? MgslEvent_IdleReceived:0) );
+ if (events)
+ break;
+
+ cprev = cnow;
+ oldsigs = newsigs;
+ }
+
+ remove_wait_queue(&info->event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+
+ if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (!waitqueue_active(&info->event_wait_q)) {
+ /* disable enable exit hunt mode/idle rcvd IRQs */
+ usc_OutReg(info, RICR, usc_InReg(info,RICR) &
+ ~(RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED));
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+exit:
+ if ( rc == 0 )
+ PUT_USER(rc, events, mask_ptr);
+
+ return rc;
+
+} /* end of mgsl_wait_event() */
+
+static int modem_input_wait(struct mgsl_struct *info,int arg)
+{
+ unsigned long flags;
+ int rc;
+ struct mgsl_icount cprev, cnow;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /* save current irq counts */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ cprev = info->icount;
+ add_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get new irq counts */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ cnow = info->icount;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+ rc = -EIO;
+ break;
+ }
+
+ /* check for change in caller specified modem input */
+ if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+ (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+ (arg & TIOCM_CD && cnow.dcd != cprev.dcd) ||
+ (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+ rc = 0;
+ break;
+ }
+
+ cprev = cnow;
+ }
+ remove_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+ return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned int result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_get_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+ ((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+ ((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+ ((info->serial_signals & SerialSignal_RI) ? TIOCM_RNG:0) +
+ ((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+ ((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmget() value=%08X\n",
+ __FILE__,__LINE__, info->device_name, result );
+ return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmset(%x,%x)\n",
+ __FILE__,__LINE__,info->device_name, set, clear);
+
+ if (set & TIOCM_RTS)
+ info->serial_signals |= SerialSignal_RTS;
+ if (set & TIOCM_DTR)
+ info->serial_signals |= SerialSignal_DTR;
+ if (clear & TIOCM_RTS)
+ info->serial_signals &= ~SerialSignal_RTS;
+ if (clear & TIOCM_DTR)
+ info->serial_signals &= ~SerialSignal_DTR;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return 0;
+}
+
+/* mgsl_break() Set or clear transmit break condition
+ *
+ * Arguments: tty pointer to tty instance data
+ * break_state -1=set break condition, 0=clear
+ * Return Value: None
+ */
+static void mgsl_break(struct tty_struct *tty, int break_state)
+{
+ struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_break(%s,%d)\n",
+ __FILE__,__LINE__, info->device_name, break_state);
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_break"))
+ return;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (break_state == -1)
+ usc_OutReg(info,IOCR,(u16)(usc_InReg(info,IOCR) | BIT7));
+ else
+ usc_OutReg(info,IOCR,(u16)(usc_InReg(info,IOCR) & ~BIT7));
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+} /* end of mgsl_break() */
+
+/* mgsl_ioctl() Service an IOCTL request
+ *
+ * Arguments:
+ *
+ * tty pointer to tty instance data
+ * file pointer to associated file object for device
+ * cmd IOCTL command code
+ * arg command argument/context
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_ioctl %s cmd=%08X\n", __FILE__,__LINE__,
+ info->device_name, cmd );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_ioctl"))
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ return mgsl_ioctl_common(info, cmd, arg);
+}
+
+static int mgsl_ioctl_common(struct mgsl_struct *info, unsigned int cmd, unsigned long arg)
+{
+ int error;
+ struct mgsl_icount cnow; /* kernel counter temps */
+ void __user *argp = (void __user *)arg;
+ struct serial_icounter_struct __user *p_cuser; /* user space */
+ unsigned long flags;
+
+ switch (cmd) {
+ case MGSL_IOCGPARAMS:
+ return mgsl_get_params(info, argp);
+ case MGSL_IOCSPARAMS:
+ return mgsl_set_params(info, argp);
+ case MGSL_IOCGTXIDLE:
+ return mgsl_get_txidle(info, argp);
+ case MGSL_IOCSTXIDLE:
+ return mgsl_set_txidle(info,(int)arg);
+ case MGSL_IOCTXENABLE:
+ return mgsl_txenable(info,(int)arg);
+ case MGSL_IOCRXENABLE:
+ return mgsl_rxenable(info,(int)arg);
+ case MGSL_IOCTXABORT:
+ return mgsl_txabort(info);
+ case MGSL_IOCGSTATS:
+ return mgsl_get_stats(info, argp);
+ case MGSL_IOCWAITEVENT:
+ return mgsl_wait_event(info, argp);
+ case MGSL_IOCLOOPTXDONE:
+ return mgsl_loopmode_send_done(info);
+ /* Wait for modem input (DCD,RI,DSR,CTS) change
+ * as specified by mask in arg (TIOCM_RNG/DSR/CD/CTS)
+ */
+ case TIOCMIWAIT:
+ return modem_input_wait(info,(int)arg);
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ p_cuser = argp;
+ PUT_USER(error,cnow.cts, &p_cuser->cts);
+ if (error) return error;
+ PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+ if (error) return error;
+ PUT_USER(error,cnow.rng, &p_cuser->rng);
+ if (error) return error;
+ PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+ if (error) return error;
+ PUT_USER(error,cnow.rx, &p_cuser->rx);
+ if (error) return error;
+ PUT_USER(error,cnow.tx, &p_cuser->tx);
+ if (error) return error;
+ PUT_USER(error,cnow.frame, &p_cuser->frame);
+ if (error) return error;
+ PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+ if (error) return error;
+ PUT_USER(error,cnow.parity, &p_cuser->parity);
+ if (error) return error;
+ PUT_USER(error,cnow.brk, &p_cuser->brk);
+ if (error) return error;
+ PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+ if (error) return error;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+/* mgsl_set_termios()
+ *
+ * Set new termios settings
+ *
+ * Arguments:
+ *
+ * tty pointer to tty structure
+ * termios pointer to buffer to hold returned old termios
+ *
+ * Return Value: None
+ */
+static void mgsl_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_set_termios %s\n", __FILE__,__LINE__,
+ tty->driver->name );
+
+ /* just return if nothing has changed */
+ if ((tty->termios->c_cflag == old_termios->c_cflag)
+ && (RELEVANT_IFLAG(tty->termios->c_iflag)
+ == RELEVANT_IFLAG(old_termios->c_iflag)))
+ return;
+
+ mgsl_change_params(info);
+
+ /* Handle transition to B0 status */
+ if (old_termios->c_cflag & CBAUD &&
+ !(tty->termios->c_cflag & CBAUD)) {
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ tty->termios->c_cflag & CBAUD) {
+ info->serial_signals |= SerialSignal_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ info->serial_signals |= SerialSignal_RTS;
+ }
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if (old_termios->c_cflag & CRTSCTS &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ mgsl_start(tty);
+ }
+
+} /* end of mgsl_set_termios() */
+
+/* mgsl_close()
+ *
+ * Called when port is closed. Wait for remaining data to be
+ * sent. Disable port and free resources.
+ *
+ * Arguments:
+ *
+ * tty pointer to open tty structure
+ * filp pointer to open file object
+ *
+ * Return Value: None
+ */
+static void mgsl_close(struct tty_struct *tty, struct file * filp)
+{
+ struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_close"))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_close(%s) entry, count=%d\n",
+ __FILE__,__LINE__, info->device_name, info->count);
+
+ if (!info->count)
+ return;
+
+ if (tty_hung_up_p(filp))
+ goto cleanup;
+
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * tty->count is 1 and the tty structure will be freed.
+ * info->count should be one in this case.
+ * if it's not, correct it so that the port is shutdown.
+ */
+ printk("mgsl_close: bad refcount; tty->count is 1, "
+ "info->count is %d\n", info->count);
+ info->count = 1;
+ }
+
+ info->count--;
+
+ /* if at least one open remaining, leave hardware active */
+ if (info->count)
+ goto cleanup;
+
+ info->flags |= ASYNC_CLOSING;
+
+ /* set tty->closing to notify line discipline to
+ * only process XON/XOFF characters. Only the N_TTY
+ * discipline appears to use this (ppp does not).
+ */
+ tty->closing = 1;
+
+ /* wait for transmit data to clear all layers */
+
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_close(%s) calling tty_wait_until_sent\n",
+ __FILE__,__LINE__, info->device_name );
+ tty_wait_until_sent(tty, info->closing_wait);
+ }
+
+ if (info->flags & ASYNC_INITIALIZED)
+ mgsl_wait_until_sent(tty, info->timeout);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+
+ shutdown(info);
+
+ tty->closing = 0;
+ info->tty = NULL;
+
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+
+ wake_up_interruptible(&info->close_wait);
+
+cleanup:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_close(%s) exit, count=%d\n", __FILE__,__LINE__,
+ tty->driver->name, info->count);
+
+} /* end of mgsl_close() */
+
+/* mgsl_wait_until_sent()
+ *
+ * Wait until the transmitter is empty.
+ *
+ * Arguments:
+ *
+ * tty pointer to tty info structure
+ * timeout time to wait for send completion
+ *
+ * Return Value: None
+ */
+static void mgsl_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+
+ if (!info )
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_wait_until_sent(%s) entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_wait_until_sent"))
+ return;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ goto exit;
+
+ orig_jiffies = jiffies;
+
+ /* Set check interval to 1/5 of estimated time to
+ * send a character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ * Note: use tight timings here to satisfy the NIST-PCTS.
+ */
+
+ if ( info->params.data_rate ) {
+ char_time = info->timeout/(32 * 5);
+ if (!char_time)
+ char_time++;
+ } else
+ char_time = 1;
+
+ if (timeout)
+ char_time = min_t(unsigned long, char_time, timeout);
+
+ if ( info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ while (info->tx_active) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ } else {
+ while (!(usc_InReg(info,TCSR) & TXSTATUS_ALL_SENT) &&
+ info->tx_enabled) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ }
+
+exit:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_wait_until_sent(%s) exit\n",
+ __FILE__,__LINE__, info->device_name );
+
+} /* end of mgsl_wait_until_sent() */
+
+/* mgsl_hangup()
+ *
+ * Called by tty_hangup() when a hangup is signaled.
+ * This is the same as to closing all open files for the port.
+ *
+ * Arguments: tty pointer to associated tty object
+ * Return Value: None
+ */
+static void mgsl_hangup(struct tty_struct *tty)
+{
+ struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_hangup(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_hangup"))
+ return;
+
+ mgsl_flush_buffer(tty);
+ shutdown(info);
+
+ info->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+
+ wake_up_interruptible(&info->open_wait);
+
+} /* end of mgsl_hangup() */
+
+/* block_til_ready()
+ *
+ * Block the current process until the specified port
+ * is ready to be opened.
+ *
+ * Arguments:
+ *
+ * tty pointer to tty info structure
+ * filp pointer to open file object
+ * info pointer to device instance data
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+ struct mgsl_struct *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready on %s\n",
+ __FILE__,__LINE__, tty->driver->name );
+
+ if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+ /* nonblock mode is set or port is not enabled */
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /* Wait for carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * mgsl_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready before block on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ spin_lock_irqsave(&info->irq_spinlock, flags);
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ info->count--;
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock, flags);
+ info->blocked_open++;
+
+ while (1) {
+ if (tty->termios->c_cflag & CBAUD) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ usc_set_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+ retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS;
+ break;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_get_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ break;
+ }
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready blocking on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ schedule();
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&info->open_wait, &wait);
+
+ if (extra_count)
+ info->count++;
+ info->blocked_open--;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready after blocking on %s count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ if (!retval)
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return retval;
+
+} /* end of block_til_ready() */
+
+/* mgsl_open()
+ *
+ * Called when a port is opened. Init and enable port.
+ * Perform serial-specific initialization for the tty structure.
+ *
+ * Arguments: tty pointer to tty info structure
+ * filp associated file pointer
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int mgsl_open(struct tty_struct *tty, struct file * filp)
+{
+ struct mgsl_struct *info;
+ int retval, line;
+ unsigned long page;
+ unsigned long flags;
+
+ /* verify range of specified line number */
+ line = tty->index;
+ if ((line < 0) || (line >= mgsl_device_count)) {
+ printk("%s(%d):mgsl_open with invalid line #%d.\n",
+ __FILE__,__LINE__,line);
+ return -ENODEV;
+ }
+
+ /* find the info structure for the specified line */
+ info = mgsl_device_list;
+ while(info && info->line != line)
+ info = info->next_device;
+ if (mgsl_paranoia_check(info, tty->name, "mgsl_open"))
+ return -ENODEV;
+
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_open(%s), old ref count = %d\n",
+ __FILE__,__LINE__,tty->driver->name, info->count);
+
+ /* If port is closing, signal caller to try again */
+ if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+ retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ goto cleanup;
+ }
+
+ if (!tmp_buf) {
+ page = get_zeroed_page(GFP_KERNEL);
+ if (!page) {
+ retval = -ENOMEM;
+ goto cleanup;
+ }
+ if (tmp_buf)
+ free_page(page);
+ else
+ tmp_buf = (unsigned char *) page;
+ }
+
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->netcount) {
+ retval = -EBUSY;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ goto cleanup;
+ }
+ info->count++;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ if (info->count == 1) {
+ /* 1st open on this device, init hardware */
+ retval = startup(info);
+ if (retval < 0)
+ goto cleanup;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):block_til_ready(%s) returned %d\n",
+ __FILE__,__LINE__, info->device_name, retval);
+ goto cleanup;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):mgsl_open(%s) success\n",
+ __FILE__,__LINE__, info->device_name);
+ retval = 0;
+
+cleanup:
+ if (retval) {
+ if (tty->count == 1)
+ info->tty = NULL; /* tty layer will release tty struct */
+ if(info->count)
+ info->count--;
+ }
+
+ return retval;
+
+} /* end of mgsl_open() */
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, struct mgsl_struct *info)
+{
+ char stat_buf[30];
+ int ret;
+ unsigned long flags;
+
+ if (info->bus_type == MGSL_BUS_TYPE_PCI) {
+ ret = sprintf(buf, "%s:PCI io:%04X irq:%d mem:%08X lcr:%08X",
+ info->device_name, info->io_base, info->irq_level,
+ info->phys_memory_base, info->phys_lcr_base);
+ } else {
+ ret = sprintf(buf, "%s:(E)ISA io:%04X irq:%d dma:%d",
+ info->device_name, info->io_base,
+ info->irq_level, info->dma_level);
+ }
+
+ /* output current serial signal states */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_get_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ stat_buf[0] = 0;
+ stat_buf[1] = 0;
+ if (info->serial_signals & SerialSignal_RTS)
+ strcat(stat_buf, "|RTS");
+ if (info->serial_signals & SerialSignal_CTS)
+ strcat(stat_buf, "|CTS");
+ if (info->serial_signals & SerialSignal_DTR)
+ strcat(stat_buf, "|DTR");
+ if (info->serial_signals & SerialSignal_DSR)
+ strcat(stat_buf, "|DSR");
+ if (info->serial_signals & SerialSignal_DCD)
+ strcat(stat_buf, "|CD");
+ if (info->serial_signals & SerialSignal_RI)
+ strcat(stat_buf, "|RI");
+
+ if (info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ ret += sprintf(buf+ret, " HDLC txok:%d rxok:%d",
+ info->icount.txok, info->icount.rxok);
+ if (info->icount.txunder)
+ ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+ if (info->icount.txabort)
+ ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+ if (info->icount.rxshort)
+ ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);
+ if (info->icount.rxlong)
+ ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+ if (info->icount.rxover)
+ ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+ if (info->icount.rxcrc)
+ ret += sprintf(buf+ret, " rxcrc:%d", info->icount.rxcrc);
+ } else {
+ ret += sprintf(buf+ret, " ASYNC tx:%d rx:%d",
+ info->icount.tx, info->icount.rx);
+ if (info->icount.frame)
+ ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+ if (info->icount.parity)
+ ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+ if (info->icount.brk)
+ ret += sprintf(buf+ret, " brk:%d", info->icount.brk);
+ if (info->icount.overrun)
+ ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+ }
+
+ /* Append serial signal status to end */
+ ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+
+ ret += sprintf(buf+ret, "txactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+ info->tx_active,info->bh_requested,info->bh_running,
+ info->pending_bh);
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ {
+ u16 Tcsr = usc_InReg( info, TCSR );
+ u16 Tdmr = usc_InDmaReg( info, TDMR );
+ u16 Ticr = usc_InReg( info, TICR );
+ u16 Rscr = usc_InReg( info, RCSR );
+ u16 Rdmr = usc_InDmaReg( info, RDMR );
+ u16 Ricr = usc_InReg( info, RICR );
+ u16 Icr = usc_InReg( info, ICR );
+ u16 Dccr = usc_InReg( info, DCCR );
+ u16 Tmr = usc_InReg( info, TMR );
+ u16 Tccr = usc_InReg( info, TCCR );
+ u16 Ccar = inw( info->io_base + CCAR );
+ ret += sprintf(buf+ret, "tcsr=%04X tdmr=%04X ticr=%04X rcsr=%04X rdmr=%04X\n"
+ "ricr=%04X icr =%04X dccr=%04X tmr=%04X tccr=%04X ccar=%04X\n",
+ Tcsr,Tdmr,Ticr,Rscr,Rdmr,Ricr,Icr,Dccr,Tmr,Tccr,Ccar );
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return ret;
+
+} /* end of line_info() */
+
+/* mgsl_read_proc()
+ *
+ * Called to print information about devices
+ *
+ * Arguments:
+ * page page of memory to hold returned info
+ * start
+ * off
+ * count
+ * eof
+ * data
+ *
+ * Return Value:
+ */
+static int mgsl_read_proc(char *page, char **start, off_t off, int count,
+ int *eof, void *data)
+{
+ int len = 0, l;
+ off_t begin = 0;
+ struct mgsl_struct *info;
+
+ len += sprintf(page, "synclink driver:%s\n", driver_version);
+
+ info = mgsl_device_list;
+ while( info ) {
+ l = line_info(page + len, info);
+ len += l;
+ if (len+begin > off+count)
+ goto done;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ info = info->next_device;
+ }
+
+ *eof = 1;
+done:
+ if (off >= len+begin)
+ return 0;
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+
+} /* end of mgsl_read_proc() */
+
+/* mgsl_allocate_dma_buffers()
+ *
+ * Allocate and format DMA buffers (ISA adapter)
+ * or format shared memory buffers (PCI adapter).
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: 0 if success, otherwise error
+ */
+static int mgsl_allocate_dma_buffers(struct mgsl_struct *info)
+{
+ unsigned short BuffersPerFrame;
+
+ info->last_mem_alloc = 0;
+
+ /* Calculate the number of DMA buffers necessary to hold the */
+ /* largest allowable frame size. Note: If the max frame size is */
+ /* not an even multiple of the DMA buffer size then we need to */
+ /* round the buffer count per frame up one. */
+
+ BuffersPerFrame = (unsigned short)(info->max_frame_size/DMABUFFERSIZE);
+ if ( info->max_frame_size % DMABUFFERSIZE )
+ BuffersPerFrame++;
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ /*
+ * The PCI adapter has 256KBytes of shared memory to use.
+ * This is 64 PAGE_SIZE buffers.
+ *
+ * The first page is used for padding at this time so the
+ * buffer list does not begin at offset 0 of the PCI
+ * adapter's shared memory.
+ *
+ * The 2nd page is used for the buffer list. A 4K buffer
+ * list can hold 128 DMA_BUFFER structures at 32 bytes
+ * each.
+ *
+ * This leaves 62 4K pages.
+ *
+ * The next N pages are used for transmit frame(s). We
+ * reserve enough 4K page blocks to hold the required
+ * number of transmit dma buffers (num_tx_dma_buffers),
+ * each of MaxFrameSize size.
+ *
+ * Of the remaining pages (62-N), determine how many can
+ * be used to receive full MaxFrameSize inbound frames
+ */
+ info->tx_buffer_count = info->num_tx_dma_buffers * BuffersPerFrame;
+ info->rx_buffer_count = 62 - info->tx_buffer_count;
+ } else {
+ /* Calculate the number of PAGE_SIZE buffers needed for */
+ /* receive and transmit DMA buffers. */
+
+
+ /* Calculate the number of DMA buffers necessary to */
+ /* hold 7 max size receive frames and one max size transmit frame. */
+ /* The receive buffer count is bumped by one so we avoid an */
+ /* End of List condition if all receive buffers are used when */
+ /* using linked list DMA buffers. */
+
+ info->tx_buffer_count = info->num_tx_dma_buffers * BuffersPerFrame;
+ info->rx_buffer_count = (BuffersPerFrame * MAXRXFRAMES) + 6;
+
+ /*
+ * limit total TxBuffers & RxBuffers to 62 4K total
+ * (ala PCI Allocation)
+ */
+
+ if ( (info->tx_buffer_count + info->rx_buffer_count) > 62 )
+ info->rx_buffer_count = 62 - info->tx_buffer_count;
+
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):Allocating %d TX and %d RX DMA buffers.\n",
+ __FILE__,__LINE__, info->tx_buffer_count,info->rx_buffer_count);
+
+ if ( mgsl_alloc_buffer_list_memory( info ) < 0 ||
+ mgsl_alloc_frame_memory(info, info->rx_buffer_list, info->rx_buffer_count) < 0 ||
+ mgsl_alloc_frame_memory(info, info->tx_buffer_list, info->tx_buffer_count) < 0 ||
+ mgsl_alloc_intermediate_rxbuffer_memory(info) < 0 ||
+ mgsl_alloc_intermediate_txbuffer_memory(info) < 0 ) {
+ printk("%s(%d):Can't allocate DMA buffer memory\n",__FILE__,__LINE__);
+ return -ENOMEM;
+ }
+
+ mgsl_reset_rx_dma_buffers( info );
+ mgsl_reset_tx_dma_buffers( info );
+
+ return 0;
+
+} /* end of mgsl_allocate_dma_buffers() */
+
+/*
+ * mgsl_alloc_buffer_list_memory()
+ *
+ * Allocate a common DMA buffer for use as the
+ * receive and transmit buffer lists.
+ *
+ * A buffer list is a set of buffer entries where each entry contains
+ * a pointer to an actual buffer and a pointer to the next buffer entry
+ * (plus some other info about the buffer).
+ *
+ * The buffer entries for a list are built to form a circular list so
+ * that when the entire list has been traversed you start back at the
+ * beginning.
+ *
+ * This function allocates memory for just the buffer entries.
+ * The links (pointer to next entry) are filled in with the physical
+ * address of the next entry so the adapter can navigate the list
+ * using bus master DMA. The pointers to the actual buffers are filled
+ * out later when the actual buffers are allocated.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: 0 if success, otherwise error
+ */
+static int mgsl_alloc_buffer_list_memory( struct mgsl_struct *info )
+{
+ unsigned int i;
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ /* PCI adapter uses shared memory. */
+ info->buffer_list = info->memory_base + info->last_mem_alloc;
+ info->buffer_list_phys = info->last_mem_alloc;
+ info->last_mem_alloc += BUFFERLISTSIZE;
+ } else {
+ /* ISA adapter uses system memory. */
+ /* The buffer lists are allocated as a common buffer that both */
+ /* the processor and adapter can access. This allows the driver to */
+ /* inspect portions of the buffer while other portions are being */
+ /* updated by the adapter using Bus Master DMA. */
+
+ info->buffer_list = kmalloc(BUFFERLISTSIZE, GFP_KERNEL | GFP_DMA);
+ if ( info->buffer_list == NULL )
+ return -ENOMEM;
+
+ info->buffer_list_phys = isa_virt_to_bus(info->buffer_list);
+ }
+
+ /* We got the memory for the buffer entry lists. */
+ /* Initialize the memory block to all zeros. */
+ memset( info->buffer_list, 0, BUFFERLISTSIZE );
+
+ /* Save virtual address pointers to the receive and */
+ /* transmit buffer lists. (Receive 1st). These pointers will */
+ /* be used by the processor to access the lists. */
+ info->rx_buffer_list = (DMABUFFERENTRY *)info->buffer_list;
+ info->tx_buffer_list = (DMABUFFERENTRY *)info->buffer_list;
+ info->tx_buffer_list += info->rx_buffer_count;
+
+ /*
+ * Build the links for the buffer entry lists such that
+ * two circular lists are built. (Transmit and Receive).
+ *
+ * Note: the links are physical addresses
+ * which are read by the adapter to determine the next
+ * buffer entry to use.
+ */
+
+ for ( i = 0; i < info->rx_buffer_count; i++ ) {
+ /* calculate and store physical address of this buffer entry */
+ info->rx_buffer_list[i].phys_entry =
+ info->buffer_list_phys + (i * sizeof(DMABUFFERENTRY));
+
+ /* calculate and store physical address of */
+ /* next entry in cirular list of entries */
+
+ info->rx_buffer_list[i].link = info->buffer_list_phys;
+
+ if ( i < info->rx_buffer_count - 1 )
+ info->rx_buffer_list[i].link += (i + 1) * sizeof(DMABUFFERENTRY);
+ }
+
+ for ( i = 0; i < info->tx_buffer_count; i++ ) {
+ /* calculate and store physical address of this buffer entry */
+ info->tx_buffer_list[i].phys_entry = info->buffer_list_phys +
+ ((info->rx_buffer_count + i) * sizeof(DMABUFFERENTRY));
+
+ /* calculate and store physical address of */
+ /* next entry in cirular list of entries */
+
+ info->tx_buffer_list[i].link = info->buffer_list_phys +
+ info->rx_buffer_count * sizeof(DMABUFFERENTRY);
+
+ if ( i < info->tx_buffer_count - 1 )
+ info->tx_buffer_list[i].link += (i + 1) * sizeof(DMABUFFERENTRY);
+ }
+
+ return 0;
+
+} /* end of mgsl_alloc_buffer_list_memory() */
+
+/* Free DMA buffers allocated for use as the
+ * receive and transmit buffer lists.
+ * Warning:
+ *
+ * The data transfer buffers associated with the buffer list
+ * MUST be freed before freeing the buffer list itself because
+ * the buffer list contains the information necessary to free
+ * the individual buffers!
+ */
+static void mgsl_free_buffer_list_memory( struct mgsl_struct *info )
+{
+ if ( info->buffer_list && info->bus_type != MGSL_BUS_TYPE_PCI )
+ kfree(info->buffer_list);
+
+ info->buffer_list = NULL;
+ info->rx_buffer_list = NULL;
+ info->tx_buffer_list = NULL;
+
+} /* end of mgsl_free_buffer_list_memory() */
+
+/*
+ * mgsl_alloc_frame_memory()
+ *
+ * Allocate the frame DMA buffers used by the specified buffer list.
+ * Each DMA buffer will be one memory page in size. This is necessary
+ * because memory can fragment enough that it may be impossible
+ * contiguous pages.
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * BufferList pointer to list of buffer entries
+ * Buffercount count of buffer entries in buffer list
+ *
+ * Return Value: 0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_frame_memory(struct mgsl_struct *info,DMABUFFERENTRY *BufferList,int Buffercount)
+{
+ int i;
+ unsigned long phys_addr;
+
+ /* Allocate page sized buffers for the receive buffer list */
+
+ for ( i = 0; i < Buffercount; i++ ) {
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ /* PCI adapter uses shared memory buffers. */
+ BufferList[i].virt_addr = info->memory_base + info->last_mem_alloc;
+ phys_addr = info->last_mem_alloc;
+ info->last_mem_alloc += DMABUFFERSIZE;
+ } else {
+ /* ISA adapter uses system memory. */
+ BufferList[i].virt_addr =
+ kmalloc(DMABUFFERSIZE, GFP_KERNEL | GFP_DMA);
+ if ( BufferList[i].virt_addr == NULL )
+ return -ENOMEM;
+ phys_addr = isa_virt_to_bus(BufferList[i].virt_addr);
+ }
+ BufferList[i].phys_addr = phys_addr;
+ }
+
+ return 0;
+
+} /* end of mgsl_alloc_frame_memory() */
+
+/*
+ * mgsl_free_frame_memory()
+ *
+ * Free the buffers associated with
+ * each buffer entry of a buffer list.
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * BufferList pointer to list of buffer entries
+ * Buffercount count of buffer entries in buffer list
+ *
+ * Return Value: None
+ */
+static void mgsl_free_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList, int Buffercount)
+{
+ int i;
+
+ if ( BufferList ) {
+ for ( i = 0 ; i < Buffercount ; i++ ) {
+ if ( BufferList[i].virt_addr ) {
+ if ( info->bus_type != MGSL_BUS_TYPE_PCI )
+ kfree(BufferList[i].virt_addr);
+ BufferList[i].virt_addr = NULL;
+ }
+ }
+ }
+
+} /* end of mgsl_free_frame_memory() */
+
+/* mgsl_free_dma_buffers()
+ *
+ * Free DMA buffers
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_free_dma_buffers( struct mgsl_struct *info )
+{
+ mgsl_free_frame_memory( info, info->rx_buffer_list, info->rx_buffer_count );
+ mgsl_free_frame_memory( info, info->tx_buffer_list, info->tx_buffer_count );
+ mgsl_free_buffer_list_memory( info );
+
+} /* end of mgsl_free_dma_buffers() */
+
+
+/*
+ * mgsl_alloc_intermediate_rxbuffer_memory()
+ *
+ * Allocate a buffer large enough to hold max_frame_size. This buffer
+ * is used to pass an assembled frame to the line discipline.
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ *
+ * Return Value: 0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_intermediate_rxbuffer_memory(struct mgsl_struct *info)
+{
+ info->intermediate_rxbuffer = kmalloc(info->max_frame_size, GFP_KERNEL | GFP_DMA);
+ if ( info->intermediate_rxbuffer == NULL )
+ return -ENOMEM;
+
+ return 0;
+
+} /* end of mgsl_alloc_intermediate_rxbuffer_memory() */
+
+/*
+ * mgsl_free_intermediate_rxbuffer_memory()
+ *
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ *
+ * Return Value: None
+ */
+static void mgsl_free_intermediate_rxbuffer_memory(struct mgsl_struct *info)
+{
+ if ( info->intermediate_rxbuffer )
+ kfree(info->intermediate_rxbuffer);
+
+ info->intermediate_rxbuffer = NULL;
+
+} /* end of mgsl_free_intermediate_rxbuffer_memory() */
+
+/*
+ * mgsl_alloc_intermediate_txbuffer_memory()
+ *
+ * Allocate intermdiate transmit buffer(s) large enough to hold max_frame_size.
+ * This buffer is used to load transmit frames into the adapter's dma transfer
+ * buffers when there is sufficient space.
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ *
+ * Return Value: 0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_intermediate_txbuffer_memory(struct mgsl_struct *info)
+{
+ int i;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s %s(%d) allocating %d tx holding buffers\n",
+ info->device_name, __FILE__,__LINE__,info->num_tx_holding_buffers);
+
+ memset(info->tx_holding_buffers,0,sizeof(info->tx_holding_buffers));
+
+ for ( i=0; i<info->num_tx_holding_buffers; ++i) {
+ info->tx_holding_buffers[i].buffer =
+ kmalloc(info->max_frame_size, GFP_KERNEL);
+ if ( info->tx_holding_buffers[i].buffer == NULL )
+ return -ENOMEM;
+ }
+
+ return 0;
+
+} /* end of mgsl_alloc_intermediate_txbuffer_memory() */
+
+/*
+ * mgsl_free_intermediate_txbuffer_memory()
+ *
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ *
+ * Return Value: None
+ */
+static void mgsl_free_intermediate_txbuffer_memory(struct mgsl_struct *info)
+{
+ int i;
+
+ for ( i=0; i<info->num_tx_holding_buffers; ++i ) {
+ if ( info->tx_holding_buffers[i].buffer ) {
+ kfree(info->tx_holding_buffers[i].buffer);
+ info->tx_holding_buffers[i].buffer=NULL;
+ }
+ }
+
+ info->get_tx_holding_index = 0;
+ info->put_tx_holding_index = 0;
+ info->tx_holding_count = 0;
+
+} /* end of mgsl_free_intermediate_txbuffer_memory() */
+
+
+/*
+ * load_next_tx_holding_buffer()
+ *
+ * attempts to load the next buffered tx request into the
+ * tx dma buffers
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ *
+ * Return Value: 1 if next buffered tx request loaded
+ * into adapter's tx dma buffer,
+ * 0 otherwise
+ */
+static int load_next_tx_holding_buffer(struct mgsl_struct *info)
+{
+ int ret = 0;
+
+ if ( info->tx_holding_count ) {
+ /* determine if we have enough tx dma buffers
+ * to accommodate the next tx frame
+ */
+ struct tx_holding_buffer *ptx =
+ &info->tx_holding_buffers[info->get_tx_holding_index];
+ int num_free = num_free_tx_dma_buffers(info);
+ int num_needed = ptx->buffer_size / DMABUFFERSIZE;
+ if ( ptx->buffer_size % DMABUFFERSIZE )
+ ++num_needed;
+
+ if (num_needed <= num_free) {
+ info->xmit_cnt = ptx->buffer_size;
+ mgsl_load_tx_dma_buffer(info,ptx->buffer,ptx->buffer_size);
+
+ --info->tx_holding_count;
+ if ( ++info->get_tx_holding_index >= info->num_tx_holding_buffers)
+ info->get_tx_holding_index=0;
+
+ /* restart transmit timer */
+ mod_timer(&info->tx_timer, jiffies + msecs_to_jiffies(5000));
+
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * save_tx_buffer_request()
+ *
+ * attempt to store transmit frame request for later transmission
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * Buffer pointer to buffer containing frame to load
+ * BufferSize size in bytes of frame in Buffer
+ *
+ * Return Value: 1 if able to store, 0 otherwise
+ */
+static int save_tx_buffer_request(struct mgsl_struct *info,const char *Buffer, unsigned int BufferSize)
+{
+ struct tx_holding_buffer *ptx;
+
+ if ( info->tx_holding_count >= info->num_tx_holding_buffers ) {
+ return 0; /* all buffers in use */
+ }
+
+ ptx = &info->tx_holding_buffers[info->put_tx_holding_index];
+ ptx->buffer_size = BufferSize;
+ memcpy( ptx->buffer, Buffer, BufferSize);
+
+ ++info->tx_holding_count;
+ if ( ++info->put_tx_holding_index >= info->num_tx_holding_buffers)
+ info->put_tx_holding_index=0;
+
+ return 1;
+}
+
+static int mgsl_claim_resources(struct mgsl_struct *info)
+{
+ if (request_region(info->io_base,info->io_addr_size,"synclink") == NULL) {
+ printk( "%s(%d):I/O address conflict on device %s Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->io_base);
+ return -ENODEV;
+ }
+ info->io_addr_requested = 1;
+
+ if ( request_irq(info->irq_level,mgsl_interrupt,info->irq_flags,
+ info->device_name, info ) < 0 ) {
+ printk( "%s(%d):Cant request interrupt on device %s IRQ=%d\n",
+ __FILE__,__LINE__,info->device_name, info->irq_level );
+ goto errout;
+ }
+ info->irq_requested = 1;
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ if (request_mem_region(info->phys_memory_base,0x40000,"synclink") == NULL) {
+ printk( "%s(%d):mem addr conflict device %s Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base);
+ goto errout;
+ }
+ info->shared_mem_requested = 1;
+ if (request_mem_region(info->phys_lcr_base + info->lcr_offset,128,"synclink") == NULL) {
+ printk( "%s(%d):lcr mem addr conflict device %s Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_lcr_base + info->lcr_offset);
+ goto errout;
+ }
+ info->lcr_mem_requested = 1;
+
+ info->memory_base = ioremap(info->phys_memory_base,0x40000);
+ if (!info->memory_base) {
+ printk( "%s(%d):Cant map shared memory on device %s MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base );
+ goto errout;
+ }
+
+ if ( !mgsl_memory_test(info) ) {
+ printk( "%s(%d):Failed shared memory test %s MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base );
+ goto errout;
+ }
+
+ info->lcr_base = ioremap(info->phys_lcr_base,PAGE_SIZE) + info->lcr_offset;
+ if (!info->lcr_base) {
+ printk( "%s(%d):Cant map LCR memory on device %s MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_lcr_base );
+ goto errout;
+ }
+
+ } else {
+ /* claim DMA channel */
+
+ if (request_dma(info->dma_level,info->device_name) < 0){
+ printk( "%s(%d):Cant request DMA channel on device %s DMA=%d\n",
+ __FILE__,__LINE__,info->device_name, info->dma_level );
+ mgsl_release_resources( info );
+ return -ENODEV;
+ }
+ info->dma_requested = 1;
+
+ /* ISA adapter uses bus master DMA */
+ set_dma_mode(info->dma_level,DMA_MODE_CASCADE);
+ enable_dma(info->dma_level);
+ }
+
+ if ( mgsl_allocate_dma_buffers(info) < 0 ) {
+ printk( "%s(%d):Cant allocate DMA buffers on device %s DMA=%d\n",
+ __FILE__,__LINE__,info->device_name, info->dma_level );
+ goto errout;
+ }
+
+ return 0;
+errout:
+ mgsl_release_resources(info);
+ return -ENODEV;
+
+} /* end of mgsl_claim_resources() */
+
+static void mgsl_release_resources(struct mgsl_struct *info)
+{
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_release_resources(%s) entry\n",
+ __FILE__,__LINE__,info->device_name );
+
+ if ( info->irq_requested ) {
+ free_irq(info->irq_level, info);
+ info->irq_requested = 0;
+ }
+ if ( info->dma_requested ) {
+ disable_dma(info->dma_level);
+ free_dma(info->dma_level);
+ info->dma_requested = 0;
+ }
+ mgsl_free_dma_buffers(info);
+ mgsl_free_intermediate_rxbuffer_memory(info);
+ mgsl_free_intermediate_txbuffer_memory(info);
+
+ if ( info->io_addr_requested ) {
+ release_region(info->io_base,info->io_addr_size);
+ info->io_addr_requested = 0;
+ }
+ if ( info->shared_mem_requested ) {
+ release_mem_region(info->phys_memory_base,0x40000);
+ info->shared_mem_requested = 0;
+ }
+ if ( info->lcr_mem_requested ) {
+ release_mem_region(info->phys_lcr_base + info->lcr_offset,128);
+ info->lcr_mem_requested = 0;
+ }
+ if (info->memory_base){
+ iounmap(info->memory_base);
+ info->memory_base = NULL;
+ }
+ if (info->lcr_base){
+ iounmap(info->lcr_base - info->lcr_offset);
+ info->lcr_base = NULL;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_release_resources(%s) exit\n",
+ __FILE__,__LINE__,info->device_name );
+
+} /* end of mgsl_release_resources() */
+
+/* mgsl_add_device()
+ *
+ * Add the specified device instance data structure to the
+ * global linked list of devices and increment the device count.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_add_device( struct mgsl_struct *info )
+{
+ info->next_device = NULL;
+ info->line = mgsl_device_count;
+ sprintf(info->device_name,"ttySL%d",info->line);
+
+ if (info->line < MAX_TOTAL_DEVICES) {
+ if (maxframe[info->line])
+ info->max_frame_size = maxframe[info->line];
+ info->dosyncppp = dosyncppp[info->line];
+
+ if (txdmabufs[info->line]) {
+ info->num_tx_dma_buffers = txdmabufs[info->line];
+ if (info->num_tx_dma_buffers < 1)
+ info->num_tx_dma_buffers = 1;
+ }
+
+ if (txholdbufs[info->line]) {
+ info->num_tx_holding_buffers = txholdbufs[info->line];
+ if (info->num_tx_holding_buffers < 1)
+ info->num_tx_holding_buffers = 1;
+ else if (info->num_tx_holding_buffers > MAX_TX_HOLDING_BUFFERS)
+ info->num_tx_holding_buffers = MAX_TX_HOLDING_BUFFERS;
+ }
+ }
+
+ mgsl_device_count++;
+
+ if ( !mgsl_device_list )
+ mgsl_device_list = info;
+ else {
+ struct mgsl_struct *current_dev = mgsl_device_list;
+ while( current_dev->next_device )
+ current_dev = current_dev->next_device;
+ current_dev->next_device = info;
+ }
+
+ if ( info->max_frame_size < 4096 )
+ info->max_frame_size = 4096;
+ else if ( info->max_frame_size > 65535 )
+ info->max_frame_size = 65535;
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ printk( "SyncLink PCI v%d %s: IO=%04X IRQ=%d Mem=%08X,%08X MaxFrameSize=%u\n",
+ info->hw_version + 1, info->device_name, info->io_base, info->irq_level,
+ info->phys_memory_base, info->phys_lcr_base,
+ info->max_frame_size );
+ } else {
+ printk( "SyncLink ISA %s: IO=%04X IRQ=%d DMA=%d MaxFrameSize=%u\n",
+ info->device_name, info->io_base, info->irq_level, info->dma_level,
+ info->max_frame_size );
+ }
+
+#ifdef CONFIG_HDLC
+ hdlcdev_init(info);
+#endif
+
+} /* end of mgsl_add_device() */
+
+/* mgsl_allocate_device()
+ *
+ * Allocate and initialize a device instance structure
+ *
+ * Arguments: none
+ * Return Value: pointer to mgsl_struct if success, otherwise NULL
+ */
+static struct mgsl_struct* mgsl_allocate_device(void)
+{
+ struct mgsl_struct *info;
+
+ info = (struct mgsl_struct *)kmalloc(sizeof(struct mgsl_struct),
+ GFP_KERNEL);
+
+ if (!info) {
+ printk("Error can't allocate device instance data\n");
+ } else {
+ memset(info, 0, sizeof(struct mgsl_struct));
+ info->magic = MGSL_MAGIC;
+ INIT_WORK(&info->task, mgsl_bh_handler, info);
+ info->max_frame_size = 4096;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = 30*HZ;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->status_event_wait_q);
+ init_waitqueue_head(&info->event_wait_q);
+ spin_lock_init(&info->irq_spinlock);
+ spin_lock_init(&info->netlock);
+ memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+ info->idle_mode = HDLC_TXIDLE_FLAGS;
+ info->num_tx_dma_buffers = 1;
+ info->num_tx_holding_buffers = 0;
+ }
+
+ return info;
+
+} /* end of mgsl_allocate_device()*/
+
+static struct tty_operations mgsl_ops = {
+ .open = mgsl_open,
+ .close = mgsl_close,
+ .write = mgsl_write,
+ .put_char = mgsl_put_char,
+ .flush_chars = mgsl_flush_chars,
+ .write_room = mgsl_write_room,
+ .chars_in_buffer = mgsl_chars_in_buffer,
+ .flush_buffer = mgsl_flush_buffer,
+ .ioctl = mgsl_ioctl,
+ .throttle = mgsl_throttle,
+ .unthrottle = mgsl_unthrottle,
+ .send_xchar = mgsl_send_xchar,
+ .break_ctl = mgsl_break,
+ .wait_until_sent = mgsl_wait_until_sent,
+ .read_proc = mgsl_read_proc,
+ .set_termios = mgsl_set_termios,
+ .stop = mgsl_stop,
+ .start = mgsl_start,
+ .hangup = mgsl_hangup,
+ .tiocmget = tiocmget,
+ .tiocmset = tiocmset,
+};
+
+/*
+ * perform tty device initialization
+ */
+static int mgsl_init_tty(void)
+{
+ int rc;
+
+ serial_driver = alloc_tty_driver(128);
+ if (!serial_driver)
+ return -ENOMEM;
+
+ serial_driver->owner = THIS_MODULE;
+ serial_driver->driver_name = "synclink";
+ serial_driver->name = "ttySL";
+ serial_driver->major = ttymajor;
+ serial_driver->minor_start = 64;
+ serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ serial_driver->init_termios = tty_std_termios;
+ serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(serial_driver, &mgsl_ops);
+ if ((rc = tty_register_driver(serial_driver)) < 0) {
+ printk("%s(%d):Couldn't register serial driver\n",
+ __FILE__,__LINE__);
+ put_tty_driver(serial_driver);
+ serial_driver = NULL;
+ return rc;
+ }
+
+ printk("%s %s, tty major#%d\n",
+ driver_name, driver_version,
+ serial_driver->major);
+ return 0;
+}
+
+/* enumerate user specified ISA adapters
+ */
+static void mgsl_enum_isa_devices(void)
+{
+ struct mgsl_struct *info;
+ int i;
+
+ /* Check for user specified ISA devices */
+
+ for (i=0 ;(i < MAX_ISA_DEVICES) && io[i] && irq[i]; i++){
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("ISA device specified io=%04X,irq=%d,dma=%d\n",
+ io[i], irq[i], dma[i] );
+
+ info = mgsl_allocate_device();
+ if ( !info ) {
+ /* error allocating device instance data */
+ if ( debug_level >= DEBUG_LEVEL_ERROR )
+ printk( "can't allocate device instance data.\n");
+ continue;
+ }
+
+ /* Copy user configuration info to device instance data */
+ info->io_base = (unsigned int)io[i];
+ info->irq_level = (unsigned int)irq[i];
+ info->irq_level = irq_canonicalize(info->irq_level);
+ info->dma_level = (unsigned int)dma[i];
+ info->bus_type = MGSL_BUS_TYPE_ISA;
+ info->io_addr_size = 16;
+ info->irq_flags = 0;
+
+ mgsl_add_device( info );
+ }
+}
+
+static void synclink_cleanup(void)
+{
+ int rc;
+ struct mgsl_struct *info;
+ struct mgsl_struct *tmp;
+
+ printk("Unloading %s: %s\n", driver_name, driver_version);
+
+ if (serial_driver) {
+ if ((rc = tty_unregister_driver(serial_driver)))
+ printk("%s(%d) failed to unregister tty driver err=%d\n",
+ __FILE__,__LINE__,rc);
+ put_tty_driver(serial_driver);
+ }
+
+ info = mgsl_device_list;
+ while(info) {
+#ifdef CONFIG_HDLC
+ hdlcdev_exit(info);
+#endif
+ mgsl_release_resources(info);
+ tmp = info;
+ info = info->next_device;
+ kfree(tmp);
+ }
+
+ if (tmp_buf) {
+ free_page((unsigned long) tmp_buf);
+ tmp_buf = NULL;
+ }
+
+ if (pci_registered)
+ pci_unregister_driver(&synclink_pci_driver);
+}
+
+static int __init synclink_init(void)
+{
+ int rc;
+
+ if (break_on_load) {
+ mgsl_get_text_ptr();
+ BREAKPOINT();
+ }
+
+ printk("%s %s\n", driver_name, driver_version);
+
+ mgsl_enum_isa_devices();
+ if ((rc = pci_register_driver(&synclink_pci_driver)) < 0)
+ printk("%s:failed to register PCI driver, error=%d\n",__FILE__,rc);
+ else
+ pci_registered = 1;
+
+ if ((rc = mgsl_init_tty()) < 0)
+ goto error;
+
+ return 0;
+
+error:
+ synclink_cleanup();
+ return rc;
+}
+
+static void __exit synclink_exit(void)
+{
+ synclink_cleanup();
+}
+
+module_init(synclink_init);
+module_exit(synclink_exit);
+
+/*
+ * usc_RTCmd()
+ *
+ * Issue a USC Receive/Transmit command to the
+ * Channel Command/Address Register (CCAR).
+ *
+ * Notes:
+ *
+ * The command is encoded in the most significant 5 bits <15..11>
+ * of the CCAR value. Bits <10..7> of the CCAR must be preserved
+ * and Bits <6..0> must be written as zeros.
+ *
+ * Arguments:
+ *
+ * info pointer to device information structure
+ * Cmd command mask (use symbolic macros)
+ *
+ * Return Value:
+ *
+ * None
+ */
+static void usc_RTCmd( struct mgsl_struct *info, u16 Cmd )
+{
+ /* output command to CCAR in bits <15..11> */
+ /* preserve bits <10..7>, bits <6..0> must be zero */
+
+ outw( Cmd + info->loopback_bits, info->io_base + CCAR );
+
+ /* Read to flush write to CCAR */
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ inw( info->io_base + CCAR );
+
+} /* end of usc_RTCmd() */
+
+/*
+ * usc_DmaCmd()
+ *
+ * Issue a DMA command to the DMA Command/Address Register (DCAR).
+ *
+ * Arguments:
+ *
+ * info pointer to device information structure
+ * Cmd DMA command mask (usc_DmaCmd_XX Macros)
+ *
+ * Return Value:
+ *
+ * None
+ */
+static void usc_DmaCmd( struct mgsl_struct *info, u16 Cmd )
+{
+ /* write command mask to DCAR */
+ outw( Cmd + info->mbre_bit, info->io_base );
+
+ /* Read to flush write to DCAR */
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ inw( info->io_base );
+
+} /* end of usc_DmaCmd() */
+
+/*
+ * usc_OutDmaReg()
+ *
+ * Write a 16-bit value to a USC DMA register
+ *
+ * Arguments:
+ *
+ * info pointer to device info structure
+ * RegAddr register address (number) for write
+ * RegValue 16-bit value to write to register
+ *
+ * Return Value:
+ *
+ * None
+ *
+ */
+static void usc_OutDmaReg( struct mgsl_struct *info, u16 RegAddr, u16 RegValue )
+{
+ /* Note: The DCAR is located at the adapter base address */
+ /* Note: must preserve state of BIT8 in DCAR */
+
+ outw( RegAddr + info->mbre_bit, info->io_base );
+ outw( RegValue, info->io_base );
+
+ /* Read to flush write to DCAR */
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ inw( info->io_base );
+
+} /* end of usc_OutDmaReg() */
+
+/*
+ * usc_InDmaReg()
+ *
+ * Read a 16-bit value from a DMA register
+ *
+ * Arguments:
+ *
+ * info pointer to device info structure
+ * RegAddr register address (number) to read from
+ *
+ * Return Value:
+ *
+ * The 16-bit value read from register
+ *
+ */
+static u16 usc_InDmaReg( struct mgsl_struct *info, u16 RegAddr )
+{
+ /* Note: The DCAR is located at the adapter base address */
+ /* Note: must preserve state of BIT8 in DCAR */
+
+ outw( RegAddr + info->mbre_bit, info->io_base );
+ return inw( info->io_base );
+
+} /* end of usc_InDmaReg() */
+
+/*
+ *
+ * usc_OutReg()
+ *
+ * Write a 16-bit value to a USC serial channel register
+ *
+ * Arguments:
+ *
+ * info pointer to device info structure
+ * RegAddr register address (number) to write to
+ * RegValue 16-bit value to write to register
+ *
+ * Return Value:
+ *
+ * None
+ *
+ */
+static void usc_OutReg( struct mgsl_struct *info, u16 RegAddr, u16 RegValue )
+{
+ outw( RegAddr + info->loopback_bits, info->io_base + CCAR );
+ outw( RegValue, info->io_base + CCAR );
+
+ /* Read to flush write to CCAR */
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ inw( info->io_base + CCAR );
+
+} /* end of usc_OutReg() */
+
+/*
+ * usc_InReg()
+ *
+ * Reads a 16-bit value from a USC serial channel register
+ *
+ * Arguments:
+ *
+ * info pointer to device extension
+ * RegAddr register address (number) to read from
+ *
+ * Return Value:
+ *
+ * 16-bit value read from register
+ */
+static u16 usc_InReg( struct mgsl_struct *info, u16 RegAddr )
+{
+ outw( RegAddr + info->loopback_bits, info->io_base + CCAR );
+ return inw( info->io_base + CCAR );
+
+} /* end of usc_InReg() */
+
+/* usc_set_sdlc_mode()
+ *
+ * Set up the adapter for SDLC DMA communications.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: NONE
+ */
+static void usc_set_sdlc_mode( struct mgsl_struct *info )
+{
+ u16 RegValue;
+ int PreSL1660;
+
+ /*
+ * determine if the IUSC on the adapter is pre-SL1660. If
+ * not, take advantage of the UnderWait feature of more
+ * modern chips. If an underrun occurs and this bit is set,
+ * the transmitter will idle the programmed idle pattern
+ * until the driver has time to service the underrun. Otherwise,
+ * the dma controller may get the cycles previously requested
+ * and begin transmitting queued tx data.
+ */
+ usc_OutReg(info,TMCR,0x1f);
+ RegValue=usc_InReg(info,TMDR);
+ if ( RegValue == IUSC_PRE_SL1660 )
+ PreSL1660 = 1;
+ else
+ PreSL1660 = 0;
+
+
+ if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+ {
+ /*
+ ** Channel Mode Register (CMR)
+ **
+ ** <15..14> 10 Tx Sub Modes, Send Flag on Underrun
+ ** <13> 0 0 = Transmit Disabled (initially)
+ ** <12> 0 1 = Consecutive Idles share common 0
+ ** <11..8> 1110 Transmitter Mode = HDLC/SDLC Loop
+ ** <7..4> 0000 Rx Sub Modes, addr/ctrl field handling
+ ** <3..0> 0110 Receiver Mode = HDLC/SDLC
+ **
+ ** 1000 1110 0000 0110 = 0x8e06
+ */
+ RegValue = 0x8e06;
+
+ /*--------------------------------------------------
+ * ignore user options for UnderRun Actions and
+ * preambles
+ *--------------------------------------------------*/
+ }
+ else
+ {
+ /* Channel mode Register (CMR)
+ *
+ * <15..14> 00 Tx Sub modes, Underrun Action
+ * <13> 0 1 = Send Preamble before opening flag
+ * <12> 0 1 = Consecutive Idles share common 0
+ * <11..8> 0110 Transmitter mode = HDLC/SDLC
+ * <7..4> 0000 Rx Sub modes, addr/ctrl field handling
+ * <3..0> 0110 Receiver mode = HDLC/SDLC
+ *
+ * 0000 0110 0000 0110 = 0x0606
+ */
+ if (info->params.mode == MGSL_MODE_RAW) {
+ RegValue = 0x0001; /* Set Receive mode = external sync */
+
+ usc_OutReg( info, IOCR, /* Set IOCR DCD is RxSync Detect Input */
+ (unsigned short)((usc_InReg(info, IOCR) & ~(BIT13|BIT12)) | BIT12));
+
+ /*
+ * TxSubMode:
+ * CMR <15> 0 Don't send CRC on Tx Underrun
+ * CMR <14> x undefined
+ * CMR <13> 0 Send preamble before openning sync
+ * CMR <12> 0 Send 8-bit syncs, 1=send Syncs per TxLength
+ *
+ * TxMode:
+ * CMR <11-8) 0100 MonoSync
+ *
+ * 0x00 0100 xxxx xxxx 04xx
+ */
+ RegValue |= 0x0400;
+ }
+ else {
+
+ RegValue = 0x0606;
+
+ if ( info->params.flags & HDLC_FLAG_UNDERRUN_ABORT15 )
+ RegValue |= BIT14;
+ else if ( info->params.flags & HDLC_FLAG_UNDERRUN_FLAG )
+ RegValue |= BIT15;
+ else if ( info->params.flags & HDLC_FLAG_UNDERRUN_CRC )
+ RegValue |= BIT15 + BIT14;
+ }
+
+ if ( info->params.preamble != HDLC_PREAMBLE_PATTERN_NONE )
+ RegValue |= BIT13;
+ }
+
+ if ( info->params.mode == MGSL_MODE_HDLC &&
+ (info->params.flags & HDLC_FLAG_SHARE_ZERO) )
+ RegValue |= BIT12;
+
+ if ( info->params.addr_filter != 0xff )
+ {
+ /* set up receive address filtering */
+ usc_OutReg( info, RSR, info->params.addr_filter );
+ RegValue |= BIT4;
+ }
+
+ usc_OutReg( info, CMR, RegValue );
+ info->cmr_value = RegValue;
+
+ /* Receiver mode Register (RMR)
+ *
+ * <15..13> 000 encoding
+ * <12..11> 00 FCS = 16bit CRC CCITT (x15 + x12 + x5 + 1)
+ * <10> 1 1 = Set CRC to all 1s (use for SDLC/HDLC)
+ * <9> 0 1 = Include Receive chars in CRC
+ * <8> 1 1 = Use Abort/PE bit as abort indicator
+ * <7..6> 00 Even parity
+ * <5> 0 parity disabled
+ * <4..2> 000 Receive Char Length = 8 bits
+ * <1..0> 00 Disable Receiver
+ *
+ * 0000 0101 0000 0000 = 0x0500
+ */
+
+ RegValue = 0x0500;
+
+ switch ( info->params.encoding ) {
+ case HDLC_ENCODING_NRZB: RegValue |= BIT13; break;
+ case HDLC_ENCODING_NRZI_MARK: RegValue |= BIT14; break;
+ case HDLC_ENCODING_NRZI_SPACE: RegValue |= BIT14 + BIT13; break;
+ case HDLC_ENCODING_BIPHASE_MARK: RegValue |= BIT15; break;
+ case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT15 + BIT13; break;
+ case HDLC_ENCODING_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14; break;
+ case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14 + BIT13; break;
+ }
+
+ if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_16_CCITT )
+ RegValue |= BIT9;
+ else if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_32_CCITT )
+ RegValue |= ( BIT12 | BIT10 | BIT9 );
+
+ usc_OutReg( info, RMR, RegValue );
+
+ /* Set the Receive count Limit Register (RCLR) to 0xffff. */
+ /* When an opening flag of an SDLC frame is recognized the */
+ /* Receive Character count (RCC) is loaded with the value in */
+ /* RCLR. The RCC is decremented for each received byte. The */
+ /* value of RCC is stored after the closing flag of the frame */
+ /* allowing the frame size to be computed. */
+
+ usc_OutReg( info, RCLR, RCLRVALUE );
+
+ usc_RCmd( info, RCmd_SelectRicrdma_level );
+
+ /* Receive Interrupt Control Register (RICR)
+ *
+ * <15..8> ? RxFIFO DMA Request Level
+ * <7> 0 Exited Hunt IA (Interrupt Arm)
+ * <6> 0 Idle Received IA
+ * <5> 0 Break/Abort IA
+ * <4> 0 Rx Bound IA
+ * <3> 1 Queued status reflects oldest 2 bytes in FIFO
+ * <2> 0 Abort/PE IA
+ * <1> 1 Rx Overrun IA
+ * <0> 0 Select TC0 value for readback
+ *
+ * 0000 0000 0000 1000 = 0x000a
+ */
+
+ /* Carry over the Exit Hunt and Idle Received bits */
+ /* in case they have been armed by usc_ArmEvents. */
+
+ RegValue = usc_InReg( info, RICR ) & 0xc0;
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ usc_OutReg( info, RICR, (u16)(0x030a | RegValue) );
+ else
+ usc_OutReg( info, RICR, (u16)(0x140a | RegValue) );
+
+ /* Unlatch all Rx status bits and clear Rx status IRQ Pending */
+
+ usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+
+ /* Transmit mode Register (TMR)
+ *
+ * <15..13> 000 encoding
+ * <12..11> 00 FCS = 16bit CRC CCITT (x15 + x12 + x5 + 1)
+ * <10> 1 1 = Start CRC as all 1s (use for SDLC/HDLC)
+ * <9> 0 1 = Tx CRC Enabled
+ * <8> 0 1 = Append CRC to end of transmit frame
+ * <7..6> 00 Transmit parity Even
+ * <5> 0 Transmit parity Disabled
+ * <4..2> 000 Tx Char Length = 8 bits
+ * <1..0> 00 Disable Transmitter
+ *
+ * 0000 0100 0000 0000 = 0x0400
+ */
+
+ RegValue = 0x0400;
+
+ switch ( info->params.encoding ) {
+ case HDLC_ENCODING_NRZB: RegValue |= BIT13; break;
+ case HDLC_ENCODING_NRZI_MARK: RegValue |= BIT14; break;
+ case HDLC_ENCODING_NRZI_SPACE: RegValue |= BIT14 + BIT13; break;
+ case HDLC_ENCODING_BIPHASE_MARK: RegValue |= BIT15; break;
+ case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT15 + BIT13; break;
+ case HDLC_ENCODING_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14; break;
+ case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14 + BIT13; break;
+ }
+
+ if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_16_CCITT )
+ RegValue |= BIT9 + BIT8;
+ else if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_32_CCITT )
+ RegValue |= ( BIT12 | BIT10 | BIT9 | BIT8);
+
+ usc_OutReg( info, TMR, RegValue );
+
+ usc_set_txidle( info );
+
+
+ usc_TCmd( info, TCmd_SelectTicrdma_level );
+
+ /* Transmit Interrupt Control Register (TICR)
+ *
+ * <15..8> ? Transmit FIFO DMA Level
+ * <7> 0 Present IA (Interrupt Arm)
+ * <6> 0 Idle Sent IA
+ * <5> 1 Abort Sent IA
+ * <4> 1 EOF/EOM Sent IA
+ * <3> 0 CRC Sent IA
+ * <2> 1 1 = Wait for SW Trigger to Start Frame
+ * <1> 1 Tx Underrun IA
+ * <0> 0 TC0 constant on read back
+ *
+ * 0000 0000 0011 0110 = 0x0036
+ */
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ usc_OutReg( info, TICR, 0x0736 );
+ else
+ usc_OutReg( info, TICR, 0x1436 );
+
+ usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+
+ /*
+ ** Transmit Command/Status Register (TCSR)
+ **
+ ** <15..12> 0000 TCmd
+ ** <11> 0/1 UnderWait
+ ** <10..08> 000 TxIdle
+ ** <7> x PreSent
+ ** <6> x IdleSent
+ ** <5> x AbortSent
+ ** <4> x EOF/EOM Sent
+ ** <3> x CRC Sent
+ ** <2> x All Sent
+ ** <1> x TxUnder
+ ** <0> x TxEmpty
+ **
+ ** 0000 0000 0000 0000 = 0x0000
+ */
+ info->tcsr_value = 0;
+
+ if ( !PreSL1660 )
+ info->tcsr_value |= TCSR_UNDERWAIT;
+
+ usc_OutReg( info, TCSR, info->tcsr_value );
+
+ /* Clock mode Control Register (CMCR)
+ *
+ * <15..14> 00 counter 1 Source = Disabled
+ * <13..12> 00 counter 0 Source = Disabled
+ * <11..10> 11 BRG1 Input is TxC Pin
+ * <9..8> 11 BRG0 Input is TxC Pin
+ * <7..6> 01 DPLL Input is BRG1 Output
+ * <5..3> XXX TxCLK comes from Port 0
+ * <2..0> XXX RxCLK comes from Port 1
+ *
+ * 0000 1111 0111 0111 = 0x0f77
+ */
+
+ RegValue = 0x0f40;
+
+ if ( info->params.flags & HDLC_FLAG_RXC_DPLL )
+ RegValue |= 0x0003; /* RxCLK from DPLL */
+ else if ( info->params.flags & HDLC_FLAG_RXC_BRG )
+ RegValue |= 0x0004; /* RxCLK from BRG0 */
+ else if ( info->params.flags & HDLC_FLAG_RXC_TXCPIN)
+ RegValue |= 0x0006; /* RxCLK from TXC Input */
+ else
+ RegValue |= 0x0007; /* RxCLK from Port1 */
+
+ if ( info->params.flags & HDLC_FLAG_TXC_DPLL )
+ RegValue |= 0x0018; /* TxCLK from DPLL */
+ else if ( info->params.flags & HDLC_FLAG_TXC_BRG )
+ RegValue |= 0x0020; /* TxCLK from BRG0 */
+ else if ( info->params.flags & HDLC_FLAG_TXC_RXCPIN)
+ RegValue |= 0x0038; /* RxCLK from TXC Input */
+ else
+ RegValue |= 0x0030; /* TxCLK from Port0 */
+
+ usc_OutReg( info, CMCR, RegValue );
+
+
+ /* Hardware Configuration Register (HCR)
+ *
+ * <15..14> 00 CTR0 Divisor:00=32,01=16,10=8,11=4
+ * <13> 0 CTR1DSel:0=CTR0Div determines CTR0Div
+ * <12> 0 CVOK:0=report code violation in biphase
+ * <11..10> 00 DPLL Divisor:00=32,01=16,10=8,11=4
+ * <9..8> XX DPLL mode:00=disable,01=NRZ,10=Biphase,11=Biphase Level
+ * <7..6> 00 reserved
+ * <5> 0 BRG1 mode:0=continuous,1=single cycle
+ * <4> X BRG1 Enable
+ * <3..2> 00 reserved
+ * <1> 0 BRG0 mode:0=continuous,1=single cycle
+ * <0> 0 BRG0 Enable
+ */
+
+ RegValue = 0x0000;
+
+ if ( info->params.flags & (HDLC_FLAG_RXC_DPLL + HDLC_FLAG_TXC_DPLL) ) {
+ u32 XtalSpeed;
+ u32 DpllDivisor;
+ u16 Tc;
+
+ /* DPLL is enabled. Use BRG1 to provide continuous reference clock */
+ /* for DPLL. DPLL mode in HCR is dependent on the encoding used. */
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ XtalSpeed = 11059200;
+ else
+ XtalSpeed = 14745600;
+
+ if ( info->params.flags & HDLC_FLAG_DPLL_DIV16 ) {
+ DpllDivisor = 16;
+ RegValue |= BIT10;
+ }
+ else if ( info->params.flags & HDLC_FLAG_DPLL_DIV8 ) {
+ DpllDivisor = 8;
+ RegValue |= BIT11;
+ }
+ else
+ DpllDivisor = 32;
+
+ /* Tc = (Xtal/Speed) - 1 */
+ /* If twice the remainder of (Xtal/Speed) is greater than Speed */
+ /* then rounding up gives a more precise time constant. Instead */
+ /* of rounding up and then subtracting 1 we just don't subtract */
+ /* the one in this case. */
+
+ /*--------------------------------------------------
+ * ejz: for DPLL mode, application should use the
+ * same clock speed as the partner system, even
+ * though clocking is derived from the input RxData.
+ * In case the user uses a 0 for the clock speed,
+ * default to 0xffffffff and don't try to divide by
+ * zero
+ *--------------------------------------------------*/
+ if ( info->params.clock_speed )
+ {
+ Tc = (u16)((XtalSpeed/DpllDivisor)/info->params.clock_speed);
+ if ( !((((XtalSpeed/DpllDivisor) % info->params.clock_speed) * 2)
+ / info->params.clock_speed) )
+ Tc--;
+ }
+ else
+ Tc = -1;
+
+
+ /* Write 16-bit Time Constant for BRG1 */
+ usc_OutReg( info, TC1R, Tc );
+
+ RegValue |= BIT4; /* enable BRG1 */
+
+ switch ( info->params.encoding ) {
+ case HDLC_ENCODING_NRZ:
+ case HDLC_ENCODING_NRZB:
+ case HDLC_ENCODING_NRZI_MARK:
+ case HDLC_ENCODING_NRZI_SPACE: RegValue |= BIT8; break;
+ case HDLC_ENCODING_BIPHASE_MARK:
+ case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT9; break;
+ case HDLC_ENCODING_BIPHASE_LEVEL:
+ case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT9 + BIT8; break;
+ }
+ }
+
+ usc_OutReg( info, HCR, RegValue );
+
+
+ /* Channel Control/status Register (CCSR)
+ *
+ * <15> X RCC FIFO Overflow status (RO)
+ * <14> X RCC FIFO Not Empty status (RO)
+ * <13> 0 1 = Clear RCC FIFO (WO)
+ * <12> X DPLL Sync (RW)
+ * <11> X DPLL 2 Missed Clocks status (RO)
+ * <10> X DPLL 1 Missed Clock status (RO)
+ * <9..8> 00 DPLL Resync on rising and falling edges (RW)
+ * <7> X SDLC Loop On status (RO)
+ * <6> X SDLC Loop Send status (RO)
+ * <5> 1 Bypass counters for TxClk and RxClk (RW)
+ * <4..2> 000 Last Char of SDLC frame has 8 bits (RW)
+ * <1..0> 00 reserved
+ *
+ * 0000 0000 0010 0000 = 0x0020
+ */
+
+ usc_OutReg( info, CCSR, 0x1020 );
+
+
+ if ( info->params.flags & HDLC_FLAG_AUTO_CTS ) {
+ usc_OutReg( info, SICR,
+ (u16)(usc_InReg(info,SICR) | SICR_CTS_INACTIVE) );
+ }
+
+
+ /* enable Master Interrupt Enable bit (MIE) */
+ usc_EnableMasterIrqBit( info );
+
+ usc_ClearIrqPendingBits( info, RECEIVE_STATUS + RECEIVE_DATA +
+ TRANSMIT_STATUS + TRANSMIT_DATA + MISC);
+
+ /* arm RCC underflow interrupt */
+ usc_OutReg(info, SICR, (u16)(usc_InReg(info,SICR) | BIT3));
+ usc_EnableInterrupts(info, MISC);
+
+ info->mbre_bit = 0;
+ outw( 0, info->io_base ); /* clear Master Bus Enable (DCAR) */
+ usc_DmaCmd( info, DmaCmd_ResetAllChannels ); /* disable both DMA channels */
+ info->mbre_bit = BIT8;
+ outw( BIT8, info->io_base ); /* set Master Bus Enable (DCAR) */
+
+ if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+ /* Enable DMAEN (Port 7, Bit 14) */
+ /* This connects the DMA request signal to the ISA bus */
+ usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT15) & ~BIT14));
+ }
+
+ /* DMA Control Register (DCR)
+ *
+ * <15..14> 10 Priority mode = Alternating Tx/Rx
+ * 01 Rx has priority
+ * 00 Tx has priority
+ *
+ * <13> 1 Enable Priority Preempt per DCR<15..14>
+ * (WARNING DCR<11..10> must be 00 when this is 1)
+ * 0 Choose activate channel per DCR<11..10>
+ *
+ * <12> 0 Little Endian for Array/List
+ * <11..10> 00 Both Channels can use each bus grant
+ * <9..6> 0000 reserved
+ * <5> 0 7 CLK - Minimum Bus Re-request Interval
+ * <4> 0 1 = drive D/C and S/D pins
+ * <3> 1 1 = Add one wait state to all DMA cycles.
+ * <2> 0 1 = Strobe /UAS on every transfer.
+ * <1..0> 11 Addr incrementing only affects LS24 bits
+ *
+ * 0110 0000 0000 1011 = 0x600b
+ */
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ /* PCI adapter does not need DMA wait state */
+ usc_OutDmaReg( info, DCR, 0xa00b );
+ }
+ else
+ usc_OutDmaReg( info, DCR, 0x800b );
+
+
+ /* Receive DMA mode Register (RDMR)
+ *
+ * <15..14> 11 DMA mode = Linked List Buffer mode
+ * <13> 1 RSBinA/L = store Rx status Block in Arrary/List entry
+ * <12> 1 Clear count of List Entry after fetching
+ * <11..10> 00 Address mode = Increment
+ * <9> 1 Terminate Buffer on RxBound
+ * <8> 0 Bus Width = 16bits
+ * <7..0> ? status Bits (write as 0s)
+ *
+ * 1111 0010 0000 0000 = 0xf200
+ */
+
+ usc_OutDmaReg( info, RDMR, 0xf200 );
+
+
+ /* Transmit DMA mode Register (TDMR)
+ *
+ * <15..14> 11 DMA mode = Linked List Buffer mode
+ * <13> 1 TCBinA/L = fetch Tx Control Block from List entry
+ * <12> 1 Clear count of List Entry after fetching
+ * <11..10> 00 Address mode = Increment
+ * <9> 1 Terminate Buffer on end of frame
+ * <8> 0 Bus Width = 16bits
+ * <7..0> ? status Bits (Read Only so write as 0)
+ *
+ * 1111 0010 0000 0000 = 0xf200
+ */
+
+ usc_OutDmaReg( info, TDMR, 0xf200 );
+
+
+ /* DMA Interrupt Control Register (DICR)
+ *
+ * <15> 1 DMA Interrupt Enable
+ * <14> 0 1 = Disable IEO from USC
+ * <13> 0 1 = Don't provide vector during IntAck
+ * <12> 1 1 = Include status in Vector
+ * <10..2> 0 reserved, Must be 0s
+ * <1> 0 1 = Rx DMA Interrupt Enabled
+ * <0> 0 1 = Tx DMA Interrupt Enabled
+ *
+ * 1001 0000 0000 0000 = 0x9000
+ */
+
+ usc_OutDmaReg( info, DICR, 0x9000 );
+
+ usc_InDmaReg( info, RDMR ); /* clear pending receive DMA IRQ bits */
+ usc_InDmaReg( info, TDMR ); /* clear pending transmit DMA IRQ bits */
+ usc_OutDmaReg( info, CDIR, 0x0303 ); /* clear IUS and Pending for Tx and Rx */
+
+ /* Channel Control Register (CCR)
+ *
+ * <15..14> 10 Use 32-bit Tx Control Blocks (TCBs)
+ * <13> 0 Trigger Tx on SW Command Disabled
+ * <12> 0 Flag Preamble Disabled
+ * <11..10> 00 Preamble Length
+ * <9..8> 00 Preamble Pattern
+ * <7..6> 10 Use 32-bit Rx status Blocks (RSBs)
+ * <5> 0 Trigger Rx on SW Command Disabled
+ * <4..0> 0 reserved
+ *
+ * 1000 0000 1000 0000 = 0x8080
+ */
+
+ RegValue = 0x8080;
+
+ switch ( info->params.preamble_length ) {
+ case HDLC_PREAMBLE_LENGTH_16BITS: RegValue |= BIT10; break;
+ case HDLC_PREAMBLE_LENGTH_32BITS: RegValue |= BIT11; break;
+ case HDLC_PREAMBLE_LENGTH_64BITS: RegValue |= BIT11 + BIT10; break;
+ }
+
+ switch ( info->params.preamble ) {
+ case HDLC_PREAMBLE_PATTERN_FLAGS: RegValue |= BIT8 + BIT12; break;
+ case HDLC_PREAMBLE_PATTERN_ONES: RegValue |= BIT8; break;
+ case HDLC_PREAMBLE_PATTERN_10: RegValue |= BIT9; break;
+ case HDLC_PREAMBLE_PATTERN_01: RegValue |= BIT9 + BIT8; break;
+ }
+
+ usc_OutReg( info, CCR, RegValue );
+
+
+ /*
+ * Burst/Dwell Control Register
+ *
+ * <15..8> 0x20 Maximum number of transfers per bus grant
+ * <7..0> 0x00 Maximum number of clock cycles per bus grant
+ */
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ /* don't limit bus occupancy on PCI adapter */
+ usc_OutDmaReg( info, BDCR, 0x0000 );
+ }
+ else
+ usc_OutDmaReg( info, BDCR, 0x2000 );
+
+ usc_stop_transmitter(info);
+ usc_stop_receiver(info);
+
+} /* end of usc_set_sdlc_mode() */
+
+/* usc_enable_loopback()
+ *
+ * Set the 16C32 for internal loopback mode.
+ * The TxCLK and RxCLK signals are generated from the BRG0 and
+ * the TxD is looped back to the RxD internally.
+ *
+ * Arguments: info pointer to device instance data
+ * enable 1 = enable loopback, 0 = disable
+ * Return Value: None
+ */
+static void usc_enable_loopback(struct mgsl_struct *info, int enable)
+{
+ if (enable) {
+ /* blank external TXD output */
+ usc_OutReg(info,IOCR,usc_InReg(info,IOCR) | (BIT7+BIT6));
+
+ /* Clock mode Control Register (CMCR)
+ *
+ * <15..14> 00 counter 1 Disabled
+ * <13..12> 00 counter 0 Disabled
+ * <11..10> 11 BRG1 Input is TxC Pin
+ * <9..8> 11 BRG0 Input is TxC Pin
+ * <7..6> 01 DPLL Input is BRG1 Output
+ * <5..3> 100 TxCLK comes from BRG0
+ * <2..0> 100 RxCLK comes from BRG0
+ *
+ * 0000 1111 0110 0100 = 0x0f64
+ */
+
+ usc_OutReg( info, CMCR, 0x0f64 );
+
+ /* Write 16-bit Time Constant for BRG0 */
+ /* use clock speed if available, otherwise use 8 for diagnostics */
+ if (info->params.clock_speed) {
+ if (info->bus_type == MGSL_BUS_TYPE_PCI)
+ usc_OutReg(info, TC0R, (u16)((11059200/info->params.clock_speed)-1));
+ else
+ usc_OutReg(info, TC0R, (u16)((14745600/info->params.clock_speed)-1));
+ } else
+ usc_OutReg(info, TC0R, (u16)8);
+
+ /* Hardware Configuration Register (HCR) Clear Bit 1, BRG0
+ mode = Continuous Set Bit 0 to enable BRG0. */
+ usc_OutReg( info, HCR, (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+ /* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+ usc_OutReg(info, IOCR, (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004));
+
+ /* set Internal Data loopback mode */
+ info->loopback_bits = 0x300;
+ outw( 0x0300, info->io_base + CCAR );
+ } else {
+ /* enable external TXD output */
+ usc_OutReg(info,IOCR,usc_InReg(info,IOCR) & ~(BIT7+BIT6));
+
+ /* clear Internal Data loopback mode */
+ info->loopback_bits = 0;
+ outw( 0,info->io_base + CCAR );
+ }
+
+} /* end of usc_enable_loopback() */
+
+/* usc_enable_aux_clock()
+ *
+ * Enabled the AUX clock output at the specified frequency.
+ *
+ * Arguments:
+ *
+ * info pointer to device extension
+ * data_rate data rate of clock in bits per second
+ * A data rate of 0 disables the AUX clock.
+ *
+ * Return Value: None
+ */
+static void usc_enable_aux_clock( struct mgsl_struct *info, u32 data_rate )
+{
+ u32 XtalSpeed;
+ u16 Tc;
+
+ if ( data_rate ) {
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ XtalSpeed = 11059200;
+ else
+ XtalSpeed = 14745600;
+
+
+ /* Tc = (Xtal/Speed) - 1 */
+ /* If twice the remainder of (Xtal/Speed) is greater than Speed */
+ /* then rounding up gives a more precise time constant. Instead */
+ /* of rounding up and then subtracting 1 we just don't subtract */
+ /* the one in this case. */
+
+
+ Tc = (u16)(XtalSpeed/data_rate);
+ if ( !(((XtalSpeed % data_rate) * 2) / data_rate) )
+ Tc--;
+
+ /* Write 16-bit Time Constant for BRG0 */
+ usc_OutReg( info, TC0R, Tc );
+
+ /*
+ * Hardware Configuration Register (HCR)
+ * Clear Bit 1, BRG0 mode = Continuous
+ * Set Bit 0 to enable BRG0.
+ */
+
+ usc_OutReg( info, HCR, (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+ /* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+ usc_OutReg( info, IOCR, (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004) );
+ } else {
+ /* data rate == 0 so turn off BRG0 */
+ usc_OutReg( info, HCR, (u16)(usc_InReg( info, HCR ) & ~BIT0) );
+ }
+
+} /* end of usc_enable_aux_clock() */
+
+/*
+ *
+ * usc_process_rxoverrun_sync()
+ *
+ * This function processes a receive overrun by resetting the
+ * receive DMA buffers and issuing a Purge Rx FIFO command
+ * to allow the receiver to continue receiving.
+ *
+ * Arguments:
+ *
+ * info pointer to device extension
+ *
+ * Return Value: None
+ */
+static void usc_process_rxoverrun_sync( struct mgsl_struct *info )
+{
+ int start_index;
+ int end_index;
+ int frame_start_index;
+ int start_of_frame_found = FALSE;
+ int end_of_frame_found = FALSE;
+ int reprogram_dma = FALSE;
+
+ DMABUFFERENTRY *buffer_list = info->rx_buffer_list;
+ u32 phys_addr;
+
+ usc_DmaCmd( info, DmaCmd_PauseRxChannel );
+ usc_RCmd( info, RCmd_EnterHuntmode );
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+ /* CurrentRxBuffer points to the 1st buffer of the next */
+ /* possibly available receive frame. */
+
+ frame_start_index = start_index = end_index = info->current_rx_buffer;
+
+ /* Search for an unfinished string of buffers. This means */
+ /* that a receive frame started (at least one buffer with */
+ /* count set to zero) but there is no terminiting buffer */
+ /* (status set to non-zero). */
+
+ while( !buffer_list[end_index].count )
+ {
+ /* Count field has been reset to zero by 16C32. */
+ /* This buffer is currently in use. */
+
+ if ( !start_of_frame_found )
+ {
+ start_of_frame_found = TRUE;
+ frame_start_index = end_index;
+ end_of_frame_found = FALSE;
+ }
+
+ if ( buffer_list[end_index].status )
+ {
+ /* Status field has been set by 16C32. */
+ /* This is the last buffer of a received frame. */
+
+ /* We want to leave the buffers for this frame intact. */
+ /* Move on to next possible frame. */
+
+ start_of_frame_found = FALSE;
+ end_of_frame_found = TRUE;
+ }
+
+ /* advance to next buffer entry in linked list */
+ end_index++;
+ if ( end_index == info->rx_buffer_count )
+ end_index = 0;
+
+ if ( start_index == end_index )
+ {
+ /* The entire list has been searched with all Counts == 0 and */
+ /* all Status == 0. The receive buffers are */
+ /* completely screwed, reset all receive buffers! */
+ mgsl_reset_rx_dma_buffers( info );
+ frame_start_index = 0;
+ start_of_frame_found = FALSE;
+ reprogram_dma = TRUE;
+ break;
+ }
+ }
+
+ if ( start_of_frame_found && !end_of_frame_found )
+ {
+ /* There is an unfinished string of receive DMA buffers */
+ /* as a result of the receiver overrun. */
+
+ /* Reset the buffers for the unfinished frame */
+ /* and reprogram the receive DMA controller to start */
+ /* at the 1st buffer of unfinished frame. */
+
+ start_index = frame_start_index;
+
+ do
+ {
+ *((unsigned long *)&(info->rx_buffer_list[start_index++].count)) = DMABUFFERSIZE;
+
+ /* Adjust index for wrap around. */
+ if ( start_index == info->rx_buffer_count )
+ start_index = 0;
+
+ } while( start_index != end_index );
+
+ reprogram_dma = TRUE;
+ }
+
+ if ( reprogram_dma )
+ {
+ usc_UnlatchRxstatusBits(info,RXSTATUS_ALL);
+ usc_ClearIrqPendingBits(info, RECEIVE_DATA|RECEIVE_STATUS);
+ usc_UnlatchRxstatusBits(info, RECEIVE_DATA|RECEIVE_STATUS);
+
+ usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+
+ /* This empties the receive FIFO and loads the RCC with RCLR */
+ usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+
+ /* program 16C32 with physical address of 1st DMA buffer entry */
+ phys_addr = info->rx_buffer_list[frame_start_index].phys_entry;
+ usc_OutDmaReg( info, NRARL, (u16)phys_addr );
+ usc_OutDmaReg( info, NRARU, (u16)(phys_addr >> 16) );
+
+ usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+ usc_EnableInterrupts( info, RECEIVE_STATUS );
+
+ /* 1. Arm End of Buffer (EOB) Receive DMA Interrupt (BIT2 of RDIAR) */
+ /* 2. Enable Receive DMA Interrupts (BIT1 of DICR) */
+
+ usc_OutDmaReg( info, RDIAR, BIT3 + BIT2 );
+ usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT1) );
+ usc_DmaCmd( info, DmaCmd_InitRxChannel );
+ if ( info->params.flags & HDLC_FLAG_AUTO_DCD )
+ usc_EnableReceiver(info,ENABLE_AUTO_DCD);
+ else
+ usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+ }
+ else
+ {
+ /* This empties the receive FIFO and loads the RCC with RCLR */
+ usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+ }
+
+} /* end of usc_process_rxoverrun_sync() */
+
+/* usc_stop_receiver()
+ *
+ * Disable USC receiver
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_stop_receiver( struct mgsl_struct *info )
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):usc_stop_receiver(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ /* Disable receive DMA channel. */
+ /* This also disables receive DMA channel interrupts */
+ usc_DmaCmd( info, DmaCmd_ResetRxChannel );
+
+ usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+ usc_DisableInterrupts( info, RECEIVE_DATA + RECEIVE_STATUS );
+
+ usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+
+ /* This empties the receive FIFO and loads the RCC with RCLR */
+ usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+ info->rx_enabled = 0;
+ info->rx_overflow = 0;
+ info->rx_rcc_underrun = 0;
+
+} /* end of stop_receiver() */
+
+/* usc_start_receiver()
+ *
+ * Enable the USC receiver
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_start_receiver( struct mgsl_struct *info )
+{
+ u32 phys_addr;
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):usc_start_receiver(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ mgsl_reset_rx_dma_buffers( info );
+ usc_stop_receiver( info );
+
+ usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+ if ( info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW ) {
+ /* DMA mode Transfers */
+ /* Program the DMA controller. */
+ /* Enable the DMA controller end of buffer interrupt. */
+
+ /* program 16C32 with physical address of 1st DMA buffer entry */
+ phys_addr = info->rx_buffer_list[0].phys_entry;
+ usc_OutDmaReg( info, NRARL, (u16)phys_addr );
+ usc_OutDmaReg( info, NRARU, (u16)(phys_addr >> 16) );
+
+ usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+ usc_EnableInterrupts( info, RECEIVE_STATUS );
+
+ /* 1. Arm End of Buffer (EOB) Receive DMA Interrupt (BIT2 of RDIAR) */
+ /* 2. Enable Receive DMA Interrupts (BIT1 of DICR) */
+
+ usc_OutDmaReg( info, RDIAR, BIT3 + BIT2 );
+ usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT1) );
+ usc_DmaCmd( info, DmaCmd_InitRxChannel );
+ if ( info->params.flags & HDLC_FLAG_AUTO_DCD )
+ usc_EnableReceiver(info,ENABLE_AUTO_DCD);
+ else
+ usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+ } else {
+ usc_UnlatchRxstatusBits(info, RXSTATUS_ALL);
+ usc_ClearIrqPendingBits(info, RECEIVE_DATA + RECEIVE_STATUS);
+ usc_EnableInterrupts(info, RECEIVE_DATA);
+
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+ usc_RCmd( info, RCmd_EnterHuntmode );
+
+ usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+ }
+
+ usc_OutReg( info, CCSR, 0x1020 );
+
+ info->rx_enabled = 1;
+
+} /* end of usc_start_receiver() */
+
+/* usc_start_transmitter()
+ *
+ * Enable the USC transmitter and send a transmit frame if
+ * one is loaded in the DMA buffers.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_start_transmitter( struct mgsl_struct *info )
+{
+ u32 phys_addr;
+ unsigned int FrameSize;
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):usc_start_transmitter(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if ( info->xmit_cnt ) {
+
+ /* If auto RTS enabled and RTS is inactive, then assert */
+ /* RTS and set a flag indicating that the driver should */
+ /* negate RTS when the transmission completes. */
+
+ info->drop_rts_on_tx_done = 0;
+
+ if ( info->params.flags & HDLC_FLAG_AUTO_RTS ) {
+ usc_get_serial_signals( info );
+ if ( !(info->serial_signals & SerialSignal_RTS) ) {
+ info->serial_signals |= SerialSignal_RTS;
+ usc_set_serial_signals( info );
+ info->drop_rts_on_tx_done = 1;
+ }
+ }
+
+
+ if ( info->params.mode == MGSL_MODE_ASYNC ) {
+ if ( !info->tx_active ) {
+ usc_UnlatchTxstatusBits(info, TXSTATUS_ALL);
+ usc_ClearIrqPendingBits(info, TRANSMIT_STATUS + TRANSMIT_DATA);
+ usc_EnableInterrupts(info, TRANSMIT_DATA);
+ usc_load_txfifo(info);
+ }
+ } else {
+ /* Disable transmit DMA controller while programming. */
+ usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+
+ /* Transmit DMA buffer is loaded, so program USC */
+ /* to send the frame contained in the buffers. */
+
+ FrameSize = info->tx_buffer_list[info->start_tx_dma_buffer].rcc;
+
+ /* if operating in Raw sync mode, reset the rcc component
+ * of the tx dma buffer entry, otherwise, the serial controller
+ * will send a closing sync char after this count.
+ */
+ if ( info->params.mode == MGSL_MODE_RAW )
+ info->tx_buffer_list[info->start_tx_dma_buffer].rcc = 0;
+
+ /* Program the Transmit Character Length Register (TCLR) */
+ /* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+ usc_OutReg( info, TCLR, (u16)FrameSize );
+
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+ /* Program the address of the 1st DMA Buffer Entry in linked list */
+ phys_addr = info->tx_buffer_list[info->start_tx_dma_buffer].phys_entry;
+ usc_OutDmaReg( info, NTARL, (u16)phys_addr );
+ usc_OutDmaReg( info, NTARU, (u16)(phys_addr >> 16) );
+
+ usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+ usc_EnableInterrupts( info, TRANSMIT_STATUS );
+
+ if ( info->params.mode == MGSL_MODE_RAW &&
+ info->num_tx_dma_buffers > 1 ) {
+ /* When running external sync mode, attempt to 'stream' transmit */
+ /* by filling tx dma buffers as they become available. To do this */
+ /* we need to enable Tx DMA EOB Status interrupts : */
+ /* */
+ /* 1. Arm End of Buffer (EOB) Transmit DMA Interrupt (BIT2 of TDIAR) */
+ /* 2. Enable Transmit DMA Interrupts (BIT0 of DICR) */
+
+ usc_OutDmaReg( info, TDIAR, BIT2|BIT3 );
+ usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT0) );
+ }
+
+ /* Initialize Transmit DMA Channel */
+ usc_DmaCmd( info, DmaCmd_InitTxChannel );
+
+ usc_TCmd( info, TCmd_SendFrame );
+
+ info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+ add_timer(&info->tx_timer);
+ }
+ info->tx_active = 1;
+ }
+
+ if ( !info->tx_enabled ) {
+ info->tx_enabled = 1;
+ if ( info->params.flags & HDLC_FLAG_AUTO_CTS )
+ usc_EnableTransmitter(info,ENABLE_AUTO_CTS);
+ else
+ usc_EnableTransmitter(info,ENABLE_UNCONDITIONAL);
+ }
+
+} /* end of usc_start_transmitter() */
+
+/* usc_stop_transmitter()
+ *
+ * Stops the transmitter and DMA
+ *
+ * Arguments: info pointer to device isntance data
+ * Return Value: None
+ */
+static void usc_stop_transmitter( struct mgsl_struct *info )
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):usc_stop_transmitter(%s)\n",
+ __FILE__,__LINE__, info->device_name );
+
+ del_timer(&info->tx_timer);
+
+ usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS + TRANSMIT_DATA );
+ usc_DisableInterrupts( info, TRANSMIT_STATUS + TRANSMIT_DATA );
+
+ usc_EnableTransmitter(info,DISABLE_UNCONDITIONAL);
+ usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+ info->tx_enabled = 0;
+ info->tx_active = 0;
+
+} /* end of usc_stop_transmitter() */
+
+/* usc_load_txfifo()
+ *
+ * Fill the transmit FIFO until the FIFO is full or
+ * there is no more data to load.
+ *
+ * Arguments: info pointer to device extension (instance data)
+ * Return Value: None
+ */
+static void usc_load_txfifo( struct mgsl_struct *info )
+{
+ int Fifocount;
+ u8 TwoBytes[2];
+
+ if ( !info->xmit_cnt && !info->x_char )
+ return;
+
+ /* Select transmit FIFO status readback in TICR */
+ usc_TCmd( info, TCmd_SelectTicrTxFifostatus );
+
+ /* load the Transmit FIFO until FIFOs full or all data sent */
+
+ while( (Fifocount = usc_InReg(info, TICR) >> 8) && info->xmit_cnt ) {
+ /* there is more space in the transmit FIFO and */
+ /* there is more data in transmit buffer */
+
+ if ( (info->xmit_cnt > 1) && (Fifocount > 1) && !info->x_char ) {
+ /* write a 16-bit word from transmit buffer to 16C32 */
+
+ TwoBytes[0] = info->xmit_buf[info->xmit_tail++];
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ TwoBytes[1] = info->xmit_buf[info->xmit_tail++];
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+
+ outw( *((u16 *)TwoBytes), info->io_base + DATAREG);
+
+ info->xmit_cnt -= 2;
+ info->icount.tx += 2;
+ } else {
+ /* only 1 byte left to transmit or 1 FIFO slot left */
+
+ outw( (inw( info->io_base + CCAR) & 0x0780) | (TDR+LSBONLY),
+ info->io_base + CCAR );
+
+ if (info->x_char) {
+ /* transmit pending high priority char */
+ outw( info->x_char,info->io_base + CCAR );
+ info->x_char = 0;
+ } else {
+ outw( info->xmit_buf[info->xmit_tail++],info->io_base + CCAR );
+ info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+ info->xmit_cnt--;
+ }
+ info->icount.tx++;
+ }
+ }
+
+} /* end of usc_load_txfifo() */
+
+/* usc_reset()
+ *
+ * Reset the adapter to a known state and prepare it for further use.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_reset( struct mgsl_struct *info )
+{
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+ int i;
+ u32 readval;
+
+ /* Set BIT30 of Misc Control Register */
+ /* (Local Control Register 0x50) to force reset of USC. */
+
+ volatile u32 *MiscCtrl = (u32 *)(info->lcr_base + 0x50);
+ u32 *LCR0BRDR = (u32 *)(info->lcr_base + 0x28);
+
+ info->misc_ctrl_value |= BIT30;
+ *MiscCtrl = info->misc_ctrl_value;
+
+ /*
+ * Force at least 170ns delay before clearing
+ * reset bit. Each read from LCR takes at least
+ * 30ns so 10 times for 300ns to be safe.
+ */
+ for(i=0;i<10;i++)
+ readval = *MiscCtrl;
+
+ info->misc_ctrl_value &= ~BIT30;
+ *MiscCtrl = info->misc_ctrl_value;
+
+ *LCR0BRDR = BUS_DESCRIPTOR(
+ 1, // Write Strobe Hold (0-3)
+ 2, // Write Strobe Delay (0-3)
+ 2, // Read Strobe Delay (0-3)
+ 0, // NWDD (Write data-data) (0-3)
+ 4, // NWAD (Write Addr-data) (0-31)
+ 0, // NXDA (Read/Write Data-Addr) (0-3)
+ 0, // NRDD (Read Data-Data) (0-3)
+ 5 // NRAD (Read Addr-Data) (0-31)
+ );
+ } else {
+ /* do HW reset */
+ outb( 0,info->io_base + 8 );
+ }
+
+ info->mbre_bit = 0;
+ info->loopback_bits = 0;
+ info->usc_idle_mode = 0;
+
+ /*
+ * Program the Bus Configuration Register (BCR)
+ *
+ * <15> 0 Don't use separate address
+ * <14..6> 0 reserved
+ * <5..4> 00 IAckmode = Default, don't care
+ * <3> 1 Bus Request Totem Pole output
+ * <2> 1 Use 16 Bit data bus
+ * <1> 0 IRQ Totem Pole output
+ * <0> 0 Don't Shift Right Addr
+ *
+ * 0000 0000 0000 1100 = 0x000c
+ *
+ * By writing to io_base + SDPIN the Wait/Ack pin is
+ * programmed to work as a Wait pin.
+ */
+
+ outw( 0x000c,info->io_base + SDPIN );
+
+
+ outw( 0,info->io_base );
+ outw( 0,info->io_base + CCAR );
+
+ /* select little endian byte ordering */
+ usc_RTCmd( info, RTCmd_SelectLittleEndian );
+
+
+ /* Port Control Register (PCR)
+ *
+ * <15..14> 11 Port 7 is Output (~DMAEN, Bit 14 : 0 = Enabled)
+ * <13..12> 11 Port 6 is Output (~INTEN, Bit 12 : 0 = Enabled)
+ * <11..10> 00 Port 5 is Input (No Connect, Don't Care)
+ * <9..8> 00 Port 4 is Input (No Connect, Don't Care)
+ * <7..6> 11 Port 3 is Output (~RTS, Bit 6 : 0 = Enabled )
+ * <5..4> 11 Port 2 is Output (~DTR, Bit 4 : 0 = Enabled )
+ * <3..2> 01 Port 1 is Input (Dedicated RxC)
+ * <1..0> 01 Port 0 is Input (Dedicated TxC)
+ *
+ * 1111 0000 1111 0101 = 0xf0f5
+ */
+
+ usc_OutReg( info, PCR, 0xf0f5 );
+
+
+ /*
+ * Input/Output Control Register
+ *
+ * <15..14> 00 CTS is active low input
+ * <13..12> 00 DCD is active low input
+ * <11..10> 00 TxREQ pin is input (DSR)
+ * <9..8> 00 RxREQ pin is input (RI)
+ * <7..6> 00 TxD is output (Transmit Data)
+ * <5..3> 000 TxC Pin in Input (14.7456MHz Clock)
+ * <2..0> 100 RxC is Output (drive with BRG0)
+ *
+ * 0000 0000 0000 0100 = 0x0004
+ */
+
+ usc_OutReg( info, IOCR, 0x0004 );
+
+} /* end of usc_reset() */
+
+/* usc_set_async_mode()
+ *
+ * Program adapter for asynchronous communications.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_set_async_mode( struct mgsl_struct *info )
+{
+ u16 RegValue;
+
+ /* disable interrupts while programming USC */
+ usc_DisableMasterIrqBit( info );
+
+ outw( 0, info->io_base ); /* clear Master Bus Enable (DCAR) */
+ usc_DmaCmd( info, DmaCmd_ResetAllChannels ); /* disable both DMA channels */
+
+ usc_loopback_frame( info );
+
+ /* Channel mode Register (CMR)
+ *
+ * <15..14> 00 Tx Sub modes, 00 = 1 Stop Bit
+ * <13..12> 00 00 = 16X Clock
+ * <11..8> 0000 Transmitter mode = Asynchronous
+ * <7..6> 00 reserved?
+ * <5..4> 00 Rx Sub modes, 00 = 16X Clock
+ * <3..0> 0000 Receiver mode = Asynchronous
+ *
+ * 0000 0000 0000 0000 = 0x0
+ */
+
+ RegValue = 0;
+ if ( info->params.stop_bits != 1 )
+ RegValue |= BIT14;
+ usc_OutReg( info, CMR, RegValue );
+
+
+ /* Receiver mode Register (RMR)
+ *
+ * <15..13> 000 encoding = None
+ * <12..08> 00000 reserved (Sync Only)
+ * <7..6> 00 Even parity
+ * <5> 0 parity disabled
+ * <4..2> 000 Receive Char Length = 8 bits
+ * <1..0> 00 Disable Receiver
+ *
+ * 0000 0000 0000 0000 = 0x0
+ */
+
+ RegValue = 0;
+
+ if ( info->params.data_bits != 8 )
+ RegValue |= BIT4+BIT3+BIT2;
+
+ if ( info->params.parity != ASYNC_PARITY_NONE ) {
+ RegValue |= BIT5;
+ if ( info->params.parity != ASYNC_PARITY_ODD )
+ RegValue |= BIT6;
+ }
+
+ usc_OutReg( info, RMR, RegValue );
+
+
+ /* Set IRQ trigger level */
+
+ usc_RCmd( info, RCmd_SelectRicrIntLevel );
+
+
+ /* Receive Interrupt Control Register (RICR)
+ *
+ * <15..8> ? RxFIFO IRQ Request Level
+ *
+ * Note: For async mode the receive FIFO level must be set
+ * to 0 to aviod the situation where the FIFO contains fewer bytes
+ * than the trigger level and no more data is expected.
+ *
+ * <7> 0 Exited Hunt IA (Interrupt Arm)
+ * <6> 0 Idle Received IA
+ * <5> 0 Break/Abort IA
+ * <4> 0 Rx Bound IA
+ * <3> 0 Queued status reflects oldest byte in FIFO
+ * <2> 0 Abort/PE IA
+ * <1> 0 Rx Overrun IA
+ * <0> 0 Select TC0 value for readback
+ *
+ * 0000 0000 0100 0000 = 0x0000 + (FIFOLEVEL in MSB)
+ */
+
+ usc_OutReg( info, RICR, 0x0000 );
+
+ usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+
+
+ /* Transmit mode Register (TMR)
+ *
+ * <15..13> 000 encoding = None
+ * <12..08> 00000 reserved (Sync Only)
+ * <7..6> 00 Transmit parity Even
+ * <5> 0 Transmit parity Disabled
+ * <4..2> 000 Tx Char Length = 8 bits
+ * <1..0> 00 Disable Transmitter
+ *
+ * 0000 0000 0000 0000 = 0x0
+ */
+
+ RegValue = 0;
+
+ if ( info->params.data_bits != 8 )
+ RegValue |= BIT4+BIT3+BIT2;
+
+ if ( info->params.parity != ASYNC_PARITY_NONE ) {
+ RegValue |= BIT5;
+ if ( info->params.parity != ASYNC_PARITY_ODD )
+ RegValue |= BIT6;
+ }
+
+ usc_OutReg( info, TMR, RegValue );
+
+ usc_set_txidle( info );
+
+
+ /* Set IRQ trigger level */
+
+ usc_TCmd( info, TCmd_SelectTicrIntLevel );
+
+
+ /* Transmit Interrupt Control Register (TICR)
+ *
+ * <15..8> ? Transmit FIFO IRQ Level
+ * <7> 0 Present IA (Interrupt Arm)
+ * <6> 1 Idle Sent IA
+ * <5> 0 Abort Sent IA
+ * <4> 0 EOF/EOM Sent IA
+ * <3> 0 CRC Sent IA
+ * <2> 0 1 = Wait for SW Trigger to Start Frame
+ * <1> 0 Tx Underrun IA
+ * <0> 0 TC0 constant on read back
+ *
+ * 0000 0000 0100 0000 = 0x0040
+ */
+
+ usc_OutReg( info, TICR, 0x1f40 );
+
+ usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+
+ usc_enable_async_clock( info, info->params.data_rate );
+
+
+ /* Channel Control/status Register (CCSR)
+ *
+ * <15> X RCC FIFO Overflow status (RO)
+ * <14> X RCC FIFO Not Empty status (RO)
+ * <13> 0 1 = Clear RCC FIFO (WO)
+ * <12> X DPLL in Sync status (RO)
+ * <11> X DPLL 2 Missed Clocks status (RO)
+ * <10> X DPLL 1 Missed Clock status (RO)
+ * <9..8> 00 DPLL Resync on rising and falling edges (RW)
+ * <7> X SDLC Loop On status (RO)
+ * <6> X SDLC Loop Send status (RO)
+ * <5> 1 Bypass counters for TxClk and RxClk (RW)
+ * <4..2> 000 Last Char of SDLC frame has 8 bits (RW)
+ * <1..0> 00 reserved
+ *
+ * 0000 0000 0010 0000 = 0x0020
+ */
+
+ usc_OutReg( info, CCSR, 0x0020 );
+
+ usc_DisableInterrupts( info, TRANSMIT_STATUS + TRANSMIT_DATA +
+ RECEIVE_DATA + RECEIVE_STATUS );
+
+ usc_ClearIrqPendingBits( info, TRANSMIT_STATUS + TRANSMIT_DATA +
+ RECEIVE_DATA + RECEIVE_STATUS );
+
+ usc_EnableMasterIrqBit( info );
+
+ if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+ /* Enable INTEN (Port 6, Bit12) */
+ /* This connects the IRQ request signal to the ISA bus */
+ usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) & ~BIT12));
+ }
+
+} /* end of usc_set_async_mode() */
+
+/* usc_loopback_frame()
+ *
+ * Loop back a small (2 byte) dummy SDLC frame.
+ * Interrupts and DMA are NOT used. The purpose of this is to
+ * clear any 'stale' status info left over from running in async mode.
+ *
+ * The 16C32 shows the strange behaviour of marking the 1st
+ * received SDLC frame with a CRC error even when there is no
+ * CRC error. To get around this a small dummy from of 2 bytes
+ * is looped back when switching from async to sync mode.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_loopback_frame( struct mgsl_struct *info )
+{
+ int i;
+ unsigned long oldmode = info->params.mode;
+
+ info->params.mode = MGSL_MODE_HDLC;
+
+ usc_DisableMasterIrqBit( info );
+
+ usc_set_sdlc_mode( info );
+ usc_enable_loopback( info, 1 );
+
+ /* Write 16-bit Time Constant for BRG0 */
+ usc_OutReg( info, TC0R, 0 );
+
+ /* Channel Control Register (CCR)
+ *
+ * <15..14> 00 Don't use 32-bit Tx Control Blocks (TCBs)
+ * <13> 0 Trigger Tx on SW Command Disabled
+ * <12> 0 Flag Preamble Disabled
+ * <11..10> 00 Preamble Length = 8-Bits
+ * <9..8> 01 Preamble Pattern = flags
+ * <7..6> 10 Don't use 32-bit Rx status Blocks (RSBs)
+ * <5> 0 Trigger Rx on SW Command Disabled
+ * <4..0> 0 reserved
+ *
+ * 0000 0001 0000 0000 = 0x0100
+ */
+
+ usc_OutReg( info, CCR, 0x0100 );
+
+ /* SETUP RECEIVER */
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+ usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+
+ /* SETUP TRANSMITTER */
+ /* Program the Transmit Character Length Register (TCLR) */
+ /* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+ usc_OutReg( info, TCLR, 2 );
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+ /* unlatch Tx status bits, and start transmit channel. */
+ usc_UnlatchTxstatusBits(info,TXSTATUS_ALL);
+ outw(0,info->io_base + DATAREG);
+
+ /* ENABLE TRANSMITTER */
+ usc_TCmd( info, TCmd_SendFrame );
+ usc_EnableTransmitter(info,ENABLE_UNCONDITIONAL);
+
+ /* WAIT FOR RECEIVE COMPLETE */
+ for (i=0 ; i<1000 ; i++)
+ if (usc_InReg( info, RCSR ) & (BIT8 + BIT4 + BIT3 + BIT1))
+ break;
+
+ /* clear Internal Data loopback mode */
+ usc_enable_loopback(info, 0);
+
+ usc_EnableMasterIrqBit(info);
+
+ info->params.mode = oldmode;
+
+} /* end of usc_loopback_frame() */
+
+/* usc_set_sync_mode() Programs the USC for SDLC communications.
+ *
+ * Arguments: info pointer to adapter info structure
+ * Return Value: None
+ */
+static void usc_set_sync_mode( struct mgsl_struct *info )
+{
+ usc_loopback_frame( info );
+ usc_set_sdlc_mode( info );
+
+ if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+ /* Enable INTEN (Port 6, Bit12) */
+ /* This connects the IRQ request signal to the ISA bus */
+ usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) & ~BIT12));
+ }
+
+ usc_enable_aux_clock(info, info->params.clock_speed);
+
+ if (info->params.loopback)
+ usc_enable_loopback(info,1);
+
+} /* end of mgsl_set_sync_mode() */
+
+/* usc_set_txidle() Set the HDLC idle mode for the transmitter.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_set_txidle( struct mgsl_struct *info )
+{
+ u16 usc_idle_mode = IDLEMODE_FLAGS;
+
+ /* Map API idle mode to USC register bits */
+
+ switch( info->idle_mode ){
+ case HDLC_TXIDLE_FLAGS: usc_idle_mode = IDLEMODE_FLAGS; break;
+ case HDLC_TXIDLE_ALT_ZEROS_ONES: usc_idle_mode = IDLEMODE_ALT_ONE_ZERO; break;
+ case HDLC_TXIDLE_ZEROS: usc_idle_mode = IDLEMODE_ZERO; break;
+ case HDLC_TXIDLE_ONES: usc_idle_mode = IDLEMODE_ONE; break;
+ case HDLC_TXIDLE_ALT_MARK_SPACE: usc_idle_mode = IDLEMODE_ALT_MARK_SPACE; break;
+ case HDLC_TXIDLE_SPACE: usc_idle_mode = IDLEMODE_SPACE; break;
+ case HDLC_TXIDLE_MARK: usc_idle_mode = IDLEMODE_MARK; break;
+ }
+
+ info->usc_idle_mode = usc_idle_mode;
+ //usc_OutReg(info, TCSR, usc_idle_mode);
+ info->tcsr_value &= ~IDLEMODE_MASK; /* clear idle mode bits */
+ info->tcsr_value += usc_idle_mode;
+ usc_OutReg(info, TCSR, info->tcsr_value);
+
+ /*
+ * if SyncLink WAN adapter is running in external sync mode, the
+ * transmitter has been set to Monosync in order to try to mimic
+ * a true raw outbound bit stream. Monosync still sends an open/close
+ * sync char at the start/end of a frame. Try to match those sync
+ * patterns to the idle mode set here
+ */
+ if ( info->params.mode == MGSL_MODE_RAW ) {
+ unsigned char syncpat = 0;
+ switch( info->idle_mode ) {
+ case HDLC_TXIDLE_FLAGS:
+ syncpat = 0x7e;
+ break;
+ case HDLC_TXIDLE_ALT_ZEROS_ONES:
+ syncpat = 0x55;
+ break;
+ case HDLC_TXIDLE_ZEROS:
+ case HDLC_TXIDLE_SPACE:
+ syncpat = 0x00;
+ break;
+ case HDLC_TXIDLE_ONES:
+ case HDLC_TXIDLE_MARK:
+ syncpat = 0xff;
+ break;
+ case HDLC_TXIDLE_ALT_MARK_SPACE:
+ syncpat = 0xaa;
+ break;
+ }
+
+ usc_SetTransmitSyncChars(info,syncpat,syncpat);
+ }
+
+} /* end of usc_set_txidle() */
+
+/* usc_get_serial_signals()
+ *
+ * Query the adapter for the state of the V24 status (input) signals.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_get_serial_signals( struct mgsl_struct *info )
+{
+ u16 status;
+
+ /* clear all serial signals except DTR and RTS */
+ info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+ /* Read the Misc Interrupt status Register (MISR) to get */
+ /* the V24 status signals. */
+
+ status = usc_InReg( info, MISR );
+
+ /* set serial signal bits to reflect MISR */
+
+ if ( status & MISCSTATUS_CTS )
+ info->serial_signals |= SerialSignal_CTS;
+
+ if ( status & MISCSTATUS_DCD )
+ info->serial_signals |= SerialSignal_DCD;
+
+ if ( status & MISCSTATUS_RI )
+ info->serial_signals |= SerialSignal_RI;
+
+ if ( status & MISCSTATUS_DSR )
+ info->serial_signals |= SerialSignal_DSR;
+
+} /* end of usc_get_serial_signals() */
+
+/* usc_set_serial_signals()
+ *
+ * Set the state of DTR and RTS based on contents of
+ * serial_signals member of device extension.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void usc_set_serial_signals( struct mgsl_struct *info )
+{
+ u16 Control;
+ unsigned char V24Out = info->serial_signals;
+
+ /* get the current value of the Port Control Register (PCR) */
+
+ Control = usc_InReg( info, PCR );
+
+ if ( V24Out & SerialSignal_RTS )
+ Control &= ~(BIT6);
+ else
+ Control |= BIT6;
+
+ if ( V24Out & SerialSignal_DTR )
+ Control &= ~(BIT4);
+ else
+ Control |= BIT4;
+
+ usc_OutReg( info, PCR, Control );
+
+} /* end of usc_set_serial_signals() */
+
+/* usc_enable_async_clock()
+ *
+ * Enable the async clock at the specified frequency.
+ *
+ * Arguments: info pointer to device instance data
+ * data_rate data rate of clock in bps
+ * 0 disables the AUX clock.
+ * Return Value: None
+ */
+static void usc_enable_async_clock( struct mgsl_struct *info, u32 data_rate )
+{
+ if ( data_rate ) {
+ /*
+ * Clock mode Control Register (CMCR)
+ *
+ * <15..14> 00 counter 1 Disabled
+ * <13..12> 00 counter 0 Disabled
+ * <11..10> 11 BRG1 Input is TxC Pin
+ * <9..8> 11 BRG0 Input is TxC Pin
+ * <7..6> 01 DPLL Input is BRG1 Output
+ * <5..3> 100 TxCLK comes from BRG0
+ * <2..0> 100 RxCLK comes from BRG0
+ *
+ * 0000 1111 0110 0100 = 0x0f64
+ */
+
+ usc_OutReg( info, CMCR, 0x0f64 );
+
+
+ /*
+ * Write 16-bit Time Constant for BRG0
+ * Time Constant = (ClkSpeed / data_rate) - 1
+ * ClkSpeed = 921600 (ISA), 691200 (PCI)
+ */
+
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ usc_OutReg( info, TC0R, (u16)((691200/data_rate) - 1) );
+ else
+ usc_OutReg( info, TC0R, (u16)((921600/data_rate) - 1) );
+
+
+ /*
+ * Hardware Configuration Register (HCR)
+ * Clear Bit 1, BRG0 mode = Continuous
+ * Set Bit 0 to enable BRG0.
+ */
+
+ usc_OutReg( info, HCR,
+ (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+
+ /* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+
+ usc_OutReg( info, IOCR,
+ (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004) );
+ } else {
+ /* data rate == 0 so turn off BRG0 */
+ usc_OutReg( info, HCR, (u16)(usc_InReg( info, HCR ) & ~BIT0) );
+ }
+
+} /* end of usc_enable_async_clock() */
+
+/*
+ * Buffer Structures:
+ *
+ * Normal memory access uses virtual addresses that can make discontiguous
+ * physical memory pages appear to be contiguous in the virtual address
+ * space (the processors memory mapping handles the conversions).
+ *
+ * DMA transfers require physically contiguous memory. This is because
+ * the DMA system controller and DMA bus masters deal with memory using
+ * only physical addresses.
+ *
+ * This causes a problem under Windows NT when large DMA buffers are
+ * needed. Fragmentation of the nonpaged pool prevents allocations of
+ * physically contiguous buffers larger than the PAGE_SIZE.
+ *
+ * However the 16C32 supports Bus Master Scatter/Gather DMA which
+ * allows DMA transfers to physically discontiguous buffers. Information
+ * about each data transfer buffer is contained in a memory structure
+ * called a 'buffer entry'. A list of buffer entries is maintained
+ * to track and control the use of the data transfer buffers.
+ *
+ * To support this strategy we will allocate sufficient PAGE_SIZE
+ * contiguous memory buffers to allow for the total required buffer
+ * space.
+ *
+ * The 16C32 accesses the list of buffer entries using Bus Master
+ * DMA. Control information is read from the buffer entries by the
+ * 16C32 to control data transfers. status information is written to
+ * the buffer entries by the 16C32 to indicate the status of completed
+ * transfers.
+ *
+ * The CPU writes control information to the buffer entries to control
+ * the 16C32 and reads status information from the buffer entries to
+ * determine information about received and transmitted frames.
+ *
+ * Because the CPU and 16C32 (adapter) both need simultaneous access
+ * to the buffer entries, the buffer entry memory is allocated with
+ * HalAllocateCommonBuffer(). This restricts the size of the buffer
+ * entry list to PAGE_SIZE.
+ *
+ * The actual data buffers on the other hand will only be accessed
+ * by the CPU or the adapter but not by both simultaneously. This allows
+ * Scatter/Gather packet based DMA procedures for using physically
+ * discontiguous pages.
+ */
+
+/*
+ * mgsl_reset_tx_dma_buffers()
+ *
+ * Set the count for all transmit buffers to 0 to indicate the
+ * buffer is available for use and set the current buffer to the
+ * first buffer. This effectively makes all buffers free and
+ * discards any data in buffers.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_reset_tx_dma_buffers( struct mgsl_struct *info )
+{
+ unsigned int i;
+
+ for ( i = 0; i < info->tx_buffer_count; i++ ) {
+ *((unsigned long *)&(info->tx_buffer_list[i].count)) = 0;
+ }
+
+ info->current_tx_buffer = 0;
+ info->start_tx_dma_buffer = 0;
+ info->tx_dma_buffers_used = 0;
+
+ info->get_tx_holding_index = 0;
+ info->put_tx_holding_index = 0;
+ info->tx_holding_count = 0;
+
+} /* end of mgsl_reset_tx_dma_buffers() */
+
+/*
+ * num_free_tx_dma_buffers()
+ *
+ * returns the number of free tx dma buffers available
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: number of free tx dma buffers
+ */
+static int num_free_tx_dma_buffers(struct mgsl_struct *info)
+{
+ return info->tx_buffer_count - info->tx_dma_buffers_used;
+}
+
+/*
+ * mgsl_reset_rx_dma_buffers()
+ *
+ * Set the count for all receive buffers to DMABUFFERSIZE
+ * and set the current buffer to the first buffer. This effectively
+ * makes all buffers free and discards any data in buffers.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_reset_rx_dma_buffers( struct mgsl_struct *info )
+{
+ unsigned int i;
+
+ for ( i = 0; i < info->rx_buffer_count; i++ ) {
+ *((unsigned long *)&(info->rx_buffer_list[i].count)) = DMABUFFERSIZE;
+// info->rx_buffer_list[i].count = DMABUFFERSIZE;
+// info->rx_buffer_list[i].status = 0;
+ }
+
+ info->current_rx_buffer = 0;
+
+} /* end of mgsl_reset_rx_dma_buffers() */
+
+/*
+ * mgsl_free_rx_frame_buffers()
+ *
+ * Free the receive buffers used by a received SDLC
+ * frame such that the buffers can be reused.
+ *
+ * Arguments:
+ *
+ * info pointer to device instance data
+ * StartIndex index of 1st receive buffer of frame
+ * EndIndex index of last receive buffer of frame
+ *
+ * Return Value: None
+ */
+static void mgsl_free_rx_frame_buffers( struct mgsl_struct *info, unsigned int StartIndex, unsigned int EndIndex )
+{
+ int Done = 0;
+ DMABUFFERENTRY *pBufEntry;
+ unsigned int Index;
+
+ /* Starting with 1st buffer entry of the frame clear the status */
+ /* field and set the count field to DMA Buffer Size. */
+
+ Index = StartIndex;
+
+ while( !Done ) {
+ pBufEntry = &(info->rx_buffer_list[Index]);
+
+ if ( Index == EndIndex ) {
+ /* This is the last buffer of the frame! */
+ Done = 1;
+ }
+
+ /* reset current buffer for reuse */
+// pBufEntry->status = 0;
+// pBufEntry->count = DMABUFFERSIZE;
+ *((unsigned long *)&(pBufEntry->count)) = DMABUFFERSIZE;
+
+ /* advance to next buffer entry in linked list */
+ Index++;
+ if ( Index == info->rx_buffer_count )
+ Index = 0;
+ }
+
+ /* set current buffer to next buffer after last buffer of frame */
+ info->current_rx_buffer = Index;
+
+} /* end of free_rx_frame_buffers() */
+
+/* mgsl_get_rx_frame()
+ *
+ * This function attempts to return a received SDLC frame from the
+ * receive DMA buffers. Only frames received without errors are returned.
+ *
+ * Arguments: info pointer to device extension
+ * Return Value: 1 if frame returned, otherwise 0
+ */
+static int mgsl_get_rx_frame(struct mgsl_struct *info)
+{
+ unsigned int StartIndex, EndIndex; /* index of 1st and last buffers of Rx frame */
+ unsigned short status;
+ DMABUFFERENTRY *pBufEntry;
+ unsigned int framesize = 0;
+ int ReturnCode = 0;
+ unsigned long flags;
+ struct tty_struct *tty = info->tty;
+ int return_frame = 0;
+
+ /*
+ * current_rx_buffer points to the 1st buffer of the next available
+ * receive frame. To find the last buffer of the frame look for
+ * a non-zero status field in the buffer entries. (The status
+ * field is set by the 16C32 after completing a receive frame.
+ */
+
+ StartIndex = EndIndex = info->current_rx_buffer;
+
+ while( !info->rx_buffer_list[EndIndex].status ) {
+ /*
+ * If the count field of the buffer entry is non-zero then
+ * this buffer has not been used. (The 16C32 clears the count
+ * field when it starts using the buffer.) If an unused buffer
+ * is encountered then there are no frames available.
+ */
+
+ if ( info->rx_buffer_list[EndIndex].count )
+ goto Cleanup;
+
+ /* advance to next buffer entry in linked list */
+ EndIndex++;
+ if ( EndIndex == info->rx_buffer_count )
+ EndIndex = 0;
+
+ /* if entire list searched then no frame available */
+ if ( EndIndex == StartIndex ) {
+ /* If this occurs then something bad happened,
+ * all buffers have been 'used' but none mark
+ * the end of a frame. Reset buffers and receiver.
+ */
+
+ if ( info->rx_enabled ){
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_start_receiver(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+ goto Cleanup;
+ }
+ }
+
+
+ /* check status of receive frame */
+
+ status = info->rx_buffer_list[EndIndex].status;
+
+ if ( status & (RXSTATUS_SHORT_FRAME + RXSTATUS_OVERRUN +
+ RXSTATUS_CRC_ERROR + RXSTATUS_ABORT) ) {
+ if ( status & RXSTATUS_SHORT_FRAME )
+ info->icount.rxshort++;
+ else if ( status & RXSTATUS_ABORT )
+ info->icount.rxabort++;
+ else if ( status & RXSTATUS_OVERRUN )
+ info->icount.rxover++;
+ else {
+ info->icount.rxcrc++;
+ if ( info->params.crc_type & HDLC_CRC_RETURN_EX )
+ return_frame = 1;
+ }
+ framesize = 0;
+#ifdef CONFIG_HDLC
+ {
+ struct net_device_stats *stats = hdlc_stats(info->netdev);
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ }
+#endif
+ } else
+ return_frame = 1;
+
+ if ( return_frame ) {
+ /* receive frame has no errors, get frame size.
+ * The frame size is the starting value of the RCC (which was
+ * set to 0xffff) minus the ending value of the RCC (decremented
+ * once for each receive character) minus 2 for the 16-bit CRC.
+ */
+
+ framesize = RCLRVALUE - info->rx_buffer_list[EndIndex].rcc;
+
+ /* adjust frame size for CRC if any */
+ if ( info->params.crc_type == HDLC_CRC_16_CCITT )
+ framesize -= 2;
+ else if ( info->params.crc_type == HDLC_CRC_32_CCITT )
+ framesize -= 4;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk("%s(%d):mgsl_get_rx_frame(%s) status=%04X size=%d\n",
+ __FILE__,__LINE__,info->device_name,status,framesize);
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ mgsl_trace_block(info,info->rx_buffer_list[StartIndex].virt_addr,
+ min_t(int, framesize, DMABUFFERSIZE),0);
+
+ if (framesize) {
+ if ( ( (info->params.crc_type & HDLC_CRC_RETURN_EX) &&
+ ((framesize+1) > info->max_frame_size) ) ||
+ (framesize > info->max_frame_size) )
+ info->icount.rxlong++;
+ else {
+ /* copy dma buffer(s) to contiguous intermediate buffer */
+ int copy_count = framesize;
+ int index = StartIndex;
+ unsigned char *ptmp = info->intermediate_rxbuffer;
+
+ if ( !(status & RXSTATUS_CRC_ERROR))
+ info->icount.rxok++;
+
+ while(copy_count) {
+ int partial_count;
+ if ( copy_count > DMABUFFERSIZE )
+ partial_count = DMABUFFERSIZE;
+ else
+ partial_count = copy_count;
+
+ pBufEntry = &(info->rx_buffer_list[index]);
+ memcpy( ptmp, pBufEntry->virt_addr, partial_count );
+ ptmp += partial_count;
+ copy_count -= partial_count;
+
+ if ( ++index == info->rx_buffer_count )
+ index = 0;
+ }
+
+ if ( info->params.crc_type & HDLC_CRC_RETURN_EX ) {
+ ++framesize;
+ *ptmp = (status & RXSTATUS_CRC_ERROR ?
+ RX_CRC_ERROR :
+ RX_OK);
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ printk("%s(%d):mgsl_get_rx_frame(%s) rx frame status=%d\n",
+ __FILE__,__LINE__,info->device_name,
+ *ptmp);
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_rx(info,info->intermediate_rxbuffer,framesize);
+ else
+#endif
+ ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
+ }
+ }
+ /* Free the buffers used by this frame. */
+ mgsl_free_rx_frame_buffers( info, StartIndex, EndIndex );
+
+ ReturnCode = 1;
+
+Cleanup:
+
+ if ( info->rx_enabled && info->rx_overflow ) {
+ /* The receiver needs to restarted because of
+ * a receive overflow (buffer or FIFO). If the
+ * receive buffers are now empty, then restart receiver.
+ */
+
+ if ( !info->rx_buffer_list[EndIndex].status &&
+ info->rx_buffer_list[EndIndex].count ) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_start_receiver(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+ }
+
+ return ReturnCode;
+
+} /* end of mgsl_get_rx_frame() */
+
+/* mgsl_get_raw_rx_frame()
+ *
+ * This function attempts to return a received frame from the
+ * receive DMA buffers when running in external loop mode. In this mode,
+ * we will return at most one DMABUFFERSIZE frame to the application.
+ * The USC receiver is triggering off of DCD going active to start a new
+ * frame, and DCD going inactive to terminate the frame (similar to
+ * processing a closing flag character).
+ *
+ * In this routine, we will return DMABUFFERSIZE "chunks" at a time.
+ * If DCD goes inactive, the last Rx DMA Buffer will have a non-zero
+ * status field and the RCC field will indicate the length of the
+ * entire received frame. We take this RCC field and get the modulus
+ * of RCC and DMABUFFERSIZE to determine if number of bytes in the
+ * last Rx DMA buffer and return that last portion of the frame.
+ *
+ * Arguments: info pointer to device extension
+ * Return Value: 1 if frame returned, otherwise 0
+ */
+static int mgsl_get_raw_rx_frame(struct mgsl_struct *info)
+{
+ unsigned int CurrentIndex, NextIndex;
+ unsigned short status;
+ DMABUFFERENTRY *pBufEntry;
+ unsigned int framesize = 0;
+ int ReturnCode = 0;
+ unsigned long flags;
+ struct tty_struct *tty = info->tty;
+
+ /*
+ * current_rx_buffer points to the 1st buffer of the next available
+ * receive frame. The status field is set by the 16C32 after
+ * completing a receive frame. If the status field of this buffer
+ * is zero, either the USC is still filling this buffer or this
+ * is one of a series of buffers making up a received frame.
+ *
+ * If the count field of this buffer is zero, the USC is either
+ * using this buffer or has used this buffer. Look at the count
+ * field of the next buffer. If that next buffer's count is
+ * non-zero, the USC is still actively using the current buffer.
+ * Otherwise, if the next buffer's count field is zero, the
+ * current buffer is complete and the USC is using the next
+ * buffer.
+ */
+ CurrentIndex = NextIndex = info->current_rx_buffer;
+ ++NextIndex;
+ if ( NextIndex == info->rx_buffer_count )
+ NextIndex = 0;
+
+ if ( info->rx_buffer_list[CurrentIndex].status != 0 ||
+ (info->rx_buffer_list[CurrentIndex].count == 0 &&
+ info->rx_buffer_list[NextIndex].count == 0)) {
+ /*
+ * Either the status field of this dma buffer is non-zero
+ * (indicating the last buffer of a receive frame) or the next
+ * buffer is marked as in use -- implying this buffer is complete
+ * and an intermediate buffer for this received frame.
+ */
+
+ status = info->rx_buffer_list[CurrentIndex].status;
+
+ if ( status & (RXSTATUS_SHORT_FRAME + RXSTATUS_OVERRUN +
+ RXSTATUS_CRC_ERROR + RXSTATUS_ABORT) ) {
+ if ( status & RXSTATUS_SHORT_FRAME )
+ info->icount.rxshort++;
+ else if ( status & RXSTATUS_ABORT )
+ info->icount.rxabort++;
+ else if ( status & RXSTATUS_OVERRUN )
+ info->icount.rxover++;
+ else
+ info->icount.rxcrc++;
+ framesize = 0;
+ } else {
+ /*
+ * A receive frame is available, get frame size and status.
+ *
+ * The frame size is the starting value of the RCC (which was
+ * set to 0xffff) minus the ending value of the RCC (decremented
+ * once for each receive character) minus 2 or 4 for the 16-bit
+ * or 32-bit CRC.
+ *
+ * If the status field is zero, this is an intermediate buffer.
+ * It's size is 4K.
+ *
+ * If the DMA Buffer Entry's Status field is non-zero, the
+ * receive operation completed normally (ie: DCD dropped). The
+ * RCC field is valid and holds the received frame size.
+ * It is possible that the RCC field will be zero on a DMA buffer
+ * entry with a non-zero status. This can occur if the total
+ * frame size (number of bytes between the time DCD goes active
+ * to the time DCD goes inactive) exceeds 65535 bytes. In this
+ * case the 16C32 has underrun on the RCC count and appears to
+ * stop updating this counter to let us know the actual received
+ * frame size. If this happens (non-zero status and zero RCC),
+ * simply return the entire RxDMA Buffer
+ */
+ if ( status ) {
+ /*
+ * In the event that the final RxDMA Buffer is
+ * terminated with a non-zero status and the RCC
+ * field is zero, we interpret this as the RCC
+ * having underflowed (received frame > 65535 bytes).
+ *
+ * Signal the event to the user by passing back
+ * a status of RxStatus_CrcError returning the full
+ * buffer and let the app figure out what data is
+ * actually valid
+ */
+ if ( info->rx_buffer_list[CurrentIndex].rcc )
+ framesize = RCLRVALUE - info->rx_buffer_list[CurrentIndex].rcc;
+ else
+ framesize = DMABUFFERSIZE;
+ }
+ else
+ framesize = DMABUFFERSIZE;
+ }
+
+ if ( framesize > DMABUFFERSIZE ) {
+ /*
+ * if running in raw sync mode, ISR handler for
+ * End Of Buffer events terminates all buffers at 4K.
+ * If this frame size is said to be >4K, get the
+ * actual number of bytes of the frame in this buffer.
+ */
+ framesize = framesize % DMABUFFERSIZE;
+ }
+
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk("%s(%d):mgsl_get_raw_rx_frame(%s) status=%04X size=%d\n",
+ __FILE__,__LINE__,info->device_name,status,framesize);
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ mgsl_trace_block(info,info->rx_buffer_list[CurrentIndex].virt_addr,
+ min_t(int, framesize, DMABUFFERSIZE),0);
+
+ if (framesize) {
+ /* copy dma buffer(s) to contiguous intermediate buffer */
+ /* NOTE: we never copy more than DMABUFFERSIZE bytes */
+
+ pBufEntry = &(info->rx_buffer_list[CurrentIndex]);
+ memcpy( info->intermediate_rxbuffer, pBufEntry->virt_addr, framesize);
+ info->icount.rxok++;
+
+ ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
+ }
+
+ /* Free the buffers used by this frame. */
+ mgsl_free_rx_frame_buffers( info, CurrentIndex, CurrentIndex );
+
+ ReturnCode = 1;
+ }
+
+
+ if ( info->rx_enabled && info->rx_overflow ) {
+ /* The receiver needs to restarted because of
+ * a receive overflow (buffer or FIFO). If the
+ * receive buffers are now empty, then restart receiver.
+ */
+
+ if ( !info->rx_buffer_list[CurrentIndex].status &&
+ info->rx_buffer_list[CurrentIndex].count ) {
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_start_receiver(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+ }
+
+ return ReturnCode;
+
+} /* end of mgsl_get_raw_rx_frame() */
+
+/* mgsl_load_tx_dma_buffer()
+ *
+ * Load the transmit DMA buffer with the specified data.
+ *
+ * Arguments:
+ *
+ * info pointer to device extension
+ * Buffer pointer to buffer containing frame to load
+ * BufferSize size in bytes of frame in Buffer
+ *
+ * Return Value: None
+ */
+static void mgsl_load_tx_dma_buffer(struct mgsl_struct *info,
+ const char *Buffer, unsigned int BufferSize)
+{
+ unsigned short Copycount;
+ unsigned int i = 0;
+ DMABUFFERENTRY *pBufEntry;
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ mgsl_trace_block(info,Buffer, min_t(int, BufferSize, DMABUFFERSIZE), 1);
+
+ if (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) {
+ /* set CMR:13 to start transmit when
+ * next GoAhead (abort) is received
+ */
+ info->cmr_value |= BIT13;
+ }
+
+ /* begin loading the frame in the next available tx dma
+ * buffer, remember it's starting location for setting
+ * up tx dma operation
+ */
+ i = info->current_tx_buffer;
+ info->start_tx_dma_buffer = i;
+
+ /* Setup the status and RCC (Frame Size) fields of the 1st */
+ /* buffer entry in the transmit DMA buffer list. */
+
+ info->tx_buffer_list[i].status = info->cmr_value & 0xf000;
+ info->tx_buffer_list[i].rcc = BufferSize;
+ info->tx_buffer_list[i].count = BufferSize;
+
+ /* Copy frame data from 1st source buffer to the DMA buffers. */
+ /* The frame data may span multiple DMA buffers. */
+
+ while( BufferSize ){
+ /* Get a pointer to next DMA buffer entry. */
+ pBufEntry = &info->tx_buffer_list[i++];
+
+ if ( i == info->tx_buffer_count )
+ i=0;
+
+ /* Calculate the number of bytes that can be copied from */
+ /* the source buffer to this DMA buffer. */
+ if ( BufferSize > DMABUFFERSIZE )
+ Copycount = DMABUFFERSIZE;
+ else
+ Copycount = BufferSize;
+
+ /* Actually copy data from source buffer to DMA buffer. */
+ /* Also set the data count for this individual DMA buffer. */
+ if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+ mgsl_load_pci_memory(pBufEntry->virt_addr, Buffer,Copycount);
+ else
+ memcpy(pBufEntry->virt_addr, Buffer, Copycount);
+
+ pBufEntry->count = Copycount;
+
+ /* Advance source pointer and reduce remaining data count. */
+ Buffer += Copycount;
+ BufferSize -= Copycount;
+
+ ++info->tx_dma_buffers_used;
+ }
+
+ /* remember next available tx dma buffer */
+ info->current_tx_buffer = i;
+
+} /* end of mgsl_load_tx_dma_buffer() */
+
+/*
+ * mgsl_register_test()
+ *
+ * Performs a register test of the 16C32.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_register_test( struct mgsl_struct *info )
+{
+ static unsigned short BitPatterns[] =
+ { 0x0000, 0xffff, 0xaaaa, 0x5555, 0x1234, 0x6969, 0x9696, 0x0f0f };
+ static unsigned int Patterncount = sizeof(BitPatterns)/sizeof(unsigned short);
+ unsigned int i;
+ BOOLEAN rc = TRUE;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_reset(info);
+
+ /* Verify the reset state of some registers. */
+
+ if ( (usc_InReg( info, SICR ) != 0) ||
+ (usc_InReg( info, IVR ) != 0) ||
+ (usc_InDmaReg( info, DIVR ) != 0) ){
+ rc = FALSE;
+ }
+
+ if ( rc == TRUE ){
+ /* Write bit patterns to various registers but do it out of */
+ /* sync, then read back and verify values. */
+
+ for ( i = 0 ; i < Patterncount ; i++ ) {
+ usc_OutReg( info, TC0R, BitPatterns[i] );
+ usc_OutReg( info, TC1R, BitPatterns[(i+1)%Patterncount] );
+ usc_OutReg( info, TCLR, BitPatterns[(i+2)%Patterncount] );
+ usc_OutReg( info, RCLR, BitPatterns[(i+3)%Patterncount] );
+ usc_OutReg( info, RSR, BitPatterns[(i+4)%Patterncount] );
+ usc_OutDmaReg( info, TBCR, BitPatterns[(i+5)%Patterncount] );
+
+ if ( (usc_InReg( info, TC0R ) != BitPatterns[i]) ||
+ (usc_InReg( info, TC1R ) != BitPatterns[(i+1)%Patterncount]) ||
+ (usc_InReg( info, TCLR ) != BitPatterns[(i+2)%Patterncount]) ||
+ (usc_InReg( info, RCLR ) != BitPatterns[(i+3)%Patterncount]) ||
+ (usc_InReg( info, RSR ) != BitPatterns[(i+4)%Patterncount]) ||
+ (usc_InDmaReg( info, TBCR ) != BitPatterns[(i+5)%Patterncount]) ){
+ rc = FALSE;
+ break;
+ }
+ }
+ }
+
+ usc_reset(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return rc;
+
+} /* end of mgsl_register_test() */
+
+/* mgsl_irq_test() Perform interrupt test of the 16C32.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_irq_test( struct mgsl_struct *info )
+{
+ unsigned long EndTime;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_reset(info);
+
+ /*
+ * Setup 16C32 to interrupt on TxC pin (14MHz clock) transition.
+ * The ISR sets irq_occurred to 1.
+ */
+
+ info->irq_occurred = FALSE;
+
+ /* Enable INTEN gate for ISA adapter (Port 6, Bit12) */
+ /* Enable INTEN (Port 6, Bit12) */
+ /* This connects the IRQ request signal to the ISA bus */
+ /* on the ISA adapter. This has no effect for the PCI adapter */
+ usc_OutReg( info, PCR, (unsigned short)((usc_InReg(info, PCR) | BIT13) & ~BIT12) );
+
+ usc_EnableMasterIrqBit(info);
+ usc_EnableInterrupts(info, IO_PIN);
+ usc_ClearIrqPendingBits(info, IO_PIN);
+
+ usc_UnlatchIostatusBits(info, MISCSTATUS_TXC_LATCHED);
+ usc_EnableStatusIrqs(info, SICR_TXC_ACTIVE + SICR_TXC_INACTIVE);
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ EndTime=100;
+ while( EndTime-- && !info->irq_occurred ) {
+ msleep_interruptible(10);
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_reset(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ if ( !info->irq_occurred )
+ return FALSE;
+ else
+ return TRUE;
+
+} /* end of mgsl_irq_test() */
+
+/* mgsl_dma_test()
+ *
+ * Perform a DMA test of the 16C32. A small frame is
+ * transmitted via DMA from a transmit buffer to a receive buffer
+ * using single buffer DMA mode.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_dma_test( struct mgsl_struct *info )
+{
+ unsigned short FifoLevel;
+ unsigned long phys_addr;
+ unsigned int FrameSize;
+ unsigned int i;
+ char *TmpPtr;
+ BOOLEAN rc = TRUE;
+ unsigned short status=0;
+ unsigned long EndTime;
+ unsigned long flags;
+ MGSL_PARAMS tmp_params;
+
+ /* save current port options */
+ memcpy(&tmp_params,&info->params,sizeof(MGSL_PARAMS));
+ /* load default port options */
+ memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+
+#define TESTFRAMESIZE 40
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ /* setup 16C32 for SDLC DMA transfer mode */
+
+ usc_reset(info);
+ usc_set_sdlc_mode(info);
+ usc_enable_loopback(info,1);
+
+ /* Reprogram the RDMR so that the 16C32 does NOT clear the count
+ * field of the buffer entry after fetching buffer address. This
+ * way we can detect a DMA failure for a DMA read (which should be
+ * non-destructive to system memory) before we try and write to
+ * memory (where a failure could corrupt system memory).
+ */
+
+ /* Receive DMA mode Register (RDMR)
+ *
+ * <15..14> 11 DMA mode = Linked List Buffer mode
+ * <13> 1 RSBinA/L = store Rx status Block in List entry
+ * <12> 0 1 = Clear count of List Entry after fetching
+ * <11..10> 00 Address mode = Increment
+ * <9> 1 Terminate Buffer on RxBound
+ * <8> 0 Bus Width = 16bits
+ * <7..0> ? status Bits (write as 0s)
+ *
+ * 1110 0010 0000 0000 = 0xe200
+ */
+
+ usc_OutDmaReg( info, RDMR, 0xe200 );
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+ /* SETUP TRANSMIT AND RECEIVE DMA BUFFERS */
+
+ FrameSize = TESTFRAMESIZE;
+
+ /* setup 1st transmit buffer entry: */
+ /* with frame size and transmit control word */
+
+ info->tx_buffer_list[0].count = FrameSize;
+ info->tx_buffer_list[0].rcc = FrameSize;
+ info->tx_buffer_list[0].status = 0x4000;
+
+ /* build a transmit frame in 1st transmit DMA buffer */
+
+ TmpPtr = info->tx_buffer_list[0].virt_addr;
+ for (i = 0; i < FrameSize; i++ )
+ *TmpPtr++ = i;
+
+ /* setup 1st receive buffer entry: */
+ /* clear status, set max receive buffer size */
+
+ info->rx_buffer_list[0].status = 0;
+ info->rx_buffer_list[0].count = FrameSize + 4;
+
+ /* zero out the 1st receive buffer */
+
+ memset( info->rx_buffer_list[0].virt_addr, 0, FrameSize + 4 );
+
+ /* Set count field of next buffer entries to prevent */
+ /* 16C32 from using buffers after the 1st one. */
+
+ info->tx_buffer_list[1].count = 0;
+ info->rx_buffer_list[1].count = 0;
+
+
+ /***************************/
+ /* Program 16C32 receiver. */
+ /***************************/
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ /* setup DMA transfers */
+ usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+ /* program 16C32 receiver with physical address of 1st DMA buffer entry */
+ phys_addr = info->rx_buffer_list[0].phys_entry;
+ usc_OutDmaReg( info, NRARL, (unsigned short)phys_addr );
+ usc_OutDmaReg( info, NRARU, (unsigned short)(phys_addr >> 16) );
+
+ /* Clear the Rx DMA status bits (read RDMR) and start channel */
+ usc_InDmaReg( info, RDMR );
+ usc_DmaCmd( info, DmaCmd_InitRxChannel );
+
+ /* Enable Receiver (RMR <1..0> = 10) */
+ usc_OutReg( info, RMR, (unsigned short)((usc_InReg(info, RMR) & 0xfffc) | 0x0002) );
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+ /*************************************************************/
+ /* WAIT FOR RECEIVER TO DMA ALL PARAMETERS FROM BUFFER ENTRY */
+ /*************************************************************/
+
+ /* Wait 100ms for interrupt. */
+ EndTime = jiffies + msecs_to_jiffies(100);
+
+ for(;;) {
+ if (time_after(jiffies, EndTime)) {
+ rc = FALSE;
+ break;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ status = usc_InDmaReg( info, RDMR );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ if ( !(status & BIT4) && (status & BIT5) ) {
+ /* INITG (BIT 4) is inactive (no entry read in progress) AND */
+ /* BUSY (BIT 5) is active (channel still active). */
+ /* This means the buffer entry read has completed. */
+ break;
+ }
+ }
+
+
+ /******************************/
+ /* Program 16C32 transmitter. */
+ /******************************/
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ /* Program the Transmit Character Length Register (TCLR) */
+ /* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+
+ usc_OutReg( info, TCLR, (unsigned short)info->tx_buffer_list[0].count );
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+ /* Program the address of the 1st DMA Buffer Entry in linked list */
+
+ phys_addr = info->tx_buffer_list[0].phys_entry;
+ usc_OutDmaReg( info, NTARL, (unsigned short)phys_addr );
+ usc_OutDmaReg( info, NTARU, (unsigned short)(phys_addr >> 16) );
+
+ /* unlatch Tx status bits, and start transmit channel. */
+
+ usc_OutReg( info, TCSR, (unsigned short)(( usc_InReg(info, TCSR) & 0x0f00) | 0xfa) );
+ usc_DmaCmd( info, DmaCmd_InitTxChannel );
+
+ /* wait for DMA controller to fill transmit FIFO */
+
+ usc_TCmd( info, TCmd_SelectTicrTxFifostatus );
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+ /**********************************/
+ /* WAIT FOR TRANSMIT FIFO TO FILL */
+ /**********************************/
+
+ /* Wait 100ms */
+ EndTime = jiffies + msecs_to_jiffies(100);
+
+ for(;;) {
+ if (time_after(jiffies, EndTime)) {
+ rc = FALSE;
+ break;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ FifoLevel = usc_InReg(info, TICR) >> 8;
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ if ( FifoLevel < 16 )
+ break;
+ else
+ if ( FrameSize < 32 ) {
+ /* This frame is smaller than the entire transmit FIFO */
+ /* so wait for the entire frame to be loaded. */
+ if ( FifoLevel <= (32 - FrameSize) )
+ break;
+ }
+ }
+
+
+ if ( rc == TRUE )
+ {
+ /* Enable 16C32 transmitter. */
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+
+ /* Transmit mode Register (TMR), <1..0> = 10, Enable Transmitter */
+ usc_TCmd( info, TCmd_SendFrame );
+ usc_OutReg( info, TMR, (unsigned short)((usc_InReg(info, TMR) & 0xfffc) | 0x0002) );
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+ /******************************/
+ /* WAIT FOR TRANSMIT COMPLETE */
+ /******************************/
+
+ /* Wait 100ms */
+ EndTime = jiffies + msecs_to_jiffies(100);
+
+ /* While timer not expired wait for transmit complete */
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ status = usc_InReg( info, TCSR );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ while ( !(status & (BIT6+BIT5+BIT4+BIT2+BIT1)) ) {
+ if (time_after(jiffies, EndTime)) {
+ rc = FALSE;
+ break;
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ status = usc_InReg( info, TCSR );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ }
+ }
+
+
+ if ( rc == TRUE ){
+ /* CHECK FOR TRANSMIT ERRORS */
+ if ( status & (BIT5 + BIT1) )
+ rc = FALSE;
+ }
+
+ if ( rc == TRUE ) {
+ /* WAIT FOR RECEIVE COMPLETE */
+
+ /* Wait 100ms */
+ EndTime = jiffies + msecs_to_jiffies(100);
+
+ /* Wait for 16C32 to write receive status to buffer entry. */
+ status=info->rx_buffer_list[0].status;
+ while ( status == 0 ) {
+ if (time_after(jiffies, EndTime)) {
+ rc = FALSE;
+ break;
+ }
+ status=info->rx_buffer_list[0].status;
+ }
+ }
+
+
+ if ( rc == TRUE ) {
+ /* CHECK FOR RECEIVE ERRORS */
+ status = info->rx_buffer_list[0].status;
+
+ if ( status & (BIT8 + BIT3 + BIT1) ) {
+ /* receive error has occurred */
+ rc = FALSE;
+ } else {
+ if ( memcmp( info->tx_buffer_list[0].virt_addr ,
+ info->rx_buffer_list[0].virt_addr, FrameSize ) ){
+ rc = FALSE;
+ }
+ }
+ }
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_reset( info );
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ /* restore current port options */
+ memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+
+ return rc;
+
+} /* end of mgsl_dma_test() */
+
+/* mgsl_adapter_test()
+ *
+ * Perform the register, IRQ, and DMA tests for the 16C32.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: 0 if success, otherwise -ENODEV
+ */
+static int mgsl_adapter_test( struct mgsl_struct *info )
+{
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):Testing device %s\n",
+ __FILE__,__LINE__,info->device_name );
+
+ if ( !mgsl_register_test( info ) ) {
+ info->init_error = DiagStatus_AddressFailure;
+ printk( "%s(%d):Register test failure for device %s Addr=%04X\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->io_base) );
+ return -ENODEV;
+ }
+
+ if ( !mgsl_irq_test( info ) ) {
+ info->init_error = DiagStatus_IrqFailure;
+ printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+ return -ENODEV;
+ }
+
+ if ( !mgsl_dma_test( info ) ) {
+ info->init_error = DiagStatus_DmaFailure;
+ printk( "%s(%d):DMA test failure for device %s DMA=%d\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->dma_level) );
+ return -ENODEV;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):device %s passed diagnostics\n",
+ __FILE__,__LINE__,info->device_name );
+
+ return 0;
+
+} /* end of mgsl_adapter_test() */
+
+/* mgsl_memory_test()
+ *
+ * Test the shared memory on a PCI adapter.
+ *
+ * Arguments: info pointer to device instance data
+ * Return Value: TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_memory_test( struct mgsl_struct *info )
+{
+ static unsigned long BitPatterns[] = { 0x0, 0x55555555, 0xaaaaaaaa,
+ 0x66666666, 0x99999999, 0xffffffff, 0x12345678 };
+ unsigned long Patterncount = sizeof(BitPatterns)/sizeof(unsigned long);
+ unsigned long i;
+ unsigned long TestLimit = SHARED_MEM_ADDRESS_SIZE/sizeof(unsigned long);
+ unsigned long * TestAddr;
+
+ if ( info->bus_type != MGSL_BUS_TYPE_PCI )
+ return TRUE;
+
+ TestAddr = (unsigned long *)info->memory_base;
+
+ /* Test data lines with test pattern at one location. */
+
+ for ( i = 0 ; i < Patterncount ; i++ ) {
+ *TestAddr = BitPatterns[i];
+ if ( *TestAddr != BitPatterns[i] )
+ return FALSE;
+ }
+
+ /* Test address lines with incrementing pattern over */
+ /* entire address range. */
+
+ for ( i = 0 ; i < TestLimit ; i++ ) {
+ *TestAddr = i * 4;
+ TestAddr++;
+ }
+
+ TestAddr = (unsigned long *)info->memory_base;
+
+ for ( i = 0 ; i < TestLimit ; i++ ) {
+ if ( *TestAddr != i * 4 )
+ return FALSE;
+ TestAddr++;
+ }
+
+ memset( info->memory_base, 0, SHARED_MEM_ADDRESS_SIZE );
+
+ return TRUE;
+
+} /* End Of mgsl_memory_test() */
+
+
+/* mgsl_load_pci_memory()
+ *
+ * Load a large block of data into the PCI shared memory.
+ * Use this instead of memcpy() or memmove() to move data
+ * into the PCI shared memory.
+ *
+ * Notes:
+ *
+ * This function prevents the PCI9050 interface chip from hogging
+ * the adapter local bus, which can starve the 16C32 by preventing
+ * 16C32 bus master cycles.
+ *
+ * The PCI9050 documentation says that the 9050 will always release
+ * control of the local bus after completing the current read
+ * or write operation.
+ *
+ * It appears that as long as the PCI9050 write FIFO is full, the
+ * PCI9050 treats all of the writes as a single burst transaction
+ * and will not release the bus. This causes DMA latency problems
+ * at high speeds when copying large data blocks to the shared
+ * memory.
+ *
+ * This function in effect, breaks the a large shared memory write
+ * into multiple transations by interleaving a shared memory read
+ * which will flush the write FIFO and 'complete' the write
+ * transation. This allows any pending DMA request to gain control
+ * of the local bus in a timely fasion.
+ *
+ * Arguments:
+ *
+ * TargetPtr pointer to target address in PCI shared memory
+ * SourcePtr pointer to source buffer for data
+ * count count in bytes of data to copy
+ *
+ * Return Value: None
+ */
+static void mgsl_load_pci_memory( char* TargetPtr, const char* SourcePtr,
+ unsigned short count )
+{
+ /* 16 32-bit writes @ 60ns each = 960ns max latency on local bus */
+#define PCI_LOAD_INTERVAL 64
+
+ unsigned short Intervalcount = count / PCI_LOAD_INTERVAL;
+ unsigned short Index;
+ unsigned long Dummy;
+
+ for ( Index = 0 ; Index < Intervalcount ; Index++ )
+ {
+ memcpy(TargetPtr, SourcePtr, PCI_LOAD_INTERVAL);
+ Dummy = *((volatile unsigned long *)TargetPtr);
+ TargetPtr += PCI_LOAD_INTERVAL;
+ SourcePtr += PCI_LOAD_INTERVAL;
+ }
+
+ memcpy( TargetPtr, SourcePtr, count % PCI_LOAD_INTERVAL );
+
+} /* End Of mgsl_load_pci_memory() */
+
+static void mgsl_trace_block(struct mgsl_struct *info,const char* data, int count, int xmit)
+{
+ int i;
+ int linecount;
+ if (xmit)
+ printk("%s tx data:\n",info->device_name);
+ else
+ printk("%s rx data:\n",info->device_name);
+
+ while(count) {
+ if (count > 16)
+ linecount = 16;
+ else
+ linecount = count;
+
+ for(i=0;i<linecount;i++)
+ printk("%02X ",(unsigned char)data[i]);
+ for(;i<17;i++)
+ printk(" ");
+ for(i=0;i<linecount;i++) {
+ if (data[i]>=040 && data[i]<=0176)
+ printk("%c",data[i]);
+ else
+ printk(".");
+ }
+ printk("\n");
+
+ data += linecount;
+ count -= linecount;
+ }
+} /* end of mgsl_trace_block() */
+
+/* mgsl_tx_timeout()
+ *
+ * called when HDLC frame times out
+ * update stats and do tx completion processing
+ *
+ * Arguments: context pointer to device instance data
+ * Return Value: None
+ */
+static void mgsl_tx_timeout(unsigned long context)
+{
+ struct mgsl_struct *info = (struct mgsl_struct*)context;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):mgsl_tx_timeout(%s)\n",
+ __FILE__,__LINE__,info->device_name);
+ if(info->tx_active &&
+ (info->params.mode == MGSL_MODE_HDLC ||
+ info->params.mode == MGSL_MODE_RAW) ) {
+ info->icount.txtimeout++;
+ }
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ info->tx_active = 0;
+ info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+ if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+ usc_loopmode_cancel_transmit( info );
+
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ mgsl_bh_transmit(info);
+
+} /* end of mgsl_tx_timeout() */
+
+/* signal that there are no more frames to send, so that
+ * line is 'released' by echoing RxD to TxD when current
+ * transmission is complete (or immediately if no tx in progress).
+ */
+static int mgsl_loopmode_send_done( struct mgsl_struct * info )
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) {
+ if (info->tx_active)
+ info->loopmode_send_done_requested = TRUE;
+ else
+ usc_loopmode_send_done(info);
+ }
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return 0;
+}
+
+/* release the line by echoing RxD to TxD
+ * upon completion of a transmit frame
+ */
+static void usc_loopmode_send_done( struct mgsl_struct * info )
+{
+ info->loopmode_send_done_requested = FALSE;
+ /* clear CMR:13 to 0 to start echoing RxData to TxData */
+ info->cmr_value &= ~BIT13;
+ usc_OutReg(info, CMR, info->cmr_value);
+}
+
+/* abort a transmit in progress while in HDLC LoopMode
+ */
+static void usc_loopmode_cancel_transmit( struct mgsl_struct * info )
+{
+ /* reset tx dma channel and purge TxFifo */
+ usc_RTCmd( info, RTCmd_PurgeTxFifo );
+ usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+ usc_loopmode_send_done( info );
+}
+
+/* for HDLC/SDLC LoopMode, setting CMR:13 after the transmitter is enabled
+ * is an Insert Into Loop action. Upon receipt of a GoAhead sequence (RxAbort)
+ * we must clear CMR:13 to begin repeating TxData to RxData
+ */
+static void usc_loopmode_insert_request( struct mgsl_struct * info )
+{
+ info->loopmode_insert_requested = TRUE;
+
+ /* enable RxAbort irq. On next RxAbort, clear CMR:13 to
+ * begin repeating TxData on RxData (complete insertion)
+ */
+ usc_OutReg( info, RICR,
+ (usc_InReg( info, RICR ) | RXSTATUS_ABORT_RECEIVED ) );
+
+ /* set CMR:13 to insert into loop on next GoAhead (RxAbort) */
+ info->cmr_value |= BIT13;
+ usc_OutReg(info, CMR, info->cmr_value);
+}
+
+/* return 1 if station is inserted into the loop, otherwise 0
+ */
+static int usc_loopmode_active( struct mgsl_struct * info)
+{
+ return usc_InReg( info, CCSR ) & BIT7 ? 1 : 0 ;
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev pointer to network device structure
+ * encoding serial encoding setting
+ * parity FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ struct mgsl_struct *info = dev_to_port(dev);
+ unsigned char new_encoding;
+ unsigned short new_crctype;
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ switch (encoding)
+ {
+ case ENCODING_NRZ: new_encoding = HDLC_ENCODING_NRZ; break;
+ case ENCODING_NRZI: new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+ case ENCODING_FM_MARK: new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+ case ENCODING_FM_SPACE: new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+ case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+ default: return -EINVAL;
+ }
+
+ switch (parity)
+ {
+ case PARITY_NONE: new_crctype = HDLC_CRC_NONE; break;
+ case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+ case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+ default: return -EINVAL;
+ }
+
+ info->params.encoding = new_encoding;
+ info->params.crc_type = new_crctype;;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ mgsl_program_hw(info);
+
+ return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb socket buffer containing HDLC frame
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct mgsl_struct *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+ /* stop sending until this frame completes */
+ netif_stop_queue(dev);
+
+ /* copy data to device buffers */
+ info->xmit_cnt = skb->len;
+ mgsl_load_tx_dma_buffer(info, skb->data, skb->len);
+
+ /* update network statistics */
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+
+ /* done with socket buffer, so free it */
+ dev_kfree_skb(skb);
+
+ /* save start time for transmit timeout detection */
+ dev->trans_start = jiffies;
+
+ /* start hardware transmitter if necessary */
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ if (!info->tx_active)
+ usc_start_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+ struct mgsl_struct *info = dev_to_port(dev);
+ int rc;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+ /* generic HDLC layer open processing */
+ if ((rc = hdlc_open(dev)))
+ return rc;
+
+ /* arbitrate between network and tty opens */
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->count != 0 || info->netcount != 0) {
+ printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return -EBUSY;
+ }
+ info->netcount=1;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ /* claim resources and init adapter */
+ if ((rc = startup(info)) != 0) {
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return rc;
+ }
+
+ /* assert DTR and RTS, apply hardware settings */
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ mgsl_program_hw(info);
+
+ /* enable network layer transmit */
+ dev->trans_start = jiffies;
+ netif_start_queue(dev);
+
+ /* inform generic HDLC layer of current DCD status */
+ spin_lock_irqsave(&info->irq_spinlock, flags);
+ usc_get_serial_signals(info);
+ spin_unlock_irqrestore(&info->irq_spinlock, flags);
+ hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+ struct mgsl_struct *info = dev_to_port(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+ netif_stop_queue(dev);
+
+ /* shutdown adapter and release resources */
+ shutdown(info);
+
+ hdlc_close(dev);
+
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev pointer to network device structure
+ * ifr pointer to network interface request structure
+ * cmd IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ struct mgsl_struct *info = dev_to_port(dev);
+ unsigned int flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE: /* return current sync_serial_settings */
+
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+
+ flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+
+ switch (flags){
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+ case (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_INT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_TXINT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+ default: new_line.clock_type = CLOCK_DEFAULT;
+ }
+
+ new_line.clock_rate = info->params.clock_speed;
+ new_line.loopback = info->params.loopback ? 1:0;
+
+ if (copy_to_user(line, &new_line, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ switch (new_line.clock_type)
+ {
+ case CLOCK_EXT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+ case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+ case CLOCK_INT: flags = HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_TXINT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_DEFAULT: flags = info->params.flags &
+ (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN); break;
+ default: return -EINVAL;
+ }
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+ info->params.flags |= flags;
+
+ info->params.loopback = new_line.loopback;
+
+ if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+ info->params.clock_speed = new_line.clock_rate;
+ else
+ info->params.clock_speed = 0;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ mgsl_program_hw(info);
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+ struct mgsl_struct *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+
+ spin_lock_irqsave(&info->irq_spinlock,flags);
+ usc_stop_transmitter(info);
+ spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+ netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_tx_done(struct mgsl_struct *info)
+{
+ if (netif_queue_stopped(info->netdev))
+ netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info pointer to device instance information
+ * buf pointer to buffer contianing frame data
+ * size count of data bytes in buf
+ */
+static void hdlcdev_rx(struct mgsl_struct *info, char *buf, int size)
+{
+ struct sk_buff *skb = dev_alloc_skb(size);
+ struct net_device *dev = info->netdev;
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_rx(%s)\n",dev->name);
+
+ if (skb == NULL) {
+ printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+ stats->rx_dropped++;
+ return;
+ }
+
+ memcpy(skb_put(skb, size),buf,size);
+
+ skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+ stats->rx_packets++;
+ stats->rx_bytes += size;
+
+ netif_rx(skb);
+
+ info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(struct mgsl_struct *info)
+{
+ int rc;
+ struct net_device *dev;
+ hdlc_device *hdlc;
+
+ /* allocate and initialize network and HDLC layer objects */
+
+ if (!(dev = alloc_hdlcdev(info))) {
+ printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+ return -ENOMEM;
+ }
+
+ /* for network layer reporting purposes only */
+ dev->base_addr = info->io_base;
+ dev->irq = info->irq_level;
+ dev->dma = info->dma_level;
+
+ /* network layer callbacks and settings */
+ dev->do_ioctl = hdlcdev_ioctl;
+ dev->open = hdlcdev_open;
+ dev->stop = hdlcdev_close;
+ dev->tx_timeout = hdlcdev_tx_timeout;
+ dev->watchdog_timeo = 10*HZ;
+ dev->tx_queue_len = 50;
+
+ /* generic HDLC layer callbacks and settings */
+ hdlc = dev_to_hdlc(dev);
+ hdlc->attach = hdlcdev_attach;
+ hdlc->xmit = hdlcdev_xmit;
+
+ /* register objects with HDLC layer */
+ if ((rc = register_hdlc_device(dev))) {
+ printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+ free_netdev(dev);
+ return rc;
+ }
+
+ info->netdev = dev;
+ return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_exit(struct mgsl_struct *info)
+{
+ unregister_hdlc_device(info->netdev);
+ free_netdev(info->netdev);
+ info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
+
+static int __devinit synclink_init_one (struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ struct mgsl_struct *info;
+
+ if (pci_enable_device(dev)) {
+ printk("error enabling pci device %p\n", dev);
+ return -EIO;
+ }
+
+ if (!(info = mgsl_allocate_device())) {
+ printk("can't allocate device instance data.\n");
+ return -EIO;
+ }
+
+ /* Copy user configuration info to device instance data */
+
+ info->io_base = pci_resource_start(dev, 2);
+ info->irq_level = dev->irq;
+ info->phys_memory_base = pci_resource_start(dev, 3);
+
+ /* Because veremap only works on page boundaries we must map
+ * a larger area than is actually implemented for the LCR
+ * memory range. We map a full page starting at the page boundary.
+ */
+ info->phys_lcr_base = pci_resource_start(dev, 0);
+ info->lcr_offset = info->phys_lcr_base & (PAGE_SIZE-1);
+ info->phys_lcr_base &= ~(PAGE_SIZE-1);
+
+ info->bus_type = MGSL_BUS_TYPE_PCI;
+ info->io_addr_size = 8;
+ info->irq_flags = SA_SHIRQ;
+
+ if (dev->device == 0x0210) {
+ /* Version 1 PCI9030 based universal PCI adapter */
+ info->misc_ctrl_value = 0x007c4080;
+ info->hw_version = 1;
+ } else {
+ /* Version 0 PCI9050 based 5V PCI adapter
+ * A PCI9050 bug prevents reading LCR registers if
+ * LCR base address bit 7 is set. Maintain shadow
+ * value so we can write to LCR misc control reg.
+ */
+ info->misc_ctrl_value = 0x087e4546;
+ info->hw_version = 0;
+ }
+
+ mgsl_add_device(info);
+
+ return 0;
+}
+
+static void __devexit synclink_remove_one (struct pci_dev *dev)
+{
+}
+
diff --git a/drivers/char/synclinkmp.c b/drivers/char/synclinkmp.c
new file mode 100644
index 000000000000..ec949e4c070f
--- /dev/null
+++ b/drivers/char/synclinkmp.c
@@ -0,0 +1,5671 @@
+/*
+ * $Id: synclinkmp.c,v 4.34 2005/03/04 15:07:10 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink Multiport
+ * high speed multiprotocol serial adapter.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * Derived from serial.c written by Theodore Ts'o and Linus Torvalds
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+#if defined(__i386__)
+# define BREAKPOINT() asm(" int $3");
+#else
+# define BREAKPOINT() { }
+#endif
+
+#define MAX_DEVICES 12
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+static MGSL_PARAMS default_params = {
+ MGSL_MODE_HDLC, /* unsigned long mode */
+ 0, /* unsigned char loopback; */
+ HDLC_FLAG_UNDERRUN_ABORT15, /* unsigned short flags; */
+ HDLC_ENCODING_NRZI_SPACE, /* unsigned char encoding; */
+ 0, /* unsigned long clock_speed; */
+ 0xff, /* unsigned char addr_filter; */
+ HDLC_CRC_16_CCITT, /* unsigned short crc_type; */
+ HDLC_PREAMBLE_LENGTH_8BITS, /* unsigned char preamble_length; */
+ HDLC_PREAMBLE_PATTERN_NONE, /* unsigned char preamble; */
+ 9600, /* unsigned long data_rate; */
+ 8, /* unsigned char data_bits; */
+ 1, /* unsigned char stop_bits; */
+ ASYNC_PARITY_NONE /* unsigned char parity; */
+};
+
+/* size in bytes of DMA data buffers */
+#define SCABUFSIZE 1024
+#define SCA_MEM_SIZE 0x40000
+#define SCA_BASE_SIZE 512
+#define SCA_REG_SIZE 16
+#define SCA_MAX_PORTS 4
+#define SCAMAXDESC 128
+
+#define BUFFERLISTSIZE 4096
+
+/* SCA-I style DMA buffer descriptor */
+typedef struct _SCADESC
+{
+ u16 next; /* lower l6 bits of next descriptor addr */
+ u16 buf_ptr; /* lower 16 bits of buffer addr */
+ u8 buf_base; /* upper 8 bits of buffer addr */
+ u8 pad1;
+ u16 length; /* length of buffer */
+ u8 status; /* status of buffer */
+ u8 pad2;
+} SCADESC, *PSCADESC;
+
+typedef struct _SCADESC_EX
+{
+ /* device driver bookkeeping section */
+ char *virt_addr; /* virtual address of data buffer */
+ u16 phys_entry; /* lower 16-bits of physical address of this descriptor */
+} SCADESC_EX, *PSCADESC_EX;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE 1
+#define BH_TRANSMIT 2
+#define BH_STATUS 4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct _input_signal_events {
+ int ri_up;
+ int ri_down;
+ int dsr_up;
+ int dsr_down;
+ int dcd_up;
+ int dcd_down;
+ int cts_up;
+ int cts_down;
+};
+
+/*
+ * Device instance data structure
+ */
+typedef struct _synclinkmp_info {
+ void *if_ptr; /* General purpose pointer (used by SPPP) */
+ int magic;
+ int flags;
+ int count; /* count of opens */
+ int line;
+ unsigned short close_delay;
+ unsigned short closing_wait; /* time to wait before closing */
+
+ struct mgsl_icount icount;
+
+ struct tty_struct *tty;
+ int timeout;
+ int x_char; /* xon/xoff character */
+ int blocked_open; /* # of blocked opens */
+ u16 read_status_mask1; /* break detection (SR1 indications) */
+ u16 read_status_mask2; /* parity/framing/overun (SR2 indications) */
+ unsigned char ignore_status_mask1; /* break detection (SR1 indications) */
+ unsigned char ignore_status_mask2; /* parity/framing/overun (SR2 indications) */
+ unsigned char *tx_buf;
+ int tx_put;
+ int tx_get;
+ int tx_count;
+
+ wait_queue_head_t open_wait;
+ wait_queue_head_t close_wait;
+
+ wait_queue_head_t status_event_wait_q;
+ wait_queue_head_t event_wait_q;
+ struct timer_list tx_timer; /* HDLC transmit timeout timer */
+ struct _synclinkmp_info *next_device; /* device list link */
+ struct timer_list status_timer; /* input signal status check timer */
+
+ spinlock_t lock; /* spinlock for synchronizing with ISR */
+ struct work_struct task; /* task structure for scheduling bh */
+
+ u32 max_frame_size; /* as set by device config */
+
+ u32 pending_bh;
+
+ int bh_running; /* Protection from multiple */
+ int isr_overflow;
+ int bh_requested;
+
+ int dcd_chkcount; /* check counts to prevent */
+ int cts_chkcount; /* too many IRQs if a signal */
+ int dsr_chkcount; /* is floating */
+ int ri_chkcount;
+
+ char *buffer_list; /* virtual address of Rx & Tx buffer lists */
+ unsigned long buffer_list_phys;
+
+ unsigned int rx_buf_count; /* count of total allocated Rx buffers */
+ SCADESC *rx_buf_list; /* list of receive buffer entries */
+ SCADESC_EX rx_buf_list_ex[SCAMAXDESC]; /* list of receive buffer entries */
+ unsigned int current_rx_buf;
+
+ unsigned int tx_buf_count; /* count of total allocated Tx buffers */
+ SCADESC *tx_buf_list; /* list of transmit buffer entries */
+ SCADESC_EX tx_buf_list_ex[SCAMAXDESC]; /* list of transmit buffer entries */
+ unsigned int last_tx_buf;
+
+ unsigned char *tmp_rx_buf;
+ unsigned int tmp_rx_buf_count;
+
+ int rx_enabled;
+ int rx_overflow;
+
+ int tx_enabled;
+ int tx_active;
+ u32 idle_mode;
+
+ unsigned char ie0_value;
+ unsigned char ie1_value;
+ unsigned char ie2_value;
+ unsigned char ctrlreg_value;
+ unsigned char old_signals;
+
+ char device_name[25]; /* device instance name */
+
+ int port_count;
+ int adapter_num;
+ int port_num;
+
+ struct _synclinkmp_info *port_array[SCA_MAX_PORTS];
+
+ unsigned int bus_type; /* expansion bus type (ISA,EISA,PCI) */
+
+ unsigned int irq_level; /* interrupt level */
+ unsigned long irq_flags;
+ int irq_requested; /* nonzero if IRQ requested */
+
+ MGSL_PARAMS params; /* communications parameters */
+
+ unsigned char serial_signals; /* current serial signal states */
+
+ int irq_occurred; /* for diagnostics use */
+ unsigned int init_error; /* Initialization startup error */
+
+ u32 last_mem_alloc;
+ unsigned char* memory_base; /* shared memory address (PCI only) */
+ u32 phys_memory_base;
+ int shared_mem_requested;
+
+ unsigned char* sca_base; /* HD64570 SCA Memory address */
+ u32 phys_sca_base;
+ u32 sca_offset;
+ int sca_base_requested;
+
+ unsigned char* lcr_base; /* local config registers (PCI only) */
+ u32 phys_lcr_base;
+ u32 lcr_offset;
+ int lcr_mem_requested;
+
+ unsigned char* statctrl_base; /* status/control register memory */
+ u32 phys_statctrl_base;
+ u32 statctrl_offset;
+ int sca_statctrl_requested;
+
+ u32 misc_ctrl_value;
+ char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+ char char_buf[MAX_ASYNC_BUFFER_SIZE];
+ BOOLEAN drop_rts_on_tx_done;
+
+ struct _input_signal_events input_signal_events;
+
+ /* SPPP/Cisco HDLC device parts */
+ int netcount;
+ int dosyncppp;
+ spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+ struct net_device *netdev;
+#endif
+
+} SLMP_INFO;
+
+#define MGSL_MAGIC 0x5401
+
+/*
+ * define serial signal status change macros
+ */
+#define MISCSTATUS_DCD_LATCHED (SerialSignal_DCD<<8) /* indicates change in DCD */
+#define MISCSTATUS_RI_LATCHED (SerialSignal_RI<<8) /* indicates change in RI */
+#define MISCSTATUS_CTS_LATCHED (SerialSignal_CTS<<8) /* indicates change in CTS */
+#define MISCSTATUS_DSR_LATCHED (SerialSignal_DSR<<8) /* change in DSR */
+
+/* Common Register macros */
+#define LPR 0x00
+#define PABR0 0x02
+#define PABR1 0x03
+#define WCRL 0x04
+#define WCRM 0x05
+#define WCRH 0x06
+#define DPCR 0x08
+#define DMER 0x09
+#define ISR0 0x10
+#define ISR1 0x11
+#define ISR2 0x12
+#define IER0 0x14
+#define IER1 0x15
+#define IER2 0x16
+#define ITCR 0x18
+#define INTVR 0x1a
+#define IMVR 0x1c
+
+/* MSCI Register macros */
+#define TRB 0x20
+#define TRBL 0x20
+#define TRBH 0x21
+#define SR0 0x22
+#define SR1 0x23
+#define SR2 0x24
+#define SR3 0x25
+#define FST 0x26
+#define IE0 0x28
+#define IE1 0x29
+#define IE2 0x2a
+#define FIE 0x2b
+#define CMD 0x2c
+#define MD0 0x2e
+#define MD1 0x2f
+#define MD2 0x30
+#define CTL 0x31
+#define SA0 0x32
+#define SA1 0x33
+#define IDL 0x34
+#define TMC 0x35
+#define RXS 0x36
+#define TXS 0x37
+#define TRC0 0x38
+#define TRC1 0x39
+#define RRC 0x3a
+#define CST0 0x3c
+#define CST1 0x3d
+
+/* Timer Register Macros */
+#define TCNT 0x60
+#define TCNTL 0x60
+#define TCNTH 0x61
+#define TCONR 0x62
+#define TCONRL 0x62
+#define TCONRH 0x63
+#define TMCS 0x64
+#define TEPR 0x65
+
+/* DMA Controller Register macros */
+#define DARL 0x80
+#define DARH 0x81
+#define DARB 0x82
+#define BAR 0x80
+#define BARL 0x80
+#define BARH 0x81
+#define BARB 0x82
+#define SAR 0x84
+#define SARL 0x84
+#define SARH 0x85
+#define SARB 0x86
+#define CPB 0x86
+#define CDA 0x88
+#define CDAL 0x88
+#define CDAH 0x89
+#define EDA 0x8a
+#define EDAL 0x8a
+#define EDAH 0x8b
+#define BFL 0x8c
+#define BFLL 0x8c
+#define BFLH 0x8d
+#define BCR 0x8e
+#define BCRL 0x8e
+#define BCRH 0x8f
+#define DSR 0x90
+#define DMR 0x91
+#define FCT 0x93
+#define DIR 0x94
+#define DCMD 0x95
+
+/* combine with timer or DMA register address */
+#define TIMER0 0x00
+#define TIMER1 0x08
+#define TIMER2 0x10
+#define TIMER3 0x18
+#define RXDMA 0x00
+#define TXDMA 0x20
+
+/* SCA Command Codes */
+#define NOOP 0x00
+#define TXRESET 0x01
+#define TXENABLE 0x02
+#define TXDISABLE 0x03
+#define TXCRCINIT 0x04
+#define TXCRCEXCL 0x05
+#define TXEOM 0x06
+#define TXABORT 0x07
+#define MPON 0x08
+#define TXBUFCLR 0x09
+#define RXRESET 0x11
+#define RXENABLE 0x12
+#define RXDISABLE 0x13
+#define RXCRCINIT 0x14
+#define RXREJECT 0x15
+#define SEARCHMP 0x16
+#define RXCRCEXCL 0x17
+#define RXCRCCALC 0x18
+#define CHRESET 0x21
+#define HUNT 0x31
+
+/* DMA command codes */
+#define SWABORT 0x01
+#define FEICLEAR 0x02
+
+/* IE0 */
+#define TXINTE BIT7
+#define RXINTE BIT6
+#define TXRDYE BIT1
+#define RXRDYE BIT0
+
+/* IE1 & SR1 */
+#define UDRN BIT7
+#define IDLE BIT6
+#define SYNCD BIT4
+#define FLGD BIT4
+#define CCTS BIT3
+#define CDCD BIT2
+#define BRKD BIT1
+#define ABTD BIT1
+#define GAPD BIT1
+#define BRKE BIT0
+#define IDLD BIT0
+
+/* IE2 & SR2 */
+#define EOM BIT7
+#define PMP BIT6
+#define SHRT BIT6
+#define PE BIT5
+#define ABT BIT5
+#define FRME BIT4
+#define RBIT BIT4
+#define OVRN BIT3
+#define CRCE BIT2
+
+
+/*
+ * Global linked list of SyncLink devices
+ */
+static SLMP_INFO *synclinkmp_device_list = NULL;
+static int synclinkmp_adapter_count = -1;
+static int synclinkmp_device_count = 0;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load=0;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor=0;
+
+/*
+ * Array of user specified options for ISA adapters.
+ */
+static int debug_level = 0;
+static int maxframe[MAX_DEVICES] = {0,};
+static int dosyncppp[MAX_DEVICES] = {0,};
+
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+
+static char *driver_name = "SyncLink MultiPort driver";
+static char *driver_version = "$Revision: 4.34 $";
+
+static int synclinkmp_init_one(struct pci_dev *dev,const struct pci_device_id *ent);
+static void synclinkmp_remove_one(struct pci_dev *dev);
+
+static struct pci_device_id synclinkmp_pci_tbl[] = {
+ { PCI_VENDOR_ID_MICROGATE, PCI_DEVICE_ID_MICROGATE_SCA, PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, synclinkmp_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+static struct pci_driver synclinkmp_pci_driver = {
+ .name = "synclinkmp",
+ .id_table = synclinkmp_pci_tbl,
+ .probe = synclinkmp_init_one,
+ .remove = __devexit_p(synclinkmp_remove_one),
+};
+
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+
+/* tty callbacks */
+
+static int open(struct tty_struct *tty, struct file * filp);
+static void close(struct tty_struct *tty, struct file * filp);
+static void hangup(struct tty_struct *tty);
+static void set_termios(struct tty_struct *tty, struct termios *old_termios);
+
+static int write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void put_char(struct tty_struct *tty, unsigned char ch);
+static void send_xchar(struct tty_struct *tty, char ch);
+static void wait_until_sent(struct tty_struct *tty, int timeout);
+static int write_room(struct tty_struct *tty);
+static void flush_chars(struct tty_struct *tty);
+static void flush_buffer(struct tty_struct *tty);
+static void tx_hold(struct tty_struct *tty);
+static void tx_release(struct tty_struct *tty);
+
+static int ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static int read_proc(char *page, char **start, off_t off, int count,int *eof, void *data);
+static int chars_in_buffer(struct tty_struct *tty);
+static void throttle(struct tty_struct * tty);
+static void unthrottle(struct tty_struct * tty);
+static void set_break(struct tty_struct *tty, int break_state);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(SLMP_INFO *info);
+static void hdlcdev_rx(SLMP_INFO *info, char *buf, int size);
+static int hdlcdev_init(SLMP_INFO *info);
+static void hdlcdev_exit(SLMP_INFO *info);
+#endif
+
+/* ioctl handlers */
+
+static int get_stats(SLMP_INFO *info, struct mgsl_icount __user *user_icount);
+static int get_params(SLMP_INFO *info, MGSL_PARAMS __user *params);
+static int set_params(SLMP_INFO *info, MGSL_PARAMS __user *params);
+static int get_txidle(SLMP_INFO *info, int __user *idle_mode);
+static int set_txidle(SLMP_INFO *info, int idle_mode);
+static int tx_enable(SLMP_INFO *info, int enable);
+static int tx_abort(SLMP_INFO *info);
+static int rx_enable(SLMP_INFO *info, int enable);
+static int map_status(int signals);
+static int modem_input_wait(SLMP_INFO *info,int arg);
+static int wait_mgsl_event(SLMP_INFO *info, int __user *mask_ptr);
+static int tiocmget(struct tty_struct *tty, struct file *file);
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear);
+static void set_break(struct tty_struct *tty, int break_state);
+
+static void add_device(SLMP_INFO *info);
+static void device_init(int adapter_num, struct pci_dev *pdev);
+static int claim_resources(SLMP_INFO *info);
+static void release_resources(SLMP_INFO *info);
+
+static int startup(SLMP_INFO *info);
+static int block_til_ready(struct tty_struct *tty, struct file * filp,SLMP_INFO *info);
+static void shutdown(SLMP_INFO *info);
+static void program_hw(SLMP_INFO *info);
+static void change_params(SLMP_INFO *info);
+
+static int init_adapter(SLMP_INFO *info);
+static int register_test(SLMP_INFO *info);
+static int irq_test(SLMP_INFO *info);
+static int loopback_test(SLMP_INFO *info);
+static int adapter_test(SLMP_INFO *info);
+static int memory_test(SLMP_INFO *info);
+
+static void reset_adapter(SLMP_INFO *info);
+static void reset_port(SLMP_INFO *info);
+static void async_mode(SLMP_INFO *info);
+static void hdlc_mode(SLMP_INFO *info);
+
+static void rx_stop(SLMP_INFO *info);
+static void rx_start(SLMP_INFO *info);
+static void rx_reset_buffers(SLMP_INFO *info);
+static void rx_free_frame_buffers(SLMP_INFO *info, unsigned int first, unsigned int last);
+static int rx_get_frame(SLMP_INFO *info);
+
+static void tx_start(SLMP_INFO *info);
+static void tx_stop(SLMP_INFO *info);
+static void tx_load_fifo(SLMP_INFO *info);
+static void tx_set_idle(SLMP_INFO *info);
+static void tx_load_dma_buffer(SLMP_INFO *info, const char *buf, unsigned int count);
+
+static void get_signals(SLMP_INFO *info);
+static void set_signals(SLMP_INFO *info);
+static void enable_loopback(SLMP_INFO *info, int enable);
+static void set_rate(SLMP_INFO *info, u32 data_rate);
+
+static int bh_action(SLMP_INFO *info);
+static void bh_handler(void* Context);
+static void bh_receive(SLMP_INFO *info);
+static void bh_transmit(SLMP_INFO *info);
+static void bh_status(SLMP_INFO *info);
+static void isr_timer(SLMP_INFO *info);
+static void isr_rxint(SLMP_INFO *info);
+static void isr_rxrdy(SLMP_INFO *info);
+static void isr_txint(SLMP_INFO *info);
+static void isr_txrdy(SLMP_INFO *info);
+static void isr_rxdmaok(SLMP_INFO *info);
+static void isr_rxdmaerror(SLMP_INFO *info);
+static void isr_txdmaok(SLMP_INFO *info);
+static void isr_txdmaerror(SLMP_INFO *info);
+static void isr_io_pin(SLMP_INFO *info, u16 status);
+
+static int alloc_dma_bufs(SLMP_INFO *info);
+static void free_dma_bufs(SLMP_INFO *info);
+static int alloc_buf_list(SLMP_INFO *info);
+static int alloc_frame_bufs(SLMP_INFO *info, SCADESC *list, SCADESC_EX *list_ex,int count);
+static int alloc_tmp_rx_buf(SLMP_INFO *info);
+static void free_tmp_rx_buf(SLMP_INFO *info);
+
+static void load_pci_memory(SLMP_INFO *info, char* dest, const char* src, unsigned short count);
+static void trace_block(SLMP_INFO *info, const char* data, int count, int xmit);
+static void tx_timeout(unsigned long context);
+static void status_timeout(unsigned long context);
+
+static unsigned char read_reg(SLMP_INFO *info, unsigned char addr);
+static void write_reg(SLMP_INFO *info, unsigned char addr, unsigned char val);
+static u16 read_reg16(SLMP_INFO *info, unsigned char addr);
+static void write_reg16(SLMP_INFO *info, unsigned char addr, u16 val);
+static unsigned char read_status_reg(SLMP_INFO * info);
+static void write_control_reg(SLMP_INFO * info);
+
+
+static unsigned char rx_active_fifo_level = 16; // rx request FIFO activation level in bytes
+static unsigned char tx_active_fifo_level = 16; // tx request FIFO activation level in bytes
+static unsigned char tx_negate_fifo_level = 32; // tx request FIFO negation level in bytes
+
+static u32 misc_ctrl_value = 0x007e4040;
+static u32 lcr1_brdr_value = 0x00800029;
+
+static u32 read_ahead_count = 8;
+
+/* DPCR, DMA Priority Control
+ *
+ * 07..05 Not used, must be 0
+ * 04 BRC, bus release condition: 0=all transfers complete
+ * 1=release after 1 xfer on all channels
+ * 03 CCC, channel change condition: 0=every cycle
+ * 1=after each channel completes all xfers
+ * 02..00 PR<2..0>, priority 100=round robin
+ *
+ * 00000100 = 0x00
+ */
+static unsigned char dma_priority = 0x04;
+
+// Number of bytes that can be written to shared RAM
+// in a single write operation
+static u32 sca_pci_load_interval = 64;
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* synclinkmp_get_text_ptr(void);
+static void* synclinkmp_get_text_ptr(void) {return synclinkmp_get_text_ptr;}
+
+static inline int sanity_check(SLMP_INFO *info,
+ char *name, const char *routine)
+{
+#ifdef SANITY_CHECK
+ static const char *badmagic =
+ "Warning: bad magic number for synclinkmp_struct (%s) in %s\n";
+ static const char *badinfo =
+ "Warning: null synclinkmp_struct for (%s) in %s\n";
+
+ if (!info) {
+ printk(badinfo, name, routine);
+ return 1;
+ }
+ if (info->magic != MGSL_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+#else
+ if (!info)
+ return 1;
+#endif
+ return 0;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_receive_buf - pass receive data to line discipline
+ */
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+ const __u8 *data, char *flags, int count)
+{
+ struct tty_ldisc *ld;
+ if (!tty)
+ return;
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->receive_buf)
+ ld->receive_buf(tty, data, flags, count);
+ tty_ldisc_deref(ld);
+ }
+}
+
+/* tty callbacks */
+
+/* Called when a port is opened. Init and enable port.
+ */
+static int open(struct tty_struct *tty, struct file *filp)
+{
+ SLMP_INFO *info;
+ int retval, line;
+ unsigned long flags;
+
+ line = tty->index;
+ if ((line < 0) || (line >= synclinkmp_device_count)) {
+ printk("%s(%d): open with invalid line #%d.\n",
+ __FILE__,__LINE__,line);
+ return -ENODEV;
+ }
+
+ info = synclinkmp_device_list;
+ while(info && info->line != line)
+ info = info->next_device;
+ if (sanity_check(info, tty->name, "open"))
+ return -ENODEV;
+ if ( info->init_error ) {
+ printk("%s(%d):%s device is not allocated, init error=%d\n",
+ __FILE__,__LINE__,info->device_name,info->init_error);
+ return -ENODEV;
+ }
+
+ tty->driver_data = info;
+ info->tty = tty;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s open(), old ref count = %d\n",
+ __FILE__,__LINE__,tty->driver->name, info->count);
+
+ /* If port is closing, signal caller to try again */
+ if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+ if (info->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&info->close_wait);
+ retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+ goto cleanup;
+ }
+
+ info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->netcount) {
+ retval = -EBUSY;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ goto cleanup;
+ }
+ info->count++;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ if (info->count == 1) {
+ /* 1st open on this device, init hardware */
+ retval = startup(info);
+ if (retval < 0)
+ goto cleanup;
+ }
+
+ retval = block_til_ready(tty, filp, info);
+ if (retval) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s block_til_ready() returned %d\n",
+ __FILE__,__LINE__, info->device_name, retval);
+ goto cleanup;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s open() success\n",
+ __FILE__,__LINE__, info->device_name);
+ retval = 0;
+
+cleanup:
+ if (retval) {
+ if (tty->count == 1)
+ info->tty = NULL; /* tty layer will release tty struct */
+ if(info->count)
+ info->count--;
+ }
+
+ return retval;
+}
+
+/* Called when port is closed. Wait for remaining data to be
+ * sent. Disable port and free resources.
+ */
+static void close(struct tty_struct *tty, struct file *filp)
+{
+ SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+
+ if (sanity_check(info, tty->name, "close"))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s close() entry, count=%d\n",
+ __FILE__,__LINE__, info->device_name, info->count);
+
+ if (!info->count)
+ return;
+
+ if (tty_hung_up_p(filp))
+ goto cleanup;
+
+ if ((tty->count == 1) && (info->count != 1)) {
+ /*
+ * tty->count is 1 and the tty structure will be freed.
+ * info->count should be one in this case.
+ * if it's not, correct it so that the port is shutdown.
+ */
+ printk("%s(%d):%s close: bad refcount; tty->count is 1, "
+ "info->count is %d\n",
+ __FILE__,__LINE__, info->device_name, info->count);
+ info->count = 1;
+ }
+
+ info->count--;
+
+ /* if at least one open remaining, leave hardware active */
+ if (info->count)
+ goto cleanup;
+
+ info->flags |= ASYNC_CLOSING;
+
+ /* set tty->closing to notify line discipline to
+ * only process XON/XOFF characters. Only the N_TTY
+ * discipline appears to use this (ppp does not).
+ */
+ tty->closing = 1;
+
+ /* wait for transmit data to clear all layers */
+
+ if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s close() calling tty_wait_until_sent\n",
+ __FILE__,__LINE__, info->device_name );
+ tty_wait_until_sent(tty, info->closing_wait);
+ }
+
+ if (info->flags & ASYNC_INITIALIZED)
+ wait_until_sent(tty, info->timeout);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ tty_ldisc_flush(tty);
+
+ shutdown(info);
+
+ tty->closing = 0;
+ info->tty = NULL;
+
+ if (info->blocked_open) {
+ if (info->close_delay) {
+ msleep_interruptible(jiffies_to_msecs(info->close_delay));
+ }
+ wake_up_interruptible(&info->open_wait);
+ }
+
+ info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+
+ wake_up_interruptible(&info->close_wait);
+
+cleanup:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s close() exit, count=%d\n", __FILE__,__LINE__,
+ tty->driver->name, info->count);
+}
+
+/* Called by tty_hangup() when a hangup is signaled.
+ * This is the same as closing all open descriptors for the port.
+ */
+static void hangup(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s hangup()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (sanity_check(info, tty->name, "hangup"))
+ return;
+
+ flush_buffer(tty);
+ shutdown(info);
+
+ info->count = 0;
+ info->flags &= ~ASYNC_NORMAL_ACTIVE;
+ info->tty = NULL;
+
+ wake_up_interruptible(&info->open_wait);
+}
+
+/* Set new termios settings
+ */
+static void set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s set_termios()\n", __FILE__,__LINE__,
+ tty->driver->name );
+
+ /* just return if nothing has changed */
+ if ((tty->termios->c_cflag == old_termios->c_cflag)
+ && (RELEVANT_IFLAG(tty->termios->c_iflag)
+ == RELEVANT_IFLAG(old_termios->c_iflag)))
+ return;
+
+ change_params(info);
+
+ /* Handle transition to B0 status */
+ if (old_termios->c_cflag & CBAUD &&
+ !(tty->termios->c_cflag & CBAUD)) {
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ tty->termios->c_cflag & CBAUD) {
+ info->serial_signals |= SerialSignal_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ info->serial_signals |= SerialSignal_RTS;
+ }
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if (old_termios->c_cflag & CRTSCTS &&
+ !(tty->termios->c_cflag & CRTSCTS)) {
+ tty->hw_stopped = 0;
+ tx_release(tty);
+ }
+}
+
+/* Send a block of data
+ *
+ * Arguments:
+ *
+ * tty pointer to tty information structure
+ * buf pointer to buffer containing send data
+ * count size of send data in bytes
+ *
+ * Return Value: number of characters written
+ */
+static int write(struct tty_struct *tty,
+ const unsigned char *buf, int count)
+{
+ int c, ret = 0;
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s write() count=%d\n",
+ __FILE__,__LINE__,info->device_name,count);
+
+ if (sanity_check(info, tty->name, "write"))
+ goto cleanup;
+
+ if (!tty || !info->tx_buf)
+ goto cleanup;
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ if (count > info->max_frame_size) {
+ ret = -EIO;
+ goto cleanup;
+ }
+ if (info->tx_active)
+ goto cleanup;
+ if (info->tx_count) {
+ /* send accumulated data from send_char() calls */
+ /* as frame and wait before accepting more data. */
+ tx_load_dma_buffer(info, info->tx_buf, info->tx_count);
+ goto start;
+ }
+ ret = info->tx_count = count;
+ tx_load_dma_buffer(info, buf, count);
+ goto start;
+ }
+
+ for (;;) {
+ c = min_t(int, count,
+ min(info->max_frame_size - info->tx_count - 1,
+ info->max_frame_size - info->tx_put));
+ if (c <= 0)
+ break;
+
+ memcpy(info->tx_buf + info->tx_put, buf, c);
+
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_put += c;
+ if (info->tx_put >= info->max_frame_size)
+ info->tx_put -= info->max_frame_size;
+ info->tx_count += c;
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ buf += c;
+ count -= c;
+ ret += c;
+ }
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ if (count) {
+ ret = info->tx_count = 0;
+ goto cleanup;
+ }
+ tx_load_dma_buffer(info, info->tx_buf, info->tx_count);
+ }
+start:
+ if (info->tx_count && !tty->stopped && !tty->hw_stopped) {
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_active)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+cleanup:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk( "%s(%d):%s write() returning=%d\n",
+ __FILE__,__LINE__,info->device_name,ret);
+ return ret;
+}
+
+/* Add a character to the transmit buffer.
+ */
+static void put_char(struct tty_struct *tty, unsigned char ch)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO ) {
+ printk( "%s(%d):%s put_char(%d)\n",
+ __FILE__,__LINE__,info->device_name,ch);
+ }
+
+ if (sanity_check(info, tty->name, "put_char"))
+ return;
+
+ if (!tty || !info->tx_buf)
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ if ( (info->params.mode != MGSL_MODE_HDLC) ||
+ !info->tx_active ) {
+
+ if (info->tx_count < info->max_frame_size - 1) {
+ info->tx_buf[info->tx_put++] = ch;
+ if (info->tx_put >= info->max_frame_size)
+ info->tx_put -= info->max_frame_size;
+ info->tx_count++;
+ }
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Send a high-priority XON/XOFF character
+ */
+static void send_xchar(struct tty_struct *tty, char ch)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s send_xchar(%d)\n",
+ __FILE__,__LINE__, info->device_name, ch );
+
+ if (sanity_check(info, tty->name, "send_xchar"))
+ return;
+
+ info->x_char = ch;
+ if (ch) {
+ /* Make sure transmit interrupts are on */
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_enabled)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* Wait until the transmitter is empty.
+ */
+static void wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+ unsigned long orig_jiffies, char_time;
+
+ if (!info )
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s wait_until_sent() entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (sanity_check(info, tty->name, "wait_until_sent"))
+ return;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ goto exit;
+
+ orig_jiffies = jiffies;
+
+ /* Set check interval to 1/5 of estimated time to
+ * send a character, and make it at least 1. The check
+ * interval should also be less than the timeout.
+ * Note: use tight timings here to satisfy the NIST-PCTS.
+ */
+
+ if ( info->params.data_rate ) {
+ char_time = info->timeout/(32 * 5);
+ if (!char_time)
+ char_time++;
+ } else
+ char_time = 1;
+
+ if (timeout)
+ char_time = min_t(unsigned long, char_time, timeout);
+
+ if ( info->params.mode == MGSL_MODE_HDLC ) {
+ while (info->tx_active) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ } else {
+ //TODO: determine if there is something similar to USC16C32
+ // TXSTATUS_ALL_SENT status
+ while ( info->tx_active && info->tx_enabled) {
+ msleep_interruptible(jiffies_to_msecs(char_time));
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ }
+
+exit:
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s wait_until_sent() exit\n",
+ __FILE__,__LINE__, info->device_name );
+}
+
+/* Return the count of free bytes in transmit buffer
+ */
+static int write_room(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ int ret;
+
+ if (sanity_check(info, tty->name, "write_room"))
+ return 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ ret = (info->tx_active) ? 0 : HDLC_MAX_FRAME_SIZE;
+ } else {
+ ret = info->max_frame_size - info->tx_count - 1;
+ if (ret < 0)
+ ret = 0;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s write_room()=%d\n",
+ __FILE__, __LINE__, info->device_name, ret);
+
+ return ret;
+}
+
+/* enable transmitter and send remaining buffered characters
+ */
+static void flush_chars(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s flush_chars() entry tx_count=%d\n",
+ __FILE__,__LINE__,info->device_name,info->tx_count);
+
+ if (sanity_check(info, tty->name, "flush_chars"))
+ return;
+
+ if (info->tx_count <= 0 || tty->stopped || tty->hw_stopped ||
+ !info->tx_buf)
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s flush_chars() entry, starting transmitter\n",
+ __FILE__,__LINE__,info->device_name );
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ if (!info->tx_active) {
+ if ( (info->params.mode == MGSL_MODE_HDLC) &&
+ info->tx_count ) {
+ /* operating in synchronous (frame oriented) mode */
+ /* copy data from circular tx_buf to */
+ /* transmit DMA buffer. */
+ tx_load_dma_buffer(info,
+ info->tx_buf,info->tx_count);
+ }
+ tx_start(info);
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Discard all data in the send buffer
+ */
+static void flush_buffer(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s flush_buffer() entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (sanity_check(info, tty->name, "flush_buffer"))
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_count = info->tx_put = info->tx_get = 0;
+ del_timer(&info->tx_timer);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ wake_up_interruptible(&tty->write_wait);
+ tty_wakeup(tty);
+}
+
+/* throttle (stop) transmitter
+ */
+static void tx_hold(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (sanity_check(info, tty->name, "tx_hold"))
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):%s tx_hold()\n",
+ __FILE__,__LINE__,info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (info->tx_enabled)
+ tx_stop(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* release (start) transmitter
+ */
+static void tx_release(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (sanity_check(info, tty->name, "tx_release"))
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):%s tx_release()\n",
+ __FILE__,__LINE__,info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_enabled)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Service an IOCTL request
+ *
+ * Arguments:
+ *
+ * tty pointer to tty instance data
+ * file pointer to associated file object for device
+ * cmd IOCTL command code
+ * arg command argument/context
+ *
+ * Return Value: 0 if success, otherwise error code
+ */
+static int ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ int error;
+ struct mgsl_icount cnow; /* kernel counter temps */
+ struct serial_icounter_struct __user *p_cuser; /* user space */
+ unsigned long flags;
+ void __user *argp = (void __user *)arg;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s ioctl() cmd=%08X\n", __FILE__,__LINE__,
+ info->device_name, cmd );
+
+ if (sanity_check(info, tty->name, "ioctl"))
+ return -ENODEV;
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case MGSL_IOCGPARAMS:
+ return get_params(info, argp);
+ case MGSL_IOCSPARAMS:
+ return set_params(info, argp);
+ case MGSL_IOCGTXIDLE:
+ return get_txidle(info, argp);
+ case MGSL_IOCSTXIDLE:
+ return set_txidle(info, (int)arg);
+ case MGSL_IOCTXENABLE:
+ return tx_enable(info, (int)arg);
+ case MGSL_IOCRXENABLE:
+ return rx_enable(info, (int)arg);
+ case MGSL_IOCTXABORT:
+ return tx_abort(info);
+ case MGSL_IOCGSTATS:
+ return get_stats(info, argp);
+ case MGSL_IOCWAITEVENT:
+ return wait_mgsl_event(info, argp);
+ case MGSL_IOCLOOPTXDONE:
+ return 0; // TODO: Not supported, need to document
+ /* Wait for modem input (DCD,RI,DSR,CTS) change
+ * as specified by mask in arg (TIOCM_RNG/DSR/CD/CTS)
+ */
+ case TIOCMIWAIT:
+ return modem_input_wait(info,(int)arg);
+
+ /*
+ * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+ * Return: write counters to the user passed counter struct
+ * NB: both 1->0 and 0->1 transitions are counted except for
+ * RI where only 0->1 is counted.
+ */
+ case TIOCGICOUNT:
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ spin_unlock_irqrestore(&info->lock,flags);
+ p_cuser = argp;
+ PUT_USER(error,cnow.cts, &p_cuser->cts);
+ if (error) return error;
+ PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+ if (error) return error;
+ PUT_USER(error,cnow.rng, &p_cuser->rng);
+ if (error) return error;
+ PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+ if (error) return error;
+ PUT_USER(error,cnow.rx, &p_cuser->rx);
+ if (error) return error;
+ PUT_USER(error,cnow.tx, &p_cuser->tx);
+ if (error) return error;
+ PUT_USER(error,cnow.frame, &p_cuser->frame);
+ if (error) return error;
+ PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+ if (error) return error;
+ PUT_USER(error,cnow.parity, &p_cuser->parity);
+ if (error) return error;
+ PUT_USER(error,cnow.brk, &p_cuser->brk);
+ if (error) return error;
+ PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+ if (error) return error;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, SLMP_INFO *info)
+{
+ char stat_buf[30];
+ int ret;
+ unsigned long flags;
+
+ ret = sprintf(buf, "%s: SCABase=%08x Mem=%08X StatusControl=%08x LCR=%08X\n"
+ "\tIRQ=%d MaxFrameSize=%u\n",
+ info->device_name,
+ info->phys_sca_base,
+ info->phys_memory_base,
+ info->phys_statctrl_base,
+ info->phys_lcr_base,
+ info->irq_level,
+ info->max_frame_size );
+
+ /* output current serial signal states */
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ stat_buf[0] = 0;
+ stat_buf[1] = 0;
+ if (info->serial_signals & SerialSignal_RTS)
+ strcat(stat_buf, "|RTS");
+ if (info->serial_signals & SerialSignal_CTS)
+ strcat(stat_buf, "|CTS");
+ if (info->serial_signals & SerialSignal_DTR)
+ strcat(stat_buf, "|DTR");
+ if (info->serial_signals & SerialSignal_DSR)
+ strcat(stat_buf, "|DSR");
+ if (info->serial_signals & SerialSignal_DCD)
+ strcat(stat_buf, "|CD");
+ if (info->serial_signals & SerialSignal_RI)
+ strcat(stat_buf, "|RI");
+
+ if (info->params.mode == MGSL_MODE_HDLC) {
+ ret += sprintf(buf+ret, "\tHDLC txok:%d rxok:%d",
+ info->icount.txok, info->icount.rxok);
+ if (info->icount.txunder)
+ ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+ if (info->icount.txabort)
+ ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+ if (info->icount.rxshort)
+ ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);
+ if (info->icount.rxlong)
+ ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+ if (info->icount.rxover)
+ ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+ if (info->icount.rxcrc)
+ ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxcrc);
+ } else {
+ ret += sprintf(buf+ret, "\tASYNC tx:%d rx:%d",
+ info->icount.tx, info->icount.rx);
+ if (info->icount.frame)
+ ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+ if (info->icount.parity)
+ ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+ if (info->icount.brk)
+ ret += sprintf(buf+ret, " brk:%d", info->icount.brk);
+ if (info->icount.overrun)
+ ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+ }
+
+ /* Append serial signal status to end */
+ ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+
+ ret += sprintf(buf+ret, "\ttxactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+ info->tx_active,info->bh_requested,info->bh_running,
+ info->pending_bh);
+
+ return ret;
+}
+
+/* Called to print information about devices
+ */
+int read_proc(char *page, char **start, off_t off, int count,
+ int *eof, void *data)
+{
+ int len = 0, l;
+ off_t begin = 0;
+ SLMP_INFO *info;
+
+ len += sprintf(page, "synclinkmp driver:%s\n", driver_version);
+
+ info = synclinkmp_device_list;
+ while( info ) {
+ l = line_info(page + len, info);
+ len += l;
+ if (len+begin > off+count)
+ goto done;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ info = info->next_device;
+ }
+
+ *eof = 1;
+done:
+ if (off >= len+begin)
+ return 0;
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+/* Return the count of bytes in transmit buffer
+ */
+static int chars_in_buffer(struct tty_struct *tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+
+ if (sanity_check(info, tty->name, "chars_in_buffer"))
+ return 0;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s chars_in_buffer()=%d\n",
+ __FILE__, __LINE__, info->device_name, info->tx_count);
+
+ return info->tx_count;
+}
+
+/* Signal remote device to throttle send data (our receive data)
+ */
+static void throttle(struct tty_struct * tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s throttle() entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (sanity_check(info, tty->name, "throttle"))
+ return;
+
+ if (I_IXOFF(tty))
+ send_xchar(tty, STOP_CHAR(tty));
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals &= ~SerialSignal_RTS;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* Signal remote device to stop throttling send data (our receive data)
+ */
+static void unthrottle(struct tty_struct * tty)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s unthrottle() entry\n",
+ __FILE__,__LINE__, info->device_name );
+
+ if (sanity_check(info, tty->name, "unthrottle"))
+ return;
+
+ if (I_IXOFF(tty)) {
+ if (info->x_char)
+ info->x_char = 0;
+ else
+ send_xchar(tty, START_CHAR(tty));
+ }
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals |= SerialSignal_RTS;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+}
+
+/* set or clear transmit break condition
+ * break_state -1=set break condition, 0=clear
+ */
+static void set_break(struct tty_struct *tty, int break_state)
+{
+ unsigned char RegValue;
+ SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s set_break(%d)\n",
+ __FILE__,__LINE__, info->device_name, break_state);
+
+ if (sanity_check(info, tty->name, "set_break"))
+ return;
+
+ spin_lock_irqsave(&info->lock,flags);
+ RegValue = read_reg(info, CTL);
+ if (break_state == -1)
+ RegValue |= BIT3;
+ else
+ RegValue &= ~BIT3;
+ write_reg(info, CTL, RegValue);
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev pointer to network device structure
+ * encoding serial encoding setting
+ * parity FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+ unsigned short parity)
+{
+ SLMP_INFO *info = dev_to_port(dev);
+ unsigned char new_encoding;
+ unsigned short new_crctype;
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ switch (encoding)
+ {
+ case ENCODING_NRZ: new_encoding = HDLC_ENCODING_NRZ; break;
+ case ENCODING_NRZI: new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+ case ENCODING_FM_MARK: new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+ case ENCODING_FM_SPACE: new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+ case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+ default: return -EINVAL;
+ }
+
+ switch (parity)
+ {
+ case PARITY_NONE: new_crctype = HDLC_CRC_NONE; break;
+ case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+ case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+ default: return -EINVAL;
+ }
+
+ info->params.encoding = new_encoding;
+ info->params.crc_type = new_crctype;;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ program_hw(info);
+
+ return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb socket buffer containing HDLC frame
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ SLMP_INFO *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+ /* stop sending until this frame completes */
+ netif_stop_queue(dev);
+
+ /* copy data to device buffers */
+ info->tx_count = skb->len;
+ tx_load_dma_buffer(info, skb->data, skb->len);
+
+ /* update network statistics */
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+
+ /* done with socket buffer, so free it */
+ dev_kfree_skb(skb);
+
+ /* save start time for transmit timeout detection */
+ dev->trans_start = jiffies;
+
+ /* start hardware transmitter if necessary */
+ spin_lock_irqsave(&info->lock,flags);
+ if (!info->tx_active)
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+ SLMP_INFO *info = dev_to_port(dev);
+ int rc;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+ /* generic HDLC layer open processing */
+ if ((rc = hdlc_open(dev)))
+ return rc;
+
+ /* arbitrate between network and tty opens */
+ spin_lock_irqsave(&info->netlock, flags);
+ if (info->count != 0 || info->netcount != 0) {
+ printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return -EBUSY;
+ }
+ info->netcount=1;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ /* claim resources and init adapter */
+ if ((rc = startup(info)) != 0) {
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+ return rc;
+ }
+
+ /* assert DTR and RTS, apply hardware settings */
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ program_hw(info);
+
+ /* enable network layer transmit */
+ dev->trans_start = jiffies;
+ netif_start_queue(dev);
+
+ /* inform generic HDLC layer of current DCD status */
+ spin_lock_irqsave(&info->lock, flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock, flags);
+ hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+ return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+ SLMP_INFO *info = dev_to_port(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+ netif_stop_queue(dev);
+
+ /* shutdown adapter and release resources */
+ shutdown(info);
+
+ hdlc_close(dev);
+
+ spin_lock_irqsave(&info->netlock, flags);
+ info->netcount=0;
+ spin_unlock_irqrestore(&info->netlock, flags);
+
+ return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev pointer to network device structure
+ * ifr pointer to network interface request structure
+ * cmd IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ const size_t size = sizeof(sync_serial_settings);
+ sync_serial_settings new_line;
+ sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+ SLMP_INFO *info = dev_to_port(dev);
+ unsigned int flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+ /* return error if TTY interface open */
+ if (info->count)
+ return -EBUSY;
+
+ if (cmd != SIOCWANDEV)
+ return hdlc_ioctl(dev, ifr, cmd);
+
+ switch(ifr->ifr_settings.type) {
+ case IF_GET_IFACE: /* return current sync_serial_settings */
+
+ ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+ if (ifr->ifr_settings.size < size) {
+ ifr->ifr_settings.size = size; /* data size wanted */
+ return -ENOBUFS;
+ }
+
+ flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+
+ switch (flags){
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+ case (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_INT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG): new_line.clock_type = CLOCK_TXINT; break;
+ case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+ default: new_line.clock_type = CLOCK_DEFAULT;
+ }
+
+ new_line.clock_rate = info->params.clock_speed;
+ new_line.loopback = info->params.loopback ? 1:0;
+
+ if (copy_to_user(line, &new_line, size))
+ return -EFAULT;
+ return 0;
+
+ case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&new_line, line, size))
+ return -EFAULT;
+
+ switch (new_line.clock_type)
+ {
+ case CLOCK_EXT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+ case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+ case CLOCK_INT: flags = HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_TXINT: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG; break;
+ case CLOCK_DEFAULT: flags = info->params.flags &
+ (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN); break;
+ default: return -EINVAL;
+ }
+
+ if (new_line.loopback != 0 && new_line.loopback != 1)
+ return -EINVAL;
+
+ info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+ HDLC_FLAG_RXC_BRG | HDLC_FLAG_RXC_TXCPIN |
+ HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+ HDLC_FLAG_TXC_BRG | HDLC_FLAG_TXC_RXCPIN);
+ info->params.flags |= flags;
+
+ info->params.loopback = new_line.loopback;
+
+ if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+ info->params.clock_speed = new_line.clock_rate;
+ else
+ info->params.clock_speed = 0;
+
+ /* if network interface up, reprogram hardware */
+ if (info->netcount)
+ program_hw(info);
+ return 0;
+
+ default:
+ return hdlc_ioctl(dev, ifr, cmd);
+ }
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+ SLMP_INFO *info = dev_to_port(dev);
+ struct net_device_stats *stats = hdlc_stats(dev);
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+
+ spin_lock_irqsave(&info->lock,flags);
+ tx_stop(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_tx_done(SLMP_INFO *info)
+{
+ if (netif_queue_stopped(info->netdev))
+ netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info pointer to device instance information
+ * buf pointer to buffer contianing frame data
+ * size count of data bytes in buf
+ */
+static void hdlcdev_rx(SLMP_INFO *info, char *buf, int size)
+{
+ struct sk_buff *skb = dev_alloc_skb(size);
+ struct net_device *dev = info->netdev;
+ struct net_device_stats *stats = hdlc_stats(dev);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("hdlcdev_rx(%s)\n",dev->name);
+
+ if (skb == NULL) {
+ printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+ stats->rx_dropped++;
+ return;
+ }
+
+ memcpy(skb_put(skb, size),buf,size);
+
+ skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+ stats->rx_packets++;
+ stats->rx_bytes += size;
+
+ netif_rx(skb);
+
+ info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(SLMP_INFO *info)
+{
+ int rc;
+ struct net_device *dev;
+ hdlc_device *hdlc;
+
+ /* allocate and initialize network and HDLC layer objects */
+
+ if (!(dev = alloc_hdlcdev(info))) {
+ printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+ return -ENOMEM;
+ }
+
+ /* for network layer reporting purposes only */
+ dev->mem_start = info->phys_sca_base;
+ dev->mem_end = info->phys_sca_base + SCA_BASE_SIZE - 1;
+ dev->irq = info->irq_level;
+
+ /* network layer callbacks and settings */
+ dev->do_ioctl = hdlcdev_ioctl;
+ dev->open = hdlcdev_open;
+ dev->stop = hdlcdev_close;
+ dev->tx_timeout = hdlcdev_tx_timeout;
+ dev->watchdog_timeo = 10*HZ;
+ dev->tx_queue_len = 50;
+
+ /* generic HDLC layer callbacks and settings */
+ hdlc = dev_to_hdlc(dev);
+ hdlc->attach = hdlcdev_attach;
+ hdlc->xmit = hdlcdev_xmit;
+
+ /* register objects with HDLC layer */
+ if ((rc = register_hdlc_device(dev))) {
+ printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+ free_netdev(dev);
+ return rc;
+ }
+
+ info->netdev = dev;
+ return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info pointer to device instance information
+ */
+static void hdlcdev_exit(SLMP_INFO *info)
+{
+ unregister_hdlc_device(info->netdev);
+ free_netdev(info->netdev);
+ info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
+
+/* Return next bottom half action to perform.
+ * Return Value: BH action code or 0 if nothing to do.
+ */
+int bh_action(SLMP_INFO *info)
+{
+ unsigned long flags;
+ int rc = 0;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ if (info->pending_bh & BH_RECEIVE) {
+ info->pending_bh &= ~BH_RECEIVE;
+ rc = BH_RECEIVE;
+ } else if (info->pending_bh & BH_TRANSMIT) {
+ info->pending_bh &= ~BH_TRANSMIT;
+ rc = BH_TRANSMIT;
+ } else if (info->pending_bh & BH_STATUS) {
+ info->pending_bh &= ~BH_STATUS;
+ rc = BH_STATUS;
+ }
+
+ if (!rc) {
+ /* Mark BH routine as complete */
+ info->bh_running = 0;
+ info->bh_requested = 0;
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return rc;
+}
+
+/* Perform bottom half processing of work items queued by ISR.
+ */
+void bh_handler(void* Context)
+{
+ SLMP_INFO *info = (SLMP_INFO*)Context;
+ int action;
+
+ if (!info)
+ return;
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_handler() entry\n",
+ __FILE__,__LINE__,info->device_name);
+
+ info->bh_running = 1;
+
+ while((action = bh_action(info)) != 0) {
+
+ /* Process work item */
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_handler() work item action=%d\n",
+ __FILE__,__LINE__,info->device_name, action);
+
+ switch (action) {
+
+ case BH_RECEIVE:
+ bh_receive(info);
+ break;
+ case BH_TRANSMIT:
+ bh_transmit(info);
+ break;
+ case BH_STATUS:
+ bh_status(info);
+ break;
+ default:
+ /* unknown work item ID */
+ printk("%s(%d):%s Unknown work item ID=%08X!\n",
+ __FILE__,__LINE__,info->device_name,action);
+ break;
+ }
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_handler() exit\n",
+ __FILE__,__LINE__,info->device_name);
+}
+
+void bh_receive(SLMP_INFO *info)
+{
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_receive()\n",
+ __FILE__,__LINE__,info->device_name);
+
+ while( rx_get_frame(info) );
+}
+
+void bh_transmit(SLMP_INFO *info)
+{
+ struct tty_struct *tty = info->tty;
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_transmit() entry\n",
+ __FILE__,__LINE__,info->device_name);
+
+ if (tty) {
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+ }
+}
+
+void bh_status(SLMP_INFO *info)
+{
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk( "%s(%d):%s bh_status() entry\n",
+ __FILE__,__LINE__,info->device_name);
+
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+}
+
+void isr_timer(SLMP_INFO * info)
+{
+ unsigned char timer = (info->port_num & 1) ? TIMER2 : TIMER0;
+
+ /* IER2<7..4> = timer<3..0> interrupt enables (0=disabled) */
+ write_reg(info, IER2, 0);
+
+ /* TMCS, Timer Control/Status Register
+ *
+ * 07 CMF, Compare match flag (read only) 1=match
+ * 06 ECMI, CMF Interrupt Enable: 0=disabled
+ * 05 Reserved, must be 0
+ * 04 TME, Timer Enable
+ * 03..00 Reserved, must be 0
+ *
+ * 0000 0000
+ */
+ write_reg(info, (unsigned char)(timer + TMCS), 0);
+
+ info->irq_occurred = TRUE;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_timer()\n",
+ __FILE__,__LINE__,info->device_name);
+}
+
+void isr_rxint(SLMP_INFO * info)
+{
+ struct tty_struct *tty = info->tty;
+ struct mgsl_icount *icount = &info->icount;
+ unsigned char status = read_reg(info, SR1) & info->ie1_value & (FLGD + IDLD + CDCD + BRKD);
+ unsigned char status2 = read_reg(info, SR2) & info->ie2_value & OVRN;
+
+ /* clear status bits */
+ if (status)
+ write_reg(info, SR1, status);
+
+ if (status2)
+ write_reg(info, SR2, status2);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_rxint status=%02X %02x\n",
+ __FILE__,__LINE__,info->device_name,status,status2);
+
+ if (info->params.mode == MGSL_MODE_ASYNC) {
+ if (status & BRKD) {
+ icount->brk++;
+
+ /* process break detection if tty control
+ * is not set to ignore it
+ */
+ if ( tty ) {
+ if (!(status & info->ignore_status_mask1)) {
+ if (info->read_status_mask1 & BRKD) {
+ *tty->flip.flag_buf_ptr = TTY_BREAK;
+ if (info->flags & ASYNC_SAK)
+ do_SAK(tty);
+ }
+ }
+ }
+ }
+ }
+ else {
+ if (status & (FLGD|IDLD)) {
+ if (status & FLGD)
+ info->icount.exithunt++;
+ else if (status & IDLD)
+ info->icount.rxidle++;
+ wake_up_interruptible(&info->event_wait_q);
+ }
+ }
+
+ if (status & CDCD) {
+ /* simulate a common modem status change interrupt
+ * for our handler
+ */
+ get_signals( info );
+ isr_io_pin(info,
+ MISCSTATUS_DCD_LATCHED|(info->serial_signals&SerialSignal_DCD));
+ }
+}
+
+/*
+ * handle async rx data interrupts
+ */
+void isr_rxrdy(SLMP_INFO * info)
+{
+ u16 status;
+ unsigned char DataByte;
+ struct tty_struct *tty = info->tty;
+ struct mgsl_icount *icount = &info->icount;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_rxrdy\n",
+ __FILE__,__LINE__,info->device_name);
+
+ while((status = read_reg(info,CST0)) & BIT0)
+ {
+ DataByte = read_reg(info,TRB);
+
+ if ( tty ) {
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ continue;
+
+ *tty->flip.char_buf_ptr = DataByte;
+ *tty->flip.flag_buf_ptr = 0;
+ }
+
+ icount->rx++;
+
+ if ( status & (PE + FRME + OVRN) ) {
+ printk("%s(%d):%s rxerr=%04X\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ /* update error statistics */
+ if (status & PE)
+ icount->parity++;
+ else if (status & FRME)
+ icount->frame++;
+ else if (status & OVRN)
+ icount->overrun++;
+
+ /* discard char if tty control flags say so */
+ if (status & info->ignore_status_mask2)
+ continue;
+
+ status &= info->read_status_mask2;
+
+ if ( tty ) {
+ if (status & PE)
+ *tty->flip.flag_buf_ptr = TTY_PARITY;
+ else if (status & FRME)
+ *tty->flip.flag_buf_ptr = TTY_FRAME;
+ if (status & OVRN) {
+ /* Overrun is special, since it's
+ * reported immediately, and doesn't
+ * affect the current character
+ */
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ tty->flip.count++;
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ *tty->flip.flag_buf_ptr = TTY_OVERRUN;
+ }
+ }
+ }
+ } /* end of if (error) */
+
+ if ( tty ) {
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_ISR ) {
+ printk("%s(%d):%s isr_rxrdy() flip count=%d\n",
+ __FILE__,__LINE__,info->device_name,
+ tty ? tty->flip.count : 0);
+ printk("%s(%d):%s rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+ __FILE__,__LINE__,info->device_name,
+ icount->rx,icount->brk,icount->parity,
+ icount->frame,icount->overrun);
+ }
+
+ if ( tty && tty->flip.count )
+ tty_flip_buffer_push(tty);
+}
+
+static void isr_txeom(SLMP_INFO * info, unsigned char status)
+{
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_txeom status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ write_reg(info, TXDMA + DIR, 0x00); /* disable Tx DMA IRQs */
+ write_reg(info, TXDMA + DSR, 0xc0); /* clear IRQs and disable DMA */
+ write_reg(info, TXDMA + DCMD, SWABORT); /* reset/init DMA channel */
+
+ if (status & UDRN) {
+ write_reg(info, CMD, TXRESET);
+ write_reg(info, CMD, TXENABLE);
+ } else
+ write_reg(info, CMD, TXBUFCLR);
+
+ /* disable and clear tx interrupts */
+ info->ie0_value &= ~TXRDYE;
+ info->ie1_value &= ~(IDLE + UDRN);
+ write_reg16(info, IE0, (unsigned short)((info->ie1_value << 8) + info->ie0_value));
+ write_reg(info, SR1, (unsigned char)(UDRN + IDLE));
+
+ if ( info->tx_active ) {
+ if (info->params.mode != MGSL_MODE_ASYNC) {
+ if (status & UDRN)
+ info->icount.txunder++;
+ else if (status & IDLE)
+ info->icount.txok++;
+ }
+
+ info->tx_active = 0;
+ info->tx_count = info->tx_put = info->tx_get = 0;
+
+ del_timer(&info->tx_timer);
+
+ if (info->params.mode != MGSL_MODE_ASYNC && info->drop_rts_on_tx_done ) {
+ info->serial_signals &= ~SerialSignal_RTS;
+ info->drop_rts_on_tx_done = 0;
+ set_signals(info);
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ {
+ if (info->tty && (info->tty->stopped || info->tty->hw_stopped)) {
+ tx_stop(info);
+ return;
+ }
+ info->pending_bh |= BH_TRANSMIT;
+ }
+ }
+}
+
+
+/*
+ * handle tx status interrupts
+ */
+void isr_txint(SLMP_INFO * info)
+{
+ unsigned char status = read_reg(info, SR1) & info->ie1_value & (UDRN + IDLE + CCTS);
+
+ /* clear status bits */
+ write_reg(info, SR1, status);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_txint status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ if (status & (UDRN + IDLE))
+ isr_txeom(info, status);
+
+ if (status & CCTS) {
+ /* simulate a common modem status change interrupt
+ * for our handler
+ */
+ get_signals( info );
+ isr_io_pin(info,
+ MISCSTATUS_CTS_LATCHED|(info->serial_signals&SerialSignal_CTS));
+
+ }
+}
+
+/*
+ * handle async tx data interrupts
+ */
+void isr_txrdy(SLMP_INFO * info)
+{
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_txrdy() tx_count=%d\n",
+ __FILE__,__LINE__,info->device_name,info->tx_count);
+
+ if (info->params.mode != MGSL_MODE_ASYNC) {
+ /* disable TXRDY IRQ, enable IDLE IRQ */
+ info->ie0_value &= ~TXRDYE;
+ info->ie1_value |= IDLE;
+ write_reg16(info, IE0, (unsigned short)((info->ie1_value << 8) + info->ie0_value));
+ return;
+ }
+
+ if (info->tty && (info->tty->stopped || info->tty->hw_stopped)) {
+ tx_stop(info);
+ return;
+ }
+
+ if ( info->tx_count )
+ tx_load_fifo( info );
+ else {
+ info->tx_active = 0;
+ info->ie0_value &= ~TXRDYE;
+ write_reg(info, IE0, info->ie0_value);
+ }
+
+ if (info->tx_count < WAKEUP_CHARS)
+ info->pending_bh |= BH_TRANSMIT;
+}
+
+void isr_rxdmaok(SLMP_INFO * info)
+{
+ /* BIT7 = EOT (end of transfer)
+ * BIT6 = EOM (end of message/frame)
+ */
+ unsigned char status = read_reg(info,RXDMA + DSR) & 0xc0;
+
+ /* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+ write_reg(info, RXDMA + DSR, (unsigned char)(status | 1));
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_rxdmaok(), status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ info->pending_bh |= BH_RECEIVE;
+}
+
+void isr_rxdmaerror(SLMP_INFO * info)
+{
+ /* BIT5 = BOF (buffer overflow)
+ * BIT4 = COF (counter overflow)
+ */
+ unsigned char status = read_reg(info,RXDMA + DSR) & 0x30;
+
+ /* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+ write_reg(info, RXDMA + DSR, (unsigned char)(status | 1));
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_rxdmaerror(), status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status);
+
+ info->rx_overflow = TRUE;
+ info->pending_bh |= BH_RECEIVE;
+}
+
+void isr_txdmaok(SLMP_INFO * info)
+{
+ unsigned char status_reg1 = read_reg(info, SR1);
+
+ write_reg(info, TXDMA + DIR, 0x00); /* disable Tx DMA IRQs */
+ write_reg(info, TXDMA + DSR, 0xc0); /* clear IRQs and disable DMA */
+ write_reg(info, TXDMA + DCMD, SWABORT); /* reset/init DMA channel */
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_txdmaok(), status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status_reg1);
+
+ /* program TXRDY as FIFO empty flag, enable TXRDY IRQ */
+ write_reg16(info, TRC0, 0);
+ info->ie0_value |= TXRDYE;
+ write_reg(info, IE0, info->ie0_value);
+}
+
+void isr_txdmaerror(SLMP_INFO * info)
+{
+ /* BIT5 = BOF (buffer overflow)
+ * BIT4 = COF (counter overflow)
+ */
+ unsigned char status = read_reg(info,TXDMA + DSR) & 0x30;
+
+ /* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+ write_reg(info, TXDMA + DSR, (unsigned char)(status | 1));
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s isr_txdmaerror(), status=%02x\n",
+ __FILE__,__LINE__,info->device_name,status);
+}
+
+/* handle input serial signal changes
+ */
+void isr_io_pin( SLMP_INFO *info, u16 status )
+{
+ struct mgsl_icount *icount;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):isr_io_pin status=%04X\n",
+ __FILE__,__LINE__,status);
+
+ if (status & (MISCSTATUS_CTS_LATCHED | MISCSTATUS_DCD_LATCHED |
+ MISCSTATUS_DSR_LATCHED | MISCSTATUS_RI_LATCHED) ) {
+ icount = &info->icount;
+ /* update input line counters */
+ if (status & MISCSTATUS_RI_LATCHED) {
+ icount->rng++;
+ if ( status & SerialSignal_RI )
+ info->input_signal_events.ri_up++;
+ else
+ info->input_signal_events.ri_down++;
+ }
+ if (status & MISCSTATUS_DSR_LATCHED) {
+ icount->dsr++;
+ if ( status & SerialSignal_DSR )
+ info->input_signal_events.dsr_up++;
+ else
+ info->input_signal_events.dsr_down++;
+ }
+ if (status & MISCSTATUS_DCD_LATCHED) {
+ if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT) {
+ info->ie1_value &= ~CDCD;
+ write_reg(info, IE1, info->ie1_value);
+ }
+ icount->dcd++;
+ if (status & SerialSignal_DCD) {
+ info->input_signal_events.dcd_up++;
+ } else
+ info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlc_set_carrier(status & SerialSignal_DCD, info->netdev);
+#endif
+ }
+ if (status & MISCSTATUS_CTS_LATCHED)
+ {
+ if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT) {
+ info->ie1_value &= ~CCTS;
+ write_reg(info, IE1, info->ie1_value);
+ }
+ icount->cts++;
+ if ( status & SerialSignal_CTS )
+ info->input_signal_events.cts_up++;
+ else
+ info->input_signal_events.cts_down++;
+ }
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ if ( (info->flags & ASYNC_CHECK_CD) &&
+ (status & MISCSTATUS_DCD_LATCHED) ) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s CD now %s...", info->device_name,
+ (status & SerialSignal_DCD) ? "on" : "off");
+ if (status & SerialSignal_DCD)
+ wake_up_interruptible(&info->open_wait);
+ else {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("doing serial hangup...");
+ if (info->tty)
+ tty_hangup(info->tty);
+ }
+ }
+
+ if ( (info->flags & ASYNC_CTS_FLOW) &&
+ (status & MISCSTATUS_CTS_LATCHED) ) {
+ if ( info->tty ) {
+ if (info->tty->hw_stopped) {
+ if (status & SerialSignal_CTS) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("CTS tx start...");
+ info->tty->hw_stopped = 0;
+ tx_start(info);
+ info->pending_bh |= BH_TRANSMIT;
+ return;
+ }
+ } else {
+ if (!(status & SerialSignal_CTS)) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("CTS tx stop...");
+ info->tty->hw_stopped = 1;
+ tx_stop(info);
+ }
+ }
+ }
+ }
+ }
+
+ info->pending_bh |= BH_STATUS;
+}
+
+/* Interrupt service routine entry point.
+ *
+ * Arguments:
+ * irq interrupt number that caused interrupt
+ * dev_id device ID supplied during interrupt registration
+ * regs interrupted processor context
+ */
+static irqreturn_t synclinkmp_interrupt(int irq, void *dev_id,
+ struct pt_regs *regs)
+{
+ SLMP_INFO * info;
+ unsigned char status, status0, status1=0;
+ unsigned char dmastatus, dmastatus0, dmastatus1=0;
+ unsigned char timerstatus0, timerstatus1=0;
+ unsigned char shift;
+ unsigned int i;
+ unsigned short tmp;
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d): synclinkmp_interrupt(%d)entry.\n",
+ __FILE__,__LINE__,irq);
+
+ info = (SLMP_INFO *)dev_id;
+ if (!info)
+ return IRQ_NONE;
+
+ spin_lock(&info->lock);
+
+ for(;;) {
+
+ /* get status for SCA0 (ports 0-1) */
+ tmp = read_reg16(info, ISR0); /* get ISR0 and ISR1 in one read */
+ status0 = (unsigned char)tmp;
+ dmastatus0 = (unsigned char)(tmp>>8);
+ timerstatus0 = read_reg(info, ISR2);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s status0=%02x, dmastatus0=%02x, timerstatus0=%02x\n",
+ __FILE__,__LINE__,info->device_name,
+ status0,dmastatus0,timerstatus0);
+
+ if (info->port_count == 4) {
+ /* get status for SCA1 (ports 2-3) */
+ tmp = read_reg16(info->port_array[2], ISR0);
+ status1 = (unsigned char)tmp;
+ dmastatus1 = (unsigned char)(tmp>>8);
+ timerstatus1 = read_reg(info->port_array[2], ISR2);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s status1=%02x, dmastatus1=%02x, timerstatus1=%02x\n",
+ __FILE__,__LINE__,info->device_name,
+ status1,dmastatus1,timerstatus1);
+ }
+
+ if (!status0 && !dmastatus0 && !timerstatus0 &&
+ !status1 && !dmastatus1 && !timerstatus1)
+ break;
+
+ for(i=0; i < info->port_count ; i++) {
+ if (info->port_array[i] == NULL)
+ continue;
+ if (i < 2) {
+ status = status0;
+ dmastatus = dmastatus0;
+ } else {
+ status = status1;
+ dmastatus = dmastatus1;
+ }
+
+ shift = i & 1 ? 4 :0;
+
+ if (status & BIT0 << shift)
+ isr_rxrdy(info->port_array[i]);
+ if (status & BIT1 << shift)
+ isr_txrdy(info->port_array[i]);
+ if (status & BIT2 << shift)
+ isr_rxint(info->port_array[i]);
+ if (status & BIT3 << shift)
+ isr_txint(info->port_array[i]);
+
+ if (dmastatus & BIT0 << shift)
+ isr_rxdmaerror(info->port_array[i]);
+ if (dmastatus & BIT1 << shift)
+ isr_rxdmaok(info->port_array[i]);
+ if (dmastatus & BIT2 << shift)
+ isr_txdmaerror(info->port_array[i]);
+ if (dmastatus & BIT3 << shift)
+ isr_txdmaok(info->port_array[i]);
+ }
+
+ if (timerstatus0 & (BIT5 | BIT4))
+ isr_timer(info->port_array[0]);
+ if (timerstatus0 & (BIT7 | BIT6))
+ isr_timer(info->port_array[1]);
+ if (timerstatus1 & (BIT5 | BIT4))
+ isr_timer(info->port_array[2]);
+ if (timerstatus1 & (BIT7 | BIT6))
+ isr_timer(info->port_array[3]);
+ }
+
+ for(i=0; i < info->port_count ; i++) {
+ SLMP_INFO * port = info->port_array[i];
+
+ /* Request bottom half processing if there's something
+ * for it to do and the bh is not already running.
+ *
+ * Note: startup adapter diags require interrupts.
+ * do not request bottom half processing if the
+ * device is not open in a normal mode.
+ */
+ if ( port && (port->count || port->netcount) &&
+ port->pending_bh && !port->bh_running &&
+ !port->bh_requested ) {
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):%s queueing bh task.\n",
+ __FILE__,__LINE__,port->device_name);
+ schedule_work(&port->task);
+ port->bh_requested = 1;
+ }
+ }
+
+ spin_unlock(&info->lock);
+
+ if ( debug_level >= DEBUG_LEVEL_ISR )
+ printk("%s(%d):synclinkmp_interrupt(%d)exit.\n",
+ __FILE__,__LINE__,irq);
+ return IRQ_HANDLED;
+}
+
+/* Initialize and start device.
+ */
+static int startup(SLMP_INFO * info)
+{
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):%s tx_releaseup()\n",__FILE__,__LINE__,info->device_name);
+
+ if (info->flags & ASYNC_INITIALIZED)
+ return 0;
+
+ if (!info->tx_buf) {
+ info->tx_buf = (unsigned char *)kmalloc(info->max_frame_size, GFP_KERNEL);
+ if (!info->tx_buf) {
+ printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+ __FILE__,__LINE__,info->device_name);
+ return -ENOMEM;
+ }
+ }
+
+ info->pending_bh = 0;
+
+ /* program hardware for current parameters */
+ reset_port(info);
+
+ change_params(info);
+
+ info->status_timer.expires = jiffies + msecs_to_jiffies(10);
+ add_timer(&info->status_timer);
+
+ if (info->tty)
+ clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags |= ASYNC_INITIALIZED;
+
+ return 0;
+}
+
+/* Called by close() and hangup() to shutdown hardware
+ */
+static void shutdown(SLMP_INFO * info)
+{
+ unsigned long flags;
+
+ if (!(info->flags & ASYNC_INITIALIZED))
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s synclinkmp_shutdown()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ /* clear status wait queue because status changes */
+ /* can't happen after shutting down the hardware */
+ wake_up_interruptible(&info->status_event_wait_q);
+ wake_up_interruptible(&info->event_wait_q);
+
+ del_timer(&info->tx_timer);
+ del_timer(&info->status_timer);
+
+ if (info->tx_buf) {
+ kfree(info->tx_buf);
+ info->tx_buf = NULL;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ reset_port(info);
+
+ if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+ set_signals(info);
+ }
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ if (info->tty)
+ set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+ info->flags &= ~ASYNC_INITIALIZED;
+}
+
+static void program_hw(SLMP_INFO *info)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ rx_stop(info);
+ tx_stop(info);
+
+ info->tx_count = info->tx_put = info->tx_get = 0;
+
+ if (info->params.mode == MGSL_MODE_HDLC || info->netcount)
+ hdlc_mode(info);
+ else
+ async_mode(info);
+
+ set_signals(info);
+
+ info->dcd_chkcount = 0;
+ info->cts_chkcount = 0;
+ info->ri_chkcount = 0;
+ info->dsr_chkcount = 0;
+
+ info->ie1_value |= (CDCD|CCTS);
+ write_reg(info, IE1, info->ie1_value);
+
+ get_signals(info);
+
+ if (info->netcount || (info->tty && info->tty->termios->c_cflag & CREAD) )
+ rx_start(info);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void change_params(SLMP_INFO *info)
+{
+ unsigned cflag;
+ int bits_per_char;
+
+ if (!info->tty || !info->tty->termios)
+ return;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s change_params()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ cflag = info->tty->termios->c_cflag;
+
+ /* if B0 rate (hangup) specified then negate DTR and RTS */
+ /* otherwise assert DTR and RTS */
+ if (cflag & CBAUD)
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ else
+ info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+
+ /* byte size and parity */
+
+ switch (cflag & CSIZE) {
+ case CS5: info->params.data_bits = 5; break;
+ case CS6: info->params.data_bits = 6; break;
+ case CS7: info->params.data_bits = 7; break;
+ case CS8: info->params.data_bits = 8; break;
+ /* Never happens, but GCC is too dumb to figure it out */
+ default: info->params.data_bits = 7; break;
+ }
+
+ if (cflag & CSTOPB)
+ info->params.stop_bits = 2;
+ else
+ info->params.stop_bits = 1;
+
+ info->params.parity = ASYNC_PARITY_NONE;
+ if (cflag & PARENB) {
+ if (cflag & PARODD)
+ info->params.parity = ASYNC_PARITY_ODD;
+ else
+ info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+ if (cflag & CMSPAR)
+ info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+ }
+
+ /* calculate number of jiffies to transmit a full
+ * FIFO (32 bytes) at specified data rate
+ */
+ bits_per_char = info->params.data_bits +
+ info->params.stop_bits + 1;
+
+ /* if port data rate is set to 460800 or less then
+ * allow tty settings to override, otherwise keep the
+ * current data rate.
+ */
+ if (info->params.data_rate <= 460800) {
+ info->params.data_rate = tty_get_baud_rate(info->tty);
+ }
+
+ if ( info->params.data_rate ) {
+ info->timeout = (32*HZ*bits_per_char) /
+ info->params.data_rate;
+ }
+ info->timeout += HZ/50; /* Add .02 seconds of slop */
+
+ if (cflag & CRTSCTS)
+ info->flags |= ASYNC_CTS_FLOW;
+ else
+ info->flags &= ~ASYNC_CTS_FLOW;
+
+ if (cflag & CLOCAL)
+ info->flags &= ~ASYNC_CHECK_CD;
+ else
+ info->flags |= ASYNC_CHECK_CD;
+
+ /* process tty input control flags */
+
+ info->read_status_mask2 = OVRN;
+ if (I_INPCK(info->tty))
+ info->read_status_mask2 |= PE | FRME;
+ if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ info->read_status_mask1 |= BRKD;
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask2 |= PE | FRME;
+ if (I_IGNBRK(info->tty)) {
+ info->ignore_status_mask1 |= BRKD;
+ /* If ignoring parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(info->tty))
+ info->ignore_status_mask2 |= OVRN;
+ }
+
+ program_hw(info);
+}
+
+static int get_stats(SLMP_INFO * info, struct mgsl_icount __user *user_icount)
+{
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s get_params()\n",
+ __FILE__,__LINE__, info->device_name);
+
+ COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s get_stats() user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int get_params(SLMP_INFO * info, MGSL_PARAMS __user *user_params)
+{
+ int err;
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s get_params()\n",
+ __FILE__,__LINE__, info->device_name);
+
+ COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s get_params() user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int set_params(SLMP_INFO * info, MGSL_PARAMS __user *new_params)
+{
+ unsigned long flags;
+ MGSL_PARAMS tmp_params;
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s set_params\n",
+ __FILE__,__LINE__,info->device_name );
+ COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s set_params() user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ change_params(info);
+
+ return 0;
+}
+
+static int get_txidle(SLMP_INFO * info, int __user *idle_mode)
+{
+ int err;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s get_txidle()=%d\n",
+ __FILE__,__LINE__, info->device_name, info->idle_mode);
+
+ COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+ if (err) {
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s get_txidle() user buffer copy failed\n",
+ __FILE__,__LINE__,info->device_name);
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static int set_txidle(SLMP_INFO * info, int idle_mode)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s set_txidle(%d)\n",
+ __FILE__,__LINE__,info->device_name, idle_mode );
+
+ spin_lock_irqsave(&info->lock,flags);
+ info->idle_mode = idle_mode;
+ tx_set_idle( info );
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int tx_enable(SLMP_INFO * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tx_enable(%d)\n",
+ __FILE__,__LINE__,info->device_name, enable);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if ( enable ) {
+ if ( !info->tx_enabled ) {
+ tx_start(info);
+ }
+ } else {
+ if ( info->tx_enabled )
+ tx_stop(info);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+/* abort send HDLC frame
+ */
+static int tx_abort(SLMP_INFO * info)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tx_abort()\n",
+ __FILE__,__LINE__,info->device_name);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if ( info->tx_active && info->params.mode == MGSL_MODE_HDLC ) {
+ info->ie1_value &= ~UDRN;
+ info->ie1_value |= IDLE;
+ write_reg(info, IE1, info->ie1_value); /* disable tx status interrupts */
+ write_reg(info, SR1, (unsigned char)(IDLE + UDRN)); /* clear pending */
+
+ write_reg(info, TXDMA + DSR, 0); /* disable DMA channel */
+ write_reg(info, TXDMA + DCMD, SWABORT); /* reset/init DMA channel */
+
+ write_reg(info, CMD, TXABORT);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int rx_enable(SLMP_INFO * info, int enable)
+{
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s rx_enable(%d)\n",
+ __FILE__,__LINE__,info->device_name,enable);
+
+ spin_lock_irqsave(&info->lock,flags);
+ if ( enable ) {
+ if ( !info->rx_enabled )
+ rx_start(info);
+ } else {
+ if ( info->rx_enabled )
+ rx_stop(info);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ return 0;
+}
+
+static int map_status(int signals)
+{
+ /* Map status bits to API event bits */
+
+ return ((signals & SerialSignal_DSR) ? MgslEvent_DsrActive : MgslEvent_DsrInactive) +
+ ((signals & SerialSignal_CTS) ? MgslEvent_CtsActive : MgslEvent_CtsInactive) +
+ ((signals & SerialSignal_DCD) ? MgslEvent_DcdActive : MgslEvent_DcdInactive) +
+ ((signals & SerialSignal_RI) ? MgslEvent_RiActive : MgslEvent_RiInactive);
+}
+
+/* wait for specified event to occur
+ */
+static int wait_mgsl_event(SLMP_INFO * info, int __user *mask_ptr)
+{
+ unsigned long flags;
+ int s;
+ int rc=0;
+ struct mgsl_icount cprev, cnow;
+ int events;
+ int mask;
+ struct _input_signal_events oldsigs, newsigs;
+ DECLARE_WAITQUEUE(wait, current);
+
+ COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+ if (rc) {
+ return -EFAULT;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s wait_mgsl_event(%d)\n",
+ __FILE__,__LINE__,info->device_name,mask);
+
+ spin_lock_irqsave(&info->lock,flags);
+
+ /* return immediately if state matches requested events */
+ get_signals(info);
+ s = map_status(info->serial_signals);
+
+ events = mask &
+ ( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+ ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+ ((s & SerialSignal_RI) ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+ if (events) {
+ spin_unlock_irqrestore(&info->lock,flags);
+ goto exit;
+ }
+
+ /* save current irq counts */
+ cprev = info->icount;
+ oldsigs = info->input_signal_events;
+
+ /* enable hunt and idle irqs if needed */
+ if (mask & (MgslEvent_ExitHuntMode+MgslEvent_IdleReceived)) {
+ unsigned char oldval = info->ie1_value;
+ unsigned char newval = oldval +
+ (mask & MgslEvent_ExitHuntMode ? FLGD:0) +
+ (mask & MgslEvent_IdleReceived ? IDLD:0);
+ if ( oldval != newval ) {
+ info->ie1_value = newval;
+ write_reg(info, IE1, info->ie1_value);
+ }
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&info->event_wait_q, &wait);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get current irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ newsigs = info->input_signal_events;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (newsigs.dsr_up == oldsigs.dsr_up &&
+ newsigs.dsr_down == oldsigs.dsr_down &&
+ newsigs.dcd_up == oldsigs.dcd_up &&
+ newsigs.dcd_down == oldsigs.dcd_down &&
+ newsigs.cts_up == oldsigs.cts_up &&
+ newsigs.cts_down == oldsigs.cts_down &&
+ newsigs.ri_up == oldsigs.ri_up &&
+ newsigs.ri_down == oldsigs.ri_down &&
+ cnow.exithunt == cprev.exithunt &&
+ cnow.rxidle == cprev.rxidle) {
+ rc = -EIO;
+ break;
+ }
+
+ events = mask &
+ ( (newsigs.dsr_up != oldsigs.dsr_up ? MgslEvent_DsrActive:0) +
+ (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+ (newsigs.dcd_up != oldsigs.dcd_up ? MgslEvent_DcdActive:0) +
+ (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+ (newsigs.cts_up != oldsigs.cts_up ? MgslEvent_CtsActive:0) +
+ (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+ (newsigs.ri_up != oldsigs.ri_up ? MgslEvent_RiActive:0) +
+ (newsigs.ri_down != oldsigs.ri_down ? MgslEvent_RiInactive:0) +
+ (cnow.exithunt != cprev.exithunt ? MgslEvent_ExitHuntMode:0) +
+ (cnow.rxidle != cprev.rxidle ? MgslEvent_IdleReceived:0) );
+ if (events)
+ break;
+
+ cprev = cnow;
+ oldsigs = newsigs;
+ }
+
+ remove_wait_queue(&info->event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+
+
+ if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+ spin_lock_irqsave(&info->lock,flags);
+ if (!waitqueue_active(&info->event_wait_q)) {
+ /* disable enable exit hunt mode/idle rcvd IRQs */
+ info->ie1_value &= ~(FLGD|IDLD);
+ write_reg(info, IE1, info->ie1_value);
+ }
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+exit:
+ if ( rc == 0 )
+ PUT_USER(rc, events, mask_ptr);
+
+ return rc;
+}
+
+static int modem_input_wait(SLMP_INFO *info,int arg)
+{
+ unsigned long flags;
+ int rc;
+ struct mgsl_icount cprev, cnow;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /* save current irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cprev = info->icount;
+ add_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ for(;;) {
+ schedule();
+ if (signal_pending(current)) {
+ rc = -ERESTARTSYS;
+ break;
+ }
+
+ /* get new irq counts */
+ spin_lock_irqsave(&info->lock,flags);
+ cnow = info->icount;
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* if no change, wait aborted for some reason */
+ if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+ cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+ rc = -EIO;
+ break;
+ }
+
+ /* check for change in caller specified modem input */
+ if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+ (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+ (arg & TIOCM_CD && cnow.dcd != cprev.dcd) ||
+ (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+ rc = 0;
+ break;
+ }
+
+ cprev = cnow;
+ }
+ remove_wait_queue(&info->status_event_wait_q, &wait);
+ set_current_state(TASK_RUNNING);
+ return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned int result;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+ ((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+ ((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+ ((info->serial_signals & SerialSignal_RI) ? TIOCM_RNG:0) +
+ ((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+ ((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmget() value=%08X\n",
+ __FILE__,__LINE__, info->device_name, result );
+ return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+ unsigned int set, unsigned int clear)
+{
+ SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s tiocmset(%x,%x)\n",
+ __FILE__,__LINE__,info->device_name, set, clear);
+
+ if (set & TIOCM_RTS)
+ info->serial_signals |= SerialSignal_RTS;
+ if (set & TIOCM_DTR)
+ info->serial_signals |= SerialSignal_DTR;
+ if (clear & TIOCM_RTS)
+ info->serial_signals &= ~SerialSignal_RTS;
+ if (clear & TIOCM_DTR)
+ info->serial_signals &= ~SerialSignal_DTR;
+
+ spin_lock_irqsave(&info->lock,flags);
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return 0;
+}
+
+
+
+/* Block the current process until the specified port is ready to open.
+ */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+ SLMP_INFO *info)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s block_til_ready()\n",
+ __FILE__,__LINE__, tty->driver->name );
+
+ if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+ /* nonblock mode is set or port is not enabled */
+ /* just verify that callout device is not active */
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+ return 0;
+ }
+
+ if (tty->termios->c_cflag & CLOCAL)
+ do_clocal = 1;
+
+ /* Wait for carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, info->count is dropped by one, so that
+ * close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+
+ retval = 0;
+ add_wait_queue(&info->open_wait, &wait);
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s block_til_ready() before block, count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ spin_lock_irqsave(&info->lock, flags);
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ info->count--;
+ }
+ spin_unlock_irqrestore(&info->lock, flags);
+ info->blocked_open++;
+
+ while (1) {
+ if ((tty->termios->c_cflag & CBAUD)) {
+ spin_lock_irqsave(&info->lock,flags);
+ info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+ set_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+ retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS;
+ break;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ if (!(info->flags & ASYNC_CLOSING) &&
+ (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ break;
+ }
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s block_til_ready() count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ schedule();
+ }
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&info->open_wait, &wait);
+
+ if (extra_count)
+ info->count++;
+ info->blocked_open--;
+
+ if (debug_level >= DEBUG_LEVEL_INFO)
+ printk("%s(%d):%s block_til_ready() after, count=%d\n",
+ __FILE__,__LINE__, tty->driver->name, info->count );
+
+ if (!retval)
+ info->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return retval;
+}
+
+int alloc_dma_bufs(SLMP_INFO *info)
+{
+ unsigned short BuffersPerFrame;
+ unsigned short BufferCount;
+
+ // Force allocation to start at 64K boundary for each port.
+ // This is necessary because *all* buffer descriptors for a port
+ // *must* be in the same 64K block. All descriptors on a port
+ // share a common 'base' address (upper 8 bits of 24 bits) programmed
+ // into the CBP register.
+ info->port_array[0]->last_mem_alloc = (SCA_MEM_SIZE/4) * info->port_num;
+
+ /* Calculate the number of DMA buffers necessary to hold the */
+ /* largest allowable frame size. Note: If the max frame size is */
+ /* not an even multiple of the DMA buffer size then we need to */
+ /* round the buffer count per frame up one. */
+
+ BuffersPerFrame = (unsigned short)(info->max_frame_size/SCABUFSIZE);
+ if ( info->max_frame_size % SCABUFSIZE )
+ BuffersPerFrame++;
+
+ /* calculate total number of data buffers (SCABUFSIZE) possible
+ * in one ports memory (SCA_MEM_SIZE/4) after allocating memory
+ * for the descriptor list (BUFFERLISTSIZE).
+ */
+ BufferCount = (SCA_MEM_SIZE/4 - BUFFERLISTSIZE)/SCABUFSIZE;
+
+ /* limit number of buffers to maximum amount of descriptors */
+ if (BufferCount > BUFFERLISTSIZE/sizeof(SCADESC))
+ BufferCount = BUFFERLISTSIZE/sizeof(SCADESC);
+
+ /* use enough buffers to transmit one max size frame */
+ info->tx_buf_count = BuffersPerFrame + 1;
+
+ /* never use more than half the available buffers for transmit */
+ if (info->tx_buf_count > (BufferCount/2))
+ info->tx_buf_count = BufferCount/2;
+
+ if (info->tx_buf_count > SCAMAXDESC)
+ info->tx_buf_count = SCAMAXDESC;
+
+ /* use remaining buffers for receive */
+ info->rx_buf_count = BufferCount - info->tx_buf_count;
+
+ if (info->rx_buf_count > SCAMAXDESC)
+ info->rx_buf_count = SCAMAXDESC;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk("%s(%d):%s Allocating %d TX and %d RX DMA buffers.\n",
+ __FILE__,__LINE__, info->device_name,
+ info->tx_buf_count,info->rx_buf_count);
+
+ if ( alloc_buf_list( info ) < 0 ||
+ alloc_frame_bufs(info,
+ info->rx_buf_list,
+ info->rx_buf_list_ex,
+ info->rx_buf_count) < 0 ||
+ alloc_frame_bufs(info,
+ info->tx_buf_list,
+ info->tx_buf_list_ex,
+ info->tx_buf_count) < 0 ||
+ alloc_tmp_rx_buf(info) < 0 ) {
+ printk("%s(%d):%s Can't allocate DMA buffer memory\n",
+ __FILE__,__LINE__, info->device_name);
+ return -ENOMEM;
+ }
+
+ rx_reset_buffers( info );
+
+ return 0;
+}
+
+/* Allocate DMA buffers for the transmit and receive descriptor lists.
+ */
+int alloc_buf_list(SLMP_INFO *info)
+{
+ unsigned int i;
+
+ /* build list in adapter shared memory */
+ info->buffer_list = info->memory_base + info->port_array[0]->last_mem_alloc;
+ info->buffer_list_phys = info->port_array[0]->last_mem_alloc;
+ info->port_array[0]->last_mem_alloc += BUFFERLISTSIZE;
+
+ memset(info->buffer_list, 0, BUFFERLISTSIZE);
+
+ /* Save virtual address pointers to the receive and */
+ /* transmit buffer lists. (Receive 1st). These pointers will */
+ /* be used by the processor to access the lists. */
+ info->rx_buf_list = (SCADESC *)info->buffer_list;
+
+ info->tx_buf_list = (SCADESC *)info->buffer_list;
+ info->tx_buf_list += info->rx_buf_count;
+
+ /* Build links for circular buffer entry lists (tx and rx)
+ *
+ * Note: links are physical addresses read by the SCA device
+ * to determine the next buffer entry to use.
+ */
+
+ for ( i = 0; i < info->rx_buf_count; i++ ) {
+ /* calculate and store physical address of this buffer entry */
+ info->rx_buf_list_ex[i].phys_entry =
+ info->buffer_list_phys + (i * sizeof(SCABUFSIZE));
+
+ /* calculate and store physical address of */
+ /* next entry in cirular list of entries */
+ info->rx_buf_list[i].next = info->buffer_list_phys;
+ if ( i < info->rx_buf_count - 1 )
+ info->rx_buf_list[i].next += (i + 1) * sizeof(SCADESC);
+
+ info->rx_buf_list[i].length = SCABUFSIZE;
+ }
+
+ for ( i = 0; i < info->tx_buf_count; i++ ) {
+ /* calculate and store physical address of this buffer entry */
+ info->tx_buf_list_ex[i].phys_entry = info->buffer_list_phys +
+ ((info->rx_buf_count + i) * sizeof(SCADESC));
+
+ /* calculate and store physical address of */
+ /* next entry in cirular list of entries */
+
+ info->tx_buf_list[i].next = info->buffer_list_phys +
+ info->rx_buf_count * sizeof(SCADESC);
+
+ if ( i < info->tx_buf_count - 1 )
+ info->tx_buf_list[i].next += (i + 1) * sizeof(SCADESC);
+ }
+
+ return 0;
+}
+
+/* Allocate the frame DMA buffers used by the specified buffer list.
+ */
+int alloc_frame_bufs(SLMP_INFO *info, SCADESC *buf_list,SCADESC_EX *buf_list_ex,int count)
+{
+ int i;
+ unsigned long phys_addr;
+
+ for ( i = 0; i < count; i++ ) {
+ buf_list_ex[i].virt_addr = info->memory_base + info->port_array[0]->last_mem_alloc;
+ phys_addr = info->port_array[0]->last_mem_alloc;
+ info->port_array[0]->last_mem_alloc += SCABUFSIZE;
+
+ buf_list[i].buf_ptr = (unsigned short)phys_addr;
+ buf_list[i].buf_base = (unsigned char)(phys_addr >> 16);
+ }
+
+ return 0;
+}
+
+void free_dma_bufs(SLMP_INFO *info)
+{
+ info->buffer_list = NULL;
+ info->rx_buf_list = NULL;
+ info->tx_buf_list = NULL;
+}
+
+/* allocate buffer large enough to hold max_frame_size.
+ * This buffer is used to pass an assembled frame to the line discipline.
+ */
+int alloc_tmp_rx_buf(SLMP_INFO *info)
+{
+ info->tmp_rx_buf = kmalloc(info->max_frame_size, GFP_KERNEL);
+ if (info->tmp_rx_buf == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+void free_tmp_rx_buf(SLMP_INFO *info)
+{
+ if (info->tmp_rx_buf)
+ kfree(info->tmp_rx_buf);
+ info->tmp_rx_buf = NULL;
+}
+
+int claim_resources(SLMP_INFO *info)
+{
+ if (request_mem_region(info->phys_memory_base,SCA_MEM_SIZE,"synclinkmp") == NULL) {
+ printk( "%s(%d):%s mem addr conflict, Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base);
+ info->init_error = DiagStatus_AddressConflict;
+ goto errout;
+ }
+ else
+ info->shared_mem_requested = 1;
+
+ if (request_mem_region(info->phys_lcr_base + info->lcr_offset,128,"synclinkmp") == NULL) {
+ printk( "%s(%d):%s lcr mem addr conflict, Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_lcr_base);
+ info->init_error = DiagStatus_AddressConflict;
+ goto errout;
+ }
+ else
+ info->lcr_mem_requested = 1;
+
+ if (request_mem_region(info->phys_sca_base + info->sca_offset,SCA_BASE_SIZE,"synclinkmp") == NULL) {
+ printk( "%s(%d):%s sca mem addr conflict, Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_sca_base);
+ info->init_error = DiagStatus_AddressConflict;
+ goto errout;
+ }
+ else
+ info->sca_base_requested = 1;
+
+ if (request_mem_region(info->phys_statctrl_base + info->statctrl_offset,SCA_REG_SIZE,"synclinkmp") == NULL) {
+ printk( "%s(%d):%s stat/ctrl mem addr conflict, Addr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_statctrl_base);
+ info->init_error = DiagStatus_AddressConflict;
+ goto errout;
+ }
+ else
+ info->sca_statctrl_requested = 1;
+
+ info->memory_base = ioremap(info->phys_memory_base,SCA_MEM_SIZE);
+ if (!info->memory_base) {
+ printk( "%s(%d):%s Cant map shared memory, MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base );
+ info->init_error = DiagStatus_CantAssignPciResources;
+ goto errout;
+ }
+
+ info->lcr_base = ioremap(info->phys_lcr_base,PAGE_SIZE);
+ if (!info->lcr_base) {
+ printk( "%s(%d):%s Cant map LCR memory, MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_lcr_base );
+ info->init_error = DiagStatus_CantAssignPciResources;
+ goto errout;
+ }
+ info->lcr_base += info->lcr_offset;
+
+ info->sca_base = ioremap(info->phys_sca_base,PAGE_SIZE);
+ if (!info->sca_base) {
+ printk( "%s(%d):%s Cant map SCA memory, MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_sca_base );
+ info->init_error = DiagStatus_CantAssignPciResources;
+ goto errout;
+ }
+ info->sca_base += info->sca_offset;
+
+ info->statctrl_base = ioremap(info->phys_statctrl_base,PAGE_SIZE);
+ if (!info->statctrl_base) {
+ printk( "%s(%d):%s Cant map SCA Status/Control memory, MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_statctrl_base );
+ info->init_error = DiagStatus_CantAssignPciResources;
+ goto errout;
+ }
+ info->statctrl_base += info->statctrl_offset;
+
+ if ( !memory_test(info) ) {
+ printk( "%s(%d):Shared Memory Test failed for device %s MemAddr=%08X\n",
+ __FILE__,__LINE__,info->device_name, info->phys_memory_base );
+ info->init_error = DiagStatus_MemoryError;
+ goto errout;
+ }
+
+ return 0;
+
+errout:
+ release_resources( info );
+ return -ENODEV;
+}
+
+void release_resources(SLMP_INFO *info)
+{
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s release_resources() entry\n",
+ __FILE__,__LINE__,info->device_name );
+
+ if ( info->irq_requested ) {
+ free_irq(info->irq_level, info);
+ info->irq_requested = 0;
+ }
+
+ if ( info->shared_mem_requested ) {
+ release_mem_region(info->phys_memory_base,SCA_MEM_SIZE);
+ info->shared_mem_requested = 0;
+ }
+ if ( info->lcr_mem_requested ) {
+ release_mem_region(info->phys_lcr_base + info->lcr_offset,128);
+ info->lcr_mem_requested = 0;
+ }
+ if ( info->sca_base_requested ) {
+ release_mem_region(info->phys_sca_base + info->sca_offset,SCA_BASE_SIZE);
+ info->sca_base_requested = 0;
+ }
+ if ( info->sca_statctrl_requested ) {
+ release_mem_region(info->phys_statctrl_base + info->statctrl_offset,SCA_REG_SIZE);
+ info->sca_statctrl_requested = 0;
+ }
+
+ if (info->memory_base){
+ iounmap(info->memory_base);
+ info->memory_base = NULL;
+ }
+
+ if (info->sca_base) {
+ iounmap(info->sca_base - info->sca_offset);
+ info->sca_base=NULL;
+ }
+
+ if (info->statctrl_base) {
+ iounmap(info->statctrl_base - info->statctrl_offset);
+ info->statctrl_base=NULL;
+ }
+
+ if (info->lcr_base){
+ iounmap(info->lcr_base - info->lcr_offset);
+ info->lcr_base = NULL;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s release_resources() exit\n",
+ __FILE__,__LINE__,info->device_name );
+}
+
+/* Add the specified device instance data structure to the
+ * global linked list of devices and increment the device count.
+ */
+void add_device(SLMP_INFO *info)
+{
+ info->next_device = NULL;
+ info->line = synclinkmp_device_count;
+ sprintf(info->device_name,"ttySLM%dp%d",info->adapter_num,info->port_num);
+
+ if (info->line < MAX_DEVICES) {
+ if (maxframe[info->line])
+ info->max_frame_size = maxframe[info->line];
+ info->dosyncppp = dosyncppp[info->line];
+ }
+
+ synclinkmp_device_count++;
+
+ if ( !synclinkmp_device_list )
+ synclinkmp_device_list = info;
+ else {
+ SLMP_INFO *current_dev = synclinkmp_device_list;
+ while( current_dev->next_device )
+ current_dev = current_dev->next_device;
+ current_dev->next_device = info;
+ }
+
+ if ( info->max_frame_size < 4096 )
+ info->max_frame_size = 4096;
+ else if ( info->max_frame_size > 65535 )
+ info->max_frame_size = 65535;
+
+ printk( "SyncLink MultiPort %s: "
+ "Mem=(%08x %08X %08x %08X) IRQ=%d MaxFrameSize=%u\n",
+ info->device_name,
+ info->phys_sca_base,
+ info->phys_memory_base,
+ info->phys_statctrl_base,
+ info->phys_lcr_base,
+ info->irq_level,
+ info->max_frame_size );
+
+#ifdef CONFIG_HDLC
+ hdlcdev_init(info);
+#endif
+}
+
+/* Allocate and initialize a device instance structure
+ *
+ * Return Value: pointer to SLMP_INFO if success, otherwise NULL
+ */
+static SLMP_INFO *alloc_dev(int adapter_num, int port_num, struct pci_dev *pdev)
+{
+ SLMP_INFO *info;
+
+ info = (SLMP_INFO *)kmalloc(sizeof(SLMP_INFO),
+ GFP_KERNEL);
+
+ if (!info) {
+ printk("%s(%d) Error can't allocate device instance data for adapter %d, port %d\n",
+ __FILE__,__LINE__, adapter_num, port_num);
+ } else {
+ memset(info, 0, sizeof(SLMP_INFO));
+ info->magic = MGSL_MAGIC;
+ INIT_WORK(&info->task, bh_handler, info);
+ info->max_frame_size = 4096;
+ info->close_delay = 5*HZ/10;
+ info->closing_wait = 30*HZ;
+ init_waitqueue_head(&info->open_wait);
+ init_waitqueue_head(&info->close_wait);
+ init_waitqueue_head(&info->status_event_wait_q);
+ init_waitqueue_head(&info->event_wait_q);
+ spin_lock_init(&info->netlock);
+ memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+ info->idle_mode = HDLC_TXIDLE_FLAGS;
+ info->adapter_num = adapter_num;
+ info->port_num = port_num;
+
+ /* Copy configuration info to device instance data */
+ info->irq_level = pdev->irq;
+ info->phys_lcr_base = pci_resource_start(pdev,0);
+ info->phys_sca_base = pci_resource_start(pdev,2);
+ info->phys_memory_base = pci_resource_start(pdev,3);
+ info->phys_statctrl_base = pci_resource_start(pdev,4);
+
+ /* Because veremap only works on page boundaries we must map
+ * a larger area than is actually implemented for the LCR
+ * memory range. We map a full page starting at the page boundary.
+ */
+ info->lcr_offset = info->phys_lcr_base & (PAGE_SIZE-1);
+ info->phys_lcr_base &= ~(PAGE_SIZE-1);
+
+ info->sca_offset = info->phys_sca_base & (PAGE_SIZE-1);
+ info->phys_sca_base &= ~(PAGE_SIZE-1);
+
+ info->statctrl_offset = info->phys_statctrl_base & (PAGE_SIZE-1);
+ info->phys_statctrl_base &= ~(PAGE_SIZE-1);
+
+ info->bus_type = MGSL_BUS_TYPE_PCI;
+ info->irq_flags = SA_SHIRQ;
+
+ init_timer(&info->tx_timer);
+ info->tx_timer.data = (unsigned long)info;
+ info->tx_timer.function = tx_timeout;
+
+ init_timer(&info->status_timer);
+ info->status_timer.data = (unsigned long)info;
+ info->status_timer.function = status_timeout;
+
+ /* Store the PCI9050 misc control register value because a flaw
+ * in the PCI9050 prevents LCR registers from being read if
+ * BIOS assigns an LCR base address with bit 7 set.
+ *
+ * Only the misc control register is accessed for which only
+ * write access is needed, so set an initial value and change
+ * bits to the device instance data as we write the value
+ * to the actual misc control register.
+ */
+ info->misc_ctrl_value = 0x087e4546;
+
+ /* initial port state is unknown - if startup errors
+ * occur, init_error will be set to indicate the
+ * problem. Once the port is fully initialized,
+ * this value will be set to 0 to indicate the
+ * port is available.
+ */
+ info->init_error = -1;
+ }
+
+ return info;
+}
+
+void device_init(int adapter_num, struct pci_dev *pdev)
+{
+ SLMP_INFO *port_array[SCA_MAX_PORTS];
+ int port;
+
+ /* allocate device instances for up to SCA_MAX_PORTS devices */
+ for ( port = 0; port < SCA_MAX_PORTS; ++port ) {
+ port_array[port] = alloc_dev(adapter_num,port,pdev);
+ if( port_array[port] == NULL ) {
+ for ( --port; port >= 0; --port )
+ kfree(port_array[port]);
+ return;
+ }
+ }
+
+ /* give copy of port_array to all ports and add to device list */
+ for ( port = 0; port < SCA_MAX_PORTS; ++port ) {
+ memcpy(port_array[port]->port_array,port_array,sizeof(port_array));
+ add_device( port_array[port] );
+ spin_lock_init(&port_array[port]->lock);
+ }
+
+ /* Allocate and claim adapter resources */
+ if ( !claim_resources(port_array[0]) ) {
+
+ alloc_dma_bufs(port_array[0]);
+
+ /* copy resource information from first port to others */
+ for ( port = 1; port < SCA_MAX_PORTS; ++port ) {
+ port_array[port]->lock = port_array[0]->lock;
+ port_array[port]->irq_level = port_array[0]->irq_level;
+ port_array[port]->memory_base = port_array[0]->memory_base;
+ port_array[port]->sca_base = port_array[0]->sca_base;
+ port_array[port]->statctrl_base = port_array[0]->statctrl_base;
+ port_array[port]->lcr_base = port_array[0]->lcr_base;
+ alloc_dma_bufs(port_array[port]);
+ }
+
+ if ( request_irq(port_array[0]->irq_level,
+ synclinkmp_interrupt,
+ port_array[0]->irq_flags,
+ port_array[0]->device_name,
+ port_array[0]) < 0 ) {
+ printk( "%s(%d):%s Cant request interrupt, IRQ=%d\n",
+ __FILE__,__LINE__,
+ port_array[0]->device_name,
+ port_array[0]->irq_level );
+ }
+ else {
+ port_array[0]->irq_requested = 1;
+ adapter_test(port_array[0]);
+ }
+ }
+}
+
+static struct tty_operations ops = {
+ .open = open,
+ .close = close,
+ .write = write,
+ .put_char = put_char,
+ .flush_chars = flush_chars,
+ .write_room = write_room,
+ .chars_in_buffer = chars_in_buffer,
+ .flush_buffer = flush_buffer,
+ .ioctl = ioctl,
+ .throttle = throttle,
+ .unthrottle = unthrottle,
+ .send_xchar = send_xchar,
+ .break_ctl = set_break,
+ .wait_until_sent = wait_until_sent,
+ .read_proc = read_proc,
+ .set_termios = set_termios,
+ .stop = tx_hold,
+ .start = tx_release,
+ .hangup = hangup,
+ .tiocmget = tiocmget,
+ .tiocmset = tiocmset,
+};
+
+static void synclinkmp_cleanup(void)
+{
+ int rc;
+ SLMP_INFO *info;
+ SLMP_INFO *tmp;
+
+ printk("Unloading %s %s\n", driver_name, driver_version);
+
+ if (serial_driver) {
+ if ((rc = tty_unregister_driver(serial_driver)))
+ printk("%s(%d) failed to unregister tty driver err=%d\n",
+ __FILE__,__LINE__,rc);
+ put_tty_driver(serial_driver);
+ }
+
+ /* reset devices */
+ info = synclinkmp_device_list;
+ while(info) {
+ reset_port(info);
+ info = info->next_device;
+ }
+
+ /* release devices */
+ info = synclinkmp_device_list;
+ while(info) {
+#ifdef CONFIG_HDLC
+ hdlcdev_exit(info);
+#endif
+ free_dma_bufs(info);
+ free_tmp_rx_buf(info);
+ if ( info->port_num == 0 ) {
+ if (info->sca_base)
+ write_reg(info, LPR, 1); /* set low power mode */
+ release_resources(info);
+ }
+ tmp = info;
+ info = info->next_device;
+ kfree(tmp);
+ }
+
+ pci_unregister_driver(&synclinkmp_pci_driver);
+}
+
+/* Driver initialization entry point.
+ */
+
+static int __init synclinkmp_init(void)
+{
+ int rc;
+
+ if (break_on_load) {
+ synclinkmp_get_text_ptr();
+ BREAKPOINT();
+ }
+
+ printk("%s %s\n", driver_name, driver_version);
+
+ if ((rc = pci_register_driver(&synclinkmp_pci_driver)) < 0) {
+ printk("%s:failed to register PCI driver, error=%d\n",__FILE__,rc);
+ return rc;
+ }
+
+ serial_driver = alloc_tty_driver(128);
+ if (!serial_driver) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ /* Initialize the tty_driver structure */
+
+ serial_driver->owner = THIS_MODULE;
+ serial_driver->driver_name = "synclinkmp";
+ serial_driver->name = "ttySLM";
+ serial_driver->major = ttymajor;
+ serial_driver->minor_start = 64;
+ serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ serial_driver->subtype = SERIAL_TYPE_NORMAL;
+ serial_driver->init_termios = tty_std_termios;
+ serial_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ serial_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(serial_driver, &ops);
+ if ((rc = tty_register_driver(serial_driver)) < 0) {
+ printk("%s(%d):Couldn't register serial driver\n",
+ __FILE__,__LINE__);
+ put_tty_driver(serial_driver);
+ serial_driver = NULL;
+ goto error;
+ }
+
+ printk("%s %s, tty major#%d\n",
+ driver_name, driver_version,
+ serial_driver->major);
+
+ return 0;
+
+error:
+ synclinkmp_cleanup();
+ return rc;
+}
+
+static void __exit synclinkmp_exit(void)
+{
+ synclinkmp_cleanup();
+}
+
+module_init(synclinkmp_init);
+module_exit(synclinkmp_exit);
+
+/* Set the port for internal loopback mode.
+ * The TxCLK and RxCLK signals are generated from the BRG and
+ * the TxD is looped back to the RxD internally.
+ */
+void enable_loopback(SLMP_INFO *info, int enable)
+{
+ if (enable) {
+ /* MD2 (Mode Register 2)
+ * 01..00 CNCT<1..0> Channel Connection 11=Local Loopback
+ */
+ write_reg(info, MD2, (unsigned char)(read_reg(info, MD2) | (BIT1 + BIT0)));
+
+ /* degate external TxC clock source */
+ info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+ write_control_reg(info);
+
+ /* RXS/TXS (Rx/Tx clock source)
+ * 07 Reserved, must be 0
+ * 06..04 Clock Source, 100=BRG
+ * 03..00 Clock Divisor, 0000=1
+ */
+ write_reg(info, RXS, 0x40);
+ write_reg(info, TXS, 0x40);
+
+ } else {
+ /* MD2 (Mode Register 2)
+ * 01..00 CNCT<1..0> Channel connection, 0=normal
+ */
+ write_reg(info, MD2, (unsigned char)(read_reg(info, MD2) & ~(BIT1 + BIT0)));
+
+ /* RXS/TXS (Rx/Tx clock source)
+ * 07 Reserved, must be 0
+ * 06..04 Clock Source, 000=RxC/TxC Pin
+ * 03..00 Clock Divisor, 0000=1
+ */
+ write_reg(info, RXS, 0x00);
+ write_reg(info, TXS, 0x00);
+ }
+
+ /* set LinkSpeed if available, otherwise default to 2Mbps */
+ if (info->params.clock_speed)
+ set_rate(info, info->params.clock_speed);
+ else
+ set_rate(info, 3686400);
+}
+
+/* Set the baud rate register to the desired speed
+ *
+ * data_rate data rate of clock in bits per second
+ * A data rate of 0 disables the AUX clock.
+ */
+void set_rate( SLMP_INFO *info, u32 data_rate )
+{
+ u32 TMCValue;
+ unsigned char BRValue;
+ u32 Divisor=0;
+
+ /* fBRG = fCLK/(TMC * 2^BR)
+ */
+ if (data_rate != 0) {
+ Divisor = 14745600/data_rate;
+ if (!Divisor)
+ Divisor = 1;
+
+ TMCValue = Divisor;
+
+ BRValue = 0;
+ if (TMCValue != 1 && TMCValue != 2) {
+ /* BRValue of 0 provides 50/50 duty cycle *only* when
+ * TMCValue is 1 or 2. BRValue of 1 to 9 always provides
+ * 50/50 duty cycle.
+ */
+ BRValue = 1;
+ TMCValue >>= 1;
+ }
+
+ /* while TMCValue is too big for TMC register, divide
+ * by 2 and increment BR exponent.
+ */
+ for(; TMCValue > 256 && BRValue < 10; BRValue++)
+ TMCValue >>= 1;
+
+ write_reg(info, TXS,
+ (unsigned char)((read_reg(info, TXS) & 0xf0) | BRValue));
+ write_reg(info, RXS,
+ (unsigned char)((read_reg(info, RXS) & 0xf0) | BRValue));
+ write_reg(info, TMC, (unsigned char)TMCValue);
+ }
+ else {
+ write_reg(info, TXS,0);
+ write_reg(info, RXS,0);
+ write_reg(info, TMC, 0);
+ }
+}
+
+/* Disable receiver
+ */
+void rx_stop(SLMP_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):%s rx_stop()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ write_reg(info, CMD, RXRESET);
+
+ info->ie0_value &= ~RXRDYE;
+ write_reg(info, IE0, info->ie0_value); /* disable Rx data interrupts */
+
+ write_reg(info, RXDMA + DSR, 0); /* disable Rx DMA */
+ write_reg(info, RXDMA + DCMD, SWABORT); /* reset/init Rx DMA */
+ write_reg(info, RXDMA + DIR, 0); /* disable Rx DMA interrupts */
+
+ info->rx_enabled = 0;
+ info->rx_overflow = 0;
+}
+
+/* enable the receiver
+ */
+void rx_start(SLMP_INFO *info)
+{
+ int i;
+
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):%s rx_start()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ write_reg(info, CMD, RXRESET);
+
+ if ( info->params.mode == MGSL_MODE_HDLC ) {
+ /* HDLC, disabe IRQ on rxdata */
+ info->ie0_value &= ~RXRDYE;
+ write_reg(info, IE0, info->ie0_value);
+
+ /* Reset all Rx DMA buffers and program rx dma */
+ write_reg(info, RXDMA + DSR, 0); /* disable Rx DMA */
+ write_reg(info, RXDMA + DCMD, SWABORT); /* reset/init Rx DMA */
+
+ for (i = 0; i < info->rx_buf_count; i++) {
+ info->rx_buf_list[i].status = 0xff;
+
+ // throttle to 4 shared memory writes at a time to prevent
+ // hogging local bus (keep latency time for DMA requests low).
+ if (!(i % 4))
+ read_status_reg(info);
+ }
+ info->current_rx_buf = 0;
+
+ /* set current/1st descriptor address */
+ write_reg16(info, RXDMA + CDA,
+ info->rx_buf_list_ex[0].phys_entry);
+
+ /* set new last rx descriptor address */
+ write_reg16(info, RXDMA + EDA,
+ info->rx_buf_list_ex[info->rx_buf_count - 1].phys_entry);
+
+ /* set buffer length (shared by all rx dma data buffers) */
+ write_reg16(info, RXDMA + BFL, SCABUFSIZE);
+
+ write_reg(info, RXDMA + DIR, 0x60); /* enable Rx DMA interrupts (EOM/BOF) */
+ write_reg(info, RXDMA + DSR, 0xf2); /* clear Rx DMA IRQs, enable Rx DMA */
+ } else {
+ /* async, enable IRQ on rxdata */
+ info->ie0_value |= RXRDYE;
+ write_reg(info, IE0, info->ie0_value);
+ }
+
+ write_reg(info, CMD, RXENABLE);
+
+ info->rx_overflow = FALSE;
+ info->rx_enabled = 1;
+}
+
+/* Enable the transmitter and send a transmit frame if
+ * one is loaded in the DMA buffers.
+ */
+void tx_start(SLMP_INFO *info)
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):%s tx_start() tx_count=%d\n",
+ __FILE__,__LINE__, info->device_name,info->tx_count );
+
+ if (!info->tx_enabled ) {
+ write_reg(info, CMD, TXRESET);
+ write_reg(info, CMD, TXENABLE);
+ info->tx_enabled = TRUE;
+ }
+
+ if ( info->tx_count ) {
+
+ /* If auto RTS enabled and RTS is inactive, then assert */
+ /* RTS and set a flag indicating that the driver should */
+ /* negate RTS when the transmission completes. */
+
+ info->drop_rts_on_tx_done = 0;
+
+ if (info->params.mode != MGSL_MODE_ASYNC) {
+
+ if ( info->params.flags & HDLC_FLAG_AUTO_RTS ) {
+ get_signals( info );
+ if ( !(info->serial_signals & SerialSignal_RTS) ) {
+ info->serial_signals |= SerialSignal_RTS;
+ set_signals( info );
+ info->drop_rts_on_tx_done = 1;
+ }
+ }
+
+ write_reg16(info, TRC0,
+ (unsigned short)(((tx_negate_fifo_level-1)<<8) + tx_active_fifo_level));
+
+ write_reg(info, TXDMA + DSR, 0); /* disable DMA channel */
+ write_reg(info, TXDMA + DCMD, SWABORT); /* reset/init DMA channel */
+
+ /* set TX CDA (current descriptor address) */
+ write_reg16(info, TXDMA + CDA,
+ info->tx_buf_list_ex[0].phys_entry);
+
+ /* set TX EDA (last descriptor address) */
+ write_reg16(info, TXDMA + EDA,
+ info->tx_buf_list_ex[info->last_tx_buf].phys_entry);
+
+ /* enable underrun IRQ */
+ info->ie1_value &= ~IDLE;
+ info->ie1_value |= UDRN;
+ write_reg(info, IE1, info->ie1_value);
+ write_reg(info, SR1, (unsigned char)(IDLE + UDRN));
+
+ write_reg(info, TXDMA + DIR, 0x40); /* enable Tx DMA interrupts (EOM) */
+ write_reg(info, TXDMA + DSR, 0xf2); /* clear Tx DMA IRQs, enable Tx DMA */
+
+ info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+ add_timer(&info->tx_timer);
+ }
+ else {
+ tx_load_fifo(info);
+ /* async, enable IRQ on txdata */
+ info->ie0_value |= TXRDYE;
+ write_reg(info, IE0, info->ie0_value);
+ }
+
+ info->tx_active = 1;
+ }
+}
+
+/* stop the transmitter and DMA
+ */
+void tx_stop( SLMP_INFO *info )
+{
+ if (debug_level >= DEBUG_LEVEL_ISR)
+ printk("%s(%d):%s tx_stop()\n",
+ __FILE__,__LINE__, info->device_name );
+
+ del_timer(&info->tx_timer);
+
+ write_reg(info, TXDMA + DSR, 0); /* disable DMA channel */
+ write_reg(info, TXDMA + DCMD, SWABORT); /* reset/init DMA channel */
+
+ write_reg(info, CMD, TXRESET);
+
+ info->ie1_value &= ~(UDRN + IDLE);
+ write_reg(info, IE1, info->ie1_value); /* disable tx status interrupts */
+ write_reg(info, SR1, (unsigned char)(IDLE + UDRN)); /* clear pending */
+
+ info->ie0_value &= ~TXRDYE;
+ write_reg(info, IE0, info->ie0_value); /* disable tx data interrupts */
+
+ info->tx_enabled = 0;
+ info->tx_active = 0;
+}
+
+/* Fill the transmit FIFO until the FIFO is full or
+ * there is no more data to load.
+ */
+void tx_load_fifo(SLMP_INFO *info)
+{
+ u8 TwoBytes[2];
+
+ /* do nothing is now tx data available and no XON/XOFF pending */
+
+ if ( !info->tx_count && !info->x_char )
+ return;
+
+ /* load the Transmit FIFO until FIFOs full or all data sent */
+
+ while( info->tx_count && (read_reg(info,SR0) & BIT1) ) {
+
+ /* there is more space in the transmit FIFO and */
+ /* there is more data in transmit buffer */
+
+ if ( (info->tx_count > 1) && !info->x_char ) {
+ /* write 16-bits */
+ TwoBytes[0] = info->tx_buf[info->tx_get++];
+ if (info->tx_get >= info->max_frame_size)
+ info->tx_get -= info->max_frame_size;
+ TwoBytes[1] = info->tx_buf[info->tx_get++];
+ if (info->tx_get >= info->max_frame_size)
+ info->tx_get -= info->max_frame_size;
+
+ write_reg16(info, TRB, *((u16 *)TwoBytes));
+
+ info->tx_count -= 2;
+ info->icount.tx += 2;
+ } else {
+ /* only 1 byte left to transmit or 1 FIFO slot left */
+
+ if (info->x_char) {
+ /* transmit pending high priority char */
+ write_reg(info, TRB, info->x_char);
+ info->x_char = 0;
+ } else {
+ write_reg(info, TRB, info->tx_buf[info->tx_get++]);
+ if (info->tx_get >= info->max_frame_size)
+ info->tx_get -= info->max_frame_size;
+ info->tx_count--;
+ }
+ info->icount.tx++;
+ }
+ }
+}
+
+/* Reset a port to a known state
+ */
+void reset_port(SLMP_INFO *info)
+{
+ if (info->sca_base) {
+
+ tx_stop(info);
+ rx_stop(info);
+
+ info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+ set_signals(info);
+
+ /* disable all port interrupts */
+ info->ie0_value = 0;
+ info->ie1_value = 0;
+ info->ie2_value = 0;
+ write_reg(info, IE0, info->ie0_value);
+ write_reg(info, IE1, info->ie1_value);
+ write_reg(info, IE2, info->ie2_value);
+
+ write_reg(info, CMD, CHRESET);
+ }
+}
+
+/* Reset all the ports to a known state.
+ */
+void reset_adapter(SLMP_INFO *info)
+{
+ int i;
+
+ for ( i=0; i < SCA_MAX_PORTS; ++i) {
+ if (info->port_array[i])
+ reset_port(info->port_array[i]);
+ }
+}
+
+/* Program port for asynchronous communications.
+ */
+void async_mode(SLMP_INFO *info)
+{
+
+ unsigned char RegValue;
+
+ tx_stop(info);
+ rx_stop(info);
+
+ /* MD0, Mode Register 0
+ *
+ * 07..05 PRCTL<2..0>, Protocol Mode, 000=async
+ * 04 AUTO, Auto-enable (RTS/CTS/DCD)
+ * 03 Reserved, must be 0
+ * 02 CRCCC, CRC Calculation, 0=disabled
+ * 01..00 STOP<1..0> Stop bits (00=1,10=2)
+ *
+ * 0000 0000
+ */
+ RegValue = 0x00;
+ if (info->params.stop_bits != 1)
+ RegValue |= BIT1;
+ write_reg(info, MD0, RegValue);
+
+ /* MD1, Mode Register 1
+ *
+ * 07..06 BRATE<1..0>, bit rate, 00=1/1 01=1/16 10=1/32 11=1/64
+ * 05..04 TXCHR<1..0>, tx char size, 00=8 bits,01=7,10=6,11=5
+ * 03..02 RXCHR<1..0>, rx char size
+ * 01..00 PMPM<1..0>, Parity mode, 00=none 10=even 11=odd
+ *
+ * 0100 0000
+ */
+ RegValue = 0x40;
+ switch (info->params.data_bits) {
+ case 7: RegValue |= BIT4 + BIT2; break;
+ case 6: RegValue |= BIT5 + BIT3; break;
+ case 5: RegValue |= BIT5 + BIT4 + BIT3 + BIT2; break;
+ }
+ if (info->params.parity != ASYNC_PARITY_NONE) {
+ RegValue |= BIT1;
+ if (info->params.parity == ASYNC_PARITY_ODD)
+ RegValue |= BIT0;
+ }
+ write_reg(info, MD1, RegValue);
+
+ /* MD2, Mode Register 2
+ *
+ * 07..02 Reserved, must be 0
+ * 01..00 CNCT<1..0> Channel connection, 0=normal
+ *
+ * 0000 0000
+ */
+ RegValue = 0x00;
+ write_reg(info, MD2, RegValue);
+
+ /* RXS, Receive clock source
+ *
+ * 07 Reserved, must be 0
+ * 06..04 RXCS<2..0>, clock source, 000=RxC Pin, 100=BRG, 110=DPLL
+ * 03..00 RXBR<3..0>, rate divisor, 0000=1
+ */
+ RegValue=BIT6;
+ write_reg(info, RXS, RegValue);
+
+ /* TXS, Transmit clock source
+ *
+ * 07 Reserved, must be 0
+ * 06..04 RXCS<2..0>, clock source, 000=TxC Pin, 100=BRG, 110=Receive Clock
+ * 03..00 RXBR<3..0>, rate divisor, 0000=1
+ */
+ RegValue=BIT6;
+ write_reg(info, TXS, RegValue);
+
+ /* Control Register
+ *
+ * 6,4,2,0 CLKSEL<3..0>, 0 = TcCLK in, 1 = Auxclk out
+ */
+ info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+ write_control_reg(info);
+
+ tx_set_idle(info);
+
+ /* RRC Receive Ready Control 0
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 RRC<4..0> Rx FIFO trigger active 0x00 = 1 byte
+ */
+ write_reg(info, RRC, 0x00);
+
+ /* TRC0 Transmit Ready Control 0
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 TRC<4..0> Tx FIFO trigger active 0x10 = 16 bytes
+ */
+ write_reg(info, TRC0, 0x10);
+
+ /* TRC1 Transmit Ready Control 1
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 TRC<4..0> Tx FIFO trigger inactive 0x1e = 31 bytes (full-1)
+ */
+ write_reg(info, TRC1, 0x1e);
+
+ /* CTL, MSCI control register
+ *
+ * 07..06 Reserved, set to 0
+ * 05 UDRNC, underrun control, 0=abort 1=CRC+flag (HDLC/BSC)
+ * 04 IDLC, idle control, 0=mark 1=idle register
+ * 03 BRK, break, 0=off 1 =on (async)
+ * 02 SYNCLD, sync char load enable (BSC) 1=enabled
+ * 01 GOP, go active on poll (LOOP mode) 1=enabled
+ * 00 RTS, RTS output control, 0=active 1=inactive
+ *
+ * 0001 0001
+ */
+ RegValue = 0x10;
+ if (!(info->serial_signals & SerialSignal_RTS))
+ RegValue |= 0x01;
+ write_reg(info, CTL, RegValue);
+
+ /* enable status interrupts */
+ info->ie0_value |= TXINTE + RXINTE;
+ write_reg(info, IE0, info->ie0_value);
+
+ /* enable break detect interrupt */
+ info->ie1_value = BRKD;
+ write_reg(info, IE1, info->ie1_value);
+
+ /* enable rx overrun interrupt */
+ info->ie2_value = OVRN;
+ write_reg(info, IE2, info->ie2_value);
+
+ set_rate( info, info->params.data_rate * 16 );
+
+ if (info->params.loopback)
+ enable_loopback(info,1);
+}
+
+/* Program the SCA for HDLC communications.
+ */
+void hdlc_mode(SLMP_INFO *info)
+{
+ unsigned char RegValue;
+ u32 DpllDivisor;
+
+ // Can't use DPLL because SCA outputs recovered clock on RxC when
+ // DPLL mode selected. This causes output contention with RxC receiver.
+ // Use of DPLL would require external hardware to disable RxC receiver
+ // when DPLL mode selected.
+ info->params.flags &= ~(HDLC_FLAG_TXC_DPLL + HDLC_FLAG_RXC_DPLL);
+
+ /* disable DMA interrupts */
+ write_reg(info, TXDMA + DIR, 0);
+ write_reg(info, RXDMA + DIR, 0);
+
+ /* MD0, Mode Register 0
+ *
+ * 07..05 PRCTL<2..0>, Protocol Mode, 100=HDLC
+ * 04 AUTO, Auto-enable (RTS/CTS/DCD)
+ * 03 Reserved, must be 0
+ * 02 CRCCC, CRC Calculation, 1=enabled
+ * 01 CRC1, CRC selection, 0=CRC-16,1=CRC-CCITT-16
+ * 00 CRC0, CRC initial value, 1 = all 1s
+ *
+ * 1000 0001
+ */
+ RegValue = 0x81;
+ if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+ RegValue |= BIT4;
+ if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+ RegValue |= BIT4;
+ if (info->params.crc_type == HDLC_CRC_16_CCITT)
+ RegValue |= BIT2 + BIT1;
+ write_reg(info, MD0, RegValue);
+
+ /* MD1, Mode Register 1
+ *
+ * 07..06 ADDRS<1..0>, Address detect, 00=no addr check
+ * 05..04 TXCHR<1..0>, tx char size, 00=8 bits
+ * 03..02 RXCHR<1..0>, rx char size, 00=8 bits
+ * 01..00 PMPM<1..0>, Parity mode, 00=no parity
+ *
+ * 0000 0000
+ */
+ RegValue = 0x00;
+ write_reg(info, MD1, RegValue);
+
+ /* MD2, Mode Register 2
+ *
+ * 07 NRZFM, 0=NRZ, 1=FM
+ * 06..05 CODE<1..0> Encoding, 00=NRZ
+ * 04..03 DRATE<1..0> DPLL Divisor, 00=8
+ * 02 Reserved, must be 0
+ * 01..00 CNCT<1..0> Channel connection, 0=normal
+ *
+ * 0000 0000
+ */
+ RegValue = 0x00;
+ switch(info->params.encoding) {
+ case HDLC_ENCODING_NRZI: RegValue |= BIT5; break;
+ case HDLC_ENCODING_BIPHASE_MARK: RegValue |= BIT7 + BIT5; break; /* aka FM1 */
+ case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT7 + BIT6; break; /* aka FM0 */
+ case HDLC_ENCODING_BIPHASE_LEVEL: RegValue |= BIT7; break; /* aka Manchester */
+#if 0
+ case HDLC_ENCODING_NRZB: /* not supported */
+ case HDLC_ENCODING_NRZI_MARK: /* not supported */
+ case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: /* not supported */
+#endif
+ }
+ if ( info->params.flags & HDLC_FLAG_DPLL_DIV16 ) {
+ DpllDivisor = 16;
+ RegValue |= BIT3;
+ } else if ( info->params.flags & HDLC_FLAG_DPLL_DIV8 ) {
+ DpllDivisor = 8;
+ } else {
+ DpllDivisor = 32;
+ RegValue |= BIT4;
+ }
+ write_reg(info, MD2, RegValue);
+
+
+ /* RXS, Receive clock source
+ *
+ * 07 Reserved, must be 0
+ * 06..04 RXCS<2..0>, clock source, 000=RxC Pin, 100=BRG, 110=DPLL
+ * 03..00 RXBR<3..0>, rate divisor, 0000=1
+ */
+ RegValue=0;
+ if (info->params.flags & HDLC_FLAG_RXC_BRG)
+ RegValue |= BIT6;
+ if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+ RegValue |= BIT6 + BIT5;
+ write_reg(info, RXS, RegValue);
+
+ /* TXS, Transmit clock source
+ *
+ * 07 Reserved, must be 0
+ * 06..04 RXCS<2..0>, clock source, 000=TxC Pin, 100=BRG, 110=Receive Clock
+ * 03..00 RXBR<3..0>, rate divisor, 0000=1
+ */
+ RegValue=0;
+ if (info->params.flags & HDLC_FLAG_TXC_BRG)
+ RegValue |= BIT6;
+ if (info->params.flags & HDLC_FLAG_TXC_DPLL)
+ RegValue |= BIT6 + BIT5;
+ write_reg(info, TXS, RegValue);
+
+ if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+ set_rate(info, info->params.clock_speed * DpllDivisor);
+ else
+ set_rate(info, info->params.clock_speed);
+
+ /* GPDATA (General Purpose I/O Data Register)
+ *
+ * 6,4,2,0 CLKSEL<3..0>, 0 = TcCLK in, 1 = Auxclk out
+ */
+ if (info->params.flags & HDLC_FLAG_TXC_BRG)
+ info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+ else
+ info->port_array[0]->ctrlreg_value &= ~(BIT0 << (info->port_num * 2));
+ write_control_reg(info);
+
+ /* RRC Receive Ready Control 0
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 RRC<4..0> Rx FIFO trigger active
+ */
+ write_reg(info, RRC, rx_active_fifo_level);
+
+ /* TRC0 Transmit Ready Control 0
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 TRC<4..0> Tx FIFO trigger active
+ */
+ write_reg(info, TRC0, tx_active_fifo_level);
+
+ /* TRC1 Transmit Ready Control 1
+ *
+ * 07..05 Reserved, must be 0
+ * 04..00 TRC<4..0> Tx FIFO trigger inactive 0x1f = 32 bytes (full)
+ */
+ write_reg(info, TRC1, (unsigned char)(tx_negate_fifo_level - 1));
+
+ /* DMR, DMA Mode Register
+ *
+ * 07..05 Reserved, must be 0
+ * 04 TMOD, Transfer Mode: 1=chained-block
+ * 03 Reserved, must be 0
+ * 02 NF, Number of Frames: 1=multi-frame
+ * 01 CNTE, Frame End IRQ Counter enable: 0=disabled
+ * 00 Reserved, must be 0
+ *
+ * 0001 0100
+ */
+ write_reg(info, TXDMA + DMR, 0x14);
+ write_reg(info, RXDMA + DMR, 0x14);
+
+ /* Set chain pointer base (upper 8 bits of 24 bit addr) */
+ write_reg(info, RXDMA + CPB,
+ (unsigned char)(info->buffer_list_phys >> 16));
+
+ /* Set chain pointer base (upper 8 bits of 24 bit addr) */
+ write_reg(info, TXDMA + CPB,
+ (unsigned char)(info->buffer_list_phys >> 16));
+
+ /* enable status interrupts. other code enables/disables
+ * the individual sources for these two interrupt classes.
+ */
+ info->ie0_value |= TXINTE + RXINTE;
+ write_reg(info, IE0, info->ie0_value);
+
+ /* CTL, MSCI control register
+ *
+ * 07..06 Reserved, set to 0
+ * 05 UDRNC, underrun control, 0=abort 1=CRC+flag (HDLC/BSC)
+ * 04 IDLC, idle control, 0=mark 1=idle register
+ * 03 BRK, break, 0=off 1 =on (async)
+ * 02 SYNCLD, sync char load enable (BSC) 1=enabled
+ * 01 GOP, go active on poll (LOOP mode) 1=enabled
+ * 00 RTS, RTS output control, 0=active 1=inactive
+ *
+ * 0001 0001
+ */
+ RegValue = 0x10;
+ if (!(info->serial_signals & SerialSignal_RTS))
+ RegValue |= 0x01;
+ write_reg(info, CTL, RegValue);
+
+ /* preamble not supported ! */
+
+ tx_set_idle(info);
+ tx_stop(info);
+ rx_stop(info);
+
+ set_rate(info, info->params.clock_speed);
+
+ if (info->params.loopback)
+ enable_loopback(info,1);
+}
+
+/* Set the transmit HDLC idle mode
+ */
+void tx_set_idle(SLMP_INFO *info)
+{
+ unsigned char RegValue = 0xff;
+
+ /* Map API idle mode to SCA register bits */
+ switch(info->idle_mode) {
+ case HDLC_TXIDLE_FLAGS: RegValue = 0x7e; break;
+ case HDLC_TXIDLE_ALT_ZEROS_ONES: RegValue = 0xaa; break;
+ case HDLC_TXIDLE_ZEROS: RegValue = 0x00; break;
+ case HDLC_TXIDLE_ONES: RegValue = 0xff; break;
+ case HDLC_TXIDLE_ALT_MARK_SPACE: RegValue = 0xaa; break;
+ case HDLC_TXIDLE_SPACE: RegValue = 0x00; break;
+ case HDLC_TXIDLE_MARK: RegValue = 0xff; break;
+ }
+
+ write_reg(info, IDL, RegValue);
+}
+
+/* Query the adapter for the state of the V24 status (input) signals.
+ */
+void get_signals(SLMP_INFO *info)
+{
+ u16 status = read_reg(info, SR3);
+ u16 gpstatus = read_status_reg(info);
+ u16 testbit;
+
+ /* clear all serial signals except DTR and RTS */
+ info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+ /* set serial signal bits to reflect MISR */
+
+ if (!(status & BIT3))
+ info->serial_signals |= SerialSignal_CTS;
+
+ if ( !(status & BIT2))
+ info->serial_signals |= SerialSignal_DCD;
+
+ testbit = BIT1 << (info->port_num * 2); // Port 0..3 RI is GPDATA<1,3,5,7>
+ if (!(gpstatus & testbit))
+ info->serial_signals |= SerialSignal_RI;
+
+ testbit = BIT0 << (info->port_num * 2); // Port 0..3 DSR is GPDATA<0,2,4,6>
+ if (!(gpstatus & testbit))
+ info->serial_signals |= SerialSignal_DSR;
+}
+
+/* Set the state of DTR and RTS based on contents of
+ * serial_signals member of device context.
+ */
+void set_signals(SLMP_INFO *info)
+{
+ unsigned char RegValue;
+ u16 EnableBit;
+
+ RegValue = read_reg(info, CTL);
+ if (info->serial_signals & SerialSignal_RTS)
+ RegValue &= ~BIT0;
+ else
+ RegValue |= BIT0;
+ write_reg(info, CTL, RegValue);
+
+ // Port 0..3 DTR is ctrl reg <1,3,5,7>
+ EnableBit = BIT1 << (info->port_num*2);
+ if (info->serial_signals & SerialSignal_DTR)
+ info->port_array[0]->ctrlreg_value &= ~EnableBit;
+ else
+ info->port_array[0]->ctrlreg_value |= EnableBit;
+ write_control_reg(info);
+}
+
+/*******************/
+/* DMA Buffer Code */
+/*******************/
+
+/* Set the count for all receive buffers to SCABUFSIZE
+ * and set the current buffer to the first buffer. This effectively
+ * makes all buffers free and discards any data in buffers.
+ */
+void rx_reset_buffers(SLMP_INFO *info)
+{
+ rx_free_frame_buffers(info, 0, info->rx_buf_count - 1);
+}
+
+/* Free the buffers used by a received frame
+ *
+ * info pointer to device instance data
+ * first index of 1st receive buffer of frame
+ * last index of last receive buffer of frame
+ */
+void rx_free_frame_buffers(SLMP_INFO *info, unsigned int first, unsigned int last)
+{
+ int done = 0;
+
+ while(!done) {
+ /* reset current buffer for reuse */
+ info->rx_buf_list[first].status = 0xff;
+
+ if (first == last) {
+ done = 1;
+ /* set new last rx descriptor address */
+ write_reg16(info, RXDMA + EDA, info->rx_buf_list_ex[first].phys_entry);
+ }
+
+ first++;
+ if (first == info->rx_buf_count)
+ first = 0;
+ }
+
+ /* set current buffer to next buffer after last buffer of frame */
+ info->current_rx_buf = first;
+}
+
+/* Return a received frame from the receive DMA buffers.
+ * Only frames received without errors are returned.
+ *
+ * Return Value: 1 if frame returned, otherwise 0
+ */
+int rx_get_frame(SLMP_INFO *info)
+{
+ unsigned int StartIndex, EndIndex; /* index of 1st and last buffers of Rx frame */
+ unsigned short status;
+ unsigned int framesize = 0;
+ int ReturnCode = 0;
+ unsigned long flags;
+ struct tty_struct *tty = info->tty;
+ unsigned char addr_field = 0xff;
+ SCADESC *desc;
+ SCADESC_EX *desc_ex;
+
+CheckAgain:
+ /* assume no frame returned, set zero length */
+ framesize = 0;
+ addr_field = 0xff;
+
+ /*
+ * current_rx_buf points to the 1st buffer of the next available
+ * receive frame. To find the last buffer of the frame look for
+ * a non-zero status field in the buffer entries. (The status
+ * field is set by the 16C32 after completing a receive frame.
+ */
+ StartIndex = EndIndex = info->current_rx_buf;
+
+ for ( ;; ) {
+ desc = &info->rx_buf_list[EndIndex];
+ desc_ex = &info->rx_buf_list_ex[EndIndex];
+
+ if (desc->status == 0xff)
+ goto Cleanup; /* current desc still in use, no frames available */
+
+ if (framesize == 0 && info->params.addr_filter != 0xff)
+ addr_field = desc_ex->virt_addr[0];
+
+ framesize += desc->length;
+
+ /* Status != 0 means last buffer of frame */
+ if (desc->status)
+ break;
+
+ EndIndex++;
+ if (EndIndex == info->rx_buf_count)
+ EndIndex = 0;
+
+ if (EndIndex == info->current_rx_buf) {
+ /* all buffers have been 'used' but none mark */
+ /* the end of a frame. Reset buffers and receiver. */
+ if ( info->rx_enabled ){
+ spin_lock_irqsave(&info->lock,flags);
+ rx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+ goto Cleanup;
+ }
+
+ }
+
+ /* check status of receive frame */
+
+ /* frame status is byte stored after frame data
+ *
+ * 7 EOM (end of msg), 1 = last buffer of frame
+ * 6 Short Frame, 1 = short frame
+ * 5 Abort, 1 = frame aborted
+ * 4 Residue, 1 = last byte is partial
+ * 3 Overrun, 1 = overrun occurred during frame reception
+ * 2 CRC, 1 = CRC error detected
+ *
+ */
+ status = desc->status;
+
+ /* ignore CRC bit if not using CRC (bit is undefined) */
+ /* Note:CRC is not save to data buffer */
+ if (info->params.crc_type == HDLC_CRC_NONE)
+ status &= ~BIT2;
+
+ if (framesize == 0 ||
+ (addr_field != 0xff && addr_field != info->params.addr_filter)) {
+ /* discard 0 byte frames, this seems to occur sometime
+ * when remote is idling flags.
+ */
+ rx_free_frame_buffers(info, StartIndex, EndIndex);
+ goto CheckAgain;
+ }
+
+ if (framesize < 2)
+ status |= BIT6;
+
+ if (status & (BIT6+BIT5+BIT3+BIT2)) {
+ /* received frame has errors,
+ * update counts and mark frame size as 0
+ */
+ if (status & BIT6)
+ info->icount.rxshort++;
+ else if (status & BIT5)
+ info->icount.rxabort++;
+ else if (status & BIT3)
+ info->icount.rxover++;
+ else
+ info->icount.rxcrc++;
+
+ framesize = 0;
+#ifdef CONFIG_HDLC
+ {
+ struct net_device_stats *stats = hdlc_stats(info->netdev);
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ }
+#endif
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_BH )
+ printk("%s(%d):%s rx_get_frame() status=%04X size=%d\n",
+ __FILE__,__LINE__,info->device_name,status,framesize);
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ trace_block(info,info->rx_buf_list_ex[StartIndex].virt_addr,
+ min_t(int, framesize,SCABUFSIZE),0);
+
+ if (framesize) {
+ if (framesize > info->max_frame_size)
+ info->icount.rxlong++;
+ else {
+ /* copy dma buffer(s) to contiguous intermediate buffer */
+ int copy_count = framesize;
+ int index = StartIndex;
+ unsigned char *ptmp = info->tmp_rx_buf;
+ info->tmp_rx_buf_count = framesize;
+
+ info->icount.rxok++;
+
+ while(copy_count) {
+ int partial_count = min(copy_count,SCABUFSIZE);
+ memcpy( ptmp,
+ info->rx_buf_list_ex[index].virt_addr,
+ partial_count );
+ ptmp += partial_count;
+ copy_count -= partial_count;
+
+ if ( ++index == info->rx_buf_count )
+ index = 0;
+ }
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_rx(info,info->tmp_rx_buf,framesize);
+ else
+#endif
+ ldisc_receive_buf(tty,info->tmp_rx_buf,
+ info->flag_buf, framesize);
+ }
+ }
+ /* Free the buffers used by this frame. */
+ rx_free_frame_buffers( info, StartIndex, EndIndex );
+
+ ReturnCode = 1;
+
+Cleanup:
+ if ( info->rx_enabled && info->rx_overflow ) {
+ /* Receiver is enabled, but needs to restarted due to
+ * rx buffer overflow. If buffers are empty, restart receiver.
+ */
+ if (info->rx_buf_list[EndIndex].status == 0xff) {
+ spin_lock_irqsave(&info->lock,flags);
+ rx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+ }
+ }
+
+ return ReturnCode;
+}
+
+/* load the transmit DMA buffer with data
+ */
+void tx_load_dma_buffer(SLMP_INFO *info, const char *buf, unsigned int count)
+{
+ unsigned short copy_count;
+ unsigned int i = 0;
+ SCADESC *desc;
+ SCADESC_EX *desc_ex;
+
+ if ( debug_level >= DEBUG_LEVEL_DATA )
+ trace_block(info,buf, min_t(int, count,SCABUFSIZE), 1);
+
+ /* Copy source buffer to one or more DMA buffers, starting with
+ * the first transmit dma buffer.
+ */
+ for(i=0;;)
+ {
+ copy_count = min_t(unsigned short,count,SCABUFSIZE);
+
+ desc = &info->tx_buf_list[i];
+ desc_ex = &info->tx_buf_list_ex[i];
+
+ load_pci_memory(info, desc_ex->virt_addr,buf,copy_count);
+
+ desc->length = copy_count;
+ desc->status = 0;
+
+ buf += copy_count;
+ count -= copy_count;
+
+ if (!count)
+ break;
+
+ i++;
+ if (i >= info->tx_buf_count)
+ i = 0;
+ }
+
+ info->tx_buf_list[i].status = 0x81; /* set EOM and EOT status */
+ info->last_tx_buf = ++i;
+}
+
+int register_test(SLMP_INFO *info)
+{
+ static unsigned char testval[] = {0x00, 0xff, 0xaa, 0x55, 0x69, 0x96};
+ static unsigned int count = sizeof(testval)/sizeof(unsigned char);
+ unsigned int i;
+ int rc = TRUE;
+ unsigned long flags;
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_port(info);
+
+ /* assume failure */
+ info->init_error = DiagStatus_AddressFailure;
+
+ /* Write bit patterns to various registers but do it out of */
+ /* sync, then read back and verify values. */
+
+ for (i = 0 ; i < count ; i++) {
+ write_reg(info, TMC, testval[i]);
+ write_reg(info, IDL, testval[(i+1)%count]);
+ write_reg(info, SA0, testval[(i+2)%count]);
+ write_reg(info, SA1, testval[(i+3)%count]);
+
+ if ( (read_reg(info, TMC) != testval[i]) ||
+ (read_reg(info, IDL) != testval[(i+1)%count]) ||
+ (read_reg(info, SA0) != testval[(i+2)%count]) ||
+ (read_reg(info, SA1) != testval[(i+3)%count]) )
+ {
+ rc = FALSE;
+ break;
+ }
+ }
+
+ reset_port(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return rc;
+}
+
+int irq_test(SLMP_INFO *info)
+{
+ unsigned long timeout;
+ unsigned long flags;
+
+ unsigned char timer = (info->port_num & 1) ? TIMER2 : TIMER0;
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_port(info);
+
+ /* assume failure */
+ info->init_error = DiagStatus_IrqFailure;
+ info->irq_occurred = FALSE;
+
+ /* setup timer0 on SCA0 to interrupt */
+
+ /* IER2<7..4> = timer<3..0> interrupt enables (1=enabled) */
+ write_reg(info, IER2, (unsigned char)((info->port_num & 1) ? BIT6 : BIT4));
+
+ write_reg(info, (unsigned char)(timer + TEPR), 0); /* timer expand prescale */
+ write_reg16(info, (unsigned char)(timer + TCONR), 1); /* timer constant */
+
+
+ /* TMCS, Timer Control/Status Register
+ *
+ * 07 CMF, Compare match flag (read only) 1=match
+ * 06 ECMI, CMF Interrupt Enable: 1=enabled
+ * 05 Reserved, must be 0
+ * 04 TME, Timer Enable
+ * 03..00 Reserved, must be 0
+ *
+ * 0101 0000
+ */
+ write_reg(info, (unsigned char)(timer + TMCS), 0x50);
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ timeout=100;
+ while( timeout-- && !info->irq_occurred ) {
+ msleep_interruptible(10);
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_port(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ return info->irq_occurred;
+}
+
+/* initialize individual SCA device (2 ports)
+ */
+static int sca_init(SLMP_INFO *info)
+{
+ /* set wait controller to single mem partition (low), no wait states */
+ write_reg(info, PABR0, 0); /* wait controller addr boundary 0 */
+ write_reg(info, PABR1, 0); /* wait controller addr boundary 1 */
+ write_reg(info, WCRL, 0); /* wait controller low range */
+ write_reg(info, WCRM, 0); /* wait controller mid range */
+ write_reg(info, WCRH, 0); /* wait controller high range */
+
+ /* DPCR, DMA Priority Control
+ *
+ * 07..05 Not used, must be 0
+ * 04 BRC, bus release condition: 0=all transfers complete
+ * 03 CCC, channel change condition: 0=every cycle
+ * 02..00 PR<2..0>, priority 100=round robin
+ *
+ * 00000100 = 0x04
+ */
+ write_reg(info, DPCR, dma_priority);
+
+ /* DMA Master Enable, BIT7: 1=enable all channels */
+ write_reg(info, DMER, 0x80);
+
+ /* enable all interrupt classes */
+ write_reg(info, IER0, 0xff); /* TxRDY,RxRDY,TxINT,RxINT (ports 0-1) */
+ write_reg(info, IER1, 0xff); /* DMIB,DMIA (channels 0-3) */
+ write_reg(info, IER2, 0xf0); /* TIRQ (timers 0-3) */
+
+ /* ITCR, interrupt control register
+ * 07 IPC, interrupt priority, 0=MSCI->DMA
+ * 06..05 IAK<1..0>, Acknowledge cycle, 00=non-ack cycle
+ * 04 VOS, Vector Output, 0=unmodified vector
+ * 03..00 Reserved, must be 0
+ */
+ write_reg(info, ITCR, 0);
+
+ return TRUE;
+}
+
+/* initialize adapter hardware
+ */
+int init_adapter(SLMP_INFO *info)
+{
+ int i;
+
+ /* Set BIT30 of Local Control Reg 0x50 to reset SCA */
+ volatile u32 *MiscCtrl = (u32 *)(info->lcr_base + 0x50);
+ u32 readval;
+
+ info->misc_ctrl_value |= BIT30;
+ *MiscCtrl = info->misc_ctrl_value;
+
+ /*
+ * Force at least 170ns delay before clearing
+ * reset bit. Each read from LCR takes at least
+ * 30ns so 10 times for 300ns to be safe.
+ */
+ for(i=0;i<10;i++)
+ readval = *MiscCtrl;
+
+ info->misc_ctrl_value &= ~BIT30;
+ *MiscCtrl = info->misc_ctrl_value;
+
+ /* init control reg (all DTRs off, all clksel=input) */
+ info->ctrlreg_value = 0xaa;
+ write_control_reg(info);
+
+ {
+ volatile u32 *LCR1BRDR = (u32 *)(info->lcr_base + 0x2c);
+ lcr1_brdr_value &= ~(BIT5 + BIT4 + BIT3);
+
+ switch(read_ahead_count)
+ {
+ case 16:
+ lcr1_brdr_value |= BIT5 + BIT4 + BIT3;
+ break;
+ case 8:
+ lcr1_brdr_value |= BIT5 + BIT4;
+ break;
+ case 4:
+ lcr1_brdr_value |= BIT5 + BIT3;
+ break;
+ case 0:
+ lcr1_brdr_value |= BIT5;
+ break;
+ }
+
+ *LCR1BRDR = lcr1_brdr_value;
+ *MiscCtrl = misc_ctrl_value;
+ }
+
+ sca_init(info->port_array[0]);
+ sca_init(info->port_array[2]);
+
+ return TRUE;
+}
+
+/* Loopback an HDLC frame to test the hardware
+ * interrupt and DMA functions.
+ */
+int loopback_test(SLMP_INFO *info)
+{
+#define TESTFRAMESIZE 20
+
+ unsigned long timeout;
+ u16 count = TESTFRAMESIZE;
+ unsigned char buf[TESTFRAMESIZE];
+ int rc = FALSE;
+ unsigned long flags;
+
+ struct tty_struct *oldtty = info->tty;
+ u32 speed = info->params.clock_speed;
+
+ info->params.clock_speed = 3686400;
+ info->tty = NULL;
+
+ /* assume failure */
+ info->init_error = DiagStatus_DmaFailure;
+
+ /* build and send transmit frame */
+ for (count = 0; count < TESTFRAMESIZE;++count)
+ buf[count] = (unsigned char)count;
+
+ memset(info->tmp_rx_buf,0,TESTFRAMESIZE);
+
+ /* program hardware for HDLC and enabled receiver */
+ spin_lock_irqsave(&info->lock,flags);
+ hdlc_mode(info);
+ enable_loopback(info,1);
+ rx_start(info);
+ info->tx_count = count;
+ tx_load_dma_buffer(info,buf,count);
+ tx_start(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* wait for receive complete */
+ /* Set a timeout for waiting for interrupt. */
+ for ( timeout = 100; timeout; --timeout ) {
+ msleep_interruptible(10);
+
+ if (rx_get_frame(info)) {
+ rc = TRUE;
+ break;
+ }
+ }
+
+ /* verify received frame length and contents */
+ if (rc == TRUE &&
+ ( info->tmp_rx_buf_count != count ||
+ memcmp(buf, info->tmp_rx_buf,count))) {
+ rc = FALSE;
+ }
+
+ spin_lock_irqsave(&info->lock,flags);
+ reset_adapter(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ info->params.clock_speed = speed;
+ info->tty = oldtty;
+
+ return rc;
+}
+
+/* Perform diagnostics on hardware
+ */
+int adapter_test( SLMP_INFO *info )
+{
+ unsigned long flags;
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):Testing device %s\n",
+ __FILE__,__LINE__,info->device_name );
+
+ spin_lock_irqsave(&info->lock,flags);
+ init_adapter(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ info->port_array[0]->port_count = 0;
+
+ if ( register_test(info->port_array[0]) &&
+ register_test(info->port_array[1])) {
+
+ info->port_array[0]->port_count = 2;
+
+ if ( register_test(info->port_array[2]) &&
+ register_test(info->port_array[3]) )
+ info->port_array[0]->port_count += 2;
+ }
+ else {
+ printk( "%s(%d):Register test failure for device %s Addr=%08lX\n",
+ __FILE__,__LINE__,info->device_name, (unsigned long)(info->phys_sca_base));
+ return -ENODEV;
+ }
+
+ if ( !irq_test(info->port_array[0]) ||
+ !irq_test(info->port_array[1]) ||
+ (info->port_count == 4 && !irq_test(info->port_array[2])) ||
+ (info->port_count == 4 && !irq_test(info->port_array[3]))) {
+ printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+ __FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+ return -ENODEV;
+ }
+
+ if (!loopback_test(info->port_array[0]) ||
+ !loopback_test(info->port_array[1]) ||
+ (info->port_count == 4 && !loopback_test(info->port_array[2])) ||
+ (info->port_count == 4 && !loopback_test(info->port_array[3]))) {
+ printk( "%s(%d):DMA test failure for device %s\n",
+ __FILE__,__LINE__,info->device_name);
+ return -ENODEV;
+ }
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):device %s passed diagnostics\n",
+ __FILE__,__LINE__,info->device_name );
+
+ info->port_array[0]->init_error = 0;
+ info->port_array[1]->init_error = 0;
+ if ( info->port_count > 2 ) {
+ info->port_array[2]->init_error = 0;
+ info->port_array[3]->init_error = 0;
+ }
+
+ return 0;
+}
+
+/* Test the shared memory on a PCI adapter.
+ */
+int memory_test(SLMP_INFO *info)
+{
+ static unsigned long testval[] = { 0x0, 0x55555555, 0xaaaaaaaa,
+ 0x66666666, 0x99999999, 0xffffffff, 0x12345678 };
+ unsigned long count = sizeof(testval)/sizeof(unsigned long);
+ unsigned long i;
+ unsigned long limit = SCA_MEM_SIZE/sizeof(unsigned long);
+ unsigned long * addr = (unsigned long *)info->memory_base;
+
+ /* Test data lines with test pattern at one location. */
+
+ for ( i = 0 ; i < count ; i++ ) {
+ *addr = testval[i];
+ if ( *addr != testval[i] )
+ return FALSE;
+ }
+
+ /* Test address lines with incrementing pattern over */
+ /* entire address range. */
+
+ for ( i = 0 ; i < limit ; i++ ) {
+ *addr = i * 4;
+ addr++;
+ }
+
+ addr = (unsigned long *)info->memory_base;
+
+ for ( i = 0 ; i < limit ; i++ ) {
+ if ( *addr != i * 4 )
+ return FALSE;
+ addr++;
+ }
+
+ memset( info->memory_base, 0, SCA_MEM_SIZE );
+ return TRUE;
+}
+
+/* Load data into PCI adapter shared memory.
+ *
+ * The PCI9050 releases control of the local bus
+ * after completing the current read or write operation.
+ *
+ * While the PCI9050 write FIFO not empty, the
+ * PCI9050 treats all of the writes as a single transaction
+ * and does not release the bus. This causes DMA latency problems
+ * at high speeds when copying large data blocks to the shared memory.
+ *
+ * This function breaks a write into multiple transations by
+ * interleaving a read which flushes the write FIFO and 'completes'
+ * the write transation. This allows any pending DMA request to gain control
+ * of the local bus in a timely fasion.
+ */
+void load_pci_memory(SLMP_INFO *info, char* dest, const char* src, unsigned short count)
+{
+ /* A load interval of 16 allows for 4 32-bit writes at */
+ /* 136ns each for a maximum latency of 542ns on the local bus.*/
+
+ unsigned short interval = count / sca_pci_load_interval;
+ unsigned short i;
+
+ for ( i = 0 ; i < interval ; i++ )
+ {
+ memcpy(dest, src, sca_pci_load_interval);
+ read_status_reg(info);
+ dest += sca_pci_load_interval;
+ src += sca_pci_load_interval;
+ }
+
+ memcpy(dest, src, count % sca_pci_load_interval);
+}
+
+void trace_block(SLMP_INFO *info,const char* data, int count, int xmit)
+{
+ int i;
+ int linecount;
+ if (xmit)
+ printk("%s tx data:\n",info->device_name);
+ else
+ printk("%s rx data:\n",info->device_name);
+
+ while(count) {
+ if (count > 16)
+ linecount = 16;
+ else
+ linecount = count;
+
+ for(i=0;i<linecount;i++)
+ printk("%02X ",(unsigned char)data[i]);
+ for(;i<17;i++)
+ printk(" ");
+ for(i=0;i<linecount;i++) {
+ if (data[i]>=040 && data[i]<=0176)
+ printk("%c",data[i]);
+ else
+ printk(".");
+ }
+ printk("\n");
+
+ data += linecount;
+ count -= linecount;
+ }
+} /* end of trace_block() */
+
+/* called when HDLC frame times out
+ * update stats and do tx completion processing
+ */
+void tx_timeout(unsigned long context)
+{
+ SLMP_INFO *info = (SLMP_INFO*)context;
+ unsigned long flags;
+
+ if ( debug_level >= DEBUG_LEVEL_INFO )
+ printk( "%s(%d):%s tx_timeout()\n",
+ __FILE__,__LINE__,info->device_name);
+ if(info->tx_active && info->params.mode == MGSL_MODE_HDLC) {
+ info->icount.txtimeout++;
+ }
+ spin_lock_irqsave(&info->lock,flags);
+ info->tx_active = 0;
+ info->tx_count = info->tx_put = info->tx_get = 0;
+
+ spin_unlock_irqrestore(&info->lock,flags);
+
+#ifdef CONFIG_HDLC
+ if (info->netcount)
+ hdlcdev_tx_done(info);
+ else
+#endif
+ bh_transmit(info);
+}
+
+/* called to periodically check the DSR/RI modem signal input status
+ */
+void status_timeout(unsigned long context)
+{
+ u16 status = 0;
+ SLMP_INFO *info = (SLMP_INFO*)context;
+ unsigned long flags;
+ unsigned char delta;
+
+
+ spin_lock_irqsave(&info->lock,flags);
+ get_signals(info);
+ spin_unlock_irqrestore(&info->lock,flags);
+
+ /* check for DSR/RI state change */
+
+ delta = info->old_signals ^ info->serial_signals;
+ info->old_signals = info->serial_signals;
+
+ if (delta & SerialSignal_DSR)
+ status |= MISCSTATUS_DSR_LATCHED|(info->serial_signals&SerialSignal_DSR);
+
+ if (delta & SerialSignal_RI)
+ status |= MISCSTATUS_RI_LATCHED|(info->serial_signals&SerialSignal_RI);
+
+ if (delta & SerialSignal_DCD)
+ status |= MISCSTATUS_DCD_LATCHED|(info->serial_signals&SerialSignal_DCD);
+
+ if (delta & SerialSignal_CTS)
+ status |= MISCSTATUS_CTS_LATCHED|(info->serial_signals&SerialSignal_CTS);
+
+ if (status)
+ isr_io_pin(info,status);
+
+ info->status_timer.data = (unsigned long)info;
+ info->status_timer.function = status_timeout;
+ info->status_timer.expires = jiffies + msecs_to_jiffies(10);
+ add_timer(&info->status_timer);
+}
+
+
+/* Register Access Routines -
+ * All registers are memory mapped
+ */
+#define CALC_REGADDR() \
+ unsigned char * RegAddr = (unsigned char*)(info->sca_base + Addr); \
+ if (info->port_num > 1) \
+ RegAddr += 256; /* port 0-1 SCA0, 2-3 SCA1 */ \
+ if ( info->port_num & 1) { \
+ if (Addr > 0x7f) \
+ RegAddr += 0x40; /* DMA access */ \
+ else if (Addr > 0x1f && Addr < 0x60) \
+ RegAddr += 0x20; /* MSCI access */ \
+ }
+
+
+unsigned char read_reg(SLMP_INFO * info, unsigned char Addr)
+{
+ CALC_REGADDR();
+ return *RegAddr;
+}
+void write_reg(SLMP_INFO * info, unsigned char Addr, unsigned char Value)
+{
+ CALC_REGADDR();
+ *RegAddr = Value;
+}
+
+u16 read_reg16(SLMP_INFO * info, unsigned char Addr)
+{
+ CALC_REGADDR();
+ return *((u16 *)RegAddr);
+}
+
+void write_reg16(SLMP_INFO * info, unsigned char Addr, u16 Value)
+{
+ CALC_REGADDR();
+ *((u16 *)RegAddr) = Value;
+}
+
+unsigned char read_status_reg(SLMP_INFO * info)
+{
+ unsigned char *RegAddr = (unsigned char *)info->statctrl_base;
+ return *RegAddr;
+}
+
+void write_control_reg(SLMP_INFO * info)
+{
+ unsigned char *RegAddr = (unsigned char *)info->statctrl_base;
+ *RegAddr = info->port_array[0]->ctrlreg_value;
+}
+
+
+static int __devinit synclinkmp_init_one (struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ if (pci_enable_device(dev)) {
+ printk("error enabling pci device %p\n", dev);
+ return -EIO;
+ }
+ device_init( ++synclinkmp_adapter_count, dev );
+ return 0;
+}
+
+static void __devexit synclinkmp_remove_one (struct pci_dev *dev)
+{
+}
diff --git a/drivers/char/sysrq.c b/drivers/char/sysrq.c
new file mode 100644
index 000000000000..f59f7cbd525b
--- /dev/null
+++ b/drivers/char/sysrq.c
@@ -0,0 +1,432 @@
+/* -*- linux-c -*-
+ *
+ * $Id: sysrq.c,v 1.15 1998/08/23 14:56:41 mj Exp $
+ *
+ * Linux Magic System Request Key Hacks
+ *
+ * (c) 1997 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
+ * based on ideas by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>
+ *
+ * (c) 2000 Crutcher Dunnavant <crutcher+kernel@datastacks.com>
+ * overhauled to use key registration
+ * based upon discusions in irc://irc.openprojects.net/#kernelnewbies
+ */
+
+#include <linux/config.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/tty.h>
+#include <linux/mount.h>
+#include <linux/kdev_t.h>
+#include <linux/major.h>
+#include <linux/reboot.h>
+#include <linux/sysrq.h>
+#include <linux/kbd_kern.h>
+#include <linux/quotaops.h>
+#include <linux/smp_lock.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/suspend.h>
+#include <linux/writeback.h>
+#include <linux/buffer_head.h> /* for fsync_bdev() */
+#include <linux/swap.h>
+#include <linux/spinlock.h>
+#include <linux/vt_kern.h>
+#include <linux/workqueue.h>
+
+#include <asm/ptrace.h>
+
+/* Whether we react on sysrq keys or just ignore them */
+int sysrq_enabled = 1;
+
+/* Loglevel sysrq handler */
+static void sysrq_handle_loglevel(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ int i;
+ i = key - '0';
+ console_loglevel = 7;
+ printk("Loglevel set to %d\n", i);
+ console_loglevel = i;
+}
+static struct sysrq_key_op sysrq_loglevel_op = {
+ .handler = sysrq_handle_loglevel,
+ .help_msg = "loglevel0-8",
+ .action_msg = "Changing Loglevel",
+ .enable_mask = SYSRQ_ENABLE_LOG,
+};
+
+
+/* SAK sysrq handler */
+#ifdef CONFIG_VT
+static void sysrq_handle_SAK(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ if (tty)
+ do_SAK(tty);
+ reset_vc(vc_cons[fg_console].d);
+}
+static struct sysrq_key_op sysrq_SAK_op = {
+ .handler = sysrq_handle_SAK,
+ .help_msg = "saK",
+ .action_msg = "SAK",
+ .enable_mask = SYSRQ_ENABLE_KEYBOARD,
+};
+#endif
+
+#ifdef CONFIG_VT
+/* unraw sysrq handler */
+static void sysrq_handle_unraw(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ struct kbd_struct *kbd = &kbd_table[fg_console];
+
+ if (kbd)
+ kbd->kbdmode = VC_XLATE;
+}
+static struct sysrq_key_op sysrq_unraw_op = {
+ .handler = sysrq_handle_unraw,
+ .help_msg = "unRaw",
+ .action_msg = "Keyboard mode set to XLATE",
+ .enable_mask = SYSRQ_ENABLE_KEYBOARD,
+};
+#endif /* CONFIG_VT */
+
+/* reboot sysrq handler */
+static void sysrq_handle_reboot(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ local_irq_enable();
+ machine_restart(NULL);
+}
+
+static struct sysrq_key_op sysrq_reboot_op = {
+ .handler = sysrq_handle_reboot,
+ .help_msg = "reBoot",
+ .action_msg = "Resetting",
+ .enable_mask = SYSRQ_ENABLE_BOOT,
+};
+
+static void sysrq_handle_sync(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ emergency_sync();
+}
+
+static struct sysrq_key_op sysrq_sync_op = {
+ .handler = sysrq_handle_sync,
+ .help_msg = "Sync",
+ .action_msg = "Emergency Sync",
+ .enable_mask = SYSRQ_ENABLE_SYNC,
+};
+
+static void sysrq_handle_mountro(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ emergency_remount();
+}
+
+static struct sysrq_key_op sysrq_mountro_op = {
+ .handler = sysrq_handle_mountro,
+ .help_msg = "Unmount",
+ .action_msg = "Emergency Remount R/O",
+ .enable_mask = SYSRQ_ENABLE_REMOUNT,
+};
+
+/* END SYNC SYSRQ HANDLERS BLOCK */
+
+
+/* SHOW SYSRQ HANDLERS BLOCK */
+
+static void sysrq_handle_showregs(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ if (pt_regs)
+ show_regs(pt_regs);
+}
+static struct sysrq_key_op sysrq_showregs_op = {
+ .handler = sysrq_handle_showregs,
+ .help_msg = "showPc",
+ .action_msg = "Show Regs",
+ .enable_mask = SYSRQ_ENABLE_DUMP,
+};
+
+
+static void sysrq_handle_showstate(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ show_state();
+}
+static struct sysrq_key_op sysrq_showstate_op = {
+ .handler = sysrq_handle_showstate,
+ .help_msg = "showTasks",
+ .action_msg = "Show State",
+ .enable_mask = SYSRQ_ENABLE_DUMP,
+};
+
+
+static void sysrq_handle_showmem(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ show_mem();
+}
+static struct sysrq_key_op sysrq_showmem_op = {
+ .handler = sysrq_handle_showmem,
+ .help_msg = "showMem",
+ .action_msg = "Show Memory",
+ .enable_mask = SYSRQ_ENABLE_DUMP,
+};
+
+/* SHOW SYSRQ HANDLERS BLOCK */
+
+
+/* SIGNAL SYSRQ HANDLERS BLOCK */
+
+/* signal sysrq helper function
+ * Sends a signal to all user processes */
+static void send_sig_all(int sig)
+{
+ struct task_struct *p;
+
+ for_each_process(p) {
+ if (p->mm && p->pid != 1)
+ /* Not swapper, init nor kernel thread */
+ force_sig(sig, p);
+ }
+}
+
+static void sysrq_handle_term(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ send_sig_all(SIGTERM);
+ console_loglevel = 8;
+}
+static struct sysrq_key_op sysrq_term_op = {
+ .handler = sysrq_handle_term,
+ .help_msg = "tErm",
+ .action_msg = "Terminate All Tasks",
+ .enable_mask = SYSRQ_ENABLE_SIGNAL,
+};
+
+static void moom_callback(void *ignored)
+{
+ out_of_memory(GFP_KERNEL);
+}
+
+static DECLARE_WORK(moom_work, moom_callback, NULL);
+
+static void sysrq_handle_moom(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ schedule_work(&moom_work);
+}
+static struct sysrq_key_op sysrq_moom_op = {
+ .handler = sysrq_handle_moom,
+ .help_msg = "Full",
+ .action_msg = "Manual OOM execution",
+};
+
+static void sysrq_handle_kill(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ send_sig_all(SIGKILL);
+ console_loglevel = 8;
+}
+static struct sysrq_key_op sysrq_kill_op = {
+ .handler = sysrq_handle_kill,
+ .help_msg = "kIll",
+ .action_msg = "Kill All Tasks",
+ .enable_mask = SYSRQ_ENABLE_SIGNAL,
+};
+
+/* END SIGNAL SYSRQ HANDLERS BLOCK */
+
+static void sysrq_handle_unrt(int key, struct pt_regs *pt_regs,
+ struct tty_struct *tty)
+{
+ normalize_rt_tasks();
+}
+static struct sysrq_key_op sysrq_unrt_op = {
+ .handler = sysrq_handle_unrt,
+ .help_msg = "Nice",
+ .action_msg = "Nice All RT Tasks",
+ .enable_mask = SYSRQ_ENABLE_RTNICE,
+};
+
+/* Key Operations table and lock */
+static DEFINE_SPINLOCK(sysrq_key_table_lock);
+#define SYSRQ_KEY_TABLE_LENGTH 36
+static struct sysrq_key_op *sysrq_key_table[SYSRQ_KEY_TABLE_LENGTH] = {
+/* 0 */ &sysrq_loglevel_op,
+/* 1 */ &sysrq_loglevel_op,
+/* 2 */ &sysrq_loglevel_op,
+/* 3 */ &sysrq_loglevel_op,
+/* 4 */ &sysrq_loglevel_op,
+/* 5 */ &sysrq_loglevel_op,
+/* 6 */ &sysrq_loglevel_op,
+/* 7 */ &sysrq_loglevel_op,
+/* 8 */ &sysrq_loglevel_op,
+/* 9 */ &sysrq_loglevel_op,
+/* a */ NULL, /* Don't use for system provided sysrqs,
+ it is handled specially on the sparc
+ and will never arrive */
+/* b */ &sysrq_reboot_op,
+/* c */ NULL,
+/* d */ NULL,
+/* e */ &sysrq_term_op,
+/* f */ &sysrq_moom_op,
+/* g */ NULL,
+/* h */ NULL,
+/* i */ &sysrq_kill_op,
+/* j */ NULL,
+#ifdef CONFIG_VT
+/* k */ &sysrq_SAK_op,
+#else
+/* k */ NULL,
+#endif
+/* l */ NULL,
+/* m */ &sysrq_showmem_op,
+/* n */ &sysrq_unrt_op,
+/* o */ NULL, /* This will often be registered
+ as 'Off' at init time */
+/* p */ &sysrq_showregs_op,
+/* q */ NULL,
+#ifdef CONFIG_VT
+/* r */ &sysrq_unraw_op,
+#else
+/* r */ NULL,
+#endif
+/* s */ &sysrq_sync_op,
+/* t */ &sysrq_showstate_op,
+/* u */ &sysrq_mountro_op,
+/* v */ NULL, /* May be assigned at init time by SMP VOYAGER */
+/* w */ NULL,
+/* x */ NULL,
+/* y */ NULL,
+/* z */ NULL
+};
+
+/* key2index calculation, -1 on invalid index */
+static int sysrq_key_table_key2index(int key) {
+ int retval;
+ if ((key >= '0') && (key <= '9')) {
+ retval = key - '0';
+ } else if ((key >= 'a') && (key <= 'z')) {
+ retval = key + 10 - 'a';
+ } else {
+ retval = -1;
+ }
+ return retval;
+}
+
+/*
+ * get and put functions for the table, exposed to modules.
+ */
+
+struct sysrq_key_op *__sysrq_get_key_op (int key) {
+ struct sysrq_key_op *op_p;
+ int i;
+
+ i = sysrq_key_table_key2index(key);
+ op_p = (i == -1) ? NULL : sysrq_key_table[i];
+ return op_p;
+}
+
+void __sysrq_put_key_op (int key, struct sysrq_key_op *op_p) {
+ int i;
+
+ i = sysrq_key_table_key2index(key);
+ if (i != -1)
+ sysrq_key_table[i] = op_p;
+}
+
+/*
+ * This is the non-locking version of handle_sysrq
+ * It must/can only be called by sysrq key handlers,
+ * as they are inside of the lock
+ */
+
+void __handle_sysrq(int key, struct pt_regs *pt_regs, struct tty_struct *tty, int check_mask)
+{
+ struct sysrq_key_op *op_p;
+ int orig_log_level;
+ int i, j;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sysrq_key_table_lock, flags);
+ orig_log_level = console_loglevel;
+ console_loglevel = 7;
+ printk(KERN_INFO "SysRq : ");
+
+ op_p = __sysrq_get_key_op(key);
+ if (op_p) {
+ /* Should we check for enabled operations (/proc/sysrq-trigger should not)
+ * and is the invoked operation enabled? */
+ if (!check_mask || sysrq_enabled == 1 ||
+ (sysrq_enabled & op_p->enable_mask)) {
+ printk ("%s\n", op_p->action_msg);
+ console_loglevel = orig_log_level;
+ op_p->handler(key, pt_regs, tty);
+ }
+ else
+ printk("This sysrq operation is disabled.\n");
+ } else {
+ printk("HELP : ");
+ /* Only print the help msg once per handler */
+ for (i=0; i<SYSRQ_KEY_TABLE_LENGTH; i++)
+ if (sysrq_key_table[i]) {
+ for (j=0; sysrq_key_table[i] != sysrq_key_table[j]; j++);
+ if (j == i)
+ printk ("%s ", sysrq_key_table[i]->help_msg);
+ }
+ printk ("\n");
+ console_loglevel = orig_log_level;
+ }
+ spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
+}
+
+/*
+ * This function is called by the keyboard handler when SysRq is pressed
+ * and any other keycode arrives.
+ */
+
+void handle_sysrq(int key, struct pt_regs *pt_regs, struct tty_struct *tty)
+{
+ if (!sysrq_enabled)
+ return;
+ __handle_sysrq(key, pt_regs, tty, 1);
+}
+
+int __sysrq_swap_key_ops(int key, struct sysrq_key_op *insert_op_p,
+ struct sysrq_key_op *remove_op_p) {
+
+ int retval;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sysrq_key_table_lock, flags);
+ if (__sysrq_get_key_op(key) == remove_op_p) {
+ __sysrq_put_key_op(key, insert_op_p);
+ retval = 0;
+ } else {
+ retval = -1;
+ }
+ spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
+
+ return retval;
+}
+
+int register_sysrq_key(int key, struct sysrq_key_op *op_p)
+{
+ return __sysrq_swap_key_ops(key, op_p, NULL);
+}
+
+int unregister_sysrq_key(int key, struct sysrq_key_op *op_p)
+{
+ return __sysrq_swap_key_ops(key, NULL, op_p);
+}
+
+EXPORT_SYMBOL(handle_sysrq);
+EXPORT_SYMBOL(register_sysrq_key);
+EXPORT_SYMBOL(unregister_sysrq_key);
diff --git a/drivers/char/tb0219.c b/drivers/char/tb0219.c
new file mode 100644
index 000000000000..5413f2908859
--- /dev/null
+++ b/drivers/char/tb0219.c
@@ -0,0 +1,347 @@
+/*
+ * Driver for TANBAC TB0219 base board.
+ *
+ * Copyright (C) 2005 Yoichi Yuasa <yuasa@hh.iij4u.or.jp>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <asm/reboot.h>
+
+MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>");
+MODULE_DESCRIPTION("TANBAC TB0219 base board driver");
+MODULE_LICENSE("GPL");
+
+static int major; /* default is dynamic major device number */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+static void (*old_machine_restart)(char *command);
+static void __iomem *tb0219_base;
+static spinlock_t tb0219_lock;
+
+#define tb0219_read(offset) readw(tb0219_base + (offset))
+#define tb0219_write(offset, value) writew((value), tb0219_base + (offset))
+
+#define TB0219_START 0x0a000000UL
+#define TB0219_SIZE 0x20UL
+
+#define TB0219_LED 0x00
+#define TB0219_GPIO_INPUT 0x02
+#define TB0219_GPIO_OUTPUT 0x04
+#define TB0219_DIP_SWITCH 0x06
+#define TB0219_MISC 0x08
+#define TB0219_RESET 0x0e
+#define TB0219_PCI_SLOT1_IRQ_STATUS 0x10
+#define TB0219_PCI_SLOT2_IRQ_STATUS 0x12
+#define TB0219_PCI_SLOT3_IRQ_STATUS 0x14
+
+typedef enum {
+ TYPE_LED,
+ TYPE_GPIO_OUTPUT,
+} tb0219_type_t;
+
+/*
+ * Minor device number
+ * 0 = 7 segment LED
+ *
+ * 16 = GPIO IN 0
+ * 17 = GPIO IN 1
+ * 18 = GPIO IN 2
+ * 19 = GPIO IN 3
+ * 20 = GPIO IN 4
+ * 21 = GPIO IN 5
+ * 22 = GPIO IN 6
+ * 23 = GPIO IN 7
+ *
+ * 32 = GPIO OUT 0
+ * 33 = GPIO OUT 1
+ * 34 = GPIO OUT 2
+ * 35 = GPIO OUT 3
+ * 36 = GPIO OUT 4
+ * 37 = GPIO OUT 5
+ * 38 = GPIO OUT 6
+ * 39 = GPIO OUT 7
+ *
+ * 48 = DIP switch 1
+ * 49 = DIP switch 2
+ * 50 = DIP switch 3
+ * 51 = DIP switch 4
+ * 52 = DIP switch 5
+ * 53 = DIP switch 6
+ * 54 = DIP switch 7
+ * 55 = DIP switch 8
+ */
+
+static inline char get_led(void)
+{
+ return (char)tb0219_read(TB0219_LED);
+}
+
+static inline char get_gpio_input_pin(unsigned int pin)
+{
+ uint16_t values;
+
+ values = tb0219_read(TB0219_GPIO_INPUT);
+ if (values & (1 << pin))
+ return '1';
+
+ return '0';
+}
+
+static inline char get_gpio_output_pin(unsigned int pin)
+{
+ uint16_t values;
+
+ values = tb0219_read(TB0219_GPIO_OUTPUT);
+ if (values & (1 << pin))
+ return '1';
+
+ return '0';
+}
+
+static inline char get_dip_switch(unsigned int pin)
+{
+ uint16_t values;
+
+ values = tb0219_read(TB0219_DIP_SWITCH);
+ if (values & (1 << pin))
+ return '1';
+
+ return '0';
+}
+
+static inline int set_led(char command)
+{
+ tb0219_write(TB0219_LED, command);
+
+ return 0;
+}
+
+static inline int set_gpio_output_pin(unsigned int pin, char command)
+{
+ unsigned long flags;
+ uint16_t value;
+
+ if (command != '0' && command != '1')
+ return -EINVAL;
+
+ spin_lock_irqsave(&tb0219_lock, flags);
+ value = tb0219_read(TB0219_GPIO_OUTPUT);
+ if (command == '0')
+ value &= ~(1 << pin);
+ else
+ value |= 1 << pin;
+ tb0219_write(TB0219_GPIO_OUTPUT, value);
+ spin_unlock_irqrestore(&tb0219_lock, flags);
+
+ return 0;
+
+}
+
+static ssize_t tanbac_tb0219_read(struct file *file, char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ unsigned int minor;
+ char value;
+
+ minor = iminor(file->f_dentry->d_inode);
+ switch (minor) {
+ case 0:
+ value = get_led();
+ break;
+ case 16 ... 23:
+ value = get_gpio_input_pin(minor - 16);
+ break;
+ case 32 ... 39:
+ value = get_gpio_output_pin(minor - 32);
+ break;
+ case 48 ... 55:
+ value = get_dip_switch(minor - 48);
+ break;
+ default:
+ return -EBADF;
+ }
+
+ if (len <= 0)
+ return -EFAULT;
+
+ if (put_user(value, buf))
+ return -EFAULT;
+
+ return 1;
+}
+
+static ssize_t tanbac_tb0219_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ unsigned int minor;
+ tb0219_type_t type;
+ size_t i;
+ int retval = 0;
+ char c;
+
+ minor = iminor(file->f_dentry->d_inode);
+ switch (minor) {
+ case 0:
+ type = TYPE_LED;
+ break;
+ case 32 ... 39:
+ type = TYPE_GPIO_OUTPUT;
+ break;
+ default:
+ return -EBADF;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ switch (type) {
+ case TYPE_LED:
+ retval = set_led(c);
+ break;
+ case TYPE_GPIO_OUTPUT:
+ retval = set_gpio_output_pin(minor - 32, c);
+ break;
+ }
+
+ if (retval < 0)
+ break;
+ }
+
+ return i;
+}
+
+static int tanbac_tb0219_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor;
+
+ minor = iminor(inode);
+ switch (minor) {
+ case 0:
+ case 16 ... 23:
+ case 32 ... 39:
+ case 48 ... 55:
+ return nonseekable_open(inode, file);
+ default:
+ break;
+ }
+
+ return -EBADF;
+}
+
+static int tanbac_tb0219_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static struct file_operations tb0219_fops = {
+ .owner = THIS_MODULE,
+ .read = tanbac_tb0219_read,
+ .write = tanbac_tb0219_write,
+ .open = tanbac_tb0219_open,
+ .release = tanbac_tb0219_release,
+};
+
+static void tb0219_restart(char *command)
+{
+ tb0219_write(TB0219_RESET, 0);
+}
+
+static int tb0219_probe(struct device *dev)
+{
+ int retval;
+
+ if (request_mem_region(TB0219_START, TB0219_SIZE, "TB0219") == NULL)
+ return -EBUSY;
+
+ tb0219_base = ioremap(TB0219_START, TB0219_SIZE);
+ if (tb0219_base == NULL) {
+ release_mem_region(TB0219_START, TB0219_SIZE);
+ return -ENOMEM;
+ }
+
+ retval = register_chrdev(major, "TB0219", &tb0219_fops);
+ if (retval < 0) {
+ iounmap(tb0219_base);
+ tb0219_base = NULL;
+ release_mem_region(TB0219_START, TB0219_SIZE);
+ return retval;
+ }
+
+ spin_lock_init(&tb0219_lock);
+
+ old_machine_restart = _machine_restart;
+ _machine_restart = tb0219_restart;
+
+ if (major == 0) {
+ major = retval;
+ printk(KERN_INFO "TB0219: major number %d\n", major);
+ }
+
+ return 0;
+}
+
+static int tb0219_remove(struct device *dev)
+{
+ _machine_restart = old_machine_restart;
+
+ iounmap(tb0219_base);
+ tb0219_base = NULL;
+
+ release_mem_region(TB0219_START, TB0219_SIZE);
+
+ return 0;
+}
+
+static struct platform_device *tb0219_platform_device;
+
+static struct device_driver tb0219_device_driver = {
+ .name = "TB0219",
+ .bus = &platform_bus_type,
+ .probe = tb0219_probe,
+ .remove = tb0219_remove,
+};
+
+static int __devinit tanbac_tb0219_init(void)
+{
+ int retval;
+
+ tb0219_platform_device = platform_device_register_simple("TB0219", -1, NULL, 0);
+ if (IS_ERR(tb0219_platform_device))
+ return PTR_ERR(tb0219_platform_device);
+
+ retval = driver_register(&tb0219_device_driver);
+ if (retval < 0)
+ platform_device_unregister(tb0219_platform_device);
+
+ return retval;
+}
+
+static void __devexit tanbac_tb0219_exit(void)
+{
+ driver_unregister(&tb0219_device_driver);
+
+ platform_device_unregister(tb0219_platform_device);
+}
+
+module_init(tanbac_tb0219_init);
+module_exit(tanbac_tb0219_exit);
diff --git a/drivers/char/tipar.c b/drivers/char/tipar.c
new file mode 100644
index 000000000000..0c5ba9dc9063
--- /dev/null
+++ b/drivers/char/tipar.c
@@ -0,0 +1,564 @@
+/* Hey EMACS -*- linux-c -*-
+ *
+ * tipar - low level driver for handling a parallel link cable designed
+ * for Texas Instruments graphing calculators (http://lpg.ticalc.org).
+ * A part of the TiLP project.
+ *
+ * Copyright (C) 2000-2002, Romain Lievin <roms@lpg.ticalc.org>
+ * under the terms of the GNU General Public License.
+ *
+ * Various fixes & clean-up from the Linux Kernel Mailing List
+ * (Alan Cox, Richard B. Johnson, Christoph Hellwig).
+ */
+
+/* This driver should, in theory, work with any parallel port that has an
+ * appropriate low-level driver; all I/O is done through the parport
+ * abstraction layer.
+ *
+ * If this driver is built into the kernel, you can configure it using the
+ * kernel command-line. For example:
+ *
+ * tipar=timeout,delay (set timeout and delay)
+ *
+ * If the driver is loaded as a module, similar functionality is available
+ * using module parameters. The equivalent of the above commands would be:
+ *
+ * # insmod tipar timeout=15 delay=10
+ */
+
+/* COMPATIBILITY WITH OLD KERNELS
+ *
+ * Usually, parallel cables were bound to ports at
+ * particular I/O addresses, as follows:
+ *
+ * tipar0 0x378
+ * tipar1 0x278
+ * tipar2 0x3bc
+ *
+ *
+ * This driver, by default, binds tipar devices according to parport and
+ * the minor number.
+ *
+ */
+#undef DEBUG /* change to #define to get debugging
+ * output - for pr_debug() */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/fcntl.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <linux/devfs_fs_kernel.h> /* DevFs support */
+#include <linux/parport.h> /* Our code depend on parport */
+#include <linux/device.h>
+
+/*
+ * TI definitions
+ */
+#include <linux/ticable.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "1.19"
+#define DRIVER_AUTHOR "Romain Lievin <roms@lpg.ticalc.org>"
+#define DRIVER_DESC "Device driver for TI/PC parallel link cables"
+#define DRIVER_LICENSE "GPL"
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+
+/* ----- global variables --------------------------------------------- */
+
+struct tipar_struct {
+ struct pardevice *dev; /* Parport device entry */
+};
+
+#define PP_NO 3
+static struct tipar_struct table[PP_NO];
+
+static int delay = IO_DELAY; /* inter-bit delay in microseconds */
+static int timeout = TIMAXTIME; /* timeout in tenth of seconds */
+
+static unsigned int tp_count; /* tipar count */
+static unsigned long opened; /* opened devices */
+
+static struct class_simple *tipar_class;
+
+/* --- macros for parport access -------------------------------------- */
+
+#define r_dtr(x) (parport_read_data(table[(x)].dev->port))
+#define r_str(x) (parport_read_status(table[(x)].dev->port))
+#define w_ctr(x,y) (parport_write_control(table[(x)].dev->port, (y)))
+#define w_dtr(x,y) (parport_write_data(table[(x)].dev->port, (y)))
+
+/* --- setting states on the D-bus with the right timing: ------------- */
+
+static inline void
+outbyte(int value, int minor)
+{
+ w_dtr(minor, value);
+}
+
+static inline int
+inbyte(int minor)
+{
+ return (r_str(minor));
+}
+
+static inline void
+init_ti_parallel(int minor)
+{
+ outbyte(3, minor);
+}
+
+/* ----- global defines ----------------------------------------------- */
+
+#define START(x) { x = jiffies + (HZ * timeout) / 10; }
+#define WAIT(x) { \
+ if (time_before((x), jiffies)) return -1; \
+ if (need_resched()) schedule(); }
+
+/* ----- D-bus bit-banging functions ---------------------------------- */
+
+/* D-bus protocol (45kbit/s max):
+ 1 0 0
+ _______ ______|______ __________|________ __________
+Red : ________ | ____ | ____
+ _ ____________|________ ______|__________ _____
+White: ________ | ______ | _______
+*/
+
+/* Try to transmit a byte on the specified port (-1 if error). */
+static int
+put_ti_parallel(int minor, unsigned char data)
+{
+ unsigned int bit;
+ unsigned long max;
+
+ for (bit = 0; bit < 8; bit++) {
+ if (data & 1) {
+ outbyte(2, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (inbyte(minor) & 0x10);
+
+ outbyte(3, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (!(inbyte(minor) & 0x10));
+ } else {
+ outbyte(1, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (inbyte(minor) & 0x20);
+
+ outbyte(3, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (!(inbyte(minor) & 0x20));
+ }
+
+ data >>= 1;
+ udelay(delay);
+
+ if (need_resched())
+ schedule();
+ }
+
+ return 0;
+}
+
+/* Receive a byte on the specified port or -1 if error. */
+static int
+get_ti_parallel(int minor)
+{
+ unsigned int bit;
+ unsigned char v, data = 0;
+ unsigned long max;
+
+ for (bit = 0; bit < 8; bit++) {
+ START(max);
+ do {
+ WAIT(max);
+ } while ((v = inbyte(minor) & 0x30) == 0x30);
+
+ if (v == 0x10) {
+ data = (data >> 1) | 0x80;
+ outbyte(1, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (!(inbyte(minor) & 0x20));
+ outbyte(3, minor);
+ } else {
+ data = data >> 1;
+ outbyte(2, minor);
+ START(max);
+ do {
+ WAIT(max);
+ } while (!(inbyte(minor) & 0x10));
+ outbyte(3, minor);
+ }
+
+ udelay(delay);
+ if (need_resched())
+ schedule();
+ }
+
+ return (int) data;
+}
+
+/* Try to detect a parallel link cable on the specified port */
+static int
+probe_ti_parallel(int minor)
+{
+ int i;
+ int seq[] = { 0x00, 0x20, 0x10, 0x30 };
+
+ for (i = 3; i >= 0; i--) {
+ outbyte(3, minor);
+ outbyte(i, minor);
+ udelay(delay);
+ pr_debug("tipar: Probing -> %i: 0x%02x 0x%02x\n", i,
+ data & 0x30, seq[i]);
+ if ((inbyte(minor) & 0x30) != seq[i]) {
+ outbyte(3, minor);
+ return -1;
+ }
+ }
+
+ outbyte(3, minor);
+ return 0;
+}
+
+/* ----- kernel module functions--------------------------------------- */
+
+static int
+tipar_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor = iminor(inode) - TIPAR_MINOR;
+
+ if (minor > tp_count - 1)
+ return -ENXIO;
+
+ if (test_and_set_bit(minor, &opened))
+ return -EBUSY;
+
+ parport_claim_or_block(table[minor].dev);
+ init_ti_parallel(minor);
+ parport_release(table[minor].dev);
+
+ return nonseekable_open(inode, file);
+}
+
+static int
+tipar_close(struct inode *inode, struct file *file)
+{
+ unsigned int minor = iminor(inode) - TIPAR_MINOR;
+
+ if (minor > tp_count - 1)
+ return -ENXIO;
+
+ clear_bit(minor, &opened);
+
+ return 0;
+}
+
+static ssize_t
+tipar_write (struct file *file, const char __user *buf, size_t count,
+ loff_t * ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode) - TIPAR_MINOR;
+ ssize_t n;
+
+ parport_claim_or_block(table[minor].dev);
+
+ for (n = 0; n < count; n++) {
+ unsigned char b;
+
+ if (get_user(b, buf + n)) {
+ n = -EFAULT;
+ goto out;
+ }
+
+ if (put_ti_parallel(minor, b) == -1) {
+ init_ti_parallel(minor);
+ n = -ETIMEDOUT;
+ goto out;
+ }
+ }
+ out:
+ parport_release(table[minor].dev);
+ return n;
+}
+
+static ssize_t
+tipar_read(struct file *file, char __user *buf, size_t count, loff_t * ppos)
+{
+ int b = 0;
+ unsigned int minor = iminor(file->f_dentry->d_inode) - TIPAR_MINOR;
+ ssize_t retval = 0;
+ ssize_t n = 0;
+
+ if (count == 0)
+ return 0;
+
+ parport_claim_or_block(table[minor].dev);
+
+ while (n < count) {
+ b = get_ti_parallel(minor);
+ if (b == -1) {
+ init_ti_parallel(minor);
+ retval = -ETIMEDOUT;
+ goto out;
+ } else {
+ if (put_user(b, buf + n)) {
+ retval = -EFAULT;
+ break;
+ } else
+ retval = ++n;
+ }
+
+ /* Non-blocking mode : try again ! */
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto out;
+ }
+
+ /* Signal pending, try again ! */
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ goto out;
+ }
+
+ if (need_resched())
+ schedule();
+ }
+
+ out:
+ parport_release(table[minor].dev);
+ return retval;
+}
+
+static int
+tipar_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int retval = 0;
+
+ switch (cmd) {
+ case IOCTL_TIPAR_DELAY:
+ delay = (int)arg; //get_user(delay, &arg);
+ break;
+ case IOCTL_TIPAR_TIMEOUT:
+ if (arg != 0)
+ timeout = (int)arg;
+ else
+ retval = -EINVAL;
+ break;
+ default:
+ retval = -ENOTTY;
+ break;
+ }
+
+ return retval;
+}
+
+/* ----- kernel module registering ------------------------------------ */
+
+static struct file_operations tipar_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = tipar_read,
+ .write = tipar_write,
+ .ioctl = tipar_ioctl,
+ .open = tipar_open,
+ .release = tipar_close,
+};
+
+/* --- initialisation code ------------------------------------- */
+
+#ifndef MODULE
+/* You must set these - there is no sane way to probe for this cable.
+ * You can use 'tipar=timeout,delay' to set these now. */
+static int __init
+tipar_setup(char *str)
+{
+ int ints[2];
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+
+ if (ints[0] > 0) {
+ if (ints[1] != 0)
+ timeout = ints[1];
+ else
+ printk(KERN_WARNING "tipar: bad timeout value (0), "
+ "using default value instead");
+ if (ints[0] > 1) {
+ delay = ints[2];
+ }
+ }
+
+ return 1;
+}
+#endif
+
+/*
+ * Register our module into parport.
+ * Pass also 2 callbacks functions to parport: a pre-emptive function and an
+ * interrupt handler function (unused).
+ * Display a message such "tipar0: using parport0 (polling)".
+ */
+static int
+tipar_register(int nr, struct parport *port)
+{
+ int err = 0;
+
+ /* Register our module into parport */
+ table[nr].dev = parport_register_device(port, "tipar",
+ NULL, NULL, NULL, 0,
+ (void *) &table[nr]);
+
+ if (table[nr].dev == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ class_simple_device_add(tipar_class, MKDEV(TIPAR_MAJOR,
+ TIPAR_MINOR + nr), NULL, "par%d", nr);
+ /* Use devfs, tree: /dev/ticables/par/[0..2] */
+ err = devfs_mk_cdev(MKDEV(TIPAR_MAJOR, TIPAR_MINOR + nr),
+ S_IFCHR | S_IRUGO | S_IWUGO,
+ "ticables/par/%d", nr);
+ if (err)
+ goto out_class;
+
+ /* Display informations */
+ pr_info("tipar%d: using %s (%s)\n", nr, port->name, (port->irq ==
+ PARPORT_IRQ_NONE) ? "polling" : "interrupt-driven");
+
+ if (probe_ti_parallel(nr) != -1)
+ pr_info("tipar%d: link cable found\n", nr);
+ else
+ pr_info("tipar%d: link cable not found\n", nr);
+
+ err = 0;
+ goto out;
+
+out_class:
+ class_simple_device_remove(MKDEV(TIPAR_MAJOR, TIPAR_MINOR + nr));
+ class_simple_destroy(tipar_class);
+out:
+ return err;
+}
+
+static void
+tipar_attach(struct parport *port)
+{
+ if (tp_count == PP_NO) {
+ pr_info("tipar: ignoring parallel port (max. %d)\n", PP_NO);
+ return;
+ }
+
+ if (!tipar_register(tp_count, port))
+ tp_count++;
+}
+
+static void
+tipar_detach(struct parport *port)
+{
+ /* Nothing to do */
+}
+
+static struct parport_driver tipar_driver = {
+ .name = "tipar",
+ .attach = tipar_attach,
+ .detach = tipar_detach,
+};
+
+static int __init
+tipar_init_module(void)
+{
+ int err = 0;
+
+ pr_info("tipar: parallel link cable driver, version %s\n",
+ DRIVER_VERSION);
+
+ if (register_chrdev(TIPAR_MAJOR, "tipar", &tipar_fops)) {
+ printk(KERN_ERR "tipar: unable to get major %d\n", TIPAR_MAJOR);
+ err = -EIO;
+ goto out;
+ }
+
+ /* Use devfs with tree: /dev/ticables/par/[0..2] */
+ devfs_mk_dir("ticables/par");
+
+ tipar_class = class_simple_create(THIS_MODULE, "ticables");
+ if (IS_ERR(tipar_class)) {
+ err = PTR_ERR(tipar_class);
+ goto out_chrdev;
+ }
+ if (parport_register_driver(&tipar_driver)) {
+ printk(KERN_ERR "tipar: unable to register with parport\n");
+ err = -EIO;
+ goto out;
+ }
+
+ err = 0;
+ goto out;
+
+out_chrdev:
+ unregister_chrdev(TIPAR_MAJOR, "tipar");
+out:
+ return err;
+}
+
+static void __exit
+tipar_cleanup_module(void)
+{
+ unsigned int i;
+
+ /* Unregistering module */
+ parport_unregister_driver(&tipar_driver);
+
+ unregister_chrdev(TIPAR_MAJOR, "tipar");
+
+ for (i = 0; i < PP_NO; i++) {
+ if (table[i].dev == NULL)
+ continue;
+ parport_unregister_device(table[i].dev);
+ class_simple_device_remove(MKDEV(TIPAR_MAJOR, i));
+ devfs_remove("ticables/par/%d", i);
+ }
+ class_simple_destroy(tipar_class);
+ devfs_remove("ticables/par");
+
+ pr_info("tipar: module unloaded\n");
+}
+
+/* --------------------------------------------------------------------- */
+
+__setup("tipar=", tipar_setup);
+module_init(tipar_init_module);
+module_exit(tipar_cleanup_module);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Timeout (default=1.5 seconds)");
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Inter-bit delay (default=10 microseconds)");
diff --git a/drivers/char/toshiba.c b/drivers/char/toshiba.c
new file mode 100644
index 000000000000..58e21fe44262
--- /dev/null
+++ b/drivers/char/toshiba.c
@@ -0,0 +1,532 @@
+/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
+ *
+ * Copyright (c) 1996-2001 Jonathan A. Buzzard (jonathan@buzzard.org.uk)
+ *
+ * Valuable assistance and patches from:
+ * Tom May <tom@you-bastards.com>
+ * Rob Napier <rnapier@employees.org>
+ *
+ * Fn status port numbers for machine ID's courtesy of
+ * 0xfc02: Scott Eisert <scott.e@sky-eye.com>
+ * 0xfc04: Steve VanDevender <stevev@efn.org>
+ * 0xfc08: Garth Berry <garth@itsbruce.net>
+ * 0xfc0a: Egbert Eich <eich@xfree86.org>
+ * 0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil>
+ * 0xfc11: Spencer Olson <solson@novell.com>
+ * 0xfc13: Claudius Frankewitz <kryp@gmx.de>
+ * 0xfc15: Tom May <tom@you-bastards.com>
+ * 0xfc17: Dave Konrad <konrad@xenia.it>
+ * 0xfc1a: George Betzos <betzos@engr.colostate.edu>
+ * 0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp>
+ * 0xfc1d: Arthur Liu <armie@slap.mine.nu>
+ * 0xfc5a: Jacques L'helgoualc'h <lhh@free.fr>
+ * 0xfcd1: Mr. Dave Konrad <konrad@xenia.it>
+ *
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ *
+ * This code is covered by the GNU GPL and you are free to make any
+ * changes you wish to it under the terms of the license. However the
+ * code has the potential to render your computer and/or someone else's
+ * unusable. Please proceed with care when modifying the code.
+ *
+ * Note: Unfortunately the laptop hardware can close the System Configuration
+ * Interface on it's own accord. It is therefore necessary for *all*
+ * programs using this driver to be aware that *any* SCI call can fail at
+ * *any* time. It is up to any program to be aware of this eventuality
+ * and take appropriate steps.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * The information used to write this driver has been obtained by reverse
+ * engineering the software supplied by Toshiba for their portable computers in
+ * strict accordance with the European Council Directive 92/250/EEC on the legal
+ * protection of computer programs, and it's implementation into English Law by
+ * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233).
+ *
+ */
+
+#define TOSH_VERSION "1.11 26/9/2001"
+#define TOSH_DEBUG 0
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+
+#include <linux/toshiba.h>
+
+#define TOSH_MINOR_DEV 181
+
+static int tosh_id = 0x0000;
+static int tosh_bios = 0x0000;
+static int tosh_date = 0x0000;
+static int tosh_sci = 0x0000;
+static int tosh_fan = 0;
+
+static int tosh_fn = 0;
+
+module_param(tosh_fn, int, 0);
+
+
+static int tosh_ioctl(struct inode *, struct file *, unsigned int,
+ unsigned long);
+
+
+static struct file_operations tosh_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = tosh_ioctl,
+};
+
+static struct miscdevice tosh_device = {
+ TOSH_MINOR_DEV,
+ "toshiba",
+ &tosh_fops
+};
+
+/*
+ * Read the Fn key status
+ */
+#ifdef CONFIG_PROC_FS
+static int tosh_fn_status(void)
+{
+ unsigned char scan;
+ unsigned long flags;
+
+ if (tosh_fn!=0) {
+ scan = inb(tosh_fn);
+ } else {
+ local_irq_save(flags);
+ outb(0x8e, 0xe4);
+ scan = inb(0xe5);
+ local_irq_restore(flags);
+ }
+
+ return (int) scan;
+}
+#endif
+
+
+/*
+ * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
+ */
+static int tosh_emulate_fan(SMMRegisters *regs)
+{
+ unsigned long eax,ecx,flags;
+ unsigned char al;
+
+ eax = regs->eax & 0xff00;
+ ecx = regs->ecx & 0xffff;
+
+ /* Portage 610CT */
+
+ if (tosh_id==0xfccb) {
+ if (eax==0xfe00) {
+ /* fan status */
+ local_irq_save(flags);
+ outb(0xbe, 0xe4);
+ al = inb(0xe5);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = (unsigned int) (al & 0x01);
+ }
+ if ((eax==0xff00) && (ecx==0x0000)) {
+ /* fan off */
+ local_irq_save(flags);
+ outb(0xbe, 0xe4);
+ al = inb(0xe5);
+ outb(0xbe, 0xe4);
+ outb (al | 0x01, 0xe5);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = 0x00;
+ }
+ if ((eax==0xff00) && (ecx==0x0001)) {
+ /* fan on */
+ local_irq_save(flags);
+ outb(0xbe, 0xe4);
+ al = inb(0xe5);
+ outb(0xbe, 0xe4);
+ outb(al & 0xfe, 0xe5);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = 0x01;
+ }
+ }
+
+ /* Tecra 700CS/CDT */
+
+ if (tosh_id==0xfccc) {
+ if (eax==0xfe00) {
+ /* fan status */
+ local_irq_save(flags);
+ outb(0xe0, 0xe4);
+ al = inb(0xe5);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = al & 0x01;
+ }
+ if ((eax==0xff00) && (ecx==0x0000)) {
+ /* fan off */
+ local_irq_save(flags);
+ outb(0xe0, 0xe4);
+ al = inb(0xe5);
+ outw(0xe0 | ((al & 0xfe) << 8), 0xe4);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = 0x00;
+ }
+ if ((eax==0xff00) && (ecx==0x0001)) {
+ /* fan on */
+ local_irq_save(flags);
+ outb(0xe0, 0xe4);
+ al = inb(0xe5);
+ outw(0xe0 | ((al | 0x01) << 8), 0xe4);
+ local_irq_restore(flags);
+ regs->eax = 0x00;
+ regs->ecx = 0x01;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Put the laptop into System Management Mode
+ */
+int tosh_smm(SMMRegisters *regs)
+{
+ int eax;
+
+ asm ("# load the values into the registers\n\t" \
+ "pushl %%eax\n\t" \
+ "movl 0(%%eax),%%edx\n\t" \
+ "push %%edx\n\t" \
+ "movl 4(%%eax),%%ebx\n\t" \
+ "movl 8(%%eax),%%ecx\n\t" \
+ "movl 12(%%eax),%%edx\n\t" \
+ "movl 16(%%eax),%%esi\n\t" \
+ "movl 20(%%eax),%%edi\n\t" \
+ "popl %%eax\n\t" \
+ "# call the System Management mode\n\t" \
+ "inb $0xb2,%%al\n\t"
+ "# fill out the memory with the values in the registers\n\t" \
+ "xchgl %%eax,(%%esp)\n\t"
+ "movl %%ebx,4(%%eax)\n\t" \
+ "movl %%ecx,8(%%eax)\n\t" \
+ "movl %%edx,12(%%eax)\n\t" \
+ "movl %%esi,16(%%eax)\n\t" \
+ "movl %%edi,20(%%eax)\n\t" \
+ "popl %%edx\n\t" \
+ "movl %%edx,0(%%eax)\n\t" \
+ "# setup the return value to the carry flag\n\t" \
+ "lahf\n\t" \
+ "shrl $8,%%eax\n\t" \
+ "andl $1,%%eax\n" \
+ : "=a" (eax)
+ : "a" (regs)
+ : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+
+ return eax;
+}
+
+
+static int tosh_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+ unsigned long arg)
+{
+ SMMRegisters regs;
+ SMMRegisters __user *argp = (SMMRegisters __user *)arg;
+ unsigned short ax,bx;
+ int err;
+
+ if (!argp)
+ return -EINVAL;
+
+ if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case TOSH_SMM:
+ ax = regs.eax & 0xff00;
+ bx = regs.ebx & 0xffff;
+ /* block HCI calls to read/write memory & PCI devices */
+ if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069))
+ return -EINVAL;
+
+ /* do we need to emulate the fan ? */
+ if (tosh_fan==1) {
+ if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {
+ err = tosh_emulate_fan(&regs);
+ break;
+ }
+ }
+ err = tosh_smm(&regs);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
+ return -EFAULT;
+
+ return (err==0) ? 0:-EINVAL;
+}
+
+
+/*
+ * Print the information for /proc/toshiba
+ */
+#ifdef CONFIG_PROC_FS
+static int tosh_get_info(char *buffer, char **start, off_t fpos, int length)
+{
+ char *temp;
+ int key;
+
+ temp = buffer;
+ key = tosh_fn_status();
+
+ /* Arguments
+ 0) Linux driver version (this will change if format changes)
+ 1) Machine ID
+ 2) SCI version
+ 3) BIOS version (major, minor)
+ 4) BIOS date (in SCI date format)
+ 5) Fn Key status
+ */
+
+ temp += sprintf(temp, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",
+ tosh_id,
+ (tosh_sci & 0xff00)>>8,
+ tosh_sci & 0xff,
+ (tosh_bios & 0xff00)>>8,
+ tosh_bios & 0xff,
+ tosh_date,
+ key);
+
+ return temp-buffer;
+}
+#endif
+
+
+/*
+ * Determine which port to use for the Fn key status
+ */
+static void tosh_set_fn_port(void)
+{
+ switch (tosh_id) {
+ case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:
+ case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:
+ case 0xfc5a:
+ tosh_fn = 0x62;
+ break;
+ case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:
+ case 0xfce2:
+ tosh_fn = 0x68;
+ break;
+ default:
+ tosh_fn = 0x00;
+ break;
+ }
+
+ return;
+}
+
+
+/*
+ * Get the machine identification number of the current model
+ */
+static int tosh_get_machine_id(void)
+{
+ int id;
+ SMMRegisters regs;
+ unsigned short bx,cx;
+ unsigned long address;
+
+ id = (0x100*(int) isa_readb(0xffffe))+((int) isa_readb(0xffffa));
+
+ /* do we have a SCTTable machine identication number on our hands */
+
+ if (id==0xfc2f) {
+
+ /* start by getting a pointer into the BIOS */
+
+ regs.eax = 0xc000;
+ regs.ebx = 0x0000;
+ regs.ecx = 0x0000;
+ tosh_smm(&regs);
+ bx = (unsigned short) (regs.ebx & 0xffff);
+
+ /* At this point in the Toshiba routines under MS Windows
+ the bx register holds 0xe6f5. However my code is producing
+ a different value! For the time being I will just fudge the
+ value. This has been verified on a Satellite Pro 430CDT,
+ Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
+#if TOSH_DEBUG
+ printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
+#endif
+ bx = 0xe6f5;
+
+ /* now twiddle with our pointer a bit */
+
+ address = 0x000f0000+bx;
+ cx = isa_readw(address);
+ address = 0x000f0009+bx+cx;
+ cx = isa_readw(address);
+ address = 0x000f000a+cx;
+ cx = isa_readw(address);
+
+ /* now construct our machine identification number */
+
+ id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);
+ }
+
+ return id;
+}
+
+
+/*
+ * Probe for the presence of a Toshiba laptop
+ *
+ * returns and non-zero if unable to detect the presence of a Toshiba
+ * laptop, otherwise zero and determines the Machine ID, BIOS version and
+ * date, and SCI version.
+ */
+static int tosh_probe(void)
+{
+ int i,major,minor,day,year,month,flag;
+ unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };
+ SMMRegisters regs;
+
+ /* extra sanity check for the string "TOSHIBA" in the BIOS because
+ some machines that are not Toshiba's pass the next test */
+
+ for (i=0;i<7;i++) {
+ if (isa_readb(0xfe010+i)!=signature[i]) {
+ printk("toshiba: not a supported Toshiba laptop\n");
+ return -ENODEV;
+ }
+ }
+
+ /* call the Toshiba SCI support check routine */
+
+ regs.eax = 0xf0f0;
+ regs.ebx = 0x0000;
+ regs.ecx = 0x0000;
+ flag = tosh_smm(&regs);
+
+ /* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
+
+ if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {
+ printk("toshiba: not a supported Toshiba laptop\n");
+ return -ENODEV;
+ }
+
+ /* if we get this far then we are running on a Toshiba (probably)! */
+
+ tosh_sci = regs.edx & 0xffff;
+
+ /* next get the machine ID of the current laptop */
+
+ tosh_id = tosh_get_machine_id();
+
+ /* get the BIOS version */
+
+ major = isa_readb(0xfe009)-'0';
+ minor = ((isa_readb(0xfe00b)-'0')*10)+(isa_readb(0xfe00c)-'0');
+ tosh_bios = (major*0x100)+minor;
+
+ /* get the BIOS date */
+
+ day = ((isa_readb(0xffff5)-'0')*10)+(isa_readb(0xffff6)-'0');
+ month = ((isa_readb(0xffff8)-'0')*10)+(isa_readb(0xffff9)-'0');
+ year = ((isa_readb(0xffffb)-'0')*10)+(isa_readb(0xffffc)-'0');
+ tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)
+ | ((day & 0x1f)<<1);
+
+
+ /* in theory we should check the ports we are going to use for the
+ fn key detection (and the fan on the Portage 610/Tecra700), and
+ then request them to stop other drivers using them. However as
+ the keyboard driver grabs 0x60-0x6f and the pic driver grabs
+ 0xa0-0xbf we can't. We just have to live dangerously and use the
+ ports anyway, oh boy! */
+
+ /* do we need to emulate the fan? */
+
+ if ((tosh_id==0xfccb) || (tosh_id==0xfccc))
+ tosh_fan = 1;
+
+ return 0;
+}
+
+int __init tosh_init(void)
+{
+ int retval;
+ /* are we running on a Toshiba laptop */
+
+ if (tosh_probe()!=0)
+ return -EIO;
+
+ printk(KERN_INFO "Toshiba System Managment Mode driver v"
+ TOSH_VERSION"\n");
+
+ /* set the port to use for Fn status if not specified as a parameter */
+ if (tosh_fn==0x00)
+ tosh_set_fn_port();
+
+ /* register the device file */
+ retval = misc_register(&tosh_device);
+ if(retval < 0)
+ return retval;
+
+#ifdef CONFIG_PROC_FS
+ /* register the proc entry */
+ if(create_proc_info_entry("toshiba", 0, NULL, tosh_get_info) == NULL){
+ misc_deregister(&tosh_device);
+ return -ENOMEM;
+ }
+#endif
+
+ return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ return tosh_init();
+}
+
+void cleanup_module(void)
+{
+ /* remove the proc entry */
+
+ remove_proc_entry("toshiba", NULL);
+
+ /* unregister the device file */
+
+ misc_deregister(&tosh_device);
+}
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_PARM_DESC(tosh_fn, "User specified Fn key detection port");
+MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");
+MODULE_DESCRIPTION("Toshiba laptop SMM driver");
+MODULE_SUPPORTED_DEVICE("toshiba");
+
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
new file mode 100644
index 000000000000..7a969778915a
--- /dev/null
+++ b/drivers/char/tpm/Kconfig
@@ -0,0 +1,39 @@
+#
+# TPM device configuration
+#
+
+menu "TPM devices"
+
+config TCG_TPM
+ tristate "TPM Hardware Support"
+ depends on EXPERIMENTAL && PCI
+ ---help---
+ If you have a TPM security chip in your system, which
+ implements the Trusted Computing Group's specification,
+ say Yes and it will be accessible from within Linux. For
+ more information see <http://www.trustedcomputinggroup.org>.
+ An implementation of the Trusted Software Stack (TSS), the
+ userspace enablement piece of the specification, can be
+ obtained at: <http://sourceforge.net/projects/trousers>. To
+ compile this driver as a module, choose M here; the module
+ will be called tpm. If unsure, say N.
+
+config TCG_NSC
+ tristate "National Semiconductor TPM Interface"
+ depends on TCG_TPM
+ ---help---
+ If you have a TPM security chip from National Semicondutor
+ say Yes and it will be accessible from within Linux. To
+ compile this driver as a module, choose M here; the module
+ will be called tpm_nsc.
+
+config TCG_ATMEL
+ tristate "Atmel TPM Interface"
+ depends on TCG_TPM
+ ---help---
+ If you have a TPM security chip from Atmel say Yes and it
+ will be accessible from within Linux. To compile this driver
+ as a module, choose M here; the module will be called tpm_atmel.
+
+endmenu
+
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
new file mode 100644
index 000000000000..736d3df266f5
--- /dev/null
+++ b/drivers/char/tpm/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the kernel tpm device drivers.
+#
+obj-$(CONFIG_TCG_TPM) += tpm.o
+obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
+obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
+
diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c
new file mode 100644
index 000000000000..8318268169d6
--- /dev/null
+++ b/drivers/char/tpm/tpm.c
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * Note, the TPM chip is not interrupt driven (only polling)
+ * and can have very long timeouts (minutes!). Hence the unusual
+ * calls to schedule_timeout.
+ *
+ */
+
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include "tpm.h"
+
+#define TPM_MINOR 224 /* officially assigned */
+
+#define TPM_BUFSIZE 2048
+
+/* PCI configuration addresses */
+#define PCI_GEN_PMCON_1 0xA0
+#define PCI_GEN1_DEC 0xE4
+#define PCI_LPC_EN 0xE6
+#define PCI_GEN2_DEC 0xEC
+
+static LIST_HEAD(tpm_chip_list);
+static DEFINE_SPINLOCK(driver_lock);
+static int dev_mask[32];
+
+static void user_reader_timeout(unsigned long ptr)
+{
+ struct tpm_chip *chip = (struct tpm_chip *) ptr;
+
+ down(&chip->buffer_mutex);
+ atomic_set(&chip->data_pending, 0);
+ memset(chip->data_buffer, 0, TPM_BUFSIZE);
+ up(&chip->buffer_mutex);
+}
+
+void tpm_time_expired(unsigned long ptr)
+{
+ int *exp = (int *) ptr;
+ *exp = 1;
+}
+
+EXPORT_SYMBOL_GPL(tpm_time_expired);
+
+/*
+ * Initialize the LPC bus and enable the TPM ports
+ */
+int tpm_lpc_bus_init(struct pci_dev *pci_dev, u16 base)
+{
+ u32 lpcenable, tmp;
+ int is_lpcm = 0;
+
+ switch (pci_dev->vendor) {
+ case PCI_VENDOR_ID_INTEL:
+ switch (pci_dev->device) {
+ case PCI_DEVICE_ID_INTEL_82801CA_12:
+ case PCI_DEVICE_ID_INTEL_82801DB_12:
+ is_lpcm = 1;
+ break;
+ }
+ /* init ICH (enable LPC) */
+ pci_read_config_dword(pci_dev, PCI_GEN1_DEC, &lpcenable);
+ lpcenable |= 0x20000000;
+ pci_write_config_dword(pci_dev, PCI_GEN1_DEC, lpcenable);
+
+ if (is_lpcm) {
+ pci_read_config_dword(pci_dev, PCI_GEN1_DEC,
+ &lpcenable);
+ if ((lpcenable & 0x20000000) == 0) {
+ dev_err(&pci_dev->dev,
+ "cannot enable LPC\n");
+ return -ENODEV;
+ }
+ }
+
+ /* initialize TPM registers */
+ pci_read_config_dword(pci_dev, PCI_GEN2_DEC, &tmp);
+
+ if (!is_lpcm)
+ tmp = (tmp & 0xFFFF0000) | (base & 0xFFF0);
+ else
+ tmp =
+ (tmp & 0xFFFF0000) | (base & 0xFFF0) |
+ 0x00000001;
+
+ pci_write_config_dword(pci_dev, PCI_GEN2_DEC, tmp);
+
+ if (is_lpcm) {
+ pci_read_config_dword(pci_dev, PCI_GEN_PMCON_1,
+ &tmp);
+ tmp |= 0x00000004; /* enable CLKRUN */
+ pci_write_config_dword(pci_dev, PCI_GEN_PMCON_1,
+ tmp);
+ }
+ tpm_write_index(0x0D, 0x55); /* unlock 4F */
+ tpm_write_index(0x0A, 0x00); /* int disable */
+ tpm_write_index(0x08, base); /* base addr lo */
+ tpm_write_index(0x09, (base & 0xFF00) >> 8); /* base addr hi */
+ tpm_write_index(0x0D, 0xAA); /* lock 4F */
+ break;
+ case PCI_VENDOR_ID_AMD:
+ /* nothing yet */
+ break;
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_lpc_bus_init);
+
+/*
+ * Internal kernel interface to transmit TPM commands
+ */
+static ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf,
+ size_t bufsiz)
+{
+ ssize_t len;
+ u32 count;
+ __be32 *native_size;
+
+ native_size = (__force __be32 *) (buf + 2);
+ count = be32_to_cpu(*native_size);
+
+ if (count == 0)
+ return -ENODATA;
+ if (count > bufsiz) {
+ dev_err(&chip->pci_dev->dev,
+ "invalid count value %x %x \n", count, bufsiz);
+ return -E2BIG;
+ }
+
+ down(&chip->tpm_mutex);
+
+ if ((len = chip->vendor->send(chip, (u8 *) buf, count)) < 0) {
+ dev_err(&chip->pci_dev->dev,
+ "tpm_transmit: tpm_send: error %d\n", len);
+ return len;
+ }
+
+ down(&chip->timer_manipulation_mutex);
+ chip->time_expired = 0;
+ init_timer(&chip->device_timer);
+ chip->device_timer.function = tpm_time_expired;
+ chip->device_timer.expires = jiffies + 2 * 60 * HZ;
+ chip->device_timer.data = (unsigned long) &chip->time_expired;
+ add_timer(&chip->device_timer);
+ up(&chip->timer_manipulation_mutex);
+
+ do {
+ u8 status = inb(chip->vendor->base + 1);
+ if ((status & chip->vendor->req_complete_mask) ==
+ chip->vendor->req_complete_val) {
+ down(&chip->timer_manipulation_mutex);
+ del_singleshot_timer_sync(&chip->device_timer);
+ up(&chip->timer_manipulation_mutex);
+ goto out_recv;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(TPM_TIMEOUT);
+ rmb();
+ } while (!chip->time_expired);
+
+
+ chip->vendor->cancel(chip);
+ dev_err(&chip->pci_dev->dev, "Time expired\n");
+ up(&chip->tpm_mutex);
+ return -EIO;
+
+out_recv:
+ len = chip->vendor->recv(chip, (u8 *) buf, bufsiz);
+ if (len < 0)
+ dev_err(&chip->pci_dev->dev,
+ "tpm_transmit: tpm_recv: error %d\n", len);
+ up(&chip->tpm_mutex);
+ return len;
+}
+
+#define TPM_DIGEST_SIZE 20
+#define CAP_PCR_RESULT_SIZE 18
+static u8 cap_pcr[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 22, /* length */
+ 0, 0, 0, 101, /* TPM_ORD_GetCapability */
+ 0, 0, 0, 5,
+ 0, 0, 0, 4,
+ 0, 0, 1, 1
+};
+
+#define READ_PCR_RESULT_SIZE 30
+static u8 pcrread[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 14, /* length */
+ 0, 0, 0, 21, /* TPM_ORD_PcrRead */
+ 0, 0, 0, 0 /* PCR index */
+};
+
+static ssize_t show_pcrs(struct device *dev, char *buf)
+{
+ u8 data[READ_PCR_RESULT_SIZE];
+ ssize_t len;
+ int i, j, index, num_pcrs;
+ char *str = buf;
+
+ struct tpm_chip *chip =
+ pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+ if (chip == NULL)
+ return -ENODEV;
+
+ memcpy(data, cap_pcr, sizeof(cap_pcr));
+ if ((len = tpm_transmit(chip, data, sizeof(data)))
+ < CAP_PCR_RESULT_SIZE)
+ return len;
+
+ num_pcrs = be32_to_cpu(*((__force __be32 *) (data + 14)));
+
+ for (i = 0; i < num_pcrs; i++) {
+ memcpy(data, pcrread, sizeof(pcrread));
+ index = cpu_to_be32(i);
+ memcpy(data + 10, &index, 4);
+ if ((len = tpm_transmit(chip, data, sizeof(data)))
+ < READ_PCR_RESULT_SIZE)
+ return len;
+ str += sprintf(str, "PCR-%02d: ", i);
+ for (j = 0; j < TPM_DIGEST_SIZE; j++)
+ str += sprintf(str, "%02X ", *(data + 10 + j));
+ str += sprintf(str, "\n");
+ }
+ return str - buf;
+}
+
+static DEVICE_ATTR(pcrs, S_IRUGO, show_pcrs, NULL);
+
+#define READ_PUBEK_RESULT_SIZE 314
+static u8 readpubek[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 30, /* length */
+ 0, 0, 0, 124, /* TPM_ORD_ReadPubek */
+};
+
+static ssize_t show_pubek(struct device *dev, char *buf)
+{
+ u8 data[READ_PUBEK_RESULT_SIZE];
+ ssize_t len;
+ __be32 *native_val;
+ int i;
+ char *str = buf;
+
+ struct tpm_chip *chip =
+ pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+ if (chip == NULL)
+ return -ENODEV;
+
+ memcpy(data, readpubek, sizeof(readpubek));
+ memset(data + sizeof(readpubek), 0, 20); /* zero nonce */
+
+ if ((len = tpm_transmit(chip, data, sizeof(data))) <
+ READ_PUBEK_RESULT_SIZE)
+ return len;
+
+ /*
+ ignore header 10 bytes
+ algorithm 32 bits (1 == RSA )
+ encscheme 16 bits
+ sigscheme 16 bits
+ parameters (RSA 12->bytes: keybit, #primes, expbit)
+ keylenbytes 32 bits
+ 256 byte modulus
+ ignore checksum 20 bytes
+ */
+
+ native_val = (__force __be32 *) (data + 34);
+
+ str +=
+ sprintf(str,
+ "Algorithm: %02X %02X %02X %02X\nEncscheme: %02X %02X\n"
+ "Sigscheme: %02X %02X\nParameters: %02X %02X %02X %02X"
+ " %02X %02X %02X %02X %02X %02X %02X %02X\n"
+ "Modulus length: %d\nModulus: \n",
+ data[10], data[11], data[12], data[13], data[14],
+ data[15], data[16], data[17], data[22], data[23],
+ data[24], data[25], data[26], data[27], data[28],
+ data[29], data[30], data[31], data[32], data[33],
+ be32_to_cpu(*native_val)
+ );
+
+ for (i = 0; i < 256; i++) {
+ str += sprintf(str, "%02X ", data[i + 39]);
+ if ((i + 1) % 16 == 0)
+ str += sprintf(str, "\n");
+ }
+ return str - buf;
+}
+
+static DEVICE_ATTR(pubek, S_IRUGO, show_pubek, NULL);
+
+#define CAP_VER_RESULT_SIZE 18
+static u8 cap_version[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 18, /* length */
+ 0, 0, 0, 101, /* TPM_ORD_GetCapability */
+ 0, 0, 0, 6,
+ 0, 0, 0, 0
+};
+
+#define CAP_MANUFACTURER_RESULT_SIZE 18
+static u8 cap_manufacturer[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 22, /* length */
+ 0, 0, 0, 101, /* TPM_ORD_GetCapability */
+ 0, 0, 0, 5,
+ 0, 0, 0, 4,
+ 0, 0, 1, 3
+};
+
+static ssize_t show_caps(struct device *dev, char *buf)
+{
+ u8 data[READ_PUBEK_RESULT_SIZE];
+ ssize_t len;
+ char *str = buf;
+
+ struct tpm_chip *chip =
+ pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+ if (chip == NULL)
+ return -ENODEV;
+
+ memcpy(data, cap_manufacturer, sizeof(cap_manufacturer));
+
+ if ((len = tpm_transmit(chip, data, sizeof(data))) <
+ CAP_MANUFACTURER_RESULT_SIZE)
+ return len;
+
+ str += sprintf(str, "Manufacturer: 0x%x\n",
+ be32_to_cpu(*(data + 14)));
+
+ memcpy(data, cap_version, sizeof(cap_version));
+
+ if ((len = tpm_transmit(chip, data, sizeof(data))) <
+ CAP_VER_RESULT_SIZE)
+ return len;
+
+ str +=
+ sprintf(str, "TCG version: %d.%d\nFirmware version: %d.%d\n",
+ (int) data[14], (int) data[15], (int) data[16],
+ (int) data[17]);
+
+ return str - buf;
+}
+
+static DEVICE_ATTR(caps, S_IRUGO, show_caps, NULL);
+
+/*
+ * Device file system interface to the TPM
+ */
+int tpm_open(struct inode *inode, struct file *file)
+{
+ int rc = 0, minor = iminor(inode);
+ struct tpm_chip *chip = NULL, *pos;
+
+ spin_lock(&driver_lock);
+
+ list_for_each_entry(pos, &tpm_chip_list, list) {
+ if (pos->vendor->miscdev.minor == minor) {
+ chip = pos;
+ break;
+ }
+ }
+
+ if (chip == NULL) {
+ rc = -ENODEV;
+ goto err_out;
+ }
+
+ if (chip->num_opens) {
+ dev_dbg(&chip->pci_dev->dev,
+ "Another process owns this TPM\n");
+ rc = -EBUSY;
+ goto err_out;
+ }
+
+ chip->num_opens++;
+ pci_dev_get(chip->pci_dev);
+
+ spin_unlock(&driver_lock);
+
+ chip->data_buffer = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL);
+ if (chip->data_buffer == NULL) {
+ chip->num_opens--;
+ pci_dev_put(chip->pci_dev);
+ return -ENOMEM;
+ }
+
+ atomic_set(&chip->data_pending, 0);
+
+ file->private_data = chip;
+ return 0;
+
+err_out:
+ spin_unlock(&driver_lock);
+ return rc;
+}
+
+EXPORT_SYMBOL_GPL(tpm_open);
+
+int tpm_release(struct inode *inode, struct file *file)
+{
+ struct tpm_chip *chip = file->private_data;
+
+ file->private_data = NULL;
+
+ spin_lock(&driver_lock);
+ chip->num_opens--;
+ spin_unlock(&driver_lock);
+
+ down(&chip->timer_manipulation_mutex);
+ if (timer_pending(&chip->user_read_timer))
+ del_singleshot_timer_sync(&chip->user_read_timer);
+ else if (timer_pending(&chip->device_timer))
+ del_singleshot_timer_sync(&chip->device_timer);
+ up(&chip->timer_manipulation_mutex);
+
+ kfree(chip->data_buffer);
+ atomic_set(&chip->data_pending, 0);
+
+ pci_dev_put(chip->pci_dev);
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_release);
+
+ssize_t tpm_write(struct file * file, const char __user * buf,
+ size_t size, loff_t * off)
+{
+ struct tpm_chip *chip = file->private_data;
+ int in_size = size, out_size;
+
+ /* cannot perform a write until the read has cleared
+ either via tpm_read or a user_read_timer timeout */
+ while (atomic_read(&chip->data_pending) != 0) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(TPM_TIMEOUT);
+ }
+
+ down(&chip->buffer_mutex);
+
+ if (in_size > TPM_BUFSIZE)
+ in_size = TPM_BUFSIZE;
+
+ if (copy_from_user
+ (chip->data_buffer, (void __user *) buf, in_size)) {
+ up(&chip->buffer_mutex);
+ return -EFAULT;
+ }
+
+ /* atomic tpm command send and result receive */
+ out_size = tpm_transmit(chip, chip->data_buffer, TPM_BUFSIZE);
+
+ atomic_set(&chip->data_pending, out_size);
+ up(&chip->buffer_mutex);
+
+ /* Set a timeout by which the reader must come claim the result */
+ down(&chip->timer_manipulation_mutex);
+ init_timer(&chip->user_read_timer);
+ chip->user_read_timer.function = user_reader_timeout;
+ chip->user_read_timer.data = (unsigned long) chip;
+ chip->user_read_timer.expires = jiffies + (60 * HZ);
+ add_timer(&chip->user_read_timer);
+ up(&chip->timer_manipulation_mutex);
+
+ return in_size;
+}
+
+EXPORT_SYMBOL_GPL(tpm_write);
+
+ssize_t tpm_read(struct file * file, char __user * buf,
+ size_t size, loff_t * off)
+{
+ struct tpm_chip *chip = file->private_data;
+ int ret_size = -ENODATA;
+
+ if (atomic_read(&chip->data_pending) != 0) { /* Result available */
+ down(&chip->timer_manipulation_mutex);
+ del_singleshot_timer_sync(&chip->user_read_timer);
+ up(&chip->timer_manipulation_mutex);
+
+ down(&chip->buffer_mutex);
+
+ ret_size = atomic_read(&chip->data_pending);
+ atomic_set(&chip->data_pending, 0);
+
+ if (ret_size == 0) /* timeout just occurred */
+ ret_size = -ETIME;
+ else if (ret_size > 0) { /* relay data */
+ if (size < ret_size)
+ ret_size = size;
+
+ if (copy_to_user((void __user *) buf,
+ chip->data_buffer, ret_size)) {
+ ret_size = -EFAULT;
+ }
+ }
+ up(&chip->buffer_mutex);
+ }
+
+ return ret_size;
+}
+
+EXPORT_SYMBOL_GPL(tpm_read);
+
+void __devexit tpm_remove(struct pci_dev *pci_dev)
+{
+ struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+
+ if (chip == NULL) {
+ dev_err(&pci_dev->dev, "No device data found\n");
+ return;
+ }
+
+ spin_lock(&driver_lock);
+
+ list_del(&chip->list);
+
+ spin_unlock(&driver_lock);
+
+ pci_set_drvdata(pci_dev, NULL);
+ misc_deregister(&chip->vendor->miscdev);
+
+ device_remove_file(&pci_dev->dev, &dev_attr_pubek);
+ device_remove_file(&pci_dev->dev, &dev_attr_pcrs);
+ device_remove_file(&pci_dev->dev, &dev_attr_caps);
+
+ pci_disable_device(pci_dev);
+
+ dev_mask[chip->dev_num / 32] &= !(1 << (chip->dev_num % 32));
+
+ kfree(chip);
+
+ pci_dev_put(pci_dev);
+}
+
+EXPORT_SYMBOL_GPL(tpm_remove);
+
+static u8 savestate[] = {
+ 0, 193, /* TPM_TAG_RQU_COMMAND */
+ 0, 0, 0, 10, /* blob length (in bytes) */
+ 0, 0, 0, 152 /* TPM_ORD_SaveState */
+};
+
+/*
+ * We are about to suspend. Save the TPM state
+ * so that it can be restored.
+ */
+int tpm_pm_suspend(struct pci_dev *pci_dev, u32 pm_state)
+{
+ struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+ if (chip == NULL)
+ return -ENODEV;
+
+ tpm_transmit(chip, savestate, sizeof(savestate));
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_pm_suspend);
+
+/*
+ * Resume from a power safe. The BIOS already restored
+ * the TPM state.
+ */
+int tpm_pm_resume(struct pci_dev *pci_dev)
+{
+ struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+
+ if (chip == NULL)
+ return -ENODEV;
+
+ spin_lock(&driver_lock);
+ tpm_lpc_bus_init(pci_dev, chip->vendor->base);
+ spin_unlock(&driver_lock);
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_pm_resume);
+
+/*
+ * Called from tpm_<specific>.c probe function only for devices
+ * the driver has determined it should claim. Prior to calling
+ * this function the specific probe function has called pci_enable_device
+ * upon errant exit from this function specific probe function should call
+ * pci_disable_device
+ */
+int tpm_register_hardware(struct pci_dev *pci_dev,
+ struct tpm_vendor_specific *entry)
+{
+ char devname[7];
+ struct tpm_chip *chip;
+ int i, j;
+
+ /* Driver specific per-device data */
+ chip = kmalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ memset(chip, 0, sizeof(struct tpm_chip));
+
+ init_MUTEX(&chip->buffer_mutex);
+ init_MUTEX(&chip->tpm_mutex);
+ init_MUTEX(&chip->timer_manipulation_mutex);
+ INIT_LIST_HEAD(&chip->list);
+
+ chip->vendor = entry;
+
+ chip->dev_num = -1;
+
+ for (i = 0; i < 32; i++)
+ for (j = 0; j < 8; j++)
+ if ((dev_mask[i] & (1 << j)) == 0) {
+ chip->dev_num = i * 32 + j;
+ dev_mask[i] |= 1 << j;
+ goto dev_num_search_complete;
+ }
+
+dev_num_search_complete:
+ if (chip->dev_num < 0) {
+ dev_err(&pci_dev->dev,
+ "No available tpm device numbers\n");
+ kfree(chip);
+ return -ENODEV;
+ } else if (chip->dev_num == 0)
+ chip->vendor->miscdev.minor = TPM_MINOR;
+ else
+ chip->vendor->miscdev.minor = MISC_DYNAMIC_MINOR;
+
+ snprintf(devname, sizeof(devname), "%s%d", "tpm", chip->dev_num);
+ chip->vendor->miscdev.name = devname;
+
+ chip->vendor->miscdev.dev = &(pci_dev->dev);
+ chip->pci_dev = pci_dev_get(pci_dev);
+
+ if (misc_register(&chip->vendor->miscdev)) {
+ dev_err(&chip->pci_dev->dev,
+ "unable to misc_register %s, minor %d\n",
+ chip->vendor->miscdev.name,
+ chip->vendor->miscdev.minor);
+ pci_dev_put(pci_dev);
+ kfree(chip);
+ dev_mask[i] &= !(1 << j);
+ return -ENODEV;
+ }
+
+ pci_set_drvdata(pci_dev, chip);
+
+ list_add(&chip->list, &tpm_chip_list);
+
+ device_create_file(&pci_dev->dev, &dev_attr_pubek);
+ device_create_file(&pci_dev->dev, &dev_attr_pcrs);
+ device_create_file(&pci_dev->dev, &dev_attr_caps);
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_register_hardware);
+
+static int __init init_tpm(void)
+{
+ return 0;
+}
+
+static void __exit cleanup_tpm(void)
+{
+
+}
+
+module_init(init_tpm);
+module_exit(cleanup_tpm);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
new file mode 100644
index 000000000000..575cf5aed41a
--- /dev/null
+++ b/drivers/char/tpm/tpm.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+
+#define TPM_TIMEOUT msecs_to_jiffies(5)
+
+/* TPM addresses */
+#define TPM_ADDR 0x4E
+#define TPM_DATA 0x4F
+
+struct tpm_chip;
+
+struct tpm_vendor_specific {
+ u8 req_complete_mask;
+ u8 req_complete_val;
+ u16 base; /* TPM base address */
+
+ int (*recv) (struct tpm_chip *, u8 *, size_t);
+ int (*send) (struct tpm_chip *, u8 *, size_t);
+ void (*cancel) (struct tpm_chip *);
+ struct miscdevice miscdev;
+};
+
+struct tpm_chip {
+ struct pci_dev *pci_dev; /* PCI device stuff */
+
+ int dev_num; /* /dev/tpm# */
+ int num_opens; /* only one allowed */
+ int time_expired;
+
+ /* Data passed to and from the tpm via the read/write calls */
+ u8 *data_buffer;
+ atomic_t data_pending;
+ struct semaphore buffer_mutex;
+
+ struct timer_list user_read_timer; /* user needs to claim result */
+ struct semaphore tpm_mutex; /* tpm is processing */
+ struct timer_list device_timer; /* tpm is processing */
+ struct semaphore timer_manipulation_mutex;
+
+ struct tpm_vendor_specific *vendor;
+
+ struct list_head list;
+};
+
+static inline int tpm_read_index(int index)
+{
+ outb(index, TPM_ADDR);
+ return inb(TPM_DATA) & 0xFF;
+}
+
+static inline void tpm_write_index(int index, int value)
+{
+ outb(index, TPM_ADDR);
+ outb(value & 0xFF, TPM_DATA);
+}
+
+extern void tpm_time_expired(unsigned long);
+extern int tpm_lpc_bus_init(struct pci_dev *, u16);
+
+extern int tpm_register_hardware(struct pci_dev *,
+ struct tpm_vendor_specific *);
+extern int tpm_open(struct inode *, struct file *);
+extern int tpm_release(struct inode *, struct file *);
+extern ssize_t tpm_write(struct file *, const char __user *, size_t,
+ loff_t *);
+extern ssize_t tpm_read(struct file *, char __user *, size_t, loff_t *);
+extern void __devexit tpm_remove(struct pci_dev *);
+extern int tpm_pm_suspend(struct pci_dev *, u32);
+extern int tpm_pm_resume(struct pci_dev *);
diff --git a/drivers/char/tpm/tpm_atmel.c b/drivers/char/tpm/tpm_atmel.c
new file mode 100644
index 000000000000..f9333e729b62
--- /dev/null
+++ b/drivers/char/tpm/tpm_atmel.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ */
+
+#include "tpm.h"
+
+/* Atmel definitions */
+#define TPM_ATML_BASE 0x400
+
+/* write status bits */
+#define ATML_STATUS_ABORT 0x01
+#define ATML_STATUS_LASTBYTE 0x04
+
+/* read status bits */
+#define ATML_STATUS_BUSY 0x01
+#define ATML_STATUS_DATA_AVAIL 0x02
+#define ATML_STATUS_REWRITE 0x04
+
+
+static int tpm_atml_recv(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+ u8 status, *hdr = buf;
+ u32 size;
+ int i;
+ __be32 *native_size;
+
+ /* start reading header */
+ if (count < 6)
+ return -EIO;
+
+ for (i = 0; i < 6; i++) {
+ status = inb(chip->vendor->base + 1);
+ if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+ dev_err(&chip->pci_dev->dev,
+ "error reading header\n");
+ return -EIO;
+ }
+ *buf++ = inb(chip->vendor->base);
+ }
+
+ /* size of the data received */
+ native_size = (__force __be32 *) (hdr + 2);
+ size = be32_to_cpu(*native_size);
+
+ if (count < size) {
+ dev_err(&chip->pci_dev->dev,
+ "Recv size(%d) less than available space\n", size);
+ for (; i < size; i++) { /* clear the waiting data anyway */
+ status = inb(chip->vendor->base + 1);
+ if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+ dev_err(&chip->pci_dev->dev,
+ "error reading data\n");
+ return -EIO;
+ }
+ }
+ return -EIO;
+ }
+
+ /* read all the data available */
+ for (; i < size; i++) {
+ status = inb(chip->vendor->base + 1);
+ if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+ dev_err(&chip->pci_dev->dev,
+ "error reading data\n");
+ return -EIO;
+ }
+ *buf++ = inb(chip->vendor->base);
+ }
+
+ /* make sure data available is gone */
+ status = inb(chip->vendor->base + 1);
+ if (status & ATML_STATUS_DATA_AVAIL) {
+ dev_err(&chip->pci_dev->dev, "data available is stuck\n");
+ return -EIO;
+ }
+
+ return size;
+}
+
+static int tpm_atml_send(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+ int i;
+
+ dev_dbg(&chip->pci_dev->dev, "tpm_atml_send: ");
+ for (i = 0; i < count; i++) {
+ dev_dbg(&chip->pci_dev->dev, "0x%x(%d) ", buf[i], buf[i]);
+ outb(buf[i], chip->vendor->base);
+ }
+
+ return count;
+}
+
+static void tpm_atml_cancel(struct tpm_chip *chip)
+{
+ outb(ATML_STATUS_ABORT, chip->vendor->base + 1);
+}
+
+static struct file_operations atmel_ops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = tpm_open,
+ .read = tpm_read,
+ .write = tpm_write,
+ .release = tpm_release,
+};
+
+static struct tpm_vendor_specific tpm_atmel = {
+ .recv = tpm_atml_recv,
+ .send = tpm_atml_send,
+ .cancel = tpm_atml_cancel,
+ .req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL,
+ .req_complete_val = ATML_STATUS_DATA_AVAIL,
+ .base = TPM_ATML_BASE,
+ .miscdev = { .fops = &atmel_ops, },
+};
+
+static int __devinit tpm_atml_init(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ u8 version[4];
+ int rc = 0;
+
+ if (pci_enable_device(pci_dev))
+ return -EIO;
+
+ if (tpm_lpc_bus_init(pci_dev, TPM_ATML_BASE)) {
+ rc = -ENODEV;
+ goto out_err;
+ }
+
+ /* verify that it is an Atmel part */
+ if (tpm_read_index(4) != 'A' || tpm_read_index(5) != 'T'
+ || tpm_read_index(6) != 'M' || tpm_read_index(7) != 'L') {
+ rc = -ENODEV;
+ goto out_err;
+ }
+
+ /* query chip for its version number */
+ if ((version[0] = tpm_read_index(0x00)) != 0xFF) {
+ version[1] = tpm_read_index(0x01);
+ version[2] = tpm_read_index(0x02);
+ version[3] = tpm_read_index(0x03);
+ } else {
+ dev_info(&pci_dev->dev, "version query failed\n");
+ rc = -ENODEV;
+ goto out_err;
+ }
+
+ if ((rc = tpm_register_hardware(pci_dev, &tpm_atmel)) < 0)
+ goto out_err;
+
+ dev_info(&pci_dev->dev,
+ "Atmel TPM version %d.%d.%d.%d\n", version[0], version[1],
+ version[2], version[3]);
+
+ return 0;
+out_err:
+ pci_disable_device(pci_dev);
+ return rc;
+}
+
+static struct pci_device_id tpm_pci_tbl[] __devinitdata = {
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, tpm_pci_tbl);
+
+static struct pci_driver atmel_pci_driver = {
+ .name = "tpm_atmel",
+ .id_table = tpm_pci_tbl,
+ .probe = tpm_atml_init,
+ .remove = __devexit_p(tpm_remove),
+ .suspend = tpm_pm_suspend,
+ .resume = tpm_pm_resume,
+};
+
+static int __init init_atmel(void)
+{
+ return pci_register_driver(&atmel_pci_driver);
+}
+
+static void __exit cleanup_atmel(void)
+{
+ pci_unregister_driver(&atmel_pci_driver);
+}
+
+module_init(init_atmel);
+module_exit(cleanup_atmel);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tpm/tpm_nsc.c b/drivers/char/tpm/tpm_nsc.c
new file mode 100644
index 000000000000..9cce833a0923
--- /dev/null
+++ b/drivers/char/tpm/tpm_nsc.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ */
+
+#include "tpm.h"
+
+/* National definitions */
+#define TPM_NSC_BASE 0x360
+#define TPM_NSC_IRQ 0x07
+
+#define NSC_LDN_INDEX 0x07
+#define NSC_SID_INDEX 0x20
+#define NSC_LDC_INDEX 0x30
+#define NSC_DIO_INDEX 0x60
+#define NSC_CIO_INDEX 0x62
+#define NSC_IRQ_INDEX 0x70
+#define NSC_ITS_INDEX 0x71
+
+#define NSC_STATUS 0x01
+#define NSC_COMMAND 0x01
+#define NSC_DATA 0x00
+
+/* status bits */
+#define NSC_STATUS_OBF 0x01 /* output buffer full */
+#define NSC_STATUS_IBF 0x02 /* input buffer full */
+#define NSC_STATUS_F0 0x04 /* F0 */
+#define NSC_STATUS_A2 0x08 /* A2 */
+#define NSC_STATUS_RDY 0x10 /* ready to receive command */
+#define NSC_STATUS_IBR 0x20 /* ready to receive data */
+
+/* command bits */
+#define NSC_COMMAND_NORMAL 0x01 /* normal mode */
+#define NSC_COMMAND_EOC 0x03
+#define NSC_COMMAND_CANCEL 0x22
+
+/*
+ * Wait for a certain status to appear
+ */
+static int wait_for_stat(struct tpm_chip *chip, u8 mask, u8 val, u8 * data)
+{
+ int expired = 0;
+ struct timer_list status_timer =
+ TIMER_INITIALIZER(tpm_time_expired, jiffies + 10 * HZ,
+ (unsigned long) &expired);
+
+ /* status immediately available check */
+ *data = inb(chip->vendor->base + NSC_STATUS);
+ if ((*data & mask) == val)
+ return 0;
+
+ /* wait for status */
+ add_timer(&status_timer);
+ do {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(TPM_TIMEOUT);
+ *data = inb(chip->vendor->base + 1);
+ if ((*data & mask) == val) {
+ del_singleshot_timer_sync(&status_timer);
+ return 0;
+ }
+ }
+ while (!expired);
+
+ return -EBUSY;
+}
+
+static int nsc_wait_for_ready(struct tpm_chip *chip)
+{
+ int status;
+ int expired = 0;
+ struct timer_list status_timer =
+ TIMER_INITIALIZER(tpm_time_expired, jiffies + 100,
+ (unsigned long) &expired);
+
+ /* status immediately available check */
+ status = inb(chip->vendor->base + NSC_STATUS);
+ if (status & NSC_STATUS_OBF)
+ status = inb(chip->vendor->base + NSC_DATA);
+ if (status & NSC_STATUS_RDY)
+ return 0;
+
+ /* wait for status */
+ add_timer(&status_timer);
+ do {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(TPM_TIMEOUT);
+ status = inb(chip->vendor->base + NSC_STATUS);
+ if (status & NSC_STATUS_OBF)
+ status = inb(chip->vendor->base + NSC_DATA);
+ if (status & NSC_STATUS_RDY) {
+ del_singleshot_timer_sync(&status_timer);
+ return 0;
+ }
+ }
+ while (!expired);
+
+ dev_info(&chip->pci_dev->dev, "wait for ready failed\n");
+ return -EBUSY;
+}
+
+
+static int tpm_nsc_recv(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+ u8 *buffer = buf;
+ u8 data, *p;
+ u32 size;
+ __be32 *native_size;
+
+ if (count < 6)
+ return -EIO;
+
+ if (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0) {
+ dev_err(&chip->pci_dev->dev, "F0 timeout\n");
+ return -EIO;
+ }
+ if ((data =
+ inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_NORMAL) {
+ dev_err(&chip->pci_dev->dev, "not in normal mode (0x%x)\n",
+ data);
+ return -EIO;
+ }
+
+ /* read the whole packet */
+ for (p = buffer; p < &buffer[count]; p++) {
+ if (wait_for_stat
+ (chip, NSC_STATUS_OBF, NSC_STATUS_OBF, &data) < 0) {
+ dev_err(&chip->pci_dev->dev,
+ "OBF timeout (while reading data)\n");
+ return -EIO;
+ }
+ if (data & NSC_STATUS_F0)
+ break;
+ *p = inb(chip->vendor->base + NSC_DATA);
+ }
+
+ if ((data & NSC_STATUS_F0) == 0) {
+ dev_err(&chip->pci_dev->dev, "F0 not set\n");
+ return -EIO;
+ }
+ if ((data = inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_EOC) {
+ dev_err(&chip->pci_dev->dev,
+ "expected end of command(0x%x)\n", data);
+ return -EIO;
+ }
+
+ native_size = (__force __be32 *) (buf + 2);
+ size = be32_to_cpu(*native_size);
+
+ if (count < size)
+ return -EIO;
+
+ return size;
+}
+
+static int tpm_nsc_send(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+ u8 data;
+ int i;
+
+ /*
+ * If we hit the chip with back to back commands it locks up
+ * and never set IBF. Hitting it with this "hammer" seems to
+ * fix it. Not sure why this is needed, we followed the flow
+ * chart in the manual to the letter.
+ */
+ outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND);
+
+ if (nsc_wait_for_ready(chip) != 0)
+ return -EIO;
+
+ if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+ dev_err(&chip->pci_dev->dev, "IBF timeout\n");
+ return -EIO;
+ }
+
+ outb(NSC_COMMAND_NORMAL, chip->vendor->base + NSC_COMMAND);
+ if (wait_for_stat(chip, NSC_STATUS_IBR, NSC_STATUS_IBR, &data) < 0) {
+ dev_err(&chip->pci_dev->dev, "IBR timeout\n");
+ return -EIO;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+ dev_err(&chip->pci_dev->dev,
+ "IBF timeout (while writing data)\n");
+ return -EIO;
+ }
+ outb(buf[i], chip->vendor->base + NSC_DATA);
+ }
+
+ if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+ dev_err(&chip->pci_dev->dev, "IBF timeout\n");
+ return -EIO;
+ }
+ outb(NSC_COMMAND_EOC, chip->vendor->base + NSC_COMMAND);
+
+ return count;
+}
+
+static void tpm_nsc_cancel(struct tpm_chip *chip)
+{
+ outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND);
+}
+
+static struct file_operations nsc_ops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = tpm_open,
+ .read = tpm_read,
+ .write = tpm_write,
+ .release = tpm_release,
+};
+
+static struct tpm_vendor_specific tpm_nsc = {
+ .recv = tpm_nsc_recv,
+ .send = tpm_nsc_send,
+ .cancel = tpm_nsc_cancel,
+ .req_complete_mask = NSC_STATUS_OBF,
+ .req_complete_val = NSC_STATUS_OBF,
+ .base = TPM_NSC_BASE,
+ .miscdev = { .fops = &nsc_ops, },
+
+};
+
+static int __devinit tpm_nsc_init(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ int rc = 0;
+
+ if (pci_enable_device(pci_dev))
+ return -EIO;
+
+ if (tpm_lpc_bus_init(pci_dev, TPM_NSC_BASE)) {
+ rc = -ENODEV;
+ goto out_err;
+ }
+
+ /* verify that it is a National part (SID) */
+ if (tpm_read_index(NSC_SID_INDEX) != 0xEF) {
+ rc = -ENODEV;
+ goto out_err;
+ }
+
+ dev_dbg(&pci_dev->dev, "NSC TPM detected\n");
+ dev_dbg(&pci_dev->dev,
+ "NSC LDN 0x%x, SID 0x%x, SRID 0x%x\n",
+ tpm_read_index(0x07), tpm_read_index(0x20),
+ tpm_read_index(0x27));
+ dev_dbg(&pci_dev->dev,
+ "NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x\n",
+ tpm_read_index(0x21), tpm_read_index(0x25),
+ tpm_read_index(0x26), tpm_read_index(0x28));
+ dev_dbg(&pci_dev->dev, "NSC IO Base0 0x%x\n",
+ (tpm_read_index(0x60) << 8) | tpm_read_index(0x61));
+ dev_dbg(&pci_dev->dev, "NSC IO Base1 0x%x\n",
+ (tpm_read_index(0x62) << 8) | tpm_read_index(0x63));
+ dev_dbg(&pci_dev->dev, "NSC Interrupt number and wakeup 0x%x\n",
+ tpm_read_index(0x70));
+ dev_dbg(&pci_dev->dev, "NSC IRQ type select 0x%x\n",
+ tpm_read_index(0x71));
+ dev_dbg(&pci_dev->dev,
+ "NSC DMA channel select0 0x%x, select1 0x%x\n",
+ tpm_read_index(0x74), tpm_read_index(0x75));
+ dev_dbg(&pci_dev->dev,
+ "NSC Config "
+ "0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+ tpm_read_index(0xF0), tpm_read_index(0xF1),
+ tpm_read_index(0xF2), tpm_read_index(0xF3),
+ tpm_read_index(0xF4), tpm_read_index(0xF5),
+ tpm_read_index(0xF6), tpm_read_index(0xF7),
+ tpm_read_index(0xF8), tpm_read_index(0xF9));
+
+ dev_info(&pci_dev->dev,
+ "NSC PC21100 TPM revision %d\n",
+ tpm_read_index(0x27) & 0x1F);
+
+ if (tpm_read_index(NSC_LDC_INDEX) == 0)
+ dev_info(&pci_dev->dev, ": NSC TPM not active\n");
+
+ /* select PM channel 1 */
+ tpm_write_index(NSC_LDN_INDEX, 0x12);
+ tpm_read_index(NSC_LDN_INDEX);
+
+ /* disable the DPM module */
+ tpm_write_index(NSC_LDC_INDEX, 0);
+ tpm_read_index(NSC_LDC_INDEX);
+
+ /* set the data register base addresses */
+ tpm_write_index(NSC_DIO_INDEX, TPM_NSC_BASE >> 8);
+ tpm_write_index(NSC_DIO_INDEX + 1, TPM_NSC_BASE);
+ tpm_read_index(NSC_DIO_INDEX);
+ tpm_read_index(NSC_DIO_INDEX + 1);
+
+ /* set the command register base addresses */
+ tpm_write_index(NSC_CIO_INDEX, (TPM_NSC_BASE + 1) >> 8);
+ tpm_write_index(NSC_CIO_INDEX + 1, (TPM_NSC_BASE + 1));
+ tpm_read_index(NSC_DIO_INDEX);
+ tpm_read_index(NSC_DIO_INDEX + 1);
+
+ /* set the interrupt number to be used for the host interface */
+ tpm_write_index(NSC_IRQ_INDEX, TPM_NSC_IRQ);
+ tpm_write_index(NSC_ITS_INDEX, 0x00);
+ tpm_read_index(NSC_IRQ_INDEX);
+
+ /* enable the DPM module */
+ tpm_write_index(NSC_LDC_INDEX, 0x01);
+ tpm_read_index(NSC_LDC_INDEX);
+
+ if ((rc = tpm_register_hardware(pci_dev, &tpm_nsc)) < 0)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ pci_disable_device(pci_dev);
+ return rc;
+}
+
+static struct pci_device_id tpm_pci_tbl[] __devinitdata = {
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)},
+ {PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)},
+ {PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, tpm_pci_tbl);
+
+static struct pci_driver nsc_pci_driver = {
+ .name = "tpm_nsc",
+ .id_table = tpm_pci_tbl,
+ .probe = tpm_nsc_init,
+ .remove = __devexit_p(tpm_remove),
+ .suspend = tpm_pm_suspend,
+ .resume = tpm_pm_resume,
+};
+
+static int __init init_nsc(void)
+{
+ return pci_register_driver(&nsc_pci_driver);
+}
+
+static void __exit cleanup_nsc(void)
+{
+ pci_unregister_driver(&nsc_pci_driver);
+}
+
+module_init(init_nsc);
+module_exit(cleanup_nsc);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
new file mode 100644
index 000000000000..06e5a3f1836d
--- /dev/null
+++ b/drivers/char/tty_io.c
@@ -0,0 +1,2980 @@
+/*
+ * linux/drivers/char/tty_io.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ */
+
+/*
+ * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
+ * or rs-channels. It also implements echoing, cooked mode etc.
+ *
+ * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0.
+ *
+ * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the
+ * tty_struct and tty_queue structures. Previously there was an array
+ * of 256 tty_struct's which was statically allocated, and the
+ * tty_queue structures were allocated at boot time. Both are now
+ * dynamically allocated only when the tty is open.
+ *
+ * Also restructured routines so that there is more of a separation
+ * between the high-level tty routines (tty_io.c and tty_ioctl.c) and
+ * the low-level tty routines (serial.c, pty.c, console.c). This
+ * makes for cleaner and more compact code. -TYT, 9/17/92
+ *
+ * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
+ * which can be dynamically activated and de-activated by the line
+ * discipline handling modules (like SLIP).
+ *
+ * NOTE: pay no attention to the line discipline code (yet); its
+ * interface is still subject to change in this version...
+ * -- TYT, 1/31/92
+ *
+ * Added functionality to the OPOST tty handling. No delays, but all
+ * other bits should be there.
+ * -- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993.
+ *
+ * Rewrote canonical mode and added more termios flags.
+ * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94
+ *
+ * Reorganized FASYNC support so mouse code can share it.
+ * -- ctm@ardi.com, 9Sep95
+ *
+ * New TIOCLINUX variants added.
+ * -- mj@k332.feld.cvut.cz, 19-Nov-95
+ *
+ * Restrict vt switching via ioctl()
+ * -- grif@cs.ucr.edu, 5-Dec-95
+ *
+ * Move console and virtual terminal code to more appropriate files,
+ * implement CONFIG_VT and generalize console device interface.
+ * -- Marko Kohtala <Marko.Kohtala@hut.fi>, March 97
+ *
+ * Rewrote init_dev and release_dev to eliminate races.
+ * -- Bill Hawes <whawes@star.net>, June 97
+ *
+ * Added devfs support.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 13-Jan-1998
+ *
+ * Added support for a Unix98-style ptmx device.
+ * -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
+ *
+ * Reduced memory usage for older ARM systems
+ * -- Russell King <rmk@arm.linux.org.uk>
+ *
+ * Move do_SAK() into process context. Less stack use in devfs functions.
+ * alloc_tty_struct() always uses kmalloc() -- Andrew Morton <andrewm@uow.edu.eu> 17Mar01
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/devpts_fs.h>
+#include <linux/file.h>
+#include <linux/console.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/kd.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/kmod.h>
+
+#undef TTY_DEBUG_HANGUP
+
+#define TTY_PARANOIA_CHECK 1
+#define CHECK_TTY_COUNT 1
+
+struct termios tty_std_termios = { /* for the benefit of tty drivers */
+ .c_iflag = ICRNL | IXON,
+ .c_oflag = OPOST | ONLCR,
+ .c_cflag = B38400 | CS8 | CREAD | HUPCL,
+ .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
+ ECHOCTL | ECHOKE | IEXTEN,
+ .c_cc = INIT_C_CC
+};
+
+EXPORT_SYMBOL(tty_std_termios);
+
+/* This list gets poked at by procfs and various bits of boot up code. This
+ could do with some rationalisation such as pulling the tty proc function
+ into this file */
+
+LIST_HEAD(tty_drivers); /* linked list of tty drivers */
+
+/* Semaphore to protect creating and releasing a tty. This is shared with
+ vt.c for deeply disgusting hack reasons */
+DECLARE_MUTEX(tty_sem);
+
+#ifdef CONFIG_UNIX98_PTYS
+extern struct tty_driver *ptm_driver; /* Unix98 pty masters; for /dev/ptmx */
+extern int pty_limit; /* Config limit on Unix98 ptys */
+static DEFINE_IDR(allocated_ptys);
+static DECLARE_MUTEX(allocated_ptys_lock);
+static int ptmx_open(struct inode *, struct file *);
+#endif
+
+extern void disable_early_printk(void);
+
+static void initialize_tty_struct(struct tty_struct *tty);
+
+static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *);
+ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *);
+static unsigned int tty_poll(struct file *, poll_table *);
+static int tty_open(struct inode *, struct file *);
+static int tty_release(struct inode *, struct file *);
+int tty_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg);
+static int tty_fasync(int fd, struct file * filp, int on);
+extern void rs_360_init(void);
+static void release_mem(struct tty_struct *tty, int idx);
+
+
+static struct tty_struct *alloc_tty_struct(void)
+{
+ struct tty_struct *tty;
+
+ tty = kmalloc(sizeof(struct tty_struct), GFP_KERNEL);
+ if (tty)
+ memset(tty, 0, sizeof(struct tty_struct));
+ return tty;
+}
+
+static inline void free_tty_struct(struct tty_struct *tty)
+{
+ kfree(tty->write_buf);
+ kfree(tty);
+}
+
+#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base)
+
+char *tty_name(struct tty_struct *tty, char *buf)
+{
+ if (!tty) /* Hmm. NULL pointer. That's fun. */
+ strcpy(buf, "NULL tty");
+ else
+ strcpy(buf, tty->name);
+ return buf;
+}
+
+EXPORT_SYMBOL(tty_name);
+
+inline int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
+ const char *routine)
+{
+#ifdef TTY_PARANOIA_CHECK
+ if (!tty) {
+ printk(KERN_WARNING
+ "null TTY for (%d:%d) in %s\n",
+ imajor(inode), iminor(inode), routine);
+ return 1;
+ }
+ if (tty->magic != TTY_MAGIC) {
+ printk(KERN_WARNING
+ "bad magic number for tty struct (%d:%d) in %s\n",
+ imajor(inode), iminor(inode), routine);
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static int check_tty_count(struct tty_struct *tty, const char *routine)
+{
+#ifdef CHECK_TTY_COUNT
+ struct list_head *p;
+ int count = 0;
+
+ file_list_lock();
+ list_for_each(p, &tty->tty_files) {
+ count++;
+ }
+ file_list_unlock();
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_SLAVE &&
+ tty->link && tty->link->count)
+ count++;
+ if (tty->count != count) {
+ printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) "
+ "!= #fd's(%d) in %s\n",
+ tty->name, tty->count, count, routine);
+ return count;
+ }
+#endif
+ return 0;
+}
+
+/*
+ * This is probably overkill for real world processors but
+ * they are not on hot paths so a little discipline won't do
+ * any harm.
+ */
+
+static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
+{
+ down(&tty->termios_sem);
+ tty->termios->c_line = num;
+ up(&tty->termios_sem);
+}
+
+/*
+ * This guards the refcounted line discipline lists. The lock
+ * must be taken with irqs off because there are hangup path
+ * callers who will do ldisc lookups and cannot sleep.
+ */
+
+static DEFINE_SPINLOCK(tty_ldisc_lock);
+static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
+static struct tty_ldisc tty_ldiscs[NR_LDISCS]; /* line disc dispatch table */
+
+int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (disc < N_TTY || disc >= NR_LDISCS)
+ return -EINVAL;
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ if (new_ldisc) {
+ tty_ldiscs[disc] = *new_ldisc;
+ tty_ldiscs[disc].num = disc;
+ tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
+ tty_ldiscs[disc].refcount = 0;
+ } else {
+ if(tty_ldiscs[disc].refcount)
+ ret = -EBUSY;
+ else
+ tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
+ }
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+
+ return ret;
+}
+
+EXPORT_SYMBOL(tty_register_ldisc);
+
+struct tty_ldisc *tty_ldisc_get(int disc)
+{
+ unsigned long flags;
+ struct tty_ldisc *ld;
+
+ if (disc < N_TTY || disc >= NR_LDISCS)
+ return NULL;
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+
+ ld = &tty_ldiscs[disc];
+ /* Check the entry is defined */
+ if(ld->flags & LDISC_FLAG_DEFINED)
+ {
+ /* If the module is being unloaded we can't use it */
+ if (!try_module_get(ld->owner))
+ ld = NULL;
+ else /* lock it */
+ ld->refcount++;
+ }
+ else
+ ld = NULL;
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ return ld;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_get);
+
+void tty_ldisc_put(int disc)
+{
+ struct tty_ldisc *ld;
+ unsigned long flags;
+
+ if (disc < N_TTY || disc >= NR_LDISCS)
+ BUG();
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ ld = &tty_ldiscs[disc];
+ if(ld->refcount == 0)
+ BUG();
+ ld->refcount --;
+ module_put(ld->owner);
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_put);
+
+static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+ tty->ldisc = *ld;
+ tty->ldisc.refcount = 0;
+}
+
+/**
+ * tty_ldisc_try - internal helper
+ * @tty: the tty
+ *
+ * Make a single attempt to grab and bump the refcount on
+ * the tty ldisc. Return 0 on failure or 1 on success. This is
+ * used to implement both the waiting and non waiting versions
+ * of tty_ldisc_ref
+ */
+
+static int tty_ldisc_try(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct tty_ldisc *ld;
+ int ret = 0;
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ ld = &tty->ldisc;
+ if(test_bit(TTY_LDISC, &tty->flags))
+ {
+ ld->refcount++;
+ ret = 1;
+ }
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ return ret;
+}
+
+/**
+ * tty_ldisc_ref_wait - wait for the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a
+ * reference to it. If the line discipline is in flux then
+ * wait patiently until it changes.
+ *
+ * Note: Must not be called from an IRQ/timer context. The caller
+ * must also be careful not to hold other locks that will deadlock
+ * against a discipline change, such as an existing ldisc reference
+ * (which we check for)
+ */
+
+struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
+{
+ /* wait_event is a macro */
+ wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
+ if(tty->ldisc.refcount == 0)
+ printk(KERN_ERR "tty_ldisc_ref_wait\n");
+ return &tty->ldisc;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
+
+/**
+ * tty_ldisc_ref - get the tty ldisc
+ * @tty: tty device
+ *
+ * Dereference the line discipline for the terminal and take a
+ * reference to it. If the line discipline is in flux then
+ * return NULL. Can be called from IRQ and timer functions.
+ */
+
+struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
+{
+ if(tty_ldisc_try(tty))
+ return &tty->ldisc;
+ return NULL;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref);
+
+/**
+ * tty_ldisc_deref - free a tty ldisc reference
+ * @ld: reference to free up
+ *
+ * Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
+ * be called in IRQ context.
+ */
+
+void tty_ldisc_deref(struct tty_ldisc *ld)
+{
+ unsigned long flags;
+
+ if(ld == NULL)
+ BUG();
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ if(ld->refcount == 0)
+ printk(KERN_ERR "tty_ldisc_deref: no references.\n");
+ else
+ ld->refcount--;
+ if(ld->refcount == 0)
+ wake_up(&tty_ldisc_wait);
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_deref);
+
+/**
+ * tty_ldisc_enable - allow ldisc use
+ * @tty: terminal to activate ldisc on
+ *
+ * Set the TTY_LDISC flag when the line discipline can be called
+ * again. Do neccessary wakeups for existing sleepers.
+ *
+ * Note: nobody should set this bit except via this function. Clearing
+ * directly is allowed.
+ */
+
+static void tty_ldisc_enable(struct tty_struct *tty)
+{
+ set_bit(TTY_LDISC, &tty->flags);
+ wake_up(&tty_ldisc_wait);
+}
+
+/**
+ * tty_set_ldisc - set line discipline
+ * @tty: the terminal to set
+ * @ldisc: the line discipline
+ *
+ * Set the discipline of a tty line. Must be called from a process
+ * context.
+ */
+
+static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
+{
+ int retval = 0;
+ struct tty_ldisc o_ldisc;
+ char buf[64];
+ int work;
+ unsigned long flags;
+ struct tty_ldisc *ld;
+
+ if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
+ return -EINVAL;
+
+restart:
+
+ if (tty->ldisc.num == ldisc)
+ return 0; /* We are already in the desired discipline */
+
+ ld = tty_ldisc_get(ldisc);
+ /* Eduardo Blanco <ejbs@cs.cs.com.uy> */
+ /* Cyrus Durgin <cider@speakeasy.org> */
+ if (ld == NULL) {
+ request_module("tty-ldisc-%d", ldisc);
+ ld = tty_ldisc_get(ldisc);
+ }
+ if (ld == NULL)
+ return -EINVAL;
+
+ o_ldisc = tty->ldisc;
+
+ tty_wait_until_sent(tty, 0);
+
+ /*
+ * Make sure we don't change while someone holds a
+ * reference to the line discipline. The TTY_LDISC bit
+ * prevents anyone taking a reference once it is clear.
+ * We need the lock to avoid racing reference takers.
+ */
+
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ if(tty->ldisc.refcount)
+ {
+ /* Free the new ldisc we grabbed. Must drop the lock
+ first. */
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ tty_ldisc_put(ldisc);
+ /*
+ * There are several reasons we may be busy, including
+ * random momentary I/O traffic. We must therefore
+ * retry. We could distinguish between blocking ops
+ * and retries if we made tty_ldisc_wait() smarter. That
+ * is up for discussion.
+ */
+ if(wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0)
+ return -ERESTARTSYS;
+ goto restart;
+ }
+ clear_bit(TTY_LDISC, &tty->flags);
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+
+ /*
+ * From this point on we know nobody has an ldisc
+ * usage reference, nor can they obtain one until
+ * we say so later on.
+ */
+
+ work = cancel_delayed_work(&tty->flip.work);
+ /*
+ * Wait for ->hangup_work and ->flip.work handlers to terminate
+ */
+
+ flush_scheduled_work();
+ /* Shutdown the current discipline. */
+ if (tty->ldisc.close)
+ (tty->ldisc.close)(tty);
+
+ /* Now set up the new line discipline. */
+ tty_ldisc_assign(tty, ld);
+ tty_set_termios_ldisc(tty, ldisc);
+ if (tty->ldisc.open)
+ retval = (tty->ldisc.open)(tty);
+ if (retval < 0) {
+ tty_ldisc_put(ldisc);
+ /* There is an outstanding reference here so this is safe */
+ tty_ldisc_assign(tty, tty_ldisc_get(o_ldisc.num));
+ tty_set_termios_ldisc(tty, tty->ldisc.num);
+ if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) {
+ tty_ldisc_put(o_ldisc.num);
+ /* This driver is always present */
+ tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ tty_set_termios_ldisc(tty, N_TTY);
+ if (tty->ldisc.open) {
+ int r = tty->ldisc.open(tty);
+
+ if (r < 0)
+ panic("Couldn't open N_TTY ldisc for "
+ "%s --- error %d.",
+ tty_name(tty, buf), r);
+ }
+ }
+ }
+ /* At this point we hold a reference to the new ldisc and a
+ a reference to the old ldisc. If we ended up flipping back
+ to the existing ldisc we have two references to it */
+
+ if (tty->ldisc.num != o_ldisc.num && tty->driver->set_ldisc)
+ tty->driver->set_ldisc(tty);
+
+ tty_ldisc_put(o_ldisc.num);
+
+ /*
+ * Allow ldisc referencing to occur as soon as the driver
+ * ldisc callback completes.
+ */
+
+ tty_ldisc_enable(tty);
+
+ /* Restart it in case no characters kick it off. Safe if
+ already running */
+ if(work)
+ schedule_delayed_work(&tty->flip.work, 1);
+ return retval;
+}
+
+/*
+ * This routine returns a tty driver structure, given a device number
+ */
+static struct tty_driver *get_tty_driver(dev_t device, int *index)
+{
+ struct tty_driver *p;
+
+ list_for_each_entry(p, &tty_drivers, tty_drivers) {
+ dev_t base = MKDEV(p->major, p->minor_start);
+ if (device < base || device >= base + p->num)
+ continue;
+ *index = device - base;
+ return p;
+ }
+ return NULL;
+}
+
+/*
+ * If we try to write to, or set the state of, a terminal and we're
+ * not in the foreground, send a SIGTTOU. If the signal is blocked or
+ * ignored, go ahead and perform the operation. (POSIX 7.2)
+ */
+int tty_check_change(struct tty_struct * tty)
+{
+ if (current->signal->tty != tty)
+ return 0;
+ if (tty->pgrp <= 0) {
+ printk(KERN_WARNING "tty_check_change: tty->pgrp <= 0!\n");
+ return 0;
+ }
+ if (process_group(current) == tty->pgrp)
+ return 0;
+ if (is_ignored(SIGTTOU))
+ return 0;
+ if (is_orphaned_pgrp(process_group(current)))
+ return -EIO;
+ (void) kill_pg(process_group(current), SIGTTOU, 1);
+ return -ERESTARTSYS;
+}
+
+EXPORT_SYMBOL(tty_check_change);
+
+static ssize_t hung_up_tty_read(struct file * file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return 0;
+}
+
+static ssize_t hung_up_tty_write(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return -EIO;
+}
+
+/* No kernel lock held - none needed ;) */
+static unsigned int hung_up_tty_poll(struct file * filp, poll_table * wait)
+{
+ return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM;
+}
+
+static int hung_up_tty_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ return cmd == TIOCSPGRP ? -ENOTTY : -EIO;
+}
+
+static struct file_operations tty_fops = {
+ .llseek = no_llseek,
+ .read = tty_read,
+ .write = tty_write,
+ .poll = tty_poll,
+ .ioctl = tty_ioctl,
+ .open = tty_open,
+ .release = tty_release,
+ .fasync = tty_fasync,
+};
+
+#ifdef CONFIG_UNIX98_PTYS
+static struct file_operations ptmx_fops = {
+ .llseek = no_llseek,
+ .read = tty_read,
+ .write = tty_write,
+ .poll = tty_poll,
+ .ioctl = tty_ioctl,
+ .open = ptmx_open,
+ .release = tty_release,
+ .fasync = tty_fasync,
+};
+#endif
+
+static struct file_operations console_fops = {
+ .llseek = no_llseek,
+ .read = tty_read,
+ .write = redirected_tty_write,
+ .poll = tty_poll,
+ .ioctl = tty_ioctl,
+ .open = tty_open,
+ .release = tty_release,
+ .fasync = tty_fasync,
+};
+
+static struct file_operations hung_up_tty_fops = {
+ .llseek = no_llseek,
+ .read = hung_up_tty_read,
+ .write = hung_up_tty_write,
+ .poll = hung_up_tty_poll,
+ .ioctl = hung_up_tty_ioctl,
+ .release = tty_release,
+};
+
+static DEFINE_SPINLOCK(redirect_lock);
+static struct file *redirect;
+
+/**
+ * tty_wakeup - request more data
+ * @tty: terminal
+ *
+ * Internal and external helper for wakeups of tty. This function
+ * informs the line discipline if present that the driver is ready
+ * to receive more output data.
+ */
+
+void tty_wakeup(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld;
+
+ if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
+ ld = tty_ldisc_ref(tty);
+ if(ld) {
+ if(ld->write_wakeup)
+ ld->write_wakeup(tty);
+ tty_ldisc_deref(ld);
+ }
+ }
+ wake_up_interruptible(&tty->write_wait);
+}
+
+EXPORT_SYMBOL_GPL(tty_wakeup);
+
+/**
+ * tty_ldisc_flush - flush line discipline queue
+ * @tty: tty
+ *
+ * Flush the line discipline queue (if any) for this tty. If there
+ * is no line discipline active this is a no-op.
+ */
+
+void tty_ldisc_flush(struct tty_struct *tty)
+{
+ struct tty_ldisc *ld = tty_ldisc_ref(tty);
+ if(ld) {
+ if(ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_flush);
+
+/*
+ * This can be called by the "eventd" kernel thread. That is process synchronous,
+ * but doesn't hold any locks, so we need to make sure we have the appropriate
+ * locks for what we're doing..
+ */
+static void do_tty_hangup(void *data)
+{
+ struct tty_struct *tty = (struct tty_struct *) data;
+ struct file * cons_filp = NULL;
+ struct file *filp, *f = NULL;
+ struct task_struct *p;
+ struct tty_ldisc *ld;
+ int closecount = 0, n;
+
+ if (!tty)
+ return;
+
+ /* inuse_filps is protected by the single kernel lock */
+ lock_kernel();
+
+ spin_lock(&redirect_lock);
+ if (redirect && redirect->private_data == tty) {
+ f = redirect;
+ redirect = NULL;
+ }
+ spin_unlock(&redirect_lock);
+
+ check_tty_count(tty, "do_tty_hangup");
+ file_list_lock();
+ /* This breaks for file handles being sent over AF_UNIX sockets ? */
+ list_for_each_entry(filp, &tty->tty_files, f_list) {
+ if (filp->f_op->write == redirected_tty_write)
+ cons_filp = filp;
+ if (filp->f_op->write != tty_write)
+ continue;
+ closecount++;
+ tty_fasync(-1, filp, 0); /* can't block */
+ filp->f_op = &hung_up_tty_fops;
+ }
+ file_list_unlock();
+
+ /* FIXME! What are the locking issues here? This may me overdoing things..
+ * this question is especially important now that we've removed the irqlock. */
+
+ ld = tty_ldisc_ref(tty);
+ if(ld != NULL) /* We may have no line discipline at this point */
+ {
+ if (ld->flush_buffer)
+ ld->flush_buffer(tty);
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
+ ld->write_wakeup)
+ ld->write_wakeup(tty);
+ if (ld->hangup)
+ ld->hangup(tty);
+ }
+
+ /* FIXME: Once we trust the LDISC code better we can wait here for
+ ldisc completion and fix the driver call race */
+
+ wake_up_interruptible(&tty->write_wait);
+ wake_up_interruptible(&tty->read_wait);
+
+ /*
+ * Shutdown the current line discipline, and reset it to
+ * N_TTY.
+ */
+ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+ {
+ down(&tty->termios_sem);
+ *tty->termios = tty->driver->init_termios;
+ up(&tty->termios_sem);
+ }
+
+ /* Defer ldisc switch */
+ /* tty_deferred_ldisc_switch(N_TTY);
+
+ This should get done automatically when the port closes and
+ tty_release is called */
+
+ read_lock(&tasklist_lock);
+ if (tty->session > 0) {
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+ if (p->signal->tty == tty)
+ p->signal->tty = NULL;
+ if (!p->signal->leader)
+ continue;
+ send_group_sig_info(SIGHUP, SEND_SIG_PRIV, p);
+ send_group_sig_info(SIGCONT, SEND_SIG_PRIV, p);
+ if (tty->pgrp > 0)
+ p->signal->tty_old_pgrp = tty->pgrp;
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
+ }
+ read_unlock(&tasklist_lock);
+
+ tty->flags = 0;
+ tty->session = 0;
+ tty->pgrp = -1;
+ tty->ctrl_status = 0;
+ /*
+ * If one of the devices matches a console pointer, we
+ * cannot just call hangup() because that will cause
+ * tty->count and state->count to go out of sync.
+ * So we just call close() the right number of times.
+ */
+ if (cons_filp) {
+ if (tty->driver->close)
+ for (n = 0; n < closecount; n++)
+ tty->driver->close(tty, cons_filp);
+ } else if (tty->driver->hangup)
+ (tty->driver->hangup)(tty);
+
+ /* We don't want to have driver/ldisc interactions beyond
+ the ones we did here. The driver layer expects no
+ calls after ->hangup() from the ldisc side. However we
+ can't yet guarantee all that */
+
+ set_bit(TTY_HUPPED, &tty->flags);
+ if (ld) {
+ tty_ldisc_enable(tty);
+ tty_ldisc_deref(ld);
+ }
+ unlock_kernel();
+ if (f)
+ fput(f);
+}
+
+void tty_hangup(struct tty_struct * tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+ char buf[64];
+
+ printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf));
+#endif
+ schedule_work(&tty->hangup_work);
+}
+
+EXPORT_SYMBOL(tty_hangup);
+
+void tty_vhangup(struct tty_struct * tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+ char buf[64];
+
+ printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf));
+#endif
+ do_tty_hangup((void *) tty);
+}
+EXPORT_SYMBOL(tty_vhangup);
+
+int tty_hung_up_p(struct file * filp)
+{
+ return (filp->f_op == &hung_up_tty_fops);
+}
+
+EXPORT_SYMBOL(tty_hung_up_p);
+
+/*
+ * This function is typically called only by the session leader, when
+ * it wants to disassociate itself from its controlling tty.
+ *
+ * It performs the following functions:
+ * (1) Sends a SIGHUP and SIGCONT to the foreground process group
+ * (2) Clears the tty from being controlling the session
+ * (3) Clears the controlling tty for all processes in the
+ * session group.
+ *
+ * The argument on_exit is set to 1 if called when a process is
+ * exiting; it is 0 if called by the ioctl TIOCNOTTY.
+ */
+void disassociate_ctty(int on_exit)
+{
+ struct tty_struct *tty;
+ struct task_struct *p;
+ int tty_pgrp = -1;
+
+ lock_kernel();
+
+ down(&tty_sem);
+ tty = current->signal->tty;
+ if (tty) {
+ tty_pgrp = tty->pgrp;
+ up(&tty_sem);
+ if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
+ tty_vhangup(tty);
+ } else {
+ if (current->signal->tty_old_pgrp) {
+ kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
+ kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
+ }
+ up(&tty_sem);
+ unlock_kernel();
+ return;
+ }
+ if (tty_pgrp > 0) {
+ kill_pg(tty_pgrp, SIGHUP, on_exit);
+ if (!on_exit)
+ kill_pg(tty_pgrp, SIGCONT, on_exit);
+ }
+
+ /* Must lock changes to tty_old_pgrp */
+ down(&tty_sem);
+ current->signal->tty_old_pgrp = 0;
+ tty->session = 0;
+ tty->pgrp = -1;
+
+ /* Now clear signal->tty under the lock */
+ read_lock(&tasklist_lock);
+ do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
+ p->signal->tty = NULL;
+ } while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
+ read_unlock(&tasklist_lock);
+ up(&tty_sem);
+ unlock_kernel();
+}
+
+void stop_tty(struct tty_struct *tty)
+{
+ if (tty->stopped)
+ return;
+ tty->stopped = 1;
+ if (tty->link && tty->link->packet) {
+ tty->ctrl_status &= ~TIOCPKT_START;
+ tty->ctrl_status |= TIOCPKT_STOP;
+ wake_up_interruptible(&tty->link->read_wait);
+ }
+ if (tty->driver->stop)
+ (tty->driver->stop)(tty);
+}
+
+EXPORT_SYMBOL(stop_tty);
+
+void start_tty(struct tty_struct *tty)
+{
+ if (!tty->stopped || tty->flow_stopped)
+ return;
+ tty->stopped = 0;
+ if (tty->link && tty->link->packet) {
+ tty->ctrl_status &= ~TIOCPKT_STOP;
+ tty->ctrl_status |= TIOCPKT_START;
+ wake_up_interruptible(&tty->link->read_wait);
+ }
+ if (tty->driver->start)
+ (tty->driver->start)(tty);
+
+ /* If we have a running line discipline it may need kicking */
+ tty_wakeup(tty);
+ wake_up_interruptible(&tty->write_wait);
+}
+
+EXPORT_SYMBOL(start_tty);
+
+static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
+ loff_t *ppos)
+{
+ int i;
+ struct tty_struct * tty;
+ struct inode *inode;
+ struct tty_ldisc *ld;
+
+ tty = (struct tty_struct *)file->private_data;
+ inode = file->f_dentry->d_inode;
+ if (tty_paranoia_check(tty, inode, "tty_read"))
+ return -EIO;
+ if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
+ return -EIO;
+
+ /* We want to wait for the line discipline to sort out in this
+ situation */
+ ld = tty_ldisc_ref_wait(tty);
+ lock_kernel();
+ if (ld->read)
+ i = (ld->read)(tty,file,buf,count);
+ else
+ i = -EIO;
+ tty_ldisc_deref(ld);
+ unlock_kernel();
+ if (i > 0)
+ inode->i_atime = current_fs_time(inode->i_sb);
+ return i;
+}
+
+/*
+ * Split writes up in sane blocksizes to avoid
+ * denial-of-service type attacks
+ */
+static inline ssize_t do_tty_write(
+ ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
+ struct tty_struct *tty,
+ struct file *file,
+ const char __user *buf,
+ size_t count)
+{
+ ssize_t ret = 0, written = 0;
+ unsigned int chunk;
+
+ if (down_interruptible(&tty->atomic_write)) {
+ return -ERESTARTSYS;
+ }
+
+ /*
+ * We chunk up writes into a temporary buffer. This
+ * simplifies low-level drivers immensely, since they
+ * don't have locking issues and user mode accesses.
+ *
+ * But if TTY_NO_WRITE_SPLIT is set, we should use a
+ * big chunk-size..
+ *
+ * The default chunk-size is 2kB, because the NTTY
+ * layer has problems with bigger chunks. It will
+ * claim to be able to handle more characters than
+ * it actually does.
+ */
+ chunk = 2048;
+ if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
+ chunk = 65536;
+ if (count < chunk)
+ chunk = count;
+
+ /* write_buf/write_cnt is protected by the atomic_write semaphore */
+ if (tty->write_cnt < chunk) {
+ unsigned char *buf;
+
+ if (chunk < 1024)
+ chunk = 1024;
+
+ buf = kmalloc(chunk, GFP_KERNEL);
+ if (!buf) {
+ up(&tty->atomic_write);
+ return -ENOMEM;
+ }
+ kfree(tty->write_buf);
+ tty->write_cnt = chunk;
+ tty->write_buf = buf;
+ }
+
+ /* Do the write .. */
+ for (;;) {
+ size_t size = count;
+ if (size > chunk)
+ size = chunk;
+ ret = -EFAULT;
+ if (copy_from_user(tty->write_buf, buf, size))
+ break;
+ lock_kernel();
+ ret = write(tty, file, tty->write_buf, size);
+ unlock_kernel();
+ if (ret <= 0)
+ break;
+ written += ret;
+ buf += ret;
+ count -= ret;
+ if (!count)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ cond_resched();
+ }
+ if (written) {
+ struct inode *inode = file->f_dentry->d_inode;
+ inode->i_mtime = current_fs_time(inode->i_sb);
+ ret = written;
+ }
+ up(&tty->atomic_write);
+ return ret;
+}
+
+
+static ssize_t tty_write(struct file * file, const char __user * buf, size_t count,
+ loff_t *ppos)
+{
+ struct tty_struct * tty;
+ struct inode *inode = file->f_dentry->d_inode;
+ ssize_t ret;
+ struct tty_ldisc *ld;
+
+ tty = (struct tty_struct *)file->private_data;
+ if (tty_paranoia_check(tty, inode, "tty_write"))
+ return -EIO;
+ if (!tty || !tty->driver->write || (test_bit(TTY_IO_ERROR, &tty->flags)))
+ return -EIO;
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (!ld->write)
+ ret = -EIO;
+ else
+ ret = do_tty_write(ld->write, tty, file, buf, count);
+ tty_ldisc_deref(ld);
+ return ret;
+}
+
+ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t count,
+ loff_t *ppos)
+{
+ struct file *p = NULL;
+
+ spin_lock(&redirect_lock);
+ if (redirect) {
+ get_file(redirect);
+ p = redirect;
+ }
+ spin_unlock(&redirect_lock);
+
+ if (p) {
+ ssize_t res;
+ res = vfs_write(p, buf, count, &p->f_pos);
+ fput(p);
+ return res;
+ }
+
+ return tty_write(file, buf, count, ppos);
+}
+
+static char ptychar[] = "pqrstuvwxyzabcde";
+
+static inline void pty_line_name(struct tty_driver *driver, int index, char *p)
+{
+ int i = index + driver->name_base;
+ /* ->name is initialized to "ttyp", but "tty" is expected */
+ sprintf(p, "%s%c%x",
+ driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name,
+ ptychar[i >> 4 & 0xf], i & 0xf);
+}
+
+static inline void tty_line_name(struct tty_driver *driver, int index, char *p)
+{
+ sprintf(p, "%s%d", driver->name, index + driver->name_base);
+}
+
+/*
+ * WSH 06/09/97: Rewritten to remove races and properly clean up after a
+ * failed open. The new code protects the open with a semaphore, so it's
+ * really quite straightforward. The semaphore locking can probably be
+ * relaxed for the (most common) case of reopening a tty.
+ */
+static int init_dev(struct tty_driver *driver, int idx,
+ struct tty_struct **ret_tty)
+{
+ struct tty_struct *tty, *o_tty;
+ struct termios *tp, **tp_loc, *o_tp, **o_tp_loc;
+ struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
+ int retval=0;
+
+ /* check whether we're reopening an existing tty */
+ if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+ tty = devpts_get_tty(idx);
+ if (tty && driver->subtype == PTY_TYPE_MASTER)
+ tty = tty->link;
+ } else {
+ tty = driver->ttys[idx];
+ }
+ if (tty) goto fast_track;
+
+ /*
+ * First time open is complex, especially for PTY devices.
+ * This code guarantees that either everything succeeds and the
+ * TTY is ready for operation, or else the table slots are vacated
+ * and the allocated memory released. (Except that the termios
+ * and locked termios may be retained.)
+ */
+
+ if (!try_module_get(driver->owner)) {
+ retval = -ENODEV;
+ goto end_init;
+ }
+
+ o_tty = NULL;
+ tp = o_tp = NULL;
+ ltp = o_ltp = NULL;
+
+ tty = alloc_tty_struct();
+ if(!tty)
+ goto fail_no_mem;
+ initialize_tty_struct(tty);
+ tty->driver = driver;
+ tty->index = idx;
+ tty_line_name(driver, idx, tty->name);
+
+ if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+ tp_loc = &tty->termios;
+ ltp_loc = &tty->termios_locked;
+ } else {
+ tp_loc = &driver->termios[idx];
+ ltp_loc = &driver->termios_locked[idx];
+ }
+
+ if (!*tp_loc) {
+ tp = (struct termios *) kmalloc(sizeof(struct termios),
+ GFP_KERNEL);
+ if (!tp)
+ goto free_mem_out;
+ *tp = driver->init_termios;
+ }
+
+ if (!*ltp_loc) {
+ ltp = (struct termios *) kmalloc(sizeof(struct termios),
+ GFP_KERNEL);
+ if (!ltp)
+ goto free_mem_out;
+ memset(ltp, 0, sizeof(struct termios));
+ }
+
+ if (driver->type == TTY_DRIVER_TYPE_PTY) {
+ o_tty = alloc_tty_struct();
+ if (!o_tty)
+ goto free_mem_out;
+ initialize_tty_struct(o_tty);
+ o_tty->driver = driver->other;
+ o_tty->index = idx;
+ tty_line_name(driver->other, idx, o_tty->name);
+
+ if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+ o_tp_loc = &o_tty->termios;
+ o_ltp_loc = &o_tty->termios_locked;
+ } else {
+ o_tp_loc = &driver->other->termios[idx];
+ o_ltp_loc = &driver->other->termios_locked[idx];
+ }
+
+ if (!*o_tp_loc) {
+ o_tp = (struct termios *)
+ kmalloc(sizeof(struct termios), GFP_KERNEL);
+ if (!o_tp)
+ goto free_mem_out;
+ *o_tp = driver->other->init_termios;
+ }
+
+ if (!*o_ltp_loc) {
+ o_ltp = (struct termios *)
+ kmalloc(sizeof(struct termios), GFP_KERNEL);
+ if (!o_ltp)
+ goto free_mem_out;
+ memset(o_ltp, 0, sizeof(struct termios));
+ }
+
+ /*
+ * Everything allocated ... set up the o_tty structure.
+ */
+ if (!(driver->other->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ driver->other->ttys[idx] = o_tty;
+ }
+ if (!*o_tp_loc)
+ *o_tp_loc = o_tp;
+ if (!*o_ltp_loc)
+ *o_ltp_loc = o_ltp;
+ o_tty->termios = *o_tp_loc;
+ o_tty->termios_locked = *o_ltp_loc;
+ driver->other->refcount++;
+ if (driver->subtype == PTY_TYPE_MASTER)
+ o_tty->count++;
+
+ /* Establish the links in both directions */
+ tty->link = o_tty;
+ o_tty->link = tty;
+ }
+
+ /*
+ * All structures have been allocated, so now we install them.
+ * Failures after this point use release_mem to clean up, so
+ * there's no need to null out the local pointers.
+ */
+ if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ driver->ttys[idx] = tty;
+ }
+
+ if (!*tp_loc)
+ *tp_loc = tp;
+ if (!*ltp_loc)
+ *ltp_loc = ltp;
+ tty->termios = *tp_loc;
+ tty->termios_locked = *ltp_loc;
+ driver->refcount++;
+ tty->count++;
+
+ /*
+ * Structures all installed ... call the ldisc open routines.
+ * If we fail here just call release_mem to clean up. No need
+ * to decrement the use counts, as release_mem doesn't care.
+ */
+
+ if (tty->ldisc.open) {
+ retval = (tty->ldisc.open)(tty);
+ if (retval)
+ goto release_mem_out;
+ }
+ if (o_tty && o_tty->ldisc.open) {
+ retval = (o_tty->ldisc.open)(o_tty);
+ if (retval) {
+ if (tty->ldisc.close)
+ (tty->ldisc.close)(tty);
+ goto release_mem_out;
+ }
+ tty_ldisc_enable(o_tty);
+ }
+ tty_ldisc_enable(tty);
+ goto success;
+
+ /*
+ * This fast open can be used if the tty is already open.
+ * No memory is allocated, and the only failures are from
+ * attempting to open a closing tty or attempting multiple
+ * opens on a pty master.
+ */
+fast_track:
+ if (test_bit(TTY_CLOSING, &tty->flags)) {
+ retval = -EIO;
+ goto end_init;
+ }
+ if (driver->type == TTY_DRIVER_TYPE_PTY &&
+ driver->subtype == PTY_TYPE_MASTER) {
+ /*
+ * special case for PTY masters: only one open permitted,
+ * and the slave side open count is incremented as well.
+ */
+ if (tty->count) {
+ retval = -EIO;
+ goto end_init;
+ }
+ tty->link->count++;
+ }
+ tty->count++;
+ tty->driver = driver; /* N.B. why do this every time?? */
+
+ /* FIXME */
+ if(!test_bit(TTY_LDISC, &tty->flags))
+ printk(KERN_ERR "init_dev but no ldisc\n");
+success:
+ *ret_tty = tty;
+
+ /* All paths come through here to release the semaphore */
+end_init:
+ return retval;
+
+ /* Release locally allocated memory ... nothing placed in slots */
+free_mem_out:
+ if (o_tp)
+ kfree(o_tp);
+ if (o_tty)
+ free_tty_struct(o_tty);
+ if (ltp)
+ kfree(ltp);
+ if (tp)
+ kfree(tp);
+ free_tty_struct(tty);
+
+fail_no_mem:
+ module_put(driver->owner);
+ retval = -ENOMEM;
+ goto end_init;
+
+ /* call the tty release_mem routine to clean out this slot */
+release_mem_out:
+ printk(KERN_INFO "init_dev: ldisc open failed, "
+ "clearing slot %d\n", idx);
+ release_mem(tty, idx);
+ goto end_init;
+}
+
+/*
+ * Releases memory associated with a tty structure, and clears out the
+ * driver table slots.
+ */
+static void release_mem(struct tty_struct *tty, int idx)
+{
+ struct tty_struct *o_tty;
+ struct termios *tp;
+ int devpts = tty->driver->flags & TTY_DRIVER_DEVPTS_MEM;
+
+ if ((o_tty = tty->link) != NULL) {
+ if (!devpts)
+ o_tty->driver->ttys[idx] = NULL;
+ if (o_tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+ tp = o_tty->termios;
+ if (!devpts)
+ o_tty->driver->termios[idx] = NULL;
+ kfree(tp);
+
+ tp = o_tty->termios_locked;
+ if (!devpts)
+ o_tty->driver->termios_locked[idx] = NULL;
+ kfree(tp);
+ }
+ o_tty->magic = 0;
+ o_tty->driver->refcount--;
+ file_list_lock();
+ list_del_init(&o_tty->tty_files);
+ file_list_unlock();
+ free_tty_struct(o_tty);
+ }
+
+ if (!devpts)
+ tty->driver->ttys[idx] = NULL;
+ if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+ tp = tty->termios;
+ if (!devpts)
+ tty->driver->termios[idx] = NULL;
+ kfree(tp);
+
+ tp = tty->termios_locked;
+ if (!devpts)
+ tty->driver->termios_locked[idx] = NULL;
+ kfree(tp);
+ }
+
+ tty->magic = 0;
+ tty->driver->refcount--;
+ file_list_lock();
+ list_del_init(&tty->tty_files);
+ file_list_unlock();
+ module_put(tty->driver->owner);
+ free_tty_struct(tty);
+}
+
+/*
+ * Even releasing the tty structures is a tricky business.. We have
+ * to be very careful that the structures are all released at the
+ * same time, as interrupts might otherwise get the wrong pointers.
+ *
+ * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
+ * lead to double frees or releasing memory still in use.
+ */
+static void release_dev(struct file * filp)
+{
+ struct tty_struct *tty, *o_tty;
+ int pty_master, tty_closing, o_tty_closing, do_sleep;
+ int devpts_master, devpts;
+ int idx;
+ char buf[64];
+ unsigned long flags;
+
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "release_dev"))
+ return;
+
+ check_tty_count(tty, "release_dev");
+
+ tty_fasync(-1, filp, 0);
+
+ idx = tty->index;
+ pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER);
+ devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0;
+ devpts_master = pty_master && devpts;
+ o_tty = tty->link;
+
+#ifdef TTY_PARANOIA_CHECK
+ if (idx < 0 || idx >= tty->driver->num) {
+ printk(KERN_DEBUG "release_dev: bad idx when trying to "
+ "free (%s)\n", tty->name);
+ return;
+ }
+ if (!(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ if (tty != tty->driver->ttys[idx]) {
+ printk(KERN_DEBUG "release_dev: driver.table[%d] not tty "
+ "for (%s)\n", idx, tty->name);
+ return;
+ }
+ if (tty->termios != tty->driver->termios[idx]) {
+ printk(KERN_DEBUG "release_dev: driver.termios[%d] not termios "
+ "for (%s)\n",
+ idx, tty->name);
+ return;
+ }
+ if (tty->termios_locked != tty->driver->termios_locked[idx]) {
+ printk(KERN_DEBUG "release_dev: driver.termios_locked[%d] not "
+ "termios_locked for (%s)\n",
+ idx, tty->name);
+ return;
+ }
+ }
+#endif
+
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "release_dev of %s (tty count=%d)...",
+ tty_name(tty, buf), tty->count);
+#endif
+
+#ifdef TTY_PARANOIA_CHECK
+ if (tty->driver->other &&
+ !(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ if (o_tty != tty->driver->other->ttys[idx]) {
+ printk(KERN_DEBUG "release_dev: other->table[%d] "
+ "not o_tty for (%s)\n",
+ idx, tty->name);
+ return;
+ }
+ if (o_tty->termios != tty->driver->other->termios[idx]) {
+ printk(KERN_DEBUG "release_dev: other->termios[%d] "
+ "not o_termios for (%s)\n",
+ idx, tty->name);
+ return;
+ }
+ if (o_tty->termios_locked !=
+ tty->driver->other->termios_locked[idx]) {
+ printk(KERN_DEBUG "release_dev: other->termios_locked["
+ "%d] not o_termios_locked for (%s)\n",
+ idx, tty->name);
+ return;
+ }
+ if (o_tty->link != tty) {
+ printk(KERN_DEBUG "release_dev: bad pty pointers\n");
+ return;
+ }
+ }
+#endif
+ if (tty->driver->close)
+ tty->driver->close(tty, filp);
+
+ /*
+ * Sanity check: if tty->count is going to zero, there shouldn't be
+ * any waiters on tty->read_wait or tty->write_wait. We test the
+ * wait queues and kick everyone out _before_ actually starting to
+ * close. This ensures that we won't block while releasing the tty
+ * structure.
+ *
+ * The test for the o_tty closing is necessary, since the master and
+ * slave sides may close in any order. If the slave side closes out
+ * first, its count will be one, since the master side holds an open.
+ * Thus this test wouldn't be triggered at the time the slave closes,
+ * so we do it now.
+ *
+ * Note that it's possible for the tty to be opened again while we're
+ * flushing out waiters. By recalculating the closing flags before
+ * each iteration we avoid any problems.
+ */
+ while (1) {
+ /* Guard against races with tty->count changes elsewhere and
+ opens on /dev/tty */
+
+ down(&tty_sem);
+ tty_closing = tty->count <= 1;
+ o_tty_closing = o_tty &&
+ (o_tty->count <= (pty_master ? 1 : 0));
+ up(&tty_sem);
+ do_sleep = 0;
+
+ if (tty_closing) {
+ if (waitqueue_active(&tty->read_wait)) {
+ wake_up(&tty->read_wait);
+ do_sleep++;
+ }
+ if (waitqueue_active(&tty->write_wait)) {
+ wake_up(&tty->write_wait);
+ do_sleep++;
+ }
+ }
+ if (o_tty_closing) {
+ if (waitqueue_active(&o_tty->read_wait)) {
+ wake_up(&o_tty->read_wait);
+ do_sleep++;
+ }
+ if (waitqueue_active(&o_tty->write_wait)) {
+ wake_up(&o_tty->write_wait);
+ do_sleep++;
+ }
+ }
+ if (!do_sleep)
+ break;
+
+ printk(KERN_WARNING "release_dev: %s: read/write wait queue "
+ "active!\n", tty_name(tty, buf));
+ schedule();
+ }
+
+ /*
+ * The closing flags are now consistent with the open counts on
+ * both sides, and we've completed the last operation that could
+ * block, so it's safe to proceed with closing.
+ */
+
+ down(&tty_sem);
+ if (pty_master) {
+ if (--o_tty->count < 0) {
+ printk(KERN_WARNING "release_dev: bad pty slave count "
+ "(%d) for %s\n",
+ o_tty->count, tty_name(o_tty, buf));
+ o_tty->count = 0;
+ }
+ }
+ if (--tty->count < 0) {
+ printk(KERN_WARNING "release_dev: bad tty->count (%d) for %s\n",
+ tty->count, tty_name(tty, buf));
+ tty->count = 0;
+ }
+ up(&tty_sem);
+
+ /*
+ * We've decremented tty->count, so we need to remove this file
+ * descriptor off the tty->tty_files list; this serves two
+ * purposes:
+ * - check_tty_count sees the correct number of file descriptors
+ * associated with this tty.
+ * - do_tty_hangup no longer sees this file descriptor as
+ * something that needs to be handled for hangups.
+ */
+ file_kill(filp);
+ filp->private_data = NULL;
+
+ /*
+ * Perform some housekeeping before deciding whether to return.
+ *
+ * Set the TTY_CLOSING flag if this was the last open. In the
+ * case of a pty we may have to wait around for the other side
+ * to close, and TTY_CLOSING makes sure we can't be reopened.
+ */
+ if(tty_closing)
+ set_bit(TTY_CLOSING, &tty->flags);
+ if(o_tty_closing)
+ set_bit(TTY_CLOSING, &o_tty->flags);
+
+ /*
+ * If _either_ side is closing, make sure there aren't any
+ * processes that still think tty or o_tty is their controlling
+ * tty.
+ */
+ if (tty_closing || o_tty_closing) {
+ struct task_struct *p;
+
+ read_lock(&tasklist_lock);
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+ p->signal->tty = NULL;
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
+ if (o_tty)
+ do_each_task_pid(o_tty->session, PIDTYPE_SID, p) {
+ p->signal->tty = NULL;
+ } while_each_task_pid(o_tty->session, PIDTYPE_SID, p);
+ read_unlock(&tasklist_lock);
+ }
+
+ /* check whether both sides are closing ... */
+ if (!tty_closing || (o_tty && !o_tty_closing))
+ return;
+
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "freeing tty structure...");
+#endif
+ /*
+ * Prevent flush_to_ldisc() from rescheduling the work for later. Then
+ * kill any delayed work. As this is the final close it does not
+ * race with the set_ldisc code path.
+ */
+ clear_bit(TTY_LDISC, &tty->flags);
+ clear_bit(TTY_DONT_FLIP, &tty->flags);
+ cancel_delayed_work(&tty->flip.work);
+
+ /*
+ * Wait for ->hangup_work and ->flip.work handlers to terminate
+ */
+
+ flush_scheduled_work();
+
+ /*
+ * Wait for any short term users (we know they are just driver
+ * side waiters as the file is closing so user count on the file
+ * side is zero.
+ */
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ while(tty->ldisc.refcount)
+ {
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0);
+ spin_lock_irqsave(&tty_ldisc_lock, flags);
+ }
+ spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+ /*
+ * Shutdown the current line discipline, and reset it to N_TTY.
+ * N.B. why reset ldisc when we're releasing the memory??
+ *
+ * FIXME: this MUST get fixed for the new reflocking
+ */
+ if (tty->ldisc.close)
+ (tty->ldisc.close)(tty);
+ tty_ldisc_put(tty->ldisc.num);
+
+ /*
+ * Switch the line discipline back
+ */
+ tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ tty_set_termios_ldisc(tty,N_TTY);
+ if (o_tty) {
+ /* FIXME: could o_tty be in setldisc here ? */
+ clear_bit(TTY_LDISC, &o_tty->flags);
+ if (o_tty->ldisc.close)
+ (o_tty->ldisc.close)(o_tty);
+ tty_ldisc_put(o_tty->ldisc.num);
+ tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY));
+ tty_set_termios_ldisc(o_tty,N_TTY);
+ }
+ /*
+ * The release_mem function takes care of the details of clearing
+ * the slots and preserving the termios structure.
+ */
+ release_mem(tty, idx);
+
+#ifdef CONFIG_UNIX98_PTYS
+ /* Make this pty number available for reallocation */
+ if (devpts) {
+ down(&allocated_ptys_lock);
+ idr_remove(&allocated_ptys, idx);
+ up(&allocated_ptys_lock);
+ }
+#endif
+
+}
+
+/*
+ * tty_open and tty_release keep up the tty count that contains the
+ * number of opens done on a tty. We cannot use the inode-count, as
+ * different inodes might point to the same tty.
+ *
+ * Open-counting is needed for pty masters, as well as for keeping
+ * track of serial lines: DTR is dropped when the last close happens.
+ * (This is not done solely through tty->count, now. - Ted 1/27/92)
+ *
+ * The termios state of a pty is reset on first open so that
+ * settings don't persist across reuse.
+ */
+static int tty_open(struct inode * inode, struct file * filp)
+{
+ struct tty_struct *tty;
+ int noctty, retval;
+ struct tty_driver *driver;
+ int index;
+ dev_t device = inode->i_rdev;
+ unsigned short saved_flags = filp->f_flags;
+
+ nonseekable_open(inode, filp);
+
+retry_open:
+ noctty = filp->f_flags & O_NOCTTY;
+ index = -1;
+ retval = 0;
+
+ down(&tty_sem);
+
+ if (device == MKDEV(TTYAUX_MAJOR,0)) {
+ if (!current->signal->tty) {
+ up(&tty_sem);
+ return -ENXIO;
+ }
+ driver = current->signal->tty->driver;
+ index = current->signal->tty->index;
+ filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
+ /* noctty = 1; */
+ goto got_driver;
+ }
+#ifdef CONFIG_VT
+ if (device == MKDEV(TTY_MAJOR,0)) {
+ extern struct tty_driver *console_driver;
+ driver = console_driver;
+ index = fg_console;
+ noctty = 1;
+ goto got_driver;
+ }
+#endif
+ if (device == MKDEV(TTYAUX_MAJOR,1)) {
+ driver = console_device(&index);
+ if (driver) {
+ /* Don't let /dev/console block */
+ filp->f_flags |= O_NONBLOCK;
+ noctty = 1;
+ goto got_driver;
+ }
+ up(&tty_sem);
+ return -ENODEV;
+ }
+
+ driver = get_tty_driver(device, &index);
+ if (!driver) {
+ up(&tty_sem);
+ return -ENODEV;
+ }
+got_driver:
+ retval = init_dev(driver, index, &tty);
+ up(&tty_sem);
+ if (retval)
+ return retval;
+
+ filp->private_data = tty;
+ file_move(filp, &tty->tty_files);
+ check_tty_count(tty, "tty_open");
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ noctty = 1;
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "opening %s...", tty->name);
+#endif
+ if (!retval) {
+ if (tty->driver->open)
+ retval = tty->driver->open(tty, filp);
+ else
+ retval = -ENODEV;
+ }
+ filp->f_flags = saved_flags;
+
+ if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN))
+ retval = -EBUSY;
+
+ if (retval) {
+#ifdef TTY_DEBUG_HANGUP
+ printk(KERN_DEBUG "error %d in opening %s...", retval,
+ tty->name);
+#endif
+ release_dev(filp);
+ if (retval != -ERESTARTSYS)
+ return retval;
+ if (signal_pending(current))
+ return retval;
+ schedule();
+ /*
+ * Need to reset f_op in case a hangup happened.
+ */
+ if (filp->f_op == &hung_up_tty_fops)
+ filp->f_op = &tty_fops;
+ goto retry_open;
+ }
+ if (!noctty &&
+ current->signal->leader &&
+ !current->signal->tty &&
+ tty->session == 0) {
+ task_lock(current);
+ current->signal->tty = tty;
+ task_unlock(current);
+ current->signal->tty_old_pgrp = 0;
+ tty->session = current->signal->session;
+ tty->pgrp = process_group(current);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_UNIX98_PTYS
+static int ptmx_open(struct inode * inode, struct file * filp)
+{
+ struct tty_struct *tty;
+ int retval;
+ int index;
+ int idr_ret;
+
+ nonseekable_open(inode, filp);
+
+ /* find a device that is not in use. */
+ down(&allocated_ptys_lock);
+ if (!idr_pre_get(&allocated_ptys, GFP_KERNEL)) {
+ up(&allocated_ptys_lock);
+ return -ENOMEM;
+ }
+ idr_ret = idr_get_new(&allocated_ptys, NULL, &index);
+ if (idr_ret < 0) {
+ up(&allocated_ptys_lock);
+ if (idr_ret == -EAGAIN)
+ return -ENOMEM;
+ return -EIO;
+ }
+ if (index >= pty_limit) {
+ idr_remove(&allocated_ptys, index);
+ up(&allocated_ptys_lock);
+ return -EIO;
+ }
+ up(&allocated_ptys_lock);
+
+ down(&tty_sem);
+ retval = init_dev(ptm_driver, index, &tty);
+ up(&tty_sem);
+
+ if (retval)
+ goto out;
+
+ set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
+ filp->private_data = tty;
+ file_move(filp, &tty->tty_files);
+
+ retval = -ENOMEM;
+ if (devpts_pty_new(tty->link))
+ goto out1;
+
+ check_tty_count(tty, "tty_open");
+ retval = ptm_driver->open(tty, filp);
+ if (!retval)
+ return 0;
+out1:
+ release_dev(filp);
+out:
+ down(&allocated_ptys_lock);
+ idr_remove(&allocated_ptys, index);
+ up(&allocated_ptys_lock);
+ return retval;
+}
+#endif
+
+static int tty_release(struct inode * inode, struct file * filp)
+{
+ lock_kernel();
+ release_dev(filp);
+ unlock_kernel();
+ return 0;
+}
+
+/* No kernel lock held - fine */
+static unsigned int tty_poll(struct file * filp, poll_table * wait)
+{
+ struct tty_struct * tty;
+ struct tty_ldisc *ld;
+ int ret = 0;
+
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_poll"))
+ return 0;
+
+ ld = tty_ldisc_ref_wait(tty);
+ if (ld->poll)
+ ret = (ld->poll)(tty, filp, wait);
+ tty_ldisc_deref(ld);
+ return ret;
+}
+
+static int tty_fasync(int fd, struct file * filp, int on)
+{
+ struct tty_struct * tty;
+ int retval;
+
+ tty = (struct tty_struct *)filp->private_data;
+ if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_fasync"))
+ return 0;
+
+ retval = fasync_helper(fd, filp, on, &tty->fasync);
+ if (retval <= 0)
+ return retval;
+
+ if (on) {
+ if (!waitqueue_active(&tty->read_wait))
+ tty->minimum_to_wake = 1;
+ retval = f_setown(filp, (-tty->pgrp) ? : current->pid, 0);
+ if (retval)
+ return retval;
+ } else {
+ if (!tty->fasync && !waitqueue_active(&tty->read_wait))
+ tty->minimum_to_wake = N_TTY_BUF_SIZE;
+ }
+ return 0;
+}
+
+static int tiocsti(struct tty_struct *tty, char __user *p)
+{
+ char ch, mbz = 0;
+ struct tty_ldisc *ld;
+
+ if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(ch, p))
+ return -EFAULT;
+ ld = tty_ldisc_ref_wait(tty);
+ ld->receive_buf(tty, &ch, &mbz, 1);
+ tty_ldisc_deref(ld);
+ return 0;
+}
+
+static int tiocgwinsz(struct tty_struct *tty, struct winsize __user * arg)
+{
+ if (copy_to_user(arg, &tty->winsize, sizeof(*arg)))
+ return -EFAULT;
+ return 0;
+}
+
+static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty,
+ struct winsize __user * arg)
+{
+ struct winsize tmp_ws;
+
+ if (copy_from_user(&tmp_ws, arg, sizeof(*arg)))
+ return -EFAULT;
+ if (!memcmp(&tmp_ws, &tty->winsize, sizeof(*arg)))
+ return 0;
+#ifdef CONFIG_VT
+ if (tty->driver->type == TTY_DRIVER_TYPE_CONSOLE) {
+ int rc;
+
+ acquire_console_sem();
+ rc = vc_resize(tty->driver_data, tmp_ws.ws_col, tmp_ws.ws_row);
+ release_console_sem();
+ if (rc)
+ return -ENXIO;
+ }
+#endif
+ if (tty->pgrp > 0)
+ kill_pg(tty->pgrp, SIGWINCH, 1);
+ if ((real_tty->pgrp != tty->pgrp) && (real_tty->pgrp > 0))
+ kill_pg(real_tty->pgrp, SIGWINCH, 1);
+ tty->winsize = tmp_ws;
+ real_tty->winsize = tmp_ws;
+ return 0;
+}
+
+static int tioccons(struct file *file)
+{
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (file->f_op->write == redirected_tty_write) {
+ struct file *f;
+ spin_lock(&redirect_lock);
+ f = redirect;
+ redirect = NULL;
+ spin_unlock(&redirect_lock);
+ if (f)
+ fput(f);
+ return 0;
+ }
+ spin_lock(&redirect_lock);
+ if (redirect) {
+ spin_unlock(&redirect_lock);
+ return -EBUSY;
+ }
+ get_file(file);
+ redirect = file;
+ spin_unlock(&redirect_lock);
+ return 0;
+}
+
+
+static int fionbio(struct file *file, int __user *p)
+{
+ int nonblock;
+
+ if (get_user(nonblock, p))
+ return -EFAULT;
+
+ if (nonblock)
+ file->f_flags |= O_NONBLOCK;
+ else
+ file->f_flags &= ~O_NONBLOCK;
+ return 0;
+}
+
+static int tiocsctty(struct tty_struct *tty, int arg)
+{
+ task_t *p;
+
+ if (current->signal->leader &&
+ (current->signal->session == tty->session))
+ return 0;
+ /*
+ * The process must be a session leader and
+ * not have a controlling tty already.
+ */
+ if (!current->signal->leader || current->signal->tty)
+ return -EPERM;
+ if (tty->session > 0) {
+ /*
+ * This tty is already the controlling
+ * tty for another session group!
+ */
+ if ((arg == 1) && capable(CAP_SYS_ADMIN)) {
+ /*
+ * Steal it away
+ */
+
+ read_lock(&tasklist_lock);
+ do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+ p->signal->tty = NULL;
+ } while_each_task_pid(tty->session, PIDTYPE_SID, p);
+ read_unlock(&tasklist_lock);
+ } else
+ return -EPERM;
+ }
+ task_lock(current);
+ current->signal->tty = tty;
+ task_unlock(current);
+ current->signal->tty_old_pgrp = 0;
+ tty->session = current->signal->session;
+ tty->pgrp = process_group(current);
+ return 0;
+}
+
+static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ /*
+ * (tty == real_tty) is a cheap way of
+ * testing if the tty is NOT a master pty.
+ */
+ if (tty == real_tty && current->signal->tty != real_tty)
+ return -ENOTTY;
+ return put_user(real_tty->pgrp, p);
+}
+
+static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ pid_t pgrp;
+ int retval = tty_check_change(real_tty);
+
+ if (retval == -EIO)
+ return -ENOTTY;
+ if (retval)
+ return retval;
+ if (!current->signal->tty ||
+ (current->signal->tty != real_tty) ||
+ (real_tty->session != current->signal->session))
+ return -ENOTTY;
+ if (get_user(pgrp, p))
+ return -EFAULT;
+ if (pgrp < 0)
+ return -EINVAL;
+ if (session_of_pgrp(pgrp) != current->signal->session)
+ return -EPERM;
+ real_tty->pgrp = pgrp;
+ return 0;
+}
+
+static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+ /*
+ * (tty == real_tty) is a cheap way of
+ * testing if the tty is NOT a master pty.
+ */
+ if (tty == real_tty && current->signal->tty != real_tty)
+ return -ENOTTY;
+ if (real_tty->session <= 0)
+ return -ENOTTY;
+ return put_user(real_tty->session, p);
+}
+
+static int tiocsetd(struct tty_struct *tty, int __user *p)
+{
+ int ldisc;
+
+ if (get_user(ldisc, p))
+ return -EFAULT;
+ return tty_set_ldisc(tty, ldisc);
+}
+
+static int send_break(struct tty_struct *tty, int duration)
+{
+ tty->driver->break_ctl(tty, -1);
+ if (!signal_pending(current)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(duration);
+ }
+ tty->driver->break_ctl(tty, 0);
+ if (signal_pending(current))
+ return -EINTR;
+ return 0;
+}
+
+static int
+tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p)
+{
+ int retval = -EINVAL;
+
+ if (tty->driver->tiocmget) {
+ retval = tty->driver->tiocmget(tty, file);
+
+ if (retval >= 0)
+ retval = put_user(retval, p);
+ }
+ return retval;
+}
+
+static int
+tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int cmd,
+ unsigned __user *p)
+{
+ int retval = -EINVAL;
+
+ if (tty->driver->tiocmset) {
+ unsigned int set, clear, val;
+
+ retval = get_user(val, p);
+ if (retval)
+ return retval;
+
+ set = clear = 0;
+ switch (cmd) {
+ case TIOCMBIS:
+ set = val;
+ break;
+ case TIOCMBIC:
+ clear = val;
+ break;
+ case TIOCMSET:
+ set = val;
+ clear = ~val;
+ break;
+ }
+
+ set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+ clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+
+ retval = tty->driver->tiocmset(tty, file, set, clear);
+ }
+ return retval;
+}
+
+/*
+ * Split this up, as gcc can choke on it otherwise..
+ */
+int tty_ioctl(struct inode * inode, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tty_struct *tty, *real_tty;
+ void __user *p = (void __user *)arg;
+ int retval;
+ struct tty_ldisc *ld;
+
+ tty = (struct tty_struct *)file->private_data;
+ if (tty_paranoia_check(tty, inode, "tty_ioctl"))
+ return -EINVAL;
+
+ real_tty = tty;
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ real_tty = tty->link;
+
+ /*
+ * Break handling by driver
+ */
+ if (!tty->driver->break_ctl) {
+ switch(cmd) {
+ case TIOCSBRK:
+ case TIOCCBRK:
+ if (tty->driver->ioctl)
+ return tty->driver->ioctl(tty, file, cmd, arg);
+ return -EINVAL;
+
+ /* These two ioctl's always return success; even if */
+ /* the driver doesn't support them. */
+ case TCSBRK:
+ case TCSBRKP:
+ if (!tty->driver->ioctl)
+ return 0;
+ retval = tty->driver->ioctl(tty, file, cmd, arg);
+ if (retval == -ENOIOCTLCMD)
+ retval = 0;
+ return retval;
+ }
+ }
+
+ /*
+ * Factor out some common prep work
+ */
+ switch (cmd) {
+ case TIOCSETD:
+ case TIOCSBRK:
+ case TIOCCBRK:
+ case TCSBRK:
+ case TCSBRKP:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ if (cmd != TIOCCBRK) {
+ tty_wait_until_sent(tty, 0);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+ break;
+ }
+
+ switch (cmd) {
+ case TIOCSTI:
+ return tiocsti(tty, p);
+ case TIOCGWINSZ:
+ return tiocgwinsz(tty, p);
+ case TIOCSWINSZ:
+ return tiocswinsz(tty, real_tty, p);
+ case TIOCCONS:
+ return real_tty!=tty ? -EINVAL : tioccons(file);
+ case FIONBIO:
+ return fionbio(file, p);
+ case TIOCEXCL:
+ set_bit(TTY_EXCLUSIVE, &tty->flags);
+ return 0;
+ case TIOCNXCL:
+ clear_bit(TTY_EXCLUSIVE, &tty->flags);
+ return 0;
+ case TIOCNOTTY:
+ if (current->signal->tty != tty)
+ return -ENOTTY;
+ if (current->signal->leader)
+ disassociate_ctty(0);
+ task_lock(current);
+ current->signal->tty = NULL;
+ task_unlock(current);
+ return 0;
+ case TIOCSCTTY:
+ return tiocsctty(tty, arg);
+ case TIOCGPGRP:
+ return tiocgpgrp(tty, real_tty, p);
+ case TIOCSPGRP:
+ return tiocspgrp(tty, real_tty, p);
+ case TIOCGSID:
+ return tiocgsid(tty, real_tty, p);
+ case TIOCGETD:
+ /* FIXME: check this is ok */
+ return put_user(tty->ldisc.num, (int __user *)p);
+ case TIOCSETD:
+ return tiocsetd(tty, p);
+#ifdef CONFIG_VT
+ case TIOCLINUX:
+ return tioclinux(tty, arg);
+#endif
+ /*
+ * Break handling
+ */
+ case TIOCSBRK: /* Turn break on, unconditionally */
+ tty->driver->break_ctl(tty, -1);
+ return 0;
+
+ case TIOCCBRK: /* Turn break off, unconditionally */
+ tty->driver->break_ctl(tty, 0);
+ return 0;
+ case TCSBRK: /* SVID version: non-zero arg --> no break */
+ /*
+ * XXX is the above comment correct, or the
+ * code below correct? Is this ioctl used at
+ * all by anyone?
+ */
+ if (!arg)
+ return send_break(tty, HZ/4);
+ return 0;
+ case TCSBRKP: /* support for POSIX tcsendbreak() */
+ return send_break(tty, arg ? arg*(HZ/10) : HZ/4);
+
+ case TIOCMGET:
+ return tty_tiocmget(tty, file, p);
+
+ case TIOCMSET:
+ case TIOCMBIC:
+ case TIOCMBIS:
+ return tty_tiocmset(tty, file, cmd, p);
+ }
+ if (tty->driver->ioctl) {
+ retval = (tty->driver->ioctl)(tty, file, cmd, arg);
+ if (retval != -ENOIOCTLCMD)
+ return retval;
+ }
+ ld = tty_ldisc_ref_wait(tty);
+ retval = -EINVAL;
+ if (ld->ioctl) {
+ retval = ld->ioctl(tty, file, cmd, arg);
+ if (retval == -ENOIOCTLCMD)
+ retval = -EINVAL;
+ }
+ tty_ldisc_deref(ld);
+ return retval;
+}
+
+
+/*
+ * This implements the "Secure Attention Key" --- the idea is to
+ * prevent trojan horses by killing all processes associated with this
+ * tty when the user hits the "Secure Attention Key". Required for
+ * super-paranoid applications --- see the Orange Book for more details.
+ *
+ * This code could be nicer; ideally it should send a HUP, wait a few
+ * seconds, then send a INT, and then a KILL signal. But you then
+ * have to coordinate with the init process, since all processes associated
+ * with the current tty must be dead before the new getty is allowed
+ * to spawn.
+ *
+ * Now, if it would be correct ;-/ The current code has a nasty hole -
+ * it doesn't catch files in flight. We may send the descriptor to ourselves
+ * via AF_UNIX socket, close it and later fetch from socket. FIXME.
+ *
+ * Nasty bug: do_SAK is being called in interrupt context. This can
+ * deadlock. We punt it up to process context. AKPM - 16Mar2001
+ */
+static void __do_SAK(void *arg)
+{
+#ifdef TTY_SOFT_SAK
+ tty_hangup(tty);
+#else
+ struct tty_struct *tty = arg;
+ struct task_struct *p;
+ int session;
+ int i;
+ struct file *filp;
+ struct tty_ldisc *disc;
+
+ if (!tty)
+ return;
+ session = tty->session;
+
+ /* We don't want an ldisc switch during this */
+ disc = tty_ldisc_ref(tty);
+ if (disc && disc->flush_buffer)
+ disc->flush_buffer(tty);
+ tty_ldisc_deref(disc);
+
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+
+ read_lock(&tasklist_lock);
+ do_each_task_pid(session, PIDTYPE_SID, p) {
+ if (p->signal->tty == tty || session > 0) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): p->signal->session==tty->session\n",
+ p->pid, p->comm);
+ send_sig(SIGKILL, p, 1);
+ continue;
+ }
+ task_lock(p);
+ if (p->files) {
+ spin_lock(&p->files->file_lock);
+ for (i=0; i < p->files->max_fds; i++) {
+ filp = fcheck_files(p->files, i);
+ if (!filp)
+ continue;
+ if (filp->f_op->read == tty_read &&
+ filp->private_data == tty) {
+ printk(KERN_NOTICE "SAK: killed process %d"
+ " (%s): fd#%d opened to the tty\n",
+ p->pid, p->comm, i);
+ send_sig(SIGKILL, p, 1);
+ break;
+ }
+ }
+ spin_unlock(&p->files->file_lock);
+ }
+ task_unlock(p);
+ } while_each_task_pid(session, PIDTYPE_SID, p);
+ read_unlock(&tasklist_lock);
+#endif
+}
+
+/*
+ * The tq handling here is a little racy - tty->SAK_work may already be queued.
+ * Fortunately we don't need to worry, because if ->SAK_work is already queued,
+ * the values which we write to it will be identical to the values which it
+ * already has. --akpm
+ */
+void do_SAK(struct tty_struct *tty)
+{
+ if (!tty)
+ return;
+ PREPARE_WORK(&tty->SAK_work, __do_SAK, tty);
+ schedule_work(&tty->SAK_work);
+}
+
+EXPORT_SYMBOL(do_SAK);
+
+/*
+ * This routine is called out of the software interrupt to flush data
+ * from the flip buffer to the line discipline.
+ */
+
+static void flush_to_ldisc(void *private_)
+{
+ struct tty_struct *tty = (struct tty_struct *) private_;
+ unsigned char *cp;
+ char *fp;
+ int count;
+ unsigned long flags;
+ struct tty_ldisc *disc;
+
+ disc = tty_ldisc_ref(tty);
+ if (disc == NULL) /* !TTY_LDISC */
+ return;
+
+ if (test_bit(TTY_DONT_FLIP, &tty->flags)) {
+ /*
+ * Do it after the next timer tick:
+ */
+ schedule_delayed_work(&tty->flip.work, 1);
+ goto out;
+ }
+ spin_lock_irqsave(&tty->read_lock, flags);
+ if (tty->flip.buf_num) {
+ cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
+ fp = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
+ tty->flip.buf_num = 0;
+ tty->flip.char_buf_ptr = tty->flip.char_buf;
+ tty->flip.flag_buf_ptr = tty->flip.flag_buf;
+ } else {
+ cp = tty->flip.char_buf;
+ fp = tty->flip.flag_buf;
+ tty->flip.buf_num = 1;
+ tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
+ tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
+ }
+ count = tty->flip.count;
+ tty->flip.count = 0;
+ spin_unlock_irqrestore(&tty->read_lock, flags);
+
+ disc->receive_buf(tty, cp, fp, count);
+out:
+ tty_ldisc_deref(disc);
+}
+
+/*
+ * Routine which returns the baud rate of the tty
+ *
+ * Note that the baud_table needs to be kept in sync with the
+ * include/asm/termbits.h file.
+ */
+static int baud_table[] = {
+ 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+ 9600, 19200, 38400, 57600, 115200, 230400, 460800,
+#ifdef __sparc__
+ 76800, 153600, 307200, 614400, 921600
+#else
+ 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
+ 2500000, 3000000, 3500000, 4000000
+#endif
+};
+
+static int n_baud_table = ARRAY_SIZE(baud_table);
+
+/**
+ * tty_termios_baud_rate
+ * @termios: termios structure
+ *
+ * Convert termios baud rate data into a speed. This should be called
+ * with the termios lock held if this termios is a terminal termios
+ * structure. May change the termios data.
+ */
+
+int tty_termios_baud_rate(struct termios *termios)
+{
+ unsigned int cbaud;
+
+ cbaud = termios->c_cflag & CBAUD;
+
+ if (cbaud & CBAUDEX) {
+ cbaud &= ~CBAUDEX;
+
+ if (cbaud < 1 || cbaud + 15 > n_baud_table)
+ termios->c_cflag &= ~CBAUDEX;
+ else
+ cbaud += 15;
+ }
+ return baud_table[cbaud];
+}
+
+EXPORT_SYMBOL(tty_termios_baud_rate);
+
+/**
+ * tty_get_baud_rate - get tty bit rates
+ * @tty: tty to query
+ *
+ * Returns the baud rate as an integer for this terminal. The
+ * termios lock must be held by the caller and the terminal bit
+ * flags may be updated.
+ */
+
+int tty_get_baud_rate(struct tty_struct *tty)
+{
+ int baud = tty_termios_baud_rate(tty->termios);
+
+ if (baud == 38400 && tty->alt_speed) {
+ if (!tty->warned) {
+ printk(KERN_WARNING "Use of setserial/setrocket to "
+ "set SPD_* flags is deprecated\n");
+ tty->warned = 1;
+ }
+ baud = tty->alt_speed;
+ }
+
+ return baud;
+}
+
+EXPORT_SYMBOL(tty_get_baud_rate);
+
+/**
+ * tty_flip_buffer_push - terminal
+ * @tty: tty to push
+ *
+ * Queue a push of the terminal flip buffers to the line discipline. This
+ * function must not be called from IRQ context if tty->low_latency is set.
+ *
+ * In the event of the queue being busy for flipping the work will be
+ * held off and retried later.
+ */
+
+void tty_flip_buffer_push(struct tty_struct *tty)
+{
+ if (tty->low_latency)
+ flush_to_ldisc((void *) tty);
+ else
+ schedule_delayed_work(&tty->flip.work, 1);
+}
+
+EXPORT_SYMBOL(tty_flip_buffer_push);
+
+/*
+ * This subroutine initializes a tty structure.
+ */
+static void initialize_tty_struct(struct tty_struct *tty)
+{
+ memset(tty, 0, sizeof(struct tty_struct));
+ tty->magic = TTY_MAGIC;
+ tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+ tty->pgrp = -1;
+ tty->overrun_time = jiffies;
+ tty->flip.char_buf_ptr = tty->flip.char_buf;
+ tty->flip.flag_buf_ptr = tty->flip.flag_buf;
+ INIT_WORK(&tty->flip.work, flush_to_ldisc, tty);
+ init_MUTEX(&tty->flip.pty_sem);
+ init_MUTEX(&tty->termios_sem);
+ init_waitqueue_head(&tty->write_wait);
+ init_waitqueue_head(&tty->read_wait);
+ INIT_WORK(&tty->hangup_work, do_tty_hangup, tty);
+ sema_init(&tty->atomic_read, 1);
+ sema_init(&tty->atomic_write, 1);
+ spin_lock_init(&tty->read_lock);
+ INIT_LIST_HEAD(&tty->tty_files);
+ INIT_WORK(&tty->SAK_work, NULL, NULL);
+}
+
+/*
+ * The default put_char routine if the driver did not define one.
+ */
+static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ tty->driver->write(tty, &ch, 1);
+}
+
+static struct class_simple *tty_class;
+
+/**
+ * tty_register_device - register a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ * @device: a struct device that is associated with this tty device.
+ * This field is optional, if there is no known struct device for this
+ * tty device it can be set to NULL safely.
+ *
+ * This call is required to be made to register an individual tty device if
+ * the tty driver's flags have the TTY_DRIVER_NO_DEVFS bit set. If that
+ * bit is not set, this function should not be called.
+ */
+void tty_register_device(struct tty_driver *driver, unsigned index,
+ struct device *device)
+{
+ char name[64];
+ dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
+
+ if (index >= driver->num) {
+ printk(KERN_ERR "Attempt to register invalid tty line number "
+ " (%d).\n", index);
+ return;
+ }
+
+ devfs_mk_cdev(dev, S_IFCHR | S_IRUSR | S_IWUSR,
+ "%s%d", driver->devfs_name, index + driver->name_base);
+
+ if (driver->type == TTY_DRIVER_TYPE_PTY)
+ pty_line_name(driver, index, name);
+ else
+ tty_line_name(driver, index, name);
+ class_simple_device_add(tty_class, dev, device, name);
+}
+
+/**
+ * tty_unregister_device - unregister a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ *
+ * If a tty device is registered with a call to tty_register_device() then
+ * this function must be made when the tty device is gone.
+ */
+void tty_unregister_device(struct tty_driver *driver, unsigned index)
+{
+ devfs_remove("%s%d", driver->devfs_name, index + driver->name_base);
+ class_simple_device_remove(MKDEV(driver->major, driver->minor_start) + index);
+}
+
+EXPORT_SYMBOL(tty_register_device);
+EXPORT_SYMBOL(tty_unregister_device);
+
+struct tty_driver *alloc_tty_driver(int lines)
+{
+ struct tty_driver *driver;
+
+ driver = kmalloc(sizeof(struct tty_driver), GFP_KERNEL);
+ if (driver) {
+ memset(driver, 0, sizeof(struct tty_driver));
+ driver->magic = TTY_DRIVER_MAGIC;
+ driver->num = lines;
+ /* later we'll move allocation of tables here */
+ }
+ return driver;
+}
+
+void put_tty_driver(struct tty_driver *driver)
+{
+ kfree(driver);
+}
+
+void tty_set_operations(struct tty_driver *driver, struct tty_operations *op)
+{
+ driver->open = op->open;
+ driver->close = op->close;
+ driver->write = op->write;
+ driver->put_char = op->put_char;
+ driver->flush_chars = op->flush_chars;
+ driver->write_room = op->write_room;
+ driver->chars_in_buffer = op->chars_in_buffer;
+ driver->ioctl = op->ioctl;
+ driver->set_termios = op->set_termios;
+ driver->throttle = op->throttle;
+ driver->unthrottle = op->unthrottle;
+ driver->stop = op->stop;
+ driver->start = op->start;
+ driver->hangup = op->hangup;
+ driver->break_ctl = op->break_ctl;
+ driver->flush_buffer = op->flush_buffer;
+ driver->set_ldisc = op->set_ldisc;
+ driver->wait_until_sent = op->wait_until_sent;
+ driver->send_xchar = op->send_xchar;
+ driver->read_proc = op->read_proc;
+ driver->write_proc = op->write_proc;
+ driver->tiocmget = op->tiocmget;
+ driver->tiocmset = op->tiocmset;
+}
+
+
+EXPORT_SYMBOL(alloc_tty_driver);
+EXPORT_SYMBOL(put_tty_driver);
+EXPORT_SYMBOL(tty_set_operations);
+
+/*
+ * Called by a tty driver to register itself.
+ */
+int tty_register_driver(struct tty_driver *driver)
+{
+ int error;
+ int i;
+ dev_t dev;
+ void **p = NULL;
+
+ if (driver->flags & TTY_DRIVER_INSTALLED)
+ return 0;
+
+ if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+ p = kmalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ memset(p, 0, driver->num * 3 * sizeof(void *));
+ }
+
+ if (!driver->major) {
+ error = alloc_chrdev_region(&dev, driver->minor_start, driver->num,
+ (char*)driver->name);
+ if (!error) {
+ driver->major = MAJOR(dev);
+ driver->minor_start = MINOR(dev);
+ }
+ } else {
+ dev = MKDEV(driver->major, driver->minor_start);
+ error = register_chrdev_region(dev, driver->num,
+ (char*)driver->name);
+ }
+ if (error < 0) {
+ kfree(p);
+ return error;
+ }
+
+ if (p) {
+ driver->ttys = (struct tty_struct **)p;
+ driver->termios = (struct termios **)(p + driver->num);
+ driver->termios_locked = (struct termios **)(p + driver->num * 2);
+ } else {
+ driver->ttys = NULL;
+ driver->termios = NULL;
+ driver->termios_locked = NULL;
+ }
+
+ cdev_init(&driver->cdev, &tty_fops);
+ driver->cdev.owner = driver->owner;
+ error = cdev_add(&driver->cdev, dev, driver->num);
+ if (error) {
+ cdev_del(&driver->cdev);
+ unregister_chrdev_region(dev, driver->num);
+ driver->ttys = NULL;
+ driver->termios = driver->termios_locked = NULL;
+ kfree(p);
+ return error;
+ }
+
+ if (!driver->put_char)
+ driver->put_char = tty_default_put_char;
+
+ list_add(&driver->tty_drivers, &tty_drivers);
+
+ if ( !(driver->flags & TTY_DRIVER_NO_DEVFS) ) {
+ for(i = 0; i < driver->num; i++)
+ tty_register_device(driver, i, NULL);
+ }
+ proc_tty_register_driver(driver);
+ return 0;
+}
+
+EXPORT_SYMBOL(tty_register_driver);
+
+/*
+ * Called by a tty driver to unregister itself.
+ */
+int tty_unregister_driver(struct tty_driver *driver)
+{
+ int i;
+ struct termios *tp;
+ void *p;
+
+ if (driver->refcount)
+ return -EBUSY;
+
+ unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
+ driver->num);
+
+ list_del(&driver->tty_drivers);
+
+ /*
+ * Free the termios and termios_locked structures because
+ * we don't want to get memory leaks when modular tty
+ * drivers are removed from the kernel.
+ */
+ for (i = 0; i < driver->num; i++) {
+ tp = driver->termios[i];
+ if (tp) {
+ driver->termios[i] = NULL;
+ kfree(tp);
+ }
+ tp = driver->termios_locked[i];
+ if (tp) {
+ driver->termios_locked[i] = NULL;
+ kfree(tp);
+ }
+ if (!(driver->flags & TTY_DRIVER_NO_DEVFS))
+ tty_unregister_device(driver, i);
+ }
+ p = driver->ttys;
+ proc_tty_unregister_driver(driver);
+ driver->ttys = NULL;
+ driver->termios = driver->termios_locked = NULL;
+ kfree(p);
+ cdev_del(&driver->cdev);
+ return 0;
+}
+
+EXPORT_SYMBOL(tty_unregister_driver);
+
+
+/*
+ * Initialize the console device. This is called *early*, so
+ * we can't necessarily depend on lots of kernel help here.
+ * Just do some early initializations, and do the complex setup
+ * later.
+ */
+void __init console_init(void)
+{
+ initcall_t *call;
+
+ /* Setup the default TTY line discipline. */
+ (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
+
+ /*
+ * set up the console device so that later boot sequences can
+ * inform about problems etc..
+ */
+#ifdef CONFIG_EARLY_PRINTK
+ disable_early_printk();
+#endif
+#ifdef CONFIG_SERIAL_68360
+ /* This is not a console initcall. I know not what it's doing here.
+ So I haven't moved it. dwmw2 */
+ rs_360_init();
+#endif
+ call = __con_initcall_start;
+ while (call < __con_initcall_end) {
+ (*call)();
+ call++;
+ }
+}
+
+#ifdef CONFIG_VT
+extern int vty_init(void);
+#endif
+
+static int __init tty_class_init(void)
+{
+ tty_class = class_simple_create(THIS_MODULE, "tty");
+ if (IS_ERR(tty_class))
+ return PTR_ERR(tty_class);
+ return 0;
+}
+
+postcore_initcall(tty_class_init);
+
+/* 3/2004 jmc: why do these devices exist? */
+
+static struct cdev tty_cdev, console_cdev;
+#ifdef CONFIG_UNIX98_PTYS
+static struct cdev ptmx_cdev;
+#endif
+#ifdef CONFIG_VT
+static struct cdev vc0_cdev;
+#endif
+
+/*
+ * Ok, now we can initialize the rest of the tty devices and can count
+ * on memory allocations, interrupts etc..
+ */
+static int __init tty_init(void)
+{
+ cdev_init(&tty_cdev, &tty_fops);
+ if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
+ panic("Couldn't register /dev/tty driver\n");
+ devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty");
+ class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
+
+ cdev_init(&console_cdev, &console_fops);
+ if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
+ panic("Couldn't register /dev/console driver\n");
+ devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console");
+ class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 1), NULL, "console");
+
+#ifdef CONFIG_UNIX98_PTYS
+ cdev_init(&ptmx_cdev, &ptmx_fops);
+ if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
+ register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
+ panic("Couldn't register /dev/ptmx driver\n");
+ devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
+ class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
+#endif
+
+#ifdef CONFIG_VT
+ cdev_init(&vc0_cdev, &console_fops);
+ if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
+ register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
+ panic("Couldn't register /dev/tty0 driver\n");
+ devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0");
+ class_simple_device_add(tty_class, MKDEV(TTY_MAJOR, 0), NULL, "tty0");
+
+ vty_init();
+#endif
+ return 0;
+}
+module_init(tty_init);
diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c
new file mode 100644
index 000000000000..58597993954f
--- /dev/null
+++ b/drivers/char/tty_ioctl.c
@@ -0,0 +1,551 @@
+/*
+ * linux/drivers/char/tty_ioctl.c
+ *
+ * Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
+ *
+ * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
+ * which can be dynamically activated and de-activated by the line
+ * discipline handling modules (like SLIP).
+ */
+
+#include <linux/types.h>
+#include <linux/termios.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/tty.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#undef TTY_DEBUG_WAIT_UNTIL_SENT
+
+#undef DEBUG
+
+/*
+ * Internal flag options for termios setting behavior
+ */
+#define TERMIOS_FLUSH 1
+#define TERMIOS_WAIT 2
+#define TERMIOS_TERMIO 4
+
+void tty_wait_until_sent(struct tty_struct * tty, long timeout)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
+ char buf[64];
+
+ printk(KERN_DEBUG "%s wait until sent...\n", tty_name(tty, buf));
+#endif
+ if (!tty->driver->chars_in_buffer)
+ return;
+ add_wait_queue(&tty->write_wait, &wait);
+ if (!timeout)
+ timeout = MAX_SCHEDULE_TIMEOUT;
+ do {
+#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
+ printk(KERN_DEBUG "waiting %s...(%d)\n", tty_name(tty, buf),
+ tty->driver->chars_in_buffer(tty));
+#endif
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current))
+ goto stop_waiting;
+ if (!tty->driver->chars_in_buffer(tty))
+ break;
+ timeout = schedule_timeout(timeout);
+ } while (timeout);
+ if (tty->driver->wait_until_sent)
+ tty->driver->wait_until_sent(tty, timeout);
+stop_waiting:
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&tty->write_wait, &wait);
+}
+
+EXPORT_SYMBOL(tty_wait_until_sent);
+
+static void unset_locked_termios(struct termios *termios,
+ struct termios *old,
+ struct termios *locked)
+{
+ int i;
+
+#define NOSET_MASK(x,y,z) (x = ((x) & ~(z)) | ((y) & (z)))
+
+ if (!locked) {
+ printk(KERN_WARNING "Warning?!? termios_locked is NULL.\n");
+ return;
+ }
+
+ NOSET_MASK(termios->c_iflag, old->c_iflag, locked->c_iflag);
+ NOSET_MASK(termios->c_oflag, old->c_oflag, locked->c_oflag);
+ NOSET_MASK(termios->c_cflag, old->c_cflag, locked->c_cflag);
+ NOSET_MASK(termios->c_lflag, old->c_lflag, locked->c_lflag);
+ termios->c_line = locked->c_line ? old->c_line : termios->c_line;
+ for (i=0; i < NCCS; i++)
+ termios->c_cc[i] = locked->c_cc[i] ?
+ old->c_cc[i] : termios->c_cc[i];
+}
+
+static void change_termios(struct tty_struct * tty, struct termios * new_termios)
+{
+ int canon_change;
+ struct termios old_termios = *tty->termios;
+ struct tty_ldisc *ld;
+
+ /*
+ * Perform the actual termios internal changes under lock.
+ */
+
+
+ /* FIXME: we need to decide on some locking/ordering semantics
+ for the set_termios notification eventually */
+ down(&tty->termios_sem);
+
+ *tty->termios = *new_termios;
+ unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
+ canon_change = (old_termios.c_lflag ^ tty->termios->c_lflag) & ICANON;
+ if (canon_change) {
+ memset(&tty->read_flags, 0, sizeof tty->read_flags);
+ tty->canon_head = tty->read_tail;
+ tty->canon_data = 0;
+ tty->erasing = 0;
+ }
+
+
+ if (canon_change && !L_ICANON(tty) && tty->read_cnt)
+ /* Get characters left over from canonical mode. */
+ wake_up_interruptible(&tty->read_wait);
+
+ /* See if packet mode change of state. */
+
+ if (tty->link && tty->link->packet) {
+ int old_flow = ((old_termios.c_iflag & IXON) &&
+ (old_termios.c_cc[VSTOP] == '\023') &&
+ (old_termios.c_cc[VSTART] == '\021'));
+ int new_flow = (I_IXON(tty) &&
+ STOP_CHAR(tty) == '\023' &&
+ START_CHAR(tty) == '\021');
+ if (old_flow != new_flow) {
+ tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
+ if (new_flow)
+ tty->ctrl_status |= TIOCPKT_DOSTOP;
+ else
+ tty->ctrl_status |= TIOCPKT_NOSTOP;
+ wake_up_interruptible(&tty->link->read_wait);
+ }
+ }
+
+ if (tty->driver->set_termios)
+ (*tty->driver->set_termios)(tty, &old_termios);
+
+ ld = tty_ldisc_ref(tty);
+ if (ld != NULL) {
+ if (ld->set_termios)
+ (ld->set_termios)(tty, &old_termios);
+ tty_ldisc_deref(ld);
+ }
+ up(&tty->termios_sem);
+}
+
+static int set_termios(struct tty_struct * tty, void __user *arg, int opt)
+{
+ struct termios tmp_termios;
+ struct tty_ldisc *ld;
+ int retval = tty_check_change(tty);
+
+ if (retval)
+ return retval;
+
+ if (opt & TERMIOS_TERMIO) {
+ memcpy(&tmp_termios, tty->termios, sizeof(struct termios));
+ if (user_termio_to_kernel_termios(&tmp_termios,
+ (struct termio __user *)arg))
+ return -EFAULT;
+ } else {
+ if (user_termios_to_kernel_termios(&tmp_termios,
+ (struct termios __user *)arg))
+ return -EFAULT;
+ }
+
+ ld = tty_ldisc_ref(tty);
+
+ if (ld != NULL) {
+ if ((opt & TERMIOS_FLUSH) && ld->flush_buffer)
+ ld->flush_buffer(tty);
+ tty_ldisc_deref(ld);
+ }
+
+ if (opt & TERMIOS_WAIT) {
+ tty_wait_until_sent(tty, 0);
+ if (signal_pending(current))
+ return -EINTR;
+ }
+
+ change_termios(tty, &tmp_termios);
+ return 0;
+}
+
+static int get_termio(struct tty_struct * tty, struct termio __user * termio)
+{
+ if (kernel_termios_to_user_termio(termio, tty->termios))
+ return -EFAULT;
+ return 0;
+}
+
+static unsigned long inq_canon(struct tty_struct * tty)
+{
+ int nr, head, tail;
+
+ if (!tty->canon_data || !tty->read_buf)
+ return 0;
+ head = tty->canon_head;
+ tail = tty->read_tail;
+ nr = (head - tail) & (N_TTY_BUF_SIZE-1);
+ /* Skip EOF-chars.. */
+ while (head != tail) {
+ if (test_bit(tail, tty->read_flags) &&
+ tty->read_buf[tail] == __DISABLED_CHAR)
+ nr--;
+ tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+ }
+ return nr;
+}
+
+#ifdef TIOCGETP
+/*
+ * These are deprecated, but there is limited support..
+ *
+ * The "sg_flags" translation is a joke..
+ */
+static int get_sgflags(struct tty_struct * tty)
+{
+ int flags = 0;
+
+ if (!(tty->termios->c_lflag & ICANON)) {
+ if (tty->termios->c_lflag & ISIG)
+ flags |= 0x02; /* cbreak */
+ else
+ flags |= 0x20; /* raw */
+ }
+ if (tty->termios->c_lflag & ECHO)
+ flags |= 0x08; /* echo */
+ if (tty->termios->c_oflag & OPOST)
+ if (tty->termios->c_oflag & ONLCR)
+ flags |= 0x10; /* crmod */
+ return flags;
+}
+
+static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
+{
+ struct sgttyb tmp;
+
+ down(&tty->termios_sem);
+ tmp.sg_ispeed = 0;
+ tmp.sg_ospeed = 0;
+ tmp.sg_erase = tty->termios->c_cc[VERASE];
+ tmp.sg_kill = tty->termios->c_cc[VKILL];
+ tmp.sg_flags = get_sgflags(tty);
+ up(&tty->termios_sem);
+
+ return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static void set_sgflags(struct termios * termios, int flags)
+{
+ termios->c_iflag = ICRNL | IXON;
+ termios->c_oflag = 0;
+ termios->c_lflag = ISIG | ICANON;
+ if (flags & 0x02) { /* cbreak */
+ termios->c_iflag = 0;
+ termios->c_lflag &= ~ICANON;
+ }
+ if (flags & 0x08) { /* echo */
+ termios->c_lflag |= ECHO | ECHOE | ECHOK |
+ ECHOCTL | ECHOKE | IEXTEN;
+ }
+ if (flags & 0x10) { /* crmod */
+ termios->c_oflag |= OPOST | ONLCR;
+ }
+ if (flags & 0x20) { /* raw */
+ termios->c_iflag = 0;
+ termios->c_lflag &= ~(ISIG | ICANON);
+ }
+ if (!(termios->c_lflag & ICANON)) {
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
+ }
+}
+
+static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
+{
+ int retval;
+ struct sgttyb tmp;
+ struct termios termios;
+
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+
+ if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
+ return -EFAULT;
+
+ down(&tty->termios_sem);
+ termios = *tty->termios;
+ termios.c_cc[VERASE] = tmp.sg_erase;
+ termios.c_cc[VKILL] = tmp.sg_kill;
+ set_sgflags(&termios, tmp.sg_flags);
+ up(&tty->termios_sem);
+ change_termios(tty, &termios);
+ return 0;
+}
+#endif
+
+#ifdef TIOCGETC
+static int get_tchars(struct tty_struct * tty, struct tchars __user * tchars)
+{
+ struct tchars tmp;
+
+ tmp.t_intrc = tty->termios->c_cc[VINTR];
+ tmp.t_quitc = tty->termios->c_cc[VQUIT];
+ tmp.t_startc = tty->termios->c_cc[VSTART];
+ tmp.t_stopc = tty->termios->c_cc[VSTOP];
+ tmp.t_eofc = tty->termios->c_cc[VEOF];
+ tmp.t_brkc = tty->termios->c_cc[VEOL2]; /* what is brkc anyway? */
+ return copy_to_user(tchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int set_tchars(struct tty_struct * tty, struct tchars __user * tchars)
+{
+ struct tchars tmp;
+
+ if (copy_from_user(&tmp, tchars, sizeof(tmp)))
+ return -EFAULT;
+ tty->termios->c_cc[VINTR] = tmp.t_intrc;
+ tty->termios->c_cc[VQUIT] = tmp.t_quitc;
+ tty->termios->c_cc[VSTART] = tmp.t_startc;
+ tty->termios->c_cc[VSTOP] = tmp.t_stopc;
+ tty->termios->c_cc[VEOF] = tmp.t_eofc;
+ tty->termios->c_cc[VEOL2] = tmp.t_brkc; /* what is brkc anyway? */
+ return 0;
+}
+#endif
+
+#ifdef TIOCGLTC
+static int get_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
+{
+ struct ltchars tmp;
+
+ tmp.t_suspc = tty->termios->c_cc[VSUSP];
+ tmp.t_dsuspc = tty->termios->c_cc[VSUSP]; /* what is dsuspc anyway? */
+ tmp.t_rprntc = tty->termios->c_cc[VREPRINT];
+ tmp.t_flushc = tty->termios->c_cc[VEOL2]; /* what is flushc anyway? */
+ tmp.t_werasc = tty->termios->c_cc[VWERASE];
+ tmp.t_lnextc = tty->termios->c_cc[VLNEXT];
+ return copy_to_user(ltchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int set_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
+{
+ struct ltchars tmp;
+
+ if (copy_from_user(&tmp, ltchars, sizeof(tmp)))
+ return -EFAULT;
+
+ tty->termios->c_cc[VSUSP] = tmp.t_suspc;
+ tty->termios->c_cc[VEOL2] = tmp.t_dsuspc; /* what is dsuspc anyway? */
+ tty->termios->c_cc[VREPRINT] = tmp.t_rprntc;
+ tty->termios->c_cc[VEOL2] = tmp.t_flushc; /* what is flushc anyway? */
+ tty->termios->c_cc[VWERASE] = tmp.t_werasc;
+ tty->termios->c_cc[VLNEXT] = tmp.t_lnextc;
+ return 0;
+}
+#endif
+
+/*
+ * Send a high priority character to the tty.
+ */
+static void send_prio_char(struct tty_struct *tty, char ch)
+{
+ int was_stopped = tty->stopped;
+
+ if (tty->driver->send_xchar) {
+ tty->driver->send_xchar(tty, ch);
+ return;
+ }
+ if (was_stopped)
+ start_tty(tty);
+ tty->driver->write(tty, &ch, 1);
+ if (was_stopped)
+ stop_tty(tty);
+}
+
+int n_tty_ioctl(struct tty_struct * tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct tty_struct * real_tty;
+ void __user *p = (void __user *)arg;
+ int retval;
+ struct tty_ldisc *ld;
+
+ if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+ tty->driver->subtype == PTY_TYPE_MASTER)
+ real_tty = tty->link;
+ else
+ real_tty = tty;
+
+ switch (cmd) {
+#ifdef TIOCGETP
+ case TIOCGETP:
+ return get_sgttyb(real_tty, (struct sgttyb __user *) arg);
+ case TIOCSETP:
+ case TIOCSETN:
+ return set_sgttyb(real_tty, (struct sgttyb __user *) arg);
+#endif
+#ifdef TIOCGETC
+ case TIOCGETC:
+ return get_tchars(real_tty, p);
+ case TIOCSETC:
+ return set_tchars(real_tty, p);
+#endif
+#ifdef TIOCGLTC
+ case TIOCGLTC:
+ return get_ltchars(real_tty, p);
+ case TIOCSLTC:
+ return set_ltchars(real_tty, p);
+#endif
+ case TCGETS:
+ if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios))
+ return -EFAULT;
+ return 0;
+ case TCSETSF:
+ return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT);
+ case TCSETSW:
+ return set_termios(real_tty, p, TERMIOS_WAIT);
+ case TCSETS:
+ return set_termios(real_tty, p, 0);
+ case TCGETA:
+ return get_termio(real_tty, p);
+ case TCSETAF:
+ return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO);
+ case TCSETAW:
+ return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_TERMIO);
+ case TCSETA:
+ return set_termios(real_tty, p, TERMIOS_TERMIO);
+ case TCXONC:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+ switch (arg) {
+ case TCOOFF:
+ if (!tty->flow_stopped) {
+ tty->flow_stopped = 1;
+ stop_tty(tty);
+ }
+ break;
+ case TCOON:
+ if (tty->flow_stopped) {
+ tty->flow_stopped = 0;
+ start_tty(tty);
+ }
+ break;
+ case TCIOFF:
+ if (STOP_CHAR(tty) != __DISABLED_CHAR)
+ send_prio_char(tty, STOP_CHAR(tty));
+ break;
+ case TCION:
+ if (START_CHAR(tty) != __DISABLED_CHAR)
+ send_prio_char(tty, START_CHAR(tty));
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+ case TCFLSH:
+ retval = tty_check_change(tty);
+ if (retval)
+ return retval;
+
+ ld = tty_ldisc_ref(tty);
+ switch (arg) {
+ case TCIFLUSH:
+ if (ld->flush_buffer)
+ ld->flush_buffer(tty);
+ break;
+ case TCIOFLUSH:
+ if (ld->flush_buffer)
+ ld->flush_buffer(tty);
+ /* fall through */
+ case TCOFLUSH:
+ if (tty->driver->flush_buffer)
+ tty->driver->flush_buffer(tty);
+ break;
+ default:
+ tty_ldisc_deref(ld);
+ return -EINVAL;
+ }
+ tty_ldisc_deref(ld);
+ return 0;
+ case TIOCOUTQ:
+ return put_user(tty->driver->chars_in_buffer ?
+ tty->driver->chars_in_buffer(tty) : 0,
+ (int __user *) arg);
+ case TIOCINQ:
+ retval = tty->read_cnt;
+ if (L_ICANON(tty))
+ retval = inq_canon(tty);
+ return put_user(retval, (unsigned int __user *) arg);
+ case TIOCGLCKTRMIOS:
+ if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios_locked))
+ return -EFAULT;
+ return 0;
+
+ case TIOCSLCKTRMIOS:
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (user_termios_to_kernel_termios(real_tty->termios_locked, (struct termios __user *) arg))
+ return -EFAULT;
+ return 0;
+
+ case TIOCPKT:
+ {
+ int pktmode;
+
+ if (tty->driver->type != TTY_DRIVER_TYPE_PTY ||
+ tty->driver->subtype != PTY_TYPE_MASTER)
+ return -ENOTTY;
+ if (get_user(pktmode, (int __user *) arg))
+ return -EFAULT;
+ if (pktmode) {
+ if (!tty->packet) {
+ tty->packet = 1;
+ tty->link->ctrl_status = 0;
+ }
+ } else
+ tty->packet = 0;
+ return 0;
+ }
+ case TIOCGSOFTCAR:
+ return put_user(C_CLOCAL(tty) ? 1 : 0, (int __user *)arg);
+ case TIOCSSOFTCAR:
+ if (get_user(arg, (unsigned int __user *) arg))
+ return -EFAULT;
+ down(&tty->termios_sem);
+ tty->termios->c_cflag =
+ ((tty->termios->c_cflag & ~CLOCAL) |
+ (arg ? CLOCAL : 0));
+ up(&tty->termios_sem);
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+EXPORT_SYMBOL(n_tty_ioctl);
diff --git a/drivers/char/vc_screen.c b/drivers/char/vc_screen.c
new file mode 100644
index 000000000000..7abe405b8657
--- /dev/null
+++ b/drivers/char/vc_screen.c
@@ -0,0 +1,509 @@
+/*
+ * linux/drivers/char/vc_screen.c
+ *
+ * Provide access to virtual console memory.
+ * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled)
+ * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
+ * [minor: N]
+ *
+ * /dev/vcsaN: idem, but including attributes, and prefixed with
+ * the 4 bytes lines,columns,x,y (as screendump used to give).
+ * Attribute/character pair is in native endianity.
+ * [minor: N+128]
+ *
+ * This replaces screendump and part of selection, so that the system
+ * administrator can control access using file system permissions.
+ *
+ * aeb@cwi.nl - efter Friedas begravelse - 950211
+ *
+ * machek@k332.feld.cvut.cz - modified not to send characters to wrong console
+ * - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...)
+ * - making it shorter - scr_readw are macros which expand in PRETTY long code
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/kbd_kern.h>
+#include <linux/console.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#undef attr
+#undef org
+#undef addr
+#define HEADER_SIZE 4
+
+static int
+vcs_size(struct inode *inode)
+{
+ int size;
+ int minor = iminor(inode);
+ int currcons = minor & 127;
+ struct vc_data *vc;
+
+ if (currcons == 0)
+ currcons = fg_console;
+ else
+ currcons--;
+ if (!vc_cons_allocated(currcons))
+ return -ENXIO;
+ vc = vc_cons[currcons].d;
+
+ size = vc->vc_rows * vc->vc_cols;
+
+ if (minor & 128)
+ size = 2*size + HEADER_SIZE;
+ return size;
+}
+
+static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
+{
+ int size;
+
+ down(&con_buf_sem);
+ size = vcs_size(file->f_dentry->d_inode);
+ switch (orig) {
+ default:
+ up(&con_buf_sem);
+ return -EINVAL;
+ case 2:
+ offset += size;
+ break;
+ case 1:
+ offset += file->f_pos;
+ case 0:
+ break;
+ }
+ if (offset < 0 || offset > size) {
+ up(&con_buf_sem);
+ return -EINVAL;
+ }
+ file->f_pos = offset;
+ up(&con_buf_sem);
+ return file->f_pos;
+}
+
+
+static ssize_t
+vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ unsigned int currcons = iminor(inode);
+ struct vc_data *vc;
+ long pos;
+ long viewed, attr, read;
+ int col, maxcol;
+ unsigned short *org = NULL;
+ ssize_t ret;
+
+ down(&con_buf_sem);
+
+ pos = *ppos;
+
+ /* Select the proper current console and verify
+ * sanity of the situation under the console lock.
+ */
+ acquire_console_sem();
+
+ attr = (currcons & 128);
+ currcons = (currcons & 127);
+ if (currcons == 0) {
+ currcons = fg_console;
+ viewed = 1;
+ } else {
+ currcons--;
+ viewed = 0;
+ }
+ ret = -ENXIO;
+ if (!vc_cons_allocated(currcons))
+ goto unlock_out;
+ vc = vc_cons[currcons].d;
+
+ ret = -EINVAL;
+ if (pos < 0)
+ goto unlock_out;
+ read = 0;
+ ret = 0;
+ while (count) {
+ char *con_buf0, *con_buf_start;
+ long this_round, size;
+ ssize_t orig_count;
+ long p = pos;
+
+ /* Check whether we are above size each round,
+ * as copy_to_user at the end of this loop
+ * could sleep.
+ */
+ size = vcs_size(inode);
+ if (pos >= size)
+ break;
+ if (count > size - pos)
+ count = size - pos;
+
+ this_round = count;
+ if (this_round > CON_BUF_SIZE)
+ this_round = CON_BUF_SIZE;
+
+ /* Perform the whole read into the local con_buf.
+ * Then we can drop the console spinlock and safely
+ * attempt to move it to userspace.
+ */
+
+ con_buf_start = con_buf0 = con_buf;
+ orig_count = this_round;
+ maxcol = vc->vc_cols;
+ if (!attr) {
+ org = screen_pos(vc, p, viewed);
+ col = p % maxcol;
+ p += maxcol - col;
+ while (this_round-- > 0) {
+ *con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff);
+ if (++col == maxcol) {
+ org = screen_pos(vc, p, viewed);
+ col = 0;
+ p += maxcol;
+ }
+ }
+ } else {
+ if (p < HEADER_SIZE) {
+ size_t tmp_count;
+
+ con_buf0[0] = (char)vc->vc_rows;
+ con_buf0[1] = (char)vc->vc_cols;
+ getconsxy(vc, con_buf0 + 2);
+
+ con_buf_start += p;
+ this_round += p;
+ if (this_round > CON_BUF_SIZE) {
+ this_round = CON_BUF_SIZE;
+ orig_count = this_round - p;
+ }
+
+ tmp_count = HEADER_SIZE;
+ if (tmp_count > this_round)
+ tmp_count = this_round;
+
+ /* Advance state pointers and move on. */
+ this_round -= tmp_count;
+ p = HEADER_SIZE;
+ con_buf0 = con_buf + HEADER_SIZE;
+ /* If this_round >= 0, then p is even... */
+ } else if (p & 1) {
+ /* Skip first byte for output if start address is odd
+ * Update region sizes up/down depending on free
+ * space in buffer.
+ */
+ con_buf_start++;
+ if (this_round < CON_BUF_SIZE)
+ this_round++;
+ else
+ orig_count--;
+ }
+ if (this_round > 0) {
+ unsigned short *tmp_buf = (unsigned short *)con_buf0;
+
+ p -= HEADER_SIZE;
+ p /= 2;
+ col = p % maxcol;
+
+ org = screen_pos(vc, p, viewed);
+ p += maxcol - col;
+
+ /* Buffer has even length, so we can always copy
+ * character + attribute. We do not copy last byte
+ * to userspace if this_round is odd.
+ */
+ this_round = (this_round + 1) >> 1;
+
+ while (this_round) {
+ *tmp_buf++ = vcs_scr_readw(vc, org++);
+ this_round --;
+ if (++col == maxcol) {
+ org = screen_pos(vc, p, viewed);
+ col = 0;
+ p += maxcol;
+ }
+ }
+ }
+ }
+
+ /* Finally, release the console semaphore while we push
+ * all the data to userspace from our temporary buffer.
+ *
+ * AKPM: Even though it's a semaphore, we should drop it because
+ * the pagefault handling code may want to call printk().
+ */
+
+ release_console_sem();
+ ret = copy_to_user(buf, con_buf_start, orig_count);
+ acquire_console_sem();
+
+ if (ret) {
+ read += (orig_count - ret);
+ ret = -EFAULT;
+ break;
+ }
+ buf += orig_count;
+ pos += orig_count;
+ read += orig_count;
+ count -= orig_count;
+ }
+ *ppos += read;
+ if (read)
+ ret = read;
+unlock_out:
+ release_console_sem();
+ up(&con_buf_sem);
+ return ret;
+}
+
+static ssize_t
+vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ unsigned int currcons = iminor(inode);
+ struct vc_data *vc;
+ long pos;
+ long viewed, attr, size, written;
+ char *con_buf0;
+ int col, maxcol;
+ u16 *org0 = NULL, *org = NULL;
+ size_t ret;
+
+ down(&con_buf_sem);
+
+ pos = *ppos;
+
+ /* Select the proper current console and verify
+ * sanity of the situation under the console lock.
+ */
+ acquire_console_sem();
+
+ attr = (currcons & 128);
+ currcons = (currcons & 127);
+
+ if (currcons == 0) {
+ currcons = fg_console;
+ viewed = 1;
+ } else {
+ currcons--;
+ viewed = 0;
+ }
+ ret = -ENXIO;
+ if (!vc_cons_allocated(currcons))
+ goto unlock_out;
+ vc = vc_cons[currcons].d;
+
+ size = vcs_size(inode);
+ ret = -EINVAL;
+ if (pos < 0 || pos > size)
+ goto unlock_out;
+ if (count > size - pos)
+ count = size - pos;
+ written = 0;
+ while (count) {
+ long this_round = count;
+ size_t orig_count;
+ long p;
+
+ if (this_round > CON_BUF_SIZE)
+ this_round = CON_BUF_SIZE;
+
+ /* Temporarily drop the console lock so that we can read
+ * in the write data from userspace safely.
+ */
+ release_console_sem();
+ ret = copy_from_user(con_buf, buf, this_round);
+ acquire_console_sem();
+
+ if (ret) {
+ this_round -= ret;
+ if (!this_round) {
+ /* Abort loop if no data were copied. Otherwise
+ * fail with -EFAULT.
+ */
+ if (written)
+ break;
+ ret = -EFAULT;
+ goto unlock_out;
+ }
+ }
+
+ /* The vcs_size might have changed while we slept to grab
+ * the user buffer, so recheck.
+ * Return data written up to now on failure.
+ */
+ size = vcs_size(inode);
+ if (pos >= size)
+ break;
+ if (this_round > size - pos)
+ this_round = size - pos;
+
+ /* OK, now actually push the write to the console
+ * under the lock using the local kernel buffer.
+ */
+
+ con_buf0 = con_buf;
+ orig_count = this_round;
+ maxcol = vc->vc_cols;
+ p = pos;
+ if (!attr) {
+ org0 = org = screen_pos(vc, p, viewed);
+ col = p % maxcol;
+ p += maxcol - col;
+
+ while (this_round > 0) {
+ unsigned char c = *con_buf0++;
+
+ this_round--;
+ vcs_scr_writew(vc,
+ (vcs_scr_readw(vc, org) & 0xff00) | c, org);
+ org++;
+ if (++col == maxcol) {
+ org = screen_pos(vc, p, viewed);
+ col = 0;
+ p += maxcol;
+ }
+ }
+ } else {
+ if (p < HEADER_SIZE) {
+ char header[HEADER_SIZE];
+
+ getconsxy(vc, header + 2);
+ while (p < HEADER_SIZE && this_round > 0) {
+ this_round--;
+ header[p++] = *con_buf0++;
+ }
+ if (!viewed)
+ putconsxy(vc, header + 2);
+ }
+ p -= HEADER_SIZE;
+ col = (p/2) % maxcol;
+ if (this_round > 0) {
+ org0 = org = screen_pos(vc, p/2, viewed);
+ if ((p & 1) && this_round > 0) {
+ char c;
+
+ this_round--;
+ c = *con_buf0++;
+#ifdef __BIG_ENDIAN
+ vcs_scr_writew(vc, c |
+ (vcs_scr_readw(vc, org) & 0xff00), org);
+#else
+ vcs_scr_writew(vc, (c << 8) |
+ (vcs_scr_readw(vc, org) & 0xff), org);
+#endif
+ org++;
+ p++;
+ if (++col == maxcol) {
+ org = screen_pos(vc, p/2, viewed);
+ col = 0;
+ }
+ }
+ p /= 2;
+ p += maxcol - col;
+ }
+ while (this_round > 1) {
+ unsigned short w;
+
+ w = get_unaligned(((const unsigned short *)con_buf0));
+ vcs_scr_writew(vc, w, org++);
+ con_buf0 += 2;
+ this_round -= 2;
+ if (++col == maxcol) {
+ org = screen_pos(vc, p, viewed);
+ col = 0;
+ p += maxcol;
+ }
+ }
+ if (this_round > 0) {
+ unsigned char c;
+
+ c = *con_buf0++;
+#ifdef __BIG_ENDIAN
+ vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org);
+#else
+ vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org);
+#endif
+ }
+ }
+ count -= orig_count;
+ written += orig_count;
+ buf += orig_count;
+ pos += orig_count;
+ if (org0)
+ update_region(vc, (unsigned long)(org0), org - org0);
+ }
+ *ppos += written;
+ ret = written;
+
+unlock_out:
+ release_console_sem();
+
+ up(&con_buf_sem);
+
+ return ret;
+}
+
+static int
+vcs_open(struct inode *inode, struct file *filp)
+{
+ unsigned int currcons = iminor(inode) & 127;
+ if(currcons && !vc_cons_allocated(currcons-1))
+ return -ENXIO;
+ return 0;
+}
+
+static struct file_operations vcs_fops = {
+ .llseek = vcs_lseek,
+ .read = vcs_read,
+ .write = vcs_write,
+ .open = vcs_open,
+};
+
+static struct class_simple *vc_class;
+
+void vcs_make_devfs(struct tty_struct *tty)
+{
+ devfs_mk_cdev(MKDEV(VCS_MAJOR, tty->index + 1),
+ S_IFCHR|S_IRUSR|S_IWUSR,
+ "vcc/%u", tty->index + 1);
+ devfs_mk_cdev(MKDEV(VCS_MAJOR, tty->index + 129),
+ S_IFCHR|S_IRUSR|S_IWUSR,
+ "vcc/a%u", tty->index + 1);
+ class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, tty->index + 1), NULL, "vcs%u", tty->index + 1);
+ class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, tty->index + 129), NULL, "vcsa%u", tty->index + 1);
+}
+void vcs_remove_devfs(struct tty_struct *tty)
+{
+ devfs_remove("vcc/%u", tty->index + 1);
+ devfs_remove("vcc/a%u", tty->index + 1);
+ class_simple_device_remove(MKDEV(VCS_MAJOR, tty->index + 1));
+ class_simple_device_remove(MKDEV(VCS_MAJOR, tty->index + 129));
+}
+
+int __init vcs_init(void)
+{
+ if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
+ panic("unable to get major %d for vcs device", VCS_MAJOR);
+ vc_class = class_simple_create(THIS_MODULE, "vc");
+
+ devfs_mk_cdev(MKDEV(VCS_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vcc/0");
+ devfs_mk_cdev(MKDEV(VCS_MAJOR, 128), S_IFCHR|S_IRUSR|S_IWUSR, "vcc/a0");
+ class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
+ class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
+ return 0;
+}
diff --git a/drivers/char/viocons.c b/drivers/char/viocons.c
new file mode 100644
index 000000000000..44f5fb4a46ef
--- /dev/null
+++ b/drivers/char/viocons.c
@@ -0,0 +1,1195 @@
+/* -*- linux-c -*-
+ *
+ * drivers/char/viocons.c
+ *
+ * iSeries Virtual Terminal
+ *
+ * Authors: Dave Boutcher <boutcher@us.ibm.com>
+ * Ryan Arnold <ryanarn@us.ibm.com>
+ * Colin Devilbiss <devilbis@us.ibm.com>
+ * Stephen Rothwell <sfr@au1.ibm.com>
+ *
+ * (C) Copyright 2000, 2001, 2002, 2003, 2004 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) anyu later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <asm/ioctls.h>
+#include <linux/kd.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sysrq.h>
+
+#include <asm/iSeries/vio.h>
+
+#include <asm/iSeries/HvLpEvent.h>
+#include <asm/iSeries/HvCallEvent.h>
+#include <asm/iSeries/HvLpConfig.h>
+#include <asm/iSeries/HvCall.h>
+
+#ifdef CONFIG_VT
+#error You must turn off CONFIG_VT to use CONFIG_VIOCONS
+#endif
+
+#define VIOTTY_MAGIC (0x0DCB)
+#define VTTY_PORTS 10
+
+#define VIOCONS_KERN_WARN KERN_WARNING "viocons: "
+#define VIOCONS_KERN_INFO KERN_INFO "viocons: "
+
+static DEFINE_SPINLOCK(consolelock);
+static DEFINE_SPINLOCK(consoleloglock);
+
+#ifdef CONFIG_MAGIC_SYSRQ
+static int vio_sysrq_pressed;
+extern int sysrq_enabled;
+#endif
+
+/*
+ * The structure of the events that flow between us and OS/400. You can't
+ * mess with this unless the OS/400 side changes too
+ */
+struct viocharlpevent {
+ struct HvLpEvent event;
+ u32 reserved;
+ u16 version;
+ u16 subtype_result_code;
+ u8 virtual_device;
+ u8 len;
+ u8 data[VIOCHAR_MAX_DATA];
+};
+
+#define VIOCHAR_WINDOW 10
+#define VIOCHAR_HIGHWATERMARK 3
+
+enum viocharsubtype {
+ viocharopen = 0x0001,
+ viocharclose = 0x0002,
+ viochardata = 0x0003,
+ viocharack = 0x0004,
+ viocharconfig = 0x0005
+};
+
+enum viochar_rc {
+ viochar_rc_ebusy = 1
+};
+
+#define VIOCHAR_NUM_BUF 16
+
+/*
+ * Our port information. We store a pointer to one entry in the
+ * tty_driver_data
+ */
+static struct port_info {
+ int magic;
+ struct tty_struct *tty;
+ HvLpIndex lp;
+ u8 vcons;
+ u64 seq; /* sequence number of last HV send */
+ u64 ack; /* last ack from HV */
+/*
+ * When we get writes faster than we can send it to the partition,
+ * buffer the data here. Note that used is a bit map of used buffers.
+ * It had better have enough bits to hold VIOCHAR_NUM_BUF the bitops assume
+ * it is a multiple of unsigned long
+ */
+ unsigned long used;
+ u8 *buffer[VIOCHAR_NUM_BUF];
+ int bufferBytes[VIOCHAR_NUM_BUF];
+ int curbuf;
+ int bufferOverflow;
+ int overflowMessage;
+} port_info[VTTY_PORTS];
+
+#define viochar_is_console(pi) ((pi) == &port_info[0])
+#define viochar_port(pi) ((pi) - &port_info[0])
+
+static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp);
+
+static struct tty_driver *viotty_driver;
+
+void hvlog(char *fmt, ...)
+{
+ int i;
+ unsigned long flags;
+ va_list args;
+ static char buf[256];
+
+ spin_lock_irqsave(&consoleloglock, flags);
+ va_start(args, fmt);
+ i = vscnprintf(buf, sizeof(buf) - 1, fmt, args);
+ va_end(args);
+ buf[i++] = '\r';
+ HvCall_writeLogBuffer(buf, i);
+ spin_unlock_irqrestore(&consoleloglock, flags);
+}
+
+void hvlogOutput(const char *buf, int count)
+{
+ unsigned long flags;
+ int begin;
+ int index;
+ static const char cr = '\r';
+
+ begin = 0;
+ spin_lock_irqsave(&consoleloglock, flags);
+ for (index = 0; index < count; index++) {
+ if (buf[index] == '\n') {
+ /*
+ * Start right after the last '\n' or at the zeroth
+ * array position and output the number of characters
+ * including the newline.
+ */
+ HvCall_writeLogBuffer(&buf[begin], index - begin + 1);
+ begin = index + 1;
+ HvCall_writeLogBuffer(&cr, 1);
+ }
+ }
+ if ((index - begin) > 0)
+ HvCall_writeLogBuffer(&buf[begin], index - begin);
+ spin_unlock_irqrestore(&consoleloglock, flags);
+}
+
+/*
+ * Make sure we're pointing to a valid port_info structure. Shamelessly
+ * plagerized from serial.c
+ */
+static inline int viotty_paranoia_check(struct port_info *pi,
+ char *name, const char *routine)
+{
+ static const char *bad_pi_addr = VIOCONS_KERN_WARN
+ "warning: bad address for port_info struct (%s) in %s\n";
+ static const char *badmagic = VIOCONS_KERN_WARN
+ "warning: bad magic number for port_info struct (%s) in %s\n";
+
+ if ((pi < &port_info[0]) || (viochar_port(pi) > VTTY_PORTS)) {
+ printk(bad_pi_addr, name, routine);
+ return 1;
+ }
+ if (pi->magic != VIOTTY_MAGIC) {
+ printk(badmagic, name, routine);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Add data to our pending-send buffers.
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive.
+ * hvlog can be used to log to the hypervisor buffer
+ */
+static int buffer_add(struct port_info *pi, const char *buf, size_t len)
+{
+ size_t bleft;
+ size_t curlen;
+ const char *curbuf;
+ int nextbuf;
+
+ curbuf = buf;
+ bleft = len;
+ while (bleft > 0) {
+ /*
+ * If there is no space left in the current buffer, we have
+ * filled everything up, so return. If we filled the previous
+ * buffer we would already have moved to the next one.
+ */
+ if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
+ hvlog ("\n\rviocons: No overflow buffer available for memcpy().\n");
+ pi->bufferOverflow++;
+ pi->overflowMessage = 1;
+ break;
+ }
+
+ /*
+ * Turn on the "used" bit for this buffer. If it's already on,
+ * that's fine.
+ */
+ set_bit(pi->curbuf, &pi->used);
+
+ /*
+ * See if this buffer has been allocated. If not, allocate it.
+ */
+ if (pi->buffer[pi->curbuf] == NULL) {
+ pi->buffer[pi->curbuf] =
+ kmalloc(VIOCHAR_MAX_DATA, GFP_ATOMIC);
+ if (pi->buffer[pi->curbuf] == NULL) {
+ hvlog("\n\rviocons: kmalloc failed allocating spaces for buffer %d.",
+ pi->curbuf);
+ break;
+ }
+ }
+
+ /* Figure out how much we can copy into this buffer. */
+ if (bleft < (VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf]))
+ curlen = bleft;
+ else
+ curlen = VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf];
+
+ /* Copy the data into the buffer. */
+ memcpy(pi->buffer[pi->curbuf] + pi->bufferBytes[pi->curbuf],
+ curbuf, curlen);
+
+ pi->bufferBytes[pi->curbuf] += curlen;
+ curbuf += curlen;
+ bleft -= curlen;
+
+ /*
+ * Now see if we've filled this buffer. If not then
+ * we'll try to use it again later. If we've filled it
+ * up then we'll advance the curbuf to the next in the
+ * circular queue.
+ */
+ if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
+ nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;
+ /*
+ * Move to the next buffer if it hasn't been used yet
+ */
+ if (test_bit(nextbuf, &pi->used) == 0)
+ pi->curbuf = nextbuf;
+ }
+ }
+ return len - bleft;
+}
+
+/*
+ * Send pending data
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive.
+ * hvlog can be used to log to the hypervisor buffer
+ */
+static void send_buffers(struct port_info *pi)
+{
+ HvLpEvent_Rc hvrc;
+ int nextbuf;
+ struct viocharlpevent *viochar;
+ unsigned long flags;
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ viochar = (struct viocharlpevent *)
+ vio_get_event_buffer(viomajorsubtype_chario);
+
+ /* Make sure we got a buffer */
+ if (viochar == NULL) {
+ hvlog("\n\rviocons: Can't get viochar buffer in sendBuffers().");
+ spin_unlock_irqrestore(&consolelock, flags);
+ return;
+ }
+
+ if (pi->used == 0) {
+ hvlog("\n\rviocons: in sendbuffers(), but no buffers used.\n");
+ vio_free_event_buffer(viomajorsubtype_chario, viochar);
+ spin_unlock_irqrestore(&consolelock, flags);
+ return;
+ }
+
+ /*
+ * curbuf points to the buffer we're filling. We want to
+ * start sending AFTER this one.
+ */
+ nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;
+
+ /*
+ * Loop until we find a buffer with the used bit on
+ */
+ while (test_bit(nextbuf, &pi->used) == 0)
+ nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;
+
+ initDataEvent(viochar, pi->lp);
+
+ /*
+ * While we have buffers with data, and our send window
+ * is open, send them
+ */
+ while ((test_bit(nextbuf, &pi->used)) &&
+ ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
+ viochar->len = pi->bufferBytes[nextbuf];
+ viochar->event.xCorrelationToken = pi->seq++;
+ viochar->event.xSizeMinus1 =
+ offsetof(struct viocharlpevent, data) + viochar->len;
+
+ memcpy(viochar->data, pi->buffer[nextbuf], viochar->len);
+
+ hvrc = HvCallEvent_signalLpEvent(&viochar->event);
+ if (hvrc) {
+ /*
+ * MUST unlock the spinlock before doing a printk
+ */
+ vio_free_event_buffer(viomajorsubtype_chario, viochar);
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ printk(VIOCONS_KERN_WARN
+ "error sending event! return code %d\n",
+ (int)hvrc);
+ return;
+ }
+
+ /*
+ * clear the used bit, zero the number of bytes in
+ * this buffer, and move to the next buffer
+ */
+ clear_bit(nextbuf, &pi->used);
+ pi->bufferBytes[nextbuf] = 0;
+ nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;
+ }
+
+ /*
+ * If we have emptied all the buffers, start at 0 again.
+ * this will re-use any allocated buffers
+ */
+ if (pi->used == 0) {
+ pi->curbuf = 0;
+
+ if (pi->overflowMessage)
+ pi->overflowMessage = 0;
+
+ if (pi->tty) {
+ tty_wakeup(pi->tty);
+ }
+ }
+
+ vio_free_event_buffer(viomajorsubtype_chario, viochar);
+ spin_unlock_irqrestore(&consolelock, flags);
+}
+
+/*
+ * Our internal writer. Gets called both from the console device and
+ * the tty device. the tty pointer will be NULL if called from the console.
+ * Return total number of bytes "written".
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive. hvlog
+ * can be used to log to the hypervisor buffer
+ */
+static int internal_write(struct port_info *pi, const char *buf, size_t len)
+{
+ HvLpEvent_Rc hvrc;
+ size_t bleft;
+ size_t curlen;
+ const char *curbuf;
+ unsigned long flags;
+ struct viocharlpevent *viochar;
+
+ /*
+ * Write to the hvlog of inbound data are now done prior to
+ * calling internal_write() since internal_write() is only called in
+ * the event that an lp event path is active, which isn't the case for
+ * logging attempts prior to console initialization.
+ *
+ * If there is already data queued for this port, send it prior to
+ * attempting to send any new data.
+ */
+ if (pi->used)
+ send_buffers(pi);
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ viochar = vio_get_event_buffer(viomajorsubtype_chario);
+ if (viochar == NULL) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ hvlog("\n\rviocons: Can't get vio buffer in internal_write().");
+ return -EAGAIN;
+ }
+ initDataEvent(viochar, pi->lp);
+
+ curbuf = buf;
+ bleft = len;
+
+ while ((bleft > 0) && (pi->used == 0) &&
+ ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
+ if (bleft > VIOCHAR_MAX_DATA)
+ curlen = VIOCHAR_MAX_DATA;
+ else
+ curlen = bleft;
+
+ viochar->event.xCorrelationToken = pi->seq++;
+ memcpy(viochar->data, curbuf, curlen);
+ viochar->len = curlen;
+ viochar->event.xSizeMinus1 =
+ offsetof(struct viocharlpevent, data) + curlen;
+
+ hvrc = HvCallEvent_signalLpEvent(&viochar->event);
+ if (hvrc) {
+ hvlog("viocons: error sending event! %d\n", (int)hvrc);
+ goto out;
+ }
+ curbuf += curlen;
+ bleft -= curlen;
+ }
+
+ /* If we didn't send it all, buffer as much of it as we can. */
+ if (bleft > 0)
+ bleft -= buffer_add(pi, curbuf, bleft);
+out:
+ vio_free_event_buffer(viomajorsubtype_chario, viochar);
+ spin_unlock_irqrestore(&consolelock, flags);
+ return len - bleft;
+}
+
+static struct port_info *get_port_data(struct tty_struct *tty)
+{
+ unsigned long flags;
+ struct port_info *pi;
+
+ spin_lock_irqsave(&consolelock, flags);
+ if (tty) {
+ pi = (struct port_info *)tty->driver_data;
+ if (!pi || viotty_paranoia_check(pi, tty->name,
+ "get_port_data")) {
+ pi = NULL;
+ }
+ } else
+ /*
+ * If this is the console device, use the lp from
+ * the first port entry
+ */
+ pi = &port_info[0];
+ spin_unlock_irqrestore(&consolelock, flags);
+ return pi;
+}
+
+/*
+ * Initialize the common fields in a charLpEvent
+ */
+static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp)
+{
+ memset(viochar, 0, sizeof(struct viocharlpevent));
+
+ viochar->event.xFlags.xValid = 1;
+ viochar->event.xFlags.xFunction = HvLpEvent_Function_Int;
+ viochar->event.xFlags.xAckInd = HvLpEvent_AckInd_NoAck;
+ viochar->event.xFlags.xAckType = HvLpEvent_AckType_DeferredAck;
+ viochar->event.xType = HvLpEvent_Type_VirtualIo;
+ viochar->event.xSubtype = viomajorsubtype_chario | viochardata;
+ viochar->event.xSourceLp = HvLpConfig_getLpIndex();
+ viochar->event.xTargetLp = lp;
+ viochar->event.xSizeMinus1 = sizeof(struct viocharlpevent);
+ viochar->event.xSourceInstanceId = viopath_sourceinst(lp);
+ viochar->event.xTargetInstanceId = viopath_targetinst(lp);
+}
+
+/*
+ * early console device write
+ */
+static void viocons_write_early(struct console *co, const char *s, unsigned count)
+{
+ hvlogOutput(s, count);
+}
+
+/*
+ * console device write
+ */
+static void viocons_write(struct console *co, const char *s, unsigned count)
+{
+ int index;
+ int begin;
+ struct port_info *pi;
+
+ static const char cr = '\r';
+
+ /*
+ * Check port data first because the target LP might be valid but
+ * simply not active, in which case we want to hvlog the output.
+ */
+ pi = get_port_data(NULL);
+ if (pi == NULL) {
+ hvlog("\n\rviocons_write: unable to get port data.");
+ return;
+ }
+
+ hvlogOutput(s, count);
+
+ if (!viopath_isactive(pi->lp))
+ return;
+
+ /*
+ * Any newline character found will cause a
+ * carriage return character to be emitted as well.
+ */
+ begin = 0;
+ for (index = 0; index < count; index++) {
+ if (s[index] == '\n') {
+ /*
+ * Newline found. Print everything up to and
+ * including the newline
+ */
+ internal_write(pi, &s[begin], index - begin + 1);
+ begin = index + 1;
+ /* Emit a carriage return as well */
+ internal_write(pi, &cr, 1);
+ }
+ }
+
+ /* If any characters left to write, write them now */
+ if ((index - begin) > 0)
+ internal_write(pi, &s[begin], index - begin);
+}
+
+/*
+ * Work out the device associate with this console
+ */
+static struct tty_driver *viocons_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return viotty_driver;
+}
+
+/*
+ * console device I/O methods
+ */
+static struct console viocons_early = {
+ .name = "viocons",
+ .write = viocons_write_early,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+static struct console viocons = {
+ .name = "viocons",
+ .write = viocons_write,
+ .device = viocons_device,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+/*
+ * TTY Open method
+ */
+static int viotty_open(struct tty_struct *tty, struct file *filp)
+{
+ int port;
+ unsigned long flags;
+ struct port_info *pi;
+
+ port = tty->index;
+
+ if ((port < 0) || (port >= VTTY_PORTS))
+ return -ENODEV;
+
+ spin_lock_irqsave(&consolelock, flags);
+
+ pi = &port_info[port];
+ /* If some other TTY is already connected here, reject the open */
+ if ((pi->tty) && (pi->tty != tty)) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ printk(VIOCONS_KERN_WARN
+ "attempt to open device twice from different ttys\n");
+ return -EBUSY;
+ }
+ tty->driver_data = pi;
+ pi->tty = tty;
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ return 0;
+}
+
+/*
+ * TTY Close method
+ */
+static void viotty_close(struct tty_struct *tty, struct file *filp)
+{
+ unsigned long flags;
+ struct port_info *pi;
+
+ spin_lock_irqsave(&consolelock, flags);
+ pi = (struct port_info *)tty->driver_data;
+
+ if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_close")) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ return;
+ }
+ if (tty->count == 1)
+ pi->tty = NULL;
+ spin_unlock_irqrestore(&consolelock, flags);
+}
+
+/*
+ * TTY Write method
+ */
+static int viotty_write(struct tty_struct *tty, const unsigned char *buf,
+ int count)
+{
+ struct port_info *pi;
+
+ pi = get_port_data(tty);
+ if (pi == NULL) {
+ hvlog("\n\rviotty_write: no port data.");
+ return -ENODEV;
+ }
+
+ if (viochar_is_console(pi))
+ hvlogOutput(buf, count);
+
+ /*
+ * If the path to this LP is closed, don't bother doing anything more.
+ * just dump the data on the floor and return count. For some reason
+ * some user level programs will attempt to probe available tty's and
+ * they'll attempt a viotty_write on an invalid port which maps to an
+ * invalid target lp. If this is the case then ignore the
+ * viotty_write call and, since the viopath isn't active to this
+ * partition, return count.
+ */
+ if (!viopath_isactive(pi->lp))
+ return count;
+
+ return internal_write(pi, buf, count);
+}
+
+/*
+ * TTY put_char method
+ */
+static void viotty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct port_info *pi;
+
+ pi = get_port_data(tty);
+ if (pi == NULL)
+ return;
+
+ /* This will append '\r' as well if the char is '\n' */
+ if (viochar_is_console(pi))
+ hvlogOutput(&ch, 1);
+
+ if (viopath_isactive(pi->lp))
+ internal_write(pi, &ch, 1);
+}
+
+/*
+ * TTY write_room method
+ */
+static int viotty_write_room(struct tty_struct *tty)
+{
+ int i;
+ int room = 0;
+ struct port_info *pi;
+ unsigned long flags;
+
+ spin_lock_irqsave(&consolelock, flags);
+ pi = (struct port_info *)tty->driver_data;
+ if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_write_room")) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ return 0;
+ }
+
+ /* If no buffers are used, return the max size. */
+ if (pi->used == 0) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ return VIOCHAR_MAX_DATA * VIOCHAR_NUM_BUF;
+ }
+
+ /*
+ * We retain the spinlock because we want to get an accurate
+ * count and it can change on us between each operation if we
+ * don't hold the spinlock.
+ */
+ for (i = 0; ((i < VIOCHAR_NUM_BUF) && (room < VIOCHAR_MAX_DATA)); i++)
+ room += (VIOCHAR_MAX_DATA - pi->bufferBytes[i]);
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ if (room > VIOCHAR_MAX_DATA)
+ room = VIOCHAR_MAX_DATA;
+ return room;
+}
+
+/*
+ * TTY chars_in_buffer method
+ */
+static int viotty_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0;
+}
+
+static int viotty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ /*
+ * the ioctls below read/set the flags usually shown in the leds
+ * don't use them - they will go away without warning
+ */
+ case KDGETLED:
+ case KDGKBLED:
+ return put_user(0, (char *)arg);
+
+ case KDSKBLED:
+ return 0;
+ }
+
+ return n_tty_ioctl(tty, file, cmd, arg);
+}
+
+/*
+ * Handle an open charLpEvent. Could be either interrupt or ack
+ */
+static void vioHandleOpenEvent(struct HvLpEvent *event)
+{
+ unsigned long flags;
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ u8 port = cevent->virtual_device;
+ struct port_info *pi;
+ int reject = 0;
+
+ if (event->xFlags.xFunction == HvLpEvent_Function_Ack) {
+ if (port >= VTTY_PORTS)
+ return;
+
+ spin_lock_irqsave(&consolelock, flags);
+ /* Got the lock, don't cause console output */
+
+ pi = &port_info[port];
+ if (event->xRc == HvLpEvent_Rc_Good) {
+ pi->seq = pi->ack = 0;
+ /*
+ * This line allows connections from the primary
+ * partition but once one is connected from the
+ * primary partition nothing short of a reboot
+ * of linux will allow access from the hosting
+ * partition again without a required iSeries fix.
+ */
+ pi->lp = event->xTargetLp;
+ }
+
+ spin_unlock_irqrestore(&consolelock, flags);
+ if (event->xRc != HvLpEvent_Rc_Good)
+ printk(VIOCONS_KERN_WARN
+ "handle_open_event: event->xRc == (%d).\n",
+ event->xRc);
+
+ if (event->xCorrelationToken != 0) {
+ atomic_t *aptr= (atomic_t *)event->xCorrelationToken;
+ atomic_set(aptr, 1);
+ } else
+ printk(VIOCONS_KERN_WARN
+ "weird...got open ack without atomic\n");
+ return;
+ }
+
+ /* This had better require an ack, otherwise complain */
+ if (event->xFlags.xAckInd != HvLpEvent_AckInd_DoAck) {
+ printk(VIOCONS_KERN_WARN "viocharopen without ack bit!\n");
+ return;
+ }
+
+ spin_lock_irqsave(&consolelock, flags);
+ /* Got the lock, don't cause console output */
+
+ /* Make sure this is a good virtual tty */
+ if (port >= VTTY_PORTS) {
+ event->xRc = HvLpEvent_Rc_SubtypeError;
+ cevent->subtype_result_code = viorc_openRejected;
+ /*
+ * Flag state here since we can't printk while holding
+ * a spinlock.
+ */
+ reject = 1;
+ } else {
+ pi = &port_info[port];
+ if ((pi->lp != HvLpIndexInvalid) &&
+ (pi->lp != event->xSourceLp)) {
+ /*
+ * If this is tty is already connected to a different
+ * partition, fail.
+ */
+ event->xRc = HvLpEvent_Rc_SubtypeError;
+ cevent->subtype_result_code = viorc_openRejected;
+ reject = 2;
+ } else {
+ pi->lp = event->xSourceLp;
+ event->xRc = HvLpEvent_Rc_Good;
+ cevent->subtype_result_code = viorc_good;
+ pi->seq = pi->ack = 0;
+ reject = 0;
+ }
+ }
+
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ if (reject == 1)
+ printk(VIOCONS_KERN_WARN "open rejected: bad virtual tty.\n");
+ else if (reject == 2)
+ printk(VIOCONS_KERN_WARN
+ "open rejected: console in exclusive use by another partition.\n");
+
+ /* Return the acknowledgement */
+ HvCallEvent_ackLpEvent(event);
+}
+
+/*
+ * Handle a close charLpEvent. This should ONLY be an Interrupt because the
+ * virtual console should never actually issue a close event to the hypervisor
+ * because the virtual console never goes away. A close event coming from the
+ * hypervisor simply means that there are no client consoles connected to the
+ * virtual console.
+ *
+ * Regardless of the number of connections masqueraded on the other side of
+ * the hypervisor ONLY ONE close event should be called to accompany the ONE
+ * open event that is called. The close event should ONLY be called when NO
+ * MORE connections (masqueraded or not) exist on the other side of the
+ * hypervisor.
+ */
+static void vioHandleCloseEvent(struct HvLpEvent *event)
+{
+ unsigned long flags;
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ u8 port = cevent->virtual_device;
+
+ if (event->xFlags.xFunction == HvLpEvent_Function_Int) {
+ if (port >= VTTY_PORTS) {
+ printk(VIOCONS_KERN_WARN
+ "close message from invalid virtual device.\n");
+ return;
+ }
+
+ /* For closes, just mark the console partition invalid */
+ spin_lock_irqsave(&consolelock, flags);
+ /* Got the lock, don't cause console output */
+
+ if (port_info[port].lp == event->xSourceLp)
+ port_info[port].lp = HvLpIndexInvalid;
+
+ spin_unlock_irqrestore(&consolelock, flags);
+ printk(VIOCONS_KERN_INFO "close from %d\n", event->xSourceLp);
+ } else
+ printk(VIOCONS_KERN_WARN
+ "got unexpected close acknowlegement\n");
+}
+
+/*
+ * Handle a config charLpEvent. Could be either interrupt or ack
+ */
+static void vioHandleConfig(struct HvLpEvent *event)
+{
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+
+ HvCall_writeLogBuffer(cevent->data, cevent->len);
+
+ if (cevent->data[0] == 0x01)
+ printk(VIOCONS_KERN_INFO "window resized to %d: %d: %d: %d\n",
+ cevent->data[1], cevent->data[2],
+ cevent->data[3], cevent->data[4]);
+ else
+ printk(VIOCONS_KERN_WARN "unknown config event\n");
+}
+
+/*
+ * Handle a data charLpEvent.
+ */
+static void vioHandleData(struct HvLpEvent *event)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ struct port_info *pi;
+ int index;
+ u8 port = cevent->virtual_device;
+
+ if (port >= VTTY_PORTS) {
+ printk(VIOCONS_KERN_WARN "data on invalid virtual device %d\n",
+ port);
+ return;
+ }
+
+ /*
+ * Hold the spinlock so that we don't take an interrupt that
+ * changes tty between the time we fetch the port_info
+ * pointer and the time we paranoia check.
+ */
+ spin_lock_irqsave(&consolelock, flags);
+ pi = &port_info[port];
+
+ /*
+ * Change 05/01/2003 - Ryan Arnold: If a partition other than
+ * the current exclusive partition tries to send us data
+ * events then just drop them on the floor because we don't
+ * want his stinking data. He isn't authorized to receive
+ * data because he wasn't the first one to get the console,
+ * therefore he shouldn't be allowed to send data either.
+ * This will work without an iSeries fix.
+ */
+ if (pi->lp != event->xSourceLp) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ return;
+ }
+
+ tty = pi->tty;
+ if (tty == NULL) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ printk(VIOCONS_KERN_WARN "no tty for virtual device %d\n",
+ port);
+ return;
+ }
+
+ if (tty->magic != TTY_MAGIC) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ printk(VIOCONS_KERN_WARN "tty bad magic\n");
+ return;
+ }
+
+ /*
+ * Just to be paranoid, make sure the tty points back to this port
+ */
+ pi = (struct port_info *)tty->driver_data;
+ if (!pi || viotty_paranoia_check(pi, tty->name, "vioHandleData")) {
+ spin_unlock_irqrestore(&consolelock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ /*
+ * Change 07/21/2003 - Ryan Arnold: functionality added to
+ * support sysrq utilizing ^O as the sysrq key. The sysrq
+ * functionality will only work if built into the kernel and
+ * then only if sysrq is enabled through the proc filesystem.
+ */
+ for (index = 0; index < cevent->len; index++) {
+#ifdef CONFIG_MAGIC_SYSRQ
+ if (sysrq_enabled) {
+ /* 0x0f is the ascii character for ^O */
+ if (cevent->data[index] == '\x0f') {
+ vio_sysrq_pressed = 1;
+ /*
+ * continue because we don't want to add
+ * the sysrq key into the data string.
+ */
+ continue;
+ } else if (vio_sysrq_pressed) {
+ handle_sysrq(cevent->data[index], NULL, tty);
+ vio_sysrq_pressed = 0;
+ /*
+ * continue because we don't want to add
+ * the sysrq sequence into the data string.
+ */
+ continue;
+ }
+ }
+#endif
+ /*
+ * The sysrq sequence isn't included in this check if
+ * sysrq is enabled and compiled into the kernel because
+ * the sequence will never get inserted into the buffer.
+ * Don't attempt to copy more data into the buffer than we
+ * have room for because it would fail without indication.
+ */
+ if ((tty->flip.count + 1) > TTY_FLIPBUF_SIZE) {
+ printk(VIOCONS_KERN_WARN "input buffer overflow!\n");
+ break;
+ }
+ tty_insert_flip_char(tty, cevent->data[index], TTY_NORMAL);
+ }
+
+ /* if cevent->len == 0 then no data was added to the buffer and flip.count == 0 */
+ if (tty->flip.count)
+ /* The next call resets flip.count when the data is flushed. */
+ tty_flip_buffer_push(tty);
+}
+
+/*
+ * Handle an ack charLpEvent.
+ */
+static void vioHandleAck(struct HvLpEvent *event)
+{
+ struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+ unsigned long flags;
+ u8 port = cevent->virtual_device;
+
+ if (port >= VTTY_PORTS) {
+ printk(VIOCONS_KERN_WARN "data on invalid virtual device\n");
+ return;
+ }
+
+ spin_lock_irqsave(&consolelock, flags);
+ port_info[port].ack = event->xCorrelationToken;
+ spin_unlock_irqrestore(&consolelock, flags);
+
+ if (port_info[port].used)
+ send_buffers(&port_info[port]);
+}
+
+/*
+ * Handle charLpEvents and route to the appropriate routine
+ */
+static void vioHandleCharEvent(struct HvLpEvent *event)
+{
+ int charminor;
+
+ if (event == NULL)
+ return;
+
+ charminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
+ switch (charminor) {
+ case viocharopen:
+ vioHandleOpenEvent(event);
+ break;
+ case viocharclose:
+ vioHandleCloseEvent(event);
+ break;
+ case viochardata:
+ vioHandleData(event);
+ break;
+ case viocharack:
+ vioHandleAck(event);
+ break;
+ case viocharconfig:
+ vioHandleConfig(event);
+ break;
+ default:
+ if ((event->xFlags.xFunction == HvLpEvent_Function_Int) &&
+ (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck)) {
+ event->xRc = HvLpEvent_Rc_InvalidSubtype;
+ HvCallEvent_ackLpEvent(event);
+ }
+ }
+}
+
+/*
+ * Send an open event
+ */
+static int send_open(HvLpIndex remoteLp, void *sem)
+{
+ return HvCallEvent_signalLpEventFast(remoteLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_chario | viocharopen,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(remoteLp),
+ viopath_targetinst(remoteLp),
+ (u64)(unsigned long)sem, VIOVERSION << 16,
+ 0, 0, 0, 0);
+}
+
+static struct tty_operations serial_ops = {
+ .open = viotty_open,
+ .close = viotty_close,
+ .write = viotty_write,
+ .put_char = viotty_put_char,
+ .write_room = viotty_write_room,
+ .chars_in_buffer = viotty_chars_in_buffer,
+ .ioctl = viotty_ioctl,
+};
+
+static int __init viocons_init2(void)
+{
+ atomic_t wait_flag;
+ int rc;
+
+ /* +2 for fudge */
+ rc = viopath_open(HvLpConfig_getPrimaryLpIndex(),
+ viomajorsubtype_chario, VIOCHAR_WINDOW + 2);
+ if (rc)
+ printk(VIOCONS_KERN_WARN "error opening to primary %d\n", rc);
+
+ if (viopath_hostLp == HvLpIndexInvalid)
+ vio_set_hostlp();
+
+ /*
+ * And if the primary is not the same as the hosting LP, open to the
+ * hosting lp
+ */
+ if ((viopath_hostLp != HvLpIndexInvalid) &&
+ (viopath_hostLp != HvLpConfig_getPrimaryLpIndex())) {
+ printk(VIOCONS_KERN_INFO "open path to hosting (%d)\n",
+ viopath_hostLp);
+ rc = viopath_open(viopath_hostLp, viomajorsubtype_chario,
+ VIOCHAR_WINDOW + 2); /* +2 for fudge */
+ if (rc)
+ printk(VIOCONS_KERN_WARN
+ "error opening to partition %d: %d\n",
+ viopath_hostLp, rc);
+ }
+
+ if (vio_setHandler(viomajorsubtype_chario, vioHandleCharEvent) < 0)
+ printk(VIOCONS_KERN_WARN
+ "error seting handler for console events!\n");
+
+ /*
+ * First, try to open the console to the hosting lp.
+ * Wait on a semaphore for the response.
+ */
+ atomic_set(&wait_flag, 0);
+ if ((viopath_isactive(viopath_hostLp)) &&
+ (send_open(viopath_hostLp, (void *)&wait_flag) == 0)) {
+ printk(VIOCONS_KERN_INFO "hosting partition %d\n",
+ viopath_hostLp);
+ while (atomic_read(&wait_flag) == 0)
+ mb();
+ atomic_set(&wait_flag, 0);
+ }
+
+ /*
+ * If we don't have an active console, try the primary
+ */
+ if ((!viopath_isactive(port_info[0].lp)) &&
+ (viopath_isactive(HvLpConfig_getPrimaryLpIndex())) &&
+ (send_open(HvLpConfig_getPrimaryLpIndex(), (void *)&wait_flag)
+ == 0)) {
+ printk(VIOCONS_KERN_INFO "opening console to primary partition\n");
+ while (atomic_read(&wait_flag) == 0)
+ mb();
+ }
+
+ /* Initialize the tty_driver structure */
+ viotty_driver = alloc_tty_driver(VTTY_PORTS);
+ viotty_driver->owner = THIS_MODULE;
+ viotty_driver->driver_name = "vioconsole";
+ viotty_driver->devfs_name = "vcs/";
+ viotty_driver->name = "tty";
+ viotty_driver->name_base = 1;
+ viotty_driver->major = TTY_MAJOR;
+ viotty_driver->minor_start = 1;
+ viotty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ viotty_driver->subtype = 1;
+ viotty_driver->init_termios = tty_std_termios;
+ viotty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
+ tty_set_operations(viotty_driver, &serial_ops);
+
+ if (tty_register_driver(viotty_driver)) {
+ printk(VIOCONS_KERN_WARN "couldn't register console driver\n");
+ put_tty_driver(viotty_driver);
+ viotty_driver = NULL;
+ }
+
+ unregister_console(&viocons_early);
+ register_console(&viocons);
+
+ return 0;
+}
+
+static int __init viocons_init(void)
+{
+ int i;
+
+ printk(VIOCONS_KERN_INFO "registering console\n");
+ for (i = 0; i < VTTY_PORTS; i++) {
+ port_info[i].lp = HvLpIndexInvalid;
+ port_info[i].magic = VIOTTY_MAGIC;
+ }
+ HvCall_setLogBufferFormatAndCodepage(HvCall_LogBuffer_ASCII, 437);
+ register_console(&viocons_early);
+ return 0;
+}
+
+console_initcall(viocons_init);
+module_init(viocons_init2);
diff --git a/drivers/char/viotape.c b/drivers/char/viotape.c
new file mode 100644
index 000000000000..aea3cbf5219d
--- /dev/null
+++ b/drivers/char/viotape.c
@@ -0,0 +1,1129 @@
+/* -*- linux-c -*-
+ * drivers/char/viotape.c
+ *
+ * iSeries Virtual Tape
+ *
+ * Authors: Dave Boutcher <boutcher@us.ibm.com>
+ * Ryan Arnold <ryanarn@us.ibm.com>
+ * Colin Devilbiss <devilbis@us.ibm.com>
+ * Stephen Rothwell <sfr@au1.ibm.com>
+ *
+ * (C) Copyright 2000-2004 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) anyu later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This routine provides access to tape drives owned and managed by an OS/400
+ * partition running on the same box as this Linux partition.
+ *
+ * All tape operations are performed by sending messages back and forth to
+ * the OS/400 partition. The format of the messages is defined in
+ * iSeries/vio.h
+ */
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/mtio.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/major.h>
+#include <linux/completion.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+#include <asm/vio.h>
+#include <asm/iSeries/vio.h>
+#include <asm/iSeries/HvLpEvent.h>
+#include <asm/iSeries/HvCallEvent.h>
+#include <asm/iSeries/HvLpConfig.h>
+
+#define VIOTAPE_VERSION "1.2"
+#define VIOTAPE_MAXREQ 1
+
+#define VIOTAPE_KERN_WARN KERN_WARNING "viotape: "
+#define VIOTAPE_KERN_INFO KERN_INFO "viotape: "
+
+static int viotape_numdev;
+
+/*
+ * The minor number follows the conventions of the SCSI tape drives. The
+ * rewind and mode are encoded in the minor #. We use this struct to break
+ * them out
+ */
+struct viot_devinfo_struct {
+ int devno;
+ int mode;
+ int rewind;
+};
+
+#define VIOTAPOP_RESET 0
+#define VIOTAPOP_FSF 1
+#define VIOTAPOP_BSF 2
+#define VIOTAPOP_FSR 3
+#define VIOTAPOP_BSR 4
+#define VIOTAPOP_WEOF 5
+#define VIOTAPOP_REW 6
+#define VIOTAPOP_NOP 7
+#define VIOTAPOP_EOM 8
+#define VIOTAPOP_ERASE 9
+#define VIOTAPOP_SETBLK 10
+#define VIOTAPOP_SETDENSITY 11
+#define VIOTAPOP_SETPOS 12
+#define VIOTAPOP_GETPOS 13
+#define VIOTAPOP_SETPART 14
+#define VIOTAPOP_UNLOAD 15
+
+struct viotapelpevent {
+ struct HvLpEvent event;
+ u32 reserved;
+ u16 version;
+ u16 sub_type_result;
+ u16 tape;
+ u16 flags;
+ u32 token;
+ u64 len;
+ union {
+ struct {
+ u32 tape_op;
+ u32 count;
+ } op;
+ struct {
+ u32 type;
+ u32 resid;
+ u32 dsreg;
+ u32 gstat;
+ u32 erreg;
+ u32 file_no;
+ u32 block_no;
+ } get_status;
+ struct {
+ u32 block_no;
+ } get_pos;
+ } u;
+};
+
+enum viotapesubtype {
+ viotapeopen = 0x0001,
+ viotapeclose = 0x0002,
+ viotaperead = 0x0003,
+ viotapewrite = 0x0004,
+ viotapegetinfo = 0x0005,
+ viotapeop = 0x0006,
+ viotapegetpos = 0x0007,
+ viotapesetpos = 0x0008,
+ viotapegetstatus = 0x0009
+};
+
+enum viotaperc {
+ viotape_InvalidRange = 0x0601,
+ viotape_InvalidToken = 0x0602,
+ viotape_DMAError = 0x0603,
+ viotape_UseError = 0x0604,
+ viotape_ReleaseError = 0x0605,
+ viotape_InvalidTape = 0x0606,
+ viotape_InvalidOp = 0x0607,
+ viotape_TapeErr = 0x0608,
+
+ viotape_AllocTimedOut = 0x0640,
+ viotape_BOTEnc = 0x0641,
+ viotape_BlankTape = 0x0642,
+ viotape_BufferEmpty = 0x0643,
+ viotape_CleanCartFound = 0x0644,
+ viotape_CmdNotAllowed = 0x0645,
+ viotape_CmdNotSupported = 0x0646,
+ viotape_DataCheck = 0x0647,
+ viotape_DecompressErr = 0x0648,
+ viotape_DeviceTimeout = 0x0649,
+ viotape_DeviceUnavail = 0x064a,
+ viotape_DeviceBusy = 0x064b,
+ viotape_EndOfMedia = 0x064c,
+ viotape_EndOfTape = 0x064d,
+ viotape_EquipCheck = 0x064e,
+ viotape_InsufficientRs = 0x064f,
+ viotape_InvalidLogBlk = 0x0650,
+ viotape_LengthError = 0x0651,
+ viotape_LibDoorOpen = 0x0652,
+ viotape_LoadFailure = 0x0653,
+ viotape_NotCapable = 0x0654,
+ viotape_NotOperational = 0x0655,
+ viotape_NotReady = 0x0656,
+ viotape_OpCancelled = 0x0657,
+ viotape_PhyLinkErr = 0x0658,
+ viotape_RdyNotBOT = 0x0659,
+ viotape_TapeMark = 0x065a,
+ viotape_WriteProt = 0x065b
+};
+
+static const struct vio_error_entry viotape_err_table[] = {
+ { viotape_InvalidRange, EIO, "Internal error" },
+ { viotape_InvalidToken, EIO, "Internal error" },
+ { viotape_DMAError, EIO, "DMA error" },
+ { viotape_UseError, EIO, "Internal error" },
+ { viotape_ReleaseError, EIO, "Internal error" },
+ { viotape_InvalidTape, EIO, "Invalid tape device" },
+ { viotape_InvalidOp, EIO, "Invalid operation" },
+ { viotape_TapeErr, EIO, "Tape error" },
+ { viotape_AllocTimedOut, EBUSY, "Allocate timed out" },
+ { viotape_BOTEnc, EIO, "Beginning of tape encountered" },
+ { viotape_BlankTape, EIO, "Blank tape" },
+ { viotape_BufferEmpty, EIO, "Buffer empty" },
+ { viotape_CleanCartFound, ENOMEDIUM, "Cleaning cartridge found" },
+ { viotape_CmdNotAllowed, EIO, "Command not allowed" },
+ { viotape_CmdNotSupported, EIO, "Command not supported" },
+ { viotape_DataCheck, EIO, "Data check" },
+ { viotape_DecompressErr, EIO, "Decompression error" },
+ { viotape_DeviceTimeout, EBUSY, "Device timeout" },
+ { viotape_DeviceUnavail, EIO, "Device unavailable" },
+ { viotape_DeviceBusy, EBUSY, "Device busy" },
+ { viotape_EndOfMedia, ENOSPC, "End of media" },
+ { viotape_EndOfTape, ENOSPC, "End of tape" },
+ { viotape_EquipCheck, EIO, "Equipment check" },
+ { viotape_InsufficientRs, EOVERFLOW, "Insufficient tape resources" },
+ { viotape_InvalidLogBlk, EIO, "Invalid logical block location" },
+ { viotape_LengthError, EOVERFLOW, "Length error" },
+ { viotape_LibDoorOpen, EBUSY, "Door open" },
+ { viotape_LoadFailure, ENOMEDIUM, "Load failure" },
+ { viotape_NotCapable, EIO, "Not capable" },
+ { viotape_NotOperational, EIO, "Not operational" },
+ { viotape_NotReady, EIO, "Not ready" },
+ { viotape_OpCancelled, EIO, "Operation cancelled" },
+ { viotape_PhyLinkErr, EIO, "Physical link error" },
+ { viotape_RdyNotBOT, EIO, "Ready but not beginning of tape" },
+ { viotape_TapeMark, EIO, "Tape mark" },
+ { viotape_WriteProt, EROFS, "Write protection error" },
+ { 0, 0, NULL },
+};
+
+/* Maximum number of tapes we support */
+#define VIOTAPE_MAX_TAPE HVMAXARCHITECTEDVIRTUALTAPES
+#define MAX_PARTITIONS 4
+
+/* defines for current tape state */
+#define VIOT_IDLE 0
+#define VIOT_READING 1
+#define VIOT_WRITING 2
+
+/* Our info on the tapes */
+struct tape_descr {
+ char rsrcname[10];
+ char type[4];
+ char model[3];
+};
+
+static struct tape_descr *viotape_unitinfo;
+static dma_addr_t viotape_unitinfo_token;
+
+static struct mtget viomtget[VIOTAPE_MAX_TAPE];
+
+static struct class_simple *tape_class;
+
+static struct device *tape_device[VIOTAPE_MAX_TAPE];
+
+/*
+ * maintain the current state of each tape (and partition)
+ * so that we know when to write EOF marks.
+ */
+static struct {
+ unsigned char cur_part;
+ int dev_handle;
+ unsigned char part_stat_rwi[MAX_PARTITIONS];
+} state[VIOTAPE_MAX_TAPE];
+
+/* We single-thread */
+static struct semaphore reqSem;
+
+/*
+ * When we send a request, we use this struct to get the response back
+ * from the interrupt handler
+ */
+struct op_struct {
+ void *buffer;
+ dma_addr_t dmaaddr;
+ size_t count;
+ int rc;
+ int non_blocking;
+ struct completion com;
+ struct device *dev;
+ struct op_struct *next;
+};
+
+static spinlock_t op_struct_list_lock;
+static struct op_struct *op_struct_list;
+
+/* forward declaration to resolve interdependence */
+static int chg_state(int index, unsigned char new_state, struct file *file);
+
+/* procfs support */
+static int proc_viotape_show(struct seq_file *m, void *v)
+{
+ int i;
+
+ seq_printf(m, "viotape driver version " VIOTAPE_VERSION "\n");
+ for (i = 0; i < viotape_numdev; i++) {
+ seq_printf(m, "viotape device %d is iSeries resource %10.10s"
+ "type %4.4s, model %3.3s\n",
+ i, viotape_unitinfo[i].rsrcname,
+ viotape_unitinfo[i].type,
+ viotape_unitinfo[i].model);
+ }
+ return 0;
+}
+
+static int proc_viotape_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, proc_viotape_show, NULL);
+}
+
+static struct file_operations proc_viotape_operations = {
+ .open = proc_viotape_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* Decode the device minor number into its parts */
+void get_dev_info(struct inode *ino, struct viot_devinfo_struct *devi)
+{
+ devi->devno = iminor(ino) & 0x1F;
+ devi->mode = (iminor(ino) & 0x60) >> 5;
+ /* if bit is set in the minor, do _not_ rewind automatically */
+ devi->rewind = (iminor(ino) & 0x80) == 0;
+}
+
+/* This is called only from the exit and init paths, so no need for locking */
+static void clear_op_struct_pool(void)
+{
+ while (op_struct_list) {
+ struct op_struct *toFree = op_struct_list;
+ op_struct_list = op_struct_list->next;
+ kfree(toFree);
+ }
+}
+
+/* Likewise, this is only called from the init path */
+static int add_op_structs(int structs)
+{
+ int i;
+
+ for (i = 0; i < structs; ++i) {
+ struct op_struct *new_struct =
+ kmalloc(sizeof(*new_struct), GFP_KERNEL);
+ if (!new_struct) {
+ clear_op_struct_pool();
+ return -ENOMEM;
+ }
+ new_struct->next = op_struct_list;
+ op_struct_list = new_struct;
+ }
+ return 0;
+}
+
+/* Allocate an op structure from our pool */
+static struct op_struct *get_op_struct(void)
+{
+ struct op_struct *retval;
+ unsigned long flags;
+
+ spin_lock_irqsave(&op_struct_list_lock, flags);
+ retval = op_struct_list;
+ if (retval)
+ op_struct_list = retval->next;
+ spin_unlock_irqrestore(&op_struct_list_lock, flags);
+ if (retval) {
+ memset(retval, 0, sizeof(*retval));
+ init_completion(&retval->com);
+ }
+
+ return retval;
+}
+
+/* Return an op structure to our pool */
+static void free_op_struct(struct op_struct *op_struct)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&op_struct_list_lock, flags);
+ op_struct->next = op_struct_list;
+ op_struct_list = op_struct;
+ spin_unlock_irqrestore(&op_struct_list_lock, flags);
+}
+
+/* Map our tape return codes to errno values */
+int tape_rc_to_errno(int tape_rc, char *operation, int tapeno)
+{
+ const struct vio_error_entry *err;
+
+ if (tape_rc == 0)
+ return 0;
+
+ err = vio_lookup_rc(viotape_err_table, tape_rc);
+ printk(VIOTAPE_KERN_WARN "error(%s) 0x%04x on Device %d (%-10s): %s\n",
+ operation, tape_rc, tapeno,
+ viotape_unitinfo[tapeno].rsrcname, err->msg);
+ return -err->errno;
+}
+
+/* Get info on all tapes from OS/400 */
+static int get_viotape_info(void)
+{
+ HvLpEvent_Rc hvrc;
+ int i;
+ size_t len = sizeof(*viotape_unitinfo) * VIOTAPE_MAX_TAPE;
+ struct op_struct *op = get_op_struct();
+
+ if (op == NULL)
+ return -ENOMEM;
+
+ viotape_unitinfo = dma_alloc_coherent(iSeries_vio_dev, len,
+ &viotape_unitinfo_token, GFP_ATOMIC);
+ if (viotape_unitinfo == NULL) {
+ free_op_struct(op);
+ return -ENOMEM;
+ }
+
+ memset(viotape_unitinfo, 0, len);
+
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapegetinfo,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64) (unsigned long) op, VIOVERSION << 16,
+ viotape_unitinfo_token, len, 0, 0);
+ if (hvrc != HvLpEvent_Rc_Good) {
+ printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+ (int)hvrc);
+ free_op_struct(op);
+ return -EIO;
+ }
+
+ wait_for_completion(&op->com);
+
+ free_op_struct(op);
+
+ for (i = 0;
+ ((i < VIOTAPE_MAX_TAPE) && (viotape_unitinfo[i].rsrcname[0]));
+ i++)
+ viotape_numdev++;
+ return 0;
+}
+
+
+/* Write */
+static ssize_t viotap_write(struct file *file, const char *buf,
+ size_t count, loff_t * ppos)
+{
+ HvLpEvent_Rc hvrc;
+ unsigned short flags = file->f_flags;
+ int noblock = ((flags & O_NONBLOCK) != 0);
+ ssize_t ret;
+ struct viot_devinfo_struct devi;
+ struct op_struct *op = get_op_struct();
+
+ if (op == NULL)
+ return -ENOMEM;
+
+ get_dev_info(file->f_dentry->d_inode, &devi);
+
+ /*
+ * We need to make sure we can send a request. We use
+ * a semaphore to keep track of # requests in use. If
+ * we are non-blocking, make sure we don't block on the
+ * semaphore
+ */
+ if (noblock) {
+ if (down_trylock(&reqSem)) {
+ ret = -EWOULDBLOCK;
+ goto free_op;
+ }
+ } else
+ down(&reqSem);
+
+ /* Allocate a DMA buffer */
+ op->dev = tape_device[devi.devno];
+ op->buffer = dma_alloc_coherent(op->dev, count, &op->dmaaddr,
+ GFP_ATOMIC);
+
+ if (op->buffer == NULL) {
+ printk(VIOTAPE_KERN_WARN
+ "error allocating dma buffer for len %ld\n",
+ count);
+ ret = -EFAULT;
+ goto up_sem;
+ }
+
+ /* Copy the data into the buffer */
+ if (copy_from_user(op->buffer, buf, count)) {
+ printk(VIOTAPE_KERN_WARN "tape: error on copy from user\n");
+ ret = -EFAULT;
+ goto free_dma;
+ }
+
+ op->non_blocking = noblock;
+ init_completion(&op->com);
+ op->count = count;
+
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapewrite,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48) | op->dmaaddr, count, 0, 0);
+ if (hvrc != HvLpEvent_Rc_Good) {
+ printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+ (int)hvrc);
+ ret = -EIO;
+ goto free_dma;
+ }
+
+ if (noblock)
+ return count;
+
+ wait_for_completion(&op->com);
+
+ if (op->rc)
+ ret = tape_rc_to_errno(op->rc, "write", devi.devno);
+ else {
+ chg_state(devi.devno, VIOT_WRITING, file);
+ ret = op->count;
+ }
+
+free_dma:
+ dma_free_coherent(op->dev, count, op->buffer, op->dmaaddr);
+up_sem:
+ up(&reqSem);
+free_op:
+ free_op_struct(op);
+ return ret;
+}
+
+/* read */
+static ssize_t viotap_read(struct file *file, char *buf, size_t count,
+ loff_t *ptr)
+{
+ HvLpEvent_Rc hvrc;
+ unsigned short flags = file->f_flags;
+ struct op_struct *op = get_op_struct();
+ int noblock = ((flags & O_NONBLOCK) != 0);
+ ssize_t ret;
+ struct viot_devinfo_struct devi;
+
+ if (op == NULL)
+ return -ENOMEM;
+
+ get_dev_info(file->f_dentry->d_inode, &devi);
+
+ /*
+ * We need to make sure we can send a request. We use
+ * a semaphore to keep track of # requests in use. If
+ * we are non-blocking, make sure we don't block on the
+ * semaphore
+ */
+ if (noblock) {
+ if (down_trylock(&reqSem)) {
+ ret = -EWOULDBLOCK;
+ goto free_op;
+ }
+ } else
+ down(&reqSem);
+
+ chg_state(devi.devno, VIOT_READING, file);
+
+ /* Allocate a DMA buffer */
+ op->dev = tape_device[devi.devno];
+ op->buffer = dma_alloc_coherent(op->dev, count, &op->dmaaddr,
+ GFP_ATOMIC);
+ if (op->buffer == NULL) {
+ ret = -EFAULT;
+ goto up_sem;
+ }
+
+ op->count = count;
+ init_completion(&op->com);
+
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotaperead,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48) | op->dmaaddr, count, 0, 0);
+ if (hvrc != HvLpEvent_Rc_Good) {
+ printk(VIOTAPE_KERN_WARN "tape hv error on op %d\n",
+ (int)hvrc);
+ ret = -EIO;
+ goto free_dma;
+ }
+
+ wait_for_completion(&op->com);
+
+ if (op->rc)
+ ret = tape_rc_to_errno(op->rc, "read", devi.devno);
+ else {
+ ret = op->count;
+ if (ret && copy_to_user(buf, op->buffer, ret)) {
+ printk(VIOTAPE_KERN_WARN "error on copy_to_user\n");
+ ret = -EFAULT;
+ }
+ }
+
+free_dma:
+ dma_free_coherent(op->dev, count, op->buffer, op->dmaaddr);
+up_sem:
+ up(&reqSem);
+free_op:
+ free_op_struct(op);
+ return ret;
+}
+
+/* ioctl */
+static int viotap_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ HvLpEvent_Rc hvrc;
+ int ret;
+ struct viot_devinfo_struct devi;
+ struct mtop mtc;
+ u32 myOp;
+ struct op_struct *op = get_op_struct();
+
+ if (op == NULL)
+ return -ENOMEM;
+
+ get_dev_info(file->f_dentry->d_inode, &devi);
+
+ down(&reqSem);
+
+ ret = -EINVAL;
+
+ switch (cmd) {
+ case MTIOCTOP:
+ ret = -EFAULT;
+ /*
+ * inode is null if and only if we (the kernel)
+ * made the request
+ */
+ if (inode == NULL)
+ memcpy(&mtc, (void *) arg, sizeof(struct mtop));
+ else if (copy_from_user((char *)&mtc, (char *)arg,
+ sizeof(struct mtop)))
+ goto free_op;
+
+ ret = -EIO;
+ switch (mtc.mt_op) {
+ case MTRESET:
+ myOp = VIOTAPOP_RESET;
+ break;
+ case MTFSF:
+ myOp = VIOTAPOP_FSF;
+ break;
+ case MTBSF:
+ myOp = VIOTAPOP_BSF;
+ break;
+ case MTFSR:
+ myOp = VIOTAPOP_FSR;
+ break;
+ case MTBSR:
+ myOp = VIOTAPOP_BSR;
+ break;
+ case MTWEOF:
+ myOp = VIOTAPOP_WEOF;
+ break;
+ case MTREW:
+ myOp = VIOTAPOP_REW;
+ break;
+ case MTNOP:
+ myOp = VIOTAPOP_NOP;
+ break;
+ case MTEOM:
+ myOp = VIOTAPOP_EOM;
+ break;
+ case MTERASE:
+ myOp = VIOTAPOP_ERASE;
+ break;
+ case MTSETBLK:
+ myOp = VIOTAPOP_SETBLK;
+ break;
+ case MTSETDENSITY:
+ myOp = VIOTAPOP_SETDENSITY;
+ break;
+ case MTTELL:
+ myOp = VIOTAPOP_GETPOS;
+ break;
+ case MTSEEK:
+ myOp = VIOTAPOP_SETPOS;
+ break;
+ case MTSETPART:
+ myOp = VIOTAPOP_SETPART;
+ break;
+ case MTOFFL:
+ myOp = VIOTAPOP_UNLOAD;
+ break;
+ default:
+ printk(VIOTAPE_KERN_WARN "MTIOCTOP called "
+ "with invalid op 0x%x\n", mtc.mt_op);
+ goto free_op;
+ }
+
+ /*
+ * if we moved the head, we are no longer
+ * reading or writing
+ */
+ switch (mtc.mt_op) {
+ case MTFSF:
+ case MTBSF:
+ case MTFSR:
+ case MTBSR:
+ case MTTELL:
+ case MTSEEK:
+ case MTREW:
+ chg_state(devi.devno, VIOT_IDLE, file);
+ }
+
+ init_completion(&op->com);
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapeop,
+ HvLpEvent_AckInd_DoAck,
+ HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op,
+ VIOVERSION << 16,
+ ((u64)devi.devno << 48), 0,
+ (((u64)myOp) << 32) | mtc.mt_count, 0);
+ if (hvrc != HvLpEvent_Rc_Good) {
+ printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+ (int)hvrc);
+ goto free_op;
+ }
+ wait_for_completion(&op->com);
+ ret = tape_rc_to_errno(op->rc, "tape operation", devi.devno);
+ goto free_op;
+
+ case MTIOCGET:
+ ret = -EIO;
+ init_completion(&op->com);
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapegetstatus,
+ HvLpEvent_AckInd_DoAck,
+ HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48), 0, 0, 0);
+ if (hvrc != HvLpEvent_Rc_Good) {
+ printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+ (int)hvrc);
+ goto free_op;
+ }
+ wait_for_completion(&op->com);
+
+ /* Operation is complete - grab the error code */
+ ret = tape_rc_to_errno(op->rc, "get status", devi.devno);
+ free_op_struct(op);
+ up(&reqSem);
+
+ if ((ret == 0) && copy_to_user((void *)arg,
+ &viomtget[devi.devno],
+ sizeof(viomtget[0])))
+ ret = -EFAULT;
+ return ret;
+ case MTIOCPOS:
+ printk(VIOTAPE_KERN_WARN "Got an (unsupported) MTIOCPOS\n");
+ break;
+ default:
+ printk(VIOTAPE_KERN_WARN "got an unsupported ioctl 0x%0x\n",
+ cmd);
+ break;
+ }
+
+free_op:
+ free_op_struct(op);
+ up(&reqSem);
+ return ret;
+}
+
+static int viotap_open(struct inode *inode, struct file *file)
+{
+ HvLpEvent_Rc hvrc;
+ struct viot_devinfo_struct devi;
+ int ret;
+ struct op_struct *op = get_op_struct();
+
+ if (op == NULL)
+ return -ENOMEM;
+
+ get_dev_info(file->f_dentry->d_inode, &devi);
+
+ /* Note: We currently only support one mode! */
+ if ((devi.devno >= viotape_numdev) || (devi.mode)) {
+ ret = -ENODEV;
+ goto free_op;
+ }
+
+ init_completion(&op->com);
+
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapeopen,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48), 0, 0, 0);
+ if (hvrc != 0) {
+ printk(VIOTAPE_KERN_WARN "bad rc on signalLpEvent %d\n",
+ (int) hvrc);
+ ret = -EIO;
+ goto free_op;
+ }
+
+ wait_for_completion(&op->com);
+ ret = tape_rc_to_errno(op->rc, "open", devi.devno);
+
+free_op:
+ free_op_struct(op);
+ return ret;
+}
+
+
+static int viotap_release(struct inode *inode, struct file *file)
+{
+ HvLpEvent_Rc hvrc;
+ struct viot_devinfo_struct devi;
+ int ret = 0;
+ struct op_struct *op = get_op_struct();
+
+ if (op == NULL)
+ return -ENOMEM;
+ init_completion(&op->com);
+
+ get_dev_info(file->f_dentry->d_inode, &devi);
+
+ if (devi.devno >= viotape_numdev) {
+ ret = -ENODEV;
+ goto free_op;
+ }
+
+ chg_state(devi.devno, VIOT_IDLE, file);
+
+ if (devi.rewind) {
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapeop,
+ HvLpEvent_AckInd_DoAck,
+ HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48), 0,
+ ((u64)VIOTAPOP_REW) << 32, 0);
+ wait_for_completion(&op->com);
+
+ tape_rc_to_errno(op->rc, "rewind", devi.devno);
+ }
+
+ hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+ HvLpEvent_Type_VirtualIo,
+ viomajorsubtype_tape | viotapeclose,
+ HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+ viopath_sourceinst(viopath_hostLp),
+ viopath_targetinst(viopath_hostLp),
+ (u64)(unsigned long)op, VIOVERSION << 16,
+ ((u64)devi.devno << 48), 0, 0, 0);
+ if (hvrc != 0) {
+ printk(VIOTAPE_KERN_WARN "bad rc on signalLpEvent %d\n",
+ (int) hvrc);
+ ret = -EIO;
+ goto free_op;
+ }
+
+ wait_for_completion(&op->com);
+
+ if (op->rc)
+ printk(VIOTAPE_KERN_WARN "close failed\n");
+
+free_op:
+ free_op_struct(op);
+ return ret;
+}
+
+struct file_operations viotap_fops = {
+ owner: THIS_MODULE,
+ read: viotap_read,
+ write: viotap_write,
+ ioctl: viotap_ioctl,
+ open: viotap_open,
+ release: viotap_release,
+};
+
+/* Handle interrupt events for tape */
+static void vioHandleTapeEvent(struct HvLpEvent *event)
+{
+ int tapeminor;
+ struct op_struct *op;
+ struct viotapelpevent *tevent = (struct viotapelpevent *)event;
+
+ if (event == NULL) {
+ /* Notification that a partition went away! */
+ if (!viopath_isactive(viopath_hostLp)) {
+ /* TODO! Clean up */
+ }
+ return;
+ }
+
+ tapeminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
+ op = (struct op_struct *)event->xCorrelationToken;
+ switch (tapeminor) {
+ case viotapegetinfo:
+ case viotapeopen:
+ case viotapeclose:
+ op->rc = tevent->sub_type_result;
+ complete(&op->com);
+ break;
+ case viotaperead:
+ op->rc = tevent->sub_type_result;
+ op->count = tevent->len;
+ complete(&op->com);
+ break;
+ case viotapewrite:
+ if (op->non_blocking) {
+ dma_free_coherent(op->dev, op->count,
+ op->buffer, op->dmaaddr);
+ free_op_struct(op);
+ up(&reqSem);
+ } else {
+ op->rc = tevent->sub_type_result;
+ op->count = tevent->len;
+ complete(&op->com);
+ }
+ break;
+ case viotapeop:
+ case viotapegetpos:
+ case viotapesetpos:
+ case viotapegetstatus:
+ if (op) {
+ op->count = tevent->u.op.count;
+ op->rc = tevent->sub_type_result;
+ if (!op->non_blocking)
+ complete(&op->com);
+ }
+ break;
+ default:
+ printk(VIOTAPE_KERN_WARN "weird ack\n");
+ }
+}
+
+static int viotape_probe(struct vio_dev *vdev, const struct vio_device_id *id)
+{
+ char tapename[32];
+ int i = vdev->unit_address;
+ int j;
+
+ if (i >= viotape_numdev)
+ return -ENODEV;
+
+ tape_device[i] = &vdev->dev;
+
+ state[i].cur_part = 0;
+ for (j = 0; j < MAX_PARTITIONS; ++j)
+ state[i].part_stat_rwi[j] = VIOT_IDLE;
+ class_simple_device_add(tape_class, MKDEV(VIOTAPE_MAJOR, i), NULL,
+ "iseries!vt%d", i);
+ class_simple_device_add(tape_class, MKDEV(VIOTAPE_MAJOR, i | 0x80),
+ NULL, "iseries!nvt%d", i);
+ devfs_mk_cdev(MKDEV(VIOTAPE_MAJOR, i), S_IFCHR | S_IRUSR | S_IWUSR,
+ "iseries/vt%d", i);
+ devfs_mk_cdev(MKDEV(VIOTAPE_MAJOR, i | 0x80),
+ S_IFCHR | S_IRUSR | S_IWUSR, "iseries/nvt%d", i);
+ sprintf(tapename, "iseries/vt%d", i);
+ state[i].dev_handle = devfs_register_tape(tapename);
+ printk(VIOTAPE_KERN_INFO "tape %s is iSeries "
+ "resource %10.10s type %4.4s, model %3.3s\n",
+ tapename, viotape_unitinfo[i].rsrcname,
+ viotape_unitinfo[i].type, viotape_unitinfo[i].model);
+ return 0;
+}
+
+static int viotape_remove(struct vio_dev *vdev)
+{
+ int i = vdev->unit_address;
+
+ devfs_remove("iseries/nvt%d", i);
+ devfs_remove("iseries/vt%d", i);
+ devfs_unregister_tape(state[i].dev_handle);
+ class_simple_device_remove(MKDEV(VIOTAPE_MAJOR, i | 0x80));
+ class_simple_device_remove(MKDEV(VIOTAPE_MAJOR, i));
+ return 0;
+}
+
+/**
+ * viotape_device_table: Used by vio.c to match devices that we
+ * support.
+ */
+static struct vio_device_id viotape_device_table[] __devinitdata = {
+ { "viotape", "" },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(vio, viotape_device_table);
+static struct vio_driver viotape_driver = {
+ .name = "viotape",
+ .id_table = viotape_device_table,
+ .probe = viotape_probe,
+ .remove = viotape_remove
+};
+
+
+int __init viotap_init(void)
+{
+ int ret;
+ struct proc_dir_entry *e;
+
+ op_struct_list = NULL;
+ if ((ret = add_op_structs(VIOTAPE_MAXREQ)) < 0) {
+ printk(VIOTAPE_KERN_WARN "couldn't allocate op structs\n");
+ return ret;
+ }
+ spin_lock_init(&op_struct_list_lock);
+
+ sema_init(&reqSem, VIOTAPE_MAXREQ);
+
+ if (viopath_hostLp == HvLpIndexInvalid) {
+ vio_set_hostlp();
+ if (viopath_hostLp == HvLpIndexInvalid) {
+ ret = -ENODEV;
+ goto clear_op;
+ }
+ }
+
+ ret = viopath_open(viopath_hostLp, viomajorsubtype_tape,
+ VIOTAPE_MAXREQ + 2);
+ if (ret) {
+ printk(VIOTAPE_KERN_WARN
+ "error on viopath_open to hostlp %d\n", ret);
+ ret = -EIO;
+ goto clear_op;
+ }
+
+ printk(VIOTAPE_KERN_INFO "vers " VIOTAPE_VERSION
+ ", hosting partition %d\n", viopath_hostLp);
+
+ vio_setHandler(viomajorsubtype_tape, vioHandleTapeEvent);
+
+ ret = register_chrdev(VIOTAPE_MAJOR, "viotape", &viotap_fops);
+ if (ret < 0) {
+ printk(VIOTAPE_KERN_WARN "Error registering viotape device\n");
+ goto clear_handler;
+ }
+
+ tape_class = class_simple_create(THIS_MODULE, "tape");
+ if (IS_ERR(tape_class)) {
+ printk(VIOTAPE_KERN_WARN "Unable to allocat class\n");
+ ret = PTR_ERR(tape_class);
+ goto unreg_chrdev;
+ }
+
+ if ((ret = get_viotape_info()) < 0) {
+ printk(VIOTAPE_KERN_WARN "Unable to obtain virtual device information");
+ goto unreg_class;
+ }
+
+ ret = vio_register_driver(&viotape_driver);
+ if (ret)
+ goto unreg_class;
+
+ e = create_proc_entry("iSeries/viotape", S_IFREG|S_IRUGO, NULL);
+ if (e) {
+ e->owner = THIS_MODULE;
+ e->proc_fops = &proc_viotape_operations;
+ }
+
+ return 0;
+
+unreg_class:
+ class_simple_destroy(tape_class);
+unreg_chrdev:
+ unregister_chrdev(VIOTAPE_MAJOR, "viotape");
+clear_handler:
+ vio_clearHandler(viomajorsubtype_tape);
+ viopath_close(viopath_hostLp, viomajorsubtype_tape, VIOTAPE_MAXREQ + 2);
+clear_op:
+ clear_op_struct_pool();
+ return ret;
+}
+
+/* Give a new state to the tape object */
+static int chg_state(int index, unsigned char new_state, struct file *file)
+{
+ unsigned char *cur_state =
+ &state[index].part_stat_rwi[state[index].cur_part];
+ int rc = 0;
+
+ /* if the same state, don't bother */
+ if (*cur_state == new_state)
+ return 0;
+
+ /* write an EOF if changing from writing to some other state */
+ if (*cur_state == VIOT_WRITING) {
+ struct mtop write_eof = { MTWEOF, 1 };
+
+ rc = viotap_ioctl(NULL, file, MTIOCTOP,
+ (unsigned long)&write_eof);
+ }
+ *cur_state = new_state;
+ return rc;
+}
+
+/* Cleanup */
+static void __exit viotap_exit(void)
+{
+ int ret;
+
+ remove_proc_entry("iSeries/viotape", NULL);
+ vio_unregister_driver(&viotape_driver);
+ class_simple_destroy(tape_class);
+ ret = unregister_chrdev(VIOTAPE_MAJOR, "viotape");
+ if (ret < 0)
+ printk(VIOTAPE_KERN_WARN "Error unregistering device: %d\n",
+ ret);
+ if (viotape_unitinfo)
+ dma_free_coherent(iSeries_vio_dev,
+ sizeof(viotape_unitinfo[0]) * VIOTAPE_MAX_TAPE,
+ viotape_unitinfo, viotape_unitinfo_token);
+ viopath_close(viopath_hostLp, viomajorsubtype_tape, VIOTAPE_MAXREQ + 2);
+ vio_clearHandler(viomajorsubtype_tape);
+ clear_op_struct_pool();
+}
+
+MODULE_LICENSE("GPL");
+module_init(viotap_init);
+module_exit(viotap_exit);
diff --git a/drivers/char/vme_scc.c b/drivers/char/vme_scc.c
new file mode 100644
index 000000000000..19ba83635dd7
--- /dev/null
+++ b/drivers/char/vme_scc.c
@@ -0,0 +1,1056 @@
+/*
+ * drivers/char/vme_scc.c: MVME147, MVME162, BVME6000 SCC serial ports
+ * implementation.
+ * Copyright 1999 Richard Hirst <richard@sleepie.demon.co.uk>
+ *
+ * Based on atari_SCC.c which was
+ * Copyright 1994-95 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ * Partially based on PC-Linux serial.c by Linus Torvalds and Theodore Ts'o
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kdev_t.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <asm/setup.h>
+#include <asm/bootinfo.h>
+
+#ifdef CONFIG_MVME147_SCC
+#include <asm/mvme147hw.h>
+#endif
+#ifdef CONFIG_MVME162_SCC
+#include <asm/mvme16xhw.h>
+#endif
+#ifdef CONFIG_BVME6000_SCC
+#include <asm/bvme6000hw.h>
+#endif
+
+#include <linux/generic_serial.h>
+#include "scc.h"
+
+
+#define CHANNEL_A 0
+#define CHANNEL_B 1
+
+#define SCC_MINOR_BASE 64
+
+/* Shadows for all SCC write registers */
+static unsigned char scc_shadow[2][16];
+
+/* Location to access for SCC register access delay */
+static volatile unsigned char *scc_del = NULL;
+
+/* To keep track of STATUS_REG state for detection of Ext/Status int source */
+static unsigned char scc_last_status_reg[2];
+
+/***************************** Prototypes *****************************/
+
+/* Function prototypes */
+static void scc_disable_tx_interrupts(void * ptr);
+static void scc_enable_tx_interrupts(void * ptr);
+static void scc_disable_rx_interrupts(void * ptr);
+static void scc_enable_rx_interrupts(void * ptr);
+static int scc_get_CD(void * ptr);
+static void scc_shutdown_port(void * ptr);
+static int scc_set_real_termios(void *ptr);
+static void scc_hungup(void *ptr);
+static void scc_close(void *ptr);
+static int scc_chars_in_buffer(void * ptr);
+static int scc_open(struct tty_struct * tty, struct file * filp);
+static int scc_ioctl(struct tty_struct * tty, struct file * filp,
+ unsigned int cmd, unsigned long arg);
+static void scc_throttle(struct tty_struct *tty);
+static void scc_unthrottle(struct tty_struct *tty);
+static irqreturn_t scc_tx_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_rx_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_stat_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_spcond_int(int irq, void *data, struct pt_regs *fp);
+static void scc_setsignals(struct scc_port *port, int dtr, int rts);
+static void scc_break_ctl(struct tty_struct *tty, int break_state);
+
+static struct tty_driver *scc_driver;
+
+struct scc_port scc_ports[2];
+
+int scc_initialized = 0;
+
+/*---------------------------------------------------------------------------
+ * Interface from generic_serial.c back here
+ *--------------------------------------------------------------------------*/
+
+static struct real_driver scc_real_driver = {
+ scc_disable_tx_interrupts,
+ scc_enable_tx_interrupts,
+ scc_disable_rx_interrupts,
+ scc_enable_rx_interrupts,
+ scc_get_CD,
+ scc_shutdown_port,
+ scc_set_real_termios,
+ scc_chars_in_buffer,
+ scc_close,
+ scc_hungup,
+ NULL
+};
+
+
+static struct tty_operations scc_ops = {
+ .open = scc_open,
+ .close = gs_close,
+ .write = gs_write,
+ .put_char = gs_put_char,
+ .flush_chars = gs_flush_chars,
+ .write_room = gs_write_room,
+ .chars_in_buffer = gs_chars_in_buffer,
+ .flush_buffer = gs_flush_buffer,
+ .ioctl = scc_ioctl,
+ .throttle = scc_throttle,
+ .unthrottle = scc_unthrottle,
+ .set_termios = gs_set_termios,
+ .stop = gs_stop,
+ .start = gs_start,
+ .hangup = gs_hangup,
+ .break_ctl = scc_break_ctl,
+};
+
+/*----------------------------------------------------------------------------
+ * vme_scc_init() and support functions
+ *---------------------------------------------------------------------------*/
+
+static int scc_init_drivers(void)
+{
+ int error;
+
+ scc_driver = alloc_tty_driver(2);
+ if (!scc_driver)
+ return -ENOMEM;
+ scc_driver->owner = THIS_MODULE;
+ scc_driver->driver_name = "scc";
+ scc_driver->name = "ttyS";
+ scc_driver->devfs_name = "tts/";
+ scc_driver->major = TTY_MAJOR;
+ scc_driver->minor_start = SCC_MINOR_BASE;
+ scc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ scc_driver->subtype = SERIAL_TYPE_NORMAL;
+ scc_driver->init_termios = tty_std_termios;
+ scc_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ scc_driver->flags = TTY_DRIVER_REAL_RAW;
+ tty_set_operations(scc_driver, &scc_ops);
+
+ if ((error = tty_register_driver(scc_driver))) {
+ printk(KERN_ERR "scc: Couldn't register scc driver, error = %d\n",
+ error);
+ put_tty_driver(scc_driver);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* ports[] array is indexed by line no (i.e. [0] for ttyS0, [1] for ttyS1).
+ */
+
+static void scc_init_portstructs(void)
+{
+ struct scc_port *port;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ port = scc_ports + i;
+ port->gs.magic = SCC_MAGIC;
+ port->gs.close_delay = HZ/2;
+ port->gs.closing_wait = 30 * HZ;
+ port->gs.rd = &scc_real_driver;
+#ifdef NEW_WRITE_LOCKING
+ port->gs.port_write_sem = MUTEX;
+#endif
+ init_waitqueue_head(&port->gs.open_wait);
+ init_waitqueue_head(&port->gs.close_wait);
+ }
+}
+
+
+#ifdef CONFIG_MVME147_SCC
+static int mvme147_scc_init(void)
+{
+ struct scc_port *port;
+
+ printk(KERN_INFO "SCC: MVME147 Serial Driver\n");
+ /* Init channel A */
+ port = &scc_ports[0];
+ port->channel = CHANNEL_A;
+ port->ctrlp = (volatile unsigned char *)M147_SCC_A_ADDR;
+ port->datap = port->ctrlp + 1;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(MVME147_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-A TX", port);
+ request_irq(MVME147_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-A status", port);
+ request_irq(MVME147_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-A RX", port);
+ request_irq(MVME147_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-A special cond", port);
+ {
+ SCC_ACCESS_INIT(port);
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ /* Set the interrupt vector */
+ SCCwrite(INT_VECTOR_REG, MVME147_IRQ_SCC_BASE);
+ /* Interrupt parameters: vector includes status, status low */
+ SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+ SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+ }
+
+ /* Init channel B */
+ port = &scc_ports[1];
+ port->channel = CHANNEL_B;
+ port->ctrlp = (volatile unsigned char *)M147_SCC_B_ADDR;
+ port->datap = port->ctrlp + 1;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(MVME147_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-B TX", port);
+ request_irq(MVME147_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-B status", port);
+ request_irq(MVME147_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-B RX", port);
+ request_irq(MVME147_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-B special cond", port);
+ {
+ SCC_ACCESS_INIT(port);
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ }
+
+ /* Ensure interrupts are enabled in the PCC chip */
+ m147_pcc->serial_cntrl=PCC_LEVEL_SERIAL|PCC_INT_ENAB;
+
+ /* Initialise the tty driver structures and register */
+ scc_init_portstructs();
+ scc_init_drivers();
+
+ return 0;
+}
+#endif
+
+
+#ifdef CONFIG_MVME162_SCC
+static int mvme162_scc_init(void)
+{
+ struct scc_port *port;
+
+ if (!(mvme16x_config & MVME16x_CONFIG_GOT_SCCA))
+ return (-ENODEV);
+
+ printk(KERN_INFO "SCC: MVME162 Serial Driver\n");
+ /* Init channel A */
+ port = &scc_ports[0];
+ port->channel = CHANNEL_A;
+ port->ctrlp = (volatile unsigned char *)MVME_SCC_A_ADDR;
+ port->datap = port->ctrlp + 2;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(MVME162_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-A TX", port);
+ request_irq(MVME162_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-A status", port);
+ request_irq(MVME162_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-A RX", port);
+ request_irq(MVME162_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-A special cond", port);
+ {
+ SCC_ACCESS_INIT(port);
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ /* Set the interrupt vector */
+ SCCwrite(INT_VECTOR_REG, MVME162_IRQ_SCC_BASE);
+ /* Interrupt parameters: vector includes status, status low */
+ SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+ SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+ }
+
+ /* Init channel B */
+ port = &scc_ports[1];
+ port->channel = CHANNEL_B;
+ port->ctrlp = (volatile unsigned char *)MVME_SCC_B_ADDR;
+ port->datap = port->ctrlp + 2;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(MVME162_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-B TX", port);
+ request_irq(MVME162_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-B status", port);
+ request_irq(MVME162_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-B RX", port);
+ request_irq(MVME162_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-B special cond", port);
+
+ {
+ SCC_ACCESS_INIT(port); /* Either channel will do */
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ }
+
+ /* Ensure interrupts are enabled in the MC2 chip */
+ *(volatile char *)0xfff4201d = 0x14;
+
+ /* Initialise the tty driver structures and register */
+ scc_init_portstructs();
+ scc_init_drivers();
+
+ return 0;
+}
+#endif
+
+
+#ifdef CONFIG_BVME6000_SCC
+static int bvme6000_scc_init(void)
+{
+ struct scc_port *port;
+
+ printk(KERN_INFO "SCC: BVME6000 Serial Driver\n");
+ /* Init channel A */
+ port = &scc_ports[0];
+ port->channel = CHANNEL_A;
+ port->ctrlp = (volatile unsigned char *)BVME_SCC_A_ADDR;
+ port->datap = port->ctrlp + 4;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(BVME_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-A TX", port);
+ request_irq(BVME_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-A status", port);
+ request_irq(BVME_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-A RX", port);
+ request_irq(BVME_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-A special cond", port);
+ {
+ SCC_ACCESS_INIT(port);
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ /* Set the interrupt vector */
+ SCCwrite(INT_VECTOR_REG, BVME_IRQ_SCC_BASE);
+ /* Interrupt parameters: vector includes status, status low */
+ SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+ SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+ }
+
+ /* Init channel B */
+ port = &scc_ports[1];
+ port->channel = CHANNEL_B;
+ port->ctrlp = (volatile unsigned char *)BVME_SCC_B_ADDR;
+ port->datap = port->ctrlp + 4;
+ port->port_a = &scc_ports[0];
+ port->port_b = &scc_ports[1];
+ request_irq(BVME_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+ "SCC-B TX", port);
+ request_irq(BVME_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+ "SCC-B status", port);
+ request_irq(BVME_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+ "SCC-B RX", port);
+ request_irq(BVME_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+ "SCC-B special cond", port);
+
+ {
+ SCC_ACCESS_INIT(port); /* Either channel will do */
+
+ /* disable interrupts for this channel */
+ SCCwrite(INT_AND_DMA_REG, 0);
+ }
+
+ /* Initialise the tty driver structures and register */
+ scc_init_portstructs();
+ scc_init_drivers();
+
+ return 0;
+}
+#endif
+
+
+static int vme_scc_init(void)
+{
+ int res = -ENODEV;
+
+#ifdef CONFIG_MVME147_SCC
+ if (MACH_IS_MVME147)
+ res = mvme147_scc_init();
+#endif
+#ifdef CONFIG_MVME162_SCC
+ if (MACH_IS_MVME16x)
+ res = mvme162_scc_init();
+#endif
+#ifdef CONFIG_BVME6000_SCC
+ if (MACH_IS_BVME6000)
+ res = bvme6000_scc_init();
+#endif
+ return res;
+}
+
+module_init(vme_scc_init);
+
+
+/*---------------------------------------------------------------------------
+ * Interrupt handlers
+ *--------------------------------------------------------------------------*/
+
+static irqreturn_t scc_rx_int(int irq, void *data, struct pt_regs *fp)
+{
+ unsigned char ch;
+ struct scc_port *port = data;
+ struct tty_struct *tty = port->gs.tty;
+ SCC_ACCESS_INIT(port);
+
+ ch = SCCread_NB(RX_DATA_REG);
+ if (!tty) {
+ printk(KERN_WARNING "scc_rx_int with NULL tty!\n");
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+ return IRQ_HANDLED;
+ }
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.char_buf_ptr = ch;
+ *tty->flip.flag_buf_ptr = 0;
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+
+ /* Check if another character is already ready; in that case, the
+ * spcond_int() function must be used, because this character may have an
+ * error condition that isn't signalled by the interrupt vector used!
+ */
+ if (SCCread(INT_PENDING_REG) &
+ (port->channel == CHANNEL_A ? IPR_A_RX : IPR_B_RX)) {
+ scc_spcond_int (irq, data, fp);
+ return IRQ_HANDLED;
+ }
+
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+ tty_flip_buffer_push(tty);
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_spcond_int(int irq, void *data, struct pt_regs *fp)
+{
+ struct scc_port *port = data;
+ struct tty_struct *tty = port->gs.tty;
+ unsigned char stat, ch, err;
+ int int_pending_mask = port->channel == CHANNEL_A ?
+ IPR_A_RX : IPR_B_RX;
+ SCC_ACCESS_INIT(port);
+
+ if (!tty) {
+ printk(KERN_WARNING "scc_spcond_int with NULL tty!\n");
+ SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+ return IRQ_HANDLED;
+ }
+ do {
+ stat = SCCread(SPCOND_STATUS_REG);
+ ch = SCCread_NB(RX_DATA_REG);
+
+ if (stat & SCSR_RX_OVERRUN)
+ err = TTY_OVERRUN;
+ else if (stat & SCSR_PARITY_ERR)
+ err = TTY_PARITY;
+ else if (stat & SCSR_CRC_FRAME_ERR)
+ err = TTY_FRAME;
+ else
+ err = 0;
+
+ if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+ *tty->flip.char_buf_ptr = ch;
+ *tty->flip.flag_buf_ptr = err;
+ tty->flip.flag_buf_ptr++;
+ tty->flip.char_buf_ptr++;
+ tty->flip.count++;
+ }
+
+ /* ++TeSche: *All* errors have to be cleared manually,
+ * else the condition persists for the next chars
+ */
+ if (err)
+ SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+
+ } while(SCCread(INT_PENDING_REG) & int_pending_mask);
+
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+ tty_flip_buffer_push(tty);
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_tx_int(int irq, void *data, struct pt_regs *fp)
+{
+ struct scc_port *port = data;
+ SCC_ACCESS_INIT(port);
+
+ if (!port->gs.tty) {
+ printk(KERN_WARNING "scc_tx_int with NULL tty!\n");
+ SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+ SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET);
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+ return IRQ_HANDLED;
+ }
+ while ((SCCread_NB(STATUS_REG) & SR_TX_BUF_EMPTY)) {
+ if (port->x_char) {
+ SCCwrite(TX_DATA_REG, port->x_char);
+ port->x_char = 0;
+ }
+ else if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+ port->gs.tty->hw_stopped)
+ break;
+ else {
+ SCCwrite(TX_DATA_REG, port->gs.xmit_buf[port->gs.xmit_tail++]);
+ port->gs.xmit_tail = port->gs.xmit_tail & (SERIAL_XMIT_SIZE-1);
+ if (--port->gs.xmit_cnt <= 0)
+ break;
+ }
+ }
+ if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+ port->gs.tty->hw_stopped) {
+ /* disable tx interrupts */
+ SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+ SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET); /* disable tx_int on next tx underrun? */
+ port->gs.flags &= ~GS_TX_INTEN;
+ }
+ if (port->gs.tty && port->gs.xmit_cnt <= port->gs.wakeup_chars)
+ tty_wakeup(port->gs.tty);
+
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+ return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_stat_int(int irq, void *data, struct pt_regs *fp)
+{
+ struct scc_port *port = data;
+ unsigned channel = port->channel;
+ unsigned char last_sr, sr, changed;
+ SCC_ACCESS_INIT(port);
+
+ last_sr = scc_last_status_reg[channel];
+ sr = scc_last_status_reg[channel] = SCCread_NB(STATUS_REG);
+ changed = last_sr ^ sr;
+
+ if (changed & SR_DCD) {
+ port->c_dcd = !!(sr & SR_DCD);
+ if (!(port->gs.flags & ASYNC_CHECK_CD))
+ ; /* Don't report DCD changes */
+ else if (port->c_dcd) {
+ wake_up_interruptible(&port->gs.open_wait);
+ }
+ else {
+ if (port->gs.tty)
+ tty_hangup (port->gs.tty);
+ }
+ }
+ SCCwrite(COMMAND_REG, CR_EXTSTAT_RESET);
+ SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+ return IRQ_HANDLED;
+}
+
+
+/*---------------------------------------------------------------------------
+ * generic_serial.c callback funtions
+ *--------------------------------------------------------------------------*/
+
+static void scc_disable_tx_interrupts(void *ptr)
+{
+ struct scc_port *port = ptr;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+ port->gs.flags &= ~GS_TX_INTEN;
+ local_irq_restore(flags);
+}
+
+
+static void scc_enable_tx_interrupts(void *ptr)
+{
+ struct scc_port *port = ptr;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ SCCmod(INT_AND_DMA_REG, 0xff, IDR_TX_INT_ENAB);
+ /* restart the transmitter */
+ scc_tx_int (0, port, 0);
+ local_irq_restore(flags);
+}
+
+
+static void scc_disable_rx_interrupts(void *ptr)
+{
+ struct scc_port *port = ptr;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ SCCmod(INT_AND_DMA_REG,
+ ~(IDR_RX_INT_MASK|IDR_PARERR_AS_SPCOND|IDR_EXTSTAT_INT_ENAB), 0);
+ local_irq_restore(flags);
+}
+
+
+static void scc_enable_rx_interrupts(void *ptr)
+{
+ struct scc_port *port = ptr;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ SCCmod(INT_AND_DMA_REG, 0xff,
+ IDR_EXTSTAT_INT_ENAB|IDR_PARERR_AS_SPCOND|IDR_RX_INT_ALL);
+ local_irq_restore(flags);
+}
+
+
+static int scc_get_CD(void *ptr)
+{
+ struct scc_port *port = ptr;
+ unsigned channel = port->channel;
+
+ return !!(scc_last_status_reg[channel] & SR_DCD);
+}
+
+
+static void scc_shutdown_port(void *ptr)
+{
+ struct scc_port *port = ptr;
+
+ port->gs.flags &= ~ GS_ACTIVE;
+ if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL) {
+ scc_setsignals (port, 0, 0);
+ }
+}
+
+
+static int scc_set_real_termios (void *ptr)
+{
+ /* the SCC has char sizes 5,7,6,8 in that order! */
+ static int chsize_map[4] = { 0, 2, 1, 3 };
+ unsigned cflag, baud, chsize, channel, brgval = 0;
+ unsigned long flags;
+ struct scc_port *port = ptr;
+ SCC_ACCESS_INIT(port);
+
+ if (!port->gs.tty || !port->gs.tty->termios) return 0;
+
+ channel = port->channel;
+
+ if (channel == CHANNEL_A)
+ return 0; /* Settings controlled by boot PROM */
+
+ cflag = port->gs.tty->termios->c_cflag;
+ baud = port->gs.baud;
+ chsize = (cflag & CSIZE) >> 4;
+
+ if (baud == 0) {
+ /* speed == 0 -> drop DTR */
+ local_irq_save(flags);
+ SCCmod(TX_CTRL_REG, ~TCR_DTR, 0);
+ local_irq_restore(flags);
+ return 0;
+ }
+ else if ((MACH_IS_MVME16x && (baud < 50 || baud > 38400)) ||
+ (MACH_IS_MVME147 && (baud < 50 || baud > 19200)) ||
+ (MACH_IS_BVME6000 &&(baud < 50 || baud > 76800))) {
+ printk(KERN_NOTICE "SCC: Bad speed requested, %d\n", baud);
+ return 0;
+ }
+
+ if (cflag & CLOCAL)
+ port->gs.flags &= ~ASYNC_CHECK_CD;
+ else
+ port->gs.flags |= ASYNC_CHECK_CD;
+
+#ifdef CONFIG_MVME147_SCC
+ if (MACH_IS_MVME147)
+ brgval = (M147_SCC_PCLK + baud/2) / (16 * 2 * baud) - 2;
+#endif
+#ifdef CONFIG_MVME162_SCC
+ if (MACH_IS_MVME16x)
+ brgval = (MVME_SCC_PCLK + baud/2) / (16 * 2 * baud) - 2;
+#endif
+#ifdef CONFIG_BVME6000_SCC
+ if (MACH_IS_BVME6000)
+ brgval = (BVME_SCC_RTxC + baud/2) / (16 * 2 * baud) - 2;
+#endif
+ /* Now we have all parameters and can go to set them: */
+ local_irq_save(flags);
+
+ /* receiver's character size and auto-enables */
+ SCCmod(RX_CTRL_REG, ~(RCR_CHSIZE_MASK|RCR_AUTO_ENAB_MODE),
+ (chsize_map[chsize] << 6) |
+ ((cflag & CRTSCTS) ? RCR_AUTO_ENAB_MODE : 0));
+ /* parity and stop bits (both, Tx and Rx), clock mode never changes */
+ SCCmod (AUX1_CTRL_REG,
+ ~(A1CR_PARITY_MASK | A1CR_MODE_MASK),
+ ((cflag & PARENB
+ ? (cflag & PARODD ? A1CR_PARITY_ODD : A1CR_PARITY_EVEN)
+ : A1CR_PARITY_NONE)
+ | (cflag & CSTOPB ? A1CR_MODE_ASYNC_2 : A1CR_MODE_ASYNC_1)));
+ /* sender's character size, set DTR for valid baud rate */
+ SCCmod(TX_CTRL_REG, ~TCR_CHSIZE_MASK, chsize_map[chsize] << 5 | TCR_DTR);
+ /* clock sources never change */
+ /* disable BRG before changing the value */
+ SCCmod(DPLL_CTRL_REG, ~DCR_BRG_ENAB, 0);
+ /* BRG value */
+ SCCwrite(TIMER_LOW_REG, brgval & 0xff);
+ SCCwrite(TIMER_HIGH_REG, (brgval >> 8) & 0xff);
+ /* BRG enable, and clock source never changes */
+ SCCmod(DPLL_CTRL_REG, 0xff, DCR_BRG_ENAB);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+
+static int scc_chars_in_buffer (void *ptr)
+{
+ struct scc_port *port = ptr;
+ SCC_ACCESS_INIT(port);
+
+ return (SCCread (SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0 : 1;
+}
+
+
+/* Comment taken from sx.c (2.4.0):
+ I haven't the foggiest why the decrement use count has to happen
+ here. The whole linux serial drivers stuff needs to be redesigned.
+ My guess is that this is a hack to minimize the impact of a bug
+ elsewhere. Thinking about it some more. (try it sometime) Try
+ running minicom on a serial port that is driven by a modularized
+ driver. Have the modem hangup. Then remove the driver module. Then
+ exit minicom. I expect an "oops". -- REW */
+
+static void scc_hungup(void *ptr)
+{
+ scc_disable_tx_interrupts(ptr);
+ scc_disable_rx_interrupts(ptr);
+}
+
+
+static void scc_close(void *ptr)
+{
+ scc_disable_tx_interrupts(ptr);
+ scc_disable_rx_interrupts(ptr);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Internal support functions
+ *--------------------------------------------------------------------------*/
+
+static void scc_setsignals(struct scc_port *port, int dtr, int rts)
+{
+ unsigned long flags;
+ unsigned char t;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ t = SCCread(TX_CTRL_REG);
+ if (dtr >= 0) t = dtr? (t | TCR_DTR): (t & ~TCR_DTR);
+ if (rts >= 0) t = rts? (t | TCR_RTS): (t & ~TCR_RTS);
+ SCCwrite(TX_CTRL_REG, t);
+ local_irq_restore(flags);
+}
+
+
+static void scc_send_xchar(struct tty_struct *tty, char ch)
+{
+ struct scc_port *port = (struct scc_port *)tty->driver_data;
+
+ port->x_char = ch;
+ if (ch)
+ scc_enable_tx_interrupts(port);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Driver entrypoints referenced from above
+ *--------------------------------------------------------------------------*/
+
+static int scc_open (struct tty_struct * tty, struct file * filp)
+{
+ int line = tty->index;
+ int retval;
+ struct scc_port *port = &scc_ports[line];
+ int i, channel = port->channel;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+#if defined(CONFIG_MVME162_SCC) || defined(CONFIG_MVME147_SCC)
+ static const struct {
+ unsigned reg, val;
+ } mvme_init_tab[] = {
+ /* Values for MVME162 and MVME147 */
+ /* no parity, 1 stop bit, async, 1:16 */
+ { AUX1_CTRL_REG, A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x16 },
+ /* parity error is special cond, ints disabled, no DMA */
+ { INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB },
+ /* Rx 8 bits/char, no auto enable, Rx off */
+ { RX_CTRL_REG, RCR_CHSIZE_8 },
+ /* DTR off, Tx 8 bits/char, RTS off, Tx off */
+ { TX_CTRL_REG, TCR_CHSIZE_8 },
+ /* special features off */
+ { AUX2_CTRL_REG, 0 },
+ { CLK_CTRL_REG, CCR_RXCLK_BRG | CCR_TXCLK_BRG },
+ { DPLL_CTRL_REG, DCR_BRG_ENAB | DCR_BRG_USE_PCLK },
+ /* Start Rx */
+ { RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 },
+ /* Start Tx */
+ { TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 },
+ /* Ext/Stat ints: DCD only */
+ { INT_CTRL_REG, ICR_ENAB_DCD_INT },
+ /* Reset Ext/Stat ints */
+ { COMMAND_REG, CR_EXTSTAT_RESET },
+ /* ...again */
+ { COMMAND_REG, CR_EXTSTAT_RESET },
+ };
+#endif
+#if defined(CONFIG_BVME6000_SCC)
+ static const struct {
+ unsigned reg, val;
+ } bvme_init_tab[] = {
+ /* Values for BVME6000 */
+ /* no parity, 1 stop bit, async, 1:16 */
+ { AUX1_CTRL_REG, A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x16 },
+ /* parity error is special cond, ints disabled, no DMA */
+ { INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB },
+ /* Rx 8 bits/char, no auto enable, Rx off */
+ { RX_CTRL_REG, RCR_CHSIZE_8 },
+ /* DTR off, Tx 8 bits/char, RTS off, Tx off */
+ { TX_CTRL_REG, TCR_CHSIZE_8 },
+ /* special features off */
+ { AUX2_CTRL_REG, 0 },
+ { CLK_CTRL_REG, CCR_RTxC_XTAL | CCR_RXCLK_BRG | CCR_TXCLK_BRG },
+ { DPLL_CTRL_REG, DCR_BRG_ENAB },
+ /* Start Rx */
+ { RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 },
+ /* Start Tx */
+ { TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 },
+ /* Ext/Stat ints: DCD only */
+ { INT_CTRL_REG, ICR_ENAB_DCD_INT },
+ /* Reset Ext/Stat ints */
+ { COMMAND_REG, CR_EXTSTAT_RESET },
+ /* ...again */
+ { COMMAND_REG, CR_EXTSTAT_RESET },
+ };
+#endif
+ if (!(port->gs.flags & ASYNC_INITIALIZED)) {
+ local_irq_save(flags);
+#if defined(CONFIG_MVME147_SCC) || defined(CONFIG_MVME162_SCC)
+ if (MACH_IS_MVME147 || MACH_IS_MVME16x) {
+ for (i=0; i<sizeof(mvme_init_tab)/sizeof(*mvme_init_tab); ++i)
+ SCCwrite(mvme_init_tab[i].reg, mvme_init_tab[i].val);
+ }
+#endif
+#if defined(CONFIG_BVME6000_SCC)
+ if (MACH_IS_BVME6000) {
+ for (i=0; i<sizeof(bvme_init_tab)/sizeof(*bvme_init_tab); ++i)
+ SCCwrite(bvme_init_tab[i].reg, bvme_init_tab[i].val);
+ }
+#endif
+
+ /* remember status register for detection of DCD and CTS changes */
+ scc_last_status_reg[channel] = SCCread(STATUS_REG);
+
+ port->c_dcd = 0; /* Prevent initial 1->0 interrupt */
+ scc_setsignals (port, 1,1);
+ local_irq_restore(flags);
+ }
+
+ tty->driver_data = port;
+ port->gs.tty = tty;
+ port->gs.count++;
+ retval = gs_init_port(&port->gs);
+ if (retval) {
+ port->gs.count--;
+ return retval;
+ }
+ port->gs.flags |= GS_ACTIVE;
+ retval = gs_block_til_ready(port, filp);
+
+ if (retval) {
+ port->gs.count--;
+ return retval;
+ }
+
+ port->c_dcd = scc_get_CD (port);
+
+ scc_enable_rx_interrupts(port);
+
+ return 0;
+}
+
+
+static void scc_throttle (struct tty_struct * tty)
+{
+ struct scc_port *port = (struct scc_port *)tty->driver_data;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ local_irq_save(flags);
+ SCCmod(TX_CTRL_REG, ~TCR_RTS, 0);
+ local_irq_restore(flags);
+ }
+ if (I_IXOFF(tty))
+ scc_send_xchar(tty, STOP_CHAR(tty));
+}
+
+
+static void scc_unthrottle (struct tty_struct * tty)
+{
+ struct scc_port *port = (struct scc_port *)tty->driver_data;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ if (tty->termios->c_cflag & CRTSCTS) {
+ local_irq_save(flags);
+ SCCmod(TX_CTRL_REG, 0xff, TCR_RTS);
+ local_irq_restore(flags);
+ }
+ if (I_IXOFF(tty))
+ scc_send_xchar(tty, START_CHAR(tty));
+}
+
+
+static int scc_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return -ENOIOCTLCMD;
+}
+
+
+static void scc_break_ctl(struct tty_struct *tty, int break_state)
+{
+ struct scc_port *port = (struct scc_port *)tty->driver_data;
+ unsigned long flags;
+ SCC_ACCESS_INIT(port);
+
+ local_irq_save(flags);
+ SCCmod(TX_CTRL_REG, ~TCR_SEND_BREAK,
+ break_state ? TCR_SEND_BREAK : 0);
+ local_irq_restore(flags);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Serial console stuff...
+ *--------------------------------------------------------------------------*/
+
+#define scc_delay() do { __asm__ __volatile__ (" nop; nop"); } while (0)
+
+static void scc_ch_write (char ch)
+{
+ volatile char *p = NULL;
+
+#ifdef CONFIG_MVME147_SCC
+ if (MACH_IS_MVME147)
+ p = (volatile char *)M147_SCC_A_ADDR;
+#endif
+#ifdef CONFIG_MVME162_SCC
+ if (MACH_IS_MVME16x)
+ p = (volatile char *)MVME_SCC_A_ADDR;
+#endif
+#ifdef CONFIG_BVME6000_SCC
+ if (MACH_IS_BVME6000)
+ p = (volatile char *)BVME_SCC_A_ADDR;
+#endif
+
+ do {
+ scc_delay();
+ }
+ while (!(*p & 4));
+ scc_delay();
+ *p = 8;
+ scc_delay();
+ *p = ch;
+}
+
+/* The console must be locked when we get here. */
+
+static void scc_console_write (struct console *co, const char *str, unsigned count)
+{
+ unsigned long flags;
+
+ local_irq_save(flags);
+
+ while (count--)
+ {
+ if (*str == '\n')
+ scc_ch_write ('\r');
+ scc_ch_write (*str++);
+ }
+ local_irq_restore(flags);
+}
+
+static struct tty_driver *scc_console_device(struct console *c, int *index)
+{
+ *index = c->index;
+ return scc_driver;
+}
+
+
+static int __init scc_console_setup(struct console *co, char *options)
+{
+ return 0;
+}
+
+
+static struct console sercons = {
+ .name = "ttyS",
+ .write = scc_console_write,
+ .device = scc_console_device,
+ .setup = scc_console_setup,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+
+
+static int __init vme_scc_console_init(void)
+{
+ if (vme_brdtype == VME_TYPE_MVME147 ||
+ vme_brdtype == VME_TYPE_MVME162 ||
+ vme_brdtype == VME_TYPE_MVME172 ||
+ vme_brdtype == VME_TYPE_BVME4000 ||
+ vme_brdtype == VME_TYPE_BVME6000)
+ register_console(&sercons);
+ return 0;
+}
+console_initcall(vme_scc_console_init);
diff --git a/drivers/char/vr41xx_rtc.c b/drivers/char/vr41xx_rtc.c
new file mode 100644
index 000000000000..a6dbe4da030c
--- /dev/null
+++ b/drivers/char/vr41xx_rtc.c
@@ -0,0 +1,709 @@
+/*
+ * Driver for NEC VR4100 series Real Time Clock unit.
+ *
+ * Copyright (C) 2003-2005 Yoichi Yuasa <yuasa@hh.iij4u.or.jp>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/mc146818rtc.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/rtc.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <asm/div64.h>
+#include <asm/io.h>
+#include <asm/time.h>
+#include <asm/uaccess.h>
+#include <asm/vr41xx/vr41xx.h>
+
+MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>");
+MODULE_DESCRIPTION("NEC VR4100 series RTC driver");
+MODULE_LICENSE("GPL");
+
+#define RTC1_TYPE1_START 0x0b0000c0UL
+#define RTC1_TYPE1_END 0x0b0000dfUL
+#define RTC2_TYPE1_START 0x0b0001c0UL
+#define RTC2_TYPE1_END 0x0b0001dfUL
+
+#define RTC1_TYPE2_START 0x0f000100UL
+#define RTC1_TYPE2_END 0x0f00011fUL
+#define RTC2_TYPE2_START 0x0f000120UL
+#define RTC2_TYPE2_END 0x0f00013fUL
+
+#define RTC1_SIZE 0x20
+#define RTC2_SIZE 0x20
+
+/* RTC 1 registers */
+#define ETIMELREG 0x00
+#define ETIMEMREG 0x02
+#define ETIMEHREG 0x04
+/* RFU */
+#define ECMPLREG 0x08
+#define ECMPMREG 0x0a
+#define ECMPHREG 0x0c
+/* RFU */
+#define RTCL1LREG 0x10
+#define RTCL1HREG 0x12
+#define RTCL1CNTLREG 0x14
+#define RTCL1CNTHREG 0x16
+#define RTCL2LREG 0x18
+#define RTCL2HREG 0x1a
+#define RTCL2CNTLREG 0x1c
+#define RTCL2CNTHREG 0x1e
+
+/* RTC 2 registers */
+#define TCLKLREG 0x00
+#define TCLKHREG 0x02
+#define TCLKCNTLREG 0x04
+#define TCLKCNTHREG 0x06
+/* RFU */
+#define RTCINTREG 0x1e
+ #define TCLOCK_INT 0x08
+ #define RTCLONG2_INT 0x04
+ #define RTCLONG1_INT 0x02
+ #define ELAPSEDTIME_INT 0x01
+
+#define RTC_FREQUENCY 32768
+#define MAX_PERIODIC_RATE 6553
+#define MAX_USER_PERIODIC_RATE 64
+
+static void __iomem *rtc1_base;
+static void __iomem *rtc2_base;
+
+#define rtc1_read(offset) readw(rtc1_base + (offset))
+#define rtc1_write(offset, value) writew((value), rtc1_base + (offset))
+
+#define rtc2_read(offset) readw(rtc2_base + (offset))
+#define rtc2_write(offset, value) writew((value), rtc2_base + (offset))
+
+static unsigned long epoch = 1970; /* Jan 1 1970 00:00:00 */
+
+static spinlock_t rtc_task_lock;
+static wait_queue_head_t rtc_wait;
+static unsigned long rtc_irq_data;
+static struct fasync_struct *rtc_async_queue;
+static rtc_task_t *rtc_callback;
+static char rtc_name[] = "RTC";
+static unsigned long periodic_frequency;
+static unsigned long periodic_count;
+
+typedef enum {
+ RTC_RELEASE,
+ RTC_OPEN,
+} rtc_status_t;
+
+static rtc_status_t rtc_status;
+
+typedef enum {
+ FUNCTION_RTC_IOCTL,
+ FUNCTION_RTC_CONTROL,
+} rtc_callfrom_t;
+
+struct resource rtc_resource[2] = {
+ { .name = rtc_name,
+ .flags = IORESOURCE_MEM, },
+ { .name = rtc_name,
+ .flags = IORESOURCE_MEM, },
+};
+
+#define RTC_NUM_RESOURCES sizeof(rtc_resource) / sizeof(struct resource)
+
+static inline unsigned long read_elapsed_second(void)
+{
+ unsigned long first_low, first_mid, first_high;
+ unsigned long second_low, second_mid, second_high;
+
+ do {
+ first_low = rtc1_read(ETIMELREG);
+ first_mid = rtc1_read(ETIMEMREG);
+ first_high = rtc1_read(ETIMEHREG);
+ second_low = rtc1_read(ETIMELREG);
+ second_mid = rtc1_read(ETIMEMREG);
+ second_high = rtc1_read(ETIMEHREG);
+ } while (first_low != second_low || first_mid != second_mid ||
+ first_high != second_high);
+
+ return (first_high << 17) | (first_mid << 1) | (first_low >> 15);
+}
+
+static inline void write_elapsed_second(unsigned long sec)
+{
+ spin_lock_irq(&rtc_lock);
+
+ rtc1_write(ETIMELREG, (uint16_t)(sec << 15));
+ rtc1_write(ETIMEMREG, (uint16_t)(sec >> 1));
+ rtc1_write(ETIMEHREG, (uint16_t)(sec >> 17));
+
+ spin_unlock_irq(&rtc_lock);
+}
+
+static void set_alarm(struct rtc_time *time)
+{
+ unsigned long alarm_sec;
+
+ alarm_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
+ time->tm_hour, time->tm_min, time->tm_sec);
+
+ spin_lock_irq(&rtc_lock);
+
+ rtc1_write(ECMPLREG, (uint16_t)(alarm_sec << 15));
+ rtc1_write(ECMPMREG, (uint16_t)(alarm_sec >> 1));
+ rtc1_write(ECMPHREG, (uint16_t)(alarm_sec >> 17));
+
+ spin_unlock_irq(&rtc_lock);
+}
+
+static void read_alarm(struct rtc_time *time)
+{
+ unsigned long low, mid, high;
+
+ spin_lock_irq(&rtc_lock);
+
+ low = rtc1_read(ECMPLREG);
+ mid = rtc1_read(ECMPMREG);
+ high = rtc1_read(ECMPHREG);
+
+ spin_unlock_irq(&rtc_lock);
+
+ to_tm((high << 17) | (mid << 1) | (low >> 15), time);
+ time->tm_year -= 1900;
+}
+
+static void read_time(struct rtc_time *time)
+{
+ unsigned long epoch_sec, elapsed_sec;
+
+ epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
+ elapsed_sec = read_elapsed_second();
+
+ to_tm(epoch_sec + elapsed_sec, time);
+ time->tm_year -= 1900;
+}
+
+static void set_time(struct rtc_time *time)
+{
+ unsigned long epoch_sec, current_sec;
+
+ epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
+ current_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
+ time->tm_hour, time->tm_min, time->tm_sec);
+
+ write_elapsed_second(current_sec - epoch_sec);
+}
+
+static ssize_t rtc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long irq_data;
+ int retval = 0;
+
+ if (count != sizeof(unsigned int) && count != sizeof(unsigned long))
+ return -EINVAL;
+
+ add_wait_queue(&rtc_wait, &wait);
+
+ do {
+ __set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_lock_irq(&rtc_lock);
+ irq_data = rtc_irq_data;
+ rtc_irq_data = 0;
+ spin_unlock_irq(&rtc_lock);
+
+ if (irq_data != 0)
+ break;
+
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+ } while (1);
+
+ if (retval == 0) {
+ if (count == sizeof(unsigned int)) {
+ retval = put_user(irq_data, (unsigned int __user *)buf);
+ if (retval == 0)
+ retval = sizeof(unsigned int);
+ } else {
+ retval = put_user(irq_data, (unsigned long __user *)buf);
+ if (retval == 0)
+ retval = sizeof(unsigned long);
+ }
+
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&rtc_wait, &wait);
+
+ return retval;
+}
+
+static unsigned int rtc_poll(struct file *file, struct poll_table_struct *table)
+{
+ poll_wait(file, &rtc_wait, table);
+
+ if (rtc_irq_data != 0)
+ return POLLIN | POLLRDNORM;
+
+ return 0;
+}
+
+static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, rtc_callfrom_t from)
+{
+ struct rtc_time time;
+ unsigned long count;
+
+ switch (cmd) {
+ case RTC_AIE_ON:
+ enable_irq(ELAPSEDTIME_IRQ);
+ break;
+ case RTC_AIE_OFF:
+ disable_irq(ELAPSEDTIME_IRQ);
+ break;
+ case RTC_PIE_ON:
+ enable_irq(RTCLONG1_IRQ);
+ break;
+ case RTC_PIE_OFF:
+ disable_irq(RTCLONG1_IRQ);
+ break;
+ case RTC_ALM_SET:
+ if (copy_from_user(&time, (struct rtc_time __user *)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ set_alarm(&time);
+ break;
+ case RTC_ALM_READ:
+ memset(&time, 0, sizeof(struct rtc_time));
+ read_alarm(&time);
+ break;
+ case RTC_RD_TIME:
+ memset(&time, 0, sizeof(struct rtc_time));
+ read_time(&time);
+ if (copy_to_user((void __user *)arg, &time, sizeof(struct rtc_time)))
+ return -EFAULT;
+ break;
+ case RTC_SET_TIME:
+ if (capable(CAP_SYS_TIME) == 0)
+ return -EACCES;
+
+ if (copy_from_user(&time, (struct rtc_time __user *)arg,
+ sizeof(struct rtc_time)))
+ return -EFAULT;
+
+ set_time(&time);
+ break;
+ case RTC_IRQP_READ:
+ return put_user(periodic_frequency, (unsigned long __user *)arg);
+ break;
+ case RTC_IRQP_SET:
+ if (arg > MAX_PERIODIC_RATE)
+ return -EINVAL;
+
+ if (from == FUNCTION_RTC_IOCTL && arg > MAX_USER_PERIODIC_RATE &&
+ capable(CAP_SYS_RESOURCE) == 0)
+ return -EACCES;
+
+ periodic_frequency = arg;
+
+ count = RTC_FREQUENCY;
+ do_div(count, arg);
+
+ periodic_count = count;
+
+ spin_lock_irq(&rtc_lock);
+
+ rtc1_write(RTCL1LREG, count);
+ rtc1_write(RTCL1HREG, count >> 16);
+
+ spin_unlock_irq(&rtc_lock);
+ break;
+ case RTC_EPOCH_READ:
+ return put_user(epoch, (unsigned long __user *)arg);
+ case RTC_EPOCH_SET:
+ /* Doesn't support before 1900 */
+ if (arg < 1900)
+ return -EINVAL;
+
+ if (capable(CAP_SYS_TIME) == 0)
+ return -EACCES;
+
+ epoch = arg;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ return rtc_do_ioctl(cmd, arg, FUNCTION_RTC_IOCTL);
+}
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+ spin_lock_irq(&rtc_lock);
+
+ if (rtc_status == RTC_OPEN) {
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+ }
+
+ rtc_status = RTC_OPEN;
+ rtc_irq_data = 0;
+
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+ if (file->f_flags & FASYNC)
+ (void)fasync_helper(-1, file, 0, &rtc_async_queue);
+
+ spin_lock_irq(&rtc_lock);
+
+ rtc1_write(ECMPLREG, 0);
+ rtc1_write(ECMPMREG, 0);
+ rtc1_write(ECMPHREG, 0);
+ rtc1_write(RTCL1LREG, 0);
+ rtc1_write(RTCL1HREG, 0);
+
+ rtc_status = RTC_RELEASE;
+
+ spin_unlock_irq(&rtc_lock);
+
+ disable_irq(ELAPSEDTIME_IRQ);
+ disable_irq(RTCLONG1_IRQ);
+
+ return 0;
+}
+
+static int rtc_fasync(int fd, struct file *file, int on)
+{
+ return fasync_helper(fd, file, on, &rtc_async_queue);
+}
+
+static struct file_operations rtc_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = rtc_read,
+ .poll = rtc_poll,
+ .ioctl = rtc_ioctl,
+ .open = rtc_open,
+ .release = rtc_release,
+ .fasync = rtc_fasync,
+};
+
+static irqreturn_t elapsedtime_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ spin_lock(&rtc_lock);
+ rtc2_write(RTCINTREG, ELAPSEDTIME_INT);
+
+ rtc_irq_data += 0x100;
+ rtc_irq_data &= ~0xff;
+ rtc_irq_data |= RTC_AF;
+ spin_unlock(&rtc_lock);
+
+ spin_lock(&rtc_lock);
+ if (rtc_callback)
+ rtc_callback->func(rtc_callback->private_data);
+ spin_unlock(&rtc_lock);
+
+ wake_up_interruptible(&rtc_wait);
+
+ kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rtclong1_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ unsigned long count = periodic_count;
+
+ spin_lock(&rtc_lock);
+ rtc2_write(RTCINTREG, RTCLONG1_INT);
+
+ rtc1_write(RTCL1LREG, count);
+ rtc1_write(RTCL1HREG, count >> 16);
+
+ rtc_irq_data += 0x100;
+ rtc_irq_data &= ~0xff;
+ rtc_irq_data |= RTC_PF;
+ spin_unlock(&rtc_lock);
+
+ spin_lock(&rtc_task_lock);
+ if (rtc_callback)
+ rtc_callback->func(rtc_callback->private_data);
+ spin_unlock(&rtc_task_lock);
+
+ wake_up_interruptible(&rtc_wait);
+
+ kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
+
+ return IRQ_HANDLED;
+}
+
+int rtc_register(rtc_task_t *task)
+{
+ if (task == NULL || task->func == NULL)
+ return -EINVAL;
+
+ spin_lock_irq(&rtc_lock);
+ if (rtc_status == RTC_OPEN) {
+ spin_unlock_irq(&rtc_lock);
+ return -EBUSY;
+ }
+
+ spin_lock(&rtc_task_lock);
+ if (rtc_callback != NULL) {
+ spin_unlock(&rtc_task_lock);
+ spin_unlock_irq(&rtc_task_lock);
+ return -EBUSY;
+ }
+
+ rtc_callback = task;
+ spin_unlock(&rtc_task_lock);
+
+ rtc_status = RTC_OPEN;
+
+ spin_unlock_irq(&rtc_lock);
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(rtc_register);
+
+int rtc_unregister(rtc_task_t *task)
+{
+ spin_lock_irq(&rtc_task_lock);
+ if (task == NULL || rtc_callback != task) {
+ spin_unlock_irq(&rtc_task_lock);
+ return -ENXIO;
+ }
+
+ spin_lock(&rtc_lock);
+
+ rtc1_write(ECMPLREG, 0);
+ rtc1_write(ECMPMREG, 0);
+ rtc1_write(ECMPHREG, 0);
+ rtc1_write(RTCL1LREG, 0);
+ rtc1_write(RTCL1HREG, 0);
+
+ rtc_status = RTC_RELEASE;
+
+ spin_unlock(&rtc_lock);
+
+ rtc_callback = NULL;
+
+ spin_unlock_irq(&rtc_task_lock);
+
+ disable_irq(ELAPSEDTIME_IRQ);
+ disable_irq(RTCLONG1_IRQ);
+
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(rtc_unregister);
+
+int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg)
+{
+ int retval = 0;
+
+ spin_lock_irq(&rtc_task_lock);
+
+ if (rtc_callback != task)
+ retval = -ENXIO;
+ else
+ rtc_do_ioctl(cmd, arg, FUNCTION_RTC_CONTROL);
+
+ spin_unlock_irq(&rtc_task_lock);
+
+ return retval;
+}
+
+EXPORT_SYMBOL_GPL(rtc_control);
+
+static struct miscdevice rtc_miscdevice = {
+ .minor = RTC_MINOR,
+ .name = rtc_name,
+ .fops = &rtc_fops,
+};
+
+static int rtc_probe(struct device *dev)
+{
+ struct platform_device *pdev;
+ unsigned int irq;
+ int retval;
+
+ pdev = to_platform_device(dev);
+ if (pdev->num_resources != 2)
+ return -EBUSY;
+
+ rtc1_base = ioremap(pdev->resource[0].start, RTC1_SIZE);
+ if (rtc1_base == NULL)
+ return -EBUSY;
+
+ rtc2_base = ioremap(pdev->resource[1].start, RTC2_SIZE);
+ if (rtc2_base == NULL) {
+ iounmap(rtc1_base);
+ rtc1_base = NULL;
+ return -EBUSY;
+ }
+
+ retval = misc_register(&rtc_miscdevice);
+ if (retval < 0) {
+ iounmap(rtc1_base);
+ iounmap(rtc2_base);
+ rtc1_base = NULL;
+ rtc2_base = NULL;
+ return retval;
+ }
+
+ spin_lock_irq(&rtc_lock);
+
+ rtc1_write(ECMPLREG, 0);
+ rtc1_write(ECMPMREG, 0);
+ rtc1_write(ECMPHREG, 0);
+ rtc1_write(RTCL1LREG, 0);
+ rtc1_write(RTCL1HREG, 0);
+
+ rtc_status = RTC_RELEASE;
+ rtc_irq_data = 0;
+
+ spin_unlock_irq(&rtc_lock);
+
+ init_waitqueue_head(&rtc_wait);
+
+ irq = ELAPSEDTIME_IRQ;
+ retval = request_irq(irq, elapsedtime_interrupt, SA_INTERRUPT,
+ "elapsed_time", NULL);
+ if (retval == 0) {
+ irq = RTCLONG1_IRQ;
+ retval = request_irq(irq, rtclong1_interrupt, SA_INTERRUPT,
+ "rtclong1", NULL);
+ }
+
+ if (retval < 0) {
+ printk(KERN_ERR "rtc: IRQ%d is busy\n", irq);
+ if (irq == RTCLONG1_IRQ)
+ free_irq(ELAPSEDTIME_IRQ, NULL);
+ iounmap(rtc1_base);
+ iounmap(rtc2_base);
+ rtc1_base = NULL;
+ rtc2_base = NULL;
+ return retval;
+ }
+
+ disable_irq(ELAPSEDTIME_IRQ);
+ disable_irq(RTCLONG1_IRQ);
+
+ spin_lock_init(&rtc_task_lock);
+
+ printk(KERN_INFO "rtc: Real Time Clock of NEC VR4100 series\n");
+
+ return 0;
+}
+
+static int rtc_remove(struct device *dev)
+{
+ int retval;
+
+ retval = misc_deregister(&rtc_miscdevice);
+ if (retval < 0)
+ return retval;
+
+ free_irq(ELAPSEDTIME_IRQ, NULL);
+ free_irq(RTCLONG1_IRQ, NULL);
+ if (rtc1_base != NULL)
+ iounmap(rtc1_base);
+ if (rtc2_base != NULL)
+ iounmap(rtc2_base);
+
+ return 0;
+}
+
+static struct platform_device *rtc_platform_device;
+
+static struct device_driver rtc_device_driver = {
+ .name = rtc_name,
+ .bus = &platform_bus_type,
+ .probe = rtc_probe,
+ .remove = rtc_remove,
+};
+
+static int __devinit vr41xx_rtc_init(void)
+{
+ int retval;
+
+ switch (current_cpu_data.cputype) {
+ case CPU_VR4111:
+ case CPU_VR4121:
+ rtc_resource[0].start = RTC1_TYPE1_START;
+ rtc_resource[0].end = RTC1_TYPE1_END;
+ rtc_resource[1].start = RTC2_TYPE1_START;
+ rtc_resource[1].end = RTC2_TYPE1_END;
+ break;
+ case CPU_VR4122:
+ case CPU_VR4131:
+ case CPU_VR4133:
+ rtc_resource[0].start = RTC1_TYPE2_START;
+ rtc_resource[0].end = RTC1_TYPE2_END;
+ rtc_resource[1].start = RTC2_TYPE2_START;
+ rtc_resource[1].end = RTC2_TYPE2_END;
+ break;
+ default:
+ return -ENODEV;
+ break;
+ }
+
+ rtc_platform_device = platform_device_register_simple("RTC", -1, rtc_resource, RTC_NUM_RESOURCES);
+ if (IS_ERR(rtc_platform_device))
+ return PTR_ERR(rtc_platform_device);
+
+ retval = driver_register(&rtc_device_driver);
+ if (retval < 0)
+ platform_device_unregister(rtc_platform_device);
+
+ return retval;
+}
+
+static void __devexit vr41xx_rtc_exit(void)
+{
+ driver_unregister(&rtc_device_driver);
+
+ platform_device_unregister(rtc_platform_device);
+}
+
+module_init(vr41xx_rtc_init);
+module_exit(vr41xx_rtc_exit);
diff --git a/drivers/char/vt.c b/drivers/char/vt.c
new file mode 100644
index 000000000000..e5ef1dfc5482
--- /dev/null
+++ b/drivers/char/vt.c
@@ -0,0 +1,3242 @@
+/*
+ * linux/drivers/char/vt.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds
+ */
+
+/*
+ * Hopefully this will be a rather complete VT102 implementation.
+ *
+ * Beeping thanks to John T Kohl.
+ *
+ * Virtual Consoles, Screen Blanking, Screen Dumping, Color, Graphics
+ * Chars, and VT100 enhancements by Peter MacDonald.
+ *
+ * Copy and paste function by Andrew Haylett,
+ * some enhancements by Alessandro Rubini.
+ *
+ * Code to check for different video-cards mostly by Galen Hunt,
+ * <g-hunt@ee.utah.edu>
+ *
+ * Rudimentary ISO 10646/Unicode/UTF-8 character set support by
+ * Markus Kuhn, <mskuhn@immd4.informatik.uni-erlangen.de>.
+ *
+ * Dynamic allocation of consoles, aeb@cwi.nl, May 1994
+ * Resizing of consoles, aeb, 940926
+ *
+ * Code for xterm like mouse click reporting by Peter Orbaek 20-Jul-94
+ * <poe@daimi.aau.dk>
+ *
+ * User-defined bell sound, new setterm control sequences and printk
+ * redirection by Martin Mares <mj@k332.feld.cvut.cz> 19-Nov-95
+ *
+ * APM screenblank bug fixed Takashi Manabe <manabe@roy.dsl.tutics.tut.jp>
+ *
+ * Merge with the abstract console driver by Geert Uytterhoeven
+ * <geert@linux-m68k.org>, Jan 1997.
+ *
+ * Original m68k console driver modifications by
+ *
+ * - Arno Griffioen <arno@usn.nl>
+ * - David Carter <carter@cs.bris.ac.uk>
+ *
+ * The abstract console driver provides a generic interface for a text
+ * console. It supports VGA text mode, frame buffer based graphical consoles
+ * and special graphics processors that are only accessible through some
+ * registers (e.g. a TMS340x0 GSP).
+ *
+ * The interface to the hardware is specified using a special structure
+ * (struct consw) which contains function pointers to console operations
+ * (see <linux/console.h> for more information).
+ *
+ * Support for changeable cursor shape
+ * by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>, August 1997
+ *
+ * Ported to i386 and con_scrolldelta fixed
+ * by Emmanuel Marty <core@ggi-project.org>, April 1998
+ *
+ * Resurrected character buffers in videoram plus lots of other trickery
+ * by Martin Mares <mj@atrey.karlin.mff.cuni.cz>, July 1998
+ *
+ * Removed old-style timers, introduced console_timer, made timer
+ * deletion SMP-safe. 17Jun00, Andrew Morton <andrewm@uow.edu.au>
+ *
+ * Removed console_lock, enabled interrupts across all console operations
+ * 13 March 2001, Andrew Morton
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kd.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/tiocl.h>
+#include <linux/kbd_kern.h>
+#include <linux/consolemap.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/config.h>
+#include <linux/workqueue.h>
+#include <linux/bootmem.h>
+#include <linux/pm.h>
+#include <linux/font.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+
+const struct consw *conswitchp;
+
+/* A bitmap for codes <32. A bit of 1 indicates that the code
+ * corresponding to that bit number invokes some special action
+ * (such as cursor movement) and should not be displayed as a
+ * glyph unless the disp_ctrl mode is explicitly enabled.
+ */
+#define CTRL_ACTION 0x0d00ff81
+#define CTRL_ALWAYS 0x0800f501 /* Cannot be overridden by disp_ctrl */
+
+/*
+ * Here is the default bell parameters: 750HZ, 1/8th of a second
+ */
+#define DEFAULT_BELL_PITCH 750
+#define DEFAULT_BELL_DURATION (HZ/8)
+
+extern void vcs_make_devfs(struct tty_struct *tty);
+extern void vcs_remove_devfs(struct tty_struct *tty);
+
+extern void console_map_init(void);
+#ifdef CONFIG_PROM_CONSOLE
+extern void prom_con_init(void);
+#endif
+#ifdef CONFIG_MDA_CONSOLE
+extern int mda_console_init(void);
+#endif
+
+struct vc vc_cons [MAX_NR_CONSOLES];
+
+#ifndef VT_SINGLE_DRIVER
+static const struct consw *con_driver_map[MAX_NR_CONSOLES];
+#endif
+
+static int con_open(struct tty_struct *, struct file *);
+static void vc_init(struct vc_data *vc, unsigned int rows,
+ unsigned int cols, int do_clear);
+static void gotoxy(struct vc_data *vc, int new_x, int new_y);
+static void save_cur(struct vc_data *vc);
+static void reset_terminal(struct vc_data *vc, int do_clear);
+static void con_flush_chars(struct tty_struct *tty);
+static void set_vesa_blanking(char __user *p);
+static void set_cursor(struct vc_data *vc);
+static void hide_cursor(struct vc_data *vc);
+static void console_callback(void *ignored);
+static void blank_screen_t(unsigned long dummy);
+static void set_palette(struct vc_data *vc);
+
+static int printable; /* Is console ready for printing? */
+
+/*
+ * ignore_poke: don't unblank the screen when things are typed. This is
+ * mainly for the privacy of braille terminal users.
+ */
+static int ignore_poke;
+
+int do_poke_blanked_console;
+int console_blanked;
+
+static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */
+static int blankinterval = 10*60*HZ;
+static int vesa_off_interval;
+
+static DECLARE_WORK(console_work, console_callback, NULL);
+
+/*
+ * fg_console is the current virtual console,
+ * last_console is the last used one,
+ * want_console is the console we want to switch to,
+ * kmsg_redirect is the console for kernel messages,
+ */
+int fg_console;
+int last_console;
+int want_console = -1;
+int kmsg_redirect;
+
+/*
+ * For each existing display, we have a pointer to console currently visible
+ * on that display, allowing consoles other than fg_console to be refreshed
+ * appropriately. Unless the low-level driver supplies its own display_fg
+ * variable, we use this one for the "master display".
+ */
+static struct vc_data *master_display_fg;
+
+/*
+ * Unfortunately, we need to delay tty echo when we're currently writing to the
+ * console since the code is (and always was) not re-entrant, so we schedule
+ * all flip requests to process context with schedule-task() and run it from
+ * console_callback().
+ */
+
+/*
+ * For the same reason, we defer scrollback to the console callback.
+ */
+static int scrollback_delta;
+
+/*
+ * Hook so that the power management routines can (un)blank
+ * the console on our behalf.
+ */
+int (*console_blank_hook)(int);
+
+static struct timer_list console_timer;
+static int blank_state;
+static int blank_timer_expired;
+enum {
+ blank_off = 0,
+ blank_normal_wait,
+ blank_vesa_wait,
+};
+
+/*
+ * Low-Level Functions
+ */
+
+#define IS_FG(vc) ((vc)->vc_num == fg_console)
+
+#ifdef VT_BUF_VRAM_ONLY
+#define DO_UPDATE(vc) 0
+#else
+#define DO_UPDATE(vc) CON_IS_VISIBLE(vc)
+#endif
+
+static inline unsigned short *screenpos(struct vc_data *vc, int offset, int viewed)
+{
+ unsigned short *p;
+
+ if (!viewed)
+ p = (unsigned short *)(vc->vc_origin + offset);
+ else if (!vc->vc_sw->con_screen_pos)
+ p = (unsigned short *)(vc->vc_visible_origin + offset);
+ else
+ p = vc->vc_sw->con_screen_pos(vc, offset);
+ return p;
+}
+
+static inline void scrolldelta(int lines)
+{
+ scrollback_delta += lines;
+ schedule_console_callback();
+}
+
+void schedule_console_callback(void)
+{
+ schedule_work(&console_work);
+}
+
+static void scrup(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+{
+ unsigned short *d, *s;
+
+ if (t+nr >= b)
+ nr = b - t - 1;
+ if (b > vc->vc_rows || t >= b || nr < 1)
+ return;
+ if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_UP, nr))
+ return;
+ d = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
+ s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * (t + nr));
+ scr_memmovew(d, s, (b - t - nr) * vc->vc_size_row);
+ scr_memsetw(d + (b - t - nr) * vc->vc_cols, vc->vc_video_erase_char,
+ vc->vc_size_row * nr);
+}
+
+static void scrdown(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+{
+ unsigned short *s;
+ unsigned int step;
+
+ if (t+nr >= b)
+ nr = b - t - 1;
+ if (b > vc->vc_rows || t >= b || nr < 1)
+ return;
+ if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_DOWN, nr))
+ return;
+ s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
+ step = vc->vc_cols * nr;
+ scr_memmovew(s + step, s, (b - t - nr) * vc->vc_size_row);
+ scr_memsetw(s, vc->vc_video_erase_char, 2 * step);
+}
+
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+#ifndef VT_BUF_VRAM_ONLY
+ unsigned int xx, yy, offset;
+ u16 *p;
+
+ p = (u16 *) start;
+ if (!vc->vc_sw->con_getxy) {
+ offset = (start - vc->vc_origin) / 2;
+ xx = offset % vc->vc_cols;
+ yy = offset / vc->vc_cols;
+ } else {
+ int nxx, nyy;
+ start = vc->vc_sw->con_getxy(vc, start, &nxx, &nyy);
+ xx = nxx; yy = nyy;
+ }
+ for(;;) {
+ u16 attrib = scr_readw(p) & 0xff00;
+ int startx = xx;
+ u16 *q = p;
+ while (xx < vc->vc_cols && count) {
+ if (attrib != (scr_readw(p) & 0xff00)) {
+ if (p > q)
+ vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+ startx = xx;
+ q = p;
+ attrib = scr_readw(p) & 0xff00;
+ }
+ p++;
+ xx++;
+ count--;
+ }
+ if (p > q)
+ vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+ if (!count)
+ break;
+ xx = 0;
+ yy++;
+ if (vc->vc_sw->con_getxy) {
+ p = (u16 *)start;
+ start = vc->vc_sw->con_getxy(vc, start, NULL, NULL);
+ }
+ }
+#endif
+}
+
+void update_region(struct vc_data *vc, unsigned long start, int count)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (DO_UPDATE(vc)) {
+ hide_cursor(vc);
+ do_update_region(vc, start, count);
+ set_cursor(vc);
+ }
+}
+
+/* Structure of attributes is hardware-dependent */
+
+static u8 build_attr(struct vc_data *vc, u8 _color, u8 _intensity, u8 _blink, u8 _underline, u8 _reverse)
+{
+ if (vc->vc_sw->con_build_attr)
+ return vc->vc_sw->con_build_attr(vc, _color, _intensity, _blink, _underline, _reverse);
+
+#ifndef VT_BUF_VRAM_ONLY
+/*
+ * ++roman: I completely changed the attribute format for monochrome
+ * mode (!can_do_color). The formerly used MDA (monochrome display
+ * adapter) format didn't allow the combination of certain effects.
+ * Now the attribute is just a bit vector:
+ * Bit 0..1: intensity (0..2)
+ * Bit 2 : underline
+ * Bit 3 : reverse
+ * Bit 7 : blink
+ */
+ {
+ u8 a = vc->vc_color;
+ if (!vc->vc_can_do_color)
+ return _intensity |
+ (_underline ? 4 : 0) |
+ (_reverse ? 8 : 0) |
+ (_blink ? 0x80 : 0);
+ if (_underline)
+ a = (a & 0xf0) | vc->vc_ulcolor;
+ else if (_intensity == 0)
+ a = (a & 0xf0) | vc->vc_ulcolor;
+ if (_reverse)
+ a = ((a) & 0x88) | ((((a) >> 4) | ((a) << 4)) & 0x77);
+ if (_blink)
+ a ^= 0x80;
+ if (_intensity == 2)
+ a ^= 0x08;
+ if (vc->vc_hi_font_mask == 0x100)
+ a <<= 1;
+ return a;
+ }
+#else
+ return 0;
+#endif
+}
+
+static void update_attr(struct vc_data *vc)
+{
+ vc->vc_attr = build_attr(vc, vc->vc_color, vc->vc_intensity, vc->vc_blink, vc->vc_underline, vc->vc_reverse ^ vc->vc_decscnm);
+ vc->vc_video_erase_char = (build_attr(vc, vc->vc_color, 1, vc->vc_blink, 0, vc->vc_decscnm) << 8) | ' ';
+}
+
+/* Note: inverting the screen twice should revert to the original state */
+void invert_screen(struct vc_data *vc, int offset, int count, int viewed)
+{
+ unsigned short *p;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ count /= 2;
+ p = screenpos(vc, offset, viewed);
+ if (vc->vc_sw->con_invert_region)
+ vc->vc_sw->con_invert_region(vc, p, count);
+#ifndef VT_BUF_VRAM_ONLY
+ else {
+ u16 *q = p;
+ int cnt = count;
+ u16 a;
+
+ if (!vc->vc_can_do_color) {
+ while (cnt--) {
+ a = scr_readw(q);
+ a ^= 0x0800;
+ scr_writew(a, q);
+ q++;
+ }
+ } else if (vc->vc_hi_font_mask == 0x100) {
+ while (cnt--) {
+ a = scr_readw(q);
+ a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | (((a) & 0x0e00) << 4);
+ scr_writew(a, q);
+ q++;
+ }
+ } else {
+ while (cnt--) {
+ a = scr_readw(q);
+ a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | (((a) & 0x0700) << 4);
+ scr_writew(a, q);
+ q++;
+ }
+ }
+ }
+#endif
+ if (DO_UPDATE(vc))
+ do_update_region(vc, (unsigned long) p, count);
+}
+
+/* used by selection: complement pointer position */
+void complement_pos(struct vc_data *vc, int offset)
+{
+ static unsigned short *p;
+ static unsigned short old;
+ static unsigned short oldx, oldy;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ if (p) {
+ scr_writew(old, p);
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_putc(vc, old, oldy, oldx);
+ }
+ if (offset == -1)
+ p = NULL;
+ else {
+ unsigned short new;
+ p = screenpos(vc, offset, 1);
+ old = scr_readw(p);
+ new = old ^ vc->vc_complement_mask;
+ scr_writew(new, p);
+ if (DO_UPDATE(vc)) {
+ oldx = (offset >> 1) % vc->vc_cols;
+ oldy = (offset >> 1) / vc->vc_cols;
+ vc->vc_sw->con_putc(vc, new, oldy, oldx);
+ }
+ }
+}
+
+static void insert_char(struct vc_data *vc, unsigned int nr)
+{
+ unsigned short *p, *q = (unsigned short *)vc->vc_pos;
+
+ p = q + vc->vc_cols - nr - vc->vc_x;
+ while (--p >= q)
+ scr_writew(scr_readw(p), p + nr);
+ scr_memsetw(q, vc->vc_video_erase_char, nr * 2);
+ vc->vc_need_wrap = 0;
+ if (DO_UPDATE(vc)) {
+ unsigned short oldattr = vc->vc_attr;
+ vc->vc_sw->con_bmove(vc, vc->vc_y, vc->vc_x, vc->vc_y, vc->vc_x + nr, 1,
+ vc->vc_cols - vc->vc_x - nr);
+ vc->vc_attr = vc->vc_video_erase_char >> 8;
+ while (nr--)
+ vc->vc_sw->con_putc(vc, vc->vc_video_erase_char, vc->vc_y, vc->vc_x + nr);
+ vc->vc_attr = oldattr;
+ }
+}
+
+static void delete_char(struct vc_data *vc, unsigned int nr)
+{
+ unsigned int i = vc->vc_x;
+ unsigned short *p = (unsigned short *)vc->vc_pos;
+
+ while (++i <= vc->vc_cols - nr) {
+ scr_writew(scr_readw(p+nr), p);
+ p++;
+ }
+ scr_memsetw(p, vc->vc_video_erase_char, nr * 2);
+ vc->vc_need_wrap = 0;
+ if (DO_UPDATE(vc)) {
+ unsigned short oldattr = vc->vc_attr;
+ vc->vc_sw->con_bmove(vc, vc->vc_y, vc->vc_x + nr, vc->vc_y, vc->vc_x, 1,
+ vc->vc_cols - vc->vc_x - nr);
+ vc->vc_attr = vc->vc_video_erase_char >> 8;
+ while (nr--)
+ vc->vc_sw->con_putc(vc, vc->vc_video_erase_char, vc->vc_y,
+ vc->vc_cols - 1 - nr);
+ vc->vc_attr = oldattr;
+ }
+}
+
+static int softcursor_original;
+
+static void add_softcursor(struct vc_data *vc)
+{
+ int i = scr_readw((u16 *) vc->vc_pos);
+ u32 type = vc->vc_cursor_type;
+
+ if (! (type & 0x10)) return;
+ if (softcursor_original != -1) return;
+ softcursor_original = i;
+ i |= ((type >> 8) & 0xff00 );
+ i ^= ((type) & 0xff00 );
+ if ((type & 0x20) && ((softcursor_original & 0x7000) == (i & 0x7000))) i ^= 0x7000;
+ if ((type & 0x40) && ((i & 0x700) == ((i & 0x7000) >> 4))) i ^= 0x0700;
+ scr_writew(i, (u16 *) vc->vc_pos);
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_putc(vc, i, vc->vc_y, vc->vc_x);
+}
+
+static void hide_softcursor(struct vc_data *vc)
+{
+ if (softcursor_original != -1) {
+ scr_writew(softcursor_original, (u16 *)vc->vc_pos);
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_putc(vc, softcursor_original,
+ vc->vc_y, vc->vc_x);
+ softcursor_original = -1;
+ }
+}
+
+static void hide_cursor(struct vc_data *vc)
+{
+ if (vc == sel_cons)
+ clear_selection();
+ vc->vc_sw->con_cursor(vc, CM_ERASE);
+ hide_softcursor(vc);
+}
+
+static void set_cursor(struct vc_data *vc)
+{
+ if (!IS_FG(vc) || console_blanked ||
+ vc->vc_mode == KD_GRAPHICS)
+ return;
+ if (vc->vc_deccm) {
+ if (vc == sel_cons)
+ clear_selection();
+ add_softcursor(vc);
+ if ((vc->vc_cursor_type & 0x0f) != 1)
+ vc->vc_sw->con_cursor(vc, CM_DRAW);
+ } else
+ hide_cursor(vc);
+}
+
+static void set_origin(struct vc_data *vc)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (!CON_IS_VISIBLE(vc) ||
+ !vc->vc_sw->con_set_origin ||
+ !vc->vc_sw->con_set_origin(vc))
+ vc->vc_origin = (unsigned long)vc->vc_screenbuf;
+ vc->vc_visible_origin = vc->vc_origin;
+ vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
+ vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->vc_y + 2 * vc->vc_x;
+}
+
+static inline void save_screen(struct vc_data *vc)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (vc->vc_sw->con_save_screen)
+ vc->vc_sw->con_save_screen(vc);
+}
+
+/*
+ * Redrawing of screen
+ */
+
+static void clear_buffer_attributes(struct vc_data *vc)
+{
+ unsigned short *p = (unsigned short *)vc->vc_origin;
+ int count = vc->vc_screenbuf_size / 2;
+ int mask = vc->vc_hi_font_mask | 0xff;
+
+ for (; count > 0; count--, p++) {
+ scr_writew((scr_readw(p)&mask) | (vc->vc_video_erase_char & ~mask), p);
+ }
+}
+
+void redraw_screen(struct vc_data *vc, int is_switch)
+{
+ int redraw = 0;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ if (!vc) {
+ /* strange ... */
+ /* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
+ return;
+ }
+
+ if (is_switch) {
+ struct vc_data *old_vc = vc_cons[fg_console].d;
+ if (old_vc == vc)
+ return;
+ if (!CON_IS_VISIBLE(vc))
+ redraw = 1;
+ *vc->vc_display_fg = vc;
+ fg_console = vc->vc_num;
+ hide_cursor(old_vc);
+ if (!CON_IS_VISIBLE(old_vc)) {
+ save_screen(old_vc);
+ set_origin(old_vc);
+ }
+ } else {
+ hide_cursor(vc);
+ redraw = 1;
+ }
+
+ if (redraw) {
+ int update;
+ int old_was_color = vc->vc_can_do_color;
+
+ set_origin(vc);
+ update = vc->vc_sw->con_switch(vc);
+ set_palette(vc);
+ /*
+ * If console changed from mono<->color, the best we can do
+ * is to clear the buffer attributes. As it currently stands,
+ * rebuilding new attributes from the old buffer is not doable
+ * without overly complex code.
+ */
+ if (old_was_color != vc->vc_can_do_color) {
+ update_attr(vc);
+ clear_buffer_attributes(vc);
+ }
+ if (update && vc->vc_mode != KD_GRAPHICS)
+ do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+ }
+ set_cursor(vc);
+ if (is_switch) {
+ set_leds();
+ compute_shiftstate();
+ }
+}
+
+/*
+ * Allocation, freeing and resizing of VTs.
+ */
+
+int vc_cons_allocated(unsigned int i)
+{
+ return (i < MAX_NR_CONSOLES && vc_cons[i].d);
+}
+
+static void visual_init(struct vc_data *vc, int num, int init)
+{
+ /* ++Geert: vc->vc_sw->con_init determines console size */
+ if (vc->vc_sw)
+ module_put(vc->vc_sw->owner);
+ vc->vc_sw = conswitchp;
+#ifndef VT_SINGLE_DRIVER
+ if (con_driver_map[num])
+ vc->vc_sw = con_driver_map[num];
+#endif
+ __module_get(vc->vc_sw->owner);
+ vc->vc_num = num;
+ vc->vc_display_fg = &master_display_fg;
+ vc->vc_uni_pagedir_loc = &vc->vc_uni_pagedir;
+ vc->vc_uni_pagedir = 0;
+ vc->vc_hi_font_mask = 0;
+ vc->vc_complement_mask = 0;
+ vc->vc_can_do_color = 0;
+ vc->vc_sw->con_init(vc, init);
+ if (!vc->vc_complement_mask)
+ vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+ vc->vc_s_complement_mask = vc->vc_complement_mask;
+ vc->vc_size_row = vc->vc_cols << 1;
+ vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
+}
+
+int vc_allocate(unsigned int currcons) /* return 0 on success */
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (currcons >= MAX_NR_CONSOLES)
+ return -ENXIO;
+ if (!vc_cons[currcons].d) {
+ struct vc_data *vc;
+
+ /* prevent users from taking too much memory */
+ if (currcons >= MAX_NR_USER_CONSOLES && !capable(CAP_SYS_RESOURCE))
+ return -EPERM;
+
+ /* due to the granularity of kmalloc, we waste some memory here */
+ /* the alloc is done in two steps, to optimize the common situation
+ of a 25x80 console (structsize=216, screenbuf_size=4000) */
+ /* although the numbers above are not valid since long ago, the
+ point is still up-to-date and the comment still has its value
+ even if only as a historical artifact. --mj, July 1998 */
+ vc = kmalloc(sizeof(struct vc_data), GFP_KERNEL);
+ if (!vc)
+ return -ENOMEM;
+ memset(vc, 0, sizeof(*vc));
+ vc_cons[currcons].d = vc;
+ visual_init(vc, currcons, 1);
+ if (!*vc->vc_uni_pagedir_loc)
+ con_set_default_unimap(vc);
+ vc->vc_screenbuf = kmalloc(vc->vc_screenbuf_size, GFP_KERNEL);
+ if (!vc->vc_screenbuf) {
+ kfree(vc);
+ vc_cons[currcons].d = NULL;
+ return -ENOMEM;
+ }
+ vc->vc_kmalloced = 1;
+ vc_init(vc, vc->vc_rows, vc->vc_cols, 1);
+ }
+ return 0;
+}
+
+static inline int resize_screen(struct vc_data *vc, int width, int height)
+{
+ /* Resizes the resolution of the display adapater */
+ int err = 0;
+
+ if (vc->vc_mode != KD_GRAPHICS && vc->vc_sw->con_resize)
+ err = vc->vc_sw->con_resize(vc, width, height);
+ return err;
+}
+
+/*
+ * Change # of rows and columns (0 means unchanged/the size of fg_console)
+ * [this is to be used together with some user program
+ * like resize that changes the hardware videomode]
+ */
+#define VC_RESIZE_MAXCOL (32767)
+#define VC_RESIZE_MAXROW (32767)
+int vc_resize(struct vc_data *vc, unsigned int cols, unsigned int lines)
+{
+ unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
+ unsigned int old_cols, old_rows, old_row_size, old_screen_size;
+ unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+ unsigned short *newscreen;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ if (!vc)
+ return -ENXIO;
+
+ if (cols > VC_RESIZE_MAXCOL || lines > VC_RESIZE_MAXROW)
+ return -EINVAL;
+
+ new_cols = (cols ? cols : vc->vc_cols);
+ new_rows = (lines ? lines : vc->vc_rows);
+ new_row_size = new_cols << 1;
+ new_screen_size = new_row_size * new_rows;
+
+ if (new_cols == vc->vc_cols && new_rows == vc->vc_rows)
+ return 0;
+
+ newscreen = (unsigned short *) kmalloc(new_screen_size, GFP_USER);
+ if (!newscreen)
+ return -ENOMEM;
+
+ old_rows = vc->vc_rows;
+ old_cols = vc->vc_cols;
+ old_row_size = vc->vc_size_row;
+ old_screen_size = vc->vc_screenbuf_size;
+
+ err = resize_screen(vc, new_cols, new_rows);
+ if (err) {
+ kfree(newscreen);
+ return err;
+ }
+
+ vc->vc_rows = new_rows;
+ vc->vc_cols = new_cols;
+ vc->vc_size_row = new_row_size;
+ vc->vc_screenbuf_size = new_screen_size;
+
+ rlth = min(old_row_size, new_row_size);
+ rrem = new_row_size - rlth;
+ old_origin = vc->vc_origin;
+ new_origin = (long) newscreen;
+ new_scr_end = new_origin + new_screen_size;
+ if (new_rows < old_rows)
+ old_origin += (old_rows - new_rows) * old_row_size;
+
+ update_attr(vc);
+
+ while (old_origin < vc->vc_scr_end) {
+ scr_memcpyw((unsigned short *) new_origin, (unsigned short *) old_origin, rlth);
+ if (rrem)
+ scr_memsetw((void *)(new_origin + rlth), vc->vc_video_erase_char, rrem);
+ old_origin += old_row_size;
+ new_origin += new_row_size;
+ }
+ if (new_scr_end > new_origin)
+ scr_memsetw((void *)new_origin, vc->vc_video_erase_char, new_scr_end - new_origin);
+ if (vc->vc_kmalloced)
+ kfree(vc->vc_screenbuf);
+ vc->vc_screenbuf = newscreen;
+ vc->vc_kmalloced = 1;
+ vc->vc_screenbuf_size = new_screen_size;
+ set_origin(vc);
+
+ /* do part of a reset_terminal() */
+ vc->vc_top = 0;
+ vc->vc_bottom = vc->vc_rows;
+ gotoxy(vc, vc->vc_x, vc->vc_y);
+ save_cur(vc);
+
+ if (vc->vc_tty) {
+ struct winsize ws, *cws = &vc->vc_tty->winsize;
+
+ memset(&ws, 0, sizeof(ws));
+ ws.ws_row = vc->vc_rows;
+ ws.ws_col = vc->vc_cols;
+ ws.ws_ypixel = vc->vc_scan_lines;
+ if ((ws.ws_row != cws->ws_row || ws.ws_col != cws->ws_col) &&
+ vc->vc_tty->pgrp > 0)
+ kill_pg(vc->vc_tty->pgrp, SIGWINCH, 1);
+ *cws = ws;
+ }
+
+ if (CON_IS_VISIBLE(vc))
+ update_screen(vc);
+ return err;
+}
+
+
+void vc_disallocate(unsigned int currcons)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (vc_cons_allocated(currcons)) {
+ struct vc_data *vc = vc_cons[currcons].d;
+ vc->vc_sw->con_deinit(vc);
+ if (vc->vc_kmalloced)
+ kfree(vc->vc_screenbuf);
+ if (currcons >= MIN_NR_CONSOLES)
+ kfree(vc);
+ vc_cons[currcons].d = NULL;
+ }
+}
+
+/*
+ * VT102 emulator
+ */
+
+#define set_kbd(vc, x) set_vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+#define clr_kbd(vc, x) clr_vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+#define is_kbd(vc, x) vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+
+#define decarm VC_REPEAT
+#define decckm VC_CKMODE
+#define kbdapplic VC_APPLIC
+#define lnm VC_CRLF
+
+/*
+ * this is what the terminal answers to a ESC-Z or csi0c query.
+ */
+#define VT100ID "\033[?1;2c"
+#define VT102ID "\033[?6c"
+
+unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
+ 8,12,10,14, 9,13,11,15 };
+
+/* the default colour table, for VGA+ colour systems */
+int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
+ 0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
+int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
+ 0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
+int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
+ 0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
+
+/*
+ * gotoxy() must verify all boundaries, because the arguments
+ * might also be negative. If the given position is out of
+ * bounds, the cursor is placed at the nearest margin.
+ */
+static void gotoxy(struct vc_data *vc, int new_x, int new_y)
+{
+ int min_y, max_y;
+
+ if (new_x < 0)
+ vc->vc_x = 0;
+ else {
+ if (new_x >= vc->vc_cols)
+ vc->vc_x = vc->vc_cols - 1;
+ else
+ vc->vc_x = new_x;
+ }
+
+ if (vc->vc_decom) {
+ min_y = vc->vc_top;
+ max_y = vc->vc_bottom;
+ } else {
+ min_y = 0;
+ max_y = vc->vc_rows;
+ }
+ if (new_y < min_y)
+ vc->vc_y = min_y;
+ else if (new_y >= max_y)
+ vc->vc_y = max_y - 1;
+ else
+ vc->vc_y = new_y;
+ vc->vc_pos = vc->vc_origin + vc->vc_y * vc->vc_size_row + (vc->vc_x<<1);
+ vc->vc_need_wrap = 0;
+}
+
+/* for absolute user moves, when decom is set */
+static void gotoxay(struct vc_data *vc, int new_x, int new_y)
+{
+ gotoxy(vc, new_x, vc->vc_decom ? (vc->vc_top + new_y) : new_y);
+}
+
+void scrollback(struct vc_data *vc, int lines)
+{
+ if (!lines)
+ lines = vc->vc_rows / 2;
+ scrolldelta(-lines);
+}
+
+void scrollfront(struct vc_data *vc, int lines)
+{
+ if (!lines)
+ lines = vc->vc_rows / 2;
+ scrolldelta(lines);
+}
+
+static void lf(struct vc_data *vc)
+{
+ /* don't scroll if above bottom of scrolling region, or
+ * if below scrolling region
+ */
+ if (vc->vc_y + 1 == vc->vc_bottom)
+ scrup(vc, vc->vc_top, vc->vc_bottom, 1);
+ else if (vc->vc_y < vc->vc_rows - 1) {
+ vc->vc_y++;
+ vc->vc_pos += vc->vc_size_row;
+ }
+ vc->vc_need_wrap = 0;
+}
+
+static void ri(struct vc_data *vc)
+{
+ /* don't scroll if below top of scrolling region, or
+ * if above scrolling region
+ */
+ if (vc->vc_y == vc->vc_top)
+ scrdown(vc, vc->vc_top, vc->vc_bottom, 1);
+ else if (vc->vc_y > 0) {
+ vc->vc_y--;
+ vc->vc_pos -= vc->vc_size_row;
+ }
+ vc->vc_need_wrap = 0;
+}
+
+static inline void cr(struct vc_data *vc)
+{
+ vc->vc_pos -= vc->vc_x << 1;
+ vc->vc_need_wrap = vc->vc_x = 0;
+}
+
+static inline void bs(struct vc_data *vc)
+{
+ if (vc->vc_x) {
+ vc->vc_pos -= 2;
+ vc->vc_x--;
+ vc->vc_need_wrap = 0;
+ }
+}
+
+static inline void del(struct vc_data *vc)
+{
+ /* ignored */
+}
+
+static void csi_J(struct vc_data *vc, int vpar)
+{
+ unsigned int count;
+ unsigned short * start;
+
+ switch (vpar) {
+ case 0: /* erase from cursor to end of display */
+ count = (vc->vc_scr_end - vc->vc_pos) >> 1;
+ start = (unsigned short *)vc->vc_pos;
+ if (DO_UPDATE(vc)) {
+ /* do in two stages */
+ vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1,
+ vc->vc_cols - vc->vc_x);
+ vc->vc_sw->con_clear(vc, vc->vc_y + 1, 0,
+ vc->vc_rows - vc->vc_y - 1,
+ vc->vc_cols);
+ }
+ break;
+ case 1: /* erase from start to cursor */
+ count = ((vc->vc_pos - vc->vc_origin) >> 1) + 1;
+ start = (unsigned short *)vc->vc_origin;
+ if (DO_UPDATE(vc)) {
+ /* do in two stages */
+ vc->vc_sw->con_clear(vc, 0, 0, vc->vc_y,
+ vc->vc_cols);
+ vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+ vc->vc_x + 1);
+ }
+ break;
+ case 2: /* erase whole display */
+ count = vc->vc_cols * vc->vc_rows;
+ start = (unsigned short *)vc->vc_origin;
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_clear(vc, 0, 0,
+ vc->vc_rows,
+ vc->vc_cols);
+ break;
+ default:
+ return;
+ }
+ scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+ vc->vc_need_wrap = 0;
+}
+
+static void csi_K(struct vc_data *vc, int vpar)
+{
+ unsigned int count;
+ unsigned short * start;
+
+ switch (vpar) {
+ case 0: /* erase from cursor to end of line */
+ count = vc->vc_cols - vc->vc_x;
+ start = (unsigned short *)vc->vc_pos;
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1,
+ vc->vc_cols - vc->vc_x);
+ break;
+ case 1: /* erase from start of line to cursor */
+ start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
+ count = vc->vc_x + 1;
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+ vc->vc_x + 1);
+ break;
+ case 2: /* erase whole line */
+ start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
+ count = vc->vc_cols;
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+ vc->vc_cols);
+ break;
+ default:
+ return;
+ }
+ scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+ vc->vc_need_wrap = 0;
+}
+
+static void csi_X(struct vc_data *vc, int vpar) /* erase the following vpar positions */
+{ /* not vt100? */
+ int count;
+
+ if (!vpar)
+ vpar++;
+ count = (vpar > vc->vc_cols - vc->vc_x) ? (vc->vc_cols - vc->vc_x) : vpar;
+
+ scr_memsetw((unsigned short *)vc->vc_pos, vc->vc_video_erase_char, 2 * count);
+ if (DO_UPDATE(vc))
+ vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1, count);
+ vc->vc_need_wrap = 0;
+}
+
+static void default_attr(struct vc_data *vc)
+{
+ vc->vc_intensity = 1;
+ vc->vc_underline = 0;
+ vc->vc_reverse = 0;
+ vc->vc_blink = 0;
+ vc->vc_color = vc->vc_def_color;
+}
+
+/* console_sem is held */
+static void csi_m(struct vc_data *vc)
+{
+ int i;
+
+ for (i = 0; i <= vc->vc_npar; i++)
+ switch (vc->vc_par[i]) {
+ case 0: /* all attributes off */
+ default_attr(vc);
+ break;
+ case 1:
+ vc->vc_intensity = 2;
+ break;
+ case 2:
+ vc->vc_intensity = 0;
+ break;
+ case 4:
+ vc->vc_underline = 1;
+ break;
+ case 5:
+ vc->vc_blink = 1;
+ break;
+ case 7:
+ vc->vc_reverse = 1;
+ break;
+ case 10: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select primary font, don't display
+ * control chars if defined, don't set
+ * bit 8 on output.
+ */
+ vc->vc_translate = set_translate(vc->vc_charset == 0
+ ? vc->vc_G0_charset
+ : vc->vc_G1_charset, vc);
+ vc->vc_disp_ctrl = 0;
+ vc->vc_toggle_meta = 0;
+ break;
+ case 11: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select first alternate font, lets
+ * chars < 32 be displayed as ROM chars.
+ */
+ vc->vc_translate = set_translate(IBMPC_MAP, vc);
+ vc->vc_disp_ctrl = 1;
+ vc->vc_toggle_meta = 0;
+ break;
+ case 12: /* ANSI X3.64-1979 (SCO-ish?)
+ * Select second alternate font, toggle
+ * high bit before displaying as ROM char.
+ */
+ vc->vc_translate = set_translate(IBMPC_MAP, vc);
+ vc->vc_disp_ctrl = 1;
+ vc->vc_toggle_meta = 1;
+ break;
+ case 21:
+ case 22:
+ vc->vc_intensity = 1;
+ break;
+ case 24:
+ vc->vc_underline = 0;
+ break;
+ case 25:
+ vc->vc_blink = 0;
+ break;
+ case 27:
+ vc->vc_reverse = 0;
+ break;
+ case 38: /* ANSI X3.64-1979 (SCO-ish?)
+ * Enables underscore, white foreground
+ * with white underscore (Linux - use
+ * default foreground).
+ */
+ vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
+ vc->vc_underline = 1;
+ break;
+ case 39: /* ANSI X3.64-1979 (SCO-ish?)
+ * Disable underline option.
+ * Reset colour to default? It did this
+ * before...
+ */
+ vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
+ vc->vc_underline = 0;
+ break;
+ case 49:
+ vc->vc_color = (vc->vc_def_color & 0xf0) | (vc->vc_color & 0x0f);
+ break;
+ default:
+ if (vc->vc_par[i] >= 30 && vc->vc_par[i] <= 37)
+ vc->vc_color = color_table[vc->vc_par[i] - 30]
+ | (vc->vc_color & 0xf0);
+ else if (vc->vc_par[i] >= 40 && vc->vc_par[i] <= 47)
+ vc->vc_color = (color_table[vc->vc_par[i] - 40] << 4)
+ | (vc->vc_color & 0x0f);
+ break;
+ }
+ update_attr(vc);
+}
+
+static void respond_string(const char *p, struct tty_struct *tty)
+{
+ while (*p) {
+ tty_insert_flip_char(tty, *p, 0);
+ p++;
+ }
+ con_schedule_flip(tty);
+}
+
+static void cursor_report(struct vc_data *vc, struct tty_struct *tty)
+{
+ char buf[40];
+
+ sprintf(buf, "\033[%d;%dR", vc->vc_y + (vc->vc_decom ? vc->vc_top + 1 : 1), vc->vc_x + 1);
+ respond_string(buf, tty);
+}
+
+static inline void status_report(struct tty_struct *tty)
+{
+ respond_string("\033[0n", tty); /* Terminal ok */
+}
+
+static inline void respond_ID(struct tty_struct * tty)
+{
+ respond_string(VT102ID, tty);
+}
+
+void mouse_report(struct tty_struct *tty, int butt, int mrx, int mry)
+{
+ char buf[8];
+
+ sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
+ (char)('!' + mry));
+ respond_string(buf, tty);
+}
+
+/* invoked via ioctl(TIOCLINUX) and through set_selection */
+int mouse_reporting(void)
+{
+ return vc_cons[fg_console].d->vc_report_mouse;
+}
+
+/* console_sem is held */
+static void set_mode(struct vc_data *vc, int on_off)
+{
+ int i;
+
+ for (i = 0; i <= vc->vc_npar; i++)
+ if (vc->vc_ques) {
+ switch(vc->vc_par[i]) { /* DEC private modes set/reset */
+ case 1: /* Cursor keys send ^[Ox/^[[x */
+ if (on_off)
+ set_kbd(vc, decckm);
+ else
+ clr_kbd(vc, decckm);
+ break;
+ case 3: /* 80/132 mode switch unimplemented */
+ vc->vc_deccolm = on_off;
+#if 0
+ vc_resize(deccolm ? 132 : 80, vc->vc_rows);
+ /* this alone does not suffice; some user mode
+ utility has to change the hardware regs */
+#endif
+ break;
+ case 5: /* Inverted screen on/off */
+ if (vc->vc_decscnm != on_off) {
+ vc->vc_decscnm = on_off;
+ invert_screen(vc, 0, vc->vc_screenbuf_size, 0);
+ update_attr(vc);
+ }
+ break;
+ case 6: /* Origin relative/absolute */
+ vc->vc_decom = on_off;
+ gotoxay(vc, 0, 0);
+ break;
+ case 7: /* Autowrap on/off */
+ vc->vc_decawm = on_off;
+ break;
+ case 8: /* Autorepeat on/off */
+ if (on_off)
+ set_kbd(vc, decarm);
+ else
+ clr_kbd(vc, decarm);
+ break;
+ case 9:
+ vc->vc_report_mouse = on_off ? 1 : 0;
+ break;
+ case 25: /* Cursor on/off */
+ vc->vc_deccm = on_off;
+ break;
+ case 1000:
+ vc->vc_report_mouse = on_off ? 2 : 0;
+ break;
+ }
+ } else {
+ switch(vc->vc_par[i]) { /* ANSI modes set/reset */
+ case 3: /* Monitor (display ctrls) */
+ vc->vc_disp_ctrl = on_off;
+ break;
+ case 4: /* Insert Mode on/off */
+ vc->vc_decim = on_off;
+ break;
+ case 20: /* Lf, Enter == CrLf/Lf */
+ if (on_off)
+ set_kbd(vc, lnm);
+ else
+ clr_kbd(vc, lnm);
+ break;
+ }
+ }
+}
+
+/* console_sem is held */
+static void setterm_command(struct vc_data *vc)
+{
+ switch(vc->vc_par[0]) {
+ case 1: /* set color for underline mode */
+ if (vc->vc_can_do_color &&
+ vc->vc_par[1] < 16) {
+ vc->vc_ulcolor = color_table[vc->vc_par[1]];
+ if (vc->vc_underline)
+ update_attr(vc);
+ }
+ break;
+ case 2: /* set color for half intensity mode */
+ if (vc->vc_can_do_color &&
+ vc->vc_par[1] < 16) {
+ vc->vc_halfcolor = color_table[vc->vc_par[1]];
+ if (vc->vc_intensity == 0)
+ update_attr(vc);
+ }
+ break;
+ case 8: /* store colors as defaults */
+ vc->vc_def_color = vc->vc_attr;
+ if (vc->vc_hi_font_mask == 0x100)
+ vc->vc_def_color >>= 1;
+ default_attr(vc);
+ update_attr(vc);
+ break;
+ case 9: /* set blanking interval */
+ blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+ poke_blanked_console();
+ break;
+ case 10: /* set bell frequency in Hz */
+ if (vc->vc_npar >= 1)
+ vc->vc_bell_pitch = vc->vc_par[1];
+ else
+ vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+ break;
+ case 11: /* set bell duration in msec */
+ if (vc->vc_npar >= 1)
+ vc->vc_bell_duration = (vc->vc_par[1] < 2000) ?
+ vc->vc_par[1] * HZ / 1000 : 0;
+ else
+ vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+ break;
+ case 12: /* bring specified console to the front */
+ if (vc->vc_par[1] >= 1 && vc_cons_allocated(vc->vc_par[1] - 1))
+ set_console(vc->vc_par[1] - 1);
+ break;
+ case 13: /* unblank the screen */
+ poke_blanked_console();
+ break;
+ case 14: /* set vesa powerdown interval */
+ vesa_off_interval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+ break;
+ case 15: /* activate the previous console */
+ set_console(last_console);
+ break;
+ }
+}
+
+/* console_sem is held */
+static void csi_at(struct vc_data *vc, unsigned int nr)
+{
+ if (nr > vc->vc_cols - vc->vc_x)
+ nr = vc->vc_cols - vc->vc_x;
+ else if (!nr)
+ nr = 1;
+ insert_char(vc, nr);
+}
+
+/* console_sem is held */
+static void csi_L(struct vc_data *vc, unsigned int nr)
+{
+ if (nr > vc->vc_rows - vc->vc_y)
+ nr = vc->vc_rows - vc->vc_y;
+ else if (!nr)
+ nr = 1;
+ scrdown(vc, vc->vc_y, vc->vc_bottom, nr);
+ vc->vc_need_wrap = 0;
+}
+
+/* console_sem is held */
+static void csi_P(struct vc_data *vc, unsigned int nr)
+{
+ if (nr > vc->vc_cols - vc->vc_x)
+ nr = vc->vc_cols - vc->vc_x;
+ else if (!nr)
+ nr = 1;
+ delete_char(vc, nr);
+}
+
+/* console_sem is held */
+static void csi_M(struct vc_data *vc, unsigned int nr)
+{
+ if (nr > vc->vc_rows - vc->vc_y)
+ nr = vc->vc_rows - vc->vc_y;
+ else if (!nr)
+ nr=1;
+ scrup(vc, vc->vc_y, vc->vc_bottom, nr);
+ vc->vc_need_wrap = 0;
+}
+
+/* console_sem is held (except via vc_init->reset_terminal */
+static void save_cur(struct vc_data *vc)
+{
+ vc->vc_saved_x = vc->vc_x;
+ vc->vc_saved_y = vc->vc_y;
+ vc->vc_s_intensity = vc->vc_intensity;
+ vc->vc_s_underline = vc->vc_underline;
+ vc->vc_s_blink = vc->vc_blink;
+ vc->vc_s_reverse = vc->vc_reverse;
+ vc->vc_s_charset = vc->vc_charset;
+ vc->vc_s_color = vc->vc_color;
+ vc->vc_saved_G0 = vc->vc_G0_charset;
+ vc->vc_saved_G1 = vc->vc_G1_charset;
+}
+
+/* console_sem is held */
+static void restore_cur(struct vc_data *vc)
+{
+ gotoxy(vc, vc->vc_saved_x, vc->vc_saved_y);
+ vc->vc_intensity = vc->vc_s_intensity;
+ vc->vc_underline = vc->vc_s_underline;
+ vc->vc_blink = vc->vc_s_blink;
+ vc->vc_reverse = vc->vc_s_reverse;
+ vc->vc_charset = vc->vc_s_charset;
+ vc->vc_color = vc->vc_s_color;
+ vc->vc_G0_charset = vc->vc_saved_G0;
+ vc->vc_G1_charset = vc->vc_saved_G1;
+ vc->vc_translate = set_translate(vc->vc_charset ? vc->vc_G1_charset : vc->vc_G0_charset, vc);
+ update_attr(vc);
+ vc->vc_need_wrap = 0;
+}
+
+enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
+ EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
+ ESpalette };
+
+/* console_sem is held (except via vc_init()) */
+static void reset_terminal(struct vc_data *vc, int do_clear)
+{
+ vc->vc_top = 0;
+ vc->vc_bottom = vc->vc_rows;
+ vc->vc_state = ESnormal;
+ vc->vc_ques = 0;
+ vc->vc_translate = set_translate(LAT1_MAP, vc);
+ vc->vc_G0_charset = LAT1_MAP;
+ vc->vc_G1_charset = GRAF_MAP;
+ vc->vc_charset = 0;
+ vc->vc_need_wrap = 0;
+ vc->vc_report_mouse = 0;
+ vc->vc_utf = 0;
+ vc->vc_utf_count = 0;
+
+ vc->vc_disp_ctrl = 0;
+ vc->vc_toggle_meta = 0;
+
+ vc->vc_decscnm = 0;
+ vc->vc_decom = 0;
+ vc->vc_decawm = 1;
+ vc->vc_deccm = 1;
+ vc->vc_decim = 0;
+
+ set_kbd(vc, decarm);
+ clr_kbd(vc, decckm);
+ clr_kbd(vc, kbdapplic);
+ clr_kbd(vc, lnm);
+ kbd_table[vc->vc_num].lockstate = 0;
+ kbd_table[vc->vc_num].slockstate = 0;
+ kbd_table[vc->vc_num].ledmode = LED_SHOW_FLAGS;
+ kbd_table[vc->vc_num].ledflagstate = kbd_table[vc->vc_num].default_ledflagstate;
+ /* do not do set_leds here because this causes an endless tasklet loop
+ when the keyboard hasn't been initialized yet */
+
+ vc->vc_cursor_type = CUR_DEFAULT;
+ vc->vc_complement_mask = vc->vc_s_complement_mask;
+
+ default_attr(vc);
+ update_attr(vc);
+
+ vc->vc_tab_stop[0] = 0x01010100;
+ vc->vc_tab_stop[1] =
+ vc->vc_tab_stop[2] =
+ vc->vc_tab_stop[3] =
+ vc->vc_tab_stop[4] = 0x01010101;
+
+ vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+ vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+
+ gotoxy(vc, 0, 0);
+ save_cur(vc);
+ if (do_clear)
+ csi_J(vc, 2);
+}
+
+/* console_sem is held */
+static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
+{
+ /*
+ * Control characters can be used in the _middle_
+ * of an escape sequence.
+ */
+ switch (c) {
+ case 0:
+ return;
+ case 7:
+ if (vc->vc_bell_duration)
+ kd_mksound(vc->vc_bell_pitch, vc->vc_bell_duration);
+ return;
+ case 8:
+ bs(vc);
+ return;
+ case 9:
+ vc->vc_pos -= (vc->vc_x << 1);
+ while (vc->vc_x < vc->vc_cols - 1) {
+ vc->vc_x++;
+ if (vc->vc_tab_stop[vc->vc_x >> 5] & (1 << (vc->vc_x & 31)))
+ break;
+ }
+ vc->vc_pos += (vc->vc_x << 1);
+ return;
+ case 10: case 11: case 12:
+ lf(vc);
+ if (!is_kbd(vc, lnm))
+ return;
+ case 13:
+ cr(vc);
+ return;
+ case 14:
+ vc->vc_charset = 1;
+ vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+ vc->vc_disp_ctrl = 1;
+ return;
+ case 15:
+ vc->vc_charset = 0;
+ vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+ vc->vc_disp_ctrl = 0;
+ return;
+ case 24: case 26:
+ vc->vc_state = ESnormal;
+ return;
+ case 27:
+ vc->vc_state = ESesc;
+ return;
+ case 127:
+ del(vc);
+ return;
+ case 128+27:
+ vc->vc_state = ESsquare;
+ return;
+ }
+ switch(vc->vc_state) {
+ case ESesc:
+ vc->vc_state = ESnormal;
+ switch (c) {
+ case '[':
+ vc->vc_state = ESsquare;
+ return;
+ case ']':
+ vc->vc_state = ESnonstd;
+ return;
+ case '%':
+ vc->vc_state = ESpercent;
+ return;
+ case 'E':
+ cr(vc);
+ lf(vc);
+ return;
+ case 'M':
+ ri(vc);
+ return;
+ case 'D':
+ lf(vc);
+ return;
+ case 'H':
+ vc->vc_tab_stop[vc->vc_x >> 5] |= (1 << (vc->vc_x & 31));
+ return;
+ case 'Z':
+ respond_ID(tty);
+ return;
+ case '7':
+ save_cur(vc);
+ return;
+ case '8':
+ restore_cur(vc);
+ return;
+ case '(':
+ vc->vc_state = ESsetG0;
+ return;
+ case ')':
+ vc->vc_state = ESsetG1;
+ return;
+ case '#':
+ vc->vc_state = EShash;
+ return;
+ case 'c':
+ reset_terminal(vc, 1);
+ return;
+ case '>': /* Numeric keypad */
+ clr_kbd(vc, kbdapplic);
+ return;
+ case '=': /* Appl. keypad */
+ set_kbd(vc, kbdapplic);
+ return;
+ }
+ return;
+ case ESnonstd:
+ if (c=='P') { /* palette escape sequence */
+ for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
+ vc->vc_par[vc->vc_npar] = 0;
+ vc->vc_npar = 0;
+ vc->vc_state = ESpalette;
+ return;
+ } else if (c=='R') { /* reset palette */
+ reset_palette(vc);
+ vc->vc_state = ESnormal;
+ } else
+ vc->vc_state = ESnormal;
+ return;
+ case ESpalette:
+ if ( (c>='0'&&c<='9') || (c>='A'&&c<='F') || (c>='a'&&c<='f') ) {
+ vc->vc_par[vc->vc_npar++] = (c > '9' ? (c & 0xDF) - 'A' + 10 : c - '0');
+ if (vc->vc_npar == 7) {
+ int i = vc->vc_par[0] * 3, j = 1;
+ vc->vc_palette[i] = 16 * vc->vc_par[j++];
+ vc->vc_palette[i++] += vc->vc_par[j++];
+ vc->vc_palette[i] = 16 * vc->vc_par[j++];
+ vc->vc_palette[i++] += vc->vc_par[j++];
+ vc->vc_palette[i] = 16 * vc->vc_par[j++];
+ vc->vc_palette[i] += vc->vc_par[j];
+ set_palette(vc);
+ vc->vc_state = ESnormal;
+ }
+ } else
+ vc->vc_state = ESnormal;
+ return;
+ case ESsquare:
+ for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
+ vc->vc_par[vc->vc_npar] = 0;
+ vc->vc_npar = 0;
+ vc->vc_state = ESgetpars;
+ if (c == '[') { /* Function key */
+ vc->vc_state=ESfunckey;
+ return;
+ }
+ vc->vc_ques = (c == '?');
+ if (vc->vc_ques)
+ return;
+ case ESgetpars:
+ if (c == ';' && vc->vc_npar < NPAR - 1) {
+ vc->vc_npar++;
+ return;
+ } else if (c>='0' && c<='9') {
+ vc->vc_par[vc->vc_npar] *= 10;
+ vc->vc_par[vc->vc_npar] += c - '0';
+ return;
+ } else
+ vc->vc_state = ESgotpars;
+ case ESgotpars:
+ vc->vc_state = ESnormal;
+ switch(c) {
+ case 'h':
+ set_mode(vc, 1);
+ return;
+ case 'l':
+ set_mode(vc, 0);
+ return;
+ case 'c':
+ if (vc->vc_ques) {
+ if (vc->vc_par[0])
+ vc->vc_cursor_type = vc->vc_par[0] | (vc->vc_par[1] << 8) | (vc->vc_par[2] << 16);
+ else
+ vc->vc_cursor_type = CUR_DEFAULT;
+ return;
+ }
+ break;
+ case 'm':
+ if (vc->vc_ques) {
+ clear_selection();
+ if (vc->vc_par[0])
+ vc->vc_complement_mask = vc->vc_par[0] << 8 | vc->vc_par[1];
+ else
+ vc->vc_complement_mask = vc->vc_s_complement_mask;
+ return;
+ }
+ break;
+ case 'n':
+ if (!vc->vc_ques) {
+ if (vc->vc_par[0] == 5)
+ status_report(tty);
+ else if (vc->vc_par[0] == 6)
+ cursor_report(vc, tty);
+ }
+ return;
+ }
+ if (vc->vc_ques) {
+ vc->vc_ques = 0;
+ return;
+ }
+ switch(c) {
+ case 'G': case '`':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ gotoxy(vc, vc->vc_par[0], vc->vc_y);
+ return;
+ case 'A':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->vc_x, vc->vc_y - vc->vc_par[0]);
+ return;
+ case 'B': case 'e':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->vc_x, vc->vc_y + vc->vc_par[0]);
+ return;
+ case 'C': case 'a':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->vc_x + vc->vc_par[0], vc->vc_y);
+ return;
+ case 'D':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, vc->vc_x - vc->vc_par[0], vc->vc_y);
+ return;
+ case 'E':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, 0, vc->vc_y + vc->vc_par[0]);
+ return;
+ case 'F':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ gotoxy(vc, 0, vc->vc_y - vc->vc_par[0]);
+ return;
+ case 'd':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ gotoxay(vc, vc->vc_x ,vc->vc_par[0]);
+ return;
+ case 'H': case 'f':
+ if (vc->vc_par[0])
+ vc->vc_par[0]--;
+ if (vc->vc_par[1])
+ vc->vc_par[1]--;
+ gotoxay(vc, vc->vc_par[1], vc->vc_par[0]);
+ return;
+ case 'J':
+ csi_J(vc, vc->vc_par[0]);
+ return;
+ case 'K':
+ csi_K(vc, vc->vc_par[0]);
+ return;
+ case 'L':
+ csi_L(vc, vc->vc_par[0]);
+ return;
+ case 'M':
+ csi_M(vc, vc->vc_par[0]);
+ return;
+ case 'P':
+ csi_P(vc, vc->vc_par[0]);
+ return;
+ case 'c':
+ if (!vc->vc_par[0])
+ respond_ID(tty);
+ return;
+ case 'g':
+ if (!vc->vc_par[0])
+ vc->vc_tab_stop[vc->vc_x >> 5] &= ~(1 << (vc->vc_x & 31));
+ else if (vc->vc_par[0] == 3) {
+ vc->vc_tab_stop[0] =
+ vc->vc_tab_stop[1] =
+ vc->vc_tab_stop[2] =
+ vc->vc_tab_stop[3] =
+ vc->vc_tab_stop[4] = 0;
+ }
+ return;
+ case 'm':
+ csi_m(vc);
+ return;
+ case 'q': /* DECLL - but only 3 leds */
+ /* map 0,1,2,3 to 0,1,2,4 */
+ if (vc->vc_par[0] < 4)
+ setledstate(kbd_table + vc->vc_num,
+ (vc->vc_par[0] < 3) ? vc->vc_par[0] : 4);
+ return;
+ case 'r':
+ if (!vc->vc_par[0])
+ vc->vc_par[0]++;
+ if (!vc->vc_par[1])
+ vc->vc_par[1] = vc->vc_rows;
+ /* Minimum allowed region is 2 lines */
+ if (vc->vc_par[0] < vc->vc_par[1] &&
+ vc->vc_par[1] <= vc->vc_rows) {
+ vc->vc_top = vc->vc_par[0] - 1;
+ vc->vc_bottom = vc->vc_par[1];
+ gotoxay(vc, 0, 0);
+ }
+ return;
+ case 's':
+ save_cur(vc);
+ return;
+ case 'u':
+ restore_cur(vc);
+ return;
+ case 'X':
+ csi_X(vc, vc->vc_par[0]);
+ return;
+ case '@':
+ csi_at(vc, vc->vc_par[0]);
+ return;
+ case ']': /* setterm functions */
+ setterm_command(vc);
+ return;
+ }
+ return;
+ case ESpercent:
+ vc->vc_state = ESnormal;
+ switch (c) {
+ case '@': /* defined in ISO 2022 */
+ vc->vc_utf = 0;
+ return;
+ case 'G': /* prelim official escape code */
+ case '8': /* retained for compatibility */
+ vc->vc_utf = 1;
+ return;
+ }
+ return;
+ case ESfunckey:
+ vc->vc_state = ESnormal;
+ return;
+ case EShash:
+ vc->vc_state = ESnormal;
+ if (c == '8') {
+ /* DEC screen alignment test. kludge :-) */
+ vc->vc_video_erase_char =
+ (vc->vc_video_erase_char & 0xff00) | 'E';
+ csi_J(vc, 2);
+ vc->vc_video_erase_char =
+ (vc->vc_video_erase_char & 0xff00) | ' ';
+ do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+ }
+ return;
+ case ESsetG0:
+ if (c == '0')
+ vc->vc_G0_charset = GRAF_MAP;
+ else if (c == 'B')
+ vc->vc_G0_charset = LAT1_MAP;
+ else if (c == 'U')
+ vc->vc_G0_charset = IBMPC_MAP;
+ else if (c == 'K')
+ vc->vc_G0_charset = USER_MAP;
+ if (vc->vc_charset == 0)
+ vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+ vc->vc_state = ESnormal;
+ return;
+ case ESsetG1:
+ if (c == '0')
+ vc->vc_G1_charset = GRAF_MAP;
+ else if (c == 'B')
+ vc->vc_G1_charset = LAT1_MAP;
+ else if (c == 'U')
+ vc->vc_G1_charset = IBMPC_MAP;
+ else if (c == 'K')
+ vc->vc_G1_charset = USER_MAP;
+ if (vc->vc_charset == 1)
+ vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+ vc->vc_state = ESnormal;
+ return;
+ default:
+ vc->vc_state = ESnormal;
+ }
+}
+
+/* This is a temporary buffer used to prepare a tty console write
+ * so that we can easily avoid touching user space while holding the
+ * console spinlock. It is allocated in con_init and is shared by
+ * this code and the vc_screen read/write tty calls.
+ *
+ * We have to allocate this statically in the kernel data section
+ * since console_init (and thus con_init) are called before any
+ * kernel memory allocation is available.
+ */
+char con_buf[CON_BUF_SIZE];
+DECLARE_MUTEX(con_buf_sem);
+
+/* acquires console_sem */
+static int do_con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+#ifdef VT_BUF_VRAM_ONLY
+#define FLUSH do { } while(0);
+#else
+#define FLUSH if (draw_x >= 0) { \
+ vc->vc_sw->con_putcs(vc, (u16 *)draw_from, (u16 *)draw_to - (u16 *)draw_from, vc->vc_y, draw_x); \
+ draw_x = -1; \
+ }
+#endif
+
+ int c, tc, ok, n = 0, draw_x = -1;
+ unsigned int currcons;
+ unsigned long draw_from = 0, draw_to = 0;
+ struct vc_data *vc;
+ u16 himask, charmask;
+ const unsigned char *orig_buf = NULL;
+ int orig_count;
+
+ if (in_interrupt())
+ return count;
+
+ might_sleep();
+
+ acquire_console_sem();
+ vc = tty->driver_data;
+ if (vc == NULL) {
+ printk(KERN_ERR "vt: argh, driver_data is NULL !\n");
+ release_console_sem();
+ return 0;
+ }
+
+ currcons = vc->vc_num;
+ if (!vc_cons_allocated(currcons)) {
+ /* could this happen? */
+ static int error = 0;
+ if (!error) {
+ error = 1;
+ printk("con_write: tty %d not allocated\n", currcons+1);
+ }
+ release_console_sem();
+ return 0;
+ }
+ release_console_sem();
+
+ orig_buf = buf;
+ orig_count = count;
+
+ /* At this point 'buf' is guaranteed to be a kernel buffer
+ * and therefore no access to userspace (and therefore sleeping)
+ * will be needed. The con_buf_sem serializes all tty based
+ * console rendering and vcs write/read operations. We hold
+ * the console spinlock during the entire write.
+ */
+
+ acquire_console_sem();
+
+ vc = tty->driver_data;
+ if (vc == NULL) {
+ printk(KERN_ERR "vt: argh, driver_data _became_ NULL !\n");
+ release_console_sem();
+ goto out;
+ }
+
+ himask = vc->vc_hi_font_mask;
+ charmask = himask ? 0x1ff : 0xff;
+
+ /* undraw cursor first */
+ if (IS_FG(vc))
+ hide_cursor(vc);
+
+ while (!tty->stopped && count) {
+ int orig = *buf;
+ c = orig;
+ buf++;
+ n++;
+ count--;
+
+ /* Do no translation at all in control states */
+ if (vc->vc_state != ESnormal) {
+ tc = c;
+ } else if (vc->vc_utf) {
+ /* Combine UTF-8 into Unicode */
+ /* Incomplete characters silently ignored */
+ if(c > 0x7f) {
+ if (vc->vc_utf_count > 0 && (c & 0xc0) == 0x80) {
+ vc->vc_utf_char = (vc->vc_utf_char << 6) | (c & 0x3f);
+ vc->vc_utf_count--;
+ if (vc->vc_utf_count == 0)
+ tc = c = vc->vc_utf_char;
+ else continue;
+ } else {
+ if ((c & 0xe0) == 0xc0) {
+ vc->vc_utf_count = 1;
+ vc->vc_utf_char = (c & 0x1f);
+ } else if ((c & 0xf0) == 0xe0) {
+ vc->vc_utf_count = 2;
+ vc->vc_utf_char = (c & 0x0f);
+ } else if ((c & 0xf8) == 0xf0) {
+ vc->vc_utf_count = 3;
+ vc->vc_utf_char = (c & 0x07);
+ } else if ((c & 0xfc) == 0xf8) {
+ vc->vc_utf_count = 4;
+ vc->vc_utf_char = (c & 0x03);
+ } else if ((c & 0xfe) == 0xfc) {
+ vc->vc_utf_count = 5;
+ vc->vc_utf_char = (c & 0x01);
+ } else
+ vc->vc_utf_count = 0;
+ continue;
+ }
+ } else {
+ tc = c;
+ vc->vc_utf_count = 0;
+ }
+ } else { /* no utf */
+ tc = vc->vc_translate[vc->vc_toggle_meta ? (c | 0x80) : c];
+ }
+
+ /* If the original code was a control character we
+ * only allow a glyph to be displayed if the code is
+ * not normally used (such as for cursor movement) or
+ * if the disp_ctrl mode has been explicitly enabled.
+ * Certain characters (as given by the CTRL_ALWAYS
+ * bitmap) are always displayed as control characters,
+ * as the console would be pretty useless without
+ * them; to display an arbitrary font position use the
+ * direct-to-font zone in UTF-8 mode.
+ */
+ ok = tc && (c >= 32 ||
+ (!vc->vc_utf && !(((vc->vc_disp_ctrl ? CTRL_ALWAYS
+ : CTRL_ACTION) >> c) & 1)))
+ && (c != 127 || vc->vc_disp_ctrl)
+ && (c != 128+27);
+
+ if (vc->vc_state == ESnormal && ok) {
+ /* Now try to find out how to display it */
+ tc = conv_uni_to_pc(vc, tc);
+ if ( tc == -4 ) {
+ /* If we got -4 (not found) then see if we have
+ defined a replacement character (U+FFFD) */
+ tc = conv_uni_to_pc(vc, 0xfffd);
+
+ /* One reason for the -4 can be that we just
+ did a clear_unimap();
+ try at least to show something. */
+ if (tc == -4)
+ tc = c;
+ } else if ( tc == -3 ) {
+ /* Bad hash table -- hope for the best */
+ tc = c;
+ }
+ if (tc & ~charmask)
+ continue; /* Conversion failed */
+
+ if (vc->vc_need_wrap || vc->vc_decim)
+ FLUSH
+ if (vc->vc_need_wrap) {
+ cr(vc);
+ lf(vc);
+ }
+ if (vc->vc_decim)
+ insert_char(vc, 1);
+ scr_writew(himask ?
+ ((vc->vc_attr << 8) & ~himask) + ((tc & 0x100) ? himask : 0) + (tc & 0xff) :
+ (vc->vc_attr << 8) + tc,
+ (u16 *) vc->vc_pos);
+ if (DO_UPDATE(vc) && draw_x < 0) {
+ draw_x = vc->vc_x;
+ draw_from = vc->vc_pos;
+ }
+ if (vc->vc_x == vc->vc_cols - 1) {
+ vc->vc_need_wrap = vc->vc_decawm;
+ draw_to = vc->vc_pos + 2;
+ } else {
+ vc->vc_x++;
+ draw_to = (vc->vc_pos += 2);
+ }
+ continue;
+ }
+ FLUSH
+ do_con_trol(tty, vc, orig);
+ }
+ FLUSH
+ console_conditional_schedule();
+ release_console_sem();
+
+out:
+ return n;
+#undef FLUSH
+}
+
+/*
+ * This is the console switching callback.
+ *
+ * Doing console switching in a process context allows
+ * us to do the switches asynchronously (needed when we want
+ * to switch due to a keyboard interrupt). Synchronization
+ * with other console code and prevention of re-entrancy is
+ * ensured with console_sem.
+ */
+static void console_callback(void *ignored)
+{
+ acquire_console_sem();
+
+ if (want_console >= 0) {
+ if (want_console != fg_console &&
+ vc_cons_allocated(want_console)) {
+ hide_cursor(vc_cons[fg_console].d);
+ change_console(vc_cons[want_console].d);
+ /* we only changed when the console had already
+ been allocated - a new console is not created
+ in an interrupt routine */
+ }
+ want_console = -1;
+ }
+ if (do_poke_blanked_console) { /* do not unblank for a LED change */
+ do_poke_blanked_console = 0;
+ poke_blanked_console();
+ }
+ if (scrollback_delta) {
+ struct vc_data *vc = vc_cons[fg_console].d;
+ clear_selection();
+ if (vc->vc_mode == KD_TEXT)
+ vc->vc_sw->con_scrolldelta(vc, scrollback_delta);
+ scrollback_delta = 0;
+ }
+ if (blank_timer_expired) {
+ do_blank_screen(0);
+ blank_timer_expired = 0;
+ }
+
+ release_console_sem();
+}
+
+void set_console(int nr)
+{
+ want_console = nr;
+ schedule_console_callback();
+}
+
+struct tty_driver *console_driver;
+
+#ifdef CONFIG_VT_CONSOLE
+
+/*
+ * Console on virtual terminal
+ *
+ * The console must be locked when we get here.
+ */
+
+static void vt_console_print(struct console *co, const char *b, unsigned count)
+{
+ struct vc_data *vc = vc_cons[fg_console].d;
+ unsigned char c;
+ static unsigned long printing;
+ const ushort *start;
+ ushort cnt = 0;
+ ushort myx;
+
+ /* console busy or not yet initialized */
+ if (!printable || test_and_set_bit(0, &printing))
+ return;
+
+ if (kmsg_redirect && vc_cons_allocated(kmsg_redirect - 1))
+ vc = vc_cons[kmsg_redirect - 1].d;
+
+ /* read `x' only after setting currcons properly (otherwise
+ the `x' macro will read the x of the foreground console). */
+ myx = vc->vc_x;
+
+ if (!vc_cons_allocated(fg_console)) {
+ /* impossible */
+ /* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
+ goto quit;
+ }
+
+ if (vc->vc_mode != KD_TEXT)
+ goto quit;
+
+ /* undraw cursor first */
+ if (IS_FG(vc))
+ hide_cursor(vc);
+
+ start = (ushort *)vc->vc_pos;
+
+ /* Contrived structure to try to emulate original need_wrap behaviour
+ * Problems caused when we have need_wrap set on '\n' character */
+ while (count--) {
+ c = *b++;
+ if (c == 10 || c == 13 || c == 8 || vc->vc_need_wrap) {
+ if (cnt > 0) {
+ if (CON_IS_VISIBLE(vc))
+ vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
+ vc->vc_x += cnt;
+ if (vc->vc_need_wrap)
+ vc->vc_x--;
+ cnt = 0;
+ }
+ if (c == 8) { /* backspace */
+ bs(vc);
+ start = (ushort *)vc->vc_pos;
+ myx = vc->vc_x;
+ continue;
+ }
+ if (c != 13)
+ lf(vc);
+ cr(vc);
+ start = (ushort *)vc->vc_pos;
+ myx = vc->vc_x;
+ if (c == 10 || c == 13)
+ continue;
+ }
+ scr_writew((vc->vc_attr << 8) + c, (unsigned short *)vc->vc_pos);
+ cnt++;
+ if (myx == vc->vc_cols - 1) {
+ vc->vc_need_wrap = 1;
+ continue;
+ }
+ vc->vc_pos += 2;
+ myx++;
+ }
+ if (cnt > 0) {
+ if (CON_IS_VISIBLE(vc))
+ vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
+ vc->vc_x += cnt;
+ if (vc->vc_x == vc->vc_cols) {
+ vc->vc_x--;
+ vc->vc_need_wrap = 1;
+ }
+ }
+ set_cursor(vc);
+
+quit:
+ clear_bit(0, &printing);
+}
+
+static struct tty_driver *vt_console_device(struct console *c, int *index)
+{
+ *index = c->index ? c->index-1 : fg_console;
+ return console_driver;
+}
+
+static struct console vt_console_driver = {
+ .name = "tty",
+ .write = vt_console_print,
+ .device = vt_console_device,
+ .unblank = unblank_screen,
+ .flags = CON_PRINTBUFFER,
+ .index = -1,
+};
+#endif
+
+/*
+ * Handling of Linux-specific VC ioctls
+ */
+
+/*
+ * Generally a bit racy with respect to console_sem().
+ *
+ * There are some functions which don't need it.
+ *
+ * There are some functions which can sleep for arbitrary periods
+ * (paste_selection) but we don't need the lock there anyway.
+ *
+ * set_selection has locking, and definitely needs it
+ */
+
+int tioclinux(struct tty_struct *tty, unsigned long arg)
+{
+ char type, data;
+ char __user *p = (char __user *)arg;
+ int lines;
+ int ret;
+
+ if (tty->driver->type != TTY_DRIVER_TYPE_CONSOLE)
+ return -EINVAL;
+ if (current->signal->tty != tty && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ if (get_user(type, p))
+ return -EFAULT;
+ ret = 0;
+ switch (type)
+ {
+ case TIOCL_SETSEL:
+ acquire_console_sem();
+ ret = set_selection((struct tiocl_selection __user *)(p+1), tty);
+ release_console_sem();
+ break;
+ case TIOCL_PASTESEL:
+ ret = paste_selection(tty);
+ break;
+ case TIOCL_UNBLANKSCREEN:
+ unblank_screen();
+ break;
+ case TIOCL_SELLOADLUT:
+ ret = sel_loadlut(p);
+ break;
+ case TIOCL_GETSHIFTSTATE:
+
+ /*
+ * Make it possible to react to Shift+Mousebutton.
+ * Note that 'shift_state' is an undocumented
+ * kernel-internal variable; programs not closely
+ * related to the kernel should not use this.
+ */
+ data = shift_state;
+ ret = __put_user(data, p);
+ break;
+ case TIOCL_GETMOUSEREPORTING:
+ data = mouse_reporting();
+ ret = __put_user(data, p);
+ break;
+ case TIOCL_SETVESABLANK:
+ set_vesa_blanking(p);
+ break;
+ case TIOCL_SETKMSGREDIRECT:
+ if (!capable(CAP_SYS_ADMIN)) {
+ ret = -EPERM;
+ } else {
+ if (get_user(data, p+1))
+ ret = -EFAULT;
+ else
+ kmsg_redirect = data;
+ }
+ break;
+ case TIOCL_GETFGCONSOLE:
+ ret = fg_console;
+ break;
+ case TIOCL_SCROLLCONSOLE:
+ if (get_user(lines, (s32 __user *)(p+4))) {
+ ret = -EFAULT;
+ } else {
+ scrollfront(vc_cons[fg_console].d, lines);
+ ret = 0;
+ }
+ break;
+ case TIOCL_BLANKSCREEN: /* until explicitly unblanked, not only poked */
+ ignore_poke = 1;
+ do_blank_screen(0);
+ break;
+ case TIOCL_BLANKEDSCREEN:
+ ret = console_blanked;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * /dev/ttyN handling
+ */
+
+static int con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ int retval;
+
+ retval = do_con_write(tty, buf, count);
+ con_flush_chars(tty);
+
+ return retval;
+}
+
+static void con_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ if (in_interrupt())
+ return; /* n_r3964 calls put_char() from interrupt context */
+ do_con_write(tty, &ch, 1);
+}
+
+static int con_write_room(struct tty_struct *tty)
+{
+ if (tty->stopped)
+ return 0;
+ return 4096; /* No limit, really; we're not buffering */
+}
+
+static int con_chars_in_buffer(struct tty_struct *tty)
+{
+ return 0; /* we're not buffering */
+}
+
+/*
+ * con_throttle and con_unthrottle are only used for
+ * paste_selection(), which has to stuff in a large number of
+ * characters...
+ */
+static void con_throttle(struct tty_struct *tty)
+{
+}
+
+static void con_unthrottle(struct tty_struct *tty)
+{
+ struct vc_data *vc = tty->driver_data;
+
+ wake_up_interruptible(&vc->paste_wait);
+}
+
+/*
+ * Turn the Scroll-Lock LED on when the tty is stopped
+ */
+static void con_stop(struct tty_struct *tty)
+{
+ int console_num;
+ if (!tty)
+ return;
+ console_num = tty->index;
+ if (!vc_cons_allocated(console_num))
+ return;
+ set_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK);
+ set_leds();
+}
+
+/*
+ * Turn the Scroll-Lock LED off when the console is started
+ */
+static void con_start(struct tty_struct *tty)
+{
+ int console_num;
+ if (!tty)
+ return;
+ console_num = tty->index;
+ if (!vc_cons_allocated(console_num))
+ return;
+ clr_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK);
+ set_leds();
+}
+
+static void con_flush_chars(struct tty_struct *tty)
+{
+ struct vc_data *vc;
+
+ if (in_interrupt()) /* from flush_to_ldisc */
+ return;
+
+ /* if we race with con_close(), vt may be null */
+ acquire_console_sem();
+ vc = tty->driver_data;
+ if (vc)
+ set_cursor(vc);
+ release_console_sem();
+}
+
+/*
+ * Allocate the console screen memory.
+ */
+static int con_open(struct tty_struct *tty, struct file *filp)
+{
+ unsigned int currcons = tty->index;
+ int ret = 0;
+
+ acquire_console_sem();
+ if (tty->count == 1) {
+ ret = vc_allocate(currcons);
+ if (ret == 0) {
+ struct vc_data *vc = vc_cons[currcons].d;
+ tty->driver_data = vc;
+ vc->vc_tty = tty;
+
+ if (!tty->winsize.ws_row && !tty->winsize.ws_col) {
+ tty->winsize.ws_row = vc_cons[currcons].d->vc_rows;
+ tty->winsize.ws_col = vc_cons[currcons].d->vc_cols;
+ }
+ release_console_sem();
+ vcs_make_devfs(tty);
+ return ret;
+ }
+ }
+ release_console_sem();
+ return ret;
+}
+
+/*
+ * We take tty_sem in here to prevent another thread from coming in via init_dev
+ * and taking a ref against the tty while we're in the process of forgetting
+ * about it and cleaning things up.
+ *
+ * This is because vcs_remove_devfs() can sleep and will drop the BKL.
+ */
+static void con_close(struct tty_struct *tty, struct file *filp)
+{
+ down(&tty_sem);
+ acquire_console_sem();
+ if (tty && tty->count == 1) {
+ struct vc_data *vc = tty->driver_data;
+
+ if (vc)
+ vc->vc_tty = NULL;
+ tty->driver_data = NULL;
+ release_console_sem();
+ vcs_remove_devfs(tty);
+ up(&tty_sem);
+ /*
+ * tty_sem is released, but we still hold BKL, so there is
+ * still exclusion against init_dev()
+ */
+ return;
+ }
+ release_console_sem();
+ up(&tty_sem);
+}
+
+static void vc_init(struct vc_data *vc, unsigned int rows,
+ unsigned int cols, int do_clear)
+{
+ int j, k ;
+
+ vc->vc_cols = cols;
+ vc->vc_rows = rows;
+ vc->vc_size_row = cols << 1;
+ vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
+
+ set_origin(vc);
+ vc->vc_pos = vc->vc_origin;
+ reset_vc(vc);
+ for (j=k=0; j<16; j++) {
+ vc->vc_palette[k++] = default_red[j] ;
+ vc->vc_palette[k++] = default_grn[j] ;
+ vc->vc_palette[k++] = default_blu[j] ;
+ }
+ vc->vc_def_color = 0x07; /* white */
+ vc->vc_ulcolor = 0x0f; /* bold white */
+ vc->vc_halfcolor = 0x08; /* grey */
+ init_waitqueue_head(&vc->paste_wait);
+ reset_terminal(vc, do_clear);
+}
+
+/*
+ * This routine initializes console interrupts, and does nothing
+ * else. If you want the screen to clear, call tty_write with
+ * the appropriate escape-sequence.
+ */
+
+static int __init con_init(void)
+{
+ const char *display_desc = NULL;
+ struct vc_data *vc;
+ unsigned int currcons = 0;
+
+ acquire_console_sem();
+
+ if (conswitchp)
+ display_desc = conswitchp->con_startup();
+ if (!display_desc) {
+ fg_console = 0;
+ release_console_sem();
+ return 0;
+ }
+
+ init_timer(&console_timer);
+ console_timer.function = blank_screen_t;
+ if (blankinterval) {
+ blank_state = blank_normal_wait;
+ mod_timer(&console_timer, jiffies + blankinterval);
+ }
+
+ /*
+ * kmalloc is not running yet - we use the bootmem allocator.
+ */
+ for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
+ vc_cons[currcons].d = vc = alloc_bootmem(sizeof(struct vc_data));
+ visual_init(vc, currcons, 1);
+ vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->vc_screenbuf_size);
+ vc->vc_kmalloced = 0;
+ vc_init(vc, vc->vc_rows, vc->vc_cols,
+ currcons || !vc->vc_sw->con_save_screen);
+ }
+ currcons = fg_console = 0;
+ master_display_fg = vc = vc_cons[currcons].d;
+ set_origin(vc);
+ save_screen(vc);
+ gotoxy(vc, vc->vc_x, vc->vc_y);
+ csi_J(vc, 0);
+ update_screen(vc);
+ printk("Console: %s %s %dx%d",
+ vc->vc_can_do_color ? "colour" : "mono",
+ display_desc, vc->vc_cols, vc->vc_rows);
+ printable = 1;
+ printk("\n");
+
+ release_console_sem();
+
+#ifdef CONFIG_VT_CONSOLE
+ register_console(&vt_console_driver);
+#endif
+ return 0;
+}
+console_initcall(con_init);
+
+static struct tty_operations con_ops = {
+ .open = con_open,
+ .close = con_close,
+ .write = con_write,
+ .write_room = con_write_room,
+ .put_char = con_put_char,
+ .flush_chars = con_flush_chars,
+ .chars_in_buffer = con_chars_in_buffer,
+ .ioctl = vt_ioctl,
+ .stop = con_stop,
+ .start = con_start,
+ .throttle = con_throttle,
+ .unthrottle = con_unthrottle,
+};
+
+int __init vty_init(void)
+{
+ vcs_init();
+
+ console_driver = alloc_tty_driver(MAX_NR_CONSOLES);
+ if (!console_driver)
+ panic("Couldn't allocate console driver\n");
+ console_driver->owner = THIS_MODULE;
+ console_driver->devfs_name = "vc/";
+ console_driver->name = "tty";
+ console_driver->name_base = 1;
+ console_driver->major = TTY_MAJOR;
+ console_driver->minor_start = 1;
+ console_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+ console_driver->init_termios = tty_std_termios;
+ console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
+ tty_set_operations(console_driver, &con_ops);
+ if (tty_register_driver(console_driver))
+ panic("Couldn't register console driver\n");
+
+ kbd_init();
+ console_map_init();
+#ifdef CONFIG_PROM_CONSOLE
+ prom_con_init();
+#endif
+#ifdef CONFIG_MDA_CONSOLE
+ mda_console_init();
+#endif
+ return 0;
+}
+
+#ifndef VT_SINGLE_DRIVER
+
+/*
+ * If we support more console drivers, this function is used
+ * when a driver wants to take over some existing consoles
+ * and become default driver for newly opened ones.
+ */
+
+int take_over_console(const struct consw *csw, int first, int last, int deflt)
+{
+ int i, j = -1;
+ const char *desc;
+ struct module *owner;
+
+ owner = csw->owner;
+ if (!try_module_get(owner))
+ return -ENODEV;
+
+ acquire_console_sem();
+
+ desc = csw->con_startup();
+ if (!desc) {
+ release_console_sem();
+ module_put(owner);
+ return -ENODEV;
+ }
+ if (deflt) {
+ if (conswitchp)
+ module_put(conswitchp->owner);
+ __module_get(owner);
+ conswitchp = csw;
+ }
+
+ for (i = first; i <= last; i++) {
+ int old_was_color;
+ struct vc_data *vc = vc_cons[i].d;
+
+ if (con_driver_map[i])
+ module_put(con_driver_map[i]->owner);
+ __module_get(owner);
+ con_driver_map[i] = csw;
+
+ if (!vc || !vc->vc_sw)
+ continue;
+
+ j = i;
+ if (CON_IS_VISIBLE(vc))
+ save_screen(vc);
+ old_was_color = vc->vc_can_do_color;
+ vc->vc_sw->con_deinit(vc);
+ vc->vc_origin = (unsigned long)vc->vc_screenbuf;
+ vc->vc_visible_origin = vc->vc_origin;
+ vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
+ vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->vc_y + 2 * vc->vc_x;
+ visual_init(vc, i, 0);
+ update_attr(vc);
+
+ /* If the console changed between mono <-> color, then
+ * the attributes in the screenbuf will be wrong. The
+ * following resets all attributes to something sane.
+ */
+ if (old_was_color != vc->vc_can_do_color)
+ clear_buffer_attributes(vc);
+
+ if (CON_IS_VISIBLE(vc))
+ update_screen(vc);
+ }
+ printk("Console: switching ");
+ if (!deflt)
+ printk("consoles %d-%d ", first+1, last+1);
+ if (j >= 0)
+ printk("to %s %s %dx%d\n",
+ vc_cons[j].d->vc_can_do_color ? "colour" : "mono",
+ desc, vc_cons[j].d->vc_cols, vc_cons[j].d->vc_rows);
+ else
+ printk("to %s\n", desc);
+
+ release_console_sem();
+
+ module_put(owner);
+ return 0;
+}
+
+void give_up_console(const struct consw *csw)
+{
+ int i;
+
+ for(i = 0; i < MAX_NR_CONSOLES; i++)
+ if (con_driver_map[i] == csw) {
+ module_put(csw->owner);
+ con_driver_map[i] = NULL;
+ }
+}
+
+#endif
+
+/*
+ * Screen blanking
+ */
+
+static void set_vesa_blanking(char __user *p)
+{
+ unsigned int mode;
+ get_user(mode, p + 1);
+ vesa_blank_mode = (mode < 4) ? mode : 0;
+}
+
+/*
+ * This is called by a timer handler
+ */
+static void vesa_powerdown(void)
+{
+ struct vc_data *c = vc_cons[fg_console].d;
+ /*
+ * Power down if currently suspended (1 or 2),
+ * suspend if currently blanked (0),
+ * else do nothing (i.e. already powered down (3)).
+ * Called only if powerdown features are allowed.
+ */
+ switch (vesa_blank_mode) {
+ case VESA_NO_BLANKING:
+ c->vc_sw->con_blank(c, VESA_VSYNC_SUSPEND+1, 0);
+ break;
+ case VESA_VSYNC_SUSPEND:
+ case VESA_HSYNC_SUSPEND:
+ c->vc_sw->con_blank(c, VESA_POWERDOWN+1, 0);
+ break;
+ }
+}
+
+void do_blank_screen(int entering_gfx)
+{
+ struct vc_data *vc = vc_cons[fg_console].d;
+ int i;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ if (console_blanked) {
+ if (blank_state == blank_vesa_wait) {
+ blank_state = blank_off;
+ vesa_powerdown();
+
+ }
+ return;
+ }
+ if (blank_state != blank_normal_wait)
+ return;
+ blank_state = blank_off;
+
+ /* entering graphics mode? */
+ if (entering_gfx) {
+ hide_cursor(vc);
+ save_screen(vc);
+ vc->vc_sw->con_blank(vc, -1, 1);
+ console_blanked = fg_console + 1;
+ set_origin(vc);
+ return;
+ }
+
+ /* don't blank graphics */
+ if (vc->vc_mode != KD_TEXT) {
+ console_blanked = fg_console + 1;
+ return;
+ }
+
+ hide_cursor(vc);
+ del_timer_sync(&console_timer);
+ blank_timer_expired = 0;
+
+ save_screen(vc);
+ /* In case we need to reset origin, blanking hook returns 1 */
+ i = vc->vc_sw->con_blank(vc, 1, 0);
+ console_blanked = fg_console + 1;
+ if (i)
+ set_origin(vc);
+
+ if (console_blank_hook && console_blank_hook(1))
+ return;
+
+ if (vesa_off_interval) {
+ blank_state = blank_vesa_wait,
+ mod_timer(&console_timer, jiffies + vesa_off_interval);
+ }
+
+ if (vesa_blank_mode)
+ vc->vc_sw->con_blank(vc, vesa_blank_mode + 1, 0);
+}
+EXPORT_SYMBOL(do_blank_screen);
+
+/*
+ * Called by timer as well as from vt_console_driver
+ */
+void do_unblank_screen(int leaving_gfx)
+{
+ struct vc_data *vc;
+
+ /* This should now always be called from a "sane" (read: can schedule)
+ * context for the sake of the low level drivers, except in the special
+ * case of oops_in_progress
+ */
+ if (!oops_in_progress)
+ might_sleep();
+
+ WARN_CONSOLE_UNLOCKED();
+
+ ignore_poke = 0;
+ if (!console_blanked)
+ return;
+ if (!vc_cons_allocated(fg_console)) {
+ /* impossible */
+ printk("unblank_screen: tty %d not allocated ??\n", fg_console+1);
+ return;
+ }
+ vc = vc_cons[fg_console].d;
+ if (vc->vc_mode != KD_TEXT)
+ return; /* but leave console_blanked != 0 */
+
+ if (blankinterval) {
+ mod_timer(&console_timer, jiffies + blankinterval);
+ blank_state = blank_normal_wait;
+ }
+
+ console_blanked = 0;
+ if (vc->vc_sw->con_blank(vc, 0, leaving_gfx))
+ /* Low-level driver cannot restore -> do it ourselves */
+ update_screen(vc);
+ if (console_blank_hook)
+ console_blank_hook(0);
+ set_palette(vc);
+ set_cursor(vc);
+}
+EXPORT_SYMBOL(do_unblank_screen);
+
+/*
+ * This is called by the outside world to cause a forced unblank, mostly for
+ * oopses. Currently, I just call do_unblank_screen(0), but we could eventually
+ * call it with 1 as an argument and so force a mode restore... that may kill
+ * X or at least garbage the screen but would also make the Oops visible...
+ */
+void unblank_screen(void)
+{
+ do_unblank_screen(0);
+}
+
+/*
+ * We defer the timer blanking to work queue so it can take the console semaphore
+ * (console operations can still happen at irq time, but only from printk which
+ * has the console semaphore. Not perfect yet, but better than no locking
+ */
+static void blank_screen_t(unsigned long dummy)
+{
+ blank_timer_expired = 1;
+ schedule_work(&console_work);
+}
+
+void poke_blanked_console(void)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ /* Add this so we quickly catch whoever might call us in a non
+ * safe context. Nowadays, unblank_screen() isn't to be called in
+ * atomic contexts and is allowed to schedule (with the special case
+ * of oops_in_progress, but that isn't of any concern for this
+ * function. --BenH.
+ */
+ might_sleep();
+
+ /* This isn't perfectly race free, but a race here would be mostly harmless,
+ * at worse, we'll do a spurrious blank and it's unlikely
+ */
+ del_timer(&console_timer);
+ blank_timer_expired = 0;
+
+ if (ignore_poke || !vc_cons[fg_console].d || vc_cons[fg_console].d->vc_mode == KD_GRAPHICS)
+ return;
+ if (console_blanked)
+ unblank_screen();
+ else if (blankinterval) {
+ mod_timer(&console_timer, jiffies + blankinterval);
+ blank_state = blank_normal_wait;
+ }
+}
+
+/*
+ * Palettes
+ */
+
+static void set_palette(struct vc_data *vc)
+{
+ WARN_CONSOLE_UNLOCKED();
+
+ if (vc->vc_mode != KD_GRAPHICS)
+ vc->vc_sw->con_set_palette(vc, color_table);
+}
+
+static int set_get_cmap(unsigned char __user *arg, int set)
+{
+ int i, j, k;
+
+ WARN_CONSOLE_UNLOCKED();
+
+ for (i = 0; i < 16; i++)
+ if (set) {
+ get_user(default_red[i], arg++);
+ get_user(default_grn[i], arg++);
+ get_user(default_blu[i], arg++);
+ } else {
+ put_user(default_red[i], arg++);
+ put_user(default_grn[i], arg++);
+ put_user(default_blu[i], arg++);
+ }
+ if (set) {
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ if (vc_cons_allocated(i)) {
+ for (j = k = 0; j < 16; j++) {
+ vc_cons[i].d->vc_palette[k++] = default_red[j];
+ vc_cons[i].d->vc_palette[k++] = default_grn[j];
+ vc_cons[i].d->vc_palette[k++] = default_blu[j];
+ }
+ set_palette(vc_cons[i].d);
+ }
+ }
+ return 0;
+}
+
+/*
+ * Load palette into the DAC registers. arg points to a colour
+ * map, 3 bytes per colour, 16 colours, range from 0 to 255.
+ */
+
+int con_set_cmap(unsigned char __user *arg)
+{
+ int rc;
+
+ acquire_console_sem();
+ rc = set_get_cmap (arg,1);
+ release_console_sem();
+
+ return rc;
+}
+
+int con_get_cmap(unsigned char __user *arg)
+{
+ int rc;
+
+ acquire_console_sem();
+ rc = set_get_cmap (arg,0);
+ release_console_sem();
+
+ return rc;
+}
+
+void reset_palette(struct vc_data *vc)
+{
+ int j, k;
+ for (j=k=0; j<16; j++) {
+ vc->vc_palette[k++] = default_red[j];
+ vc->vc_palette[k++] = default_grn[j];
+ vc->vc_palette[k++] = default_blu[j];
+ }
+ set_palette(vc);
+}
+
+/*
+ * Font switching
+ *
+ * Currently we only support fonts up to 32 pixels wide, at a maximum height
+ * of 32 pixels. Userspace fontdata is stored with 32 bytes (shorts/ints,
+ * depending on width) reserved for each character which is kinda wasty, but
+ * this is done in order to maintain compatibility with the EGA/VGA fonts. It
+ * is upto the actual low-level console-driver convert data into its favorite
+ * format (maybe we should add a `fontoffset' field to the `display'
+ * structure so we won't have to convert the fontdata all the time.
+ * /Jes
+ */
+
+#define max_font_size 65536
+
+static int con_font_get(struct vc_data *vc, struct console_font_op *op)
+{
+ struct console_font font;
+ int rc = -EINVAL;
+ int c;
+
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+
+ if (op->data) {
+ font.data = kmalloc(max_font_size, GFP_KERNEL);
+ if (!font.data)
+ return -ENOMEM;
+ } else
+ font.data = NULL;
+
+ acquire_console_sem();
+ if (vc->vc_sw->con_font_get)
+ rc = vc->vc_sw->con_font_get(vc, &font);
+ else
+ rc = -ENOSYS;
+ release_console_sem();
+
+ if (rc)
+ goto out;
+
+ c = (font.width+7)/8 * 32 * font.charcount;
+
+ if (op->data && font.charcount > op->charcount)
+ rc = -ENOSPC;
+ if (!(op->flags & KD_FONT_FLAG_OLD)) {
+ if (font.width > op->width || font.height > op->height)
+ rc = -ENOSPC;
+ } else {
+ if (font.width != 8)
+ rc = -EIO;
+ else if ((op->height && font.height > op->height) ||
+ font.height > 32)
+ rc = -ENOSPC;
+ }
+ if (rc)
+ goto out;
+
+ op->height = font.height;
+ op->width = font.width;
+ op->charcount = font.charcount;
+
+ if (op->data && copy_to_user(op->data, font.data, c))
+ rc = -EFAULT;
+
+out:
+ kfree(font.data);
+ return rc;
+}
+
+static int con_font_set(struct vc_data *vc, struct console_font_op *op)
+{
+ struct console_font font;
+ int rc = -EINVAL;
+ int size;
+
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+ if (!op->data)
+ return -EINVAL;
+ if (op->charcount > 512)
+ return -EINVAL;
+ if (!op->height) { /* Need to guess font height [compat] */
+ int h, i;
+ u8 __user *charmap = op->data;
+ u8 tmp;
+
+ /* If from KDFONTOP ioctl, don't allow things which can be done in userland,
+ so that we can get rid of this soon */
+ if (!(op->flags & KD_FONT_FLAG_OLD))
+ return -EINVAL;
+ for (h = 32; h > 0; h--)
+ for (i = 0; i < op->charcount; i++) {
+ if (get_user(tmp, &charmap[32*i+h-1]))
+ return -EFAULT;
+ if (tmp)
+ goto nonzero;
+ }
+ return -EINVAL;
+ nonzero:
+ op->height = h;
+ }
+ if (op->width <= 0 || op->width > 32 || op->height > 32)
+ return -EINVAL;
+ size = (op->width+7)/8 * 32 * op->charcount;
+ if (size > max_font_size)
+ return -ENOSPC;
+ font.charcount = op->charcount;
+ font.height = op->height;
+ font.width = op->width;
+ font.data = kmalloc(size, GFP_KERNEL);
+ if (!font.data)
+ return -ENOMEM;
+ if (copy_from_user(font.data, op->data, size)) {
+ kfree(font.data);
+ return -EFAULT;
+ }
+ acquire_console_sem();
+ if (vc->vc_sw->con_font_set)
+ rc = vc->vc_sw->con_font_set(vc, &font, op->flags);
+ else
+ rc = -ENOSYS;
+ release_console_sem();
+ kfree(font.data);
+ return rc;
+}
+
+static int con_font_default(struct vc_data *vc, struct console_font_op *op)
+{
+ struct console_font font = {.width = op->width, .height = op->height};
+ char name[MAX_FONT_NAME];
+ char *s = name;
+ int rc;
+
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+
+ if (!op->data)
+ s = NULL;
+ else if (strncpy_from_user(name, op->data, MAX_FONT_NAME - 1) < 0)
+ return -EFAULT;
+ else
+ name[MAX_FONT_NAME - 1] = 0;
+
+ acquire_console_sem();
+ if (vc->vc_sw->con_font_default)
+ rc = vc->vc_sw->con_font_default(vc, &font, s);
+ else
+ rc = -ENOSYS;
+ release_console_sem();
+ if (!rc) {
+ op->width = font.width;
+ op->height = font.height;
+ }
+ return rc;
+}
+
+static int con_font_copy(struct vc_data *vc, struct console_font_op *op)
+{
+ int con = op->height;
+ int rc;
+
+ if (vc->vc_mode != KD_TEXT)
+ return -EINVAL;
+
+ acquire_console_sem();
+ if (!vc->vc_sw->con_font_copy)
+ rc = -ENOSYS;
+ else if (con < 0 || !vc_cons_allocated(con))
+ rc = -ENOTTY;
+ else if (con == vc->vc_num) /* nothing to do */
+ rc = 0;
+ else
+ rc = vc->vc_sw->con_font_copy(vc, con);
+ release_console_sem();
+ return rc;
+}
+
+int con_font_op(struct vc_data *vc, struct console_font_op *op)
+{
+ switch (op->op) {
+ case KD_FONT_OP_SET:
+ return con_font_set(vc, op);
+ case KD_FONT_OP_GET:
+ return con_font_get(vc, op);
+ case KD_FONT_OP_SET_DEFAULT:
+ return con_font_default(vc, op);
+ case KD_FONT_OP_COPY:
+ return con_font_copy(vc, op);
+ }
+ return -ENOSYS;
+}
+
+/*
+ * Interface exported to selection and vcs.
+ */
+
+/* used by selection */
+u16 screen_glyph(struct vc_data *vc, int offset)
+{
+ u16 w = scr_readw(screenpos(vc, offset, 1));
+ u16 c = w & 0xff;
+
+ if (w & vc->vc_hi_font_mask)
+ c |= 0x100;
+ return c;
+}
+
+/* used by vcs - note the word offset */
+unsigned short *screen_pos(struct vc_data *vc, int w_offset, int viewed)
+{
+ return screenpos(vc, 2 * w_offset, viewed);
+}
+
+void getconsxy(struct vc_data *vc, unsigned char *p)
+{
+ p[0] = vc->vc_x;
+ p[1] = vc->vc_y;
+}
+
+void putconsxy(struct vc_data *vc, unsigned char *p)
+{
+ gotoxy(vc, p[0], p[1]);
+ set_cursor(vc);
+}
+
+u16 vcs_scr_readw(struct vc_data *vc, const u16 *org)
+{
+ if ((unsigned long)org == vc->vc_pos && softcursor_original != -1)
+ return softcursor_original;
+ return scr_readw(org);
+}
+
+void vcs_scr_writew(struct vc_data *vc, u16 val, u16 *org)
+{
+ scr_writew(val, org);
+ if ((unsigned long)org == vc->vc_pos) {
+ softcursor_original = -1;
+ add_softcursor(vc);
+ }
+}
+
+/*
+ * Visible symbols for modules
+ */
+
+EXPORT_SYMBOL(color_table);
+EXPORT_SYMBOL(default_red);
+EXPORT_SYMBOL(default_grn);
+EXPORT_SYMBOL(default_blu);
+EXPORT_SYMBOL(update_region);
+EXPORT_SYMBOL(redraw_screen);
+EXPORT_SYMBOL(vc_resize);
+EXPORT_SYMBOL(fg_console);
+EXPORT_SYMBOL(console_blank_hook);
+EXPORT_SYMBOL(console_blanked);
+EXPORT_SYMBOL(vc_cons);
+#ifndef VT_SINGLE_DRIVER
+EXPORT_SYMBOL(take_over_console);
+EXPORT_SYMBOL(give_up_console);
+#endif
diff --git a/drivers/char/vt_ioctl.c b/drivers/char/vt_ioctl.c
new file mode 100644
index 000000000000..5d386f4bea49
--- /dev/null
+++ b/drivers/char/vt_ioctl.c
@@ -0,0 +1,1201 @@
+/*
+ * linux/drivers/char/vt_ioctl.c
+ *
+ * Copyright (C) 1992 obz under the linux copyright
+ *
+ * Dynamic diacritical handling - aeb@cwi.nl - Dec 1993
+ * Dynamic keymap and string allocation - aeb@cwi.nl - May 1994
+ * Restrict VT switching via ioctl() - grif@cs.ucr.edu - Dec 1995
+ * Some code moved for less code duplication - Andi Kleen - Mar 1997
+ * Check put/get_user, cleanups - acme@conectiva.com.br - Jun 2001
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/timer.h>
+#include <linux/kernel.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/console.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/vt_kern.h>
+#include <linux/kbd_diacr.h>
+#include <linux/selection.h>
+
+static char vt_dont_switch;
+extern struct tty_driver *console_driver;
+
+#define VT_IS_IN_USE(i) (console_driver->ttys[i] && console_driver->ttys[i]->count)
+#define VT_BUSY(i) (VT_IS_IN_USE(i) || i == fg_console || vc_cons[i].d == sel_cons)
+
+/*
+ * Console (vt and kd) routines, as defined by USL SVR4 manual, and by
+ * experimentation and study of X386 SYSV handling.
+ *
+ * One point of difference: SYSV vt's are /dev/vtX, which X >= 0, and
+ * /dev/console is a separate ttyp. Under Linux, /dev/tty0 is /dev/console,
+ * and the vc start at /dev/ttyX, X >= 1. We maintain that here, so we will
+ * always treat our set of vt as numbered 1..MAX_NR_CONSOLES (corresponding to
+ * ttys 0..MAX_NR_CONSOLES-1). Explicitly naming VT 0 is illegal, but using
+ * /dev/tty0 (fg_console) as a target is legal, since an implicit aliasing
+ * to the current console is done by the main ioctl code.
+ */
+
+#ifdef CONFIG_X86
+#include <linux/syscalls.h>
+#endif
+
+static void complete_change_console(struct vc_data *vc);
+
+/*
+ * these are the valid i/o ports we're allowed to change. they map all the
+ * video ports
+ */
+#define GPFIRST 0x3b4
+#define GPLAST 0x3df
+#define GPNUM (GPLAST - GPFIRST + 1)
+
+#define i (tmp.kb_index)
+#define s (tmp.kb_table)
+#define v (tmp.kb_value)
+static inline int
+do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm, struct kbd_struct *kbd)
+{
+ struct kbentry tmp;
+ ushort *key_map, val, ov;
+
+ if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case KDGKBENT:
+ key_map = key_maps[s];
+ if (key_map) {
+ val = U(key_map[i]);
+ if (kbd->kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
+ val = K_HOLE;
+ } else
+ val = (i ? K_HOLE : K_NOSUCHMAP);
+ return put_user(val, &user_kbe->kb_value);
+ case KDSKBENT:
+ if (!perm)
+ return -EPERM;
+ if (!i && v == K_NOSUCHMAP) {
+ /* disallocate map */
+ key_map = key_maps[s];
+ if (s && key_map) {
+ key_maps[s] = NULL;
+ if (key_map[0] == U(K_ALLOCATED)) {
+ kfree(key_map);
+ keymap_count--;
+ }
+ }
+ break;
+ }
+
+ if (KTYP(v) < NR_TYPES) {
+ if (KVAL(v) > max_vals[KTYP(v)])
+ return -EINVAL;
+ } else
+ if (kbd->kbdmode != VC_UNICODE)
+ return -EINVAL;
+
+ /* ++Geert: non-PC keyboards may generate keycode zero */
+#if !defined(__mc68000__) && !defined(__powerpc__)
+ /* assignment to entry 0 only tests validity of args */
+ if (!i)
+ break;
+#endif
+
+ if (!(key_map = key_maps[s])) {
+ int j;
+
+ if (keymap_count >= MAX_NR_OF_USER_KEYMAPS &&
+ !capable(CAP_SYS_RESOURCE))
+ return -EPERM;
+
+ key_map = (ushort *) kmalloc(sizeof(plain_map),
+ GFP_KERNEL);
+ if (!key_map)
+ return -ENOMEM;
+ key_maps[s] = key_map;
+ key_map[0] = U(K_ALLOCATED);
+ for (j = 1; j < NR_KEYS; j++)
+ key_map[j] = U(K_HOLE);
+ keymap_count++;
+ }
+ ov = U(key_map[i]);
+ if (v == ov)
+ break; /* nothing to do */
+ /*
+ * Attention Key.
+ */
+ if (((ov == K_SAK) || (v == K_SAK)) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+ key_map[i] = U(v);
+ if (!s && (KTYP(ov) == KT_SHIFT || KTYP(v) == KT_SHIFT))
+ compute_shiftstate();
+ break;
+ }
+ return 0;
+}
+#undef i
+#undef s
+#undef v
+
+static inline int
+do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm)
+{
+ struct kbkeycode tmp;
+ int kc = 0;
+
+ if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode)))
+ return -EFAULT;
+ switch (cmd) {
+ case KDGETKEYCODE:
+ kc = getkeycode(tmp.scancode);
+ if (kc >= 0)
+ kc = put_user(kc, &user_kbkc->keycode);
+ break;
+ case KDSETKEYCODE:
+ if (!perm)
+ return -EPERM;
+ kc = setkeycode(tmp.scancode, tmp.keycode);
+ break;
+ }
+ return kc;
+}
+
+static inline int
+do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
+{
+ struct kbsentry *kbs;
+ char *p;
+ u_char *q;
+ u_char __user *up;
+ int sz;
+ int delta;
+ char *first_free, *fj, *fnw;
+ int i, j, k;
+ int ret;
+
+ kbs = kmalloc(sizeof(*kbs), GFP_KERNEL);
+ if (!kbs) {
+ ret = -ENOMEM;
+ goto reterr;
+ }
+
+ /* we mostly copy too much here (512bytes), but who cares ;) */
+ if (copy_from_user(kbs, user_kdgkb, sizeof(struct kbsentry))) {
+ ret = -EFAULT;
+ goto reterr;
+ }
+ kbs->kb_string[sizeof(kbs->kb_string)-1] = '\0';
+ i = kbs->kb_func;
+
+ switch (cmd) {
+ case KDGKBSENT:
+ sz = sizeof(kbs->kb_string) - 1; /* sz should have been
+ a struct member */
+ up = user_kdgkb->kb_string;
+ p = func_table[i];
+ if(p)
+ for ( ; *p && sz; p++, sz--)
+ if (put_user(*p, up++)) {
+ ret = -EFAULT;
+ goto reterr;
+ }
+ if (put_user('\0', up)) {
+ ret = -EFAULT;
+ goto reterr;
+ }
+ kfree(kbs);
+ return ((p && *p) ? -EOVERFLOW : 0);
+ case KDSKBSENT:
+ if (!perm) {
+ ret = -EPERM;
+ goto reterr;
+ }
+
+ q = func_table[i];
+ first_free = funcbufptr + (funcbufsize - funcbufleft);
+ for (j = i+1; j < MAX_NR_FUNC && !func_table[j]; j++)
+ ;
+ if (j < MAX_NR_FUNC)
+ fj = func_table[j];
+ else
+ fj = first_free;
+
+ delta = (q ? -strlen(q) : 1) + strlen(kbs->kb_string);
+ if (delta <= funcbufleft) { /* it fits in current buf */
+ if (j < MAX_NR_FUNC) {
+ memmove(fj + delta, fj, first_free - fj);
+ for (k = j; k < MAX_NR_FUNC; k++)
+ if (func_table[k])
+ func_table[k] += delta;
+ }
+ if (!q)
+ func_table[i] = fj;
+ funcbufleft -= delta;
+ } else { /* allocate a larger buffer */
+ sz = 256;
+ while (sz < funcbufsize - funcbufleft + delta)
+ sz <<= 1;
+ fnw = (char *) kmalloc(sz, GFP_KERNEL);
+ if(!fnw) {
+ ret = -ENOMEM;
+ goto reterr;
+ }
+
+ if (!q)
+ func_table[i] = fj;
+ if (fj > funcbufptr)
+ memmove(fnw, funcbufptr, fj - funcbufptr);
+ for (k = 0; k < j; k++)
+ if (func_table[k])
+ func_table[k] = fnw + (func_table[k] - funcbufptr);
+
+ if (first_free > fj) {
+ memmove(fnw + (fj - funcbufptr) + delta, fj, first_free - fj);
+ for (k = j; k < MAX_NR_FUNC; k++)
+ if (func_table[k])
+ func_table[k] = fnw + (func_table[k] - funcbufptr) + delta;
+ }
+ if (funcbufptr != func_buf)
+ kfree(funcbufptr);
+ funcbufptr = fnw;
+ funcbufleft = funcbufleft - delta + sz - funcbufsize;
+ funcbufsize = sz;
+ }
+ strcpy(func_table[i], kbs->kb_string);
+ break;
+ }
+ ret = 0;
+reterr:
+ kfree(kbs);
+ return ret;
+}
+
+static inline int
+do_fontx_ioctl(int cmd, struct consolefontdesc __user *user_cfd, int perm, struct console_font_op *op)
+{
+ struct consolefontdesc cfdarg;
+ int i;
+
+ if (copy_from_user(&cfdarg, user_cfd, sizeof(struct consolefontdesc)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case PIO_FONTX:
+ if (!perm)
+ return -EPERM;
+ op->op = KD_FONT_OP_SET;
+ op->flags = KD_FONT_FLAG_OLD;
+ op->width = 8;
+ op->height = cfdarg.charheight;
+ op->charcount = cfdarg.charcount;
+ op->data = cfdarg.chardata;
+ return con_font_op(vc_cons[fg_console].d, op);
+ case GIO_FONTX: {
+ op->op = KD_FONT_OP_GET;
+ op->flags = KD_FONT_FLAG_OLD;
+ op->width = 8;
+ op->height = cfdarg.charheight;
+ op->charcount = cfdarg.charcount;
+ op->data = cfdarg.chardata;
+ i = con_font_op(vc_cons[fg_console].d, op);
+ if (i)
+ return i;
+ cfdarg.charheight = op->height;
+ cfdarg.charcount = op->charcount;
+ if (copy_to_user(user_cfd, &cfdarg, sizeof(struct consolefontdesc)))
+ return -EFAULT;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static inline int
+do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud, int perm, struct vc_data *vc)
+{
+ struct unimapdesc tmp;
+
+ if (copy_from_user(&tmp, user_ud, sizeof tmp))
+ return -EFAULT;
+ if (tmp.entries)
+ if (!access_ok(VERIFY_WRITE, tmp.entries,
+ tmp.entry_ct*sizeof(struct unipair)))
+ return -EFAULT;
+ switch (cmd) {
+ case PIO_UNIMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_unimap(vc, tmp.entry_ct, tmp.entries);
+ case GIO_UNIMAP:
+ if (!perm && fg_console != vc->vc_num)
+ return -EPERM;
+ return con_get_unimap(vc, tmp.entry_ct, &(user_ud->entry_ct), tmp.entries);
+ }
+ return 0;
+}
+
+/*
+ * We handle the console-specific ioctl's here. We allow the
+ * capability to modify any console, not just the fg_console.
+ */
+int vt_ioctl(struct tty_struct *tty, struct file * file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct vc_data *vc = (struct vc_data *)tty->driver_data;
+ struct console_font_op op; /* used in multiple places here */
+ struct kbd_struct * kbd;
+ unsigned int console;
+ unsigned char ucval;
+ void __user *up = (void __user *)arg;
+ int i, perm;
+
+ console = vc->vc_num;
+
+ if (!vc_cons_allocated(console)) /* impossible? */
+ return -ENOIOCTLCMD;
+
+ /*
+ * To have permissions to do most of the vt ioctls, we either have
+ * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
+ */
+ perm = 0;
+ if (current->signal->tty == tty || capable(CAP_SYS_TTY_CONFIG))
+ perm = 1;
+
+ kbd = kbd_table + console;
+ switch (cmd) {
+ case KIOCSOUND:
+ if (!perm)
+ return -EPERM;
+ if (arg)
+ arg = 1193182 / arg;
+ kd_mksound(arg, 0);
+ return 0;
+
+ case KDMKTONE:
+ if (!perm)
+ return -EPERM;
+ {
+ unsigned int ticks, count;
+
+ /*
+ * Generate the tone for the appropriate number of ticks.
+ * If the time is zero, turn off sound ourselves.
+ */
+ ticks = HZ * ((arg >> 16) & 0xffff) / 1000;
+ count = ticks ? (arg & 0xffff) : 0;
+ if (count)
+ count = 1193182 / count;
+ kd_mksound(count, ticks);
+ return 0;
+ }
+
+ case KDGKBTYPE:
+ /*
+ * this is naive.
+ */
+ ucval = KB_101;
+ goto setchar;
+
+ /*
+ * These cannot be implemented on any machine that implements
+ * ioperm() in user level (such as Alpha PCs) or not at all.
+ *
+ * XXX: you should never use these, just call ioperm directly..
+ */
+#ifdef CONFIG_X86
+ case KDADDIO:
+ case KDDELIO:
+ /*
+ * KDADDIO and KDDELIO may be able to add ports beyond what
+ * we reject here, but to be safe...
+ */
+ if (arg < GPFIRST || arg > GPLAST)
+ return -EINVAL;
+ return sys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;
+
+ case KDENABIO:
+ case KDDISABIO:
+ return sys_ioperm(GPFIRST, GPNUM,
+ (cmd == KDENABIO)) ? -ENXIO : 0;
+#endif
+
+ /* Linux m68k/i386 interface for setting the keyboard delay/repeat rate */
+
+ case KDKBDREP:
+ {
+ struct kbd_repeat kbrep;
+ int err;
+
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ return -EPERM;
+
+ if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat)))
+ return -EFAULT;
+ err = kbd_rate(&kbrep);
+ if (err)
+ return err;
+ if (copy_to_user(up, &kbrep, sizeof(struct kbd_repeat)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case KDSETMODE:
+ /*
+ * currently, setting the mode from KD_TEXT to KD_GRAPHICS
+ * doesn't do a whole lot. i'm not sure if it should do any
+ * restoration of modes or what...
+ *
+ * XXX It should at least call into the driver, fbdev's definitely
+ * need to restore their engine state. --BenH
+ */
+ if (!perm)
+ return -EPERM;
+ switch (arg) {
+ case KD_GRAPHICS:
+ break;
+ case KD_TEXT0:
+ case KD_TEXT1:
+ arg = KD_TEXT;
+ case KD_TEXT:
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (vc->vc_mode == (unsigned char) arg)
+ return 0;
+ vc->vc_mode = (unsigned char) arg;
+ if (console != fg_console)
+ return 0;
+ /*
+ * explicitly blank/unblank the screen if switching modes
+ */
+ acquire_console_sem();
+ if (arg == KD_TEXT)
+ do_unblank_screen(1);
+ else
+ do_blank_screen(1);
+ release_console_sem();
+ return 0;
+
+ case KDGETMODE:
+ ucval = vc->vc_mode;
+ goto setint;
+
+ case KDMAPDISP:
+ case KDUNMAPDISP:
+ /*
+ * these work like a combination of mmap and KDENABIO.
+ * this could be easily finished.
+ */
+ return -EINVAL;
+
+ case KDSKBMODE:
+ if (!perm)
+ return -EPERM;
+ switch(arg) {
+ case K_RAW:
+ kbd->kbdmode = VC_RAW;
+ break;
+ case K_MEDIUMRAW:
+ kbd->kbdmode = VC_MEDIUMRAW;
+ break;
+ case K_XLATE:
+ kbd->kbdmode = VC_XLATE;
+ compute_shiftstate();
+ break;
+ case K_UNICODE:
+ kbd->kbdmode = VC_UNICODE;
+ compute_shiftstate();
+ break;
+ default:
+ return -EINVAL;
+ }
+ tty_ldisc_flush(tty);
+ return 0;
+
+ case KDGKBMODE:
+ ucval = ((kbd->kbdmode == VC_RAW) ? K_RAW :
+ (kbd->kbdmode == VC_MEDIUMRAW) ? K_MEDIUMRAW :
+ (kbd->kbdmode == VC_UNICODE) ? K_UNICODE :
+ K_XLATE);
+ goto setint;
+
+ /* this could be folded into KDSKBMODE, but for compatibility
+ reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */
+ case KDSKBMETA:
+ switch(arg) {
+ case K_METABIT:
+ clr_vc_kbd_mode(kbd, VC_META);
+ break;
+ case K_ESCPREFIX:
+ set_vc_kbd_mode(kbd, VC_META);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+ case KDGKBMETA:
+ ucval = (vc_kbd_mode(kbd, VC_META) ? K_ESCPREFIX : K_METABIT);
+ setint:
+ return put_user(ucval, (int __user *)arg);
+
+ case KDGETKEYCODE:
+ case KDSETKEYCODE:
+ if(!capable(CAP_SYS_TTY_CONFIG))
+ perm=0;
+ return do_kbkeycode_ioctl(cmd, up, perm);
+
+ case KDGKBENT:
+ case KDSKBENT:
+ return do_kdsk_ioctl(cmd, up, perm, kbd);
+
+ case KDGKBSENT:
+ case KDSKBSENT:
+ return do_kdgkb_ioctl(cmd, up, perm);
+
+ case KDGKBDIACR:
+ {
+ struct kbdiacrs __user *a = up;
+
+ if (put_user(accent_table_size, &a->kb_cnt))
+ return -EFAULT;
+ if (copy_to_user(a->kbdiacr, accent_table, accent_table_size*sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case KDSKBDIACR:
+ {
+ struct kbdiacrs __user *a = up;
+ unsigned int ct;
+
+ if (!perm)
+ return -EPERM;
+ if (get_user(ct,&a->kb_cnt))
+ return -EFAULT;
+ if (ct >= MAX_DIACR)
+ return -EINVAL;
+ accent_table_size = ct;
+ if (copy_from_user(accent_table, a->kbdiacr, ct*sizeof(struct kbdiacr)))
+ return -EFAULT;
+ return 0;
+ }
+
+ /* the ioctls below read/set the flags usually shown in the leds */
+ /* don't use them - they will go away without warning */
+ case KDGKBLED:
+ ucval = kbd->ledflagstate | (kbd->default_ledflagstate << 4);
+ goto setchar;
+
+ case KDSKBLED:
+ if (!perm)
+ return -EPERM;
+ if (arg & ~0x77)
+ return -EINVAL;
+ kbd->ledflagstate = (arg & 7);
+ kbd->default_ledflagstate = ((arg >> 4) & 7);
+ set_leds();
+ return 0;
+
+ /* the ioctls below only set the lights, not the functions */
+ /* for those, see KDGKBLED and KDSKBLED above */
+ case KDGETLED:
+ ucval = getledstate();
+ setchar:
+ return put_user(ucval, (char __user *)arg);
+
+ case KDSETLED:
+ if (!perm)
+ return -EPERM;
+ setledstate(kbd, arg);
+ return 0;
+
+ /*
+ * A process can indicate its willingness to accept signals
+ * generated by pressing an appropriate key combination.
+ * Thus, one can have a daemon that e.g. spawns a new console
+ * upon a keypress and then changes to it.
+ * See also the kbrequest field of inittab(5).
+ */
+ case KDSIGACCEPT:
+ {
+ extern int spawnpid, spawnsig;
+ if (!perm || !capable(CAP_KILL))
+ return -EPERM;
+ if (arg < 1 || arg > _NSIG || arg == SIGKILL)
+ return -EINVAL;
+ spawnpid = current->pid;
+ spawnsig = arg;
+ return 0;
+ }
+
+ case VT_SETMODE:
+ {
+ struct vt_mode tmp;
+
+ if (!perm)
+ return -EPERM;
+ if (copy_from_user(&tmp, up, sizeof(struct vt_mode)))
+ return -EFAULT;
+ if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS)
+ return -EINVAL;
+ acquire_console_sem();
+ vc->vt_mode = tmp;
+ /* the frsig is ignored, so we set it to 0 */
+ vc->vt_mode.frsig = 0;
+ vc->vt_pid = current->pid;
+ /* no switch is required -- saw@shade.msu.ru */
+ vc->vt_newvt = -1;
+ release_console_sem();
+ return 0;
+ }
+
+ case VT_GETMODE:
+ {
+ struct vt_mode tmp;
+ int rc;
+
+ acquire_console_sem();
+ memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
+ release_console_sem();
+
+ rc = copy_to_user(up, &tmp, sizeof(struct vt_mode));
+ return rc ? -EFAULT : 0;
+ }
+
+ /*
+ * Returns global vt state. Note that VT 0 is always open, since
+ * it's an alias for the current VT, and people can't use it here.
+ * We cannot return state for more than 16 VTs, since v_state is short.
+ */
+ case VT_GETSTATE:
+ {
+ struct vt_stat __user *vtstat = up;
+ unsigned short state, mask;
+
+ if (put_user(fg_console + 1, &vtstat->v_active))
+ return -EFAULT;
+ state = 1; /* /dev/tty0 is always open */
+ for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; ++i, mask <<= 1)
+ if (VT_IS_IN_USE(i))
+ state |= mask;
+ return put_user(state, &vtstat->v_state);
+ }
+
+ /*
+ * Returns the first available (non-opened) console.
+ */
+ case VT_OPENQRY:
+ for (i = 0; i < MAX_NR_CONSOLES; ++i)
+ if (! VT_IS_IN_USE(i))
+ break;
+ ucval = i < MAX_NR_CONSOLES ? (i+1) : -1;
+ goto setint;
+
+ /*
+ * ioctl(fd, VT_ACTIVATE, num) will cause us to switch to vt # num,
+ * with num >= 1 (switches to vt 0, our console, are not allowed, just
+ * to preserve sanity).
+ */
+ case VT_ACTIVATE:
+ if (!perm)
+ return -EPERM;
+ if (arg == 0 || arg > MAX_NR_CONSOLES)
+ return -ENXIO;
+ arg--;
+ acquire_console_sem();
+ i = vc_allocate(arg);
+ release_console_sem();
+ if (i)
+ return i;
+ set_console(arg);
+ return 0;
+
+ /*
+ * wait until the specified VT has been activated
+ */
+ case VT_WAITACTIVE:
+ if (!perm)
+ return -EPERM;
+ if (arg == 0 || arg > MAX_NR_CONSOLES)
+ return -ENXIO;
+ return vt_waitactive(arg-1);
+
+ /*
+ * If a vt is under process control, the kernel will not switch to it
+ * immediately, but postpone the operation until the process calls this
+ * ioctl, allowing the switch to complete.
+ *
+ * According to the X sources this is the behavior:
+ * 0: pending switch-from not OK
+ * 1: pending switch-from OK
+ * 2: completed switch-to OK
+ */
+ case VT_RELDISP:
+ if (!perm)
+ return -EPERM;
+ if (vc->vt_mode.mode != VT_PROCESS)
+ return -EINVAL;
+
+ /*
+ * Switching-from response
+ */
+ if (vc->vt_newvt >= 0) {
+ if (arg == 0)
+ /*
+ * Switch disallowed, so forget we were trying
+ * to do it.
+ */
+ vc->vt_newvt = -1;
+
+ else {
+ /*
+ * The current vt has been released, so
+ * complete the switch.
+ */
+ int newvt;
+ acquire_console_sem();
+ newvt = vc->vt_newvt;
+ vc->vt_newvt = -1;
+ i = vc_allocate(newvt);
+ if (i) {
+ release_console_sem();
+ return i;
+ }
+ /*
+ * When we actually do the console switch,
+ * make sure we are atomic with respect to
+ * other console switches..
+ */
+ complete_change_console(vc_cons[newvt].d);
+ release_console_sem();
+ }
+ }
+
+ /*
+ * Switched-to response
+ */
+ else
+ {
+ /*
+ * If it's just an ACK, ignore it
+ */
+ if (arg != VT_ACKACQ)
+ return -EINVAL;
+ }
+
+ return 0;
+
+ /*
+ * Disallocate memory associated to VT (but leave VT1)
+ */
+ case VT_DISALLOCATE:
+ if (arg > MAX_NR_CONSOLES)
+ return -ENXIO;
+ if (arg == 0) {
+ /* disallocate all unused consoles, but leave 0 */
+ acquire_console_sem();
+ for (i=1; i<MAX_NR_CONSOLES; i++)
+ if (! VT_BUSY(i))
+ vc_disallocate(i);
+ release_console_sem();
+ } else {
+ /* disallocate a single console, if possible */
+ arg--;
+ if (VT_BUSY(arg))
+ return -EBUSY;
+ if (arg) { /* leave 0 */
+ acquire_console_sem();
+ vc_disallocate(arg);
+ release_console_sem();
+ }
+ }
+ return 0;
+
+ case VT_RESIZE:
+ {
+ struct vt_sizes __user *vtsizes = up;
+ ushort ll,cc;
+ if (!perm)
+ return -EPERM;
+ if (get_user(ll, &vtsizes->v_rows) ||
+ get_user(cc, &vtsizes->v_cols))
+ return -EFAULT;
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ acquire_console_sem();
+ vc_resize(vc_cons[i].d, cc, ll);
+ release_console_sem();
+ }
+ return 0;
+ }
+
+ case VT_RESIZEX:
+ {
+ struct vt_consize __user *vtconsize = up;
+ ushort ll,cc,vlin,clin,vcol,ccol;
+ if (!perm)
+ return -EPERM;
+ if (!access_ok(VERIFY_READ, vtconsize,
+ sizeof(struct vt_consize)))
+ return -EFAULT;
+ __get_user(ll, &vtconsize->v_rows);
+ __get_user(cc, &vtconsize->v_cols);
+ __get_user(vlin, &vtconsize->v_vlin);
+ __get_user(clin, &vtconsize->v_clin);
+ __get_user(vcol, &vtconsize->v_vcol);
+ __get_user(ccol, &vtconsize->v_ccol);
+ vlin = vlin ? vlin : vc->vc_scan_lines;
+ if (clin) {
+ if (ll) {
+ if (ll != vlin/clin)
+ return -EINVAL; /* Parameters don't add up */
+ } else
+ ll = vlin/clin;
+ }
+ if (vcol && ccol) {
+ if (cc) {
+ if (cc != vcol/ccol)
+ return -EINVAL;
+ } else
+ cc = vcol/ccol;
+ }
+
+ if (clin > 32)
+ return -EINVAL;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ if (!vc_cons[i].d)
+ continue;
+ acquire_console_sem();
+ if (vlin)
+ vc_cons[i].d->vc_scan_lines = vlin;
+ if (clin)
+ vc_cons[i].d->vc_font.height = clin;
+ vc_resize(vc_cons[i].d, cc, ll);
+ release_console_sem();
+ }
+ return 0;
+ }
+
+ case PIO_FONT: {
+ if (!perm)
+ return -EPERM;
+ op.op = KD_FONT_OP_SET;
+ op.flags = KD_FONT_FLAG_OLD | KD_FONT_FLAG_DONT_RECALC; /* Compatibility */
+ op.width = 8;
+ op.height = 0;
+ op.charcount = 256;
+ op.data = up;
+ return con_font_op(vc_cons[fg_console].d, &op);
+ }
+
+ case GIO_FONT: {
+ op.op = KD_FONT_OP_GET;
+ op.flags = KD_FONT_FLAG_OLD;
+ op.width = 8;
+ op.height = 32;
+ op.charcount = 256;
+ op.data = up;
+ return con_font_op(vc_cons[fg_console].d, &op);
+ }
+
+ case PIO_CMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_cmap(up);
+
+ case GIO_CMAP:
+ return con_get_cmap(up);
+
+ case PIO_FONTX:
+ case GIO_FONTX:
+ return do_fontx_ioctl(cmd, up, perm, &op);
+
+ case PIO_FONTRESET:
+ {
+ if (!perm)
+ return -EPERM;
+
+#ifdef BROKEN_GRAPHICS_PROGRAMS
+ /* With BROKEN_GRAPHICS_PROGRAMS defined, the default
+ font is not saved. */
+ return -ENOSYS;
+#else
+ {
+ op.op = KD_FONT_OP_SET_DEFAULT;
+ op.data = NULL;
+ i = con_font_op(vc_cons[fg_console].d, &op);
+ if (i)
+ return i;
+ con_set_default_unimap(vc_cons[fg_console].d);
+ return 0;
+ }
+#endif
+ }
+
+ case KDFONTOP: {
+ if (copy_from_user(&op, up, sizeof(op)))
+ return -EFAULT;
+ if (!perm && op.op != KD_FONT_OP_GET)
+ return -EPERM;
+ i = con_font_op(vc, &op);
+ if (i) return i;
+ if (copy_to_user(up, &op, sizeof(op)))
+ return -EFAULT;
+ return 0;
+ }
+
+ case PIO_SCRNMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_trans_old(up);
+
+ case GIO_SCRNMAP:
+ return con_get_trans_old(up);
+
+ case PIO_UNISCRNMAP:
+ if (!perm)
+ return -EPERM;
+ return con_set_trans_new(up);
+
+ case GIO_UNISCRNMAP:
+ return con_get_trans_new(up);
+
+ case PIO_UNIMAPCLR:
+ { struct unimapinit ui;
+ if (!perm)
+ return -EPERM;
+ i = copy_from_user(&ui, up, sizeof(struct unimapinit));
+ if (i) return -EFAULT;
+ con_clear_unimap(vc, &ui);
+ return 0;
+ }
+
+ case PIO_UNIMAP:
+ case GIO_UNIMAP:
+ return do_unimap_ioctl(cmd, up, perm, vc);
+
+ case VT_LOCKSWITCH:
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ return -EPERM;
+ vt_dont_switch = 1;
+ return 0;
+ case VT_UNLOCKSWITCH:
+ if (!capable(CAP_SYS_TTY_CONFIG))
+ return -EPERM;
+ vt_dont_switch = 0;
+ return 0;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+/*
+ * Sometimes we want to wait until a particular VT has been activated. We
+ * do it in a very simple manner. Everybody waits on a single queue and
+ * get woken up at once. Those that are satisfied go on with their business,
+ * while those not ready go back to sleep. Seems overkill to add a wait
+ * to each vt just for this - usually this does nothing!
+ */
+static DECLARE_WAIT_QUEUE_HEAD(vt_activate_queue);
+
+/*
+ * Sleeps until a vt is activated, or the task is interrupted. Returns
+ * 0 if activation, -EINTR if interrupted.
+ */
+int vt_waitactive(int vt)
+{
+ int retval;
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(&vt_activate_queue, &wait);
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ retval = 0;
+ if (vt == fg_console)
+ break;
+ retval = -EINTR;
+ if (signal_pending(current))
+ break;
+ schedule();
+ }
+ remove_wait_queue(&vt_activate_queue, &wait);
+ current->state = TASK_RUNNING;
+ return retval;
+}
+
+#define vt_wake_waitactive() wake_up(&vt_activate_queue)
+
+void reset_vc(struct vc_data *vc)
+{
+ vc->vc_mode = KD_TEXT;
+ kbd_table[vc->vc_num].kbdmode = VC_XLATE;
+ vc->vt_mode.mode = VT_AUTO;
+ vc->vt_mode.waitv = 0;
+ vc->vt_mode.relsig = 0;
+ vc->vt_mode.acqsig = 0;
+ vc->vt_mode.frsig = 0;
+ vc->vt_pid = -1;
+ vc->vt_newvt = -1;
+ if (!in_interrupt()) /* Via keyboard.c:SAK() - akpm */
+ reset_palette(vc);
+}
+
+/*
+ * Performs the back end of a vt switch
+ */
+static void complete_change_console(struct vc_data *vc)
+{
+ unsigned char old_vc_mode;
+
+ last_console = fg_console;
+
+ /*
+ * If we're switching, we could be going from KD_GRAPHICS to
+ * KD_TEXT mode or vice versa, which means we need to blank or
+ * unblank the screen later.
+ */
+ old_vc_mode = vc_cons[fg_console].d->vc_mode;
+ switch_screen(vc);
+
+ /*
+ * This can't appear below a successful kill_proc(). If it did,
+ * then the *blank_screen operation could occur while X, having
+ * received acqsig, is waking up on another processor. This
+ * condition can lead to overlapping accesses to the VGA range
+ * and the framebuffer (causing system lockups).
+ *
+ * To account for this we duplicate this code below only if the
+ * controlling process is gone and we've called reset_vc.
+ */
+ if (old_vc_mode != vc->vc_mode) {
+ if (vc->vc_mode == KD_TEXT)
+ do_unblank_screen(1);
+ else
+ do_blank_screen(1);
+ }
+
+ /*
+ * If this new console is under process control, send it a signal
+ * telling it that it has acquired. Also check if it has died and
+ * clean up (similar to logic employed in change_console())
+ */
+ if (vc->vt_mode.mode == VT_PROCESS) {
+ /*
+ * Send the signal as privileged - kill_proc() will
+ * tell us if the process has gone or something else
+ * is awry
+ */
+ if (kill_proc(vc->vt_pid, vc->vt_mode.acqsig, 1) != 0) {
+ /*
+ * The controlling process has died, so we revert back to
+ * normal operation. In this case, we'll also change back
+ * to KD_TEXT mode. I'm not sure if this is strictly correct
+ * but it saves the agony when the X server dies and the screen
+ * remains blanked due to KD_GRAPHICS! It would be nice to do
+ * this outside of VT_PROCESS but there is no single process
+ * to account for and tracking tty count may be undesirable.
+ */
+ reset_vc(vc);
+
+ if (old_vc_mode != vc->vc_mode) {
+ if (vc->vc_mode == KD_TEXT)
+ do_unblank_screen(1);
+ else
+ do_blank_screen(1);
+ }
+ }
+ }
+
+ /*
+ * Wake anyone waiting for their VT to activate
+ */
+ vt_wake_waitactive();
+ return;
+}
+
+/*
+ * Performs the front-end of a vt switch
+ */
+void change_console(struct vc_data *new_vc)
+{
+ struct vc_data *vc;
+
+ if (!new_vc || new_vc->vc_num == fg_console || vt_dont_switch)
+ return;
+
+ /*
+ * If this vt is in process mode, then we need to handshake with
+ * that process before switching. Essentially, we store where that
+ * vt wants to switch to and wait for it to tell us when it's done
+ * (via VT_RELDISP ioctl).
+ *
+ * We also check to see if the controlling process still exists.
+ * If it doesn't, we reset this vt to auto mode and continue.
+ * This is a cheap way to track process control. The worst thing
+ * that can happen is: we send a signal to a process, it dies, and
+ * the switch gets "lost" waiting for a response; hopefully, the
+ * user will try again, we'll detect the process is gone (unless
+ * the user waits just the right amount of time :-) and revert the
+ * vt to auto control.
+ */
+ vc = vc_cons[fg_console].d;
+ if (vc->vt_mode.mode == VT_PROCESS) {
+ /*
+ * Send the signal as privileged - kill_proc() will
+ * tell us if the process has gone or something else
+ * is awry
+ */
+ if (kill_proc(vc->vt_pid, vc->vt_mode.relsig, 1) == 0) {
+ /*
+ * It worked. Mark the vt to switch to and
+ * return. The process needs to send us a
+ * VT_RELDISP ioctl to complete the switch.
+ */
+ vc->vt_newvt = new_vc->vc_num;
+ return;
+ }
+
+ /*
+ * The controlling process has died, so we revert back to
+ * normal operation. In this case, we'll also change back
+ * to KD_TEXT mode. I'm not sure if this is strictly correct
+ * but it saves the agony when the X server dies and the screen
+ * remains blanked due to KD_GRAPHICS! It would be nice to do
+ * this outside of VT_PROCESS but there is no single process
+ * to account for and tracking tty count may be undesirable.
+ */
+ reset_vc(vc);
+
+ /*
+ * Fall through to normal (VT_AUTO) handling of the switch...
+ */
+ }
+
+ /*
+ * Ignore all switches in KD_GRAPHICS+VT_AUTO mode
+ */
+ if (vc->vc_mode == KD_GRAPHICS)
+ return;
+
+ complete_change_console(new_vc);
+}
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig
new file mode 100644
index 000000000000..06a31da2381c
--- /dev/null
+++ b/drivers/char/watchdog/Kconfig
@@ -0,0 +1,549 @@
+#
+# Watchdog device configuration
+#
+
+menu "Watchdog Cards"
+
+config WATCHDOG
+ bool "Watchdog Timer Support"
+ ---help---
+ If you say Y here (and to one of the following options) and create a
+ character special file /dev/watchdog with major number 10 and minor
+ number 130 using mknod ("man mknod"), you will get a watchdog, i.e.:
+ subsequently opening the file and then failing to write to it for
+ longer than 1 minute will result in rebooting the machine. This
+ could be useful for a networked machine that needs to come back
+ online as fast as possible after a lock-up. There's both a watchdog
+ implementation entirely in software (which can sometimes fail to
+ reboot the machine) and a driver for hardware watchdog boards, which
+ are more robust and can also keep track of the temperature inside
+ your computer. For details, read <file:Documentation/watchdog/watchdog.txt>
+ in the kernel source.
+
+ The watchdog is usually used together with the watchdog daemon
+ which is available from
+ <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can
+ also monitor NFS connections and can reboot the machine when the process
+ table is full.
+
+ If unsure, say N.
+
+config WATCHDOG_NOWAYOUT
+ bool "Disable watchdog shutdown on close"
+ depends on WATCHDOG
+ help
+ The default watchdog behaviour (which you get if you say N here) is
+ to stop the timer if the process managing it closes the file
+ /dev/watchdog. It's always remotely possible that this process might
+ get killed. If you say Y here, the watchdog cannot be stopped once
+ it has been started.
+
+#
+# General Watchdog drivers
+#
+
+comment "Watchdog Device Drivers"
+ depends on WATCHDOG
+
+# Architecture Independant
+
+config SOFT_WATCHDOG
+ tristate "Software watchdog"
+ depends on WATCHDOG
+ help
+ A software monitoring watchdog. This will fail to reboot your system
+ from some situations that the hardware watchdog will recover
+ from. Equally it's a lot cheaper to install.
+
+ To compile this driver as a module, choose M here: the
+ module will be called softdog.
+
+# ARM Architecture
+
+config 21285_WATCHDOG
+ tristate "DC21285 watchdog"
+ depends on WATCHDOG && FOOTBRIDGE
+ help
+ The Intel Footbridge chip contains a builtin watchdog circuit. Say Y
+ here if you wish to use this. Alternatively say M to compile the
+ driver as a module, which will be called wdt285.
+
+ This driver does not work on all machines. In particular, early CATS
+ boards have hardware problems that will cause the machine to simply
+ lock up if the watchdog fires.
+
+ "If in doubt, leave it out" - say N.
+
+config 977_WATCHDOG
+ tristate "NetWinder WB83C977 watchdog"
+ depends on WATCHDOG && FOOTBRIDGE && ARCH_NETWINDER
+ help
+ Say Y here to include support for the WB977 watchdog included in
+ NetWinder machines. Alternatively say M to compile the driver as
+ a module, which will be called wdt977.
+
+ Not sure? It's safe to say N.
+
+config IXP4XX_WATCHDOG
+ tristate "IXP4xx Watchdog"
+ depends on WATCHDOG && ARCH_IXP4XX
+ help
+ Say Y here if to include support for the watchdog timer
+ in the Intel IXP4xx network processors. This driver can
+ be built as a module by choosing M. The module will
+ be called ixp4xx_wdt.
+
+ Note: The internal IXP4xx watchdog does a soft CPU reset
+ which doesn't reset any peripherals. There are circumstances
+ where the watchdog will fail to reset the board correctly
+ (e.g., if the boot ROM is in an unreadable state).
+
+ Say N if you are unsure.
+
+config IXP2000_WATCHDOG
+ tristate "IXP2000 Watchdog"
+ depends on WATCHDOG && ARCH_IXP2000
+ help
+ Say Y here if to include support for the watchdog timer
+ in the Intel IXP2000(2400, 2800, 2850) network processors.
+ This driver can be built as a module by choosing M. The module
+ will be called ixp2000_wdt.
+
+ Say N if you are unsure.
+
+config S3C2410_WATCHDOG
+ tristate "S3C2410 Watchdog"
+ depends on WATCHDOG && ARCH_S3C2410
+ help
+ Watchdog timer block in the Samsung S3C2410 chips. This will
+ reboot the system when the timer expires with the watchdog
+ enabled.
+
+ The driver is limited by the speed of the system's PCLK
+ signal, so with reasonbaly fast systems (PCLK around 50-66MHz)
+ then watchdog intervals of over approximately 20seconds are
+ unavailable.
+
+ The driver can be built as a module by choosing M, and will
+ be called s3c2410_wdt
+
+config SA1100_WATCHDOG
+ tristate "SA1100/PXA2xx watchdog"
+ depends on WATCHDOG && ( ARCH_SA1100 || ARCH_PXA )
+ help
+ Watchdog timer embedded into SA11x0 and PXA2xx chips. This will
+ reboot your system when timeout is reached.
+
+ NOTE: once enabled, this timer cannot be disabled.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sa1100_wdt.
+
+# X86 (i386 + ia64 + x86_64) Architecture
+
+config ACQUIRE_WDT
+ tristate "Acquire SBC Watchdog Timer"
+ depends on WATCHDOG && X86
+ ---help---
+ This is the driver for the hardware watchdog on Single Board
+ Computers produced by Acquire Inc (and others). This watchdog
+ simply watches your kernel to make sure it doesn't freeze, and if
+ it does, it reboots your computer after a certain amount of time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called acquirewdt.
+
+ Most people will say N.
+
+config ADVANTECH_WDT
+ tristate "Advantech SBC Watchdog Timer"
+ depends on WATCHDOG && X86
+ help
+ If you are configuring a Linux kernel for the Advantech single-board
+ computer, say `Y' here to support its built-in watchdog timer
+ feature. More information can be found at
+ <http://www.advantech.com.tw/products/>
+
+config ALIM1535_WDT
+ tristate "ALi M1535 PMU Watchdog Timer"
+ depends on WATCHDOG && X86 && PCI
+ ---help---
+ This is the driver for the hardware watchdog on the ALi M1535 PMU.
+
+ To compile this driver as a module, choose M here: the
+ module will be called alim1535_wdt.
+
+ Most people will say N.
+
+config ALIM7101_WDT
+ tristate "ALi M7101 PMU Computer Watchdog"
+ depends on WATCHDOG && X86 && PCI
+ help
+ This is the driver for the hardware watchdog on the ALi M7101 PMU
+ as used in the x86 Cobalt servers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called alim7101_wdt.
+
+ Most people will say N.
+
+config SC520_WDT
+ tristate "AMD Elan SC520 processor Watchdog"
+ depends on WATCHDOG && X86
+ help
+ This is the driver for the hardware watchdog built in to the
+ AMD "Elan" SC520 microcomputer commonly used in embedded systems.
+ This watchdog simply watches your kernel to make sure it doesn't
+ freeze, and if it does, it reboots your computer after a certain
+ amount of time.
+
+ You can compile this driver directly into the kernel, or use
+ it as a module. The module will be called sc520_wdt.
+
+config EUROTECH_WDT
+ tristate "Eurotech CPU-1220/1410 Watchdog Timer"
+ depends on WATCHDOG && X86
+ help
+ Enable support for the watchdog timer on the Eurotech CPU-1220 and
+ CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product
+ information are at <http://www.eurotech.it/>.
+
+config IB700_WDT
+ tristate "IB700 SBC Watchdog Timer"
+ depends on WATCHDOG && X86
+ ---help---
+ This is the driver for the hardware watchdog on the IB700 Single
+ Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog
+ simply watches your kernel to make sure it doesn't freeze, and if
+ it does, it reboots your computer after a certain amount of time.
+
+ This driver is like the WDT501 driver but for slightly different hardware.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ib700wdt.
+
+ Most people will say N.
+
+config WAFER_WDT
+ tristate "ICP Wafer 5823 Single Board Computer Watchdog"
+ depends on WATCHDOG && X86
+ help
+ This is a driver for the hardware watchdog on the ICP Wafer 5823
+ Single Board Computer (and probably other similar models).
+
+ To compile this driver as a module, choose M here: the
+ module will be called wafer5823wdt.
+
+config I8XX_TCO
+ tristate "Intel i8xx TCO Timer/Watchdog"
+ depends on WATCHDOG && (X86 || IA64) && PCI
+ ---help---
+ Hardware driver for the TCO timer built into the Intel 82801
+ I/O Controller Hub family. The TCO (Total Cost of Ownership)
+ timer is a watchdog timer that will reboot the machine after
+ its second expiration. The expiration time can be configured
+ with the "heartbeat" parameter.
+
+ On some motherboards the driver may fail to reset the chipset's
+ NO_REBOOT flag which prevents the watchdog from rebooting the
+ machine. If this is the case you will get a kernel message like
+ "failed to reset NO_REBOOT flag, reboot disabled by hardware".
+
+ To compile this driver as a module, choose M here: the
+ module will be called i8xx_tco.
+
+config SC1200_WDT
+ tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog"
+ depends on WATCHDOG && X86
+ help
+ This is a driver for National Semiconductor PC87307/PC97307 hardware
+ watchdog cards as found on the SC1200. This watchdog is mainly used
+ for power management purposes and can be used to power down the device
+ during inactivity periods (includes interrupt activity monitoring).
+
+ To compile this driver as a module, choose M here: the
+ module will be called sc1200wdt.
+
+ Most people will say N.
+
+config SCx200_WDT
+ tristate "National Semiconductor SCx200 Watchdog"
+ depends on WATCHDOG && SCx200 && PCI
+ help
+ Enable the built-in watchdog timer support on the National
+ Semiconductor SCx200 processors.
+
+ If compiled as a module, it will be called scx200_wdt.
+
+config 60XX_WDT
+ tristate "SBC-60XX Watchdog Timer"
+ depends on WATCHDOG && X86
+ help
+ This driver can be used with the watchdog timer found on some
+ single board computers, namely the 6010 PII based computer.
+ It may well work with other cards. It reads port 0x443 to enable
+ and re-set the watchdog timer, and reads port 0x45 to disable
+ the watchdog. If you have a card that behave in similar ways,
+ you can probably make this driver work with your card as well.
+
+ You can compile this driver directly into the kernel, or use
+ it as a module. The module will be called sbc60xxwdt.
+
+config CPU5_WDT
+ tristate "SMA CPU5 Watchdog"
+ depends on WATCHDOG && X86
+ ---help---
+ TBD.
+ To compile this driver as a module, choose M here: the
+ module will be called cpu5wdt.
+
+config W83627HF_WDT
+ tristate "W83627HF Watchdog Timer"
+ depends on WATCHDOG && X86
+ ---help---
+ This is the driver for the hardware watchdog on the W83627HF chipset
+ as used in Advantech PC-9578 and Tyan S2721-533 motherboards
+ (and likely others). This watchdog simply watches your kernel to
+ make sure it doesn't freeze, and if it does, it reboots your computer
+ after a certain amount of time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called w83627hf_wdt.
+
+ Most people will say N.
+
+config W83877F_WDT
+ tristate "W83877F (EMACS) Watchdog Timer"
+ depends on WATCHDOG && X86
+ ---help---
+ This is the driver for the hardware watchdog on the W83877F chipset
+ as used in EMACS PC-104 motherboards (and likely others). This
+ watchdog simply watches your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your computer after a certain amount of
+ time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called w83877f_wdt.
+
+ Most people will say N.
+
+config MACHZ_WDT
+ tristate "ZF MachZ Watchdog"
+ depends on WATCHDOG && X86
+ ---help---
+ If you are using a ZF Micro MachZ processor, say Y here, otherwise
+ N. This is the driver for the watchdog timer builtin on that
+ processor using ZF-Logic interface. This watchdog simply watches
+ your kernel to make sure it doesn't freeze, and if it does, it
+ reboots your computer after a certain amount of time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called machzwd.
+
+# PowerPC Architecture
+
+config 8xx_WDT
+ tristate "MPC8xx Watchdog Timer"
+ depends on WATCHDOG && 8xx
+
+# MIPS Architecture
+
+config INDYDOG
+ tristate "Indy/I2 Hardware Watchdog"
+ depends on WATCHDOG && SGI_IP22
+ help
+ Hardwaredriver for the Indy's/I2's watchdog. This is a
+ watchdog timer that will reboot the machine after a 60 second
+ timer expired and no process has written to /dev/watchdog during
+ that time.
+
+# S390 Architecture
+
+config ZVM_WATCHDOG
+ tristate "z/VM Watchdog Timer"
+ depends on WATCHDOG && ARCH_S390
+ help
+ IBM s/390 and zSeries machines running under z/VM 5.1 or later
+ provide a virtual watchdog timer to their guest that cause a
+ user define Control Program command to be executed after a
+ timeout.
+
+ To compile this driver as a module, choose M here. The module
+ will be called vmwatchdog.
+
+# SUPERH Architecture
+
+config SH_WDT
+ tristate "SuperH Watchdog"
+ depends on WATCHDOG && SUPERH
+ help
+ This driver adds watchdog support for the integrated watchdog in the
+ SuperH processors. If you have one of these processors and wish
+ to have watchdog support enabled, say Y, otherwise say N.
+
+ As a side note, saying Y here will automatically boost HZ to 1000
+ so that the timer has a chance to clear the overflow counter. On
+ slower systems (such as the SH-2 and SH-3) this will likely yield
+ some performance issues. As such, the WDT should be avoided here
+ unless it is absolutely necessary.
+
+ To compile this driver as a module, choose M here: the
+ module will be called shwdt.
+
+# SPARC64 Architecture
+
+config WATCHDOG_CP1XXX
+ tristate "CP1XXX Hardware Watchdog support"
+ depends on WATCHDOG && SPARC64 && PCI
+ ---help---
+ This is the driver for the hardware watchdog timers present on
+ Sun Microsystems CompactPCI models CP1400 and CP1500.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cpwatchdog.
+
+ If you do not have a CompactPCI model CP1400 or CP1500, or
+ another UltraSPARC-IIi-cEngine boardset with hardware watchdog,
+ you should say N to this option.
+
+config WATCHDOG_RIO
+ tristate "RIO Hardware Watchdog support"
+ depends on WATCHDOG && SPARC64 && PCI
+ help
+ Say Y here to support the hardware watchdog capability on Sun RIO
+ machines. The watchdog timeout period is normally one minute but
+ can be changed with a boot-time parameter.
+
+#
+# ISA-based Watchdog Cards
+#
+
+comment "ISA-based Watchdog Cards"
+ depends on WATCHDOG && ISA
+
+config PCWATCHDOG
+ tristate "Berkshire Products ISA-PC Watchdog"
+ depends on WATCHDOG && ISA
+ ---help---
+ This is the driver for the Berkshire Products ISA-PC Watchdog card.
+ This card simply watches your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your computer after a certain amount of
+ time. This driver is like the WDT501 driver but for different
+ hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC
+ watchdog cards can be ordered from <http://www.berkprod.com/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd.
+
+ Most people will say N.
+
+config MIXCOMWD
+ tristate "Mixcom Watchdog"
+ depends on WATCHDOG && ISA
+ ---help---
+ This is a driver for the Mixcom hardware watchdog cards. This
+ watchdog simply watches your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your computer after a certain amount of
+ time.
+
+ To compile this driver as a module, choose M here: the
+ module will be called mixcomwd.
+
+ Most people will say N.
+
+config WDT
+ tristate "WDT Watchdog timer"
+ depends on WATCHDOG && ISA
+ ---help---
+ If you have a WDT500P or WDT501P watchdog board, say Y here,
+ otherwise N. It is not possible to probe for this board, which means
+ that you have to inform the kernel about the IO port and IRQ that
+ is needed (you can do this via the io and irq parameters)
+
+ To compile this driver as a module, choose M here: the
+ module will be called wdt.
+
+config WDT_501
+ bool "WDT501 features"
+ depends on WDT
+ help
+ Saying Y here and creating a character special file /dev/temperature
+ with major number 10 and minor number 131 ("man mknod") will give
+ you a thermometer inside your computer: reading from
+ /dev/temperature yields one byte, the temperature in degrees
+ Fahrenheit. This works only if you have a WDT501P watchdog board
+ installed.
+
+ If you want to enable the Fan Tachometer on the WDT501P, then you
+ can do this via the tachometer parameter. Only do this if you have a
+ fan tachometer actually set up.
+
+#
+# PCI-based Watchdog Cards
+#
+
+comment "PCI-based Watchdog Cards"
+ depends on WATCHDOG && PCI
+
+config PCIPCWATCHDOG
+ tristate "Berkshire Products PCI-PC Watchdog"
+ depends on WATCHDOG && PCI
+ ---help---
+ This is the driver for the Berkshire Products PCI-PC Watchdog card.
+ This card simply watches your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your computer after a certain amount of
+ time. The card can also monitor the internal temperature of the PC.
+ More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd_pci.
+
+ Most people will say N.
+
+config WDTPCI
+ tristate "PCI-WDT500/501 Watchdog timer"
+ depends on WATCHDOG && PCI
+ ---help---
+ If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called wdt_pci.
+
+config WDT_501_PCI
+ bool "PCI-WDT501 features"
+ depends on WDTPCI
+ help
+ Saying Y here and creating a character special file /dev/temperature
+ with major number 10 and minor number 131 ("man mknod") will give
+ you a thermometer inside your computer: reading from
+ /dev/temperature yields one byte, the temperature in degrees
+ Fahrenheit. This works only if you have a PCI-WDT501 watchdog board
+ installed.
+
+ If you want to enable the Fan Tachometer on the PCI-WDT501, then you
+ can do this via the tachometer parameter. Only do this if you have a
+ fan tachometer actually set up.
+
+#
+# USB-based Watchdog Cards
+#
+
+comment "USB-based Watchdog Cards"
+ depends on WATCHDOG && USB
+
+config USBPCWATCHDOG
+ tristate "Berkshire Products USB-PC Watchdog"
+ depends on WATCHDOG && USB
+ ---help---
+ This is the driver for the Berkshire Products USB-PC Watchdog card.
+ This card simply watches your kernel to make sure it doesn't freeze,
+ and if it does, it reboots your computer after a certain amount of
+ time. The card can also monitor the internal temperature of the PC.
+ More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcwd_usb.
+
+ Most people will say N.
+
+endmenu
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile
new file mode 100644
index 000000000000..1cd27efa35c1
--- /dev/null
+++ b/drivers/char/watchdog/Makefile
@@ -0,0 +1,42 @@
+#
+# Makefile for the WatchDog device drivers.
+#
+
+obj-$(CONFIG_PCWATCHDOG) += pcwd.o
+obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
+obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
+obj-$(CONFIG_IB700_WDT) += ib700wdt.o
+obj-$(CONFIG_MIXCOMWD) += mixcomwd.o
+obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
+obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
+obj-$(CONFIG_WDT) += wdt.o
+obj-$(CONFIG_WDTPCI) += wdt_pci.o
+obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
+obj-$(CONFIG_977_WATCHDOG) += wdt977.o
+obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o
+obj-$(CONFIG_MACHZ_WDT) += machzwd.o
+obj-$(CONFIG_SH_WDT) += shwdt.o
+obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
+obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
+obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
+obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o
+obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o
+obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
+obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
+obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
+obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
+obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o
+obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o
+obj-$(CONFIG_INDYDOG) += indydog.o
+obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o
+obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
+obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
+obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o
+obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o
+
+# Only one watchdog can succeed. We probe the hardware watchdog
+# drivers first, then the softdog driver. This means if your hardware
+# watchdog dies or is 'borrowed' for some reason the software watchdog
+# still gives you some cover.
+
+obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
diff --git a/drivers/char/watchdog/acquirewdt.c b/drivers/char/watchdog/acquirewdt.c
new file mode 100644
index 000000000000..8f302121741b
--- /dev/null
+++ b/drivers/char/watchdog/acquirewdt.c
@@ -0,0 +1,332 @@
+/*
+ * Acquire Single Board Computer Watchdog Timer driver
+ *
+ * Based on wdt.c. Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@redhat.com>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Can't add timeout - driver doesn't allow changing value
+ */
+
+/*
+ * Theory of Operation:
+ * The Watch-Dog Timer is provided to ensure that standalone
+ * Systems can always recover from catastrophic conditions that
+ * caused the CPU to crash. This condition may have occured by
+ * external EMI or a software bug. When the CPU stops working
+ * correctly, hardware on the board will either perform a hardware
+ * reset (cold boot) or a non-maskable interrupt (NMI) to bring the
+ * system back to a known state.
+ *
+ * The Watch-Dog Timer is controlled by two I/O Ports.
+ * 443 hex - Read - Enable or refresh the Watch-Dog Timer
+ * 043 hex - Read - Disable the Watch-Dog Timer
+ *
+ * To enable the Watch-Dog Timer, a read from I/O port 443h must
+ * be performed. This will enable and activate the countdown timer
+ * which will eventually time out and either reset the CPU or cause
+ * an NMI depending on the setting of a jumper. To ensure that this
+ * reset condition does not occur, the Watch-Dog Timer must be
+ * periodically refreshed by reading the same I/O port 443h.
+ * The Watch-Dog Timer is disabled by reading I/O port 043h.
+ *
+ * The Watch-Dog Timer Time-Out Period is set via jumpers.
+ * It can be 1, 2, 10, 20, 110 or 220 seconds.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "Acquire WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */
+
+static unsigned long acq_is_open;
+static char expect_close;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ */
+
+static int wdt_stop = 0x43;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Kernel methods.
+ */
+
+static void acq_keepalive(void)
+{
+ /* Write a watchdog value */
+ inb_p(wdt_start);
+}
+
+static void acq_stop(void)
+{
+ /* Turn the card off */
+ inb_p(wdt_stop);
+}
+
+/*
+ * /dev/watchdog handling.
+ */
+
+static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if(count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ /* Well, anyhow someone wrote to us, we should return that favour */
+ acq_keepalive();
+ }
+ return count;
+}
+
+static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int options, retval = -EINVAL;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident =
+ {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "Acquire WDT",
+ };
+
+ switch(cmd)
+ {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ acq_keepalive();
+ return 0;
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(WATCHDOG_HEARTBEAT, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD)
+ {
+ acq_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD)
+ {
+ acq_keepalive();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int acq_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &acq_is_open))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate */
+ acq_keepalive();
+ return nonseekable_open(inode, file);
+}
+
+static int acq_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ acq_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ acq_keepalive();
+ }
+ clear_bit(0, &acq_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int acq_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ acq_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations acq_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = acq_write,
+ .ioctl = acq_ioctl,
+ .open = acq_open,
+ .release = acq_close,
+};
+
+static struct miscdevice acq_miscdev=
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &acq_fops,
+};
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block acq_notifier =
+{
+ .notifier_call = acq_notify_sys,
+};
+
+static int __init acq_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n");
+
+ if (wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_stop);
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_start);
+ ret = -EIO;
+ goto unreg_stop;
+ }
+
+ ret = register_reboot_notifier(&acq_notifier);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto unreg_regions;
+ }
+
+ ret = misc_register(&acq_miscdev);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_reboot;
+ }
+
+ printk (KERN_INFO PFX "initialized. (nowayout=%d)\n",
+ nowayout);
+
+ return 0;
+
+unreg_reboot:
+ unregister_reboot_notifier(&acq_notifier);
+unreg_regions:
+ release_region(wdt_start, 1);
+unreg_stop:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+out:
+ return ret;
+}
+
+static void __exit acq_exit(void)
+{
+ misc_deregister(&acq_miscdev);
+ unregister_reboot_notifier(&acq_notifier);
+ if(wdt_stop != wdt_start)
+ release_region(wdt_stop,1);
+ release_region(wdt_start,1);
+}
+
+module_init(acq_init);
+module_exit(acq_exit);
+
+MODULE_AUTHOR("David Woodhouse");
+MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/advantechwdt.c b/drivers/char/watchdog/advantechwdt.c
new file mode 100644
index 000000000000..ea73c8379bdd
--- /dev/null
+++ b/drivers/char/watchdog/advantechwdt.c
@@ -0,0 +1,333 @@
+/*
+ * Advantech Single Board Computer WDT driver
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * Based on acquirewdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@redhat.com>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * 16-Oct-2002 Rob Radez <rob@osinvestor.com>
+ * Clean up ioctls, clean up init + exit, add expect close support,
+ * add wdt_start and wdt_stop as parameters.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "Advantech WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+static unsigned long advwdt_is_open;
+static char adv_expect_close;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ *
+ * To enable or restart, write the timeout value in seconds (1 to 63)
+ * to I/O port wdt_start. To disable, read I/O port wdt_stop.
+ * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but
+ * check your manual (at least the PCA-6159 seems to be different -
+ * the manual says wdt_stop is 0x43, not 0x443).
+ * (0x43 is also a write-only control register for the 8254 timer!)
+ */
+
+static int wdt_stop = 0x443;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)");
+
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Kernel methods.
+ */
+
+static void
+advwdt_ping(void)
+{
+ /* Write a watchdog value */
+ outb_p(timeout, wdt_start);
+}
+
+static void
+advwdt_disable(void)
+{
+ inb_p(wdt_stop);
+}
+
+static ssize_t
+advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ adv_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf+i))
+ return -EFAULT;
+ if (c == 'V')
+ adv_expect_close = 42;
+ }
+ }
+ advwdt_ping();
+ }
+ return count;
+}
+
+static int
+advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_timeout;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "Advantech WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ advwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if ((new_timeout < 1) || (new_timeout > 63))
+ return -EINVAL;
+ timeout = new_timeout;
+ advwdt_ping();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ advwdt_disable();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ advwdt_ping();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int
+advwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &advwdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+
+ advwdt_ping();
+ return nonseekable_open(inode, file);
+}
+
+static int
+advwdt_close(struct inode *inode, struct file *file)
+{
+ if (adv_expect_close == 42) {
+ advwdt_disable();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ advwdt_ping();
+ }
+ clear_bit(0, &advwdt_is_open);
+ adv_expect_close = 0;
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int
+advwdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Turn the WDT off */
+ advwdt_disable();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations advwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = advwdt_write,
+ .ioctl = advwdt_ioctl,
+ .open = advwdt_open,
+ .release = advwdt_close,
+};
+
+static struct miscdevice advwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &advwdt_fops,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block advwdt_notifier = {
+ .notifier_call = advwdt_notify_sys,
+};
+
+static int __init
+advwdt_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n");
+
+ if (timeout < 1 || timeout > 63) {
+ timeout = WATCHDOG_TIMEOUT;
+ printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n",
+ timeout);
+ }
+
+ if (wdt_stop != wdt_start) {
+ if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_stop);
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_start);
+ ret = -EIO;
+ goto unreg_stop;
+ }
+
+ ret = register_reboot_notifier(&advwdt_notifier);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto unreg_regions;
+ }
+
+ ret = misc_register(&advwdt_miscdev);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_reboot;
+ }
+
+ printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+out:
+ return ret;
+unreg_reboot:
+ unregister_reboot_notifier(&advwdt_notifier);
+unreg_regions:
+ release_region(wdt_start, 1);
+unreg_stop:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+ goto out;
+}
+
+static void __exit
+advwdt_exit(void)
+{
+ misc_deregister(&advwdt_miscdev);
+ unregister_reboot_notifier(&advwdt_notifier);
+ if(wdt_stop != wdt_start)
+ release_region(wdt_stop,1);
+ release_region(wdt_start,1);
+}
+
+module_init(advwdt_init);
+module_exit(advwdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>");
+MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/alim1535_wdt.c b/drivers/char/watchdog/alim1535_wdt.c
new file mode 100644
index 000000000000..35dcbf8be7d1
--- /dev/null
+++ b/drivers/char/watchdog/alim1535_wdt.c
@@ -0,0 +1,463 @@
+/*
+ * Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define WATCHDOG_NAME "ALi_M1535"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+/* internal variables */
+static unsigned long ali_is_open;
+static char ali_expect_release;
+static struct pci_dev *ali_pci;
+static u32 ali_timeout_bits; /* stores the computed timeout */
+static spinlock_t ali_lock; /* Guards the hardware */
+
+/* module parameters */
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (0<timeout<18000, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * ali_start - start watchdog countdown
+ *
+ * Starts the timer running providing the timer has a counter
+ * configuration set.
+ */
+
+static void ali_start(void)
+{
+ u32 val;
+
+ spin_lock(&ali_lock);
+
+ pci_read_config_dword(ali_pci, 0xCC, &val);
+ val &= ~0x3F; /* Mask count */
+ val |= (1<<25) | ali_timeout_bits;
+ pci_write_config_dword(ali_pci, 0xCC, val);
+
+ spin_unlock(&ali_lock);
+}
+
+/*
+ * ali_stop - stop the timer countdown
+ *
+ * Stop the ALi watchdog countdown
+ */
+
+static void ali_stop(void)
+{
+ u32 val;
+
+ spin_lock(&ali_lock);
+
+ pci_read_config_dword(ali_pci, 0xCC, &val);
+ val &= ~0x3F; /* Mask count to zero (disabled) */
+ val &= ~(1<<25);/* and for safety mask the reset enable */
+ pci_write_config_dword(ali_pci, 0xCC, val);
+
+ spin_unlock(&ali_lock);
+}
+
+/*
+ * ali_keepalive - send a keepalive to the watchdog
+ *
+ * Send a keepalive to the timer (actually we restart the timer).
+ */
+
+static void ali_keepalive(void)
+{
+ ali_start();
+}
+
+/*
+ * ali_settimer - compute the timer reload value
+ * @t: time in seconds
+ *
+ * Computes the timeout values needed
+ */
+
+static int ali_settimer(int t)
+{
+ if(t < 0)
+ return -EINVAL;
+ else if(t < 60)
+ ali_timeout_bits = t|(1<<6);
+ else if(t < 3600)
+ ali_timeout_bits = (t/60)|(1<<7);
+ else if(t < 18000)
+ ali_timeout_bits = (t/300)|(1<<6)|(1<<7);
+ else return -EINVAL;
+
+ timeout = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+/*
+ * ali_write - writes to ALi watchdog
+ * @file: file from VFS
+ * @data: user address of data
+ * @len: length of data
+ * @ppos: pointer to the file offset
+ *
+ * Handle a write to the ALi watchdog. Writing to the file pings
+ * the watchdog and resets it. Writing the magic 'V' sequence allows
+ * the next close to turn off the watchdog.
+ */
+
+static ssize_t ali_write(struct file *file, const char __user *data,
+ size_t len, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ ali_expect_release = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if(get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ ali_expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ ali_start();
+ }
+ return len;
+}
+
+/*
+ * ali_ioctl - handle watchdog ioctls
+ * @inode: VFS inode
+ * @file: VFS file pointer
+ * @cmd: ioctl number
+ * @arg: arguments to the ioctl
+ *
+ * Handle the watchdog ioctls supported by the ALi driver. Really
+ * we want an extension to enable irq ack monitoring and the like
+ */
+
+static int ali_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "ALi M1535 WatchDog Timer",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident,
+ sizeof (ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ ali_keepalive();
+ return 0;
+
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if (get_user (new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ ali_stop();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ ali_start();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+
+ if (ali_settimer(new_timeout))
+ return -EINVAL;
+
+ ali_keepalive();
+ /* Fall */
+ }
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+/*
+ * ali_open - handle open of ali watchdog
+ * @inode: inode from VFS
+ * @file: file from VFS
+ *
+ * Open the ALi watchdog device. Ensure only one person opens it
+ * at a time. Also start the watchdog running.
+ */
+
+static int ali_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &ali_is_open))
+ return -EBUSY;
+
+ /* Activate */
+ ali_start();
+ return nonseekable_open(inode, file);
+}
+
+/*
+ * ali_release - close an ALi watchdog
+ * @inode: inode from VFS
+ * @file: file from VFS
+ *
+ * Close the ALi watchdog device. Actual shutdown of the timer
+ * only occurs if the magic sequence has been set.
+ */
+
+static int ali_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (ali_expect_release == 42) {
+ ali_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ ali_keepalive();
+ }
+ clear_bit(0, &ali_is_open);
+ ali_expect_release = 0;
+ return 0;
+}
+
+/*
+ * ali_notify_sys - System down notifier
+ *
+ * Notifier for system down
+ */
+
+
+static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ ali_stop();
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+
+static struct pci_device_id ali_pci_tbl[] = {
+ { PCI_VENDOR_ID_AL, 1535, PCI_ANY_ID, PCI_ANY_ID,},
+ { 0, },
+};
+MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
+
+/*
+ * ali_find_watchdog - find a 1535 and 7101
+ *
+ * Scans the PCI hardware for a 1535 series bridge and matching 7101
+ * watchdog device. This may be overtight but it is better to be safe
+ */
+
+static int __init ali_find_watchdog(void)
+{
+ struct pci_dev *pdev;
+ u32 wdog;
+
+ /* Check for a 1535 series bridge */
+ pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x1535, NULL);
+ if(pdev == NULL)
+ return -ENODEV;
+
+ /* Check for the a 7101 PMU */
+ pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x7101, NULL);
+ if(pdev == NULL)
+ return -ENODEV;
+
+ if(pci_enable_device(pdev))
+ return -EIO;
+
+ ali_pci = pdev;
+
+ /*
+ * Initialize the timer bits
+ */
+ pci_read_config_dword(pdev, 0xCC, &wdog);
+
+ wdog &= ~0x3F; /* Timer bits */
+ wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */
+ wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */
+
+ pci_write_config_dword(pdev, 0xCC, wdog);
+
+ return 0;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations ali_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ali_write,
+ .ioctl = ali_ioctl,
+ .open = ali_open,
+ .release = ali_release,
+};
+
+static struct miscdevice ali_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ali_fops,
+};
+
+static struct notifier_block ali_notifier = {
+ .notifier_call = ali_notify_sys,
+};
+
+/*
+ * watchdog_init - module initialiser
+ *
+ * Scan for a suitable watchdog and if so initialize it. Return an error
+ * if we cannot, the error causes the module to unload
+ */
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ spin_lock_init(&ali_lock);
+
+ /* Check whether or not the hardware watchdog is there */
+ if (ali_find_watchdog() != 0) {
+ return -ENODEV;
+ }
+
+ /* Check that the timeout value is within it's range ; if not reset to the default */
+ if (timeout < 1 || timeout >= 18000) {
+ timeout = WATCHDOG_TIMEOUT;
+ printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n",
+ timeout);
+ }
+
+ /* Calculate the watchdog's timeout */
+ ali_settimer(timeout);
+
+ ret = misc_register(&ali_miscdev);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto out;
+ }
+
+ ret = register_reboot_notifier(&ali_notifier);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto unreg_miscdev;
+ }
+
+ printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+out:
+ return ret;
+unreg_miscdev:
+ misc_deregister(&ali_miscdev);
+ goto out;
+}
+
+/*
+ * watchdog_exit - module de-initialiser
+ *
+ * Called while unloading a successfully installed watchdog module.
+ */
+
+static void __exit watchdog_exit(void)
+{
+ /* Stop the timer before we leave */
+ ali_stop();
+
+ /* Deregister */
+ unregister_reboot_notifier(&ali_notifier);
+ misc_deregister(&ali_miscdev);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/alim7101_wdt.c b/drivers/char/watchdog/alim7101_wdt.c
new file mode 100644
index 000000000000..90c091d9e0f5
--- /dev/null
+++ b/drivers/char/watchdog/alim7101_wdt.c
@@ -0,0 +1,421 @@
+/*
+ * ALi M7101 PMU Computer Watchdog Timer driver
+ *
+ * Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net>
+ * and the Cobalt kernel WDT timer driver by Tim Hockin
+ * <thockin@cobaltnet.com>
+ *
+ * (c)2002 Steve Hill <steve@navaho.co.uk>
+ *
+ * This WDT driver is different from most other Linux WDT
+ * drivers in that the driver will ping the watchdog by itself,
+ * because this particular WDT has a very short timeout (1.6
+ * seconds) and it would be insane to count on any userspace
+ * daemon always getting scheduled within that time frame.
+ *
+ * Additions:
+ * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
+ * found on very old cobalt hardware.
+ * -- Mike Waychison <michael.waychison@sun.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "alim7101_wdt"
+#define PFX OUR_NAME ": "
+
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+#define ALI_7101_WDT 0x92
+#define ALI_7101_GPIO 0x7D
+#define ALI_7101_GPIO_O 0x7E
+#define ALI_WDT_ARM 0x01
+
+/*
+ * We're going to use a 1 second timeout.
+ * If we reset the watchdog every ~250ms we should be safe. */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static int use_gpio = 0; /* Use the pic (for a1d revision alim7101) */
+module_param(use_gpio, int, 0);
+MODULE_PARM_DESC(use_gpio, "Use the gpio watchdog. (required by old cobalt boards)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static struct pci_dev *alim7101_pmu;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+ /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+ * we agree to ping the WDT
+ */
+ char tmp;
+
+ if(time_before(jiffies, next_heartbeat))
+ {
+ /* Ping the WDT (this is actually a disarm/arm sequence) */
+ pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp
+ | 0x20);
+ pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp
+ & ~0x20);
+ }
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+ /* Re-set the timer interval */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+ char tmp;
+
+ pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
+ if (writeval == WDT_ENABLE) {
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp & ~0x20);
+ }
+
+ } else {
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+ if (use_gpio) {
+ pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+ pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | 0x20);
+ }
+ }
+}
+
+static void wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* We must enable before we kick off the timer in case the timer
+ occurs as we ping it */
+
+ wdt_change(WDT_ENABLE);
+
+ /* Start the timer */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+
+
+ printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer_sync(&timer);
+ wdt_change(WDT_DISABLE);
+ printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if(count) {
+ if (!nowayout) {
+ size_t ofs;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ wdt_expect_close = 0;
+
+ /* now scan */
+ for (ofs = 0; ofs != count; ofs++) {
+ char c;
+ if (get_user(c, buf+ofs))
+ return -EFAULT;
+ if (c == 'V')
+ wdt_expect_close = 42;
+ }
+ }
+ /* someone wrote to us, we should restart timer */
+ wdt_keepalive();
+ }
+ return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+ /* Just in case we're already talking to someone... */
+ if(test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ /* Good, fire up the show */
+ wdt_startup();
+ return nonseekable_open(inode, file);
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+ if(wdt_expect_close == 42)
+ wdt_turnoff();
+ else {
+ /* wim: shouldn't there be a: del_timer(&timer); */
+ printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ clear_bit(0, &wdt_is_open);
+ wdt_expect_close = 0;
+ return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident =
+ {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "ALiM7101",
+ };
+
+ switch(cmd)
+ {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if(get_user(new_options, p))
+ return -EFAULT;
+
+ if(new_options & WDIOS_DISABLECARD) {
+ wdt_turnoff();
+ retval = 0;
+ }
+
+ if(new_options & WDIOS_ENABLECARD) {
+ wdt_startup();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if(get_user(new_timeout, p))
+ return -EFAULT;
+
+ if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+ return -EINVAL;
+
+ timeout = new_timeout;
+ wdt_keepalive();
+ /* Fall through */
+ }
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static struct file_operations wdt_fops = {
+ .owner= THIS_MODULE,
+ .llseek= no_llseek,
+ .write= fop_write,
+ .open= fop_open,
+ .release= fop_close,
+ .ioctl= fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor=WATCHDOG_MINOR,
+ .name="watchdog",
+ .fops=&wdt_fops,
+};
+
+/*
+ * Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT)
+ wdt_turnoff();
+
+ if (code==SYS_RESTART) {
+ /*
+ * Cobalt devices have no way of rebooting themselves other than
+ * getting the watchdog to pull reset, so we restart the watchdog on
+ * reboot with no heartbeat
+ */
+ wdt_change(WDT_ENABLE);
+ printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n");
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+ .notifier_call = wdt_notify_sys,
+};
+
+static void __exit alim7101_wdt_unload(void)
+{
+ wdt_turnoff();
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+}
+
+static int __init alim7101_wdt_init(void)
+{
+ int rc = -EBUSY;
+ struct pci_dev *ali1543_south;
+ char tmp;
+
+ printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n");
+ alim7101_pmu = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,NULL);
+ if (!alim7101_pmu) {
+ printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n");
+ return -EBUSY;
+ }
+
+ /* Set the WDT in the PMU to 1 second */
+ pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
+
+ ali1543_south = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL);
+ if (!ali1543_south) {
+ printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n");
+ return -EBUSY;
+ }
+ pci_read_config_byte(ali1543_south, 0x5e, &tmp);
+ if ((tmp & 0x1e) == 0x00) {
+ if (!use_gpio) {
+ printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n");
+ return -EBUSY;
+ }
+ nowayout = 1;
+ } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
+ printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
+ return -EBUSY;
+ }
+
+ if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+ {
+ timeout = WATCHDOG_TIMEOUT;
+ printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+ timeout);
+ }
+
+ init_timer(&timer);
+ timer.function = wdt_timer_ping;
+ timer.data = 1;
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ rc);
+ goto err_out_miscdev;
+ }
+
+ if (nowayout) {
+ __module_get(THIS_MODULE);
+ }
+
+ printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+ return 0;
+
+err_out_miscdev:
+ misc_deregister(&wdt_miscdev);
+err_out:
+ return rc;
+}
+
+module_init(alim7101_wdt_init);
+module_exit(alim7101_wdt_unload);
+
+MODULE_AUTHOR("Steve Hill");
+MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/cpu5wdt.c b/drivers/char/watchdog/cpu5wdt.c
new file mode 100644
index 000000000000..2865dac0a813
--- /dev/null
+++ b/drivers/char/watchdog/cpu5wdt.c
@@ -0,0 +1,303 @@
+/*
+ * sma cpu5 watchdog driver
+ *
+ * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/watchdog.h>
+
+/* adjustable parameters */
+
+static int verbose = 0;
+static int port = 0x91;
+static int ticks = 10000;
+
+#define PFX "cpu5wdt: "
+
+#define CPU5WDT_EXTENT 0x0A
+
+#define CPU5WDT_STATUS_REG 0x00
+#define CPU5WDT_TIME_A_REG 0x02
+#define CPU5WDT_TIME_B_REG 0x03
+#define CPU5WDT_MODE_REG 0x04
+#define CPU5WDT_TRIGGER_REG 0x07
+#define CPU5WDT_ENABLE_REG 0x08
+#define CPU5WDT_RESET_REG 0x09
+
+#define CPU5WDT_INTERVAL (HZ/10+1)
+
+/* some device data */
+
+static struct {
+ struct semaphore stop;
+ volatile int running;
+ struct timer_list timer;
+ volatile int queue;
+ int default_ticks;
+ unsigned long inuse;
+} cpu5wdt_device;
+
+/* generic helper functions */
+
+static void cpu5wdt_trigger(unsigned long unused)
+{
+ if ( verbose > 2 )
+ printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks);
+
+ if( cpu5wdt_device.running )
+ ticks--;
+
+ /* keep watchdog alive */
+ outb(1, port + CPU5WDT_TRIGGER_REG);
+
+ /* requeue?? */
+ if( cpu5wdt_device.queue && ticks ) {
+ cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL;
+ add_timer(&cpu5wdt_device.timer);
+ }
+ else {
+ /* ticks doesn't matter anyway */
+ up(&cpu5wdt_device.stop);
+ }
+
+}
+
+static void cpu5wdt_reset(void)
+{
+ ticks = cpu5wdt_device.default_ticks;
+
+ if ( verbose )
+ printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks);
+
+}
+
+static void cpu5wdt_start(void)
+{
+ if ( !cpu5wdt_device.queue ) {
+ cpu5wdt_device.queue = 1;
+ outb(0, port + CPU5WDT_TIME_A_REG);
+ outb(0, port + CPU5WDT_TIME_B_REG);
+ outb(1, port + CPU5WDT_MODE_REG);
+ outb(0, port + CPU5WDT_RESET_REG);
+ outb(0, port + CPU5WDT_ENABLE_REG);
+ cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL;
+ add_timer(&cpu5wdt_device.timer);
+ }
+ /* if process dies, counter is not decremented */
+ cpu5wdt_device.running++;
+}
+
+static int cpu5wdt_stop(void)
+{
+ if ( cpu5wdt_device.running )
+ cpu5wdt_device.running = 0;
+
+ ticks = cpu5wdt_device.default_ticks;
+
+ if ( verbose )
+ printk(KERN_CRIT PFX "stop not possible\n");
+
+ return -EIO;
+}
+
+/* filesystem operations */
+
+static int cpu5wdt_open(struct inode *inode, struct file *file)
+{
+ if ( test_and_set_bit(0, &cpu5wdt_device.inuse) )
+ return -EBUSY;
+
+ return nonseekable_open(inode, file);
+}
+
+static int cpu5wdt_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &cpu5wdt_device.inuse);
+ return 0;
+}
+
+static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ unsigned int value;
+ static struct watchdog_info ident =
+ {
+ .options = WDIOF_CARDRESET,
+ .identity = "CPU5 WDT",
+ };
+
+ switch(cmd) {
+ case WDIOC_KEEPALIVE:
+ cpu5wdt_reset();
+ break;
+ case WDIOC_GETSTATUS:
+ value = inb(port + CPU5WDT_STATUS_REG);
+ value = (value >> 2) & 1;
+ if ( copy_to_user(argp, &value, sizeof(int)) )
+ return -EFAULT;
+ break;
+ case WDIOC_GETSUPPORT:
+ if ( copy_to_user(argp, &ident, sizeof(ident)) )
+ return -EFAULT;
+ break;
+ case WDIOC_SETOPTIONS:
+ if ( copy_from_user(&value, argp, sizeof(int)) )
+ return -EFAULT;
+ switch(value) {
+ case WDIOS_ENABLECARD:
+ cpu5wdt_start();
+ break;
+ case WDIOS_DISABLECARD:
+ return cpu5wdt_stop();
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if ( !count )
+ return -EIO;
+
+ cpu5wdt_reset();
+
+ return count;
+}
+
+static struct file_operations cpu5wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .ioctl = cpu5wdt_ioctl,
+ .open = cpu5wdt_open,
+ .write = cpu5wdt_write,
+ .release = cpu5wdt_release,
+};
+
+static struct miscdevice cpu5wdt_misc = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &cpu5wdt_fops,
+};
+
+/* init/exit function */
+
+static int __devinit cpu5wdt_init(void)
+{
+ unsigned int val;
+ int err;
+
+ if ( verbose )
+ printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose);
+
+ if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) {
+ printk(KERN_ERR PFX "misc_register failed\n");
+ goto no_misc;
+ }
+
+ if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) {
+ printk(KERN_ERR PFX "request_region failed\n");
+ err = -EBUSY;
+ goto no_port;
+ }
+
+ /* watchdog reboot? */
+ val = inb(port + CPU5WDT_STATUS_REG);
+ val = (val >> 2) & 1;
+ if ( !val )
+ printk(KERN_INFO PFX "sorry, was my fault\n");
+
+ init_MUTEX_LOCKED(&cpu5wdt_device.stop);
+ cpu5wdt_device.queue = 0;
+
+ clear_bit(0, &cpu5wdt_device.inuse);
+
+ init_timer(&cpu5wdt_device.timer);
+ cpu5wdt_device.timer.function = cpu5wdt_trigger;
+ cpu5wdt_device.timer.data = 0;
+
+ cpu5wdt_device.default_ticks = ticks;
+
+ printk(KERN_INFO PFX "init success\n");
+
+ return 0;
+
+no_port:
+ misc_deregister(&cpu5wdt_misc);
+no_misc:
+ return err;
+}
+
+static int __devinit cpu5wdt_init_module(void)
+{
+ return cpu5wdt_init();
+}
+
+static void __devexit cpu5wdt_exit(void)
+{
+ if ( cpu5wdt_device.queue ) {
+ cpu5wdt_device.queue = 0;
+ down(&cpu5wdt_device.stop);
+ }
+
+ misc_deregister(&cpu5wdt_misc);
+
+ release_region(port, CPU5WDT_EXTENT);
+
+}
+
+static void __devexit cpu5wdt_exit_module(void)
+{
+ cpu5wdt_exit();
+}
+
+/* module entry points */
+
+module_init(cpu5wdt_init_module);
+module_exit(cpu5wdt_exit_module);
+
+MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>");
+MODULE_DESCRIPTION("sma cpu5 watchdog driver");
+MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(port, int, 0);
+MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91");
+
+module_param(verbose, int, 0);
+MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
+
+module_param(ticks, int, 0);
+MODULE_PARM_DESC(ticks, "count down ticks, default is 10000");
diff --git a/drivers/char/watchdog/eurotechwdt.c b/drivers/char/watchdog/eurotechwdt.c
new file mode 100644
index 000000000000..d10e554a14d6
--- /dev/null
+++ b/drivers/char/watchdog/eurotechwdt.c
@@ -0,0 +1,474 @@
+/*
+ * Eurotech CPU-1220/1410 on board WDT driver
+ *
+ * (c) Copyright 2001 Ascensit <support@ascensit.com>
+ * (c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com>
+ * (c) Copyright 2002 Rob Radez <rob@osinvestor.com>
+ *
+ * Based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>*
+ */
+
+/* Changelog:
+ *
+ * 2002/04/25 - Rob Radez
+ * clean up #includes
+ * clean up locking
+ * make __setup param unique
+ * proper options in watchdog_info
+ * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls
+ * add expect_close support
+ *
+ * 2001 - Rodolfo Giometti
+ * Initial release
+ *
+ * 2002.05.30 - Joel Becker <joel.becker@oracle.com>
+ * Added Matt Domsch's nowayout module option.
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static unsigned long eurwdt_is_open;
+static int eurwdt_timeout;
+static char eur_expect_close;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ * You can use eurwdt=x,y to set these now.
+ */
+
+static int io = 0x3f0;
+static int irq = 10;
+static char *ev = "int";
+
+#define WDT_TIMEOUT 60 /* 1 minute */
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Some symbolic names
+ */
+
+#define WDT_CTRL_REG 0x30
+#define WDT_OUTPIN_CFG 0xe2
+#define WDT_EVENT_INT 0x00
+#define WDT_EVENT_REBOOT 0x08
+#define WDT_UNIT_SEL 0xf1
+#define WDT_UNIT_SECS 0x80
+#define WDT_TIMEOUT_VAL 0xf2
+#define WDT_TIMER_CFG 0xf3
+
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)");
+module_param(ev, charp, 0);
+MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')");
+
+
+/*
+ * Programming support
+ */
+
+static inline void eurwdt_write_reg(u8 index, u8 data)
+{
+ outb(index, io);
+ outb(data, io+1);
+}
+
+static inline void eurwdt_lock_chip(void)
+{
+ outb(0xaa, io);
+}
+
+static inline void eurwdt_unlock_chip(void)
+{
+ outb(0x55, io);
+ eurwdt_write_reg(0x07, 0x08); /* set the logical device */
+}
+
+static inline void eurwdt_set_timeout(int timeout)
+{
+ eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout);
+}
+
+static inline void eurwdt_disable_timer(void)
+{
+ eurwdt_set_timeout(0);
+}
+
+static void eurwdt_activate_timer(void)
+{
+ eurwdt_disable_timer();
+ eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */
+ eurwdt_write_reg(WDT_OUTPIN_CFG, !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT);
+
+ /* Setting interrupt line */
+ if (irq == 2 || irq > 15 || irq < 0) {
+ printk(KERN_ERR ": invalid irq number\n");
+ irq = 0; /* if invalid we disable interrupt */
+ }
+ if (irq == 0)
+ printk(KERN_INFO ": interrupt disabled\n");
+
+ eurwdt_write_reg(WDT_TIMER_CFG, irq<<4);
+
+ eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */
+ eurwdt_set_timeout(0); /* the default timeout */
+}
+
+
+/*
+ * Kernel methods.
+ */
+
+static irqreturn_t eurwdt_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ printk(KERN_CRIT "timeout WDT timeout\n");
+
+#ifdef ONLY_TESTING
+ printk(KERN_CRIT "Would Reboot.\n");
+#else
+ printk(KERN_CRIT "Initiating system reboot.\n");
+ machine_restart(NULL);
+#endif
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * eurwdt_ping:
+ *
+ * Reload counter one with the watchdog timeout.
+ */
+
+static void eurwdt_ping(void)
+{
+ /* Write the watchdog default value */
+ eurwdt_set_timeout(eurwdt_timeout);
+}
+
+/**
+ * eurwdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t eurwdt_write(struct file *file, const char __user *buf,
+size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ eur_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if(get_user(c, buf+i))
+ return -EFAULT;
+ if (c == 'V')
+ eur_expect_close = 42;
+ }
+ }
+ eurwdt_ping(); /* the default timeout */
+ }
+
+ return count;
+}
+
+/**
+ * eurwdt_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static int eurwdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "WDT Eurotech CPU-1220/1410",
+ };
+
+ int time;
+ int options, retval = -EINVAL;
+
+ switch(cmd) {
+ default:
+ return -ENOIOCTLCMD;
+
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ eurwdt_ping();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (copy_from_user(&time, p, sizeof(int)))
+ return -EFAULT;
+
+ /* Sanity check */
+ if (time < 0 || time > 255)
+ return -EINVAL;
+
+ eurwdt_timeout = time;
+ eurwdt_set_timeout(time);
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(eurwdt_timeout, p);
+
+ case WDIOC_SETOPTIONS:
+ if (get_user(options, p))
+ return -EFAULT;
+ if (options & WDIOS_DISABLECARD) {
+ eurwdt_disable_timer();
+ retval = 0;
+ }
+ if (options & WDIOS_ENABLECARD) {
+ eurwdt_activate_timer();
+ eurwdt_ping();
+ retval = 0;
+ }
+ return retval;
+ }
+}
+
+/**
+ * eurwdt_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The misc device has been opened. The watchdog device is single
+ * open and on opening we load the counter.
+ */
+
+static int eurwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &eurwdt_is_open))
+ return -EBUSY;
+ eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */
+ /* Activate the WDT */
+ eurwdt_activate_timer();
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * eurwdt_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int eurwdt_release(struct inode *inode, struct file *file)
+{
+ if (eur_expect_close == 42) {
+ eurwdt_disable_timer();
+ } else {
+ printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n");
+ eurwdt_ping();
+ }
+ clear_bit(0, &eurwdt_is_open);
+ eur_expect_close = 0;
+ return 0;
+}
+
+/**
+ * eurwdt_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Turn the card off */
+ eurwdt_disable_timer();
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static struct file_operations eurwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = eurwdt_write,
+ .ioctl = eurwdt_ioctl,
+ .open = eurwdt_open,
+ .release = eurwdt_release,
+};
+
+static struct miscdevice eurwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &eurwdt_fops,
+};
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block eurwdt_notifier = {
+ .notifier_call = eurwdt_notify_sys,
+};
+
+/**
+ * cleanup_module:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit eurwdt_exit(void)
+{
+ eurwdt_lock_chip();
+
+ misc_deregister(&eurwdt_miscdev);
+
+ unregister_reboot_notifier(&eurwdt_notifier);
+ release_region(io, 2);
+ free_irq(irq, NULL);
+}
+
+/**
+ * eurwdt_init:
+ *
+ * Set up the WDT watchdog board. After grabbing the resources
+ * we require we need also to unlock the device.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init eurwdt_init(void)
+{
+ int ret;
+
+ ret = misc_register(&eurwdt_miscdev);
+ if (ret) {
+ printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n",
+ WATCHDOG_MINOR);
+ goto out;
+ }
+
+ ret = request_irq(irq, eurwdt_interrupt, SA_INTERRUPT, "eurwdt", NULL);
+ if(ret) {
+ printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq);
+ goto outmisc;
+ }
+
+ if (!request_region(io, 2, "eurwdt")) {
+ printk(KERN_ERR "eurwdt: IO %X is not free.\n", io);
+ ret = -EBUSY;
+ goto outirq;
+ }
+
+ ret = register_reboot_notifier(&eurwdt_notifier);
+ if (ret) {
+ printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret);
+ goto outreg;
+ }
+
+ eurwdt_unlock_chip();
+
+ ret = 0;
+ printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)"
+ " - timeout event: %s\n",
+ io, irq, (!strcmp("int", ev) ? "int" : "reboot"));
+
+out:
+ return ret;
+
+outreg:
+ release_region(io, 2);
+
+outirq:
+ free_irq(irq, NULL);
+
+outmisc:
+ misc_deregister(&eurwdt_miscdev);
+ goto out;
+}
+
+module_init(eurwdt_init);
+module_exit(eurwdt_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti");
+MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/i8xx_tco.c b/drivers/char/watchdog/i8xx_tco.c
new file mode 100644
index 000000000000..c337978dc966
--- /dev/null
+++ b/drivers/char/watchdog/i8xx_tco.c
@@ -0,0 +1,535 @@
+/*
+ * i8xx_tco 0.07: TCO timer driver for i8xx chipsets
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
+ * http://www.kernelconcepts.de
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither kernel concepts nor Nils Faerber admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
+ * developed for
+ * Jentro AG, Haar/Munich (Germany)
+ *
+ * TCO timer driver for i8xx chipsets
+ * based on softdog.c by Alan Cox <alan@redhat.com>
+ *
+ * The TCO timer is implemented in the following I/O controller hubs:
+ * (See the intel documentation on http://developer.intel.com.)
+ * 82801AA (ICH) : document number 290655-003, 290677-014,
+ * 82801AB (ICHO) : document number 290655-003, 290677-014,
+ * 82801BA (ICH2) : document number 290687-002, 298242-027,
+ * 82801BAM (ICH2-M) : document number 290687-002, 298242-027,
+ * 82801CA (ICH3-S) : document number 290733-003, 290739-013,
+ * 82801CAM (ICH3-M) : document number 290716-001, 290718-007,
+ * 82801DB (ICH4) : document number 290744-001, 290745-020,
+ * 82801DBM (ICH4-M) : document number 252337-001, 252663-005,
+ * 82801E (C-ICH) : document number 273599-001, 273645-002,
+ * 82801EB (ICH5) : document number 252516-001, 252517-003,
+ * 82801ER (ICH5R) : document number 252516-001, 252517-003,
+ * 82801FB (ICH6) : document number 301473-002, 301474-007,
+ * 82801FR (ICH6R) : document number 301473-002, 301474-007,
+ * 82801FBM (ICH6-M) : document number 301473-002, 301474-007,
+ * 82801FW (ICH6W) : document number 301473-001, 301474-007,
+ * 82801FRW (ICH6RW) : document number 301473-001, 301474-007
+ *
+ * 20000710 Nils Faerber
+ * Initial Version 0.01
+ * 20000728 Nils Faerber
+ * 0.02 Fix for SMI_EN->TCO_EN bit, some cleanups
+ * 20011214 Matt Domsch <Matt_Domsch@dell.com>
+ * 0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Didn't add timeout option as i810_margin already exists.
+ * 20020224 Joel Becker, Wim Van Sebroeck
+ * 0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3,
+ * add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT.
+ * 20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck
+ * 0.05 Fix possible timer_alive race, add expect close support,
+ * clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and
+ * WDIOC_SETOPTIONS), made i810tco_getdevice __init,
+ * removed boot_status, removed tco_timer_read,
+ * added support for 82801DB and 82801E chipset,
+ * added support for 82801EB and 8280ER chipset,
+ * general cleanup.
+ * 20030921 Wim Van Sebroeck <wim@iguana.be>
+ * 0.06 change i810_margin to heartbeat, use module_param,
+ * added notify system support, renamed module to i8xx_tco.
+ * 20050128 Wim Van Sebroeck <wim@iguana.be>
+ * 0.07 Added support for the ICH4-M, ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW
+ * chipsets. Also added support for the "undocumented" ICH7 chipset.
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include "i8xx_tco.h"
+
+/* Module and version information */
+#define TCO_VERSION "0.07"
+#define TCO_MODULE_NAME "i8xx TCO timer"
+#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
+#define PFX TCO_MODULE_NAME ": "
+
+/* internal variables */
+static unsigned int ACPIBASE;
+static spinlock_t tco_lock; /* Guards the hardware */
+static unsigned long timer_alive;
+static char tco_expect_close;
+static struct pci_dev *i8xx_tco_pci;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
+static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Some TCO specific functions
+ */
+
+static inline unsigned char seconds_to_ticks(int seconds)
+{
+ /* the internal timer is stored as ticks which decrement
+ * every 0.6 seconds */
+ return (seconds * 10) / 6;
+}
+
+static int tco_timer_start (void)
+{
+ unsigned char val;
+
+ spin_lock(&tco_lock);
+ val = inb (TCO1_CNT + 1);
+ val &= 0xf7;
+ outb (val, TCO1_CNT + 1);
+ val = inb (TCO1_CNT + 1);
+ spin_unlock(&tco_lock);
+
+ if (val & 0x08)
+ return -1;
+ return 0;
+}
+
+static int tco_timer_stop (void)
+{
+ unsigned char val;
+
+ spin_lock(&tco_lock);
+ val = inb (TCO1_CNT + 1);
+ val |= 0x08;
+ outb (val, TCO1_CNT + 1);
+ val = inb (TCO1_CNT + 1);
+ spin_unlock(&tco_lock);
+
+ if ((val & 0x08) == 0)
+ return -1;
+ return 0;
+}
+
+static int tco_timer_keepalive (void)
+{
+ spin_lock(&tco_lock);
+ outb (0x01, TCO1_RLD);
+ spin_unlock(&tco_lock);
+ return 0;
+}
+
+static int tco_timer_set_heartbeat (int t)
+{
+ unsigned char val;
+ unsigned char tmrval;
+
+ tmrval = seconds_to_ticks(t);
+ /* from the specs: */
+ /* "Values of 0h-3h are ignored and should not be attempted" */
+ if (tmrval > 0x3f || tmrval < 0x04)
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ spin_lock(&tco_lock);
+ val = inb (TCO1_TMR);
+ val &= 0xc0;
+ val |= tmrval;
+ outb (val, TCO1_TMR);
+ val = inb (TCO1_TMR);
+ spin_unlock(&tco_lock);
+
+ if ((val & 0x3f) != tmrval)
+ return -EINVAL;
+
+ heartbeat = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int i8xx_tco_open (struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &timer_alive))
+ return -EBUSY;
+
+ /*
+ * Reload and activate timer
+ */
+ tco_timer_keepalive ();
+ tco_timer_start ();
+ return nonseekable_open(inode, file);
+}
+
+static int i8xx_tco_release (struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (tco_expect_close == 42) {
+ tco_timer_stop ();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ tco_timer_keepalive ();
+ }
+ clear_bit(0, &timer_alive);
+ tco_expect_close = 0;
+ return 0;
+}
+
+static ssize_t i8xx_tco_write (struct file *file, const char __user *data,
+ size_t len, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ tco_expect_close = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if(get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ tco_expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ tco_timer_keepalive ();
+ }
+ return len;
+}
+
+static int i8xx_tco_ioctl (struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int new_options, retval = -EINVAL;
+ int new_heartbeat;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = TCO_MODULE_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident,
+ sizeof (ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user (0, p);
+
+ case WDIOC_KEEPALIVE:
+ tco_timer_keepalive ();
+ return 0;
+
+ case WDIOC_SETOPTIONS:
+ {
+ if (get_user (new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ tco_timer_stop ();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ tco_timer_keepalive ();
+ tco_timer_start ();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_SETTIMEOUT:
+ {
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (tco_timer_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ tco_timer_keepalive ();
+ /* Fall */
+ }
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+/*
+ * Notify system
+ */
+
+static int i8xx_tco_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ tco_timer_stop ();
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations i8xx_tco_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = i8xx_tco_write,
+ .ioctl = i8xx_tco_ioctl,
+ .open = i8xx_tco_open,
+ .release = i8xx_tco_release,
+};
+
+static struct miscdevice i8xx_tco_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &i8xx_tco_fops,
+};
+
+static struct notifier_block i8xx_tco_notifier = {
+ .notifier_call = i8xx_tco_notify_sys,
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE. We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id i8xx_tco_pci_tbl[] = {
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, }, /* End of list */
+};
+MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl);
+
+/*
+ * Init & exit routines
+ */
+
+static unsigned char __init i8xx_tco_getdevice (void)
+{
+ struct pci_dev *dev = NULL;
+ u8 val1, val2;
+ u16 badr;
+ /*
+ * Find the PCI device
+ */
+
+ while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
+ if (pci_match_device(i8xx_tco_pci_tbl, dev)) {
+ i8xx_tco_pci = dev;
+ break;
+ }
+ }
+
+ if (i8xx_tco_pci) {
+ /*
+ * Find the ACPI base I/O address which is the base
+ * for the TCO registers (TCOBASE=ACPIBASE + 0x60)
+ * ACPIBASE is bits [15:7] from 0x40-0x43
+ */
+ pci_read_config_byte (i8xx_tco_pci, 0x40, &val1);
+ pci_read_config_byte (i8xx_tco_pci, 0x41, &val2);
+ badr = ((val2 << 1) | (val1 >> 7)) << 7;
+ ACPIBASE = badr;
+ /* Something's wrong here, ACPIBASE has to be set */
+ if (badr == 0x0001 || badr == 0x0000) {
+ printk (KERN_ERR PFX "failed to get TCOBASE address\n");
+ return 0;
+ }
+ /*
+ * Check chipset's NO_REBOOT bit
+ */
+ pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
+ if (val1 & 0x02) {
+ val1 &= 0xfd;
+ pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
+ pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
+ if (val1 & 0x02) {
+ printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
+ return 0; /* Cannot reset NO_REBOOT bit */
+ }
+ }
+ /* Set the TCO_EN bit in SMI_EN register */
+ if (!request_region (SMI_EN + 1, 1, "i8xx TCO")) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ SMI_EN + 1);
+ return 0;
+ }
+ val1 = inb (SMI_EN + 1);
+ val1 &= 0xdf;
+ outb (val1, SMI_EN + 1);
+ release_region (SMI_EN + 1, 1);
+ return 1;
+ }
+ return 0;
+}
+
+static int __init watchdog_init (void)
+{
+ int ret;
+
+ spin_lock_init(&tco_lock);
+
+ /* Check whether or not the hardware watchdog is there */
+ if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL)
+ return -ENODEV;
+
+ if (!request_region (TCOBASE, 0x10, "i8xx TCO")) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ TCOBASE);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Clear out the (probably old) status */
+ outb (0, TCO1_STS);
+ outb (3, TCO2_STS);
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (tco_timer_set_heartbeat (heartbeat)) {
+ heartbeat = WATCHDOG_HEARTBEAT;
+ tco_timer_set_heartbeat (heartbeat);
+ printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, using %d\n",
+ heartbeat);
+ }
+
+ ret = register_reboot_notifier(&i8xx_tco_notifier);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto unreg_region;
+ }
+
+ ret = misc_register(&i8xx_tco_miscdev);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_notifier;
+ }
+
+ tco_timer_stop ();
+
+ printk (KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
+ TCOBASE, heartbeat, nowayout);
+
+ return 0;
+
+unreg_notifier:
+ unregister_reboot_notifier(&i8xx_tco_notifier);
+unreg_region:
+ release_region (TCOBASE, 0x10);
+out:
+ return ret;
+}
+
+static void __exit watchdog_cleanup (void)
+{
+ u8 val;
+
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ tco_timer_stop ();
+
+ /* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+ pci_read_config_byte (i8xx_tco_pci, 0xd4, &val);
+ val |= 0x02;
+ pci_write_config_byte (i8xx_tco_pci, 0xd4, val);
+
+ /* Deregister */
+ misc_deregister (&i8xx_tco_miscdev);
+ unregister_reboot_notifier(&i8xx_tco_notifier);
+ release_region (TCOBASE, 0x10);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_cleanup);
+
+MODULE_AUTHOR("Nils Faerber");
+MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/i8xx_tco.h b/drivers/char/watchdog/i8xx_tco.h
new file mode 100644
index 000000000000..cc14eb8ac3d6
--- /dev/null
+++ b/drivers/char/watchdog/i8xx_tco.h
@@ -0,0 +1,42 @@
+/*
+ * i8xx_tco: TCO timer driver for i8xx chipsets
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
+ * http://www.kernelconcepts.de
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither kernel concepts nor Nils Faerber admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>
+ * developed for
+ * Jentro AG, Haar/Munich (Germany)
+ *
+ * TCO timer driver for i8xx chipsets
+ * based on softdog.c by Alan Cox <alan@redhat.com>
+ *
+ * For history and the complete list of supported I/O Controller Hub's
+ * see i8xx_tco.c
+ */
+
+
+/*
+ * Some address definitions for the TCO
+ */
+
+#define TCOBASE ACPIBASE + 0x60 /* TCO base address */
+#define TCO1_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */
+#define TCO1_TMR TCOBASE + 0x01 /* TCO Timer Initial Value */
+#define TCO1_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */
+#define TCO1_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */
+#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */
+#define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */
+#define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */
+#define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */
+
+#define SMI_EN ACPIBASE + 0x30 /* SMI Control and Enable Register */
diff --git a/drivers/char/watchdog/ib700wdt.c b/drivers/char/watchdog/ib700wdt.c
new file mode 100644
index 000000000000..d974f16e84d2
--- /dev/null
+++ b/drivers/char/watchdog/ib700wdt.c
@@ -0,0 +1,352 @@
+/*
+ * IB700 Single Board Computer WDT driver
+ *
+ * (c) Copyright 2001 Charles Howes <chowes@vsol.net>
+ *
+ * Based on advantechwdt.c which is based on acquirewdt.c which
+ * is based on wdt.c.
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * Based on acquirewdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@redhat.com>
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * Added timeout module option to override default
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static unsigned long ibwdt_is_open;
+static spinlock_t ibwdt_lock;
+static char expect_close;
+
+#define PFX "ib700wdt: "
+
+/*
+ *
+ * Watchdog Timer Configuration
+ *
+ * The function of the watchdog timer is to reset the system
+ * automatically and is defined at I/O port 0443H. To enable the
+ * watchdog timer and allow the system to reset, write I/O port 0443H.
+ * To disable the timer, write I/O port 0441H for the system to stop the
+ * watchdog function. The timer has a tolerance of 20% for its
+ * intervals.
+ *
+ * The following describes how the timer should be programmed.
+ *
+ * Enabling Watchdog:
+ * MOV AX,000FH (Choose the values from 0 to F)
+ * MOV DX,0443H
+ * OUT DX,AX
+ *
+ * Disabling Watchdog:
+ * MOV AX,000FH (Any value is fine.)
+ * MOV DX,0441H
+ * OUT DX,AX
+ *
+ * Watchdog timer control table:
+ * Level Value Time/sec | Level Value Time/sec
+ * 1 F 0 | 9 7 16
+ * 2 E 2 | 10 6 18
+ * 3 D 4 | 11 5 20
+ * 4 C 6 | 12 4 22
+ * 5 B 8 | 13 3 24
+ * 6 A 10 | 14 2 26
+ * 7 9 12 | 15 1 28
+ * 8 8 14 | 16 0 30
+ *
+ */
+
+static int wd_times[] = {
+ 30, /* 0x0 */
+ 28, /* 0x1 */
+ 26, /* 0x2 */
+ 24, /* 0x3 */
+ 22, /* 0x4 */
+ 20, /* 0x5 */
+ 18, /* 0x6 */
+ 16, /* 0x7 */
+ 14, /* 0x8 */
+ 12, /* 0x9 */
+ 10, /* 0xA */
+ 8, /* 0xB */
+ 6, /* 0xC */
+ 4, /* 0xD */
+ 2, /* 0xE */
+ 0, /* 0xF */
+};
+
+#define WDT_STOP 0x441
+#define WDT_START 0x443
+
+/* Default timeout */
+#define WD_TIMO 0 /* 30 seconds +/- 20%, from table */
+
+static int wd_margin = WD_TIMO;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+
+/*
+ * Kernel methods.
+ */
+
+static void
+ibwdt_ping(void)
+{
+ /* Write a watchdog value */
+ outb_p(wd_margin, WDT_START);
+}
+
+static ssize_t
+ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ ibwdt_ping();
+ }
+ return count;
+}
+
+static int
+ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int i, new_margin;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "IB700 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ ibwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+ if ((new_margin < 0) || (new_margin > 30))
+ return -EINVAL;
+ for (i = 0x0F; i > -1; i--)
+ if (wd_times[i] > new_margin)
+ break;
+ wd_margin = i;
+ ibwdt_ping();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(wd_times[wd_margin], p);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int
+ibwdt_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&ibwdt_lock);
+ if (test_and_set_bit(0, &ibwdt_is_open)) {
+ spin_unlock(&ibwdt_lock);
+ return -EBUSY;
+ }
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate */
+ ibwdt_ping();
+ spin_unlock(&ibwdt_lock);
+ return nonseekable_open(inode, file);
+}
+
+static int
+ibwdt_close(struct inode *inode, struct file *file)
+{
+ spin_lock(&ibwdt_lock);
+ if (expect_close == 42)
+ outb_p(0, WDT_STOP);
+ else
+ printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n");
+
+ clear_bit(0, &ibwdt_is_open);
+ expect_close = 0;
+ spin_unlock(&ibwdt_lock);
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int
+ibwdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Turn the WDT off */
+ outb_p(0, WDT_STOP);
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations ibwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ibwdt_write,
+ .ioctl = ibwdt_ioctl,
+ .open = ibwdt_open,
+ .release = ibwdt_close,
+};
+
+static struct miscdevice ibwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &ibwdt_fops,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block ibwdt_notifier = {
+ .notifier_call = ibwdt_notify_sys,
+};
+
+static int __init ibwdt_init(void)
+{
+ int res;
+
+ printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n");
+
+ spin_lock_init(&ibwdt_lock);
+ res = misc_register(&ibwdt_miscdev);
+ if (res) {
+ printk (KERN_ERR PFX "failed to register misc device\n");
+ goto out_nomisc;
+ }
+
+#if WDT_START != WDT_STOP
+ if (!request_region(WDT_STOP, 1, "IB700 WDT")) {
+ printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP);
+ res = -EIO;
+ goto out_nostopreg;
+ }
+#endif
+
+ if (!request_region(WDT_START, 1, "IB700 WDT")) {
+ printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START);
+ res = -EIO;
+ goto out_nostartreg;
+ }
+ res = register_reboot_notifier(&ibwdt_notifier);
+ if (res) {
+ printk (KERN_ERR PFX "Failed to register reboot notifier.\n");
+ goto out_noreboot;
+ }
+ return 0;
+
+out_noreboot:
+ release_region(WDT_START, 1);
+out_nostartreg:
+#if WDT_START != WDT_STOP
+ release_region(WDT_STOP, 1);
+#endif
+out_nostopreg:
+ misc_deregister(&ibwdt_miscdev);
+out_nomisc:
+ return res;
+}
+
+static void __exit
+ibwdt_exit(void)
+{
+ misc_deregister(&ibwdt_miscdev);
+ unregister_reboot_notifier(&ibwdt_notifier);
+#if WDT_START != WDT_STOP
+ release_region(WDT_STOP,1);
+#endif
+ release_region(WDT_START,1);
+}
+
+module_init(ibwdt_init);
+module_exit(ibwdt_exit);
+
+MODULE_AUTHOR("Charles Howes <chowes@vsol.net>");
+MODULE_DESCRIPTION("IB700 SBC watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+/* end of ib700wdt.c */
diff --git a/drivers/char/watchdog/indydog.c b/drivers/char/watchdog/indydog.c
new file mode 100644
index 000000000000..6af2c799b57e
--- /dev/null
+++ b/drivers/char/watchdog/indydog.c
@@ -0,0 +1,221 @@
+/*
+ * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22
+ *
+ * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/sgi/mc.h>
+
+#define PFX "indydog: "
+static int indydog_alive;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void indydog_start(void)
+{
+ u32 mc_ctrl0 = sgimc->cpuctrl0;
+
+ mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG;
+ sgimc->cpuctrl0 = mc_ctrl0;
+}
+
+static void indydog_stop(void)
+{
+ u32 mc_ctrl0 = sgimc->cpuctrl0;
+
+ mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
+ sgimc->cpuctrl0 = mc_ctrl0;
+
+ printk(KERN_INFO PFX "Stopped watchdog timer.\n");
+}
+
+static void indydog_ping(void)
+{
+ sgimc->watchdogt = 0;
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int indydog_open(struct inode *inode, struct file *file)
+{
+ if (indydog_alive)
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate timer */
+ indydog_start();
+ indydog_ping();
+
+ indydog_alive = 1;
+ printk(KERN_INFO "Started watchdog timer.\n");
+
+ return nonseekable_open(inode, file);
+}
+
+static int indydog_release(struct inode *inode, struct file *file)
+{
+ /* Shut off the timer.
+ * Lock it in if it's a module and we defined ...NOWAYOUT */
+ if (!nowayout)
+ indydog_stop(); /* Turn the WDT off */
+
+ indydog_alive = 0;
+
+ return 0;
+}
+
+static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ /* Refresh the timer. */
+ if (len) {
+ indydog_ping();
+ }
+ return len;
+}
+
+static int indydog_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int options, retval = -EINVAL;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "Hardware Watchdog for SGI IP22",
+ };
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((struct watchdog_info *)arg,
+ &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0,(int *)arg);
+ case WDIOC_KEEPALIVE:
+ indydog_ping();
+ return 0;
+ case WDIOC_GETTIMEOUT:
+ return put_user(WATCHDOG_TIMEOUT,(int *)arg);
+ case WDIOC_SETOPTIONS:
+ {
+ if (get_user(options, (int *)arg))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ indydog_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ indydog_start();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ }
+}
+
+static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ indydog_stop(); /* Turn the WDT off */
+
+ return NOTIFY_DONE;
+}
+
+static struct file_operations indydog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = indydog_write,
+ .ioctl = indydog_ioctl,
+ .open = indydog_open,
+ .release = indydog_release,
+};
+
+static struct miscdevice indydog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &indydog_fops,
+};
+
+static struct notifier_block indydog_notifier = {
+ .notifier_call = indydog_notify_sys,
+};
+
+static char banner[] __initdata =
+ KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n";
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ ret = register_reboot_notifier(&indydog_notifier);
+ if (ret) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = misc_register(&indydog_miscdev);
+ if (ret) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ unregister_reboot_notifier(&indydog_notifier);
+ return ret;
+ }
+
+ printk(banner);
+
+ return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+ misc_deregister(&indydog_miscdev);
+ unregister_reboot_notifier(&indydog_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>");
+MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/ixp2000_wdt.c b/drivers/char/watchdog/ixp2000_wdt.c
new file mode 100644
index 000000000000..ab659d37b4d2
--- /dev/null
+++ b/drivers/char/watchdog/ixp2000_wdt.c
@@ -0,0 +1,219 @@
+/*
+ * drivers/watchdog/ixp2000_wdt.c
+ *
+ * Watchdog driver for Intel IXP2000 network processors
+ *
+ * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek.
+ * The original version carries these notices:
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+
+#define WDT_IN_USE 0
+#define WDT_OK_TO_CLOSE 1
+
+static unsigned long wdt_tick_rate;
+
+static void
+wdt_enable(void)
+{
+ ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE);
+ ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE);
+ ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
+ ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE);
+}
+
+static void
+wdt_disable(void)
+{
+ ixp2000_reg_write(IXP2000_T4_CTL, 0);
+}
+
+static void
+wdt_keepalive(void)
+{
+ ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
+}
+
+static int
+ixp2000_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+ return -EBUSY;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ wdt_enable();
+
+ return nonseekable_open(inode, file);
+}
+
+static ssize_t
+ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ }
+ }
+ wdt_keepalive();
+ }
+
+ return len;
+}
+
+
+static struct watchdog_info ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING,
+ .identity = "IXP2000 Watchdog",
+};
+
+static int
+ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOIOCTLCMD;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int *)arg);
+ if (ret)
+ break;
+
+ if (time <= 0 || time > 60) {
+ ret = -EINVAL;
+ break;
+ }
+
+ heartbeat = time;
+ wdt_keepalive();
+ /* Fall through */
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(heartbeat, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_enable();
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static int
+ixp2000_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+ wdt_disable();
+ } else {
+ printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - "
+ "timer will not stop\n");
+ }
+
+ clear_bit(WDT_IN_USE, &wdt_status);
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ return 0;
+}
+
+
+static struct file_operations ixp2000_wdt_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ixp2000_wdt_write,
+ .ioctl = ixp2000_wdt_ioctl,
+ .open = ixp2000_wdt_open,
+ .release = ixp2000_wdt_release,
+};
+
+static struct miscdevice ixp2000_wdt_miscdev =
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "IXP2000 Watchdog",
+ .fops = &ixp2000_wdt_fops,
+};
+
+static int __init ixp2000_wdt_init(void)
+{
+ wdt_tick_rate = (*IXP2000_T1_CLD * HZ)/ 256;;
+
+ return misc_register(&ixp2000_wdt_miscdev);
+}
+
+static void __exit ixp2000_wdt_exit(void)
+{
+ misc_deregister(&ixp2000_wdt_miscdev);
+}
+
+module_init(ixp2000_wdt_init);
+module_exit(ixp2000_wdt_exit);
+
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net">);
+MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
diff --git a/drivers/char/watchdog/ixp4xx_wdt.c b/drivers/char/watchdog/ixp4xx_wdt.c
new file mode 100644
index 000000000000..82396e06c8a8
--- /dev/null
+++ b/drivers/char/watchdog/ixp4xx_wdt.c
@@ -0,0 +1,230 @@
+/*
+ * drivers/watchdog/ixp4xx_wdt.c
+ *
+ * Watchdog driver for Intel IXP4xx network processors
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+static int heartbeat = 60; /* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+static unsigned long boot_status;
+
+#define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL)
+
+#define WDT_IN_USE 0
+#define WDT_OK_TO_CLOSE 1
+
+static void
+wdt_enable(void)
+{
+ *IXP4XX_OSWK = IXP4XX_WDT_KEY;
+ *IXP4XX_OSWE = 0;
+ *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat;
+ *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE;
+ *IXP4XX_OSWK = 0;
+}
+
+static void
+wdt_disable(void)
+{
+ *IXP4XX_OSWK = IXP4XX_WDT_KEY;
+ *IXP4XX_OSWE = 0;
+ *IXP4XX_OSWK = 0;
+}
+
+static int
+ixp4xx_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+ return -EBUSY;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ wdt_enable();
+
+ return nonseekable_open(inode, file);
+}
+
+static ssize_t
+ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ }
+ }
+ wdt_enable();
+ }
+
+ return len;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+ WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "IXP4xx Watchdog",
+};
+
+
+static int
+ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOIOCTLCMD;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(boot_status, (int *)arg);
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int *)arg);
+ if (ret)
+ break;
+
+ if (time <= 0 || time > 60) {
+ ret = -EINVAL;
+ break;
+ }
+
+ heartbeat = time;
+ wdt_enable();
+ /* Fall through */
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(heartbeat, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_enable();
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static int
+ixp4xx_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+ wdt_disable();
+ } else {
+ printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - "
+ "timer will not stop\n");
+ }
+
+ clear_bit(WDT_IN_USE, &wdt_status);
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ return 0;
+}
+
+
+static struct file_operations ixp4xx_wdt_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = ixp4xx_wdt_write,
+ .ioctl = ixp4xx_wdt_ioctl,
+ .open = ixp4xx_wdt_open,
+ .release = ixp4xx_wdt_release,
+};
+
+static struct miscdevice ixp4xx_wdt_miscdev =
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "IXP4xx Watchdog",
+ .fops = &ixp4xx_wdt_fops,
+};
+
+static int __init ixp4xx_wdt_init(void)
+{
+ int ret;
+ unsigned long processor_id;
+
+ asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :);
+ if (!(processor_id & 0xf)) {
+ printk("IXP4XXX Watchdog: Rev. A0 CPU detected - "
+ "watchdog disabled\n");
+
+ return -ENODEV;
+ }
+
+ ret = misc_register(&ixp4xx_wdt_miscdev);
+ if (ret == 0)
+ printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat);
+
+ boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ?
+ WDIOF_CARDRESET : 0;
+
+ return ret;
+}
+
+static void __exit ixp4xx_wdt_exit(void)
+{
+ misc_deregister(&ixp4xx_wdt_miscdev);
+}
+
+
+module_init(ixp4xx_wdt_init);
+module_exit(ixp4xx_wdt_exit);
+
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
+MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
diff --git a/drivers/char/watchdog/machzwd.c b/drivers/char/watchdog/machzwd.c
new file mode 100644
index 000000000000..9da395fa7794
--- /dev/null
+++ b/drivers/char/watchdog/machzwd.c
@@ -0,0 +1,501 @@
+/*
+ * MachZ ZF-Logic Watchdog Timer driver for Linux
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The author does NOT admit liability nor provide warranty for
+ * any of this software. This material is provided "AS-IS" in
+ * the hope that it may be useful for others.
+ *
+ * Author: Fernando Fuganti <fuganti@conectiva.com.br>
+ *
+ * Based on sbc60xxwdt.c by Jakob Oestergaard
+ *
+ *
+ * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the
+ * following periods:
+ * wd#1 - 2 seconds;
+ * wd#2 - 7.2 ms;
+ * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or
+ * a system RESET and it starts wd#2 that unconditionaly will RESET
+ * the system when the counter reaches zero.
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* ports */
+#define ZF_IOBASE 0x218
+#define INDEX 0x218
+#define DATA_B 0x219
+#define DATA_W 0x21A
+#define DATA_D 0x21A
+
+/* indexes */ /* size */
+#define ZFL_VERSION 0x02 /* 16 */
+#define CONTROL 0x10 /* 16 */
+#define STATUS 0x12 /* 8 */
+#define COUNTER_1 0x0C /* 16 */
+#define COUNTER_2 0x0E /* 8 */
+#define PULSE_LEN 0x0F /* 8 */
+
+/* controls */
+#define ENABLE_WD1 0x0001
+#define ENABLE_WD2 0x0002
+#define RESET_WD1 0x0010
+#define RESET_WD2 0x0020
+#define GEN_SCI 0x0100
+#define GEN_NMI 0x0200
+#define GEN_SMI 0x0400
+#define GEN_RESET 0x0800
+
+
+/* utilities */
+
+#define WD1 0
+#define WD2 1
+
+#define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); }
+#define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); }
+#define zf_get_ZFL_version() zf_readw(ZFL_VERSION)
+
+
+static unsigned short zf_readw(unsigned char port)
+{
+ outb(port, INDEX);
+ return inw(DATA_W);
+}
+
+
+MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>");
+MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#define PFX "machzwd"
+
+static struct watchdog_info zf_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "ZF-Logic watchdog",
+};
+
+
+/*
+ * action refers to action taken when watchdog resets
+ * 0 = GEN_RESET
+ * 1 = GEN_SMI
+ * 2 = GEN_NMI
+ * 3 = GEN_SCI
+ * defaults to GEN_RESET (0)
+ */
+static int action = 0;
+module_param(action, int, 0);
+MODULE_PARM_DESC(action, "after watchdog resets, generate: 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI");
+
+static int zf_action = GEN_RESET;
+static unsigned long zf_is_open;
+static char zf_expect_close;
+static spinlock_t zf_lock;
+static spinlock_t zf_port_lock;
+static struct timer_list zf_timer;
+static unsigned long next_heartbeat = 0;
+
+
+/* timeout for user land heart beat (10 seconds) */
+#define ZF_USER_TIMEO (HZ*10)
+
+/* timeout for hardware watchdog (~500ms) */
+#define ZF_HW_TIMEO (HZ/2)
+
+/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */
+#define ZF_CTIMEOUT 0xffff
+
+#ifndef ZF_DEBUG
+# define dprintk(format, args...)
+#else
+# define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __LINE__ , ## args)
+#endif
+
+
+static inline void zf_set_status(unsigned char new)
+{
+ zf_writeb(STATUS, new);
+}
+
+
+/* CONTROL register functions */
+
+static inline unsigned short zf_get_control(void)
+{
+ return zf_readw(CONTROL);
+}
+
+static inline void zf_set_control(unsigned short new)
+{
+ zf_writew(CONTROL, new);
+}
+
+
+/* WD#? counter functions */
+/*
+ * Just set counter value
+ */
+
+static inline void zf_set_timer(unsigned short new, unsigned char n)
+{
+ switch(n){
+ case WD1:
+ zf_writew(COUNTER_1, new);
+ case WD2:
+ zf_writeb(COUNTER_2, new > 0xff ? 0xff : new);
+ default:
+ return;
+ }
+}
+
+/*
+ * stop hardware timer
+ */
+static void zf_timer_off(void)
+{
+ unsigned int ctrl_reg = 0;
+ unsigned long flags;
+
+ /* stop internal ping */
+ del_timer_sync(&zf_timer);
+
+ spin_lock_irqsave(&zf_port_lock, flags);
+ /* stop watchdog timer */
+ ctrl_reg = zf_get_control();
+ ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */
+ ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2);
+ zf_set_control(ctrl_reg);
+ spin_unlock_irqrestore(&zf_port_lock, flags);
+
+ printk(KERN_INFO PFX ": Watchdog timer is now disabled\n");
+}
+
+
+/*
+ * start hardware timer
+ */
+static void zf_timer_on(void)
+{
+ unsigned int ctrl_reg = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&zf_port_lock, flags);
+
+ zf_writeb(PULSE_LEN, 0xff);
+
+ zf_set_timer(ZF_CTIMEOUT, WD1);
+
+ /* user land ping */
+ next_heartbeat = jiffies + ZF_USER_TIMEO;
+
+ /* start the timer for internal ping */
+ zf_timer.expires = jiffies + ZF_HW_TIMEO;
+
+ add_timer(&zf_timer);
+
+ /* start watchdog timer */
+ ctrl_reg = zf_get_control();
+ ctrl_reg |= (ENABLE_WD1|zf_action);
+ zf_set_control(ctrl_reg);
+ spin_unlock_irqrestore(&zf_port_lock, flags);
+
+ printk(KERN_INFO PFX ": Watchdog timer is now enabled\n");
+}
+
+
+static void zf_ping(unsigned long data)
+{
+ unsigned int ctrl_reg = 0;
+ unsigned long flags;
+
+ zf_writeb(COUNTER_2, 0xff);
+
+ if(time_before(jiffies, next_heartbeat)){
+
+ dprintk("time_before: %ld\n", next_heartbeat - jiffies);
+
+ /*
+ * reset event is activated by transition from 0 to 1 on
+ * RESET_WD1 bit and we assume that it is already zero...
+ */
+
+ spin_lock_irqsave(&zf_port_lock, flags);
+ ctrl_reg = zf_get_control();
+ ctrl_reg |= RESET_WD1;
+ zf_set_control(ctrl_reg);
+
+ /* ...and nothing changes until here */
+ ctrl_reg &= ~(RESET_WD1);
+ zf_set_control(ctrl_reg);
+ spin_unlock_irqrestore(&zf_port_lock, flags);
+
+ zf_timer.expires = jiffies + ZF_HW_TIMEO;
+ add_timer(&zf_timer);
+ }else{
+ printk(KERN_CRIT PFX ": I will reset your machine\n");
+ }
+}
+
+static ssize_t zf_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ /* See if we got the magic character */
+ if(count){
+
+ /*
+ * no need to check for close confirmation
+ * no way to disable watchdog ;)
+ */
+ if (!nowayout) {
+ size_t ofs;
+
+ /*
+ * note: just in case someone wrote the magic character
+ * five months ago...
+ */
+ zf_expect_close = 0;
+
+ /* now scan */
+ for (ofs = 0; ofs != count; ofs++){
+ char c;
+ if (get_user(c, buf + ofs))
+ return -EFAULT;
+ if (c == 'V'){
+ zf_expect_close = 42;
+ dprintk("zf_expect_close = 42\n");
+ }
+ }
+ }
+
+ /*
+ * Well, anyhow someone wrote to us,
+ * we should return that favour
+ */
+ next_heartbeat = jiffies + ZF_USER_TIMEO;
+ dprintk("user ping at %ld\n", jiffies);
+
+ }
+
+ return count;
+}
+
+static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ switch(cmd){
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &zf_info, sizeof(zf_info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ zf_ping(0);
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static int zf_open(struct inode *inode, struct file *file)
+{
+ spin_lock(&zf_lock);
+ if(test_and_set_bit(0, &zf_is_open)) {
+ spin_unlock(&zf_lock);
+ return -EBUSY;
+ }
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ spin_unlock(&zf_lock);
+
+ zf_timer_on();
+
+ return nonseekable_open(inode, file);
+}
+
+static int zf_close(struct inode *inode, struct file *file)
+{
+ if(zf_expect_close == 42){
+ zf_timer_off();
+ } else {
+ del_timer(&zf_timer);
+ printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+
+ spin_lock(&zf_lock);
+ clear_bit(0, &zf_is_open);
+ spin_unlock(&zf_lock);
+
+ zf_expect_close = 0;
+
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int zf_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code == SYS_DOWN || code == SYS_HALT){
+ zf_timer_off();
+ }
+
+ return NOTIFY_DONE;
+}
+
+
+
+
+static struct file_operations zf_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = zf_write,
+ .ioctl = zf_ioctl,
+ .open = zf_open,
+ .release = zf_close,
+};
+
+static struct miscdevice zf_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &zf_fops,
+};
+
+
+/*
+ * The device needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+static struct notifier_block zf_notifier = {
+ .notifier_call = zf_notify_sys,
+};
+
+static void __init zf_show_action(int act)
+{
+ char *str[] = { "RESET", "SMI", "NMI", "SCI" };
+
+ printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]);
+}
+
+static int __init zf_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n");
+
+ ret = zf_get_ZFL_version();
+ printk("%#x\n", ret);
+ if((!ret) || (ret != 0xffff)){
+ printk(KERN_WARNING PFX ": no ZF-Logic found\n");
+ return -ENODEV;
+ }
+
+ if((action <= 3) && (action >= 0)){
+ zf_action = zf_action>>action;
+ } else
+ action = 0;
+
+ zf_show_action(action);
+
+ spin_lock_init(&zf_lock);
+ spin_lock_init(&zf_port_lock);
+
+ ret = misc_register(&zf_miscdev);
+ if (ret){
+ printk(KERN_ERR "can't misc_register on minor=%d\n",
+ WATCHDOG_MINOR);
+ goto out;
+ }
+
+ if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){
+ printk(KERN_ERR "cannot reserve I/O ports at %d\n",
+ ZF_IOBASE);
+ ret = -EBUSY;
+ goto no_region;
+ }
+
+ ret = register_reboot_notifier(&zf_notifier);
+ if(ret){
+ printk(KERN_ERR "can't register reboot notifier (err=%d)\n",
+ ret);
+ goto no_reboot;
+ }
+
+ zf_set_status(0);
+ zf_set_control(0);
+
+ /* this is the timer that will do the hard work */
+ init_timer(&zf_timer);
+ zf_timer.function = zf_ping;
+ zf_timer.data = 0;
+
+ return 0;
+
+no_reboot:
+ release_region(ZF_IOBASE, 3);
+no_region:
+ misc_deregister(&zf_miscdev);
+out:
+ return ret;
+}
+
+
+static void __exit zf_exit(void)
+{
+ zf_timer_off();
+
+ misc_deregister(&zf_miscdev);
+ unregister_reboot_notifier(&zf_notifier);
+ release_region(ZF_IOBASE, 3);
+}
+
+module_init(zf_init);
+module_exit(zf_exit);
diff --git a/drivers/char/watchdog/mixcomwd.c b/drivers/char/watchdog/mixcomwd.c
new file mode 100644
index 000000000000..3143e4a07535
--- /dev/null
+++ b/drivers/char/watchdog/mixcomwd.c
@@ -0,0 +1,306 @@
+/*
+ * MixCom Watchdog: A Simple Hardware Watchdog Device
+ * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
+ *
+ * Author: Gergely Madarasz <gorgo@itc.hu>
+ *
+ * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Version 0.1 (99/04/15):
+ * - first version
+ *
+ * Version 0.2 (99/06/16):
+ * - added kernel timer watchdog ping after close
+ * since the hardware does not support watchdog shutdown
+ *
+ * Version 0.3 (99/06/21):
+ * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
+ *
+ * Version 0.3.1 (99/06/22):
+ * - allow module removal while internal timer is active,
+ * print warning about probable reset
+ *
+ * Version 0.4 (99/11/15):
+ * - support for one more type board
+ *
+ * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com>
+ * - added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ */
+
+#define VERSION "0.5"
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+static int mixcomwd_ioports[] = { 0x180, 0x280, 0x380, 0x000 };
+
+#define MIXCOM_WATCHDOG_OFFSET 0xc10
+#define MIXCOM_ID 0x11
+#define FLASHCOM_WATCHDOG_OFFSET 0x4
+#define FLASHCOM_ID 0x18
+
+static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */
+
+static int watchdog_port;
+static int mixcomwd_timer_alive;
+static struct timer_list mixcomwd_timer = TIMER_INITIALIZER(NULL, 0, 0);
+static char expect_close;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void mixcomwd_ping(void)
+{
+ outb_p(55,watchdog_port);
+ return;
+}
+
+static void mixcomwd_timerfun(unsigned long d)
+{
+ mixcomwd_ping();
+
+ mod_timer(&mixcomwd_timer,jiffies+ 5*HZ);
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+
+static int mixcomwd_open(struct inode *inode, struct file *file)
+{
+ if(test_and_set_bit(0,&mixcomwd_opened)) {
+ return -EBUSY;
+ }
+ mixcomwd_ping();
+
+ if (nowayout) {
+ /*
+ * fops_get() code via open() has already done
+ * a try_module_get() so it is safe to do the
+ * __module_get().
+ */
+ __module_get(THIS_MODULE);
+ } else {
+ if(mixcomwd_timer_alive) {
+ del_timer(&mixcomwd_timer);
+ mixcomwd_timer_alive=0;
+ }
+ }
+ return nonseekable_open(inode, file);
+}
+
+static int mixcomwd_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ if(mixcomwd_timer_alive) {
+ printk(KERN_ERR "mixcomwd: release called while internal timer alive");
+ return -EBUSY;
+ }
+ init_timer(&mixcomwd_timer);
+ mixcomwd_timer.expires=jiffies + 5 * HZ;
+ mixcomwd_timer.function=mixcomwd_timerfun;
+ mixcomwd_timer.data=0;
+ mixcomwd_timer_alive=1;
+ add_timer(&mixcomwd_timer);
+ } else {
+ printk(KERN_CRIT "mixcomwd: WDT device closed unexpectedly. WDT will not stop!\n");
+ }
+
+ clear_bit(0,&mixcomwd_opened);
+ expect_close=0;
+ return 0;
+}
+
+
+static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+ if(len)
+ {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ mixcomwd_ping();
+ }
+ return len;
+}
+
+static int mixcomwd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int status;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "MixCOM watchdog",
+ };
+
+ switch(cmd)
+ {
+ case WDIOC_GETSTATUS:
+ status=mixcomwd_opened;
+ if (!nowayout) {
+ status|=mixcomwd_timer_alive;
+ }
+ if (copy_to_user(p, &status, sizeof(int))) {
+ return -EFAULT;
+ }
+ break;
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident))) {
+ return -EFAULT;
+ }
+ break;
+ case WDIOC_KEEPALIVE:
+ mixcomwd_ping();
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static struct file_operations mixcomwd_fops=
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = mixcomwd_write,
+ .ioctl = mixcomwd_ioctl,
+ .open = mixcomwd_open,
+ .release = mixcomwd_release,
+};
+
+static struct miscdevice mixcomwd_miscdev=
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mixcomwd_fops,
+};
+
+static int __init mixcomwd_checkcard(int port)
+{
+ int id;
+
+ port += MIXCOM_WATCHDOG_OFFSET;
+ if (!request_region(port, 1, "MixCOM watchdog")) {
+ return 0;
+ }
+
+ id=inb_p(port) & 0x3f;
+ if(id!=MIXCOM_ID) {
+ release_region(port, 1);
+ return 0;
+ }
+ return port;
+}
+
+static int __init flashcom_checkcard(int port)
+{
+ int id;
+
+ port += FLASHCOM_WATCHDOG_OFFSET;
+ if (!request_region(port, 1, "MixCOM watchdog")) {
+ return 0;
+ }
+
+ id=inb_p(port);
+ if(id!=FLASHCOM_ID) {
+ release_region(port, 1);
+ return 0;
+ }
+ return port;
+ }
+
+static int __init mixcomwd_init(void)
+{
+ int i;
+ int ret;
+ int found=0;
+
+ for (i = 0; !found && mixcomwd_ioports[i] != 0; i++) {
+ watchdog_port = mixcomwd_checkcard(mixcomwd_ioports[i]);
+ if (watchdog_port) {
+ found = 1;
+ }
+ }
+
+ /* The FlashCOM card can be set up at 0x300 -> 0x378, in 0x8 jumps */
+ for (i = 0x300; !found && i < 0x380; i+=0x8) {
+ watchdog_port = flashcom_checkcard(i);
+ if (watchdog_port) {
+ found = 1;
+ }
+ }
+
+ if (!found) {
+ printk("mixcomwd: No card detected, or port not available.\n");
+ return -ENODEV;
+ }
+
+ ret = misc_register(&mixcomwd_miscdev);
+ if (ret)
+ {
+ release_region(watchdog_port, 1);
+ return ret;
+ }
+
+ printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",VERSION,watchdog_port);
+
+ return 0;
+}
+
+static void __exit mixcomwd_exit(void)
+{
+ if (!nowayout) {
+ if(mixcomwd_timer_alive) {
+ printk(KERN_WARNING "mixcomwd: I quit now, hardware will"
+ " probably reboot!\n");
+ del_timer(&mixcomwd_timer);
+ mixcomwd_timer_alive=0;
+ }
+ }
+ release_region(watchdog_port,1);
+ misc_deregister(&mixcomwd_miscdev);
+}
+
+module_init(mixcomwd_init);
+module_exit(mixcomwd_exit);
+
+MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
+MODULE_DESCRIPTION("MixCom Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/mpc8xx_wdt.c b/drivers/char/watchdog/mpc8xx_wdt.c
new file mode 100644
index 000000000000..56d62ba7c6ce
--- /dev/null
+++ b/drivers/char/watchdog/mpc8xx_wdt.c
@@ -0,0 +1,164 @@
+/*
+ * mpc8xx_wdt.c - MPC8xx watchdog userspace interface
+ *
+ * Author: Florian Schirmer <jolt@tuxbox.org>
+ *
+ * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <asm/8xx_immap.h>
+#include <asm/uaccess.h>
+#include <syslib/m8xx_wdt.h>
+
+static unsigned long wdt_opened;
+static int wdt_status;
+
+static void mpc8xx_wdt_handler_disable(void)
+{
+ volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR;
+
+ imap->im_sit.sit_piscr &= ~(PISCR_PIE | PISCR_PTE);
+
+ printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n");
+}
+
+static void mpc8xx_wdt_handler_enable(void)
+{
+ volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR;
+
+ imap->im_sit.sit_piscr |= PISCR_PIE | PISCR_PTE;
+
+ printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n");
+}
+
+static int mpc8xx_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &wdt_opened))
+ return -EBUSY;
+
+ m8xx_wdt_reset();
+ mpc8xx_wdt_handler_disable();
+
+ return 0;
+}
+
+static int mpc8xx_wdt_release(struct inode *inode, struct file *file)
+{
+ m8xx_wdt_reset();
+
+#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
+ mpc8xx_wdt_handler_enable();
+#endif
+
+ clear_bit(0, &wdt_opened);
+
+ return 0;
+}
+
+static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len,
+ loff_t * ppos)
+{
+ if (ppos != &file->f_pos)
+ return -ESPIPE;
+
+ if (len)
+ m8xx_wdt_reset();
+
+ return len;
+}
+
+static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int timeout;
+ static struct watchdog_info info = {
+ .options = WDIOF_KEEPALIVEPING,
+ .firmware_version = 0,
+ .identity = "MPC8xx watchdog",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(wdt_status, (int *)arg))
+ return -EFAULT;
+ wdt_status &= ~WDIOF_KEEPALIVEPING;
+ break;
+
+ case WDIOC_GETTEMP:
+ return -EOPNOTSUPP;
+
+ case WDIOC_SETOPTIONS:
+ return -EOPNOTSUPP;
+
+ case WDIOC_KEEPALIVE:
+ m8xx_wdt_reset();
+ wdt_status |= WDIOF_KEEPALIVEPING;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ return -EOPNOTSUPP;
+
+ case WDIOC_GETTIMEOUT:
+ timeout = m8xx_wdt_get_timeout();
+ if (put_user(timeout, (int *)arg))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static struct file_operations mpc8xx_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = mpc8xx_wdt_write,
+ .ioctl = mpc8xx_wdt_ioctl,
+ .open = mpc8xx_wdt_open,
+ .release = mpc8xx_wdt_release,
+};
+
+static struct miscdevice mpc8xx_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mpc8xx_wdt_fops,
+};
+
+static int __init mpc8xx_wdt_init(void)
+{
+ return misc_register(&mpc8xx_wdt_miscdev);
+}
+
+static void __exit mpc8xx_wdt_exit(void)
+{
+ misc_deregister(&mpc8xx_wdt_miscdev);
+
+ m8xx_wdt_reset();
+ mpc8xx_wdt_handler_enable();
+}
+
+module_init(mpc8xx_wdt_init);
+module_exit(mpc8xx_wdt_exit);
+
+MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>");
+MODULE_DESCRIPTION("MPC8xx watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/pcwd.c b/drivers/char/watchdog/pcwd.c
new file mode 100644
index 000000000000..592dca108866
--- /dev/null
+++ b/drivers/char/watchdog/pcwd.c
@@ -0,0 +1,926 @@
+/*
+ * PC Watchdog Driver
+ * by Ken Hollis (khollis@bitgate.com)
+ *
+ * Permission granted from Simon Machell (73244.1270@compuserve.com)
+ * Written for the Linux Kernel, and GPLed by Ken Hollis
+ *
+ * 960107 Added request_region routines, modulized the whole thing.
+ * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added
+ * WD_TIMEOUT define.
+ * 960216 Added eof marker on the file, and changed verbose messages.
+ * 960716 Made functional and cosmetic changes to the source for
+ * inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
+ * 960717 Removed read/seek routines, replaced with ioctl. Also, added
+ * check_region command due to Alan's suggestion.
+ * 960821 Made changes to compile in newer 2.0.x kernels. Added
+ * "cold reboot sense" entry.
+ * 960825 Made a few changes to code, deleted some defines and made
+ * typedefs to replace them. Made heartbeat reset only available
+ * via ioctl, and removed the write routine.
+ * 960828 Added new items for PC Watchdog Rev.C card.
+ * 960829 Changed around all of the IOCTLs, added new features,
+ * added watchdog disable/re-enable routines. Added firmware
+ * version reporting. Added read routine for temperature.
+ * Removed some extra defines, added an autodetect Revision
+ * routine.
+ * 961006 Revised some documentation, fixed some cosmetic bugs. Made
+ * drivers to panic the system if it's overheating at bootup.
+ * 961118 Changed some verbiage on some of the output, tidied up
+ * code bits, and added compatibility to 2.1.x.
+ * 970912 Enabled board on open and disable on close.
+ * 971107 Took account of recent VFS changes (broke read).
+ * 971210 Disable board on initialisation in case board already ticking.
+ * 971222 Changed open/close for temperature handling
+ * Michael Meskes <meskes@debian.org>.
+ * 980112 Used minor numbers from include/linux/miscdevice.h
+ * 990403 Clear reset status after reading control status register in
+ * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
+ * 990605 Made changes to code to support Firmware 1.22a, added
+ * fairly useless proc entry.
+ * 990610 removed said useless proc code for the merge <alan>
+ * 000403 Removed last traces of proc code. <davej>
+ * 011214 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
+ * Added timeout module option to override default
+ */
+
+/*
+ * A bells and whistles driver is available from http://www.pcwd.de/
+ * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/config.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/reboot.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define WD_VER "1.16 (06/12/2004)"
+#define PFX "pcwd: "
+
+/*
+ * It should be noted that PCWD_REVISION_B was removed because A and B
+ * are essentially the same types of card, with the exception that B
+ * has temperature reporting. Since I didn't receive a Rev.B card,
+ * the Rev.B card is not supported. (It's a good thing too, as they
+ * are no longer in production.)
+ */
+#define PCWD_REVISION_A 1
+#define PCWD_REVISION_C 2
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PC Watchdog card, revision A.
+ */
+#define WD_WDRST 0x01 /* Previously reset state */
+#define WD_T110 0x02 /* Temperature overheat sense */
+#define WD_HRTBT 0x04 /* Heartbeat sense */
+#define WD_RLY2 0x08 /* External relay triggered */
+#define WD_SRLY2 0x80 /* Software external relay triggered */
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PC Watchdog card, revision C.
+ */
+#define WD_REVC_WTRP 0x01 /* Watchdog Trip status */
+#define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */
+#define WD_REVC_TTRP 0x04 /* Temperature Trip status */
+
+/* max. time we give an ISA watchdog card to process a command */
+/* 500ms for each 4 bit response (according to spec.) */
+#define ISA_COMMAND_TIMEOUT 1000
+
+/* Watchdog's internal commands */
+#define CMD_ISA_IDLE 0x00
+#define CMD_ISA_VERSION_INTEGER 0x01
+#define CMD_ISA_VERSION_TENTH 0x02
+#define CMD_ISA_VERSION_HUNDRETH 0x03
+#define CMD_ISA_VERSION_MINOR 0x04
+#define CMD_ISA_SWITCH_SETTINGS 0x05
+#define CMD_ISA_DELAY_TIME_2SECS 0x0A
+#define CMD_ISA_DELAY_TIME_4SECS 0x0B
+#define CMD_ISA_DELAY_TIME_8SECS 0x0C
+
+/*
+ * We are using an kernel timer to do the pinging of the watchdog
+ * every ~500ms. We try to set the internal heartbeat of the
+ * watchdog to 2 ms.
+ */
+
+#define WDT_INTERVAL (HZ/2+1)
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static atomic_t open_allowed = ATOMIC_INIT(1);
+static char expect_close;
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static int temp_panic;
+static int revision; /* The card's revision */
+static int supports_temp; /* Wether or not the card has a temperature device */
+static int command_mode; /* Wether or not the card is in command mode */
+static int initial_status; /* The card's boot status */
+static int current_readport; /* The cards I/O address */
+static spinlock_t io_lock;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<=heartbeat<=7200, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Internal functions
+ */
+
+static int send_isa_command(int cmd)
+{
+ int i;
+ int control_status;
+ int port0, last_port0; /* Double read for stabilising */
+
+ /* The WCMD bit must be 1 and the command is only 4 bits in size */
+ control_status = (cmd & 0x0F) | 0x80;
+ outb_p(control_status, current_readport + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+
+ port0 = inb_p(current_readport);
+ for (i = 0; i < 25; ++i) {
+ last_port0 = port0;
+ port0 = inb_p(current_readport);
+
+ if (port0 == last_port0)
+ break; /* Data is stable */
+
+ udelay (250);
+ }
+
+ return port0;
+}
+
+static int set_command_mode(void)
+{
+ int i, found=0, count=0;
+
+ /* Set the card into command mode */
+ spin_lock(&io_lock);
+ while ((!found) && (count < 3)) {
+ i = send_isa_command(CMD_ISA_IDLE);
+
+ if (i == 0x00)
+ found = 1;
+ else if (i == 0xF3) {
+ /* Card does not like what we've done to it */
+ outb_p(0x00, current_readport + 2);
+ udelay(1200); /* Spec says wait 1ms */
+ outb_p(0x00, current_readport + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+ }
+ count++;
+ }
+ spin_unlock(&io_lock);
+ command_mode = found;
+
+ return(found);
+}
+
+static void unset_command_mode(void)
+{
+ /* Set the card into normal mode */
+ spin_lock(&io_lock);
+ outb_p(0x00, current_readport + 2);
+ udelay(ISA_COMMAND_TIMEOUT);
+ spin_unlock(&io_lock);
+
+ command_mode = 0;
+}
+
+static void pcwd_timer_ping(unsigned long data)
+{
+ int wdrst_stat;
+
+ /* If we got a heartbeat pulse within the WDT_INTERVAL
+ * we agree to ping the WDT */
+ if(time_before(jiffies, next_heartbeat)) {
+ /* Ping the watchdog */
+ spin_lock(&io_lock);
+ if (revision == PCWD_REVISION_A) {
+ /* Rev A cards are reset by setting the WD_WDRST bit in register 1 */
+ wdrst_stat = inb_p(current_readport);
+ wdrst_stat &= 0x0F;
+ wdrst_stat |= WD_WDRST;
+
+ outb_p(wdrst_stat, current_readport + 1);
+ } else {
+ /* Re-trigger watchdog by writing to port 0 */
+ outb_p(0x00, current_readport);
+ }
+
+ /* Re-set the timer interval */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+ spin_unlock(&io_lock);
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+static int pcwd_start(void)
+{
+ int stat_reg;
+
+ next_heartbeat = jiffies + (heartbeat * HZ);
+
+ /* Start the timer */
+ mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+ /* Enable the port */
+ if (revision == PCWD_REVISION_C) {
+ spin_lock(&io_lock);
+ outb_p(0x00, current_readport + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ stat_reg = inb_p(current_readport + 2);
+ spin_unlock(&io_lock);
+ if (stat_reg & 0x10) {
+ printk(KERN_INFO PFX "Could not start watchdog\n");
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+static int pcwd_stop(void)
+{
+ int stat_reg;
+
+ /* Stop the timer */
+ del_timer(&timer);
+
+ /* Disable the board */
+ if (revision == PCWD_REVISION_C) {
+ spin_lock(&io_lock);
+ outb_p(0xA5, current_readport + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ outb_p(0xA5, current_readport + 3);
+ udelay(ISA_COMMAND_TIMEOUT);
+ stat_reg = inb_p(current_readport + 2);
+ spin_unlock(&io_lock);
+ if ((stat_reg & 0x10) == 0) {
+ printk(KERN_INFO PFX "Could not stop watchdog\n");
+ return -EIO;
+ }
+ }
+ return 0;
+}
+
+static int pcwd_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (heartbeat * HZ);
+ return 0;
+}
+
+static int pcwd_set_heartbeat(int t)
+{
+ if ((t < 2) || (t > 7200)) /* arbitrary upper limit */
+ return -EINVAL;
+
+ heartbeat = t;
+ return 0;
+}
+
+static int pcwd_get_status(int *status)
+{
+ int card_status;
+
+ *status=0;
+ spin_lock(&io_lock);
+ if (revision == PCWD_REVISION_A)
+ /* Rev A cards return status information from
+ * the base register, which is used for the
+ * temperature in other cards. */
+ card_status = inb(current_readport);
+ else {
+ /* Rev C cards return card status in the base
+ * address + 1 register. And use different bits
+ * to indicate a card initiated reset, and an
+ * over-temperature condition. And the reboot
+ * status can be reset. */
+ card_status = inb(current_readport + 1);
+ }
+ spin_unlock(&io_lock);
+
+ if (revision == PCWD_REVISION_A) {
+ if (card_status & WD_WDRST)
+ *status |= WDIOF_CARDRESET;
+
+ if (card_status & WD_T110) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic) {
+ printk (KERN_INFO PFX "Temperature overheat trip!\n");
+ machine_power_off();
+ }
+ }
+ } else {
+ if (card_status & WD_REVC_WTRP)
+ *status |= WDIOF_CARDRESET;
+
+ if (card_status & WD_REVC_TTRP) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic) {
+ printk (KERN_INFO PFX "Temperature overheat trip!\n");
+ machine_power_off();
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int pcwd_clear_status(void)
+{
+ if (revision == PCWD_REVISION_C) {
+ spin_lock(&io_lock);
+ outb_p(0x00, current_readport + 1); /* clear reset status */
+ spin_unlock(&io_lock);
+ }
+ return 0;
+}
+
+static int pcwd_get_temperature(int *temperature)
+{
+ /* check that port 0 gives temperature info and no command results */
+ if (command_mode)
+ return -1;
+
+ *temperature = 0;
+ if (!supports_temp)
+ return -ENODEV;
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ spin_lock(&io_lock);
+ *temperature = ((inb(current_readport)) * 9 / 5) + 32;
+ spin_unlock(&io_lock);
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int pcwd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int rv;
+ int status;
+ int temperature;
+ int new_heartbeat;
+ int __user *argp = (int __user *)arg;
+ static struct watchdog_info ident = {
+ .options = WDIOF_OVERHEAT |
+ WDIOF_CARDRESET |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "PCWD",
+ };
+
+ switch(cmd) {
+ default:
+ return -ENOIOCTLCMD;
+
+ case WDIOC_GETSUPPORT:
+ if(copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ pcwd_get_status(&status);
+ return put_user(status, argp);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(initial_status, argp);
+
+ case WDIOC_GETTEMP:
+ if (pcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ return put_user(temperature, argp);
+
+ case WDIOC_SETOPTIONS:
+ if (revision == PCWD_REVISION_C)
+ {
+ if(copy_from_user(&rv, argp, sizeof(int)))
+ return -EFAULT;
+
+ if (rv & WDIOS_DISABLECARD)
+ {
+ return pcwd_stop();
+ }
+
+ if (rv & WDIOS_ENABLECARD)
+ {
+ return pcwd_start();
+ }
+
+ if (rv & WDIOS_TEMPPANIC)
+ {
+ temp_panic = 1;
+ }
+ }
+ return -EINVAL;
+
+ case WDIOC_KEEPALIVE:
+ pcwd_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, argp))
+ return -EFAULT;
+
+ if (pcwd_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ pcwd_keepalive();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, argp);
+ }
+
+ return 0;
+}
+
+static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ pcwd_keepalive();
+ }
+ return len;
+}
+
+static int pcwd_open(struct inode *inode, struct file *file)
+{
+ if (!atomic_dec_and_test(&open_allowed) ) {
+ atomic_inc( &open_allowed );
+ return -EBUSY;
+ }
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Activate */
+ pcwd_start();
+ pcwd_keepalive();
+ return nonseekable_open(inode, file);
+}
+
+static int pcwd_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ pcwd_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ pcwd_keepalive();
+ }
+ expect_close = 0;
+ atomic_inc( &open_allowed );
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ int temperature;
+
+ if (pcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user(buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int pcwd_temp_open(struct inode *inode, struct file *file)
+{
+ if (!supports_temp)
+ return -ENODEV;
+
+ return nonseekable_open(inode, file);
+}
+
+static int pcwd_temp_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Notify system
+ */
+
+static int pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ pcwd_stop();
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations pcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = pcwd_write,
+ .ioctl = pcwd_ioctl,
+ .open = pcwd_open,
+ .release = pcwd_close,
+};
+
+static struct miscdevice pcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pcwd_fops,
+};
+
+static struct file_operations pcwd_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pcwd_temp_read,
+ .open = pcwd_temp_open,
+ .release = pcwd_temp_close,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &pcwd_temp_fops,
+};
+
+static struct notifier_block pcwd_notifier = {
+ .notifier_call = pcwd_notify_sys,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static inline void get_support(void)
+{
+ if (inb(current_readport) != 0xF0)
+ supports_temp = 1;
+}
+
+static inline int get_revision(void)
+{
+ int r = PCWD_REVISION_C;
+
+ spin_lock(&io_lock);
+ /* REV A cards use only 2 io ports; test
+ * presumes a floating bus reads as 0xff. */
+ if ((inb(current_readport + 2) == 0xFF) ||
+ (inb(current_readport + 3) == 0xFF))
+ r=PCWD_REVISION_A;
+ spin_unlock(&io_lock);
+
+ return r;
+}
+
+static inline char *get_firmware(void)
+{
+ int one, ten, hund, minor;
+ char *ret;
+
+ ret = kmalloc(6, GFP_KERNEL);
+ if(ret == NULL)
+ return NULL;
+
+ if (set_command_mode()) {
+ one = send_isa_command(CMD_ISA_VERSION_INTEGER);
+ ten = send_isa_command(CMD_ISA_VERSION_TENTH);
+ hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH);
+ minor = send_isa_command(CMD_ISA_VERSION_MINOR);
+ sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
+ }
+ else
+ sprintf(ret, "ERROR");
+
+ unset_command_mode();
+ return(ret);
+}
+
+static inline int get_option_switches(void)
+{
+ int rv=0;
+
+ if (set_command_mode()) {
+ /* Get switch settings */
+ rv = send_isa_command(CMD_ISA_SWITCH_SETTINGS);
+ }
+
+ unset_command_mode();
+ return(rv);
+}
+
+static int __devinit pcwatchdog_init(int base_addr)
+{
+ int ret;
+ char *firmware;
+ int option_switches;
+
+ cards_found++;
+ if (cards_found == 1)
+ printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
+
+ if (cards_found > 1) {
+ printk(KERN_ERR PFX "This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ if (base_addr == 0x0000) {
+ printk(KERN_ERR PFX "No I/O-Address for card detected\n");
+ return -ENODEV;
+ }
+ current_readport = base_addr;
+
+ /* Check card's revision */
+ revision = get_revision();
+
+ if (!request_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ current_readport);
+ current_readport = 0x0000;
+ return -EIO;
+ }
+
+ /* Initial variables */
+ supports_temp = 0;
+ temp_panic = 0;
+ initial_status = 0x0000;
+
+ /* get the boot_status */
+ pcwd_get_status(&initial_status);
+
+ /* clear the "card caused reboot" flag */
+ pcwd_clear_status();
+
+ init_timer(&timer);
+ timer.function = pcwd_timer_ping;
+ timer.data = 0;
+
+ /* Disable the board */
+ pcwd_stop();
+
+ /* Check whether or not the card supports the temperature device */
+ get_support();
+
+ /* Get some extra info from the hardware (in command/debug/diag mode) */
+ if (revision == PCWD_REVISION_A)
+ printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", current_readport);
+ else if (revision == PCWD_REVISION_C) {
+ firmware = get_firmware();
+ printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n",
+ current_readport, firmware);
+ kfree(firmware);
+ option_switches = get_option_switches();
+ printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ /* Reprogram internal heartbeat to 2 seconds */
+ if (set_command_mode()) {
+ send_isa_command(CMD_ISA_DELAY_TIME_2SECS);
+ unset_command_mode();
+ }
+ } else {
+ /* Should NEVER happen, unless get_revision() fails. */
+ printk(KERN_INFO PFX "Unable to get revision\n");
+ release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+ current_readport = 0x0000;
+ return -1;
+ }
+
+ if (supports_temp)
+ printk(KERN_INFO PFX "Temperature Option Detected\n");
+
+ if (initial_status & WDIOF_CARDRESET)
+ printk(KERN_INFO PFX "Previous reboot was caused by the card\n");
+
+ if (initial_status & WDIOF_OVERHEAT) {
+ printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n");
+ printk(KERN_EMERG PFX "CPU Overheat\n");
+ }
+
+ if (initial_status == 0)
+ printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n");
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (pcwd_set_heartbeat(heartbeat)) {
+ pcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+ printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ ret = register_reboot_notifier(&pcwd_notifier);
+ if (ret) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+ current_readport = 0x0000;
+ return ret;
+ }
+
+ if (supports_temp) {
+ ret = misc_register(&temp_miscdev);
+ if (ret) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ unregister_reboot_notifier(&pcwd_notifier);
+ release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+ current_readport = 0x0000;
+ return ret;
+ }
+ }
+
+ ret = misc_register(&pcwd_miscdev);
+ if (ret) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ if (supports_temp)
+ misc_deregister(&temp_miscdev);
+ unregister_reboot_notifier(&pcwd_notifier);
+ release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+ current_readport = 0x0000;
+ return ret;
+ }
+
+ printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+}
+
+static void __devexit pcwatchdog_exit(void)
+{
+ /* Disable the board */
+ if (!nowayout)
+ pcwd_stop();
+
+ /* Deregister */
+ misc_deregister(&pcwd_miscdev);
+ if (supports_temp)
+ misc_deregister(&temp_miscdev);
+ unregister_reboot_notifier(&pcwd_notifier);
+ release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+ current_readport = 0x0000;
+}
+
+/*
+ * The ISA cards have a heartbeat bit in one of the registers, which
+ * register is card dependent. The heartbeat bit is monitored, and if
+ * found, is considered proof that a Berkshire card has been found.
+ * The initial rate is once per second at board start up, then twice
+ * per second for normal operation.
+ */
+static int __init pcwd_checkcard(int base_addr)
+{
+ int port0, last_port0; /* Reg 0, in case it's REV A */
+ int port1, last_port1; /* Register 1 for REV C cards */
+ int i;
+ int retval;
+
+ if (!request_region (base_addr, 4, "PCWD")) {
+ printk (KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr);
+ return 0;
+ }
+
+ retval = 0;
+
+ port0 = inb_p(base_addr); /* For REV A boards */
+ port1 = inb_p(base_addr + 1); /* For REV C boards */
+ if (port0 != 0xff || port1 != 0xff) {
+ /* Not an 'ff' from a floating bus, so must be a card! */
+ for (i = 0; i < 4; ++i) {
+
+ msleep(500);
+
+ last_port0 = port0;
+ last_port1 = port1;
+
+ port0 = inb_p(base_addr);
+ port1 = inb_p(base_addr + 1);
+
+ /* Has either hearbeat bit changed? */
+ if ((port0 ^ last_port0) & WD_HRTBT ||
+ (port1 ^ last_port1) & WD_REVC_HRBT) {
+ retval = 1;
+ break;
+ }
+ }
+ }
+ release_region (base_addr, 4);
+
+ return retval;
+}
+
+/*
+ * These are the auto-probe addresses available.
+ *
+ * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350.
+ * Revision A has an address range of 2 addresses, while Revision C has 4.
+ */
+static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
+
+static int __init pcwd_init_module(void)
+{
+ int i, found = 0;
+
+ spin_lock_init(&io_lock);
+
+ for (i = 0; pcwd_ioports[i] != 0; i++) {
+ if (pcwd_checkcard(pcwd_ioports[i])) {
+ if (!(pcwatchdog_init(pcwd_ioports[i])))
+ found++;
+ }
+ }
+
+ if (!found) {
+ printk (KERN_INFO PFX "No card detected, or port not available\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit pcwd_cleanup_module(void)
+{
+ if (current_readport)
+ pcwatchdog_exit();
+ return;
+}
+
+module_init(pcwd_init_module);
+module_exit(pcwd_cleanup_module);
+
+MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>");
+MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
diff --git a/drivers/char/watchdog/pcwd_pci.c b/drivers/char/watchdog/pcwd_pci.c
new file mode 100644
index 000000000000..8ce066627326
--- /dev/null
+++ b/drivers/char/watchdog/pcwd_pci.c
@@ -0,0 +1,677 @@
+/*
+ * Berkshire PCI-PC Watchdog Card Driver
+ *
+ * (c) Copyright 2003 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Based on source code of the following authors:
+ * Ken Hollis <kenji@bitgate.com>,
+ * Lindsay Harris <lindsay@bluegum.com>,
+ * Alan Cox <alan@redhat.com>,
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ */
+
+/*
+ * A bells and whistles driver is available from http://www.pcwd.de/
+ * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+/*
+ * Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* Module and version information */
+#define WATCHDOG_VERSION "1.01"
+#define WATCHDOG_DATE "15 Mar 2005"
+#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog"
+#define WATCHDOG_NAME "pcwd_pci"
+#define PFX WATCHDOG_NAME ": "
+#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n"
+
+/* Stuff for the PCI ID's */
+#ifndef PCI_VENDOR_ID_QUICKLOGIC
+#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3
+#endif
+
+#ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD
+#define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030
+#endif
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PCI-PC Watchdog card.
+ */
+#define WD_PCI_WTRP 0x01 /* Watchdog Trip status */
+#define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */
+#define WD_PCI_TTRP 0x04 /* Temperature Trip status */
+
+/* according to documentation max. time to process a command for the pci
+ * watchdog card is 100 ms, so we give it 150 ms to do it's job */
+#define PCI_COMMAND_TIMEOUT 150
+
+/* Watchdog's internal commands */
+#define CMD_GET_STATUS 0x04
+#define CMD_GET_FIRMWARE_VERSION 0x08
+#define CMD_READ_WATCHDOG_TIMEOUT 0x18
+#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static int temp_panic;
+static unsigned long is_active;
+static char expect_release;
+static struct {
+ int supports_temp; /* Wether or not the card has a temperature device */
+ int boot_status; /* The card's boot status */
+ unsigned long io_addr; /* The cards I/O address */
+ spinlock_t io_lock;
+ struct pci_dev *pdev;
+} pcipcwd_private;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Internal functions
+ */
+
+static int send_command(int cmd, int *msb, int *lsb)
+{
+ int got_response, count;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ /* If a command requires data it should be written first.
+ * Data for commands with 8 bits of data should be written to port 4.
+ * Commands with 16 bits of data, should be written as LSB to port 4
+ * and MSB to port 5.
+ * After the required data has been written then write the command to
+ * port 6. */
+ outb_p(*lsb, pcipcwd_private.io_addr + 4);
+ outb_p(*msb, pcipcwd_private.io_addr + 5);
+ outb_p(cmd, pcipcwd_private.io_addr + 6);
+
+ /* wait till the pci card processed the command, signaled by
+ * the WRSP bit in port 2 and give it a max. timeout of
+ * PCI_COMMAND_TIMEOUT to process */
+ got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40;
+ for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); count++) {
+ mdelay(1);
+ got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40;
+ }
+
+ if (got_response) {
+ /* read back response */
+ *lsb = inb_p(pcipcwd_private.io_addr + 4);
+ *msb = inb_p(pcipcwd_private.io_addr + 5);
+
+ /* clear WRSP bit */
+ inb_p(pcipcwd_private.io_addr + 6);
+ }
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ return got_response;
+}
+
+static int pcipcwd_start(void)
+{
+ int stat_reg;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ outb_p(0x00, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ if (stat_reg & 0x10) {
+ printk(KERN_ERR PFX "Card timer not enabled\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pcipcwd_stop(void)
+{
+ int stat_reg;
+
+ spin_lock(&pcipcwd_private.io_lock);
+ outb_p(0xA5, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ outb_p(0xA5, pcipcwd_private.io_addr + 3);
+ udelay(1000);
+
+ stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+ spin_unlock(&pcipcwd_private.io_lock);
+
+ if (!(stat_reg & 0x10)) {
+ printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int pcipcwd_keepalive(void)
+{
+ /* Re-trigger watchdog by writing to port 0 */
+ outb_p(0x42, pcipcwd_private.io_addr);
+ return 0;
+}
+
+static int pcipcwd_set_heartbeat(int t)
+{
+ int t_msb = t / 256;
+ int t_lsb = t % 256;
+
+ if ((t < 0x0001) || (t > 0xFFFF))
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb);
+
+ heartbeat = t;
+ return 0;
+}
+
+static int pcipcwd_get_status(int *status)
+{
+ int new_status;
+
+ *status=0;
+ new_status = inb_p(pcipcwd_private.io_addr + 1);
+ if (new_status & WD_PCI_WTRP)
+ *status |= WDIOF_CARDRESET;
+ if (new_status & WD_PCI_TTRP) {
+ *status |= WDIOF_OVERHEAT;
+ if (temp_panic)
+ panic(PFX "Temperature overheat trip!\n");
+ }
+
+ return 0;
+}
+
+static int pcipcwd_clear_status(void)
+{
+ outb_p(0x01, pcipcwd_private.io_addr + 1);
+ return 0;
+}
+
+static int pcipcwd_get_temperature(int *temperature)
+{
+ *temperature = 0;
+ if (!pcipcwd_private.supports_temp)
+ return -ENODEV;
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ *temperature = ((inb_p(pcipcwd_private.io_addr)) * 9 / 5) + 32;
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t pcipcwd_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ expect_release = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if(get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ pcipcwd_keepalive();
+ }
+ return len;
+}
+
+static int pcipcwd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_OVERHEAT |
+ WDIOF_CARDRESET |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = WATCHDOG_DRIVER_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident,
+ sizeof (ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ {
+ int status;
+
+ pcipcwd_get_status(&status);
+
+ return put_user(status, p);
+ }
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(pcipcwd_private.boot_status, p);
+
+ case WDIOC_GETTEMP:
+ {
+ int temperature;
+
+ if (pcipcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ return put_user(temperature, p);
+ }
+
+ case WDIOC_KEEPALIVE:
+ pcipcwd_keepalive();
+ return 0;
+
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if (get_user (new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ pcipcwd_stop();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ pcipcwd_start();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_TEMPPANIC) {
+ temp_panic = 1;
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_heartbeat;
+
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (pcipcwd_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ pcipcwd_keepalive();
+ /* Fall */
+ }
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int pcipcwd_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &is_active))
+ return -EBUSY;
+
+ /* Activate */
+ pcipcwd_start();
+ pcipcwd_keepalive();
+ return nonseekable_open(inode, file);
+}
+
+static int pcipcwd_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (expect_release == 42) {
+ pcipcwd_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ pcipcwd_keepalive();
+ }
+ expect_release = 0;
+ clear_bit(0, &is_active);
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t pcipcwd_temp_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int temperature;
+
+ if (pcipcwd_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user (data, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int pcipcwd_temp_open(struct inode *inode, struct file *file)
+{
+ if (!pcipcwd_private.supports_temp)
+ return -ENODEV;
+
+ return nonseekable_open(inode, file);
+}
+
+static int pcipcwd_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Notify system
+ */
+
+static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ pcipcwd_stop();
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations pcipcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = pcipcwd_write,
+ .ioctl = pcipcwd_ioctl,
+ .open = pcipcwd_open,
+ .release = pcipcwd_release,
+};
+
+static struct miscdevice pcipcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &pcipcwd_fops,
+};
+
+static struct file_operations pcipcwd_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = pcipcwd_temp_read,
+ .open = pcipcwd_temp_open,
+ .release = pcipcwd_temp_release,
+};
+
+static struct miscdevice pcipcwd_temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &pcipcwd_temp_fops,
+};
+
+static struct notifier_block pcipcwd_notifier = {
+ .notifier_call = pcipcwd_notify_sys,
+};
+
+/*
+ * Init & exit routines
+ */
+
+static inline void check_temperature_support(void)
+{
+ if (inb_p(pcipcwd_private.io_addr) != 0xF0)
+ pcipcwd_private.supports_temp = 1;
+}
+
+static int __devinit pcipcwd_card_init(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int ret = -EIO;
+ int got_fw_rev, fw_rev_major, fw_rev_minor;
+ char fw_ver_str[20];
+ char option_switches;
+
+ cards_found++;
+ if (cards_found == 1)
+ printk(KERN_INFO PFX DRIVER_VERSION);
+
+ if (cards_found > 1) {
+ printk(KERN_ERR PFX "This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ if (pci_enable_device(pdev)) {
+ printk(KERN_ERR PFX "Not possible to enable PCI Device\n");
+ return -ENODEV;
+ }
+
+ if (pci_resource_start(pdev, 0) == 0x0000) {
+ printk(KERN_ERR PFX "No I/O-Address for card detected\n");
+ ret = -ENODEV;
+ goto err_out_disable_device;
+ }
+
+ pcipcwd_private.pdev = pdev;
+ pcipcwd_private.io_addr = pci_resource_start(pdev, 0);
+
+ if (pci_request_regions(pdev, WATCHDOG_NAME)) {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ (int) pcipcwd_private.io_addr);
+ ret = -EIO;
+ goto err_out_disable_device;
+ }
+
+ /* get the boot_status */
+ pcipcwd_get_status(&pcipcwd_private.boot_status);
+
+ /* clear the "card caused reboot" flag */
+ pcipcwd_clear_status();
+
+ /* disable card */
+ pcipcwd_stop();
+
+ /* Check whether or not the card supports the temperature device */
+ check_temperature_support();
+
+ /* Get the Firmware Version */
+ got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor);
+ if (got_fw_rev) {
+ sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+ } else {
+ sprintf(fw_ver_str, "<card no answer>");
+ }
+
+ /* Get switch settings */
+ option_switches = inb_p(pcipcwd_private.io_addr + 3);
+
+ printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n",
+ (int) pcipcwd_private.io_addr, fw_ver_str,
+ (pcipcwd_private.supports_temp ? "with" : "without"));
+
+ printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ if (pcipcwd_private.boot_status & WDIOF_CARDRESET)
+ printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n");
+
+ if (pcipcwd_private.boot_status & WDIOF_OVERHEAT)
+ printk(KERN_INFO PFX "Card sensed a CPU Overheat\n");
+
+ if (pcipcwd_private.boot_status == 0)
+ printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n");
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (pcipcwd_set_heartbeat(heartbeat)) {
+ pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+ printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ ret = register_reboot_notifier(&pcipcwd_notifier);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto err_out_release_region;
+ }
+
+ if (pcipcwd_private.supports_temp) {
+ ret = misc_register(&pcipcwd_temp_miscdev);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto err_out_unregister_reboot;
+ }
+ }
+
+ ret = misc_register(&pcipcwd_miscdev);
+ if (ret != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto err_out_misc_deregister;
+ }
+
+ printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+
+err_out_misc_deregister:
+ if (pcipcwd_private.supports_temp)
+ misc_deregister(&pcipcwd_temp_miscdev);
+err_out_unregister_reboot:
+ unregister_reboot_notifier(&pcipcwd_notifier);
+err_out_release_region:
+ pci_release_regions(pdev);
+err_out_disable_device:
+ pci_disable_device(pdev);
+ return ret;
+}
+
+static void __devexit pcipcwd_card_exit(struct pci_dev *pdev)
+{
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ pcipcwd_stop();
+
+ /* Deregister */
+ misc_deregister(&pcipcwd_miscdev);
+ if (pcipcwd_private.supports_temp)
+ misc_deregister(&pcipcwd_temp_miscdev);
+ unregister_reboot_notifier(&pcipcwd_notifier);
+ pci_release_regions(pdev);
+ pci_disable_device(pdev);
+ cards_found--;
+}
+
+static struct pci_device_id pcipcwd_pci_tbl[] = {
+ { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0 }, /* End of list */
+};
+MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl);
+
+static struct pci_driver pcipcwd_driver = {
+ .name = WATCHDOG_NAME,
+ .id_table = pcipcwd_pci_tbl,
+ .probe = pcipcwd_card_init,
+ .remove = __devexit_p(pcipcwd_card_exit),
+};
+
+static int __init pcipcwd_init_module(void)
+{
+ spin_lock_init (&pcipcwd_private.io_lock);
+
+ return pci_register_driver(&pcipcwd_driver);
+}
+
+static void __exit pcipcwd_cleanup_module(void)
+{
+ pci_unregister_driver(&pcipcwd_driver);
+}
+
+module_init(pcipcwd_init_module);
+module_exit(pcipcwd_cleanup_module);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
diff --git a/drivers/char/watchdog/pcwd_usb.c b/drivers/char/watchdog/pcwd_usb.c
new file mode 100644
index 000000000000..1127201d73b8
--- /dev/null
+++ b/drivers/char/watchdog/pcwd_usb.c
@@ -0,0 +1,796 @@
+/*
+ * Berkshire USB-PC Watchdog Card Driver
+ *
+ * (c) Copyright 2004 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ * Based on source code of the following authors:
+ * Ken Hollis <kenji@bitgate.com>,
+ * Alan Cox <alan@redhat.com>,
+ * Matt Domsch <Matt_Domsch@dell.com>,
+ * Rob Radez <rob@osinvestor.com>,
+ * Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ * Thanks also to Simon Machell at Berkshire Products Inc. for
+ * providing the test hardware. More info is available at
+ * http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/completion.h>
+#include <asm/uaccess.h>
+#include <linux/usb.h>
+
+
+#ifdef CONFIG_USB_DEBUG
+ static int debug = 1;
+#else
+ static int debug;
+#endif
+
+/* Use our own dbg macro */
+#undef dbg
+#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0)
+
+
+/* Module and Version Information */
+#define DRIVER_VERSION "1.01"
+#define DRIVER_DATE "15 Mar 2005"
+#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
+#define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
+#define DRIVER_LICENSE "GPL"
+#define DRIVER_NAME "pcwd_usb"
+#define PFX DRIVER_NAME ": "
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
+
+/* Module Parameters */
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+#define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* The vendor and product id's for the USB-PC Watchdog card */
+#define USB_PCWD_VENDOR_ID 0x0c98
+#define USB_PCWD_PRODUCT_ID 0x1140
+
+/* table of devices that work with this driver */
+static struct usb_device_id usb_pcwd_table [] = {
+ { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, usb_pcwd_table);
+
+/* according to documentation max. time to process a command for the USB
+ * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
+#define USB_COMMAND_TIMEOUT 250
+
+/* Watchdog's internal commands */
+#define CMD_READ_TEMP 0x02 /* Read Temperature; Re-trigger Watchdog */
+#define CMD_TRIGGER CMD_READ_TEMP
+#define CMD_GET_STATUS 0x04 /* Get Status Information */
+#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */
+#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */
+#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */
+#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current Watchdog Time */
+#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */
+#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG
+
+/* Some defines that I like to be somewhere else like include/linux/usb_hid.h */
+#define HID_REQ_SET_REPORT 0x09
+#define HID_DT_REPORT (USB_TYPE_CLASS | 0x02)
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* some internal variables */
+static unsigned long is_active;
+static char expect_release;
+
+/* Structure to hold all of our device specific stuff */
+struct usb_pcwd_private {
+ struct usb_device * udev; /* save off the usb device pointer */
+ struct usb_interface * interface; /* the interface for this device */
+
+ unsigned int interface_number; /* the interface number used for cmd's */
+
+ unsigned char * intr_buffer; /* the buffer to intr data */
+ dma_addr_t intr_dma; /* the dma address for the intr buffer */
+ size_t intr_size; /* the size of the intr buffer */
+ struct urb * intr_urb; /* the urb used for the intr pipe */
+
+ unsigned char cmd_command; /* The command that is reported back */
+ unsigned char cmd_data_msb; /* The data MSB that is reported back */
+ unsigned char cmd_data_lsb; /* The data LSB that is reported back */
+ atomic_t cmd_received; /* true if we received a report after a command */
+
+ int exists; /* Wether or not the device exists */
+ struct semaphore sem; /* locks this structure */
+};
+static struct usb_pcwd_private *usb_pcwd_device;
+
+/* prevent races between open() and disconnect() */
+static DECLARE_MUTEX (disconnect_sem);
+
+/* local function prototypes */
+static int usb_pcwd_probe (struct usb_interface *interface, const struct usb_device_id *id);
+static void usb_pcwd_disconnect (struct usb_interface *interface);
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver usb_pcwd_driver = {
+ .owner = THIS_MODULE,
+ .name = DRIVER_NAME,
+ .probe = usb_pcwd_probe,
+ .disconnect = usb_pcwd_disconnect,
+ .id_table = usb_pcwd_table,
+};
+
+
+static void usb_pcwd_intr_done(struct urb *urb, struct pt_regs *regs)
+{
+ struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context;
+ unsigned char *data = usb_pcwd->intr_buffer;
+ int retval;
+
+ switch (urb->status) {
+ case 0: /* success */
+ break;
+ case -ECONNRESET: /* unlink */
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
+ return;
+ /* -EPIPE: should clear the halt */
+ default: /* error */
+ dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
+ goto resubmit;
+ }
+
+ dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+ data[0], data[1], data[2]);
+
+ usb_pcwd->cmd_command = data[0];
+ usb_pcwd->cmd_data_msb = data[1];
+ usb_pcwd->cmd_data_lsb = data[2];
+
+ /* notify anyone waiting that the cmd has finished */
+ atomic_set (&usb_pcwd->cmd_received, 1);
+
+resubmit:
+ retval = usb_submit_urb (urb, GFP_ATOMIC);
+ if (retval)
+ printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n",
+ retval);
+}
+
+static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd,
+ unsigned char *msb, unsigned char *lsb)
+{
+ int got_response, count;
+ unsigned char buf[6];
+
+ /* We will not send any commands if the USB PCWD device does not exist */
+ if ((!usb_pcwd) || (!usb_pcwd->exists))
+ return -1;
+
+ /* The USB PC Watchdog uses a 6 byte report format. The board currently uses
+ * only 3 of the six bytes of the report. */
+ buf[0] = cmd; /* Byte 0 = CMD */
+ buf[1] = *msb; /* Byte 1 = Data MSB */
+ buf[2] = *lsb; /* Byte 2 = Data LSB */
+ buf[3] = buf[4] = buf[5] = 0; /* All other bytes not used */
+
+ dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+ buf[0], buf[1], buf[2]);
+
+ atomic_set (&usb_pcwd->cmd_received, 0);
+
+ if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0),
+ HID_REQ_SET_REPORT, HID_DT_REPORT,
+ 0x0200, usb_pcwd->interface_number, buf, sizeof(buf),
+ USB_COMMAND_TIMEOUT) != sizeof(buf)) {
+ dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb);
+ }
+ /* wait till the usb card processed the command,
+ * with a max. timeout of USB_COMMAND_TIMEOUT */
+ got_response = 0;
+ for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) {
+ mdelay(1);
+ if (atomic_read (&usb_pcwd->cmd_received))
+ got_response = 1;
+ }
+
+ if ((got_response) && (cmd == usb_pcwd->cmd_command)) {
+ /* read back response */
+ *msb = usb_pcwd->cmd_data_msb;
+ *lsb = usb_pcwd->cmd_data_lsb;
+ }
+
+ return got_response;
+}
+
+static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char msb = 0x00;
+ unsigned char lsb = 0x00;
+ int retval;
+
+ /* Enable Watchdog */
+ retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb);
+
+ if ((retval == 0) || (lsb == 0)) {
+ printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char msb = 0xA5;
+ unsigned char lsb = 0xC3;
+ int retval;
+
+ /* Disable Watchdog */
+ retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb);
+
+ if ((retval == 0) || (lsb != 0)) {
+ printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd)
+{
+ unsigned char dummy;
+
+ /* Re-trigger Watchdog */
+ usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy);
+
+ return 0;
+}
+
+static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t)
+{
+ unsigned char msb = t / 256;
+ unsigned char lsb = t % 256;
+
+ if ((t < 0x0001) || (t > 0xFFFF))
+ return -EINVAL;
+
+ /* Write new heartbeat to watchdog */
+ usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb);
+
+ heartbeat = t;
+ return 0;
+}
+
+static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature)
+{
+ unsigned char msb, lsb;
+
+ usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb);
+
+ /*
+ * Convert celsius to fahrenheit, since this was
+ * the decided 'standard' for this return value.
+ */
+ *temperature = (lsb * 9 / 5) + 32;
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t usb_pcwd_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ expect_release = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != len; i++) {
+ char c;
+ if(get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_release = 42;
+ }
+ }
+
+ /* someone wrote to us, we should reload the timer */
+ usb_pcwd_keepalive(usb_pcwd_device);
+ }
+ return len;
+}
+
+static int usb_pcwd_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = DRIVER_NAME,
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident,
+ sizeof (ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_GETTEMP:
+ {
+ int temperature;
+
+ if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+ return -EFAULT;
+
+ return put_user(temperature, p);
+ }
+
+ case WDIOC_KEEPALIVE:
+ usb_pcwd_keepalive(usb_pcwd_device);
+ return 0;
+
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if (get_user (new_options, p))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ usb_pcwd_stop(usb_pcwd_device);
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ usb_pcwd_start(usb_pcwd_device);
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_heartbeat;
+
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat))
+ return -EINVAL;
+
+ usb_pcwd_keepalive(usb_pcwd_device);
+ /* Fall */
+ }
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int usb_pcwd_open(struct inode *inode, struct file *file)
+{
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &is_active))
+ return -EBUSY;
+
+ /* Activate */
+ usb_pcwd_start(usb_pcwd_device);
+ usb_pcwd_keepalive(usb_pcwd_device);
+ return nonseekable_open(inode, file);
+}
+
+static int usb_pcwd_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ */
+ if (expect_release == 42) {
+ usb_pcwd_stop(usb_pcwd_device);
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ usb_pcwd_keepalive(usb_pcwd_device);
+ }
+ expect_release = 0;
+ clear_bit(0, &is_active);
+ return 0;
+}
+
+/*
+ * /dev/temperature handling
+ */
+
+static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int temperature;
+
+ if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+ return -EFAULT;
+
+ if (copy_to_user(data, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+static int usb_pcwd_temperature_open(struct inode *inode, struct file *file)
+{
+ return nonseekable_open(inode, file);
+}
+
+static int usb_pcwd_temperature_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+/*
+ * Notify system
+ */
+
+static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ usb_pcwd_stop(usb_pcwd_device);
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations usb_pcwd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = usb_pcwd_write,
+ .ioctl = usb_pcwd_ioctl,
+ .open = usb_pcwd_open,
+ .release = usb_pcwd_release,
+};
+
+static struct miscdevice usb_pcwd_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &usb_pcwd_fops,
+};
+
+static struct file_operations usb_pcwd_temperature_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = usb_pcwd_temperature_read,
+ .open = usb_pcwd_temperature_open,
+ .release = usb_pcwd_temperature_release,
+};
+
+static struct miscdevice usb_pcwd_temperature_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &usb_pcwd_temperature_fops,
+};
+
+static struct notifier_block usb_pcwd_notifier = {
+ .notifier_call = usb_pcwd_notify_sys,
+};
+
+/**
+ * usb_pcwd_delete
+ */
+static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd)
+{
+ if (usb_pcwd->intr_urb != NULL)
+ usb_free_urb (usb_pcwd->intr_urb);
+ if (usb_pcwd->intr_buffer != NULL)
+ usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size,
+ usb_pcwd->intr_buffer, usb_pcwd->intr_dma);
+ kfree (usb_pcwd);
+}
+
+/**
+ * usb_pcwd_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ struct usb_pcwd_private *usb_pcwd = NULL;
+ int pipe, maxp;
+ int retval = -ENOMEM;
+ int got_fw_rev;
+ unsigned char fw_rev_major, fw_rev_minor;
+ char fw_ver_str[20];
+ unsigned char option_switches, dummy;
+
+ cards_found++;
+ if (cards_found > 1) {
+ printk(KERN_ERR PFX "This driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ /* get the active interface descriptor */
+ iface_desc = interface->cur_altsetting;
+
+ /* check out that we have a HID device */
+ if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) {
+ printk(KERN_ERR PFX "The device isn't a Human Interface Device\n");
+ return -ENODEV;
+ }
+
+ /* check out the endpoint: it has to be Interrupt & IN */
+ endpoint = &iface_desc->endpoint[0].desc;
+
+ if (!((endpoint->bEndpointAddress & USB_DIR_IN) &&
+ ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+ == USB_ENDPOINT_XFER_INT))) {
+ /* we didn't find a Interrupt endpoint with direction IN */
+ printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n");
+ return -ENODEV;
+ }
+
+ /* get a handle to the interrupt data pipe */
+ pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+ maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));
+
+ /* allocate memory for our device and initialize it */
+ usb_pcwd = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL);
+ if (usb_pcwd == NULL) {
+ printk(KERN_ERR PFX "Out of memory\n");
+ goto error;
+ }
+ memset (usb_pcwd, 0x00, sizeof (*usb_pcwd));
+
+ usb_pcwd_device = usb_pcwd;
+
+ init_MUTEX (&usb_pcwd->sem);
+ usb_pcwd->udev = udev;
+ usb_pcwd->interface = interface;
+ usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber;
+ usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8);
+
+ /* set up the memory buffer's */
+ if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, SLAB_ATOMIC, &usb_pcwd->intr_dma))) {
+ printk(KERN_ERR PFX "Out of memory\n");
+ goto error;
+ }
+
+ /* allocate the urb's */
+ usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usb_pcwd->intr_urb) {
+ printk(KERN_ERR PFX "Out of memory\n");
+ goto error;
+ }
+
+ /* initialise the intr urb's */
+ usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe,
+ usb_pcwd->intr_buffer, usb_pcwd->intr_size,
+ usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval);
+ usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma;
+ usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ /* register our interrupt URB with the USB system */
+ if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) {
+ printk(KERN_ERR PFX "Problem registering interrupt URB\n");
+ retval = -EIO; /* failure */
+ goto error;
+ }
+
+ /* The device exists and can be communicated with */
+ usb_pcwd->exists = 1;
+
+ /* disable card */
+ usb_pcwd_stop(usb_pcwd);
+
+ /* Get the Firmware Version */
+ got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor);
+ if (got_fw_rev) {
+ sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+ } else {
+ sprintf(fw_ver_str, "<card no answer>");
+ }
+
+ printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n",
+ fw_ver_str);
+
+ /* Get switch settings */
+ usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches);
+
+ printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+ option_switches,
+ ((option_switches & 0x10) ? "ON" : "OFF"),
+ ((option_switches & 0x08) ? "ON" : "OFF"));
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) {
+ usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT);
+ printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WATCHDOG_HEARTBEAT);
+ }
+
+ retval = register_reboot_notifier(&usb_pcwd_notifier);
+ if (retval != 0) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ retval);
+ goto error;
+ }
+
+ retval = misc_register(&usb_pcwd_temperature_miscdev);
+ if (retval != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, retval);
+ goto err_out_unregister_reboot;
+ }
+
+ retval = misc_register(&usb_pcwd_miscdev);
+ if (retval != 0) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, retval);
+ goto err_out_misc_deregister;
+ }
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata (interface, usb_pcwd);
+
+ printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+
+err_out_misc_deregister:
+ misc_deregister(&usb_pcwd_temperature_miscdev);
+err_out_unregister_reboot:
+ unregister_reboot_notifier(&usb_pcwd_notifier);
+error:
+ usb_pcwd_delete (usb_pcwd);
+ usb_pcwd_device = NULL;
+ return retval;
+}
+
+
+/**
+ * usb_pcwd_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ *
+ * This routine guarantees that the driver will not submit any more urbs
+ * by clearing dev->udev.
+ */
+static void usb_pcwd_disconnect(struct usb_interface *interface)
+{
+ struct usb_pcwd_private *usb_pcwd;
+
+ /* prevent races with open() */
+ down (&disconnect_sem);
+
+ usb_pcwd = usb_get_intfdata (interface);
+ usb_set_intfdata (interface, NULL);
+
+ down (&usb_pcwd->sem);
+
+ /* Stop the timer before we leave */
+ if (!nowayout)
+ usb_pcwd_stop(usb_pcwd);
+
+ /* We should now stop communicating with the USB PCWD device */
+ usb_pcwd->exists = 0;
+
+ /* Deregister */
+ misc_deregister(&usb_pcwd_miscdev);
+ misc_deregister(&usb_pcwd_temperature_miscdev);
+ unregister_reboot_notifier(&usb_pcwd_notifier);
+
+ up (&usb_pcwd->sem);
+
+ /* Delete the USB PCWD device */
+ usb_pcwd_delete(usb_pcwd);
+
+ cards_found--;
+
+ up (&disconnect_sem);
+
+ printk(KERN_INFO PFX "USB PC Watchdog disconnected\n");
+}
+
+
+
+/**
+ * usb_pcwd_init
+ */
+static int __init usb_pcwd_init(void)
+{
+ int result;
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&usb_pcwd_driver);
+ if (result) {
+ printk(KERN_ERR PFX "usb_register failed. Error number %d\n",
+ result);
+ return result;
+ }
+
+ printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n");
+ return 0;
+}
+
+
+/**
+ * usb_pcwd_exit
+ */
+static void __exit usb_pcwd_exit(void)
+{
+ /* deregister this driver with the USB subsystem */
+ usb_deregister(&usb_pcwd_driver);
+}
+
+
+module_init (usb_pcwd_init);
+module_exit (usb_pcwd_exit);
diff --git a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c
new file mode 100644
index 000000000000..1699d2c28ce5
--- /dev/null
+++ b/drivers/char/watchdog/s3c2410_wdt.c
@@ -0,0 +1,516 @@
+/* linux/drivers/char/watchdog/s3c2410_wdt.c
+ *
+ * Copyright (c) 2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410 Watchdog Timer Support
+ *
+ * Based on, softdog.c by Alan Cox,
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Changelog:
+ * 05-Oct-2004 BJD Added semaphore init to stop crashes on open
+ * Fixed tmr_count / wdt_count confusion
+ * Added configurable debug
+ *
+ * 11-Jan-2004 BJD Fixed divide-by-2 in timeout code
+ *
+ * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <asm/arch/map.h>
+#include <asm/hardware/clock.h>
+
+#undef S3C24XX_VA_WATCHDOG
+#define S3C24XX_VA_WATCHDOG (0)
+
+#include <asm/arch/regs-watchdog.h>
+
+#define PFX "s3c2410-wdt: "
+
+#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0)
+#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15)
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
+static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;
+static int soft_noboot = 0;
+static int debug = 0;
+
+module_param(tmr_margin, int, 0);
+module_param(tmr_atboot, int, 0);
+module_param(nowayout, int, 0);
+module_param(soft_noboot, int, 0);
+module_param(debug, int, 0);
+
+MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
+
+MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
+
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
+
+MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
+
+
+typedef enum close_state {
+ CLOSE_STATE_NOT,
+ CLOSE_STATE_ALLOW=0x4021
+} close_state_t;
+
+static DECLARE_MUTEX(open_lock);
+
+static struct resource *wdt_mem;
+static struct resource *wdt_irq;
+static struct clk *wdt_clock;
+static void __iomem *wdt_base;
+static unsigned int wdt_count;
+static close_state_t allow_close;
+
+/* watchdog control routines */
+
+#define DBG(msg...) do { \
+ if (debug) \
+ printk(KERN_INFO msg); \
+ } while(0)
+
+/* functions */
+
+static int s3c2410wdt_keepalive(void)
+{
+ writel(wdt_count, wdt_base + S3C2410_WTCNT);
+ return 0;
+}
+
+static int s3c2410wdt_stop(void)
+{
+ unsigned long wtcon;
+
+ wtcon = readl(wdt_base + S3C2410_WTCON);
+ wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
+ writel(wtcon, wdt_base + S3C2410_WTCON);
+
+ return 0;
+}
+
+static int s3c2410wdt_start(void)
+{
+ unsigned long wtcon;
+
+ s3c2410wdt_stop();
+
+ wtcon = readl(wdt_base + S3C2410_WTCON);
+ wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
+
+ if (soft_noboot) {
+ wtcon |= S3C2410_WTCON_INTEN;
+ wtcon &= ~S3C2410_WTCON_RSTEN;
+ } else {
+ wtcon &= ~S3C2410_WTCON_INTEN;
+ wtcon |= S3C2410_WTCON_RSTEN;
+ }
+
+ DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
+ __FUNCTION__, wdt_count, wtcon);
+
+ writel(wdt_count, wdt_base + S3C2410_WTDAT);
+ writel(wdt_count, wdt_base + S3C2410_WTCNT);
+ writel(wtcon, wdt_base + S3C2410_WTCON);
+
+ return 0;
+}
+
+static int s3c2410wdt_set_heartbeat(int timeout)
+{
+ unsigned int freq = clk_get_rate(wdt_clock);
+ unsigned int count;
+ unsigned int divisor = 1;
+ unsigned long wtcon;
+
+ if (timeout < 1)
+ return -EINVAL;
+
+ freq /= 128;
+ count = timeout * freq;
+
+ DBG("%s: count=%d, timeout=%d, freq=%d\n",
+ __FUNCTION__, count, timeout, freq);
+
+ /* if the count is bigger than the watchdog register,
+ then work out what we need to do (and if) we can
+ actually make this value
+ */
+
+ if (count >= 0x10000) {
+ for (divisor = 1; divisor <= 0x100; divisor++) {
+ if ((count / divisor) < 0x10000)
+ break;
+ }
+
+ if ((count / divisor) >= 0x10000) {
+ printk(KERN_ERR PFX "timeout %d too big\n", timeout);
+ return -EINVAL;
+ }
+ }
+
+ tmr_margin = timeout;
+
+ DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
+ __FUNCTION__, timeout, divisor, count, count/divisor);
+
+ count /= divisor;
+ wdt_count = count;
+
+ /* update the pre-scaler */
+ wtcon = readl(wdt_base + S3C2410_WTCON);
+ wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
+ wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
+
+ writel(count, wdt_base + S3C2410_WTDAT);
+ writel(wtcon, wdt_base + S3C2410_WTCON);
+
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int s3c2410wdt_open(struct inode *inode, struct file *file)
+{
+ if(down_trylock(&open_lock))
+ return -EBUSY;
+
+ if (nowayout) {
+ __module_get(THIS_MODULE);
+ } else {
+ allow_close = CLOSE_STATE_ALLOW;
+ }
+
+ /* start the timer */
+ s3c2410wdt_start();
+ return nonseekable_open(inode, file);
+}
+
+static int s3c2410wdt_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ * Lock it in if it's a module and we set nowayout
+ */
+ if (allow_close == CLOSE_STATE_ALLOW) {
+ s3c2410wdt_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ s3c2410wdt_keepalive();
+ }
+
+ allow_close = CLOSE_STATE_NOT;
+ up(&open_lock);
+ return 0;
+}
+
+static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if(len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ allow_close = CLOSE_STATE_NOT;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ allow_close = CLOSE_STATE_ALLOW;
+ }
+ }
+
+ s3c2410wdt_keepalive();
+ }
+ return len;
+}
+
+#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
+
+static struct watchdog_info s3c2410_wdt_ident = {
+ .options = OPTIONS,
+ .firmware_version = 0,
+ .identity = "S3C2410 Watchdog",
+};
+
+
+static int s3c2410wdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_margin;
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &s3c2410_wdt_ident,
+ sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ s3c2410wdt_keepalive();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+
+ if (s3c2410wdt_set_heartbeat(new_margin))
+ return -EINVAL;
+
+ s3c2410wdt_keepalive();
+ return put_user(tmr_margin, p);
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(tmr_margin, p);
+ }
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ s3c2410wdt_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/* kernel interface */
+
+static struct file_operations s3c2410wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = s3c2410wdt_write,
+ .ioctl = s3c2410wdt_ioctl,
+ .open = s3c2410wdt_open,
+ .release = s3c2410wdt_release,
+};
+
+static struct miscdevice s3c2410wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &s3c2410wdt_fops,
+};
+
+static struct notifier_block s3c2410wdt_notifier = {
+ .notifier_call = s3c2410wdt_notify_sys,
+};
+
+/* interrupt handler code */
+
+static irqreturn_t s3c2410wdt_irq(int irqno, void *param,
+ struct pt_regs *regs)
+{
+ printk(KERN_INFO PFX "Watchdog timer expired!\n");
+
+ s3c2410wdt_keepalive();
+ return IRQ_HANDLED;
+}
+/* device interface */
+
+static int s3c2410wdt_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct resource *res;
+ int started = 0;
+ int ret;
+ int size;
+
+ DBG("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
+
+ /* get the memory region for the watchdog timer */
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ printk(KERN_INFO PFX "failed to get memory region resouce\n");
+ return -ENOENT;
+ }
+
+ size = (res->end-res->start)+1;
+ wdt_mem = request_mem_region(res->start, size, pdev->name);
+ if (wdt_mem == NULL) {
+ printk(KERN_INFO PFX "failed to get memory region\n");
+ return -ENOENT;
+ }
+
+ wdt_base = ioremap(res->start, size);
+ if (wdt_base == 0) {
+ printk(KERN_INFO PFX "failed to ioremap() region\n");
+ return -EINVAL;
+ }
+
+ DBG("probe: mapped wdt_base=%p\n", wdt_base);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (res == NULL) {
+ printk(KERN_INFO PFX "failed to get irq resource\n");
+ return -ENOENT;
+ }
+
+ ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev);
+ if (ret != 0) {
+ printk(KERN_INFO PFX "failed to install irq (%d)\n", ret);
+ return ret;
+ }
+
+ wdt_clock = clk_get(dev, "watchdog");
+ if (wdt_clock == NULL) {
+ printk(KERN_INFO PFX "failed to find watchdog clock source\n");
+ return -ENOENT;
+ }
+
+ clk_use(wdt_clock);
+ clk_enable(wdt_clock);
+
+ /* see if we can actually set the requested timer margin, and if
+ * not, try the default value */
+
+ if (s3c2410wdt_set_heartbeat(tmr_margin)) {
+ started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
+
+ if (started == 0) {
+ printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n",
+ CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
+ } else {
+ printk(KERN_INFO PFX "default timer value is out of range, cannot start\n");
+ }
+ }
+
+ ret = register_reboot_notifier(&s3c2410wdt_notifier);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = misc_register(&s3c2410wdt_miscdev);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n",
+ WATCHDOG_MINOR, ret);
+ unregister_reboot_notifier(&s3c2410wdt_notifier);
+ return ret;
+ }
+
+ if (tmr_atboot && started == 0) {
+ printk(KERN_INFO PFX "Starting Watchdog Timer\n");
+ s3c2410wdt_start();
+ }
+
+ return 0;
+}
+
+static int s3c2410wdt_remove(struct device *dev)
+{
+ if (wdt_mem != NULL) {
+ release_resource(wdt_mem);
+ kfree(wdt_mem);
+ wdt_mem = NULL;
+ }
+
+ if (wdt_irq != NULL) {
+ free_irq(wdt_irq->start, dev);
+ wdt_irq = NULL;
+ }
+
+ if (wdt_clock != NULL) {
+ clk_disable(wdt_clock);
+ clk_unuse(wdt_clock);
+ clk_put(wdt_clock);
+ wdt_clock = NULL;
+ }
+
+ misc_deregister(&s3c2410wdt_miscdev);
+ return 0;
+}
+
+static struct device_driver s3c2410wdt_driver = {
+ .name = "s3c2410-wdt",
+ .bus = &platform_bus_type,
+ .probe = s3c2410wdt_probe,
+ .remove = s3c2410wdt_remove,
+};
+
+
+
+static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
+
+static int __init watchdog_init(void)
+{
+ printk(banner);
+ return driver_register(&s3c2410wdt_driver);
+}
+
+static void __exit watchdog_exit(void)
+{
+ driver_unregister(&s3c2410wdt_driver);
+ unregister_reboot_notifier(&s3c2410wdt_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sa1100_wdt.c b/drivers/char/watchdog/sa1100_wdt.c
new file mode 100644
index 000000000000..34e8f7b15e30
--- /dev/null
+++ b/drivers/char/watchdog/sa1100_wdt.c
@@ -0,0 +1,223 @@
+/*
+ * Watchdog driver for the SA11x0/PXA2xx
+ *
+ * (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ * Based on SoftDog driver by Alan Cox <alan@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Oleg Drokin nor iXcelerator.com admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * 27/11/2000 Initial release
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_ARCH_PXA
+#include <asm/arch/pxa-regs.h>
+#endif
+
+#include <asm/hardware.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+
+#define OSCR_FREQ CLOCK_TICK_RATE
+#define SA1100_CLOSE_MAGIC (0x5afc4453)
+
+static unsigned long sa1100wdt_users;
+static int expect_close;
+static int pre_margin;
+static int boot_status;
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+/*
+ * Allow only one person to hold it open
+ */
+static int sa1100dog_open(struct inode *inode, struct file *file)
+{
+ nonseekable_open(inode, file);
+ if (test_and_set_bit(1,&sa1100wdt_users))
+ return -EBUSY;
+
+ /* Activate SA1100 Watchdog timer */
+ OSMR3 = OSCR + pre_margin;
+ OSSR = OSSR_M3;
+ OWER = OWER_WME;
+ OIER |= OIER_E3;
+ return 0;
+}
+
+/*
+ * Shut off the timer.
+ * Lock it in if it's a module and we defined ...NOWAYOUT
+ * Oddly, the watchdog can only be enabled, but we can turn off
+ * the interrupt, which appears to prevent the watchdog timing out.
+ */
+static int sa1100dog_release(struct inode *inode, struct file *file)
+{
+ OSMR3 = OSCR + pre_margin;
+
+ if (expect_close == SA1100_CLOSE_MAGIC) {
+ OIER &= ~OIER_E3;
+ } else {
+ printk(KERN_CRIT "WATCHDOG: WDT device closed unexpectedly. WDT will not stop!\n");
+ }
+
+ clear_bit(1, &sa1100wdt_users);
+ expect_close = 0;
+
+ return 0;
+}
+
+static ssize_t sa1100dog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = SA1100_CLOSE_MAGIC;
+ }
+ }
+ /* Refresh OSMR3 timer. */
+ OSMR3 = OSCR + pre_margin;
+ }
+
+ return len;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+ WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "SA1100 Watchdog",
+};
+
+static int sa1100dog_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = -ENOIOCTLCMD;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(boot_status, (int *)arg);
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int *)arg);
+ if (ret)
+ break;
+
+ if (time <= 0 || time > 255) {
+ ret = -EINVAL;
+ break;
+ }
+
+ pre_margin = OSCR_FREQ * time;
+ OSMR3 = OSCR + pre_margin;
+ /*fall through*/
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(pre_margin / OSCR_FREQ, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ OSMR3 = OSCR + pre_margin;
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static struct file_operations sa1100dog_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sa1100dog_write,
+ .ioctl = sa1100dog_ioctl,
+ .open = sa1100dog_open,
+ .release = sa1100dog_release,
+};
+
+static struct miscdevice sa1100dog_miscdev =
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "SA1100/PXA2xx watchdog",
+ .fops = &sa1100dog_fops,
+};
+
+static int margin __initdata = 60; /* (secs) Default is 1 minute */
+
+static int __init sa1100dog_init(void)
+{
+ int ret;
+
+ /*
+ * Read the reset status, and save it for later. If
+ * we suspend, RCSR will be cleared, and the watchdog
+ * reset reason will be lost.
+ */
+ boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0;
+ pre_margin = OSCR_FREQ * margin;
+
+ ret = misc_register(&sa1100dog_miscdev);
+ if (ret == 0)
+ printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n",
+ margin);
+
+ return ret;
+}
+
+static void __exit sa1100dog_exit(void)
+{
+ misc_deregister(&sa1100dog_miscdev);
+}
+
+module_init(sa1100dog_init);
+module_exit(sa1100dog_exit);
+
+MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>");
+MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog");
+
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sbc60xxwdt.c b/drivers/char/watchdog/sbc60xxwdt.c
new file mode 100644
index 000000000000..d7de9880605a
--- /dev/null
+++ b/drivers/char/watchdog/sbc60xxwdt.c
@@ -0,0 +1,413 @@
+/*
+ * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
+ *
+ * Based on acquirewdt.c by Alan Cox.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The author does NOT admit liability nor provide warranty for
+ * any of this software. This material is provided "AS-IS" in
+ * the hope that it may be useful for others.
+ *
+ * (c) Copyright 2000 Jakob Oestergaard <jakob@unthought.net>
+ *
+ * 12/4 - 2000 [Initial revision]
+ * 25/4 - 2000 Added /dev/watchdog support
+ * 09/5 - 2001 [smj@oro.net] fixed fop_write to "return 1" on success
+ * 12/4 - 2002 [rob@osinvestor.com] eliminate fop_read
+ * fix possible wdt_is_open race
+ * add CONFIG_WATCHDOG_NOWAYOUT support
+ * remove lock_kernel/unlock_kernel pairs
+ * added KERN_* to printk's
+ * got rid of extraneous comments
+ * changed watchdog_info to correctly reflect what the driver offers
+ * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT,
+ * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
+ * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces
+ * use module_param
+ * made timeout (the emulated heartbeat) a module_param
+ * made the keepalive ping an internal subroutine
+ * made wdt_stop and wdt_start module params
+ * added extra printk's for startup problems
+ * added MODULE_AUTHOR and MODULE_DESCRIPTION info
+ *
+ *
+ * This WDT driver is different from the other Linux WDT
+ * drivers in the following ways:
+ * *) The driver will ping the watchdog by itself, because this
+ * particular WDT has a very short timeout (one second) and it
+ * would be insane to count on any userspace daemon always
+ * getting scheduled within that time frame.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "sbc60xxwdt"
+#define PFX OUR_NAME ": "
+
+/*
+ * You must set these - The driver cannot probe for the settings
+ */
+
+static int wdt_stop = 0x45;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
+
+/*
+ * The 60xx board can use watchdog timeout values from one second
+ * to several minutes. The default is one second, so if we reset
+ * the watchdog every ~250ms we should be safe.
+ */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ * If the daemon pulses us every 25 seconds, we can still afford
+ * a 5 second scheduling delay on the (high priority) daemon. That
+ * should be sufficient for a box under any load.
+ */
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+
+/*
+ * Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+ /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+ * we agree to ping the WDT
+ */
+ if(time_before(jiffies, next_heartbeat))
+ {
+ /* Ping the WDT by reading from wdt_start */
+ inb_p(wdt_start);
+ /* Re-set the timer interval */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* Start the timer */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+ printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer(&timer);
+ inb_p(wdt_stop);
+ printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if(count)
+ {
+ if (!nowayout)
+ {
+ size_t ofs;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ wdt_expect_close = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for(ofs = 0; ofs != count; ofs++)
+ {
+ char c;
+ if(get_user(c, buf+ofs))
+ return -EFAULT;
+ if(c == 'V')
+ wdt_expect_close = 42;
+ }
+ }
+
+ /* Well, anyhow someone wrote to us, we should return that favour */
+ wdt_keepalive();
+ }
+ return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+ nonseekable_open(inode, file);
+
+ /* Just in case we're already talking to someone... */
+ if(test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Good, fire up the show */
+ wdt_startup();
+ return 0;
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+ if(wdt_expect_close == 42)
+ wdt_turnoff();
+ else {
+ del_timer(&timer);
+ printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ clear_bit(0, &wdt_is_open);
+ wdt_expect_close = 0;
+ return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident=
+ {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "SBC60xx",
+ };
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if(get_user(new_options, p))
+ return -EFAULT;
+
+ if(new_options & WDIOS_DISABLECARD) {
+ wdt_turnoff();
+ retval = 0;
+ }
+
+ if(new_options & WDIOS_ENABLECARD) {
+ wdt_startup();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if(get_user(new_timeout, p))
+ return -EFAULT;
+
+ if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+ return -EINVAL;
+
+ timeout = new_timeout;
+ wdt_keepalive();
+ /* Fall through */
+ }
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ }
+}
+
+static struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = fop_write,
+ .open = fop_open,
+ .release = fop_close,
+ .ioctl = fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+/*
+ * Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT)
+ wdt_turnoff();
+ return NOTIFY_DONE;
+}
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+ .notifier_call = wdt_notify_sys,
+};
+
+static void __exit sbc60xxwdt_unload(void)
+{
+ wdt_turnoff();
+
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+
+ unregister_reboot_notifier(&wdt_notifier);
+ if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+ release_region(wdt_stop,1);
+ release_region(wdt_start,1);
+}
+
+static int __init sbc60xxwdt_init(void)
+{
+ int rc = -EBUSY;
+
+ if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+ {
+ timeout = WATCHDOG_TIMEOUT;
+ printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+ timeout);
+ }
+
+ if (!request_region(wdt_start, 1, "SBC 60XX WDT"))
+ {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_start);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ /* We cannot reserve 0x45 - the kernel already has! */
+ if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+ {
+ if (!request_region(wdt_stop, 1, "SBC 60XX WDT"))
+ {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_stop);
+ rc = -EIO;
+ goto err_out_region1;
+ }
+ }
+
+ init_timer(&timer);
+ timer.function = wdt_timer_ping;
+ timer.data = 0;
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc)
+ {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_region2;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc)
+ {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ rc);
+ goto err_out_miscdev;
+ }
+
+ printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return 0;
+
+err_out_miscdev:
+ misc_deregister(&wdt_miscdev);
+err_out_region2:
+ if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+ release_region(wdt_stop,1);
+err_out_region1:
+ release_region(wdt_start,1);
+err_out:
+ return rc;
+}
+
+module_init(sbc60xxwdt_init);
+module_exit(sbc60xxwdt_unload);
+
+MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
+MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sc1200wdt.c b/drivers/char/watchdog/sc1200wdt.c
new file mode 100644
index 000000000000..24401e84729e
--- /dev/null
+++ b/drivers/char/watchdog/sc1200wdt.c
@@ -0,0 +1,467 @@
+/*
+ * National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>,
+ * All Rights Reserved.
+ * Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The author(s) of this software shall not be held liable for damages
+ * of any nature resulting due to the use of this software. This
+ * software is provided AS-IS with no warranties.
+ *
+ * Changelog:
+ * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware.
+ * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik and Alan Cox.
+ * 20020222 Zwane Mwaikambo Added probing.
+ * 20020225 Zwane Mwaikambo Added ISAPNP support.
+ * 20020412 Rob Radez Broke out start/stop functions
+ * <rob@osinvestor.com> Return proper status instead of temperature warning
+ * Add WDIOC_GETBOOTSTATUS and WDIOC_SETOPTIONS ioctls
+ * Fix CONFIG_WATCHDOG_NOWAYOUT
+ * 20020530 Joel Becker Add Matt Domsch's nowayout module option
+ * 20030116 Adam Belay Updated to the latest pnp code
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define SC1200_MODULE_VER "build 20020303"
+#define SC1200_MODULE_NAME "sc1200wdt"
+#define PFX SC1200_MODULE_NAME ": "
+
+#define MAX_TIMEOUT 255 /* 255 minutes */
+#define PMIR (io) /* Power Management Index Register */
+#define PMDR (io+1) /* Power Management Data Register */
+
+/* Data Register indexes */
+#define FER1 0x00 /* Function enable register 1 */
+#define FER2 0x01 /* Function enable register 2 */
+#define PMC1 0x02 /* Power Management Ctrl 1 */
+#define PMC2 0x03 /* Power Management Ctrl 2 */
+#define PMC3 0x04 /* Power Management Ctrl 3 */
+#define WDTO 0x05 /* Watchdog timeout register */
+#define WDCF 0x06 /* Watchdog config register */
+#define WDST 0x07 /* Watchdog status register */
+
+/* WDCF bitfields - which devices assert WDO */
+#define KBC_IRQ 0x01 /* Keyboard Controller */
+#define MSE_IRQ 0x02 /* Mouse */
+#define UART1_IRQ 0x03 /* Serial0 */
+#define UART2_IRQ 0x04 /* Serial1 */
+/* 5 -7 are reserved */
+
+static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER;
+static int timeout = 1;
+static int io = -1;
+static int io_len = 2; /* for non plug and play */
+static struct semaphore open_sem;
+static char expect_close;
+static spinlock_t sc1200wdt_lock; /* io port access serialisation */
+
+#if defined CONFIG_PNP
+static int isapnp = 1;
+static struct pnp_dev *wdt_dev;
+
+module_param(isapnp, int, 0);
+MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled");
+#endif
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "io port");
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+
+
+/* Read from Data Register */
+static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data)
+{
+ spin_lock(&sc1200wdt_lock);
+ outb_p(index, PMIR);
+ *data = inb(PMDR);
+ spin_unlock(&sc1200wdt_lock);
+}
+
+
+/* Write to Data Register */
+static inline void sc1200wdt_write_data(unsigned char index, unsigned char data)
+{
+ spin_lock(&sc1200wdt_lock);
+ outb_p(index, PMIR);
+ outb(data, PMDR);
+ spin_unlock(&sc1200wdt_lock);
+}
+
+
+static void sc1200wdt_start(void)
+{
+ unsigned char reg;
+
+ sc1200wdt_read_data(WDCF, &reg);
+ /* assert WDO when any of the following interrupts are triggered too */
+ reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ);
+ sc1200wdt_write_data(WDCF, reg);
+ /* set the timeout and get the ball rolling */
+ sc1200wdt_write_data(WDTO, timeout);
+}
+
+
+static void sc1200wdt_stop(void)
+{
+ sc1200wdt_write_data(WDTO, 0);
+}
+
+
+/* This returns the status of the WDO signal, inactive high. */
+static inline int sc1200wdt_status(void)
+{
+ unsigned char ret;
+
+ sc1200wdt_read_data(WDST, &ret);
+ /* If the bit is inactive, the watchdog is enabled, so return
+ * KEEPALIVEPING which is a bit of a kludge because there's nothing
+ * else for enabled/disabled status
+ */
+ return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; /* bits 1 - 7 are undefined */
+}
+
+
+static int sc1200wdt_open(struct inode *inode, struct file *file)
+{
+ nonseekable_open(inode, file);
+
+ /* allow one at a time */
+ if (down_trylock(&open_sem))
+ return -EBUSY;
+
+ if (timeout > MAX_TIMEOUT)
+ timeout = MAX_TIMEOUT;
+
+ sc1200wdt_start();
+ printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout);
+
+ return 0;
+}
+
+
+static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int new_timeout;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "PC87307/PC97307",
+ };
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD; /* Keep Pavel Machek amused ;) */
+
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof ident))
+ return -EFAULT;
+ return 0;
+
+ case WDIOC_GETSTATUS:
+ return put_user(sc1200wdt_status(), p);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ sc1200wdt_write_data(WDTO, timeout);
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+
+ /* the API states this is given in secs */
+ new_timeout /= 60;
+ if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+ return -EINVAL;
+
+ timeout = new_timeout;
+ sc1200wdt_write_data(WDTO, timeout);
+ /* fall through and return the new timeout */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout * 60, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ sc1200wdt_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ sc1200wdt_start();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ }
+}
+
+
+static int sc1200wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ sc1200wdt_stop();
+ printk(KERN_INFO PFX "Watchdog disabled\n");
+ } else {
+ sc1200wdt_write_data(WDTO, timeout);
+ printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout);
+ }
+ up(&open_sem);
+ expect_close = 0;
+
+ return 0;
+}
+
+
+static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+ if (len) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ sc1200wdt_write_data(WDTO, timeout);
+ return len;
+ }
+
+ return 0;
+}
+
+
+static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT)
+ sc1200wdt_stop();
+
+ return NOTIFY_DONE;
+}
+
+
+static struct notifier_block sc1200wdt_notifier =
+{
+ .notifier_call = sc1200wdt_notify_sys,
+};
+
+static struct file_operations sc1200wdt_fops =
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sc1200wdt_write,
+ .ioctl = sc1200wdt_ioctl,
+ .open = sc1200wdt_open,
+ .release = sc1200wdt_release,
+};
+
+static struct miscdevice sc1200wdt_miscdev =
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sc1200wdt_fops,
+};
+
+
+static int __init sc1200wdt_probe(void)
+{
+ /* The probe works by reading the PMC3 register's default value of 0x0e
+ * there is one caveat, if the device disables the parallel port or any
+ * of the UARTs we won't be able to detect it.
+ * Nb. This could be done with accuracy by reading the SID registers, but
+ * we don't have access to those io regions.
+ */
+
+ unsigned char reg;
+
+ sc1200wdt_read_data(PMC3, &reg);
+ reg &= 0x0f; /* we don't want the UART busy bits */
+ return (reg == 0x0e) ? 0 : -ENODEV;
+}
+
+
+#if defined CONFIG_PNP
+
+static struct pnp_device_id scl200wdt_pnp_devices[] = {
+ /* National Semiconductor PC87307/PC97307 watchdog component */
+ {.id = "NSC0800", .driver_data = 0},
+ {.id = ""},
+};
+
+static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
+{
+ /* this driver only supports one card at a time */
+ if (wdt_dev || !isapnp)
+ return -EBUSY;
+
+ wdt_dev = dev;
+ io = pnp_port_start(wdt_dev, 0);
+ io_len = pnp_port_len(wdt_dev, 0);
+
+ if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+ printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
+ return -EBUSY;
+ }
+
+ printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len);
+ return 0;
+}
+
+static void scl200wdt_pnp_remove(struct pnp_dev * dev)
+{
+ if (wdt_dev){
+ release_region(io, io_len);
+ wdt_dev = NULL;
+ }
+}
+
+static struct pnp_driver scl200wdt_pnp_driver = {
+ .name = "scl200wdt",
+ .id_table = scl200wdt_pnp_devices,
+ .probe = scl200wdt_pnp_probe,
+ .remove = scl200wdt_pnp_remove,
+};
+
+#endif /* CONFIG_PNP */
+
+
+static int __init sc1200wdt_init(void)
+{
+ int ret;
+
+ printk(banner);
+
+ spin_lock_init(&sc1200wdt_lock);
+ sema_init(&open_sem, 1);
+
+#if defined CONFIG_PNP
+ if (isapnp) {
+ ret = pnp_register_driver(&scl200wdt_pnp_driver);
+ if (ret)
+ goto out_clean;
+ }
+#endif
+
+ if (io == -1) {
+ printk(KERN_ERR PFX "io parameter must be specified\n");
+ ret = -EINVAL;
+ goto out_clean;
+ }
+
+#if defined CONFIG_PNP
+ /* now that the user has specified an IO port and we haven't detected
+ * any devices, disable pnp support */
+ isapnp = 0;
+ pnp_unregister_driver(&scl200wdt_pnp_driver);
+#endif
+
+ if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+ printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
+ ret = -EBUSY;
+ goto out_clean;
+ }
+
+ ret = sc1200wdt_probe();
+ if (ret)
+ goto out_io;
+
+ ret = register_reboot_notifier(&sc1200wdt_notifier);
+ if (ret) {
+ printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
+ goto out_io;
+ }
+
+ ret = misc_register(&sc1200wdt_miscdev);
+ if (ret) {
+ printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
+ goto out_rbt;
+ }
+
+ /* ret = 0 */
+
+out_clean:
+ return ret;
+
+out_rbt:
+ unregister_reboot_notifier(&sc1200wdt_notifier);
+
+out_io:
+ release_region(io, io_len);
+
+ goto out_clean;
+}
+
+
+static void __exit sc1200wdt_exit(void)
+{
+ misc_deregister(&sc1200wdt_miscdev);
+ unregister_reboot_notifier(&sc1200wdt_notifier);
+
+#if defined CONFIG_PNP
+ if(isapnp)
+ pnp_unregister_driver(&scl200wdt_pnp_driver);
+ else
+#endif
+ release_region(io, io_len);
+}
+
+module_init(sc1200wdt_init);
+module_exit(sc1200wdt_exit);
+
+MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
+MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sc520_wdt.c b/drivers/char/watchdog/sc520_wdt.c
new file mode 100644
index 000000000000..f6d143e1900d
--- /dev/null
+++ b/drivers/char/watchdog/sc520_wdt.c
@@ -0,0 +1,447 @@
+/*
+ * AMD Elan SC520 processor Watchdog Timer driver
+ *
+ * Based on acquirewdt.c by Alan Cox,
+ * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The authors do NOT admit liability nor provide warranty for
+ * any of this software. This material is provided "AS-IS" in
+ * the hope that it may be useful for others.
+ *
+ * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net>
+ * 9/27 - 2001 [Initial release]
+ *
+ * Additional fixes Alan Cox
+ * - Fixed formatting
+ * - Removed debug printks
+ * - Fixed SMP built kernel deadlock
+ * - Switched to private locks not lock_kernel
+ * - Used ioremap/writew/readw
+ * - Added NOWAYOUT support
+ * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com>
+ * - Change comments
+ * - Eliminate fop_llseek
+ * - Change CONFIG_WATCHDOG_NOWAYOUT semantics
+ * - Add KERN_* tags to printks
+ * - fix possible wdt_is_open race
+ * - Report proper capabilities in watchdog_info
+ * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT,
+ * GETTIMEOUT, SETOPTIONS} ioctls
+ * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be>
+ * - cleanup of trailing spaces
+ * - added extra printk's for startup problems
+ * - use module_param
+ * - made timeout (the emulated heartbeat) a module_param
+ * - made the keepalive ping an internal subroutine
+ * 3/27 - 2004 Changes by Sean Young <sean@mess.org>
+ * - set MMCR_BASE to 0xfffef000
+ * - CBAR does not need to be read
+ * - removed debugging printks
+ *
+ * This WDT driver is different from most other Linux WDT
+ * drivers in that the driver will ping the watchdog by itself,
+ * because this particular WDT has a very short timeout (1.6
+ * seconds) and it would be insane to count on any userspace
+ * daemon always getting scheduled within that time frame.
+ *
+ * This driver uses memory mapped IO, and spinlock.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "sc520_wdt"
+#define PFX OUR_NAME ": "
+
+/*
+ * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
+ *
+ * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s
+ * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s
+ *
+ * We will program the SC520 watchdog for a timeout of 2.01s.
+ * If we reset the watchdog every ~250ms we should be safe.
+ */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * AMD Elan SC520 - Watchdog Timer Registers
+ */
+#define MMCR_BASE 0xfffef000 /* The default base address */
+#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */
+
+/* WDT Control Register bit definitions */
+#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */
+#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */
+#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */
+#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */
+#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */
+#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */
+
+static __u16 __iomem *wdtmrctl;
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static spinlock_t wdt_spinlock;
+
+/*
+ * Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+ /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+ * we agree to ping the WDT
+ */
+ if(time_before(jiffies, next_heartbeat))
+ {
+ /* Ping the WDT */
+ spin_lock(&wdt_spinlock);
+ writew(0xAAAA, wdtmrctl);
+ writew(0x5555, wdtmrctl);
+ spin_unlock(&wdt_spinlock);
+
+ /* Re-set the timer interval */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_config(int writeval)
+{
+ __u16 dummy;
+ unsigned long flags;
+
+ /* buy some time (ping) */
+ spin_lock_irqsave(&wdt_spinlock, flags);
+ dummy=readw(wdtmrctl); /* ensure write synchronization */
+ writew(0xAAAA, wdtmrctl);
+ writew(0x5555, wdtmrctl);
+ /* unlock WDT = make WDT configuration register writable one time */
+ writew(0x3333, wdtmrctl);
+ writew(0xCCCC, wdtmrctl);
+ /* write WDT configuration register */
+ writew(writeval, wdtmrctl);
+ spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static int wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* Start the timer */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+
+ /* Start the watchdog */
+ wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04);
+
+ printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+ return 0;
+}
+
+static int wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer(&timer);
+
+ /* Stop the watchdog */
+ wdt_config(0);
+
+ printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+ return 0;
+}
+
+static int wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+ return 0;
+}
+
+static int wdt_set_heartbeat(int t)
+{
+ if ((t < 1) || (t > 3600)) /* arbitrary upper limit */
+ return -EINVAL;
+
+ timeout = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if(count) {
+ if (!nowayout) {
+ size_t ofs;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ wdt_expect_close = 0;
+
+ /* now scan */
+ for(ofs = 0; ofs != count; ofs++) {
+ char c;
+ if (get_user(c, buf + ofs))
+ return -EFAULT;
+ if(c == 'V')
+ wdt_expect_close = 42;
+ }
+ }
+
+ /* Well, anyhow someone wrote to us, we should return that favour */
+ wdt_keepalive();
+ }
+ return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+ nonseekable_open(inode, file);
+
+ /* Just in case we're already talking to someone... */
+ if(test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ /* Good, fire up the show */
+ wdt_startup();
+ return 0;
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+ if(wdt_expect_close == 42) {
+ wdt_turnoff();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ wdt_keepalive();
+ }
+ clear_bit(0, &wdt_is_open);
+ wdt_expect_close = 0;
+ return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "SC520",
+ };
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if(get_user(new_options, p))
+ return -EFAULT;
+
+ if(new_options & WDIOS_DISABLECARD) {
+ wdt_turnoff();
+ retval = 0;
+ }
+
+ if(new_options & WDIOS_ENABLECARD) {
+ wdt_startup();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if(get_user(new_timeout, p))
+ return -EFAULT;
+
+ if(wdt_set_heartbeat(new_timeout))
+ return -EINVAL;
+
+ wdt_keepalive();
+ /* Fall through */
+ }
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ }
+}
+
+static struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = fop_write,
+ .open = fop_open,
+ .release = fop_close,
+ .ioctl = fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+/*
+ * Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT)
+ wdt_turnoff();
+ return NOTIFY_DONE;
+}
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+static void __exit sc520_wdt_unload(void)
+{
+ if (!nowayout)
+ wdt_turnoff();
+
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+ iounmap(wdtmrctl);
+}
+
+static int __init sc520_wdt_init(void)
+{
+ int rc = -EBUSY;
+
+ spin_lock_init(&wdt_spinlock);
+
+ init_timer(&timer);
+ timer.function = wdt_timer_ping;
+ timer.data = 0;
+
+ /* Check that the timeout value is within it's range ; if not reset to the default */
+ if (wdt_set_heartbeat(timeout)) {
+ wdt_set_heartbeat(WATCHDOG_TIMEOUT);
+ printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n",
+ WATCHDOG_TIMEOUT);
+ }
+
+ wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2);
+ if (!wdtmrctl) {
+ printk(KERN_ERR PFX "Unable to remap memory\n");
+ rc = -ENOMEM;
+ goto err_out_region2;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ rc);
+ goto err_out_ioremap;
+ }
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, rc);
+ goto err_out_notifier;
+ }
+
+ printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout,nowayout);
+
+ return 0;
+
+err_out_notifier:
+ unregister_reboot_notifier(&wdt_notifier);
+err_out_ioremap:
+ iounmap(wdtmrctl);
+err_out_region2:
+ return rc;
+}
+
+module_init(sc520_wdt_init);
+module_exit(sc520_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/scx200_wdt.c b/drivers/char/watchdog/scx200_wdt.c
new file mode 100644
index 000000000000..b569670e4ed5
--- /dev/null
+++ b/drivers/char/watchdog/scx200_wdt.c
@@ -0,0 +1,274 @@
+/* drivers/char/watchdog/scx200_wdt.c
+
+ National Semiconductor SCx200 Watchdog support
+
+ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
+
+ Some code taken from:
+ National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The author(s) of this software shall not be held liable for damages
+ of any nature resulting due to the use of this software. This
+ software is provided AS-IS with no warranties. */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/scx200.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define NAME "scx200_wdt"
+
+MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
+MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+#define CONFIG_WATCHDOG_NOWAYOUT 0
+#endif
+
+static int margin = 60; /* in seconds */
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
+
+static int nowayout = CONFIG_WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static u16 wdto_restart;
+static struct semaphore open_semaphore;
+static char expect_close;
+
+/* Bits of the WDCNFG register */
+#define W_ENABLE 0x00fa /* Enable watchdog */
+#define W_DISABLE 0x0000 /* Disable watchdog */
+
+/* The scaling factor for the timer, this depends on the value of W_ENABLE */
+#define W_SCALE (32768/1024)
+
+static void scx200_wdt_ping(void)
+{
+ outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
+}
+
+static void scx200_wdt_update_margin(void)
+{
+ printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
+ wdto_restart = margin * W_SCALE;
+}
+
+static void scx200_wdt_enable(void)
+{
+ printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n",
+ wdto_restart);
+
+ outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+ outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+ outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+
+ scx200_wdt_ping();
+}
+
+static void scx200_wdt_disable(void)
+{
+ printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
+
+ outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+ outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+ outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+}
+
+static int scx200_wdt_open(struct inode *inode, struct file *file)
+{
+ /* only allow one at a time */
+ if (down_trylock(&open_semaphore))
+ return -EBUSY;
+ scx200_wdt_enable();
+
+ return nonseekable_open(inode, file);
+}
+
+static int scx200_wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close != 42) {
+ printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
+ } else if (!nowayout) {
+ scx200_wdt_disable();
+ }
+ expect_close = 0;
+ up(&open_semaphore);
+
+ return 0;
+}
+
+static int scx200_wdt_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_HALT || code == SYS_POWER_OFF)
+ if (!nowayout)
+ scx200_wdt_disable();
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block scx200_wdt_notifier =
+{
+ .notifier_call = scx200_wdt_notify_sys,
+};
+
+static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ /* check for a magic close character */
+ if (len)
+ {
+ size_t i;
+
+ scx200_wdt_ping();
+
+ expect_close = 0;
+ for (i = 0; i < len; ++i) {
+ char c;
+ if (get_user(c, data+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+
+ return len;
+ }
+
+ return 0;
+}
+
+static int scx200_wdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .identity = "NatSemi SCx200 Watchdog",
+ .firmware_version = 1,
+ .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING),
+ };
+ int new_margin;
+
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ if(copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ return 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ if (put_user(0, p))
+ return -EFAULT;
+ return 0;
+ case WDIOC_KEEPALIVE:
+ scx200_wdt_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+ if (new_margin < 1)
+ return -EINVAL;
+ margin = new_margin;
+ scx200_wdt_update_margin();
+ scx200_wdt_ping();
+ case WDIOC_GETTIMEOUT:
+ if (put_user(margin, p))
+ return -EFAULT;
+ return 0;
+ }
+}
+
+static struct file_operations scx200_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = scx200_wdt_write,
+ .ioctl = scx200_wdt_ioctl,
+ .open = scx200_wdt_open,
+ .release = scx200_wdt_release,
+};
+
+static struct miscdevice scx200_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = NAME,
+ .fops = &scx200_wdt_fops,
+};
+
+static int __init scx200_wdt_init(void)
+{
+ int r;
+
+ printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
+
+ /* check that we have found the configuration block */
+ if (!scx200_cb_present())
+ return -ENODEV;
+
+ if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE,
+ "NatSemi SCx200 Watchdog")) {
+ printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
+ return -EBUSY;
+ }
+
+ scx200_wdt_update_margin();
+ scx200_wdt_disable();
+
+ sema_init(&open_semaphore, 1);
+
+ r = misc_register(&scx200_wdt_miscdev);
+ if (r) {
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+ return r;
+ }
+
+ r = register_reboot_notifier(&scx200_wdt_notifier);
+ if (r) {
+ printk(KERN_ERR NAME ": unable to register reboot notifier");
+ misc_deregister(&scx200_wdt_miscdev);
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+ return r;
+ }
+
+ return 0;
+}
+
+static void __exit scx200_wdt_cleanup(void)
+{
+ unregister_reboot_notifier(&scx200_wdt_notifier);
+ misc_deregister(&scx200_wdt_miscdev);
+ release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+ SCx200_WDT_SIZE);
+}
+
+module_init(scx200_wdt_init);
+module_exit(scx200_wdt_cleanup);
+
+/*
+ Local variables:
+ compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
+ c-basic-offset: 8
+ End:
+*/
diff --git a/drivers/char/watchdog/shwdt.c b/drivers/char/watchdog/shwdt.c
new file mode 100644
index 000000000000..3bc9272a474c
--- /dev/null
+++ b/drivers/char/watchdog/shwdt.c
@@ -0,0 +1,452 @@
+/*
+ * drivers/char/watchdog/shwdt.c
+ *
+ * Watchdog driver for integrated watchdog in the SuperH processors.
+ *
+ * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * 19-Apr-2002 Rob Radez <rob@osinvestor.com>
+ * Added expect close support, made emulated timeout runtime changeable
+ * general cleanups, add some ioctls
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/ioport.h>
+#include <linux/fs.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/watchdog.h>
+
+#define PFX "shwdt: "
+
+/*
+ * Default clock division ratio is 5.25 msecs. For an additional table of
+ * values, consult the asm-sh/watchdog.h. Overload this at module load
+ * time.
+ *
+ * In order for this to work reliably we need to have HZ set to 1000 or
+ * something quite higher than 100 (or we need a proper high-res timer
+ * implementation that will deal with this properly), otherwise the 10ms
+ * resolution of a jiffy is enough to trigger the overflow. For things like
+ * the SH-4 and SH-5, this isn't necessarily that big of a problem, though
+ * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely
+ * necssary.
+ *
+ * As a result of this timing problem, the only modes that are particularly
+ * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms
+ * overflow periods respectively.
+ *
+ * Also, since we can't really expect userspace to be responsive enough
+ * before the overflow happens, we maintain two seperate timers .. One in
+ * the kernel for clearing out WOVF every 2ms or so (again, this depends on
+ * HZ == 1000), and another for monitoring userspace writes to the WDT device.
+ *
+ * As such, we currently use a configurable heartbeat interval which defaults
+ * to 30s. In this case, the userspace daemon is only responsible for periodic
+ * writes to the device before the next heartbeat is scheduled. If the daemon
+ * misses its deadline, the kernel timer will allow the WDT to overflow.
+ */
+static int clock_division_ratio = WTCSR_CKS_4096;
+
+#define next_ping_period(cks) msecs_to_jiffies(cks - 4)
+
+static unsigned long shwdt_is_open;
+static struct watchdog_info sh_wdt_info;
+static char shwdt_expect_close;
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+
+#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+/**
+ * sh_wdt_start - Start the Watchdog
+ *
+ * Starts the watchdog.
+ */
+static void sh_wdt_start(void)
+{
+ __u8 csr;
+
+ next_heartbeat = jiffies + (heartbeat * HZ);
+ mod_timer(&timer, next_ping_period(clock_division_ratio));
+
+ csr = sh_wdt_read_csr();
+ csr |= WTCSR_WT | clock_division_ratio;
+ sh_wdt_write_csr(csr);
+
+ sh_wdt_write_cnt(0);
+
+ /*
+ * These processors have a bit of an inconsistent initialization
+ * process.. starting with SH-3, RSTS was moved to WTCSR, and the
+ * RSTCSR register was removed.
+ *
+ * On the SH-2 however, in addition with bits being in different
+ * locations, we must deal with RSTCSR outright..
+ */
+ csr = sh_wdt_read_csr();
+ csr |= WTCSR_TME;
+ csr &= ~WTCSR_RSTS;
+ sh_wdt_write_csr(csr);
+
+#ifdef CONFIG_CPU_SH2
+ /*
+ * Whoever came up with the RSTCSR semantics must've been smoking
+ * some of the good stuff, since in addition to the WTCSR/WTCNT write
+ * brain-damage, it's managed to fuck things up one step further..
+ *
+ * If we need to clear the WOVF bit, the upper byte has to be 0xa5..
+ * but if we want to touch RSTE or RSTS, the upper byte has to be
+ * 0x5a..
+ */
+ csr = sh_wdt_read_rstcsr();
+ csr &= ~RSTCSR_RSTS;
+ sh_wdt_write_rstcsr(csr);
+#endif
+}
+
+/**
+ * sh_wdt_stop - Stop the Watchdog
+ *
+ * Stops the watchdog.
+ */
+static void sh_wdt_stop(void)
+{
+ __u8 csr;
+
+ del_timer(&timer);
+
+ csr = sh_wdt_read_csr();
+ csr &= ~WTCSR_TME;
+ sh_wdt_write_csr(csr);
+}
+
+/**
+ * sh_wdt_keepalive - Keep the Userspace Watchdog Alive
+ *
+ * The Userspace watchdog got a KeepAlive: schedule the next heartbeat.
+ */
+static void sh_wdt_keepalive(void)
+{
+ next_heartbeat = jiffies + (heartbeat * HZ);
+}
+
+/**
+ * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat
+ *
+ * Set the Userspace Watchdog heartbeat
+ */
+static int sh_wdt_set_heartbeat(int t)
+{
+ if ((t < 1) || (t > 3600)) /* arbitrary upper limit */
+ return -EINVAL;
+
+ heartbeat = t;
+ return 0;
+}
+
+/**
+ * sh_wdt_ping - Ping the Watchdog
+ *
+ * @data: Unused
+ *
+ * Clears overflow bit, resets timer counter.
+ */
+static void sh_wdt_ping(unsigned long data)
+{
+ if (time_before(jiffies, next_heartbeat)) {
+ __u8 csr;
+
+ csr = sh_wdt_read_csr();
+ csr &= ~WTCSR_IOVF;
+ sh_wdt_write_csr(csr);
+
+ sh_wdt_write_cnt(0);
+
+ mod_timer(&timer, next_ping_period(clock_division_ratio));
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+/**
+ * sh_wdt_open - Open the Device
+ *
+ * @inode: inode of device
+ * @file: file handle of device
+ *
+ * Watchdog device is opened and started.
+ */
+static int sh_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &shwdt_is_open))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ sh_wdt_start();
+
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * sh_wdt_close - Close the Device
+ *
+ * @inode: inode of device
+ * @file: file handle of device
+ *
+ * Watchdog device is closed and stopped.
+ */
+static int sh_wdt_close(struct inode *inode, struct file *file)
+{
+ if (shwdt_expect_close == 42) {
+ sh_wdt_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ sh_wdt_keepalive();
+ }
+
+ clear_bit(0, &shwdt_is_open);
+ shwdt_expect_close = 0;
+
+ return 0;
+}
+
+/**
+ * sh_wdt_write - Write to Device
+ *
+ * @file: file handle of device
+ * @buf: buffer to write
+ * @count: length of buffer
+ * @ppos: offset
+ *
+ * Pings the watchdog on write.
+ */
+static ssize_t sh_wdt_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ shwdt_expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ shwdt_expect_close = 42;
+ }
+ }
+ sh_wdt_keepalive();
+ }
+
+ return count;
+}
+
+/**
+ * sh_wdt_ioctl - Query Device
+ *
+ * @inode: inode of device
+ * @file: file handle of device
+ * @cmd: watchdog command
+ * @arg: argument
+ *
+ * Query basic information from the device or ping it, as outlined by the
+ * watchdog API.
+ */
+static int sh_wdt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int new_heartbeat;
+ int options, retval = -EINVAL;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user((struct watchdog_info *)arg,
+ &sh_wdt_info,
+ sizeof(sh_wdt_info)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, (int *)arg);
+ case WDIOC_KEEPALIVE:
+ sh_wdt_keepalive();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, (int *)arg))
+ return -EFAULT;
+
+ if (sh_wdt_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ sh_wdt_keepalive();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, (int *)arg);
+ case WDIOC_SETOPTIONS:
+ if (get_user(options, (int *)arg))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ sh_wdt_stop();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ sh_wdt_start();
+ retval = 0;
+ }
+
+ return retval;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+/**
+ * sh_wdt_notify_sys - Notifier Handler
+ *
+ * @this: notifier block
+ * @code: notifier event
+ * @unused: unused
+ *
+ * Handles specific events, such as turning off the watchdog during a
+ * shutdown event.
+ */
+static int sh_wdt_notify_sys(struct notifier_block *this,
+ unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ sh_wdt_stop();
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct file_operations sh_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = sh_wdt_write,
+ .ioctl = sh_wdt_ioctl,
+ .open = sh_wdt_open,
+ .release = sh_wdt_close,
+};
+
+static struct watchdog_info sh_wdt_info = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "SH WDT",
+};
+
+static struct notifier_block sh_wdt_notifier = {
+ .notifier_call = sh_wdt_notify_sys,
+};
+
+static struct miscdevice sh_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &sh_wdt_fops,
+};
+
+/**
+ * sh_wdt_init - Initialize module
+ *
+ * Registers the device and notifier handler. Actual device
+ * initialization is handled by sh_wdt_open().
+ */
+static int __init sh_wdt_init(void)
+{
+ int rc;
+
+ if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) {
+ clock_division_ratio = WTCSR_CKS_4096;
+ printk(KERN_INFO PFX "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n",
+ clock_division_ratio);
+ }
+
+ if (sh_wdt_set_heartbeat(heartbeat))
+ {
+ heartbeat = WATCHDOG_HEARTBEAT;
+ printk(KERN_INFO PFX "heartbeat value must be 1<=x<=3600, using %d\n",
+ heartbeat);
+ }
+
+ init_timer(&timer);
+ timer.function = sh_wdt_ping;
+ timer.data = 0;
+
+ rc = register_reboot_notifier(&sh_wdt_notifier);
+ if (rc) {
+ printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", rc);
+ return rc;
+ }
+
+ rc = misc_register(&sh_wdt_miscdev);
+ if (rc) {
+ printk(KERN_ERR PFX "Can't register miscdev on minor=%d (err=%d)\n",
+ sh_wdt_miscdev.minor, rc);
+ unregister_reboot_notifier(&sh_wdt_notifier);
+ return rc;
+ }
+
+ printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+
+ return 0;
+}
+
+/**
+ * sh_wdt_exit - Deinitialize module
+ *
+ * Unregisters the device and notifier handler. Actual device
+ * deinitialization is handled by sh_wdt_close().
+ */
+static void __exit sh_wdt_exit(void)
+{
+ misc_deregister(&sh_wdt_miscdev);
+ unregister_reboot_notifier(&sh_wdt_notifier);
+}
+
+MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
+MODULE_DESCRIPTION("SuperH watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(clock_division_ratio, int, 0);
+MODULE_PARM_DESC(clock_division_ratio, "Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). Defaults to 0x7.");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+module_init(sh_wdt_init);
+module_exit(sh_wdt_exit);
+
diff --git a/drivers/char/watchdog/softdog.c b/drivers/char/watchdog/softdog.c
new file mode 100644
index 000000000000..117903498a01
--- /dev/null
+++ b/drivers/char/watchdog/softdog.c
@@ -0,0 +1,309 @@
+/*
+ * SoftDog 0.07: A Software Watchdog Device
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Software only watchdog driver. Unlike its big brother the WDT501P
+ * driver this won't always recover a failed machine.
+ *
+ * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> :
+ * Modularised.
+ * Added soft_margin; use upon insmod to change the timer delay.
+ * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate
+ * minors.
+ *
+ * 19980911 Alan Cox
+ * Made SMP safe for 2.3.x
+ *
+ * 20011127 Joel Becker (jlbec@evilplan.org>
+ * Added soft_noboot; Allows testing the softdog trigger without
+ * requiring a recompile.
+ * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT.
+ *
+ * 20020530 Joel Becker <joel.becker@oracle.com>
+ * Added Matt Domsch's nowayout module option.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#define PFX "SoftDog: "
+
+#define TIMER_MARGIN 60 /* Default is 60 seconds */
+static int soft_margin = TIMER_MARGIN; /* in seconds */
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#ifdef ONLY_TESTING
+static int soft_noboot = 1;
+#else
+static int soft_noboot = 0;
+#endif /* ONLY_TESTING */
+
+module_param(soft_noboot, int, 0);
+MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
+
+/*
+ * Our timer
+ */
+
+static void watchdog_fire(unsigned long);
+
+static struct timer_list watchdog_ticktock =
+ TIMER_INITIALIZER(watchdog_fire, 0, 0);
+static unsigned long timer_alive;
+static char expect_close;
+
+
+/*
+ * If the timer expires..
+ */
+
+static void watchdog_fire(unsigned long data)
+{
+ if (soft_noboot)
+ printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n");
+ else
+ {
+ printk(KERN_CRIT PFX "Initiating system reboot.\n");
+ machine_restart(NULL);
+ printk(KERN_CRIT PFX "Reboot didn't ?????\n");
+ }
+}
+
+/*
+ * Softdog operations
+ */
+
+static int softdog_keepalive(void)
+{
+ mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ));
+ return 0;
+}
+
+static int softdog_stop(void)
+{
+ del_timer(&watchdog_ticktock);
+ return 0;
+}
+
+static int softdog_set_heartbeat(int t)
+{
+ if ((t < 0x0001) || (t > 0xFFFF))
+ return -EINVAL;
+
+ soft_margin = t;
+ return 0;
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int softdog_open(struct inode *inode, struct file *file)
+{
+ if(test_and_set_bit(0, &timer_alive))
+ return -EBUSY;
+ if (nowayout)
+ __module_get(THIS_MODULE);
+ /*
+ * Activate timer
+ */
+ softdog_keepalive();
+ return nonseekable_open(inode, file);
+}
+
+static int softdog_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ * Lock it in if it's a module and we set nowayout
+ */
+ if (expect_close == 42) {
+ softdog_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ softdog_keepalive();
+ }
+ clear_bit(0, &timer_alive);
+ expect_close = 0;
+ return 0;
+}
+
+static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if(len) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ softdog_keepalive();
+ }
+ return len;
+}
+
+static int softdog_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_margin;
+ static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+ .firmware_version = 0,
+ .identity = "Software Watchdog",
+ };
+ switch (cmd) {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ softdog_keepalive();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_margin, p))
+ return -EFAULT;
+ if (softdog_set_heartbeat(new_margin))
+ return -EINVAL;
+ softdog_keepalive();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ return put_user(soft_margin, p);
+ }
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int softdog_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the WDT off */
+ softdog_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations softdog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = softdog_write,
+ .ioctl = softdog_ioctl,
+ .open = softdog_open,
+ .release = softdog_release,
+};
+
+static struct miscdevice softdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &softdog_fops,
+};
+
+static struct notifier_block softdog_notifier = {
+ .notifier_call = softdog_notify_sys,
+};
+
+static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n";
+
+static int __init watchdog_init(void)
+{
+ int ret;
+
+ /* Check that the soft_margin value is within it's range ; if not reset to the default */
+ if (softdog_set_heartbeat(soft_margin)) {
+ softdog_set_heartbeat(TIMER_MARGIN);
+ printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n",
+ TIMER_MARGIN);
+ }
+
+ ret = register_reboot_notifier(&softdog_notifier);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ return ret;
+ }
+
+ ret = misc_register(&softdog_miscdev);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ unregister_reboot_notifier(&softdog_notifier);
+ return ret;
+ }
+
+ printk(banner, soft_noboot, soft_margin, nowayout);
+
+ return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+ misc_deregister(&softdog_miscdev);
+ unregister_reboot_notifier(&softdog_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Software Watchdog Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/w83627hf_wdt.c b/drivers/char/watchdog/w83627hf_wdt.c
new file mode 100644
index 000000000000..813c97038f84
--- /dev/null
+++ b/drivers/char/watchdog/w83627hf_wdt.c
@@ -0,0 +1,362 @@
+/*
+ * w83627hf WDT driver
+ *
+ * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com>
+ *
+ * Based on advantechwdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "w83627hf WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */
+
+static unsigned long wdt_is_open;
+static char expect_close;
+
+/* You must set this - there is no sane way to probe for this board. */
+static int wdt_io = 0x2E;
+module_param(wdt_io, int, 0);
+MODULE_PARM_DESC(wdt_io, "w83627hf WDT io port (default 0x2E)");
+
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Kernel methods.
+ */
+
+#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */
+#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */
+#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */
+
+static void
+w83627hf_select_wd_register(void)
+{
+ outb_p(0x87, WDT_EFER); /* Enter extended function mode */
+ outb_p(0x87, WDT_EFER); /* Again according to manual */
+
+ outb_p(0x07, WDT_EFER); /* point to logical device number reg */
+ outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */
+ outb_p(0x30, WDT_EFER); /* select CR30 */
+ outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */
+}
+
+static void
+w83627hf_unselect_wd_register(void)
+{
+ outb_p(0xAA, WDT_EFER); /* Leave extended function mode */
+}
+
+/* tyan motherboards seem to set F5 to 0x4C ?
+ * So explicitly init to appropriate value. */
+static void
+w83627hf_init(void)
+{
+ unsigned char t;
+
+ w83627hf_select_wd_register();
+
+ outb_p(0xF5, WDT_EFER); /* Select CRF5 */
+ t=inb_p(WDT_EFDR); /* read CRF5 */
+ t&=~0x0C; /* set second mode & disable keyboard turning off watchdog */
+ outb_p(t, WDT_EFDR); /* Write back to CRF5 */
+
+ w83627hf_unselect_wd_register();
+}
+
+static void
+wdt_ctrl(int timeout)
+{
+ w83627hf_select_wd_register();
+
+ outb_p(0xF6, WDT_EFER); /* Select CRF6 */
+ outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */
+
+ w83627hf_unselect_wd_register();
+}
+
+static int
+wdt_ping(void)
+{
+ wdt_ctrl(timeout);
+ return 0;
+}
+
+static int
+wdt_disable(void)
+{
+ wdt_ctrl(0);
+ return 0;
+}
+
+static int
+wdt_set_heartbeat(int t)
+{
+ if ((t < 1) || (t > 63))
+ return -EINVAL;
+
+ timeout = t;
+ return 0;
+}
+
+static ssize_t
+wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ wdt_ping();
+ }
+ return count;
+}
+
+static int
+wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_timeout;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "W83627HF WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof(ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ wdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if (wdt_set_heartbeat(new_timeout))
+ return -EINVAL;
+ wdt_ping();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ wdt_disable();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ wdt_ping();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int
+wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+
+ wdt_ping();
+ return nonseekable_open(inode, file);
+}
+
+static int
+wdt_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wdt_disable();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ wdt_ping();
+ }
+ expect_close = 0;
+ clear_bit(0, &wdt_is_open);
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int
+wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Turn the WDT off */
+ wdt_disable();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt_write,
+ .ioctl = wdt_ioctl,
+ .open = wdt_open,
+ .release = wdt_close,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+static int __init
+wdt_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF Super I/O chip initialising.\n");
+
+ if (wdt_set_heartbeat(timeout)) {
+ wdt_set_heartbeat(WATCHDOG_TIMEOUT);
+ printk (KERN_INFO PFX "timeout value must be 1<=timeout<=63, using %d\n",
+ WATCHDOG_TIMEOUT);
+ }
+
+ if (!request_region(wdt_io, 1, WATCHDOG_NAME)) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_io);
+ ret = -EIO;
+ goto out;
+ }
+
+ w83627hf_init();
+
+ ret = register_reboot_notifier(&wdt_notifier);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto unreg_regions;
+ }
+
+ ret = misc_register(&wdt_miscdev);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto unreg_reboot;
+ }
+
+ printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+out:
+ return ret;
+unreg_reboot:
+ unregister_reboot_notifier(&wdt_notifier);
+unreg_regions:
+ release_region(wdt_io, 1);
+ goto out;
+}
+
+static void __exit
+wdt_exit(void)
+{
+ misc_deregister(&wdt_miscdev);
+ unregister_reboot_notifier(&wdt_notifier);
+ release_region(wdt_io,1);
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>");
+MODULE_DESCRIPTION("w38627hf WDT driver");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/w83877f_wdt.c b/drivers/char/watchdog/w83877f_wdt.c
new file mode 100644
index 000000000000..bccbd4d6ac2d
--- /dev/null
+++ b/drivers/char/watchdog/w83877f_wdt.c
@@ -0,0 +1,426 @@
+/*
+ * W83877F Computer Watchdog Timer driver
+ *
+ * Based on acquirewdt.c by Alan Cox,
+ * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The authors do NOT admit liability nor provide warranty for
+ * any of this software. This material is provided "AS-IS" in
+ * the hope that it may be useful for others.
+ *
+ * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net>
+ *
+ * 4/19 - 2001 [Initial revision]
+ * 9/27 - 2001 Added spinlocking
+ * 4/12 - 2002 [rob@osinvestor.com] Eliminate extra comments
+ * Eliminate fop_read
+ * Eliminate extra spin_unlock
+ * Added KERN_* tags to printks
+ * add CONFIG_WATCHDOG_NOWAYOUT support
+ * fix possible wdt_is_open race
+ * changed watchdog_info to correctly reflect what the driver offers
+ * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT,
+ * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
+ * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces
+ * added extra printk's for startup problems
+ * use module_param
+ * made timeout (the emulated heartbeat) a module_param
+ * made the keepalive ping an internal subroutine
+ *
+ * This WDT driver is different from most other Linux WDT
+ * drivers in that the driver will ping the watchdog by itself,
+ * because this particular WDT has a very short timeout (1.6
+ * seconds) and it would be insane to count on any userspace
+ * daemon always getting scheduled within that time frame.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "w83877f_wdt"
+#define PFX OUR_NAME ": "
+
+#define ENABLE_W83877F_PORT 0x3F0
+#define ENABLE_W83877F 0x87
+#define DISABLE_W83877F 0xAA
+#define WDT_PING 0x443
+#define WDT_REGISTER 0x14
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+/*
+ * The W83877F seems to be fixed at 1.6s timeout (at least on the
+ * EMACS PC-104 board I'm using). If we reset the watchdog every
+ * ~250ms we should be safe. */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static spinlock_t wdt_spinlock;
+
+/*
+ * Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+ /* If we got a heartbeat pulse within the WDT_US_INTERVAL
+ * we agree to ping the WDT
+ */
+ if(time_before(jiffies, next_heartbeat))
+ {
+ /* Ping the WDT */
+ spin_lock(&wdt_spinlock);
+
+ /* Ping the WDT by reading from WDT_PING */
+ inb_p(WDT_PING);
+
+ /* Re-set the timer interval */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+
+ spin_unlock(&wdt_spinlock);
+
+ } else {
+ printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+ }
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+ unsigned long flags;
+ spin_lock_irqsave(&wdt_spinlock, flags);
+
+ /* buy some time */
+ inb_p(WDT_PING);
+
+ /* make W83877F available */
+ outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT);
+ outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT);
+
+ /* enable watchdog */
+ outb_p(WDT_REGISTER, ENABLE_W83877F_PORT);
+ outb_p(writeval, ENABLE_W83877F_PORT+1);
+
+ /* lock the W8387FF away */
+ outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
+
+ spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static void wdt_startup(void)
+{
+ next_heartbeat = jiffies + (timeout * HZ);
+
+ /* Start the timer */
+ timer.expires = jiffies + WDT_INTERVAL;
+ add_timer(&timer);
+
+ wdt_change(WDT_ENABLE);
+
+ printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+ /* Stop the timer */
+ del_timer(&timer);
+
+ wdt_change(WDT_DISABLE);
+
+ printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+ /* user land ping */
+ next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if(count)
+ {
+ if (!nowayout)
+ {
+ size_t ofs;
+
+ /* note: just in case someone wrote the magic character
+ * five months ago... */
+ wdt_expect_close = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for(ofs = 0; ofs != count; ofs++)
+ {
+ char c;
+ if (get_user(c, buf + ofs))
+ return -EFAULT;
+ if (c == 'V')
+ wdt_expect_close = 42;
+ }
+ }
+
+ /* someone wrote to us, we should restart timer */
+ wdt_keepalive();
+ }
+ return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+ /* Just in case we're already talking to someone... */
+ if(test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+
+ /* Good, fire up the show */
+ wdt_startup();
+ return nonseekable_open(inode, file);
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+ if(wdt_expect_close == 42)
+ wdt_turnoff();
+ else {
+ del_timer(&timer);
+ printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+ }
+ clear_bit(0, &wdt_is_open);
+ wdt_expect_close = 0;
+ return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident=
+ {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "W83877F",
+ };
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_keepalive();
+ return 0;
+ case WDIOC_SETOPTIONS:
+ {
+ int new_options, retval = -EINVAL;
+
+ if(get_user(new_options, p))
+ return -EFAULT;
+
+ if(new_options & WDIOS_DISABLECARD) {
+ wdt_turnoff();
+ retval = 0;
+ }
+
+ if(new_options & WDIOS_ENABLECARD) {
+ wdt_startup();
+ retval = 0;
+ }
+
+ return retval;
+ }
+ case WDIOC_SETTIMEOUT:
+ {
+ int new_timeout;
+
+ if(get_user(new_timeout, p))
+ return -EFAULT;
+
+ if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+ return -EINVAL;
+
+ timeout = new_timeout;
+ wdt_keepalive();
+ /* Fall through */
+ }
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+ }
+}
+
+static struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = fop_write,
+ .open = fop_open,
+ .release = fop_close,
+ .ioctl = fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+/*
+ * Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT)
+ wdt_turnoff();
+ return NOTIFY_DONE;
+}
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+ .notifier_call = wdt_notify_sys,
+};
+
+static void __exit w83877f_wdt_unload(void)
+{
+ wdt_turnoff();
+
+ /* Deregister */
+ misc_deregister(&wdt_miscdev);
+
+ unregister_reboot_notifier(&wdt_notifier);
+ release_region(WDT_PING,1);
+ release_region(ENABLE_W83877F_PORT,2);
+}
+
+static int __init w83877f_wdt_init(void)
+{
+ int rc = -EBUSY;
+
+ spin_lock_init(&wdt_spinlock);
+
+ if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+ {
+ timeout = WATCHDOG_TIMEOUT;
+ printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+ timeout);
+ }
+
+ if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT"))
+ {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ ENABLE_W83877F_PORT);
+ rc = -EIO;
+ goto err_out;
+ }
+
+ if (!request_region(WDT_PING, 1, "W8387FF WDT"))
+ {
+ printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ WDT_PING);
+ rc = -EIO;
+ goto err_out_region1;
+ }
+
+ init_timer(&timer);
+ timer.function = wdt_timer_ping;
+ timer.data = 0;
+
+ rc = misc_register(&wdt_miscdev);
+ if (rc)
+ {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ wdt_miscdev.minor, rc);
+ goto err_out_region2;
+ }
+
+ rc = register_reboot_notifier(&wdt_notifier);
+ if (rc)
+ {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ rc);
+ goto err_out_miscdev;
+ }
+
+ printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return 0;
+
+err_out_miscdev:
+ misc_deregister(&wdt_miscdev);
+err_out_region2:
+ release_region(WDT_PING,1);
+err_out_region1:
+ release_region(ENABLE_W83877F_PORT,2);
+err_out:
+ return rc;
+}
+
+module_init(w83877f_wdt_init);
+module_exit(w83877f_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/wafer5823wdt.c b/drivers/char/watchdog/wafer5823wdt.c
new file mode 100644
index 000000000000..abb0bea45c02
--- /dev/null
+++ b/drivers/char/watchdog/wafer5823wdt.c
@@ -0,0 +1,330 @@
+/*
+ * ICP Wafer 5823 Single Board Computer WDT driver
+ * http://www.icpamerica.com/wafer_5823.php
+ * May also work on other similar models
+ *
+ * (c) Copyright 2002 Justin Cormack <justin@street-vision.com>
+ *
+ * Release 0.02
+ *
+ * Based on advantechwdt.c which is based on wdt.c.
+ * Original copyright messages:
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define WATCHDOG_NAME "Wafer 5823 WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WD_TIMO 60 /* 60 sec default timeout */
+
+static unsigned long wafwdt_is_open;
+static char expect_close;
+static spinlock_t wafwdt_lock;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ *
+ * To enable, write the timeout value in seconds (1 to 255) to I/O
+ * port WDT_START, then read the port to start the watchdog. To pat
+ * the dog, read port WDT_STOP to stop the timer, then read WDT_START
+ * to restart it again.
+ */
+
+static int wdt_stop = 0x843;
+static int wdt_start = 0x443;
+
+static int timeout = WD_TIMO; /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WD_TIMO) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wafwdt_ping(void)
+{
+ /* pat watchdog */
+ spin_lock(&wafwdt_lock);
+ inb_p(wdt_stop);
+ inb_p(wdt_start);
+ spin_unlock(&wafwdt_lock);
+}
+
+static void wafwdt_start(void)
+{
+ /* start up watchdog */
+ outb_p(timeout, wdt_start);
+ inb_p(wdt_start);
+}
+
+static void
+wafwdt_stop(void)
+{
+ /* stop watchdog */
+ inb_p(wdt_stop);
+}
+
+static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
+{
+ /* See if we got the magic character 'V' and reload the timer */
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ /* scan to see whether or not we got the magic character */
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ /* Well, anyhow someone wrote to us, we should return that favour */
+ wafwdt_ping();
+ }
+ return count;
+}
+
+static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_timeout;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ static struct watchdog_info ident = {
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+ .firmware_version = 1,
+ .identity = "Wafer 5823 WDT",
+ };
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ if (copy_to_user(argp, &ident, sizeof (ident)))
+ return -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ wafwdt_ping();
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, p))
+ return -EFAULT;
+ if ((new_timeout < 1) || (new_timeout > 255))
+ return -EINVAL;
+ timeout = new_timeout;
+ wafwdt_stop();
+ wafwdt_start();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, p);
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options, retval = -EINVAL;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ if (options & WDIOS_DISABLECARD) {
+ wafwdt_start();
+ retval = 0;
+ }
+
+ if (options & WDIOS_ENABLECARD) {
+ wafwdt_stop();
+ retval = 0;
+ }
+
+ return retval;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static int wafwdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(0, &wafwdt_is_open))
+ return -EBUSY;
+
+ /*
+ * Activate
+ */
+ wafwdt_start();
+ return nonseekable_open(inode, file);
+}
+
+static int
+wafwdt_close(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wafwdt_stop();
+ } else {
+ printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n");
+ wafwdt_ping();
+ }
+ clear_bit(0, &wafwdt_is_open);
+ expect_close = 0;
+ return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+ if (code == SYS_DOWN || code == SYS_HALT) {
+ /* Turn the WDT off */
+ wafwdt_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+static struct file_operations wafwdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wafwdt_write,
+ .ioctl = wafwdt_ioctl,
+ .open = wafwdt_open,
+ .release = wafwdt_close,
+};
+
+static struct miscdevice wafwdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wafwdt_fops,
+};
+
+/*
+ * The WDT needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wafwdt_notifier = {
+ .notifier_call = wafwdt_notify_sys,
+};
+
+static int __init wafwdt_init(void)
+{
+ int ret;
+
+ printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n");
+
+ spin_lock_init(&wafwdt_lock);
+
+ if (timeout < 1 || timeout > 255) {
+ timeout = WD_TIMO;
+ printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n",
+ timeout);
+ }
+
+ if (wdt_stop != wdt_start) {
+ if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_stop);
+ ret = -EIO;
+ goto error;
+ }
+ }
+
+ if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+ wdt_start);
+ ret = -EIO;
+ goto error2;
+ }
+
+ ret = register_reboot_notifier(&wafwdt_notifier);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ ret);
+ goto error3;
+ }
+
+ ret = misc_register(&wafwdt_miscdev);
+ if (ret != 0) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto error4;
+ }
+
+ printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+ timeout, nowayout);
+
+ return ret;
+error4:
+ unregister_reboot_notifier(&wafwdt_notifier);
+error3:
+ release_region(wdt_start, 1);
+error2:
+ if (wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+error:
+ return ret;
+}
+
+static void __exit wafwdt_exit(void)
+{
+ misc_deregister(&wafwdt_miscdev);
+ unregister_reboot_notifier(&wafwdt_notifier);
+ if(wdt_stop != wdt_start)
+ release_region(wdt_stop, 1);
+ release_region(wdt_start, 1);
+}
+
+module_init(wafwdt_init);
+module_exit(wafwdt_exit);
+
+MODULE_AUTHOR("Justin Cormack");
+MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+/* end of wafer5823wdt.c */
diff --git a/drivers/char/watchdog/wd501p.h b/drivers/char/watchdog/wd501p.h
new file mode 100644
index 000000000000..84e60eb74337
--- /dev/null
+++ b/drivers/char/watchdog/wd501p.h
@@ -0,0 +1,52 @@
+/*
+ * Industrial Computer Source WDT500/501 driver
+ *
+ * (c) Copyright 1995 CymruNET Ltd
+ * Innovation Centre
+ * Singleton Park
+ * Swansea
+ * Wales
+ * UK
+ * SA2 8PP
+ *
+ * http://www.cymru.net
+ *
+ * This driver is provided under the GNU General Public License, incorporated
+ * herein by reference. The driver is provided without warranty or
+ * support.
+ *
+ * Release 0.04.
+ *
+ */
+
+#include <linux/config.h>
+
+#define WDT_COUNT0 (io+0)
+#define WDT_COUNT1 (io+1)
+#define WDT_COUNT2 (io+2)
+#define WDT_CR (io+3)
+#define WDT_SR (io+4) /* Start buzzer on PCI write */
+#define WDT_RT (io+5) /* Stop buzzer on PCI write */
+#define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */
+#define WDT_DC (io+7)
+
+/* The following are only on the PCI card, they're outside of I/O space on
+ * the ISA card: */
+#define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */
+/* inverted opto isolated reset output: */
+#define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */
+/* opto isolated reset output: */
+#define WDT_OPTORST (io+14) /* wr=enable, rd=disable */
+/* programmable outputs: */
+#define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */
+
+ /* FAN 501 500 */
+#define WDC_SR_WCCR 1 /* Active low */ /* X X X */
+#define WDC_SR_TGOOD 2 /* X X - */
+#define WDC_SR_ISOI0 4 /* X X X */
+#define WDC_SR_ISII1 8 /* X X X */
+#define WDC_SR_FANGOOD 16 /* X - - */
+#define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */
+#define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */
+#define WDC_SR_IRQ 128 /* Active low */ /* X X X */
+
diff --git a/drivers/char/watchdog/wdt.c b/drivers/char/watchdog/wdt.c
new file mode 100644
index 000000000000..5684aa379886
--- /dev/null
+++ b/drivers/char/watchdog/wdt.c
@@ -0,0 +1,647 @@
+/*
+ * Industrial Computer Source WDT500/501 driver
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Release 0.10.
+ *
+ * Fixes
+ * Dave Gregorich : Modularisation and minor bugs
+ * Alan Cox : Added the watchdog ioctl() stuff
+ * Alan Cox : Fixed the reboot problem (as noted by
+ * Matt Crocker).
+ * Alan Cox : Added wdt= boot option
+ * Alan Cox : Cleaned up copy/user stuff
+ * Tim Hockin : Added insmod parameters, comment cleanup
+ * Parameterized timeout
+ * Tigran Aivazian : Restructured wdt_init() to handle failures
+ * Joel Becker : Added WDIOC_GET/SETTIMEOUT
+ * Matt Domsch : Added nowayout module option
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include "wd501p.h"
+
+static unsigned long wdt_is_open;
+static char expect_close;
+
+/*
+ * Module parameters
+ */
+
+#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* You must set these - there is no sane way to probe for this board. */
+static int io=0x240;
+static int irq=11;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "WDT io port (default=0x240)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "WDT irq (default=11)");
+
+#ifdef CONFIG_WDT_501
+/* Support for the Fan Tachometer on the WDT501-P */
+static int tachometer;
+
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)");
+#endif /* CONFIG_WDT_501 */
+
+/*
+ * Programming support
+ */
+
+static void wdt_ctr_mode(int ctr, int mode)
+{
+ ctr<<=6;
+ ctr|=0x30;
+ ctr|=(mode<<1);
+ outb_p(ctr, WDT_CR);
+}
+
+static void wdt_ctr_load(int ctr, int val)
+{
+ outb_p(val&0xFF, WDT_COUNT0+ctr);
+ outb_p(val>>8, WDT_COUNT0+ctr);
+}
+
+/**
+ * wdt_start:
+ *
+ * Start the watchdog driver.
+ */
+
+static int wdt_start(void)
+{
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */
+ wdt_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */
+ wdt_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */
+ wdt_ctr_load(0, 8948); /* Count at 100Hz */
+ wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */
+ wdt_ctr_load(2,65535); /* Length of reset pulse */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+ return 0;
+}
+
+/**
+ * wdt_stop:
+ *
+ * Stop the watchdog driver.
+ */
+
+static int wdt_stop (void)
+{
+ /* Turn the card off */
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_load(2,0); /* 0 length reset pulses now */
+ return 0;
+}
+
+/**
+ * wdt_ping:
+ *
+ * Reload counter one with the watchdog heartbeat. We don't bother reloading
+ * the cascade counter.
+ */
+
+static int wdt_ping(void)
+{
+ /* Write a watchdog value */
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdt_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */
+ wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+ return 0;
+}
+
+/**
+ * wdt_set_heartbeat:
+ * @t: the new heartbeat value that needs to be set.
+ *
+ * Set a new heartbeat value for the watchdog device. If the heartbeat value is
+ * incorrect we keep the old value and return -EINVAL. If successfull we
+ * return 0.
+ */
+static int wdt_set_heartbeat(int t)
+{
+ if ((t < 1) || (t > 65535))
+ return -EINVAL;
+
+ heartbeat = t;
+ wd_heartbeat = t * 100;
+ return 0;
+}
+
+/**
+ * wdt_get_status:
+ * @status: the new status.
+ *
+ * Extract the status information from a WDT watchdog device. There are
+ * several board variants so we have to know which bits are valid. Some
+ * bits default to one and some to zero in order to be maximally painful.
+ *
+ * we then map the bits onto the status ioctl flags.
+ */
+
+static int wdt_get_status(int *status)
+{
+ unsigned char new_status=inb_p(WDT_SR);
+
+ *status=0;
+ if (new_status & WDC_SR_ISOI0)
+ *status |= WDIOF_EXTERN1;
+ if (new_status & WDC_SR_ISII1)
+ *status |= WDIOF_EXTERN2;
+#ifdef CONFIG_WDT_501
+ if (!(new_status & WDC_SR_TGOOD))
+ *status |= WDIOF_OVERHEAT;
+ if (!(new_status & WDC_SR_PSUOVER))
+ *status |= WDIOF_POWEROVER;
+ if (!(new_status & WDC_SR_PSUUNDR))
+ *status |= WDIOF_POWERUNDER;
+ if (tachometer) {
+ if (!(new_status & WDC_SR_FANGOOD))
+ *status |= WDIOF_FANFAULT;
+ }
+#endif /* CONFIG_WDT_501 */
+ return 0;
+}
+
+#ifdef CONFIG_WDT_501
+/**
+ * wdt_get_temperature:
+ *
+ * Reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdt_get_temperature(int *temperature)
+{
+ unsigned short c=inb_p(WDT_RT);
+
+ *temperature = (c * 11 / 15) + 7;
+ return 0;
+}
+#endif /* CONFIG_WDT_501 */
+
+/**
+ * wdt_interrupt:
+ * @irq: Interrupt number
+ * @dev_id: Unused as we don't allow multiple devices.
+ * @regs: Unused.
+ *
+ * Handle an interrupt from the board. These are raised when the status
+ * map changes in what the board considers an interesting way. That means
+ * a failure condition occurring.
+ */
+
+static irqreturn_t wdt_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /*
+ * Read the status register see what is up and
+ * then printk it.
+ */
+ unsigned char status=inb_p(WDT_SR);
+
+ printk(KERN_CRIT "WDT status %d\n", status);
+
+#ifdef CONFIG_WDT_501
+ if (!(status & WDC_SR_TGOOD))
+ printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT));
+ if (!(status & WDC_SR_PSUOVER))
+ printk(KERN_CRIT "PSU over voltage.\n");
+ if (!(status & WDC_SR_PSUUNDR))
+ printk(KERN_CRIT "PSU under voltage.\n");
+ if (tachometer) {
+ if (!(status & WDC_SR_FANGOOD))
+ printk(KERN_CRIT "Possible fan fault.\n");
+ }
+#endif /* CONFIG_WDT_501 */
+ if (!(status & WDC_SR_WCCR))
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+ printk(KERN_CRIT "Would Reboot.\n");
+#else
+ printk(KERN_CRIT "Initiating system reboot.\n");
+ machine_restart(NULL);
+#endif
+#else
+ printk(KERN_CRIT "Reset in 5ms.\n");
+#endif
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * wdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if(count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ wdt_ping();
+ }
+ return count;
+}
+
+/**
+ * wdt_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features. We only actually usefully support
+ * querying capabilities and current status.
+ */
+
+static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_heartbeat;
+ int status;
+
+ static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT|
+ WDIOF_MAGICCLOSE|
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = "WDT500/501",
+ };
+
+ /* Add options according to the card we have */
+ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+#ifdef CONFIG_WDT_501
+ ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
+ if (tachometer)
+ ident.options |= WDIOF_FANFAULT;
+#endif /* CONFIG_WDT_501 */
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+
+ case WDIOC_GETSTATUS:
+ wdt_get_status(&status);
+ return put_user(status, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdt_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (wdt_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ wdt_ping();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ }
+}
+
+/**
+ * wdt_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The watchdog device has been opened. The watchdog device is single
+ * open and on opening we load the counters. Counter zero is a 100Hz
+ * cascade, into counter 1 which downcounts to reboot. When the counter
+ * triggers counter 2 downcounts the length of the reset pulse which
+ * set set to be as long as possible.
+ */
+
+static int wdt_open(struct inode *inode, struct file *file)
+{
+ if(test_and_set_bit(0, &wdt_is_open))
+ return -EBUSY;
+ /*
+ * Activate
+ */
+ wdt_start();
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * wdt_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int wdt_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wdt_stop();
+ clear_bit(0, &wdt_is_open);
+ } else {
+ printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n");
+ wdt_ping();
+ }
+ expect_close = 0;
+ return 0;
+}
+
+#ifdef CONFIG_WDT_501
+/**
+ * wdt_temp_read:
+ * @file: file handle to the watchdog board
+ * @buf: buffer to write 1 byte into
+ * @count: length of buffer
+ * @ptr: offset (no seek allowed)
+ *
+ * Temp_read reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdt_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+ int temperature;
+
+ if (wdt_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user (buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+/**
+ * wdt_temp_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The temperature device has been opened.
+ */
+
+static int wdt_temp_open(struct inode *inode, struct file *file)
+{
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * wdt_temp_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The temperature device has been closed.
+ */
+
+static int wdt_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+#endif /* CONFIG_WDT_501 */
+
+/**
+ * notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the card off */
+ wdt_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static struct file_operations wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt_write,
+ .ioctl = wdt_ioctl,
+ .open = wdt_open,
+ .release = wdt_release,
+};
+
+static struct miscdevice wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt_fops,
+};
+
+#ifdef CONFIG_WDT_501
+static struct file_operations wdt_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = wdt_temp_read,
+ .open = wdt_temp_open,
+ .release = wdt_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &wdt_temp_fops,
+};
+#endif /* CONFIG_WDT_501 */
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+ .notifier_call = wdt_notify_sys,
+};
+
+/**
+ * cleanup_module:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit wdt_exit(void)
+{
+ misc_deregister(&wdt_miscdev);
+#ifdef CONFIG_WDT_501
+ misc_deregister(&temp_miscdev);
+#endif /* CONFIG_WDT_501 */
+ unregister_reboot_notifier(&wdt_notifier);
+ free_irq(irq, NULL);
+ release_region(io,8);
+}
+
+/**
+ * wdt_init:
+ *
+ * Set up the WDT watchdog board. All we have to do is grab the
+ * resources we require and bitch if anyone beat us to them.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init wdt_init(void)
+{
+ int ret;
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (wdt_set_heartbeat(heartbeat)) {
+ wdt_set_heartbeat(WD_TIMO);
+ printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WD_TIMO);
+ }
+
+ if (!request_region(io, 8, "wdt501p")) {
+ printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io);
+ ret = -EBUSY;
+ goto out;
+ }
+
+ ret = request_irq(irq, wdt_interrupt, SA_INTERRUPT, "wdt501p", NULL);
+ if(ret) {
+ printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq);
+ goto outreg;
+ }
+
+ ret = register_reboot_notifier(&wdt_notifier);
+ if(ret) {
+ printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret);
+ goto outirq;
+ }
+
+#ifdef CONFIG_WDT_501
+ ret = misc_register(&temp_miscdev);
+ if (ret) {
+ printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto outrbt;
+ }
+#endif /* CONFIG_WDT_501 */
+
+ ret = misc_register(&wdt_miscdev);
+ if (ret) {
+ printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto outmisc;
+ }
+
+ ret = 0;
+ printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n",
+ io, irq, heartbeat, nowayout);
+#ifdef CONFIG_WDT_501
+ printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
+#endif /* CONFIG_WDT_501 */
+
+out:
+ return ret;
+
+outmisc:
+#ifdef CONFIG_WDT_501
+ misc_deregister(&temp_miscdev);
+outrbt:
+#endif /* CONFIG_WDT_501 */
+ unregister_reboot_notifier(&wdt_notifier);
+outirq:
+ free_irq(irq, NULL);
+outreg:
+ release_region(io,8);
+ goto out;
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/watchdog/wdt285.c b/drivers/char/watchdog/wdt285.c
new file mode 100644
index 000000000000..52825a1f1779
--- /dev/null
+++ b/drivers/char/watchdog/wdt285.c
@@ -0,0 +1,229 @@
+/*
+ * Intel 21285 watchdog driver
+ * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998
+ *
+ * based on
+ *
+ * SoftDog 0.05: A Software Watchdog Device
+ *
+ * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/hardware/dec21285.h>
+
+/*
+ * Define this to stop the watchdog actually rebooting the machine.
+ */
+#undef ONLY_TESTING
+
+static unsigned int soft_margin = 60; /* in seconds */
+static unsigned int reload;
+static unsigned long timer_alive;
+
+#ifdef ONLY_TESTING
+/*
+ * If the timer expires..
+ */
+static void watchdog_fire(int irq, void *dev_id, struct pt_regs *regs)
+{
+ printk(KERN_CRIT "Watchdog: Would Reboot.\n");
+ *CSR_TIMER4_CNTL = 0;
+ *CSR_TIMER4_CLR = 0;
+}
+#endif
+
+/*
+ * Refresh the timer.
+ */
+static void watchdog_ping(void)
+{
+ *CSR_TIMER4_LOAD = reload;
+}
+
+/*
+ * Allow only one person to hold it open
+ */
+static int watchdog_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ if (*CSR_SA110_CNTL & (1 << 13))
+ return -EBUSY;
+
+ if (test_and_set_bit(1, &timer_alive))
+ return -EBUSY;
+
+ reload = soft_margin * (mem_fclk_21285 / 256);
+
+ *CSR_TIMER4_CLR = 0;
+ watchdog_ping();
+ *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD
+ | TIMER_CNTL_DIV256;
+
+#ifdef ONLY_TESTING
+ ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL);
+ if (ret) {
+ *CSR_TIMER4_CNTL = 0;
+ clear_bit(1, &timer_alive);
+ }
+#else
+ /*
+ * Setting this bit is irreversible; once enabled, there is
+ * no way to disable the watchdog.
+ */
+ *CSR_SA110_CNTL |= 1 << 13;
+
+ ret = 0;
+#endif
+ nonseekable_open(inode, file);
+ return ret;
+}
+
+/*
+ * Shut off the timer.
+ * Note: if we really have enabled the watchdog, there
+ * is no way to turn off.
+ */
+static int watchdog_release(struct inode *inode, struct file *file)
+{
+#ifdef ONLY_TESTING
+ free_irq(IRQ_TIMER4, NULL);
+ clear_bit(1, &timer_alive);
+#endif
+ return 0;
+}
+
+static ssize_t
+watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+ /*
+ * Refresh the timer.
+ */
+ if (len)
+ watchdog_ping();
+
+ return len;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT,
+ .identity = "Footbridge Watchdog",
+};
+
+static int
+watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ unsigned int new_margin;
+ int ret = -ENOIOCTLCMD;
+
+ switch(cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = 0;
+ if (copy_to_user((void *)arg, &ident, sizeof(ident)))
+ ret = -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0,(int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ watchdog_ping();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(new_margin, (int *)arg);
+ if (ret)
+ break;
+
+ /* Arbitrary, can't find the card's limits */
+ if (new_margin < 0 || new_margin > 60) {
+ ret = -EINVAL;
+ break;
+ }
+
+ soft_margin = new_margin;
+ reload = soft_margin * (mem_fclk_21285 / 256);
+ watchdog_ping();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(soft_margin, (int *)arg);
+ break;
+ }
+ return ret;
+}
+
+static struct file_operations watchdog_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = watchdog_write,
+ .ioctl = watchdog_ioctl,
+ .open = watchdog_open,
+ .release = watchdog_release,
+};
+
+static struct miscdevice watchdog_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &watchdog_fops,
+};
+
+static int __init footbridge_watchdog_init(void)
+{
+ int retval;
+
+ if (machine_is_netwinder())
+ return -ENODEV;
+
+ retval = misc_register(&watchdog_miscdev);
+ if (retval < 0)
+ return retval;
+
+ printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n",
+ soft_margin);
+
+ if (machine_is_cats())
+ printk("Warning: Watchdog reset may not work on this machine.\n");
+ return 0;
+}
+
+static void __exit footbridge_watchdog_exit(void)
+{
+ misc_deregister(&watchdog_miscdev);
+}
+
+MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>");
+MODULE_DESCRIPTION("Footbridge watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin,"Watchdog timeout in seconds");
+
+module_init(footbridge_watchdog_init);
+module_exit(footbridge_watchdog_exit);
diff --git a/drivers/char/watchdog/wdt977.c b/drivers/char/watchdog/wdt977.c
new file mode 100644
index 000000000000..072e9b214759
--- /dev/null
+++ b/drivers/char/watchdog/wdt977.c
@@ -0,0 +1,459 @@
+/*
+ * Wdt977 0.03: A Watchdog Device for Netwinder W83977AF chip
+ *
+ * (c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>)
+ *
+ * -----------------------
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * -----------------------
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ * 19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface
+ * 06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts
+ * from minutes to seconds.
+ * 07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in
+ * nwwatchdog_init.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/mach-types.h>
+#include <asm/uaccess.h>
+
+#define PFX "Wdt977: "
+#define WATCHDOG_MINOR 130
+
+#define DEFAULT_TIMEOUT 60 /* default timeout in seconds */
+
+static int timeout = DEFAULT_TIMEOUT;
+static int timeoutM; /* timeout in minutes */
+static unsigned long timer_alive;
+static int testmode;
+static char expect_close;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (60..15300), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+module_param(testmode, int, 0);
+MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Start the watchdog
+ */
+
+static int wdt977_start(void)
+{
+ /* unlock the SuperIO chip */
+ outb(0x87,0x370);
+ outb(0x87,0x370);
+
+ /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4
+ * F2 has the timeout in minutes
+ * F3 could be set to the POWER LED blink (with GP17 set to PowerLed)
+ * at timeout, and to reset timer on kbd/mouse activity (not impl.)
+ * F4 is used to just clear the TIMEOUT'ed state (bit 0)
+ */
+ outb(0x07,0x370);
+ outb(0x08,0x371);
+ outb(0xF2,0x370);
+ outb(timeoutM,0x371);
+ outb(0xF3,0x370);
+ outb(0x00,0x371); /* another setting is 0E for kbd/mouse/LED */
+ outb(0xF4,0x370);
+ outb(0x00,0x371);
+
+ /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */
+ /* in test mode watch the bit 1 on F4 to indicate "triggered" */
+ if (!testmode)
+ {
+ outb(0x07,0x370);
+ outb(0x07,0x371);
+ outb(0xE6,0x370);
+ outb(0x08,0x371);
+ }
+
+ /* lock the SuperIO chip */
+ outb(0xAA,0x370);
+
+ printk(KERN_INFO PFX "activated.\n");
+
+ return 0;
+}
+
+/*
+ * Stop the watchdog
+ */
+
+static int wdt977_stop(void)
+{
+ /* unlock the SuperIO chip */
+ outb(0x87,0x370);
+ outb(0x87,0x370);
+
+ /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4
+ * F3 is reset to its default state
+ * F4 can clear the TIMEOUT'ed state (bit 0) - back to default
+ * We can not use GP17 as a PowerLed, as we use its usage as a RedLed
+ */
+ outb(0x07,0x370);
+ outb(0x08,0x371);
+ outb(0xF2,0x370);
+ outb(0xFF,0x371);
+ outb(0xF3,0x370);
+ outb(0x00,0x371);
+ outb(0xF4,0x370);
+ outb(0x00,0x371);
+ outb(0xF2,0x370);
+ outb(0x00,0x371);
+
+ /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */
+ outb(0x07,0x370);
+ outb(0x07,0x371);
+ outb(0xE6,0x370);
+ outb(0x08,0x371);
+
+ /* lock the SuperIO chip */
+ outb(0xAA,0x370);
+
+ printk(KERN_INFO PFX "shutdown.\n");
+
+ return 0;
+}
+
+/*
+ * Send a keepalive ping to the watchdog
+ * This is done by simply re-writing the timeout to reg. 0xF2
+ */
+
+static int wdt977_keepalive(void)
+{
+ /* unlock the SuperIO chip */
+ outb(0x87,0x370);
+ outb(0x87,0x370);
+
+ /* select device Aux2 (device=8) and kicks watchdog reg F2 */
+ /* F2 has the timeout in minutes */
+ outb(0x07,0x370);
+ outb(0x08,0x371);
+ outb(0xF2,0x370);
+ outb(timeoutM,0x371);
+
+ /* lock the SuperIO chip */
+ outb(0xAA,0x370);
+
+ return 0;
+}
+
+/*
+ * Set the watchdog timeout value
+ */
+
+static int wdt977_set_timeout(int t)
+{
+ int tmrval;
+
+ /* convert seconds to minutes, rounding up */
+ tmrval = (t + 59) / 60;
+
+ if (machine_is_netwinder()) {
+ /* we have a hw bug somewhere, so each 977 minute is actually only 30sec
+ * this limits the max timeout to half of device max of 255 minutes...
+ */
+ tmrval += tmrval;
+ }
+
+ if ((tmrval < 1) || (tmrval > 255))
+ return -EINVAL;
+
+ /* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */
+ timeout = t;
+ timeoutM = tmrval;
+ return 0;
+}
+
+/*
+ * Get the watchdog status
+ */
+
+static int wdt977_get_status(int *status)
+{
+ int new_status;
+
+ *status=0;
+
+ /* unlock the SuperIO chip */
+ outb(0x87,0x370);
+ outb(0x87,0x370);
+
+ /* select device Aux2 (device=8) and read watchdog reg F4 */
+ outb(0x07,0x370);
+ outb(0x08,0x371);
+ outb(0xF4,0x370);
+ new_status = inb(0x371);
+
+ /* lock the SuperIO chip */
+ outb(0xAA,0x370);
+
+ if (new_status & 1)
+ *status |= WDIOF_CARDRESET;
+
+ return 0;
+}
+
+
+/*
+ * /dev/watchdog handling
+ */
+
+static int wdt977_open(struct inode *inode, struct file *file)
+{
+ /* If the watchdog is alive we don't need to start it again */
+ if( test_and_set_bit(0,&timer_alive) )
+ return -EBUSY;
+
+ if (nowayout)
+ __module_get(THIS_MODULE);
+
+ wdt977_start();
+ return nonseekable_open(inode, file);
+}
+
+static int wdt977_release(struct inode *inode, struct file *file)
+{
+ /*
+ * Shut off the timer.
+ * Lock it in if it's a module and we set nowayout
+ */
+ if (expect_close == 42)
+ {
+ wdt977_stop();
+ clear_bit(0,&timer_alive);
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+ wdt977_keepalive();
+ }
+ expect_close = 0;
+ return 0;
+}
+
+
+/*
+ * wdt977_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt977_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ /* In case it was set long ago */
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, buf + i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+
+ wdt977_keepalive();
+ }
+ return count;
+}
+
+/*
+ * wdt977_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT |
+ WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = "Winbond 83977",
+};
+
+static int wdt977_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int status;
+ int new_options, retval = -EINVAL;
+ int new_timeout;
+ union {
+ struct watchdog_info __user *ident;
+ int __user *i;
+ } uarg;
+
+ uarg.i = (int __user *)arg;
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(uarg.ident, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+
+ case WDIOC_GETSTATUS:
+ wdt977_get_status(&status);
+ return put_user(status, uarg.i);
+
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, uarg.i);
+
+ case WDIOC_KEEPALIVE:
+ wdt977_keepalive();
+ return 0;
+
+ case WDIOC_SETOPTIONS:
+ if (get_user (new_options, uarg.i))
+ return -EFAULT;
+
+ if (new_options & WDIOS_DISABLECARD) {
+ wdt977_stop();
+ retval = 0;
+ }
+
+ if (new_options & WDIOS_ENABLECARD) {
+ wdt977_start();
+ retval = 0;
+ }
+
+ return retval;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_timeout, uarg.i))
+ return -EFAULT;
+
+ if (wdt977_set_timeout(new_timeout))
+ return -EINVAL;
+
+ wdt977_keepalive();
+ /* Fall */
+
+ case WDIOC_GETTIMEOUT:
+ return put_user(timeout, uarg.i);
+
+ }
+}
+
+static int wdt977_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if(code==SYS_DOWN || code==SYS_HALT)
+ wdt977_stop();
+ return NOTIFY_DONE;
+}
+
+static struct file_operations wdt977_fops=
+{
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdt977_write,
+ .ioctl = wdt977_ioctl,
+ .open = wdt977_open,
+ .release = wdt977_release,
+};
+
+static struct miscdevice wdt977_miscdev=
+{
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdt977_fops,
+};
+
+static struct notifier_block wdt977_notifier = {
+ .notifier_call = wdt977_notify_sys,
+};
+
+static int __init nwwatchdog_init(void)
+{
+ int retval;
+ if (!machine_is_netwinder())
+ return -ENODEV;
+
+ /* Check that the timeout value is within it's range ; if not reset to the default */
+ if (wdt977_set_timeout(timeout)) {
+ wdt977_set_timeout(DEFAULT_TIMEOUT);
+ printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n",
+ DEFAULT_TIMEOUT);
+ }
+
+ retval = register_reboot_notifier(&wdt977_notifier);
+ if (retval) {
+ printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+ retval);
+ return retval;
+ }
+
+ retval = misc_register(&wdt977_miscdev);
+ if (retval) {
+ printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, retval);
+ unregister_reboot_notifier(&wdt977_notifier);
+ return retval;
+ }
+
+ printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode = %i)\n",
+ timeout, nowayout, testmode);
+
+ return 0;
+}
+
+static void __exit nwwatchdog_exit(void)
+{
+ misc_deregister(&wdt977_miscdev);
+ unregister_reboot_notifier(&wdt977_notifier);
+}
+
+module_init(nwwatchdog_init);
+module_exit(nwwatchdog_exit);
+
+MODULE_AUTHOR("Woody Suwalski <woody@netwinder.org>");
+MODULE_DESCRIPTION("W83977AF Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/wdt_pci.c b/drivers/char/watchdog/wdt_pci.c
new file mode 100644
index 000000000000..7651deda928c
--- /dev/null
+++ b/drivers/char/watchdog/wdt_pci.c
@@ -0,0 +1,763 @@
+/*
+ * Industrial Computer Source PCI-WDT500/501 driver
+ *
+ * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ * http://www.redhat.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ *
+ * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Release 0.10.
+ *
+ * Fixes
+ * Dave Gregorich : Modularisation and minor bugs
+ * Alan Cox : Added the watchdog ioctl() stuff
+ * Alan Cox : Fixed the reboot problem (as noted by
+ * Matt Crocker).
+ * Alan Cox : Added wdt= boot option
+ * Alan Cox : Cleaned up copy/user stuff
+ * Tim Hockin : Added insmod parameters, comment cleanup
+ * Parameterized timeout
+ * JP Nollmann : Added support for PCI wdt501p
+ * Alan Cox : Split ISA and PCI cards into two drivers
+ * Jeff Garzik : PCI cleanups
+ * Tigran Aivazian : Restructured wdtpci_init_one() to handle failures
+ * Joel Becker : Added WDIOC_GET/SETTIMEOUT
+ * Zwane Mwaikambo : Magic char closing, locking changes, cleanups
+ * Matt Domsch : nowayout module option
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WDT_IS_PCI
+#include "wd501p.h"
+
+#define PFX "wdt_pci: "
+
+/*
+ * Until Access I/O gets their application for a PCI vendor ID approved,
+ * I don't think that it's appropriate to move these constants into the
+ * regular pci_ids.h file. -- JPN 2000/01/18
+ */
+
+#ifndef PCI_VENDOR_ID_ACCESSIO
+#define PCI_VENDOR_ID_ACCESSIO 0x494f
+#endif
+#ifndef PCI_DEVICE_ID_WDG_CSM
+#define PCI_DEVICE_ID_WDG_CSM 0x22c0
+#endif
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int dev_count;
+
+static struct semaphore open_sem;
+static spinlock_t wdtpci_lock;
+static char expect_close;
+
+static int io;
+static int irq;
+
+/* Default timeout */
+#define WD_TIMO 60 /* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#ifdef CONFIG_WDT_501_PCI
+/* Support for the Fan Tachometer on the PCI-WDT501 */
+static int tachometer;
+
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)");
+#endif /* CONFIG_WDT_501_PCI */
+
+/*
+ * Programming support
+ */
+
+static void wdtpci_ctr_mode(int ctr, int mode)
+{
+ ctr<<=6;
+ ctr|=0x30;
+ ctr|=(mode<<1);
+ outb_p(ctr, WDT_CR);
+}
+
+static void wdtpci_ctr_load(int ctr, int val)
+{
+ outb_p(val&0xFF, WDT_COUNT0+ctr);
+ outb_p(val>>8, WDT_COUNT0+ctr);
+}
+
+/**
+ * wdtpci_start:
+ *
+ * Start the watchdog driver.
+ */
+
+static int wdtpci_start(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdtpci_lock, flags);
+
+ /*
+ * "pet" the watchdog, as Access says.
+ * This resets the clock outputs.
+ */
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+
+ inb_p(WDT_DC); /* Disable watchdog */
+ outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */
+ inb_p(WDT_BUZZER); /* disable */
+ inb_p(WDT_OPTONOTRST); /* disable */
+ inb_p(WDT_OPTORST); /* disable */
+ inb_p(WDT_PROGOUT); /* disable */
+ wdtpci_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */
+ wdtpci_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */
+ wdtpci_ctr_mode(2,1); /* Program CTR2 for Mode 1: Retriggerable One-Shot */
+ wdtpci_ctr_load(0,20833); /* count at 100Hz */
+ wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
+ /* DO NOT LOAD CTR2 on PCI card! -- JPN */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_stop:
+ *
+ * Stop the watchdog driver.
+ */
+
+static int wdtpci_stop (void)
+{
+ unsigned long flags;
+
+ /* Turn the card off */
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdtpci_ctr_load(2,0); /* 0 length reset pulses now */
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_ping:
+ *
+ * Reload counter one with the watchdog heartbeat. We don't bother reloading
+ * the cascade counter.
+ */
+
+static int wdtpci_ping(void)
+{
+ unsigned long flags;
+
+ /* Write a watchdog value */
+ spin_lock_irqsave(&wdtpci_lock, flags);
+ inb_p(WDT_DC); /* Disable watchdog */
+ wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */
+ wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
+ outb_p(0, WDT_DC); /* Enable watchdog */
+ spin_unlock_irqrestore(&wdtpci_lock, flags);
+ return 0;
+}
+
+/**
+ * wdtpci_set_heartbeat:
+ * @t: the new heartbeat value that needs to be set.
+ *
+ * Set a new heartbeat value for the watchdog device. If the heartbeat value is
+ * incorrect we keep the old value and return -EINVAL. If successfull we
+ * return 0.
+ */
+static int wdtpci_set_heartbeat(int t)
+{
+ /* Arbitrary, can't find the card's limits */
+ if ((t < 1) || (t > 65535))
+ return -EINVAL;
+
+ heartbeat = t;
+ wd_heartbeat = t * 100;
+ return 0;
+}
+
+/**
+ * wdtpci_get_status:
+ * @status: the new status.
+ *
+ * Extract the status information from a WDT watchdog device. There are
+ * several board variants so we have to know which bits are valid. Some
+ * bits default to one and some to zero in order to be maximally painful.
+ *
+ * we then map the bits onto the status ioctl flags.
+ */
+
+static int wdtpci_get_status(int *status)
+{
+ unsigned char new_status=inb_p(WDT_SR);
+
+ *status=0;
+ if (new_status & WDC_SR_ISOI0)
+ *status |= WDIOF_EXTERN1;
+ if (new_status & WDC_SR_ISII1)
+ *status |= WDIOF_EXTERN2;
+#ifdef CONFIG_WDT_501_PCI
+ if (!(new_status & WDC_SR_TGOOD))
+ *status |= WDIOF_OVERHEAT;
+ if (!(new_status & WDC_SR_PSUOVER))
+ *status |= WDIOF_POWEROVER;
+ if (!(new_status & WDC_SR_PSUUNDR))
+ *status |= WDIOF_POWERUNDER;
+ if (tachometer) {
+ if (!(new_status & WDC_SR_FANGOOD))
+ *status |= WDIOF_FANFAULT;
+ }
+#endif /* CONFIG_WDT_501_PCI */
+ return 0;
+}
+
+#ifdef CONFIG_WDT_501_PCI
+/**
+ * wdtpci_get_temperature:
+ *
+ * Reports the temperature in degrees Fahrenheit. The API is in
+ * farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdtpci_get_temperature(int *temperature)
+{
+ unsigned short c=inb_p(WDT_RT);
+
+ *temperature = (c * 11 / 15) + 7;
+ return 0;
+}
+#endif /* CONFIG_WDT_501_PCI */
+
+/**
+ * wdtpci_interrupt:
+ * @irq: Interrupt number
+ * @dev_id: Unused as we don't allow multiple devices.
+ * @regs: Unused.
+ *
+ * Handle an interrupt from the board. These are raised when the status
+ * map changes in what the board considers an interesting way. That means
+ * a failure condition occurring.
+ */
+
+static irqreturn_t wdtpci_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ /*
+ * Read the status register see what is up and
+ * then printk it.
+ */
+ unsigned char status=inb_p(WDT_SR);
+
+ printk(KERN_CRIT PFX "status %d\n", status);
+
+#ifdef CONFIG_WDT_501_PCI
+ if (!(status & WDC_SR_TGOOD))
+ printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT));
+ if (!(status & WDC_SR_PSUOVER))
+ printk(KERN_CRIT PFX "PSU over voltage.\n");
+ if (!(status & WDC_SR_PSUUNDR))
+ printk(KERN_CRIT PFX "PSU under voltage.\n");
+ if (tachometer) {
+ if (!(status & WDC_SR_FANGOOD))
+ printk(KERN_CRIT PFX "Possible fan fault.\n");
+ }
+#endif /* CONFIG_WDT_501_PCI */
+ if (!(status&WDC_SR_WCCR))
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+ printk(KERN_CRIT PFX "Would Reboot.\n");
+#else
+ printk(KERN_CRIT PFX "Initiating system reboot.\n");
+ machine_restart(NULL);
+#endif
+#else
+ printk(KERN_CRIT PFX "Reset in 5ms.\n");
+#endif
+ return IRQ_HANDLED;
+}
+
+
+/**
+ * wdtpci_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+ if (count) {
+ if (!nowayout) {
+ size_t i;
+
+ expect_close = 0;
+
+ for (i = 0; i != count; i++) {
+ char c;
+ if(get_user(c, buf+i))
+ return -EFAULT;
+ if (c == 'V')
+ expect_close = 42;
+ }
+ }
+ wdtpci_ping();
+ }
+
+ return count;
+}
+
+/**
+ * wdtpci_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features. We only actually usefully support
+ * querying capabilities and current status.
+ */
+
+static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int new_heartbeat;
+ int status;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT|
+ WDIOF_MAGICCLOSE|
+ WDIOF_KEEPALIVEPING,
+ .firmware_version = 1,
+ .identity = "PCI-WDT500/501",
+ };
+
+ /* Add options according to the card we have */
+ ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+#ifdef CONFIG_WDT_501_PCI
+ ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
+ if (tachometer)
+ ident.options |= WDIOF_FANFAULT;
+#endif /* CONFIG_WDT_501_PCI */
+
+ switch(cmd)
+ {
+ default:
+ return -ENOIOCTLCMD;
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+
+ case WDIOC_GETSTATUS:
+ wdtpci_get_status(&status);
+ return put_user(status, p);
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+ case WDIOC_KEEPALIVE:
+ wdtpci_ping();
+ return 0;
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_heartbeat, p))
+ return -EFAULT;
+
+ if (wdtpci_set_heartbeat(new_heartbeat))
+ return -EINVAL;
+
+ wdtpci_ping();
+ /* Fall */
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ }
+}
+
+/**
+ * wdtpci_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The watchdog device has been opened. The watchdog device is single
+ * open and on opening we load the counters. Counter zero is a 100Hz
+ * cascade, into counter 1 which downcounts to reboot. When the counter
+ * triggers counter 2 downcounts the length of the reset pulse which
+ * set set to be as long as possible.
+ */
+
+static int wdtpci_open(struct inode *inode, struct file *file)
+{
+ if (down_trylock(&open_sem))
+ return -EBUSY;
+
+ if (nowayout) {
+ __module_get(THIS_MODULE);
+ }
+ /*
+ * Activate
+ */
+ wdtpci_start();
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * wdtpci_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int wdtpci_release(struct inode *inode, struct file *file)
+{
+ if (expect_close == 42) {
+ wdtpci_stop();
+ } else {
+ printk(KERN_CRIT PFX "Unexpected close, not stopping timer!");
+ wdtpci_ping();
+ }
+ expect_close = 0;
+ up(&open_sem);
+ return 0;
+}
+
+#ifdef CONFIG_WDT_501_PCI
+/**
+ * wdtpci_temp_read:
+ * @file: file handle to the watchdog board
+ * @buf: buffer to write 1 byte into
+ * @count: length of buffer
+ * @ptr: offset (no seek allowed)
+ *
+ * Read reports the temperature in degrees Fahrenheit. The API is in
+ * fahrenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+ int temperature;
+
+ if (wdtpci_get_temperature(&temperature))
+ return -EFAULT;
+
+ if (copy_to_user (buf, &temperature, 1))
+ return -EFAULT;
+
+ return 1;
+}
+
+/**
+ * wdtpci_temp_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The temperature device has been opened.
+ */
+
+static int wdtpci_temp_open(struct inode *inode, struct file *file)
+{
+ return nonseekable_open(inode, file);
+}
+
+/**
+ * wdtpci_temp_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The temperature device has been closed.
+ */
+
+static int wdtpci_temp_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+#endif /* CONFIG_WDT_501_PCI */
+
+/**
+ * notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code,
+ void *unused)
+{
+ if (code==SYS_DOWN || code==SYS_HALT) {
+ /* Turn the card off */
+ wdtpci_stop();
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static struct file_operations wdtpci_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wdtpci_write,
+ .ioctl = wdtpci_ioctl,
+ .open = wdtpci_open,
+ .release = wdtpci_release,
+};
+
+static struct miscdevice wdtpci_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wdtpci_fops,
+};
+
+#ifdef CONFIG_WDT_501_PCI
+static struct file_operations wdtpci_temp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = wdtpci_temp_read,
+ .open = wdtpci_temp_open,
+ .release = wdtpci_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+ .minor = TEMP_MINOR,
+ .name = "temperature",
+ .fops = &wdtpci_temp_fops,
+};
+#endif /* CONFIG_WDT_501_PCI */
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block wdtpci_notifier = {
+ .notifier_call = wdtpci_notify_sys,
+};
+
+
+static int __devinit wdtpci_init_one (struct pci_dev *dev,
+ const struct pci_device_id *ent)
+{
+ int ret = -EIO;
+
+ dev_count++;
+ if (dev_count > 1) {
+ printk (KERN_ERR PFX "this driver only supports 1 device\n");
+ return -ENODEV;
+ }
+
+ if (pci_enable_device (dev)) {
+ printk (KERN_ERR PFX "Not possible to enable PCI Device\n");
+ return -ENODEV;
+ }
+
+ if (pci_resource_start (dev, 2) == 0x0000) {
+ printk (KERN_ERR PFX "No I/O-Address for card detected\n");
+ ret = -ENODEV;
+ goto out_pci;
+ }
+
+ sema_init(&open_sem, 1);
+ spin_lock_init(&wdtpci_lock);
+
+ irq = dev->irq;
+ io = pci_resource_start (dev, 2);
+
+ if (request_region (io, 16, "wdt_pci") == NULL) {
+ printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io);
+ goto out_pci;
+ }
+
+ if (request_irq (irq, wdtpci_interrupt, SA_INTERRUPT | SA_SHIRQ,
+ "wdt_pci", &wdtpci_miscdev)) {
+ printk (KERN_ERR PFX "IRQ %d is not free\n", irq);
+ goto out_reg;
+ }
+
+ printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n",
+ io, irq);
+
+ /* Check that the heartbeat value is within it's range ; if not reset to the default */
+ if (wdtpci_set_heartbeat(heartbeat)) {
+ wdtpci_set_heartbeat(WD_TIMO);
+ printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+ WD_TIMO);
+ }
+
+ ret = register_reboot_notifier (&wdtpci_notifier);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret);
+ goto out_irq;
+ }
+
+#ifdef CONFIG_WDT_501_PCI
+ ret = misc_register (&temp_miscdev);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ TEMP_MINOR, ret);
+ goto out_rbt;
+ }
+#endif /* CONFIG_WDT_501_PCI */
+
+ ret = misc_register (&wdtpci_miscdev);
+ if (ret) {
+ printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto out_misc;
+ }
+
+ printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+ heartbeat, nowayout);
+#ifdef CONFIG_WDT_501_PCI
+ printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
+#endif /* CONFIG_WDT_501_PCI */
+
+ ret = 0;
+out:
+ return ret;
+
+out_misc:
+#ifdef CONFIG_WDT_501_PCI
+ misc_deregister(&temp_miscdev);
+out_rbt:
+#endif /* CONFIG_WDT_501_PCI */
+ unregister_reboot_notifier(&wdtpci_notifier);
+out_irq:
+ free_irq(irq, &wdtpci_miscdev);
+out_reg:
+ release_region (io, 16);
+out_pci:
+ pci_disable_device(dev);
+ goto out;
+}
+
+
+static void __devexit wdtpci_remove_one (struct pci_dev *pdev)
+{
+ /* here we assume only one device will ever have
+ * been picked up and registered by probe function */
+ misc_deregister(&wdtpci_miscdev);
+#ifdef CONFIG_WDT_501_PCI
+ misc_deregister(&temp_miscdev);
+#endif /* CONFIG_WDT_501_PCI */
+ unregister_reboot_notifier(&wdtpci_notifier);
+ free_irq(irq, &wdtpci_miscdev);
+ release_region(io, 16);
+ pci_disable_device(pdev);
+ dev_count--;
+}
+
+
+static struct pci_device_id wdtpci_pci_tbl[] = {
+ {
+ .vendor = PCI_VENDOR_ID_ACCESSIO,
+ .device = PCI_DEVICE_ID_WDG_CSM,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl);
+
+
+static struct pci_driver wdtpci_driver = {
+ .name = "wdt_pci",
+ .id_table = wdtpci_pci_tbl,
+ .probe = wdtpci_init_one,
+ .remove = __devexit_p(wdtpci_remove_one),
+};
+
+
+/**
+ * wdtpci_cleanup:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in xx seconds or reboot.
+ */
+
+static void __exit wdtpci_cleanup(void)
+{
+ pci_unregister_driver (&wdtpci_driver);
+}
+
+
+/**
+ * wdtpci_init:
+ *
+ * Set up the WDT watchdog board. All we have to do is grab the
+ * resources we require and bitch if anyone beat us to them.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init wdtpci_init(void)
+{
+ return pci_register_driver (&wdtpci_driver);
+}
+
+
+module_init(wdtpci_init);
+module_exit(wdtpci_cleanup);
+
+MODULE_AUTHOR("JP Nollmann, Alan Cox");
+MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);